Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 1 | // 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 | |
| 5 | import * as childProcess from 'child_process'; |
| 6 | import * as fs from 'fs'; |
Philip Pfaffe | a25e31f | 2024-07-09 14:09:12 | [diff] [blame] | 7 | import * as glob from 'glob'; |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 8 | import * as os from 'os'; |
| 9 | import * as path from 'path'; |
| 10 | |
Philip Pfaffe | 5014bed | 2024-04-12 07:51:39 | [diff] [blame] | 11 | import {commandLineArgs} from './conductor/commandline.js'; |
Eric Leese | 460be15 | 2024-07-10 12:07:57 | [diff] [blame] | 12 | import { |
| 13 | BUILD_WITH_CHROMIUM, |
| 14 | CHECKOUT_ROOT, |
Eric Leese | 460be15 | 2024-07-10 12:07:57 | [diff] [blame] | 15 | GEN_DIR, |
| 16 | isContainedInDirectory, |
| 17 | PathPair, |
| 18 | SOURCE_ROOT, |
| 19 | } from './conductor/paths.js'; |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 20 | |
| 21 | const yargs = require('yargs'); |
| 22 | const unparse = require('yargs-unparser'); |
Philip Pfaffe | 5014bed | 2024-04-12 07:51:39 | [diff] [blame] | 23 | const options = commandLineArgs(yargs(process.argv.slice(2))) |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 24 | .options('skip-ninja', {type: 'boolean', desc: 'Skip rebuilding'}) |
Philip Pfaffe | 782391d | 2024-04-17 12:24:04 | [diff] [blame] | 25 | .options('debug-driver', {type: 'boolean', hidden: true, desc: 'Debug the driver part of tests'}) |
Jack Franklin | a36605e | 2024-04-22 08:38:04 | [diff] [blame] | 26 | .options('verbose', {alias: 'v', type: 'count', desc: 'Increases the log level'}) |
Liviu Rau | 0b38566d | 2024-05-31 14:19:21 | [diff] [blame] | 27 | .options('bail', {alias: 'b', desc: ' bail after first test failure'}) |
Ergun Erdogmus | 87be7c1 | 2025-01-20 14:14:37 | [diff] [blame] | 28 | .options('auto-watch', { |
| 29 | desc: 'watch changes to files and run tests automatically on file change (only for unit tests)' |
| 30 | }) |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 31 | .positional('tests', { |
| 32 | type: 'string', |
| 33 | desc: 'Path to the test suite, starting from out/Target/gen directory.', |
| 34 | normalize: true, |
Liviu Rau | 34154dd | 2025-02-27 09:36:24 | [diff] [blame] | 35 | default: ['front_end', 'test/e2e', 'test/interactions', 'test/e2e_non_hosted'].map( |
Philip Pfaffe | 5014bed | 2024-04-12 07:51:39 | [diff] [blame] | 36 | f => path.relative(process.cwd(), path.join(SOURCE_ROOT, f))), |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 37 | }) |
| 38 | .strict() |
Nikolay Vitkov | 41c6911 | 2025-01-31 15:20:04 | [diff] [blame] | 39 | .parseSync(); |
| 40 | |
Liviu Rau | 0b38566d | 2024-05-31 14:19:21 | [diff] [blame] | 41 | const CONSUMED_OPTIONS = ['tests', 'skip-ninja', 'debug-driver', 'bail', 'b', 'verbose', 'v', 'watch']; |
Jack Franklin | a36605e | 2024-04-22 08:38:04 | [diff] [blame] | 42 | |
| 43 | let logLevel = 'error'; |
| 44 | if (options['verbose'] === 1) { |
| 45 | logLevel = 'info'; |
| 46 | } else if (options['verbose'] === 2) { |
| 47 | logLevel = 'debug'; |
| 48 | } |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 49 | |
| 50 | function forwardOptions() { |
| 51 | const forwardedOptions = {...options}; |
| 52 | for (const consume of CONSUMED_OPTIONS) { |
| 53 | forwardedOptions[consume] = undefined; |
| 54 | } |
| 55 | return unparse(forwardedOptions); |
| 56 | } |
| 57 | |
| 58 | function runProcess(exe: string, args: string[], options: childProcess.SpawnSyncOptionsWithStringEncoding) { |
Jack Franklin | a36605e | 2024-04-22 08:38:04 | [diff] [blame] | 59 | if (logLevel !== 'error') { |
| 60 | // eslint-disable-next-line no-console |
| 61 | console.info(`Running '${exe}${args.length > 0 ? ` "${args.join('" "')}"` : ''}'`); |
| 62 | } |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 63 | return childProcess.spawnSync(exe, args, options); |
| 64 | } |
| 65 | |
| 66 | function 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 Leese | 460be15 | 2024-07-10 12:07:57 | [diff] [blame] | 76 | // 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 Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 79 | if (result.error) { |
| 80 | throw result.error; |
| 81 | } |
| 82 | const {status, output: [, output]} = result; |
| 83 | return {status, output}; |
| 84 | } |
| 85 | |
Philip Pfaffe | ad89200 | 2024-04-12 07:39:44 | [diff] [blame] | 86 | class Tests { |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 87 | readonly suite: PathPair; |
Philip Pfaffe | ad89200 | 2024-04-12 07:39:44 | [diff] [blame] | 88 | readonly extraPaths: PathPair[]; |
Nikolay Vitkov | 99a9104 | 2025-01-23 14:38:14 | [diff] [blame] | 89 | protected readonly cwd = path.dirname(GEN_DIR); |
Philip Pfaffe | ad89200 | 2024-04-12 07:39:44 | [diff] [blame] | 90 | constructor(suite: string, ...extraSuites: string[]) { |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 91 | 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 Pfaffe | ad89200 | 2024-04-12 07:39:44 | [diff] [blame] | 96 | 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 Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 102 | } |
| 103 | |
Philip Pfaffe | ad89200 | 2024-04-12 07:39:44 | [diff] [blame] | 104 | match(path: PathPair) { |
| 105 | return [this.suite, ...this.extraPaths].some( |
| 106 | pathToCheck => isContainedInDirectory(path.buildPath, pathToCheck.buildPath)); |
| 107 | } |
| 108 | |
Philip Pfaffe | cf71126 | 2024-06-03 10:19:17 | [diff] [blame] | 109 | protected run(tests: PathPair[], args: string[], positionalTestArgs = true) { |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 110 | const argumentsForNode = [ |
Philip Pfaffe | ad89200 | 2024-04-12 07:39:44 | [diff] [blame] | 111 | ...args, |
Ergun Erdogmus | 87be7c1 | 2025-01-20 14:14:37 | [diff] [blame] | 112 | ...(options['auto-watch'] ? ['--auto-watch', '--no-single-run'] : []), |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 113 | '--', |
Philip Pfaffe | cf71126 | 2024-06-03 10:19:17 | [diff] [blame] | 114 | ...tests.map(t => positionalTestArgs ? t.buildPath : `--tests=${t.buildPath}`), |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 115 | ...forwardOptions(), |
| 116 | ]; |
| 117 | if (options['debug-driver']) { |
| 118 | argumentsForNode.unshift('--inspect-brk'); |
| 119 | } else if (options['debug']) { |
| 120 | argumentsForNode.unshift('--inspect'); |
| 121 | } |
Nikolay Vitkov | 99a9104 | 2025-01-23 14:38:14 | [diff] [blame] | 122 | |
| 123 | const result = runProcess(process.argv[0], argumentsForNode, { |
| 124 | encoding: 'utf-8', |
| 125 | stdio: 'inherit', |
| 126 | cwd: this.cwd, |
| 127 | }); |
Philip Pfaffe | f6cb436 | 2024-04-22 11:07:46 | [diff] [blame] | 128 | return !result.error && (result.status ?? 1) === 0; |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 129 | } |
| 130 | } |
| 131 | |
Philip Pfaffe | ad89200 | 2024-04-12 07:39:44 | [diff] [blame] | 132 | class MochaTests extends Tests { |
| 133 | override run(tests: PathPair[]) { |
Philip Pfaffe | cf71126 | 2024-06-03 10:19:17 | [diff] [blame] | 134 | 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 Rudenko | 5810552 | 2024-08-29 11:15:42 | [diff] [blame] | 140 | '-u', |
Alex Rudenko | d5d1201 | 2024-09-25 13:03:40 | [diff] [blame] | 141 | path.join(this.suite.buildPath, '..', 'conductor', 'mocha-interface.js'), |
Philip Pfaffe | cf71126 | 2024-06-03 10:19:17 | [diff] [blame] | 142 | ], |
| 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 Pfaffe | ad89200 | 2024-04-12 07:39:44 | [diff] [blame] | 146 | } |
| 147 | } |
| 148 | |
Liviu Rau | 34154dd | 2025-02-27 09:36:24 | [diff] [blame] | 149 | class 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 Vitkov | 99a9104 | 2025-01-23 14:38:14 | [diff] [blame] | 166 | /** |
| 167 | * Workaround the fact that these test don't have |
| 168 | * build output in out/Default like dir. |
| 169 | */ |
| 170 | class ScriptPathPair extends PathPair { |
| 171 | static getFromPair(pair: PathPair) { |
| 172 | return new ScriptPathPair(pair.sourcePath, pair.sourcePath); |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | class ScriptsMochaTests extends Tests { |
| 177 | override readonly cwd = SOURCE_ROOT; |
Nikolay Vitkov | c406636 | 2025-02-05 17:49:03 | [diff] [blame] | 178 | |
Nikolay Vitkov | 99a9104 | 2025-01-23 14:38:14 | [diff] [blame] | 179 | override run(tests: PathPair[]) { |
| 180 | return super.run( |
Nikolay Vitkov | 65a5a91 | 2025-02-18 18:30:26 | [diff] [blame] | 181 | tests.map(test => ScriptPathPair.getFromPair(test)), |
Nikolay Vitkov | 99a9104 | 2025-01-23 14:38:14 | [diff] [blame] | 182 | [ |
| 183 | path.join(SOURCE_ROOT, 'node_modules', 'mocha', 'bin', 'mocha'), |
| 184 | ], |
| 185 | ); |
| 186 | } |
| 187 | |
| 188 | override match(path: PathPair): boolean { |
Nikolay Vitkov | c406636 | 2025-02-05 17:49:03 | [diff] [blame] | 189 | return [this.suite, ...this.extraPaths].some( |
| 190 | pathToCheck => isContainedInDirectory(path.sourcePath, pathToCheck.sourcePath)); |
Nikolay Vitkov | 99a9104 | 2025-01-23 14:38:14 | [diff] [blame] | 191 | } |
| 192 | } |
| 193 | |
Philip Pfaffe | ad89200 | 2024-04-12 07:39:44 | [diff] [blame] | 194 | class KarmaTests extends Tests { |
| 195 | override run(tests: PathPair[]) { |
| 196 | return super.run(tests, [ |
Jack Franklin | a36605e | 2024-04-22 08:38:04 | [diff] [blame] | 197 | 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 Pfaffe | ad89200 | 2024-04-12 07:39:44 | [diff] [blame] | 202 | ]); |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | // TODO(333423685) |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 207 | // - watch |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 208 | function main() { |
Nikolay Vitkov | 41c6911 | 2025-01-31 15:20:04 | [diff] [blame] | 209 | const tests: string[] = typeof options['tests'] === 'string' ? [options['tests']] : options['tests']; |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 210 | const testKinds = [ |
Philip Pfaffe | 96992fe | 2024-07-09 13:43:01 | [diff] [blame] | 211 | new KarmaTests(path.join(GEN_DIR, 'front_end'), path.join(GEN_DIR, 'inspector_overlay')), |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 212 | new MochaTests(path.join(GEN_DIR, 'test/interactions')), |
Philip Pfaffe | 6435eb4 | 2024-04-11 12:36:03 | [diff] [blame] | 213 | new MochaTests(path.join(GEN_DIR, 'test/e2e')), |
Liviu Rau | 34154dd | 2025-02-27 09:36:24 | [diff] [blame] | 214 | new NonHostedMochaTests(path.join(GEN_DIR, 'test/e2e_non_hosted')), |
Philip Pfaffe | 46fe343 | 2024-04-19 10:17:04 | [diff] [blame] | 215 | new MochaTests(path.join(GEN_DIR, 'test/perf')), |
Nikolay Vitkov | 99a9104 | 2025-01-23 14:38:14 | [diff] [blame] | 216 | 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 Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 219 | ]; |
| 220 | |
| 221 | if (!options['skip-ninja']) { |
Eric Leese | 460be15 | 2024-07-10 12:07:57 | [diff] [blame] | 222 | // 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 Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 233 | if (status) { |
| 234 | return status; |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | const suites = new Map<MochaTests, PathPair[]>(); |
Philip Pfaffe | a25e31f | 2024-07-09 14:09:12 | [diff] [blame] | 239 | 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 Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 244 | const repoPath = PathPair.get(t); |
| 245 | if (!repoPath) { |
| 246 | console.error(`Could not locate the test input for '${t}'`); |
| 247 | continue; |
| 248 | } |
| 249 | |
Philip Pfaffe | ad89200 | 2024-04-12 07:39:44 | [diff] [blame] | 250 | const suite = testKinds.find(kind => kind.match(repoPath)); |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 251 | if (suite === undefined) { |
Philip Pfaffe | 6435eb4 | 2024-04-11 12:36:03 | [diff] [blame] | 252 | console.error(`Unknown test suite for '${repoPath.sourcePath}'`); |
Philip Pfaffe | 705cfde | 2024-04-11 08:53:38 | [diff] [blame] | 253 | 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 | |
| 270 | process.exit(main()); |