Skip to content

Commit b61d6d9

Browse files
committed
Move GNATcoverage-specific logic to gnatcov.ts
1 parent 5c72681 commit b61d6d9

File tree

2 files changed

+194
-169
lines changed

2 files changed

+194
-169
lines changed

integration/vscode/ada/src/gnatcov.ts

Lines changed: 181 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { X2jOptions, XMLParser } from 'fast-xml-parser';
2-
import { number } from 'fp-ts';
32
import * as fs from 'fs';
3+
import * as path from 'path';
4+
import * as vscode from 'vscode';
5+
import { CancellationToken } from 'vscode-languageclient';
46

57
/**
68
* TypeScript types to represent data from GNATcoverage XML reports
@@ -56,7 +58,7 @@ type xi_include_type = {
5658
'@_href': string;
5759
};
5860

59-
type source_type = {
61+
export type source_type = {
6062
'@_file': string;
6163
'@_coverage_level': coverage_level_type;
6264
scope_metric: scope_metric_type[];
@@ -94,7 +96,7 @@ type obligation_stats_type = {
9496
metric: metric_type[];
9597
'@_kind': string;
9698
};
97-
type src_mapping_type = {
99+
export type src_mapping_type = {
98100
src: src_type;
99101
statement: statement_type[];
100102
decision: decision_type[];
@@ -104,7 +106,7 @@ type src_mapping_type = {
104106
type src_type = {
105107
line: line_type[];
106108
};
107-
type line_type = {
109+
export type line_type = {
108110
'@_num': number;
109111
'@_src': string;
110112
'@_column_begin': number;
@@ -142,7 +144,20 @@ type statement_type = {
142144
* https://docs.adacore.com/gnatcoverage-docs/html/gnatcov/exemptions.html#reporting-about-coverage-exemptions
143145
* for more information.
144146
*/
145-
type coverage_type = '.' | '+' | '-' | '!' | '?' | '#' | '@' | '*' | '0' | 'v' | '>';
147+
export const coverage_type_values = [
148+
'.',
149+
'+',
150+
'-',
151+
'!',
152+
'?',
153+
'#',
154+
'@',
155+
'*',
156+
'0',
157+
'v',
158+
'>',
159+
] as const;
160+
export type coverage_type = (typeof coverage_type_values)[number];
146161

