Skip to content

Commit 16b8d56

Browse files
author
automatic-merge
committed
Merge remote branch 'origin/master' into edge
2 parents 6fa698f + 4880e80 commit 16b8d56

File tree

4 files changed

+253
-13
lines changed

4 files changed

+253
-13
lines changed

Diff for: integration/vscode/ada/src/ExtensionState.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ export class ExtensionState {
190190
* diagnostics.
191191
*/
192192
public updateStatusBarItem = () => {
193+
// Set the status bar item's default text ('Ada & SPARK')
194+
this.statusBar.text = 'Ada & SPARK';
195+
193196
// Use markdown for the status bar item tiooltip. This allows to have
194197
// hyperlinks that run actual commands.
195198
this.statusBar.tooltip = new vscode.MarkdownString('', true);
@@ -220,7 +223,6 @@ export class ExtensionState {
220223
const statusBarSeverity: vscode.DiagnosticSeverity = alsDiagnostics
221224
.map((a) => a.severity)
222225
.reduce((a, b) => (a < b ? a : b));
223-
this.statusBar.text = 'Ada & SPARK';
224226

225227
switch (statusBarSeverity) {
226228
case vscode.DiagnosticSeverity.Error:

Diff for: integration/vscode/ada/src/gnatcov.ts

+119-12
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import assert from 'assert';
12
import { X2jOptions, XMLParser } from 'fast-xml-parser';
23
import * as fs from 'fs';
4+
import { cpus } from 'os';
35
import * as path from 'path';
46
import * as vscode from 'vscode';
57
import { CancellationToken } from 'vscode-languageclient';
6-
import { parallelize, staggerProgress } from './helpers';
7-
import { cpus } from 'os';
8-
import { assert } from 'console';
8+
import { adaExtState } from './extension';
9+
import { getMatchingPrefixes, parallelize, staggerProgress, toPosix } from './helpers';
910

1011
/**
1112
* TypeScript types to represent data from GNATcoverage XML reports
@@ -299,25 +300,109 @@ export async function addCoverageData(run: vscode.TestRun, covDir: string) {
299300
progress.report({
300301
message: `${done} / ${totalFiles} source files`,
301302
});
303+
304+
let posixForeignPrefix: string | undefined;
305+
let posixLocalPrefix: string | undefined;
306+
307+
const procs = process.env.PROCESSORS ? Number(process.env.PROCESSORS) : 0;
302308
const fileCovs = (
303309
await parallelize(
304310
array,
305-
Math.min(cpus().length, 8),
311+
Math.min(procs == 0 ? cpus().length : procs, 8),
306312
async (file) => {
307313
if (token.isCancellationRequested) {
308314
throw new vscode.CancellationError();
309315
}
310316

311317
assert(file['@_name']);
318+
const foreignPath = file['@_name'];
319+
/**
320+
* The foreign machine may have a different path
321+
* format, so we normalize to POSIX which is valid on
322+
* both Windows and POSIX OS-es.
323+
*/
324+
const posixForeignPath = toPosix(foreignPath);
312325

313-
let srcUri: vscode.Uri;
314-
if (path.isAbsolute(file['@_name']!)) {
315-
srcUri = vscode.Uri.file(file['@_name']!);
316-
} else {
326+
let srcUri: vscode.Uri | undefined = undefined;
327+
328+
/**
329+
* The goal here is to find the file in the workspace
330+
* corresponding to the name attribute in the GNATcov
331+
* report.
332+
*
333+
* The name could be a basename (older GNATcov
334+
* versions) or an absolute path (newer GNATcov
335+
* versions).
336+
*
337+
* In the case of a basename, the only course of action
338+
* is to do a file lookup in the workspace.
339+
*
340+
* In the case of an absolute path, the path
341+
* corresponds to the machine where the report was
342+
* created which might be a foreign machine or the
343+
* local host. We can't know in which situation we are
344+
* so it's best to assume that it's a foreign machine.
345+
*
346+
* Then the logic consists of searching for the first
347+
* file by basename, which gives a local absolute path.
348+
* Then by comparing the local absolute path and the
349+
* foreign absolute path, we can find a foreign prefix
350+
* path that should be mapped to the local prefix path
351+
* to compute local absolute paths. Subsequent files
352+
* can use the computed prefixes directly without a
353+
* workspace lookup.
354+
*/
355+
356+
if (path.posix.isAbsolute(posixForeignPath)) {
357+
let localFullPath;
358+
if (posixLocalPrefix && posixForeignPrefix) {
359+
/**
360+
* The prefixes have already been determined, so
361+
* use them directly.
362+
*/
363+
364+
if (posixForeignPath.startsWith(posixForeignPrefix)) {
365+
// Extract the relative path based on the foreign prefix
366+
const posixForeignRelPath = path.relative(
367+
posixForeignPrefix,
368+
posixForeignPath,
369+
);
370+
371+
// Resolve the relative path with the local prefix
372+
localFullPath = path.join(
373+
posixLocalPrefix,
374+
posixForeignRelPath,
375+
);
376+
}
377+
}
378+
379+
// Fallback to using the input path as is
380+
localFullPath = localFullPath ?? foreignPath;
381+
382+
if (fs.existsSync(localFullPath)) {
383+
srcUri = vscode.Uri.file(localFullPath);
384+
}
385+
}
386+
387+
if (srcUri === undefined) {
388+
/**
389+
* If the prefixes haven't been found yet, or
390+
* the last prefixes used were not successful,
391+
* try a workspace lookup of the basename.
392+
*/
317393
const found = await vscode.workspace.findFiles(
318-
`**/${file['@_name']!}`,
319-
null,
394+
`**/${path.posix.basename(posixForeignPath)}`,
395+
/**
396+
* Avoid searching in the object dir because we
397+
* might land on gnatcov-instrumented versions
398+
* of the sources.
399+
*/
400+
`${await adaExtState
401+
.getObjectDir()
402+
.then((objDir) => `${objDir}/**/*`)
403+
.catch(() => null)}`,
320404
1,
405+
token,
321406
);
322407
if (found.length == 0) {
323408
return undefined;
@@ -326,6 +411,27 @@ export async function addCoverageData(run: vscode.TestRun, covDir: string) {
326411
srcUri = found[0];
327412
}
328413

414+
if (
415+
posixForeignPrefix === undefined &&
416+
posixLocalPrefix === undefined &&
417+
path.posix.isAbsolute(posixForeignPath)
418+
) {
419+
/**
420+
* If the prefixes haven't been calculated, and the
421+
* foreign path is absolute, let's try to compute
422+
* the prefixes based on the workspace URI that was
423+
* found.
424+
*/
425+
426+
const localAbsPath = srcUri.fsPath;
427+
const posixLocalAbsPath = toPosix(localAbsPath);
428+
429+
[posixForeignPrefix, posixLocalPrefix] = getMatchingPrefixes(
430+
posixForeignPath,
431+
posixLocalAbsPath,
432+
);
433+
}
434+
329435
const total = file.metric.find(
330436
(m) => m['@_kind'] == 'total_lines_of_relevance',
331437
)!['@_count'];
@@ -334,14 +440,15 @@ export async function addCoverageData(run: vscode.TestRun, covDir: string) {
334440
];
335441

336442
const fileReportBasename = data.coverage_report.sources!['xi:include'].find(
337-
(inc) => inc['@_href'] == `${path.basename(file['@_name']!)}.xml`,
443+
(inc) =>
444+
inc['@_href'] == `${path.posix.basename(posixForeignPath)}.xml`,
338445
)!['@_href'];
339446
const fileReportPath = path.join(covDir, fileReportBasename);
340447

341448
if (covered > total) {
342449
throw Error(
343450
`Got ${covered} covered lines for a` +
344-
` total of ${total} in ${file['@_name']!}`,
451+
` total of ${total} in ${file['@_name']}`,
345452
);
346453
}
347454

Diff for: integration/vscode/ada/src/helpers.ts

+68
Original file line numberDiff line numberDiff line change
@@ -644,3 +644,71 @@ export function staggerProgress(
644644
export function slugify(arg: string): string {
645645
return arg.replace(/[<>:"/\\|?*]/g, '_');
646646
}
647+
/**
648+
* @returns the length of the common suffix between the given strings. If there
649+
* is no common suffix, the result is `0`.
650+
*/
651+
export function getLengthCommonSuffix(str1: string, str2: string): number {
652+
/**
653+
* Compare characters from the end of each string and iterating backwards.
654+
*/
655+
let len = 0;
656+
while (
657+
len < str1.length &&
658+
len < str2.length &&
659+
str1[str1.length - 1 - len] == str2[str2.length - 1 - len]
660+
) {
661+
len++;
662+
}
663+
664+
return len;
665+
}
666+
667+
/**
668+
*
669+
* @param p - a path
670+
* @returns a POSIX version of the same path obtained by replacing occurences
671+
* of `\` with `/`. If the input path was a Windows absolute path, a `/` is
672+
* prepended to the output to make it also an absolute path.
673+
*/
674+
export function toPosix(p: string) {
675+
let posixPath = p.replace(RegExp(`\\${path.win32.sep}`, 'g'), path.posix.sep);
676+
677+
/**
678+
* If it was an absolute path from Windows, we have to
679+
* manually make it a POSIX absolute path.
680+
*/
681+
if (path.win32.isAbsolute(p) && !path.posix.isAbsolute(posixPath)) {
682+
posixPath = `/${posixPath}`;
683+
}
684+
685+
return posixPath;
686+
}
687+
688+
/**
689+
*
690+
* @param path1 - a path
691+
* @param path2 - another path
692+
* @returns if the given paths end with a common suffix, the remaining prefixes
693+
* are returned as a tuple by this function. Otherwise, undefined prefixes are
694+
* returned.
695+
*/
696+
export function getMatchingPrefixes(
697+
path1: string,
698+
path2: string,
699+
): [string | undefined, string | undefined] {
700+
const lenCommonSuffix = getLengthCommonSuffix(path1, path2);
701+
702+
let prefix1;
703+
let prefix2;
704+
705+
if (lenCommonSuffix > 0) {
706+
prefix1 = path1.slice(0, path1.length - lenCommonSuffix);
707+
prefix2 = path2.slice(0, path2.length - lenCommonSuffix);
708+
} else {
709+
// Could not find a common suffix
710+
prefix1 = prefix2 = undefined;
711+
}
712+
713+
return [prefix1, prefix2];
714+
}

Diff for: integration/vscode/ada/test/general/helpers.test.ts

+63
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import assert from 'assert';
22
import {
33
envHasExec,
44
findAdaMain,
5+
getLengthCommonSuffix,
6+
getMatchingPrefixes,
57
getSymbols,
68
parallelize,
79
slugify,
810
staggerProgress,
11+
toPosix,
912
which,
1013
} from '../../src/helpers';
1114
import {
@@ -350,3 +353,63 @@ suite('staggerProgress', function () {
350353
assert.equal(slugify('file<>:"/\\|?*name'), 'file' + '_'.repeat(9) + 'name');
351354
});
352355
});
356+
357+
suite('getLengthCommonSuffix', function () {
358+
test('empty', function () {
359+
assert.equal(getLengthCommonSuffix('', ''), 0);
360+
});
361+
362+
test('one char', function () {
363+
assert.equal(getLengthCommonSuffix('a', ''), 0);
364+
assert.equal(getLengthCommonSuffix('', 'b'), 0);
365+
assert.equal(getLengthCommonSuffix('a', 'a'), 1);
366+
assert.equal(getLengthCommonSuffix('a', 'b'), 0);
367+
});
368+
test('two char', function () {
369+
assert.equal(getLengthCommonSuffix('ab', ''), 0);
370+
assert.equal(getLengthCommonSuffix('', 'ab'), 0);
371+
assert.equal(getLengthCommonSuffix('ab', 'a'), 0);
372+
assert.equal(getLengthCommonSuffix('ab', 'b'), 1);
373+
assert.equal(getLengthCommonSuffix('ab', 'ab'), 2);
374+
assert.equal(getLengthCommonSuffix('ab', 'cd'), 0);
375+
});
376+
test('more', function () {
377+
assert.equal(getLengthCommonSuffix('abcdefg', ''), 0);
378+
assert.equal(getLengthCommonSuffix('abcdefg', 'g'), 1);
379+
assert.equal(getLengthCommonSuffix('abcdefg', 'x'), 0);
380+
assert.equal(getLengthCommonSuffix('abcdefg', 'fg'), 2);
381+
assert.equal(getLengthCommonSuffix('abcdefg', 'abcdefg'), 7);
382+
});
383+
});
384+
385+
suite('toPosix', function () {
386+
test('Windows paths', function () {
387+
assert.equal(toPosix('C:\\some\\path'), '/C:/some/path');
388+
assert.equal(toPosix('C:\\some\\path\\'), '/C:/some/path/');
389+
assert.equal(toPosix(''), '');
390+
assert.equal(toPosix('C:\\'), '/C:/');
391+
assert.equal(toPosix('c:\\'), '/c:/');
392+
assert.equal(toPosix('a\\relative\\path'), 'a/relative/path');
393+
});
394+
test('POSIX paths', function () {
395+
assert.equal(toPosix('/'), '/');
396+
assert.equal(toPosix('/some/path'), '/some/path');
397+
assert.equal(toPosix('/some/path/'), '/some/path/');
398+
assert.equal(toPosix('a/relative/path'), 'a/relative/path');
399+
});
400+
});
401+
402+
suite('getMatchingPrefixes', function () {
403+
test('cases', function () {
404+
assert.deepStrictEqual(
405+
getMatchingPrefixes('/C:/a/b/common/part.adb', '/other/path/common/part.adb'),
406+
['/C:/a/b', '/other/path'],
407+
);
408+
assert.deepStrictEqual(
409+
getMatchingPrefixes('/C:/a/b/d/e', '/other/path/with/no/common/part'),
410+
[undefined, undefined],
411+
);
412+
assert.deepStrictEqual(getMatchingPrefixes('', ''), [undefined, undefined]);
413+
assert.deepStrictEqual(getMatchingPrefixes('a', 'a'), ['', '']);
414+
});
415+
});

0 commit comments

Comments
 (0)