1 | 'use strict';
|
2 | const {pathToFileURL} = require('url');
|
3 | const currentlyUnhandled = require('currently-unhandled')();
|
4 |
|
5 | require('./ensure-forked');
|
6 |
|
7 | const ipc = require('./ipc');
|
8 |
|
9 | const supportsESM = async () => {
|
10 | try {
|
11 | await import('data:text/javascript,');
|
12 | return true;
|
13 | } catch {}
|
14 |
|
15 | return false;
|
16 | };
|
17 |
|
18 | ipc.send({type: 'ready-for-options'});
|
19 | ipc.options.then(async options => {
|
20 | require('./options').set(options);
|
21 | require('../chalk').set(options.chalkOptions);
|
22 |
|
23 | if (options.chalkOptions.level > 0) {
|
24 | const {stdout, stderr} = process;
|
25 | global.console = Object.assign(global.console, new console.Console({stdout, stderr, colorMode: true}));
|
26 | }
|
27 |
|
28 | const nowAndTimers = require('../now-and-timers');
|
29 | const providerManager = require('../provider-manager');
|
30 | const Runner = require('../runner');
|
31 | const serializeError = require('../serialize-error');
|
32 | const dependencyTracking = require('./dependency-tracker');
|
33 | const lineNumberSelection = require('./line-numbers');
|
34 |
|
35 | async function exit(code) {
|
36 | if (!process.exitCode) {
|
37 | process.exitCode = code;
|
38 | }
|
39 |
|
40 | dependencyTracking.flush();
|
41 | await ipc.flush();
|
42 | process.exit();
|
43 | }
|
44 |
|
45 |
|
46 |
|
47 | let checkSelectedByLineNumbers;
|
48 | try {
|
49 | checkSelectedByLineNumbers = lineNumberSelection({
|
50 | file: options.file,
|
51 | lineNumbers: options.lineNumbers
|
52 | });
|
53 | } catch (error) {
|
54 | ipc.send({type: 'line-number-selection-error', err: serializeError('Line number selection error', false, error, options.file)});
|
55 | checkSelectedByLineNumbers = () => false;
|
56 | }
|
57 |
|
58 | const runner = new Runner({
|
59 | checkSelectedByLineNumbers,
|
60 | experiments: options.experiments,
|
61 | failFast: options.failFast,
|
62 | failWithoutAssertions: options.failWithoutAssertions,
|
63 | file: options.file,
|
64 | match: options.match,
|
65 | projectDir: options.projectDir,
|
66 | recordNewSnapshots: options.recordNewSnapshots,
|
67 | runOnlyExclusive: options.runOnlyExclusive,
|
68 | serial: options.serial,
|
69 | snapshotDir: options.snapshotDir,
|
70 | updateSnapshots: options.updateSnapshots
|
71 | });
|
72 |
|
73 | ipc.peerFailed.then(() => {
|
74 | runner.interrupt();
|
75 | });
|
76 |
|
77 | const attributedRejections = new Set();
|
78 | process.on('unhandledRejection', (reason, promise) => {
|
79 | if (runner.attributeLeakedError(reason)) {
|
80 | attributedRejections.add(promise);
|
81 | }
|
82 | });
|
83 |
|
84 | runner.on('dependency', dependencyTracking.track);
|
85 | runner.on('stateChange', state => ipc.send(state));
|
86 |
|
87 | runner.on('error', error => {
|
88 | ipc.send({type: 'internal-error', err: serializeError('Internal runner error', false, error, runner.file)});
|
89 | exit(1);
|
90 | });
|
91 |
|
92 | runner.on('finish', () => {
|
93 | try {
|
94 | const {cannotSave, touchedFiles} = runner.saveSnapshotState();
|
95 | if (cannotSave) {
|
96 | ipc.send({type: 'snapshot-error'});
|
97 | } else if (touchedFiles) {
|
98 | ipc.send({type: 'touched-files', files: touchedFiles});
|
99 | }
|
100 | } catch (error) {
|
101 | ipc.send({type: 'internal-error', err: serializeError('Internal runner error', false, error, runner.file)});
|
102 | exit(1);
|
103 | return;
|
104 | }
|
105 |
|
106 | nowAndTimers.setImmediate(() => {
|
107 | currentlyUnhandled()
|
108 | .filter(rejection => !attributedRejections.has(rejection.promise))
|
109 | .forEach(rejection => {
|
110 | ipc.send({type: 'unhandled-rejection', err: serializeError('Unhandled rejection', true, rejection.reason, runner.file)});
|
111 | });
|
112 |
|
113 | exit(0);
|
114 | });
|
115 | });
|
116 |
|
117 | process.on('uncaughtException', error => {
|
118 | if (runner.attributeLeakedError(error)) {
|
119 | return;
|
120 | }
|
121 |
|
122 | ipc.send({type: 'uncaught-exception', err: serializeError('Uncaught exception', true, error, runner.file)});
|
123 | exit(1);
|
124 | });
|
125 |
|
126 | let accessedRunner = false;
|
127 | exports.getRunner = () => {
|
128 | accessedRunner = true;
|
129 | return runner;
|
130 | };
|
131 |
|
132 |
|
133 | const testPath = options.file;
|
134 |
|
135 |
|
136 | const sourceMapSupport = require('source-map-support');
|
137 | sourceMapSupport.install({
|
138 | environment: 'node',
|
139 | handleUncaughtExceptions: false
|
140 | });
|
141 |
|
142 | const extensionsToLoadAsModules = Object.entries(options.moduleTypes)
|
143 | .filter(([, type]) => type === 'module')
|
144 | .map(([extension]) => extension);
|
145 |
|
146 |
|
147 |
|
148 | const {projectDir, providerStates = []} = options;
|
149 | const providers = providerStates.map(({type, state}) => {
|
150 | if (type === 'babel') {
|
151 | const provider = providerManager.babel(projectDir).worker({extensionsToLoadAsModules, state});
|
152 | runner.powerAssert = provider.powerAssert;
|
153 | return provider;
|
154 | }
|
155 |
|
156 | if (type === 'typescript') {
|
157 | return providerManager.typescript(projectDir).worker({extensionsToLoadAsModules, state});
|
158 | }
|
159 |
|
160 | return null;
|
161 | }).filter(provider => provider !== null);
|
162 |
|
163 | let requireFn = require;
|
164 | let isESMSupported;
|
165 | const load = async ref => {
|
166 | for (const extension of extensionsToLoadAsModules) {
|
167 | if (ref.endsWith(`.${extension}`)) {
|
168 | if (typeof isESMSupported !== 'boolean') {
|
169 |
|
170 |
|
171 | isESMSupported = await supportsESM();
|
172 | }
|
173 |
|
174 | if (isESMSupported) {
|
175 | return import(pathToFileURL(ref));
|
176 | }
|
177 |
|
178 | ipc.send({type: 'internal-error', err: serializeError('Internal runner error', false, new Error('ECMAScript Modules are not supported in this Node.js version.'))});
|
179 | exit(1);
|
180 | return;
|
181 | }
|
182 | }
|
183 |
|
184 | for (const provider of providers) {
|
185 | if (provider.canLoad(ref)) {
|
186 | return provider.load(ref, {requireFn});
|
187 | }
|
188 | }
|
189 |
|
190 | return requireFn(ref);
|
191 | };
|
192 |
|
193 | try {
|
194 | for await (const ref of (options.require || [])) {
|
195 | const mod = await load(ref);
|
196 |
|
197 | try {
|
198 | if (Reflect.has(mod, Symbol.for('esm:package'))) {
|
199 | requireFn = mod(module);
|
200 | }
|
201 | } catch {}
|
202 | }
|
203 |
|
204 |
|
205 |
|
206 | dependencyTracking.install(testPath);
|
207 |
|
208 | if (options.debug && options.debug.port !== undefined && options.debug.host !== undefined) {
|
209 |
|
210 |
|
211 | const inspector = require('inspector');
|
212 | if (!options.debug.active || inspector.url() === undefined) {
|
213 | inspector.open(options.debug.port, options.debug.host, true);
|
214 | }
|
215 |
|
216 | if (options.debug.break) {
|
217 | debugger;
|
218 | }
|
219 | }
|
220 |
|
221 | await load(testPath);
|
222 |
|
223 | if (accessedRunner) {
|
224 |
|
225 |
|
226 |
|
227 | ipc.unref();
|
228 | } else {
|
229 | ipc.send({type: 'missing-ava-import'});
|
230 | exit(1);
|
231 | }
|
232 | } catch (error) {
|
233 | ipc.send({type: 'uncaught-exception', err: serializeError('Uncaught exception', true, error, runner.file)});
|
234 | exit(1);
|
235 | }
|
236 | }).catch(error => {
|
237 |
|
238 |
|
239 |
|
240 | setImmediate(() => {
|
241 | throw error;
|
242 | });
|
243 | });
|