main
js 420 lines 10.8 KB
Raw
1 'use strict';
2
3 const {spawn} = require('child_process');
4 const chalk = require('chalk');
5 const yargs = require('yargs');
6 const fs = require('fs');
7 const path = require('path');
8 const semver = require('semver');
9
10 const ossConfig = './scripts/jest/config.source.js';
11 const wwwConfig = './scripts/jest/config.source-www.js';
12 const xplatConfig = './scripts/jest/config.source-xplat.js';
13 const devToolsConfig = './scripts/jest/config.build-devtools.js';
14
15 // TODO: These configs are separate but should be rolled into the configs above
16 // so that the CLI can provide them as options for any of the configs.
17 const persistentConfig = './scripts/jest/config.source-persistent.js';
18 const buildConfig = './scripts/jest/config.build.js';
19
20 const argv = yargs
21 .parserConfiguration({
22 // Important: This option tells yargs to move all other options not
23 // specified here into the `_` key. We use this to send all of the
24 // Jest options that we don't use through to Jest (like --watch).
25 'unknown-options-as-args': true,
26 })
27 .wrap(yargs.terminalWidth())
28 .options({
29 debug: {
30 alias: 'd',
31 describe: 'Run with node debugger attached.',
32 requiresArg: false,
33 type: 'boolean',
34 default: false,
35 },
36 project: {
37 alias: 'p',
38 describe: 'Run the given project.',
39 requiresArg: true,
40 type: 'string',
41 default: 'default',
42 choices: ['default', 'devtools'],
43 },
44 releaseChannel: {
45 alias: 'r',
46 describe: 'Run with the given release channel.',
47 requiresArg: true,
48 type: 'string',
49 default: 'experimental',
50 choices: ['experimental', 'stable', 'www-classic', 'www-modern', 'xplat'],
51 },
52 env: {
53 alias: 'e',
54 describe: 'Run with the given node environment.',
55 requiresArg: true,
56 type: 'string',
57 choices: ['development', 'production'],
58 },
59 prod: {
60 describe: 'Run with NODE_ENV=production.',
61 requiresArg: false,
62 type: 'boolean',
63 default: false,
64 },
65 dev: {
66 describe: 'Run with NODE_ENV=development.',
67 requiresArg: false,
68 type: 'boolean',
69 default: false,
70 },
71 variant: {
72 alias: 'v',
73 describe: 'Run with www variant set to true.',
74 requiresArg: false,
75 type: 'boolean',
76 },
77 build: {
78 alias: 'b',
79 describe: 'Run tests on builds.',
80 requiresArg: false,
81 type: 'boolean',
82 default: false,
83 },
84 persistent: {
85 alias: 'n',
86 describe: 'Run with persistence.',
87 requiresArg: false,
88 type: 'boolean',
89 default: false,
90 },
91 ci: {
92 describe: 'Run tests in CI',
93 requiresArg: false,
94 type: 'boolean',
95 default: false,
96 },
97 compactConsole: {
98 alias: 'c',
99 describe: 'Compact console output (hide file locations).',
100 requiresArg: false,
101 type: 'boolean',
102 default: false,
103 },
104 reactVersion: {
105 describe: 'DevTools testing for specific version of React',
106 requiresArg: true,
107 type: 'string',
108 },
109 sourceMaps: {
110 describe:
111 'Enable inline source maps when transforming source files with Jest. Useful for debugging, but makes it slower.',
112 type: 'boolean',
113 default: false,
114 },
115 silent: {
116 alias: 's',
117 describe: 'Use silent reporter with dot progress (minimal output).',
118 requiresArg: false,
119 type: 'boolean',
120 default: false,
121 },
122 }).argv;
123
124 function logError(message) {
125 console.error(chalk.red(`\n${message}`));
126 }
127 function isWWWConfig() {
128 return (
129 (argv.releaseChannel === 'www-classic' ||
130 argv.releaseChannel === 'www-modern') &&
131 argv.project !== 'devtools'
132 );
133 }
134
135 function isXplatConfig() {
136 return argv.releaseChannel === 'xplat' && argv.project !== 'devtools';
137 }
138
139 function isOSSConfig() {
140 return (
141 argv.releaseChannel === 'stable' || argv.releaseChannel === 'experimental'
142 );
143 }
144
145 function validateOptions() {
146 let success = true;
147
148 if (argv.project === 'devtools') {
149 if (argv.prod) {
150 logError(
151 'DevTool tests do not support --prod. Remove this option to continue.'
152 );
153 success = false;
154 }
155
156 if (argv.dev) {
157 logError(
158 'DevTool tests do not support --dev. Remove this option to continue.'
159 );
160 success = false;
161 }
162
163 if (argv.env) {
164 logError(
165 'DevTool tests do not support --env. Remove this option to continue.'
166 );
167 success = false;
168 }
169
170 if (argv.persistent) {
171 logError(
172 'DevTool tests do not support --persistent. Remove this option to continue.'
173 );
174 success = false;
175 }
176
177 if (argv.variant) {
178 logError(
179 'DevTool tests do not support --variant. Remove this option to continue.'
180 );
181 success = false;
182 }
183
184 if (!argv.build) {
185 logError('DevTool tests require --build.');
186 success = false;
187 }
188
189 if (argv.reactVersion && !semver.validRange(argv.reactVersion)) {
190 success = false;
191 logError('please specify a valid version range for --reactVersion');
192 }
193 } else {
194 if (argv.compactConsole) {
195 logError('Only DevTool tests support compactConsole flag.');
196 success = false;
197 }
198 if (argv.reactVersion) {
199 logError('Only DevTools tests supports the --reactVersion flag.');
200 success = false;
201 }
202 }
203
204 if (isWWWConfig() || isXplatConfig()) {
205 if (argv.variant === undefined) {
206 // Turn internal experiments on by default
207 argv.variant = true;
208 }
209 } else {
210 if (argv.variant) {
211 logError(
212 'Variant is only supported for the www release channels. Update these options to continue.'
213 );
214 success = false;
215 }
216 }
217
218 if (argv.build && argv.persistent) {
219 logError(
220 'Persistence is not supported for build targets. Update these options to continue.'
221 );
222 success = false;
223 }
224
225 if (!isOSSConfig() && argv.persistent) {
226 logError(
227 'Persistence only supported for oss release channels. Update these options to continue.'
228 );
229 success = false;
230 }
231
232 if (argv.build && isWWWConfig()) {
233 logError(
234 'Build targets are only not supported for www release channels. Update these options to continue.'
235 );
236 success = false;
237 }
238
239 if (argv.build && isXplatConfig()) {
240 logError(
241 'Build targets are only not supported for xplat release channels. Update these options to continue.'
242 );
243 success = false;
244 }
245
246 if (argv.env && argv.env !== 'production' && argv.prod) {
247 logError(
248 'Build type does not match --prod. Update these options to continue.'
249 );
250 success = false;
251 }
252
253 if (argv.env && argv.env !== 'development' && argv.dev) {
254 logError(
255 'Build type does not match --dev. Update these options to continue.'
256 );
257 success = false;
258 }
259
260 if (argv.prod && argv.dev) {
261 logError(
262 'Cannot supply both --prod and --dev. Remove one of these options to continue.'
263 );
264 success = false;
265 }
266
267 if (argv.build) {
268 // TODO: We could build this if it hasn't been built yet.
269 const buildDir = path.resolve('./build');
270 if (!fs.existsSync(buildDir)) {
271 logError(
272 'Build directory does not exist, please run `yarn build` or remove the --build option.'
273 );
274 success = false;
275 } else if (Date.now() - fs.statSync(buildDir).mtimeMs > 1000 * 60 * 15) {
276 logError(
277 'Warning: Running a build test with a build directory older than 15 minutes.\nPlease remember to run `yarn build` when using --build.'
278 );
279 }
280 }
281
282 if (!success) {
283 console.log(''); // Extra newline.
284 process.exit(1);
285 }
286 }
287
288 function getCommandArgs() {
289 // Add the correct Jest config.
290 const args = ['./scripts/jest/jest.js', '--config'];
291 if (argv.project === 'devtools') {
292 args.push(devToolsConfig);
293 } else if (argv.build) {
294 args.push(buildConfig);
295 } else if (argv.persistent) {
296 args.push(persistentConfig);
297 } else if (isWWWConfig()) {
298 args.push(wwwConfig);
299 } else if (isXplatConfig()) {
300 args.push(xplatConfig);
301 } else if (isOSSConfig()) {
302 args.push(ossConfig);
303 } else {
304 // We should not get here.
305 logError('Unrecognized release channel');
306 process.exit(1);
307 }
308
309 // Set the debug options, if necessary.
310 if (argv.debug) {
311 args.unshift('--inspect-brk');
312 args.push('--runInBand');
313
314 // Prevent console logs from being hidden until test completes.
315 args.push('--useStderr');
316 }
317
318 if (argv.ci === true) {
319 args.push('--maxConcurrency=10');
320 }
321
322 // Use silent reporter if requested.
323 if (argv.silent) {
324 args.push('--reporters=jest-silent-reporter');
325 args.push('--testLocationInResults');
326 }
327
328 // Push the remaining args onto the command.
329 // This will send args like `--watch` to Jest.
330 args.push(...argv._);
331
332 return args;
333 }
334
335 function getEnvars() {
336 const envars = {
337 NODE_ENV: argv.env || 'development',
338 RELEASE_CHANNEL: argv.releaseChannel.match(/modern|experimental/)
339 ? 'experimental'
340 : 'stable',
341
342 // Pass this flag through to the config environment
343 // so the base config can conditionally load the console setup file.
344 compactConsole: argv.compactConsole,
345 };
346
347 if (argv.prod) {
348 envars.NODE_ENV = 'production';
349 }
350
351 if (argv.dev) {
352 envars.NODE_ENV = 'development';
353 }
354
355 if (argv.variant) {
356 envars.VARIANT = true;
357 }
358
359 if (argv.reactVersion) {
360 envars.REACT_VERSION = semver.coerce(argv.reactVersion);
361 }
362
363 if (argv.sourceMaps) {
364 // This is off by default because it slows down the test runner, but it's
365 // super useful when running the debugger.
366 envars.JEST_ENABLE_SOURCE_MAPS = 'inline';
367 }
368
369 if (argv.silent) {
370 // Enable dot output for jest-silent-reporter so that long test runs
371 // show progress and don't appear to be hung.
372 envars.JEST_SILENT_REPORTER_DOTS = true;
373 }
374
375 return envars;
376 }
377
378 function main() {
379 validateOptions();
380
381 const args = getCommandArgs();
382 const envars = getEnvars();
383 const env = Object.entries(envars).map(([k, v]) => `${k}=${v}`);
384
385 if (argv.ci !== true) {
386 // Print the full command we're actually running.
387 const command = `$ ${env.join(' ')} node ${args.join(' ')}`;
388 console.log(chalk.dim(command));
389
390 // Print the release channel and project we're running for quick confirmation.
391 console.log(
392 chalk.blue(
393 `\nRunning tests for ${argv.project} (${argv.releaseChannel})...`
394 )
395 );
396 }
397
398 // Print a message that the debugger is starting just
399 // for some extra feedback when running the debugger.
400 if (argv.debug) {
401 console.log(chalk.green('\nStarting debugger...'));
402 console.log(chalk.green('Open chrome://inspect and press "inspect"\n'));
403 }
404
405 // Run Jest.
406 const jest = spawn('node', args, {
407 stdio: 'inherit',
408 env: {...envars, ...process.env},
409 });
410
411 // Ensure we close our process when we get a failure case.
412 jest.on('close', code => {
413 // Forward the exit code from the Jest process.
414 if (code === 1) {
415 process.exit(1);
416 }
417 });
418 }
419
420 main();