147162
type decision_type = {
148163
src?: src_type;
@@ -256,3 +271,164 @@ export function parseGnatcovFileXml(path: string): source_type {
256271
throw Error(`Could not parse GNATcoverage report: ${path}`);
257272
}
258273
}
274+
275+
/**
276+
*
277+
* @param run - the test run to add coverage data to
278+
* @param covDir - The path to the directory containing GNATcoverage XML
279+
* reports. The directory must contain a single 'index.xml' file.
280+
*/
281+
export async function addCoverageData(run: vscode.TestRun, covDir: string) {
282+
const indexPath = path.join(covDir, 'index.xml');
283+
const data = parseGnatcovIndexXml(indexPath);
284+
285+
await vscode.window.withProgress(
286+
{
287+
cancellable: true,
288+
location: vscode.ProgressLocation.Notification,
289+
title: 'Loading GNATcoverage report',
290+
},
291+
async (progress, token) => {
292+
const array = data.coverage_report.coverage_summary!.file;
293+
let done: number = 0;
294+
const totalFiles = array.length;
295+
for (const file of array) {
296+
if (token.isCancellationRequested) {
297+
throw new vscode.CancellationError();
298+
}
299+
300+
const found = await vscode.workspace.findFiles(`**/${file['@_name']!}`, null, 1);
301+
if (found.length == 0) continue;
302+
303+
const srcUri = found[0];
304+
const total = file.metric.find((m) => m['@_kind'] == 'total_lines_of_relevance')![
305+
'@_count'
306+
];
307+
const covered = file.metric.find((m) => m['@_kind'] == 'fully_covered')!['@_count'];
308+
309+
const fileReportBasename = data.coverage_report.sources!['xi:include'].find(
310+
(inc) => inc['@_href'] == `${file['@_name']!}.xml`,
311+
)!['@_href'];
312+
const fileReportPath = path.join(covDir, fileReportBasename);
313+
314+
if (covered > total) {
315+
throw Error(
316+
`Got ${covered} covered lines for a` +
317+
` total of ${total} in ${file['@_name']!}`,
318+
);
319+
}
320+
321+
const fileCov = new GnatcovFileCoverage(fileReportPath, srcUri, { covered, total });
322+
run.addCoverage(fileCov);
323+
324+
progress.report({
325+
message: `${++done} / ${totalFiles} source files`,
326+
increment: (100 * 1) / totalFiles,
327+
});
328+
}
329+
},
330+
);
331+
}
332+
/**
333+
* A class that holds the summary coverage data of a source file, and provides
334+
* a method to load detailed coverage data about that source file.
335+
*/
336+
export class GnatcovFileCoverage extends vscode.FileCoverage {
337+
sourceFileXmlReport: string;
338+
339+
/**
340+
*
341+
* @param detailedXmlReportPath - The path to the GNATcov XML report for the given source file.
342+
* @param uri - URI of the source file
343+
* @param statementCoverage - statement coverage information
344+
* @param branchCoverage - branch coverage information, if available
345+
* @param declarationCoverage - declaration coverage information, if available
346+
*/
347+
constructor(
348+
detailedXmlReportPath: string,
349+
uri: vscode.Uri,
350+
statementCoverage: vscode.TestCoverageCount,
351+
branchCoverage?: vscode.TestCoverageCount,
352+
declarationCoverage?: vscode.TestCoverageCount,
353+
) {
354+
super(uri, statementCoverage, branchCoverage, declarationCoverage);
355+
this.sourceFileXmlReport = detailedXmlReportPath;
356+
}
357+
358+
/**
359+
* Report detailed coverage information by loading the file's GNATcov XML report.
360+
*/
361+
public async load(token?: CancellationToken): Promise<vscode.FileCoverageDetail[]> {
362+
const data = parseGnatcovFileXml(this.sourceFileXmlReport);
363+
return Promise.resolve(convertSourceReport(data, token));
364+
}
365+
}
366+
367+
export function convertSourceReport(
368+
data: source_type,
369+
token?: CancellationToken,
370+
): vscode.StatementCoverage[] {
371+
return data.src_mapping.flatMap((src_mapping) => convertSrcMapping(src_mapping, token));
372+
}
373+
374+
export function convertSrcMapping(
375+
src_mapping: src_mapping_type,
376+
token?: CancellationToken,
377+
): vscode.StatementCoverage[] {
378+
return src_mapping.src.line
379+
.map((line) => {
380+
if (token?.isCancellationRequested) {
381+
throw new vscode.CancellationError();
382+
}
383+
384+
const zeroBasedLine = line['@_num'] - 1;
385+
const lineLength = line['@_src'].length;
386+
const res = new vscode.StatementCoverage(
387+
false,
388+
new vscode.Range(zeroBasedLine, 0, zeroBasedLine, lineLength),
389+
);
390+
391+
switch (src_mapping['@_coverage']) {
392+
case '-':
393+
case '!':
394+
case '?':
395+
case '+':
396+
/**
397+
* Lines with coverage obligations are highlighted
398+
* as covered iff they are marked as '+'
399+
*/
400+
res.executed = src_mapping['@_coverage'] == '+';
401+
return res;
402+
403+
case '.':
404+
/**
405+
* Lines with no coverage obligations are not highlighted
406+
*/
407+
return undefined;
408+
409+
case '#':
410+
case '@':
411+
case '*':
412+
/**
413+
* Exempted lines are not highlighted
414+
*/
415+
return undefined;
416+
417+
case 'v':
418+
case '>':
419+
/**
420+
* Symbols are for object level coverage which
421+
* is not supported in VS Code.
422+
*/
423+
return undefined;
424+
425+
case '0':
426+
/**
427+
* Symbol specific to binary traces which are
428+
* not used in the VS Code integration.
429+
*/
430+
return undefined;
431+
}
432+
})
433+
.filter((v) => !!v);
434+
}

0 commit comments

Comments
 (0)