@@ -20,6 +20,9 @@ import { version } from '../package.json' with { type: 'json' }
2020
2121export interface ScriptCoverageWithOffset extends Profiler . ScriptCoverage {
2222 startOffset : number
23+
24+ /** Whether script ran outside Vite, e.g. in sub-processes or worker threads */
25+ isExtendedContext ?: boolean
2326}
2427
2528interface RawCoverage { result : ScriptCoverageWithOffset [ ] }
@@ -34,6 +37,18 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
3437
3538 initialize ( ctx : Vitest ) : void {
3639 this . _initialize ( ctx )
40+
41+ if ( this . options . trackProcessAndWorker ) {
42+ const isAnyThreadsPools = ctx . projects . some ( p => p . config . pool === 'threads' || p . config . pool === 'vmThreads' )
43+
44+ if ( isAnyThreadsPools ) {
45+ // Work-around for https://github.com/nodejs/node/issues/46378
46+ // Node never does anything with this directory, it's just required so that
47+ // the next Workers read **their** env.NODE_V8_COVERAGE.
48+ // Node never creates this .unused directory at all.
49+ process . env . NODE_V8_COVERAGE = `${ this . coverageFilesDirectory } /.unused`
50+ }
51+ }
3752 }
3853
3954 createCoverageMap ( ) : CoverageMap {
@@ -46,16 +61,26 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
4661 const coverageMap = this . createCoverageMap ( )
4762 let merged : RawCoverage = { result : [ ] }
4863
64+ const trackProcessAndWorker = this . options . trackProcessAndWorker
65+
4966 await this . readCoverageFiles < RawCoverage > ( {
5067 onFileRead ( coverage ) {
5168 merged = mergeProcessCovs ( [ merged , coverage ] )
5269
70+ // mergeProcessCovs sometimes loses trackProcessAndWorker
71+ const fromExtendedContext = trackProcessAndWorker ? coverage . result . filter ( r => r . isExtendedContext ) : [ ]
72+
5373 // mergeProcessCovs sometimes loses startOffset, e.g. in vue
5474 merged . result . forEach ( ( result ) => {
5575 if ( ! result . startOffset ) {
5676 const original = coverage . result . find ( r => r . url === result . url )
5777 result . startOffset = original ?. startOffset || 0
5878 }
79+
80+ if ( trackProcessAndWorker && ! result . isExtendedContext ) {
81+ const actual = fromExtendedContext . find ( r => r . url === result . url )
82+ result . isExtendedContext = actual ?. isExtendedContext
83+ }
5984 } )
6085 } ,
6186 onFinished : async ( project , environment ) => {
@@ -331,8 +356,9 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
331356
332357 private async getSources (
333358 url : string ,
334- onTransform : ( filepath : string ) => Promise < Vite . TransformResult | undefined | null > ,
359+ onTransform : ( filepath : string , isExtendedContext ?: ScriptCoverageWithOffset [ 'isExtendedContext' ] ) => Promise < Vite . TransformResult | undefined | null > ,
335360 functions : Profiler . FunctionCoverage [ ] = [ ] ,
361+ isExtendedContext : ScriptCoverageWithOffset [ 'isExtendedContext' ] = false ,
336362 ) : Promise < {
337363 code : string
338364 map ?: Vite . Rollup . SourceMap
@@ -342,7 +368,7 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
342368 ? url . slice ( 8 )
343369 : removeStartsWith ( url , FILE_PROTOCOL )
344370 // TODO: do we still need to "catch" here? why would it fail?
345- const transformResult = await onTransform ( filepath ) . catch ( ( ) => null )
371+ const transformResult = await onTransform ( filepath , isExtendedContext ) . catch ( ( ) => null )
346372
347373 const map = transformResult ?. map as Vite . Rollup . SourceMap | undefined
348374 const code = transformResult ?. code
@@ -385,8 +411,8 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
385411 throw new Error ( `Cannot access browser module graph because it was torn down.` )
386412 }
387413
388- const onTransform = async ( filepath : string ) => {
389- const result = await this . transformFile ( filepath , project , environment )
414+ const onTransform = async ( filepath : string , isExtendedContext : ScriptCoverageWithOffset [ 'isExtendedContext' ] = false ) => {
415+ const result = await this . transformFile ( filepath , project , environment , ! isExtendedContext )
390416 if ( result && environment === '__browser__' && project . browser ) {
391417 return { ...result , code : `${ result . code } // <inline-source-map>` }
392418 }
@@ -423,7 +449,7 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
423449 }
424450
425451 await Promise . all (
426- chunk . map ( async ( { url, functions, startOffset } ) => {
452+ chunk . map ( async ( { url, functions, startOffset, isExtendedContext } ) => {
427453 let timeout : ReturnType < typeof setTimeout > | undefined
428454 let start : number | undefined
429455
@@ -436,6 +462,7 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
436462 url ,
437463 onTransform ,
438464 functions ,
465+ isExtendedContext ,
439466 )
440467
441468 coverageMap . merge ( await this . remapCoverage (
0 commit comments