@@ -23,29 +23,38 @@ import {
23
23
setGlobal ,
24
24
} from 'jest-util' ;
25
25
import jasmine2 from 'jest-jasmine2' ;
26
+ import LeakDetector from 'jest-leak-detector' ;
26
27
import { getTestEnvironment } from 'jest-config' ;
27
28
import * as docblock from 'jest-docblock' ;
28
29
30
+ type RunTestInternalResult = {
31
+ leakDetector : ?LeakDetector ,
32
+ result : TestResult ,
33
+ } ;
34
+
29
35
// The default jest-runner is required because it is the default test runner
30
36
// and required implicitly through the `testRunner` ProjectConfig option.
31
37
jasmine2 ;
32
38
33
- export default ( async function runTest (
39
+ // Keeping the core of "runTest" as a separate function (as "runTestInternal")
40
+ // is key to be able to detect memory leaks. Since all variables are local to
41
+ // the function, when "runTestInternal" finishes its execution, they can all be
42
+ // freed, UNLESS something else is leaking them (and that's why we can detect
43
+ // the leak!).
44
+ //
45
+ // If we had all the code in a single function, we should manually nullify all
46
+ // references to verify if there is a leak, which is not maintainable and error
47
+ // prone. That's why "runTestInternal" CANNOT be inlined inside "runTest".
48
+ async function runTestInternal (
34
49
path : Path ,
35
50
globalConfig : GlobalConfig ,
36
51
config : ProjectConfig ,
37
52
resolver : Resolver ,
38
- ) {
39
- let testSource ;
40
-
41
- try {
42
- testSource = fs . readFileSync ( path , 'utf8' ) ;
43
- } catch ( e ) {
44
- return Promise . reject ( e ) ;
45
- }
46
-
53
+ ) : Promise < RunTestInternalResult > {
54
+ const testSource = fs . readFileSync ( path , 'utf8' ) ;
47
55
const parsedDocblock = docblock . parse ( docblock . extract ( testSource ) ) ;
48
56
const customEnvironment = parsedDocblock [ 'jest-environment' ] ;
57
+
49
58
let testEnvironment = config . testEnvironment ;
50
59
51
60
if ( customEnvironment ) {
@@ -66,6 +75,10 @@ export default (async function runTest(
66
75
> ) ;
67
76
68
77
const environment = new TestEnvironment ( config ) ;
78
+ const leakDetector = config . detectLeaks
79
+ ? new LeakDetector ( environment )
80
+ : null ;
81
+
69
82
const consoleOut = globalConfig . useStderr ? process . stderr : process . stdout ;
70
83
const consoleFormatter = ( type , message ) =>
71
84
getConsoleOutput (
@@ -76,24 +89,25 @@ export default (async function runTest(
76
89
) ;
77
90
78
91
let testConsole ;
92
+
79
93
if ( globalConfig . silent ) {
80
94
testConsole = new NullConsole ( consoleOut , process . stderr , consoleFormatter ) ;
95
+ } else if ( globalConfig . verbose ) {
96
+ testConsole = new Console ( consoleOut , process . stderr , consoleFormatter ) ;
81
97
} else {
82
- if ( globalConfig . verbose ) {
83
- testConsole = new Console ( consoleOut , process . stderr , consoleFormatter ) ;
84
- } else {
85
- testConsole = new BufferedConsole ( ) ;
86
- }
98
+ testConsole = new BufferedConsole ( ) ;
87
99
}
88
100
89
101
const cacheFS = { [ path ] : testSource } ;
90
102
setGlobal ( environment . global , 'console' , testConsole ) ;
103
+
91
104
const runtime = new Runtime ( config , environment , resolver , cacheFS , {
92
105
collectCoverage : globalConfig . collectCoverage ,
93
106
collectCoverageFrom : globalConfig . collectCoverageFrom ,
94
107
collectCoverageOnlyFrom : globalConfig . collectCoverageOnlyFrom ,
95
108
mapCoverage : globalConfig . mapCoverage ,
96
109
} ) ;
110
+
97
111
const start = Date . now ( ) ;
98
112
await environment . setup ( ) ;
99
113
try {
@@ -106,22 +120,46 @@ export default (async function runTest(
106
120
) ;
107
121
const testCount =
108
122
result . numPassingTests + result . numFailingTests + result . numPendingTests ;
123
+
109
124
result . perfStats = { end : Date . now ( ) , start} ;
110
125
result . testFilePath = path ;
111
126
result . coverage = runtime . getAllCoverageInfo ( ) ;
112
127
result . sourceMaps = runtime . getSourceMapInfo ( ) ;
113
128
result . console = testConsole . getBuffer ( ) ;
114
129
result . skipped = testCount === result . numPendingTests ;
115
130
result . displayName = config . displayName ;
131
+
116
132
if ( globalConfig . logHeapUsage ) {
117
133
if ( global . gc ) {
118
134
global . gc ( ) ;
119
135
}
120
136
result . memoryUsage = process . memoryUsage ( ) . heapUsed ;
121
137
}
138
+
122
139
// Delay the resolution to allow log messages to be output.
123
- return new Promise ( resolve => setImmediate ( ( ) => resolve ( result ) ) ) ;
140
+ return new Promise ( resolve => {
141
+ setImmediate ( ( ) => resolve ( { leakDetector, result} ) ) ;
142
+ } ) ;
124
143
} finally {
125
144
await environment . teardown ( ) ;
126
145
}
127
- } ) ;
146
+ }
147
+
148
+ export default async function runTest (
149
+ path : Path ,
150
+ globalConfig : GlobalConfig ,
151
+ config : ProjectConfig ,
152
+ resolver : Resolver ,
153
+ ) : Promise < TestResult > {
154
+ const { leakDetector, result} = await runTestInternal (
155
+ path ,
156
+ globalConfig ,
157
+ config ,
158
+ resolver ,
159
+ ) ;
160
+
161
+ // Resolve leak detector, outside the "runTestInternal" closure.
162
+ result . leaks = leakDetector ? leakDetector . isLeaking ( ) : false ;
163
+
164
+ return result ;
165
+ }
0 commit comments