@@ -5,24 +5,36 @@ import { TestRunnerCoreConfig, fetchSourceMap } from '@web/test-runner-core';
5
5
import { Profiler } from 'inspector' ;
6
6
import picoMatch from 'picomatch' ;
7
7
import LruCache from 'lru-cache' ;
8
+ import { readFile } from 'node:fs/promises' ;
8
9
9
10
import { toFilePath } from './utils' ;
10
11
11
12
type V8Coverage = Profiler . ScriptCoverage ;
12
13
type Matcher = ( test : string ) => boolean ;
13
- type V8Converter = ReturnType < typeof v8toIstanbulLib > ;
14
+ type IstanbulSource = Required < Parameters < typeof v8toIstanbulLib > > [ 2 ] ;
14
15
15
16
const cachedMatchers = new Map < string , Matcher > ( ) ;
16
17
17
- // Cache the v8-to-istanbul converters between calls since they
18
- // result in loading files from disk repeatedly otherwise.
19
- const cachedConverters = new LruCache < string , V8Converter > ( {
20
- max : 200 ,
18
+ // Cache the sourcemap/source objects to avoid repeatedly having to load
19
+ // them from disk per call
20
+ const cachedSources = new LruCache < string , IstanbulSource > ( {
21
+ maxSize : 1024 * 1024 * 50 ,
21
22
} ) ;
22
23
23
24
// coverage base dir must be separated with "/"
24
25
const coverageBaseDir = process . cwd ( ) . split ( sep ) . join ( '/' ) ;
25
26
27
+ function hasOriginalSource ( source : IstanbulSource ) : boolean {
28
+ return (
29
+ 'sourceMap' in source &&
30
+ source . sourceMap !== undefined &&
31
+ typeof source . sourceMap . sourcemap === 'object' &&
32
+ source . sourceMap . sourcemap !== null &&
33
+ Array . isArray ( source . sourceMap . sourcemap . sourcesContent ) &&
34
+ source . sourceMap . sourcemap . sourcesContent . length > 0
35
+ ) ;
36
+ }
37
+
26
38
function getMatcher ( patterns ?: string [ ] ) {
27
39
if ( ! patterns || patterns . length === 0 ) {
28
40
return ( ) => true ;
@@ -71,35 +83,29 @@ export async function v8ToIstanbul(
71
83
const filePath = join ( config . rootDir , toFilePath ( path ) ) ;
72
84
73
85
if ( ! testFiles . includes ( filePath ) && included ( filePath ) && ! excluded ( filePath ) ) {
74
- const sources = await fetchSourceMap ( {
75
- protocol : config . protocol ,
76
- host : config . hostname ,
77
- port : config . port ,
78
- browserUrl : `${ url . pathname } ${ url . search } ${ url . hash } ` ,
79
- userAgent,
80
- } ) ;
81
-
82
- const cachedConverter = cachedConverters . get ( filePath ) ;
83
- const converter = cachedConverter ?? v8toIstanbulLib ( filePath , 0 , sources as any ) ;
84
-
85
- if ( ! cachedConverter ) {
86
- await converter . load ( ) ;
87
- cachedConverters . set ( filePath , converter ) ;
88
- } else {
89
- // When we reuse a cached converter, we need to reset it before using its `applyCoverage` function.
90
- // If we don't, the coverage results will be poisoned with the results of the previous uses.
91
- //
92
- // This "workaround" is resetting some internal variables of the `V8ToIstanbul` class: `branches` & `functions`.
93
- // This can break when newer versions of v8-to-istanbul are released. (variable renaming, more variables are used, ...)
94
- //
95
- // TODO: use a (stable) clone technique instead when available (`structuredClone` is available in node 17)
96
- //
97
- // @ts -ignore
98
- converter . branches = { } ;
99
- // @ts -ignore
100
- converter . functions = { } ;
86
+ const browserUrl = `${ url . pathname } ${ url . search } ${ url . hash } ` ;
87
+ const cachedSource = cachedSources . get ( browserUrl ) ;
88
+ const sources =
89
+ cachedSource ??
90
+ ( ( await fetchSourceMap ( {
91
+ protocol : config . protocol ,
92
+ host : config . hostname ,
93
+ port : config . port ,
94
+ browserUrl,
95
+ userAgent,
96
+ } ) ) as IstanbulSource ) ;
97
+
98
+ if ( ! cachedSource ) {
99
+ if ( ! hasOriginalSource ( sources ) ) {
100
+ const contents = await readFile ( filePath , 'utf8' ) ;
101
+ ( sources as IstanbulSource & { originalSource : string } ) . originalSource = contents ;
102
+ }
103
+ cachedSources . set ( browserUrl , sources ) ;
101
104
}
102
105
106
+ const converter = v8toIstanbulLib ( filePath , 0 , sources ) ;
107
+ await converter . load ( ) ;
108
+
103
109
converter . applyCoverage ( entry . functions ) ;
104
110
Object . assign ( istanbulCoverage , converter . toIstanbul ( ) ) ;
105
111
}
0 commit comments