From 494dcb84867415e27186db196306b285771a5525 Mon Sep 17 00:00:00 2001 From: "M.Schmidt" Date: Fri, 17 Jul 2026 00:06:53 +0200 Subject: [PATCH] fix/address flaky wall clock based test by doing best of 3 --- .../api/endpoints/deltaUpload.hash.test.ts | 84 ++++++++++++------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/frontend/src/lib/api/endpoints/deltaUpload.hash.test.ts b/frontend/src/lib/api/endpoints/deltaUpload.hash.test.ts index 5d316ce7..31d7c73b 100644 --- a/frontend/src/lib/api/endpoints/deltaUpload.hash.test.ts +++ b/frontend/src/lib/api/endpoints/deltaUpload.hash.test.ts @@ -24,6 +24,7 @@ describe('worker-pool hashing (architecture gate)', () => { // on the main thread, serially. const nFiles = 24; const size = 4 * 1024 * 1024; + const trials = 3; const dir = await fs.mkdtemp(join(tmpdir(), 'hashbench-')); const paths: string[] = []; for (let i = 0; i < nFiles; i++) { @@ -35,12 +36,14 @@ describe('worker-pool hashing (architecture gate)', () => { } // Sequential (old): read + hash on the calling thread. - const t0 = performance.now(); - for (const p of paths) { - const b = await fs.readFile(p); - createHash('sha256').update(b).digest('hex'); - } - const seqMs = performance.now() - t0; + const runSequential = async () => { + const t0 = performance.now(); + for (const p of paths) { + const b = await fs.readFile(p); + createHash('sha256').update(b).digest('hex'); + } + return performance.now() - t0; + }; // 3-lane pool (new): each worker reads + hashes its own files. const lanes = 3; @@ -53,35 +56,52 @@ describe('worker-pool hashing (architecture gate)', () => { parentPort.postMessage(createHash('sha256').update(b).digest('hex')); }); `; - const workers = Array.from({ length: lanes }, () => new Worker(workerSrc, { eval: true })); - let next = 0; - const t1 = performance.now(); - await Promise.all( - workers.map( - (w) => - new Promise((resolve, reject) => { - const feed = () => { - if (next >= paths.length) { - resolve(); - return; - } - const i = next++; - w.once('message', () => feed()); - w.once('error', reject); - w.postMessage(paths[i]); - }; - feed(); - }) - ) - ); - const poolMs = performance.now() - t1; - await Promise.all(workers.map((w) => w.terminate())); + const runPooled = async () => { + const workers = Array.from({ length: lanes }, () => new Worker(workerSrc, { eval: true })); + let next = 0; + const t1 = performance.now(); + await Promise.all( + workers.map( + (w) => + new Promise((resolve, reject) => { + const feed = () => { + if (next >= paths.length) { + resolve(); + return; + } + const i = next++; + w.once('message', () => feed()); + w.once('error', reject); + w.postMessage(paths[i]); + }; + feed(); + }) + ) + ); + const ms = performance.now() - t1; + await Promise.all(workers.map((w) => w.terminate())); + return ms; + }; + + // Best-of-`trials` wall-clock per strategy: a single sample is prone + // to scheduler/GC noise on a loaded machine, which can tip either + // side when the two are close. Noise only ever adds delay, so the + // minimum across trials is each strategy's true achievable time — + // a genuine architecture regression still fails every trial. + const seqTimes: number[] = []; + const poolTimes: number[] = []; + for (let i = 0; i < trials; i++) { + seqTimes.push(await runSequential()); + poolTimes.push(await runPooled()); + } + const seqMs = Math.min(...seqTimes); + const poolMs = Math.min(...poolTimes); + await fs.rm(dir, { recursive: true, force: true }); - // eslint-disable-next-line no-console console.info( - `read+hash ${nFiles} x 4 MiB: sequential ${seqMs.toFixed(0)} ms vs 3-lane pool ${poolMs.toFixed(0)} ms (${(seqMs / poolMs).toFixed(1)}x)` + `read+hash ${nFiles} x 4 MiB over ${trials} trials: best sequential ${seqMs.toFixed(0)} ms vs best 3-lane pool ${poolMs.toFixed(0)} ms (${(seqMs / poolMs).toFixed(1)}x)` ); expect(poolMs).toBeLessThan(seqMs); - }); + }, 20000); });