1
+ import assert from 'assert' ;
1
2
import { X2jOptions , XMLParser } from 'fast-xml-parser' ;
2
3
import * as fs from 'fs' ;
4
+ import { cpus } from 'os' ;
3
5
import * as path from 'path' ;
4
6
import * as vscode from 'vscode' ;
5
7
import { CancellationToken } from 'vscode-languageclient' ;
8
+ import { adaExtState } from './extension' ;
6
9
import { parallelize , staggerProgress } from './helpers' ;
7
- import { cpus } from 'os' ;
8
- import { assert } from 'console' ;
9
10
10
11
/**
11
12
* TypeScript types to represent data from GNATcoverage XML reports
@@ -299,25 +300,109 @@ export async function addCoverageData(run: vscode.TestRun, covDir: string) {
299
300
progress . report ( {
300
301
message : `${ done } / ${ totalFiles } source files` ,
301
302
} ) ;
303
+
304
+ let posixForeignPrefix : string | undefined ;
305
+ let posixLocalPrefix : string | undefined ;
306
+
307
+ const procs = process . env . PROCESSORS ? Number ( process . env . PROCESSORS ) : 0 ;
302
308
const fileCovs = (
303
309
await parallelize (
304
310
array ,
305
- Math . min ( cpus ( ) . length , 8 ) ,
311
+ Math . min ( procs == 0 ? cpus ( ) . length : procs , 8 ) ,
306
312
async ( file ) => {
307
313
if ( token . isCancellationRequested ) {
308
314
throw new vscode . CancellationError ( ) ;
309
315
}
310
316
311
317
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 ) ;
325
+
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
+ */
312
355
313
- let srcUri : vscode . Uri ;
314
- if ( path . isAbsolute ( file [ '@_name' ] ! ) ) {
315
- srcUri = vscode . Uri . file ( file [ '@_name' ] ! ) ;
316
- } else {
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
+ */
317
393
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 ) } `,
320
404
1 ,
405
+ token ,
321
406
) ;
322
407
if ( found . length == 0 ) {
323
408
return undefined ;
@@ -326,6 +411,61 @@ export async function addCoverageData(run: vscode.TestRun, covDir: string) {
326
411
srcUri = found [ 0 ] ;
327
412
}
328
413
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
+ /**
430
+ * Find the longest common prefix between both
431
+ * paths by starting to compare the characters
432
+ * from the end of each string and iterating
433
+ * backwards.
434
+ */
435
+ let revIndex = 0 ;
436
+ while (
437
+ revIndex < posixForeignPath . length &&
438
+ revIndex < posixLocalAbsPath . length &&
439
+ posixForeignPath [ posixForeignPath . length - revIndex ] ==
440
+ posixLocalAbsPath [ posixLocalAbsPath . length - revIndex ]
441
+ ) {
442
+ revIndex ++ ;
443
+ }
444
+
445
+ // Now the index points to the first different
446
+ // character, so move it back to the last identical
447
+ // character to make the slice operations below
448
+ // more natural.
449
+ revIndex -- ;
450
+
451
+ if (
452
+ revIndex < posixForeignPath . length &&
453
+ revIndex < posixLocalAbsPath . length
454
+ ) {
455
+ posixLocalPrefix = posixLocalAbsPath . slice (
456
+ 0 ,
457
+ posixLocalAbsPath . length - revIndex ,
458
+ ) ;
459
+ posixForeignPrefix = posixForeignPath . slice (
460
+ 0 ,
461
+ posixForeignPath . length - revIndex ,
462
+ ) ;
463
+ } else {
464
+ // Could not find a common prefix so don't
465
+ // do anything
466
+ }
467
+ }
468
+
329
469
const total = file . metric . find (
330
470
( m ) => m [ '@_kind' ] == 'total_lines_of_relevance' ,
331
471
) ! [ '@_count' ] ;
@@ -334,14 +474,15 @@ export async function addCoverageData(run: vscode.TestRun, covDir: string) {
334
474
] ;
335
475
336
476
const fileReportBasename = data . coverage_report . sources ! [ 'xi:include' ] . find (
337
- ( inc ) => inc [ '@_href' ] == `${ path . basename ( file [ '@_name' ] ! ) } .xml` ,
477
+ ( inc ) =>
478
+ inc [ '@_href' ] == `${ path . posix . basename ( posixForeignPath ) } .xml` ,
338
479
) ! [ '@_href' ] ;
339
480
const fileReportPath = path . join ( covDir , fileReportBasename ) ;
340
481
341
482
if ( covered > total ) {
342
483
throw Error (
343
484
`Got ${ covered } covered lines for a` +
344
- ` total of ${ total } in ${ file [ '@_name' ] ! } ` ,
485
+ ` total of ${ total } in ${ file [ '@_name' ] } ` ,
345
486
) ;
346
487
}
347
488
@@ -434,6 +575,27 @@ export class GnatcovFileCoverage extends vscode.FileCoverage {
434
575
}
435
576
}
436
577
578
+ /**
579
+ *
580
+ * @param p - a path
581
+ * @returns a POSIX version of the same path obtained by replacing occurences
582
+ * of `\` with `/`. If the input path was a Windows absolute path, a `/` is
583
+ * prepended to the output to make it also an absolute path.
584
+ */
585
+ function toPosix ( p : string ) {
586
+ let posixPath = p . replace ( RegExp ( `\\${ path . win32 . sep } ` , 'g' ) , path . posix . sep ) ;
587
+
588
+ /**
589
+ * If it was an absolute path from Windows, we have to
590
+ * manually make it a POSIX absolute path.
591
+ */
592
+ if ( path . win32 . isAbsolute ( p ) && ! path . posix . isAbsolute ( posixPath ) ) {
593
+ posixPath = `/${ posixPath } ` ;
594
+ }
595
+
596
+ return posixPath ;
597
+ }
598
+
437
599
export function convertSourceReport (
438
600
data : source_type ,
439
601
token ?: CancellationToken ,
0 commit comments