test(engine): raise the ffmpeg-bound grouping mixes to a 120s timeout

`routing isolation` timed out at 30,000ms on the Windows runner again (#3401).
It is not a hang and not that PR's doing — that diff touches no engine code, and
on main's last passing Windows run the same test measured 10,050ms with the file
at 19,446ms. A ~10s test under a 30s cap on a runner with this much variance
fails on any bad host; 120s is ~12x the healthy time, so a timeout should mean a
real stall again.

Written as per-test arguments, replacing #3398's 30s ones. My earlier attempt at
this (#3397) added `vi.setConfig({ testTimeout: 60_000 })` instead, which would
have merged and changed nothing: an explicit per-test argument overrides the
file-level config. Verified directly — a 50ms argument beats a 60s setConfig.
This commit is contained in:
Vance Ingalls
2026-08-21 15:46:44 -07:00
parent 41af866bcb
commit 16a9c2971d
@@ -74,6 +74,17 @@ const track = (id: string, end: number, volume = 1) => ({
type: "audio" as const,
});
// Each case here runs at least two real ffmpeg mixes — the one under test plus
// a reference mix to compare it against — so it is seconds of work, not
// milliseconds. On the Windows runner `routing isolation` measures ~10s when the
// host is healthy (19.4s for the whole file) and blew the previous 30s cap on a
// noisy one, which is variance rather than a hang: nothing in the failing PRs
// touched engine code. 120s is ~12x the healthy time, so a timeout here should
// mean a genuine stall again.
//
// Per-test arguments rather than `vi.setConfig({ testTimeout })`: an explicit
// argument OVERRIDES the file-level config, so a setConfig line alongside these
// would silently do nothing.
describe.skipIf(!HAS_FFMPEG)("mix level arithmetic", () => {
afterEach(() => {
for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true });
@@ -108,7 +119,7 @@ describe.skipIf(!HAS_FFMPEG)("mix level arithmetic", () => {
// Two coherent copies of one tone = +6.02 dB. If amix's 1/N ever survives
// the correction, this lands at 0 dB instead.
expect(meanVolumeDb(twoUp) - meanVolumeDb(oneUp)).toBeCloseTo(6.02, 0);
}, 30_000);
}, 120_000);
it("does not lift the survivors when a shorter track ends", async () => {
// amix with normalize=true rescales by the number of CURRENTLY ACTIVE
@@ -145,7 +156,7 @@ describe.skipIf(!HAS_FFMPEG)("mix level arithmetic", () => {
const tailTogether = meanVolumeDb(together, 1.5, 3);
const tailAlone = meanVolumeDb(alone, 1.5, 3);
expect(Math.abs(tailTogether - tailAlone)).toBeLessThan(0.5);
}, 30_000);
}, 120_000);
/**
* The gate for group buses (plans/audio-mixer-groups.md §1).
@@ -193,7 +204,7 @@ describe.skipIf(!HAS_FFMPEG)("mix level arithmetic", () => {
// An empty group chain is pure routing — the export must read the same
// whether or not the two tones happened to share a group.
expect(Math.abs(meanVolumeDb(groupedOut) - meanVolumeDb(flatOut))).toBeLessThan(0.3);
}, 30_000);
}, 120_000);
it("a group FX chain fully cutting its members leaves an ungrouped track untouched (routing isolation)", async () => {
const projectDir = mkdtempSync(join(tmpdir(), "hf-grp-fx-"));
@@ -231,7 +242,7 @@ describe.skipIf(!HAS_FFMPEG)("mix level arithmetic", () => {
// and the ungrouped sfx track's own processing is unaffected by the
// group existing at all.
expect(Math.abs(meanVolumeDb(mixedOut) - meanVolumeDb(sfxAloneOut))).toBeLessThan(0.5);
}, 30_000);
}, 120_000);
it("a member's own volume envelope still applies inside a group", async () => {
const projectDir = mkdtempSync(join(tmpdir(), "hf-grp-env-"));
@@ -267,5 +278,5 @@ describe.skipIf(!HAS_FFMPEG)("mix level arithmetic", () => {
const groupedTail = meanVolumeDb(groupedOut, 3, 4);
const flatTail = meanVolumeDb(flatOut, 3, 4);
expect(Math.abs(groupedTail - flatTail)).toBeLessThan(0.5);
}, 30_000);
}, 120_000);
});