@@ -26,6 +26,12 @@ export let controller: vscode.TestController;
2626export let testRunProfile : vscode . TestRunProfile ;
2727export let testCoverageProfile : vscode . TestRunProfile ;
2828
29+ /**
30+ * This test controller is not used for actually running tests. It is merely
31+ * used for loading an existing GNATcoverage report obtained outside VS Code.
32+ */
33+ let fileLoadController : vscode . TestController ;
34+
2935/**
3036 * Types definition for the Gnattest XML file structure. The types match the XML
3137 * generated by src/test-mapping.adb in the libadalang-tools repository. However
@@ -131,6 +137,15 @@ export function initializeTesting(context: vscode.ExtensionContext): vscode.Test
131137
132138 configureTestExecution ( controller ) ;
133139
140+ /**
141+ * Initialize the controller responsible for loading existing GNATcov
142+ * reports on demand.
143+ */
144+ fileLoadController = vscode . tests . createTestController (
145+ 'gnatcoverage-report-loader' ,
146+ 'GNATcoverage Report Loader' ,
147+ ) ;
148+
134149 return controller ;
135150}
136151
@@ -1098,13 +1113,13 @@ class GnatcovFileCoverage extends vscode.FileCoverage {
10981113 /**
10991114 * Report detailed coverage information by loading the file's GNATcov XML report.
11001115 */
1101- public async load ( token : CancellationToken ) : Promise < vscode . FileCoverageDetail [ ] > {
1116+ public async load ( token ? : CancellationToken ) : Promise < vscode . FileCoverageDetail [ ] > {
11021117 const data = parseGnatcovFileXml ( this . sourceFileXmlReport ) ;
11031118 return Promise . resolve (
11041119 data . src_mapping . flatMap ( ( src_mapping ) =>
11051120 src_mapping . src . line
11061121 . map ( ( line ) => {
1107- if ( token . isCancellationRequested ) {
1122+ if ( token ? .isCancellationRequested ) {
11081123 throw new vscode . CancellationError ( ) ;
11091124 }
11101125
@@ -1173,24 +1188,54 @@ async function addCoverageData(run: vscode.TestRun, covDir: string) {
11731188 const indexPath = path . join ( covDir , 'index.xml' ) ;
11741189 const data = parseGnatcovIndexXml ( indexPath ) ;
11751190
1176- for ( const file of data . coverage_report . coverage_summary ! . file ) {
1177- const found = await vscode . workspace . findFiles ( `**/${ file [ '@_name' ] ! } ` , null , 1 ) ;
1178- if ( found . length == 0 ) continue ;
1191+ await vscode . window . withProgress (
1192+ {
1193+ cancellable : true ,
1194+ location : vscode . ProgressLocation . Notification ,
1195+ title : 'Loading GNATcoverage report' ,
1196+ } ,
1197+ async ( progress , token ) => {
1198+ const array = data . coverage_report . coverage_summary ! . file ;
1199+ let done : number = 0 ;
1200+ const totalFiles = array . length ;
1201+ for ( const file of array ) {
1202+ if ( token . isCancellationRequested ) {
1203+ throw new vscode . CancellationError ( ) ;
1204+ }
11791205
1180- const srcUri = found [ 0 ] ;
1181- const total = file . metric . find ( ( m ) => m [ '@_kind' ] == 'total_lines_of_relevance' ) ! [
1182- '@_count'
1183- ] ;
1184- const covered = file . metric . find ( ( m ) => m [ '@_kind' ] == 'fully_covered' ) ! [ '@_count' ] ;
1206+ const found = await vscode . workspace . findFiles ( `**/${ file [ '@_name' ] ! } ` , null , 1 ) ;
1207+ if ( found . length == 0 ) continue ;
11851208
1186- const fileReportBasename = data . coverage_report . sources ! [ 'xi:include' ] . find (
1187- ( inc ) => inc [ '@_href' ] == `${ file [ '@_name' ] ! } .xml` ,
1188- ) ! [ '@_href' ] ;
1189- const fileReportPath = path . join ( covDir , fileReportBasename ) ;
1209+ const srcUri = found [ 0 ] ;
1210+ const total = Number . parseInt (
1211+ file . metric . find ( ( m ) => m [ '@_kind' ] == 'total_lines_of_relevance' ) ! [ '@_count' ] ,
1212+ ) ;
1213+ const covered = Number . parseInt (
1214+ file . metric . find ( ( m ) => m [ '@_kind' ] == 'fully_covered' ) ! [ '@_count' ] ,
1215+ ) ;
11901216
1191- const fileCov = new GnatcovFileCoverage ( fileReportPath , srcUri , { covered, total } ) ;
1192- run . addCoverage ( fileCov ) ;
1193- }
1217+ const fileReportBasename = data . coverage_report . sources ! [ 'xi:include' ] . find (
1218+ ( inc ) => inc [ '@_href' ] == `${ file [ '@_name' ] ! } .xml` ,
1219+ ) ! [ '@_href' ] ;
1220+ const fileReportPath = path . join ( covDir , fileReportBasename ) ;
1221+
1222+ if ( covered > total ) {
1223+ throw Error (
1224+ `Got ${ covered } covered lines for a` +
1225+ ` total of ${ total } in ${ file [ '@_name' ] ! } ` ,
1226+ ) ;
1227+ }
1228+
1229+ const fileCov = new GnatcovFileCoverage ( fileReportPath , srcUri , { covered, total } ) ;
1230+ run . addCoverage ( fileCov ) ;
1231+
1232+ progress . report ( {
1233+ message : `${ ++ done } / ${ totalFiles } source files` ,
1234+ increment : ( 100 * 1 ) / totalFiles ,
1235+ } ) ;
1236+ }
1237+ } ,
1238+ ) ;
11941239}
11951240
11961241/**
@@ -1217,3 +1262,25 @@ async function loadDetailedCoverage(
12171262
12181263 return [ ] ;
12191264}
1265+
1266+ /**
1267+ * Load an external GNATcoverage XML report in the VS Code UI.
1268+ *
1269+ * @param indexXmlPath - path of the index.xml file of a GNATcoverage XML report
1270+ */
1271+ export async function loadGnatCoverageReport ( indexXmlPath : string ) {
1272+ const request = new vscode . TestRunRequest (
1273+ undefined ,
1274+ undefined ,
1275+ /**
1276+ * Associate the request with the coverage profile so that it becomes
1277+ * possible to load detailed coverage data for each file.
1278+ */
1279+ testCoverageProfile ,
1280+ ) ;
1281+ const run = fileLoadController . createTestRun ( request , path . basename ( indexXmlPath ) , false ) ;
1282+
1283+ const covDir = path . dirname ( indexXmlPath ) ;
1284+ await addCoverageData ( run , covDir ) ;
1285+ run . end ( ) ;
1286+ }
0 commit comments