Files
electron/script/gen-spec-weights.js
claude[bot]andClaude f851e5e95b ci: run reduced test subsets on macOS x64 and mas (#54013)
Pull requests now run the macOS x64 test legs and the mas test legs as a
single shard each over a curated allowlist of spec files
(script/macos-x64-spec-allowlist.txt, script/macos-mas-spec-allowlist.txt),
while the darwin arm64 legs keep running the whole suite. The whole suite
still runs on every macOS leg for pushes to main and release branches, for
roller/ pull requests and for pull requests carrying the run-full-macos
label. This takes a PR from 13 macOS runners to 8.

The gate is a new full-macos-tests boolean input on the build-and-test
pipelines and the test segment, computed in build.yml from the event name,
head ref and PR labels, and defaulting to true for every other caller.


Claude-Session: https://claude.ai/code/session_015NrhHJaEg6V3V9G6yF9SHA

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-18 02:02:27 +02:00

78 lines
2.9 KiB
JavaScript

// Regenerates script/spec-weights.json (seconds per spec file for each CI
// test job, used by script/split-tests.js) from the spec-timings.json files
// the test jobs upload in their test_artifacts_* bundles.
//
// Usage, from a recent green build.yml run on the branch (one with full macOS
// legs: a push to main or a PR carrying the run-full-macos label, since the
// reduced PR legs only time the spec files in script/macos-*-spec-allowlist.txt):
// gh run download <run-id> --repo electron/electron -D /tmp/spec-timings -p 'test_artifacts_*'
// node script/gen-spec-weights.js /tmp/spec-timings
//
// Each job gets its own table, keyed as its artifact is named:
// `<build type>_<arch>[_<sanitizer>]`. Where several shards of one job cover
// a file (a retry) the largest time wins. The Wayland job runs an allowlist
// and is left out.
const fs = require('node:fs');
const path = require('node:path');
const roots = process.argv.slice(2);
if (!roots.length) {
console.error('Usage: node script/gen-spec-weights.js <dir-with-test-artifacts> [...]');
process.exit(1);
}
const findTimings = (dir, found = []) => {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, entry.name);
if (entry.isDirectory()) findTimings(p, found);
else if (entry.name === 'spec-timings.json') found.push(p);
}
return found;
};
const BUILD_TYPES = { darwin: 'darwin', linux: 'linux', win32: 'win' };
// `darwin_x64`, `mas_arm64`, `linux_x64_asan`, ... - what split-tests.js reads
// from ARTIFACT_KEY in CI. Older timing files carry no sanitizer field; for
// those the artifact directory name says which job wrote them.
const jobKeyOf = (timings, file) => {
const buildType = timings.mas ? 'mas' : (BUILD_TYPES[timings.platform] ?? timings.platform);
const sanitizer = timings.sanitizer ?? (/_(asan|ubsan)_/.exec(file)?.[1] || null);
return `${buildType}_${timings.arch}${sanitizer ? `_${sanitizer}` : ''}`;
};
const weights = {};
let inputs = 0;
for (const root of roots) {
for (const file of findTimings(root)) {
if (/_wayland_/.test(file)) continue;
const timings = JSON.parse(fs.readFileSync(file, 'utf8'));
const key = jobKeyOf(timings, file);
inputs++;
weights[key] ??= {};
for (const [spec, seconds] of Object.entries(timings.files)) {
weights[key][spec] = Math.max(weights[key][spec] ?? 0, Math.round(seconds));
}
}
}
if (!inputs) {
console.error('No spec-timings.json files found under', roots.join(', '));
process.exit(1);
}
const sorted = {};
for (const key of Object.keys(weights).sort()) {
sorted[key] = Object.fromEntries(Object.entries(weights[key]).sort(([a], [b]) => a.localeCompare(b)));
}
const outPath = path.resolve(__dirname, 'spec-weights.json');
fs.writeFileSync(outPath, JSON.stringify(sorted, null, 2) + '\n');
console.log(
`Wrote ${outPath} from ${inputs} timing files:`,
Object.entries(sorted)
.map(([key, f]) => `${key} ${Object.keys(f).length} specs`)
.join(', ')
);