mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 14:34:42 +01:00
fix(windows-seed): record slow Windows files in full instead of at the 6-minute deadline
The first Windows recording on eca247e99 (run 39008249142) killed
test/gstack-doctor.test.ts at the 360 s per-file deadline and would have
seeded it at 360 s, although it takes about 484 s on Windows. --record-durations
now honors an explicit --wall-timeout as its per-file deadline, and the
windows-record-durations job passes --wall-timeout 1200 (inside its 30-minute
job limit). The shard job's 15-minute timeout and the shard count are unchanged.
The recording job also uploads its full per-file logs, so a file that fails
while recording (browse/test/cookie-import-native-job.test.ts did in that run)
can be diagnosed.
Intentional pin change: test/windows-native-workflows.test.ts now expects
the --wall-timeout 1200 recording command and the log upload step.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
eca247e99a
commit
3115004917
4 files changed
+33
-5
No files matched your search
@@ -235,11 +235,21 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
TEMP: ${{ runner.temp }}
|
TEMP: ${{ runner.temp }}
|
||||||
TMP: ${{ runner.temp }}
|
TMP: ${{ runner.temp }}
|
||||||
run: bun run test:windows --record-durations
|
run: bun run test:windows --record-durations --wall-timeout 1200
|
||||||
- name: Name the recording as the Windows seed
|
- name: Name the recording as the Windows seed
|
||||||
if: always()
|
if: always()
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cp scripts/free-test-durations.json "$RUNNER_TEMP/free-test-durations-windows.json"
|
run: cp scripts/free-test-durations.json "$RUNNER_TEMP/free-test-durations-windows.json"
|
||||||
|
- name: Upload full recording logs
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
|
||||||
|
with:
|
||||||
|
name: windows-record-durations-logs
|
||||||
|
path: |
|
||||||
|
.context/free-test-logs/gstack-free-test-*.log
|
||||||
|
${{ runner.temp }}/gstack-free-test-*.log
|
||||||
|
include-hidden-files: true
|
||||||
|
if-no-files-found: ignore
|
||||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -1707,13 +1707,21 @@ export function exitCodeFor(status: FreeShardStatus): number {
|
|||||||
return status === 'timed-out' ? 124 : 1;
|
return status === 'timed-out' ? 124 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Per-file deadline for `--record-durations`: an explicit --wall-timeout, else the one-file default. */
|
||||||
|
export function recordingWallTimeoutMs(options: Pick<CliOptions, 'wallTimeoutExplicit' | 'wallTimeoutMs'>): number {
|
||||||
|
return options.wallTimeoutExplicit ? options.wallTimeoutMs : wallTimeoutForShard(1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `--record-durations`: time every file in its own child (exact per-file wall,
|
* `--record-durations`: time every file in its own child (exact per-file wall,
|
||||||
* immune to bun's stream buffering) and write the committed seed atomically.
|
* immune to bun's stream buffering) and write the committed seed atomically.
|
||||||
* Uses the same isolated, strictly classified children as a normal run.
|
* Uses the same isolated, strictly classified children as a normal run.
|
||||||
* Run against an immutable checkout; never time while editing its inputs.
|
* Run against an immutable checkout; never time while editing its inputs.
|
||||||
|
* An explicit --wall-timeout sets the per-file deadline, so a file slower than
|
||||||
|
* the default deadline is timed in full instead of being killed and recorded
|
||||||
|
* at the deadline.
|
||||||
*/
|
*/
|
||||||
async function recordFreeTestDurations(files: string[], jobs: number): Promise<number> {
|
async function recordFreeTestDurations(files: string[], jobs: number, wallTimeoutMs: number): Promise<number> {
|
||||||
const durations: Record<string, number> = {};
|
const durations: Record<string, number> = {};
|
||||||
const failed: string[] = [];
|
const failed: string[] = [];
|
||||||
const expectedCount = files.length;
|
const expectedCount = files.length;
|
||||||
@@ -1722,7 +1730,7 @@ async function recordFreeTestDurations(files: string[], jobs: number): Promise<n
|
|||||||
// As in full-suite mode, finish parallel work before exclusive host-state fixtures.
|
// As in full-suite mode, finish parallel work before exclusive host-state fixtures.
|
||||||
await forEachFileAlone(files.filter(file => !(file in TREE_MUTATING)), files.filter(file => file in TREE_MUTATING), jobs, async (file, index) => {
|
await forEachFileAlone(files.filter(file => !(file in TREE_MUTATING)), files.filter(file => file in TREE_MUTATING), jobs, async (file, index) => {
|
||||||
const outcome = await runFreeShard([file], index + 1, files.length, {
|
const outcome = await runFreeShard([file], index + 1, files.length, {
|
||||||
wallTimeoutMs: wallTimeoutForShard(1), quiet: true, homeGuard: sharedFreeHome,
|
wallTimeoutMs, quiet: true, homeGuard: sharedFreeHome,
|
||||||
});
|
});
|
||||||
durations[normalizeRelativePath(file)] = outcome.elapsedMs;
|
durations[normalizeRelativePath(file)] = outcome.elapsedMs;
|
||||||
if (outcome.status !== 'passed') failed.push(file);
|
if (outcome.status !== 'passed') failed.push(file);
|
||||||
@@ -1907,7 +1915,7 @@ async function main(): Promise<number> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.recordDurations) {
|
if (options.recordDurations) {
|
||||||
return recordFreeTestDurations(files, fullSuiteJobs());
|
return recordFreeTestDurations(files, fullSuiteJobs(), recordingWallTimeoutMs(options));
|
||||||
}
|
}
|
||||||
if (options.attributeHome) return attributeFreeHomeWriters(files.filter(file => !(file in TREE_MUTATING)), files.filter(file => file in TREE_MUTATING),
|
if (options.attributeHome) return attributeFreeHomeWriters(files.filter(file => !(file in TREE_MUTATING)), files.filter(file => file in TREE_MUTATING),
|
||||||
fullSuiteJobs(), (file, index, homeGuard) => runFreeShard([file], index + 1, files.length, { wallTimeoutMs: wallTimeoutForShard(1), quiet: true, log: () => {}, homeGuard }));
|
fullSuiteJobs(), (file, index, homeGuard) => runFreeShard([file], index + 1, files.length, { wallTimeoutMs: wallTimeoutForShard(1), quiet: true, log: () => {}, homeGuard }));
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import {
|
|||||||
DEFAULT_WALL_TIMEOUT_MS,
|
DEFAULT_WALL_TIMEOUT_MS,
|
||||||
PER_FILE_WALL_MS,
|
PER_FILE_WALL_MS,
|
||||||
wallTimeoutForShard,
|
wallTimeoutForShard,
|
||||||
|
recordingWallTimeoutMs,
|
||||||
|
parseCliOptions,
|
||||||
KNOWN_WINDOWS_INCOMPATIBLE,
|
KNOWN_WINDOWS_INCOMPATIBLE,
|
||||||
TEST_ROOTS,
|
TEST_ROOTS,
|
||||||
TREE_MUTATING,
|
TREE_MUTATING,
|
||||||
@@ -940,6 +942,11 @@ describe('test-free-shards: wall-timeout scaling', () => {
|
|||||||
test('an explicit base above the scaled value wins', () => {
|
test('an explicit base above the scaled value wins', () => {
|
||||||
expect(wallTimeoutForShard(10, 10 * 60_000)).toBe(10 * 60_000);
|
expect(wallTimeoutForShard(10, 10 * 60_000)).toBe(10 * 60_000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('--record-durations times each file under the one-file default unless --wall-timeout is explicit', () => {
|
||||||
|
expect(recordingWallTimeoutMs(parseCliOptions(['--record-durations']))).toBe(wallTimeoutForShard(1));
|
||||||
|
expect(recordingWallTimeoutMs(parseCliOptions(['--record-durations', '--wall-timeout', '1200']))).toBe(1_200_000);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -62,11 +62,14 @@ test('a record_durations dispatch times each Windows-safe file alone and uploads
|
|||||||
expect(job.if).toBe('${{ inputs.record_durations }}');
|
expect(job.if).toBe('${{ inputs.record_durations }}');
|
||||||
expect(job['runs-on']).toBe('windows-latest');
|
expect(job['runs-on']).toBe('windows-latest');
|
||||||
const record = job.steps.find((step: any) => step.name === 'Time every Windows-safe file alone');
|
const record = job.steps.find((step: any) => step.name === 'Time every Windows-safe file alone');
|
||||||
expect(record.run).toBe('bun run test:windows --record-durations');
|
expect(record.run).toBe('bun run test:windows --record-durations --wall-timeout 1200');
|
||||||
expect(record.env.GSTACK_FREE_TEST_DURATIONS).toBeUndefined();
|
expect(record.env.GSTACK_FREE_TEST_DURATIONS).toBeUndefined();
|
||||||
const rename = job.steps.find((step: any) => step.name === 'Name the recording as the Windows seed');
|
const rename = job.steps.find((step: any) => step.name === 'Name the recording as the Windows seed');
|
||||||
expect(rename.if).toBe('always()');
|
expect(rename.if).toBe('always()');
|
||||||
expect(rename.run).toBe('cp scripts/free-test-durations.json "$RUNNER_TEMP/free-test-durations-windows.json"');
|
expect(rename.run).toBe('cp scripts/free-test-durations.json "$RUNNER_TEMP/free-test-durations-windows.json"');
|
||||||
|
const logs = job.steps.find((step: any) => step.name === 'Upload full recording logs');
|
||||||
|
expect(logs.if).toBe('always()');
|
||||||
|
expect(logs.with['include-hidden-files']).toBe(true);
|
||||||
expect(job.steps.at(-1).with.name).toBe('free-test-durations-windows');
|
expect(job.steps.at(-1).with.name).toBe('free-test-durations-windows');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user