blob: 4770e2c06196725ad803adc82a5f06f743badeb6 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as childProcess from 'node:child_process';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import yargs from 'yargs';
import {hideBin} from 'yargs/helpers';
import {asArray, commandLineArgs, DiffBehaviors} from './commandline.js';
import {defaultChromePath, SOURCE_ROOT} from './paths.js';
import {shardFilter} from './sharding.js';
const argv = yargs(hideBin(process.argv)).parseSync()['_'] as string[];
const options = commandLineArgs(yargs(argv)).parseSync();
export const enum ServerType {
HOSTED_MODE = 'hosted-mode',
}
interface Config {
tests: string[];
artifactsDir: string;
chromeBinary: string;
serverType: ServerType;
debug: boolean;
headless: boolean;
coverage: boolean;
repetitions: number;
onDiff: {update: boolean|string[], throw: boolean};
shuffle: boolean;
mochaGrep: {invert?: boolean, grep?: string}|{invert?: boolean, fgrep?: string};
copyScreenshotGoldens: boolean;
retries: number;
configureChrome: (executablePath: string) => void;
cpuThrottle: number;
shardCount: number;
shardNumber: number;
shardBias: number;
}
function sliceArrayFromElement(array: string[], element: string) {
const index = array.lastIndexOf(element);
return index < 0 ? array : array.slice(index + 1);
}
const diffBehaviors = asArray(options['on-diff']);
// --diff=throw is the default, so set the option to true if there is either no --diff=no-throw or if it is overridden
// by a later --diff=throw
const onDiffThrow = !diffBehaviors.includes(DiffBehaviors.NO_THROW) ||
sliceArrayFromElement(diffBehaviors, DiffBehaviors.NO_THROW).includes(DiffBehaviors.THROW);
// --diff=no-update overrules any previous --diff=update or --diff=update=X.
const onDiffUpdate =
sliceArrayFromElement(diffBehaviors, DiffBehaviors.NO_UPDATE).filter(v => v.startsWith(DiffBehaviors.UPDATE));
// --diff=update overrules any previous --diff=update=X. Subsequent --diff=update=X overrule any previous --diff=update.
const diffUpdateFilters =
sliceArrayFromElement(onDiffUpdate, DiffBehaviors.UPDATE).map(v => v.substr(v.indexOf('=') + 1));
const onDiffUpdateAll = onDiffUpdate.length > 0 && diffUpdateFilters.length === 0;
const onDiffUpdateSelected = onDiffUpdate.length > 0 ? diffUpdateFilters : false;
function mochaGrep(): Config['mochaGrep'] {
if (!(options['grep'] ?? options['fgrep'])) {
return {};
}
const isFixed = Boolean(options['fgrep']);
const grep: Config['mochaGrep'] = isFixed ? {fgrep: options['fgrep']} : {grep: options['grep']};
if (options['invert']) {
grep.invert = true;
}
return grep;
}
function getTestsFromOptions() {
const tests = options['tests'];
if (Array.isArray(tests)) {
return tests;
}
if (typeof tests === 'string') {
return [tests];
}
return [];
}
function runProcess(exe: string, args: string[], options: childProcess.SpawnSyncOptionsWithStringEncoding) {
return childProcess.spawnSync(exe, args, options);
}
function configureChrome(executablePath: string) {
if (os.type() === 'Windows_NT') {
const result = runProcess(
'python3',
[
path.join(SOURCE_ROOT, 'scripts', 'deps', 'set_lpac_acls.py'),
path.dirname(executablePath),
],
{encoding: 'utf-8', stdio: 'inherit', shell: true});
if (result.error || (result.status ?? 1) !== 0) {
throw new Error('Setting permissions failed: ' + result.error?.message);
}
}
}
export const TestConfig: Config = {
tests: getTestsFromOptions(),
artifactsDir: options['artifacts-dir'] || SOURCE_ROOT,
chromeBinary: options['chrome-binary'] ?? defaultChromePath(),
serverType: ServerType.HOSTED_MODE,
debug: options['debug'],
headless: options['headless'] === undefined ? !options['debug'] : options['headless'],
coverage: options['coverage'],
repetitions: options['repeat'],
onDiff: {
update: onDiffUpdateAll || onDiffUpdateSelected,
throw: onDiffThrow,
},
shuffle: options['shuffle'],
mochaGrep: mochaGrep(),
copyScreenshotGoldens: false,
retries: options['retries'],
configureChrome,
cpuThrottle: options['cpu-throttle'],
shardCount: options['shard-count'],
shardNumber: options['shard-number'],
shardBias: options['shard-bias'],
};
export function loadTests(testDirectory: string, filename = 'tests.txt') {
const tests = fs.readFileSync(path.join(testDirectory, filename))
.toString()
.split('\n')
.map(t => t.trim())
.filter(t => t.length > 0)
.map(t => path.normalize(path.join(testDirectory, t)))
.filter(t => TestConfig.tests.some((spec: string) => t.startsWith(spec)))
.filter(t => shardFilter(TestConfig, t));
if (TestConfig.shuffle) {
for (let i = tests.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[tests[i], tests[j]] = [tests[j], tests[i]];
}
}
return tests;
}