Skip to content

Commit cff7494

Browse files
committed
Add command to load an existing GNATcoverage XML report
1 parent 7546fc6 commit cff7494

File tree

4 files changed

+120
-18
lines changed

4 files changed

+120
-18
lines changed

integration/vscode/ada/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,10 @@
850850
"command": "ada.spark.tasks.proveLine",
851851
"title": "spark: Prove line (task wrapper)",
852852
"when": "editorLangId == ada && editorTextFocus"
853+
},
854+
{
855+
"command": "ada.loadGnatCovXMLReport",
856+
"title": "ada: GNATcoverage - Load an existing XML coverage report"
853857
}
854858
],
855859
"keybindings": [

integration/vscode/ada/src/commands.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
getBuildAndRunTaskName,
2626
} from './taskProviders';
2727
import { createHelloWorldProject, walkthroughStartDebugging } from './walkthrough';
28+
import { loadGnatCoverageReport } from './gnattest';
2829

2930
/**
3031
* Identifier for a hidden command used for building and running a project main.
@@ -171,6 +172,10 @@ export function registerCommands(context: vscode.ExtensionContext, clients: Exte
171172
vscode.commands.registerCommand(CMD_SPARK_PROVE_SUBP, sparkProveSubprogram),
172173
);
173174

175+
context.subscriptions.push(
176+
commands.registerCommand('ada.loadGnatCovXMLReport', loadGnatCovXMLReport),
177+
);
178+
174179
registerTaskWrappers(context);
175180
}
176181

@@ -936,3 +941,24 @@ async function sparkProveSubprogram(
936941
*/
937942
return await vscode.tasks.executeTask(resolvedTask);
938943
}
944+
async function loadGnatCovXMLReport() {
945+
const selection = await vscode.window.showOpenDialog({
946+
canSelectFiles: true,
947+
canSelectFolders: false,
948+
canSelectMany: false,
949+
filters: {
950+
'index.xml': ['xml'],
951+
},
952+
title: "Select a 'index.xml' GNATcoverage report to load",
953+
});
954+
955+
if (selection && selection.length > 0 && selection[0]) {
956+
const path = selection[0].fsPath;
957+
if (!path.endsWith('index.xml')) {
958+
throw Error(
959+
`The selected file must be 'index.xml'. Instead, the selected file was: ${path}`,
960+
);
961+
}
962+
await loadGnatCoverageReport(path);
963+
}
964+
}

integration/vscode/ada/src/gnatcov.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,12 @@ type scope_metric_type = {
7070
};
7171
type metric_type = {
7272
'@_kind': metric_kind_type;
73-
'@_count': number;
73+
74+
/**
75+
* should be 'number' but we don't want to ask the XML parser to parse
76+
* numbers
77+
*/
78+
'@_count': string;
7479
'@_ratio': number;
7580
};
7681
type metric_kind_type =

integration/vscode/ada/src/gnattest.ts

Lines changed: 84 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ export let controller: vscode.TestController;
2626
export let testRunProfile: vscode.TestRunProfile;
2727
export 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

Comments
 (0)