blob: 1b58f083e611f3c76b19b1d90fc1c72d6f9b353c [file] [log] [blame]
Philip Pfaffe705cfde2024-04-11 08:53:381// Copyright 2024 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import * as childProcess from 'child_process';
6import * as fs from 'fs';
Philip Pfaffea25e31f2024-07-09 14:09:127import * as glob from 'glob';
Philip Pfaffe705cfde2024-04-11 08:53:388import * as os from 'os';
9import * as path from 'path';
10
Philip Pfaffe5014bed2024-04-12 07:51:3911import {commandLineArgs} from './conductor/commandline.js';
Eric Leese460be152024-07-10 12:07:5712import {
13 BUILD_WITH_CHROMIUM,
14 CHECKOUT_ROOT,
Eric Leese460be152024-07-10 12:07:5715 GEN_DIR,
16 isContainedInDirectory,
17 PathPair,
18 SOURCE_ROOT,
19} from './conductor/paths.js';
Philip Pfaffe705cfde2024-04-11 08:53:3820
21const yargs = require('yargs');
22const unparse = require('yargs-unparser');
Philip Pfaffe5014bed2024-04-12 07:51:3923const options = commandLineArgs(yargs(process.argv.slice(2)))
Philip Pfaffe705cfde2024-04-11 08:53:3824 .options('skip-ninja', {type: 'boolean', desc: 'Skip rebuilding'})
Philip Pfaffe782391d2024-04-17 12:24:0425 .options('debug-driver', {type: 'boolean', hidden: true, desc: 'Debug the driver part of tests'})
Jack Franklina36605e2024-04-22 08:38:0426 .options('verbose', {alias: 'v', type: 'count', desc: 'Increases the log level'})
Liviu Rau0b38566d2024-05-31 14:19:2127 .options('bail', {alias: 'b', desc: ' bail after first test failure'})
Ergun Erdogmus87be7c12025-01-20 14:14:3728 .options('auto-watch', {
29 desc: 'watch changes to files and run tests automatically on file change (only for unit tests)'
30 })
Philip Pfaffe705cfde2024-04-11 08:53:3831 .positional('tests', {
32 type: 'string',
33 desc: 'Path to the test suite, starting from out/Target/gen directory.',
34 normalize: true,
Liviu Rau34154dd2025-02-27 09:36:2435 default: ['front_end', 'test/e2e', 'test/interactions', 'test/e2e_non_hosted'].map(
Philip Pfaffe5014bed2024-04-12 07:51:3936 f => path.relative(process.cwd(), path.join(SOURCE_ROOT, f))),
Philip Pfaffe705cfde2024-04-11 08:53:3837 })
38 .strict()
Nikolay Vitkov41c69112025-01-31 15:20:0439 .parseSync();
40
Liviu Rau0b38566d2024-05-31 14:19:2141const CONSUMED_OPTIONS = ['tests', 'skip-ninja', 'debug-driver', 'bail', 'b', 'verbose', 'v', 'watch'];
Jack Franklina36605e2024-04-22 08:38:0442
43let logLevel = 'error';
44if (options['verbose'] === 1) {
45 logLevel = 'info';
46} else if (options['verbose'] === 2) {
47 logLevel = 'debug';
48}
Philip Pfaffe705cfde2024-04-11 08:53:3849
50function forwardOptions() {
51 const forwardedOptions = {...options};
52 for (const consume of CONSUMED_OPTIONS) {
53 forwardedOptions[consume] = undefined;
54 }
55 return unparse(forwardedOptions);
56}
57
58function runProcess(exe: string, args: string[], options: childProcess.SpawnSyncOptionsWithStringEncoding) {
Jack Franklina36605e2024-04-22 08:38:0459 if (logLevel !== 'error') {
60 // eslint-disable-next-line no-console
61 console.info(`Running '${exe}${args.length > 0 ? ` "${args.join('" "')}"` : ''}'`);
62 }
Philip Pfaffe705cfde2024-04-11 08:53:3863 return childProcess.spawnSync(exe, args, options);
64}
65
66function ninja(stdio: 'inherit'|'pipe', ...args: string[]) {
67 let buildRoot = path.dirname(GEN_DIR);
68 while (!fs.existsSync(path.join(buildRoot, 'args.gn'))) {
69 const parent = path.dirname(buildRoot);
70 if (parent === buildRoot) {
71 throw new Error('Failed to find a build directory containing args.gn');
72 }
73 buildRoot = parent;
74 }
75 const ninjaCommand = os.platform() === 'win32' ? 'autoninja.bat' : 'autoninja';
Eric Leese460be152024-07-10 12:07:5776 // autoninja can't always find ninja if not run from the checkout root, so
77 // run it from there and pass the build root as an argument.
78 const result = runProcess(ninjaCommand, ['-C', buildRoot, ...args], {encoding: 'utf-8', cwd: CHECKOUT_ROOT, stdio});
Philip Pfaffe705cfde2024-04-11 08:53:3879 if (result.error) {
80 throw result.error;
81 }
82 const {status, output: [, output]} = result;
83 return {status, output};
84}
85
Philip Pfaffead892002024-04-12 07:39:4486class Tests {
Philip Pfaffe705cfde2024-04-11 08:53:3887 readonly suite: PathPair;
Philip Pfaffead892002024-04-12 07:39:4488 readonly extraPaths: PathPair[];
Nikolay Vitkov99a91042025-01-23 14:38:1489 protected readonly cwd = path.dirname(GEN_DIR);
Philip Pfaffead892002024-04-12 07:39:4490 constructor(suite: string, ...extraSuites: string[]) {
Philip Pfaffe705cfde2024-04-11 08:53:3891 const suitePath = PathPair.get(suite);
92 if (!suitePath) {
93 throw new Error(`Could not locate the test suite '${suite}'`);
94 }
95 this.suite = suitePath;
Philip Pfaffead892002024-04-12 07:39:4496 const extraPaths = extraSuites.map(p => [p, PathPair.get(p)]);
97 const failures = extraPaths.filter(p => p[1] === null);
98 if (failures.length > 0) {
99 throw new Error(`Could not resolve extra paths for ${failures.map(p => p[0]).join()}`);
100 }
101 this.extraPaths = extraPaths.filter((p): p is[string, PathPair] => p[1] !== null).map(p => p[1]);
Philip Pfaffe705cfde2024-04-11 08:53:38102 }
103
Philip Pfaffead892002024-04-12 07:39:44104 match(path: PathPair) {
105 return [this.suite, ...this.extraPaths].some(
106 pathToCheck => isContainedInDirectory(path.buildPath, pathToCheck.buildPath));
107 }
108
Philip Pfaffecf711262024-06-03 10:19:17109 protected run(tests: PathPair[], args: string[], positionalTestArgs = true) {
Philip Pfaffe705cfde2024-04-11 08:53:38110 const argumentsForNode = [
Philip Pfaffead892002024-04-12 07:39:44111 ...args,
Ergun Erdogmus87be7c12025-01-20 14:14:37112 ...(options['auto-watch'] ? ['--auto-watch', '--no-single-run'] : []),
Philip Pfaffe705cfde2024-04-11 08:53:38113 '--',
Philip Pfaffecf711262024-06-03 10:19:17114 ...tests.map(t => positionalTestArgs ? t.buildPath : `--tests=${t.buildPath}`),
Philip Pfaffe705cfde2024-04-11 08:53:38115 ...forwardOptions(),
116 ];
117 if (options['debug-driver']) {
118 argumentsForNode.unshift('--inspect-brk');
119 } else if (options['debug']) {
120 argumentsForNode.unshift('--inspect');
121 }
Nikolay Vitkov99a91042025-01-23 14:38:14122
123 const result = runProcess(process.argv[0], argumentsForNode, {
124 encoding: 'utf-8',
125 stdio: 'inherit',
126 cwd: this.cwd,
127 });
Philip Pfaffef6cb4362024-04-22 11:07:46128 return !result.error && (result.status ?? 1) === 0;
Philip Pfaffe705cfde2024-04-11 08:53:38129 }
130}
131
Philip Pfaffead892002024-04-12 07:39:44132class MochaTests extends Tests {
133 override run(tests: PathPair[]) {
Philip Pfaffecf711262024-06-03 10:19:17134 return super.run(
135 tests,
136 [
137 path.join(SOURCE_ROOT, 'node_modules', 'mocha', 'bin', 'mocha'),
138 '--config',
139 path.join(this.suite.buildPath, 'mocharc.js'),
Alex Rudenko58105522024-08-29 11:15:42140 '-u',
Alex Rudenkod5d12012024-09-25 13:03:40141 path.join(this.suite.buildPath, '..', 'conductor', 'mocha-interface.js'),
Philip Pfaffecf711262024-06-03 10:19:17142 ],
143 /* positionalTestArgs= */ false, // Mocha interprets positional arguments as test files itself. Work around
144 // that by passing the tests as dashed args instead.
145 );
Philip Pfaffead892002024-04-12 07:39:44146 }
147}
148
Liviu Rau34154dd2025-02-27 09:36:24149class NonHostedMochaTests extends Tests {
150 override run(tests: PathPair[]) {
151 return super.run(
152 tests,
153 [
154 path.join(SOURCE_ROOT, 'node_modules', 'mocha', 'bin', 'mocha'),
155 '--config',
156 path.join(this.suite.buildPath, 'mocharc.js'),
157 '-u',
158 path.join(this.suite.buildPath, 'conductor', 'mocha-interface.js'),
159 ],
160 /* positionalTestArgs= */ false, // Mocha interprets positional arguments as test files itself. Work around
161 // that by passing the tests as dashed args instead.
162 );
163 }
164}
165
Nikolay Vitkov99a91042025-01-23 14:38:14166/**
167 * Workaround the fact that these test don't have
168 * build output in out/Default like dir.
169 */
170class ScriptPathPair extends PathPair {
171 static getFromPair(pair: PathPair) {
172 return new ScriptPathPair(pair.sourcePath, pair.sourcePath);
173 }
174}
175
176class ScriptsMochaTests extends Tests {
177 override readonly cwd = SOURCE_ROOT;
Nikolay Vitkovc4066362025-02-05 17:49:03178
Nikolay Vitkov99a91042025-01-23 14:38:14179 override run(tests: PathPair[]) {
180 return super.run(
Nikolay Vitkov65a5a912025-02-18 18:30:26181 tests.map(test => ScriptPathPair.getFromPair(test)),
Nikolay Vitkov99a91042025-01-23 14:38:14182 [
183 path.join(SOURCE_ROOT, 'node_modules', 'mocha', 'bin', 'mocha'),
184 ],
185 );
186 }
187
188 override match(path: PathPair): boolean {
Nikolay Vitkovc4066362025-02-05 17:49:03189 return [this.suite, ...this.extraPaths].some(
190 pathToCheck => isContainedInDirectory(path.sourcePath, pathToCheck.sourcePath));
Nikolay Vitkov99a91042025-01-23 14:38:14191 }
192}
193
Philip Pfaffead892002024-04-12 07:39:44194class KarmaTests extends Tests {
195 override run(tests: PathPair[]) {
196 return super.run(tests, [
Jack Franklina36605e2024-04-22 08:38:04197 path.join(SOURCE_ROOT, 'node_modules', 'karma', 'bin', 'karma'),
198 'start',
199 path.join(GEN_DIR, 'test', 'unit', 'karma.conf.js'),
200 '--log-level',
201 logLevel,
Philip Pfaffead892002024-04-12 07:39:44202 ]);
203 }
204}
205
206// TODO(333423685)
Philip Pfaffe705cfde2024-04-11 08:53:38207// - watch
Philip Pfaffe705cfde2024-04-11 08:53:38208function main() {
Nikolay Vitkov41c69112025-01-31 15:20:04209 const tests: string[] = typeof options['tests'] === 'string' ? [options['tests']] : options['tests'];
Philip Pfaffe705cfde2024-04-11 08:53:38210 const testKinds = [
Philip Pfaffe96992fe2024-07-09 13:43:01211 new KarmaTests(path.join(GEN_DIR, 'front_end'), path.join(GEN_DIR, 'inspector_overlay')),
Philip Pfaffe705cfde2024-04-11 08:53:38212 new MochaTests(path.join(GEN_DIR, 'test/interactions')),
Philip Pfaffe6435eb42024-04-11 12:36:03213 new MochaTests(path.join(GEN_DIR, 'test/e2e')),
Liviu Rau34154dd2025-02-27 09:36:24214 new NonHostedMochaTests(path.join(GEN_DIR, 'test/e2e_non_hosted')),
Philip Pfaffe46fe3432024-04-19 10:17:04215 new MochaTests(path.join(GEN_DIR, 'test/perf')),
Nikolay Vitkov99a91042025-01-23 14:38:14216 new ScriptsMochaTests(path.join(SOURCE_ROOT, 'scripts/eslint_rules/tests')),
217 new ScriptsMochaTests(path.join(SOURCE_ROOT, 'scripts/stylelint_rules/tests')),
218 new ScriptsMochaTests(path.join(SOURCE_ROOT, 'scripts/build/tests')),
Philip Pfaffe705cfde2024-04-11 08:53:38219 ];
220
221 if (!options['skip-ninja']) {
Eric Leese460be152024-07-10 12:07:57222 // For a devtools only checkout, it is fast enough to build everything. For
223 // a chromium checkout we want to build only the targets that are needed.
224 const targets = BUILD_WITH_CHROMIUM ?
225 [
226 'chrome',
227 'third_party/devtools-frontend/src/test:test',
228 'third_party/devtools-frontend/src/scripts/hosted_mode:hosted_mode',
229 'third_party/devtools-frontend/src/scripts/component_server:component_server',
230 ] :
231 [];
232 const {status} = ninja('inherit', ...targets);
Philip Pfaffe705cfde2024-04-11 08:53:38233 if (status) {
234 return status;
235 }
236 }
237
238 const suites = new Map<MochaTests, PathPair[]>();
Philip Pfaffea25e31f2024-07-09 14:09:12239 const testFiles = tests.flatMap(t => {
240 const globbed = glob.glob.sync(t);
241 return globbed.length > 0 ? globbed : t;
242 });
243 for (const t of testFiles) {
Philip Pfaffe705cfde2024-04-11 08:53:38244 const repoPath = PathPair.get(t);
245 if (!repoPath) {
246 console.error(`Could not locate the test input for '${t}'`);
247 continue;
248 }
249
Philip Pfaffead892002024-04-12 07:39:44250 const suite = testKinds.find(kind => kind.match(repoPath));
Philip Pfaffe705cfde2024-04-11 08:53:38251 if (suite === undefined) {
Philip Pfaffe6435eb42024-04-11 12:36:03252 console.error(`Unknown test suite for '${repoPath.sourcePath}'`);
Philip Pfaffe705cfde2024-04-11 08:53:38253 continue;
254 }
255
256 suites.get(suite)?.push(repoPath) ?? suites.set(suite, [repoPath]);
257 }
258
259 if (suites.size > 0) {
260 const success = Array.from(suites).every(([suite, files]) => suite.run(files));
261 return success ? 0 : 1;
262 }
263 if (tests.length > 0) {
264 return 1;
265 }
266 const success = testKinds.every(kind => kind.run([kind.suite]));
267 return success ? 0 : 1;
268}
269
270process.exit(main());