From db5a1dc13195afb46ceb28d25492b5d864bd7922 Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Wed, 28 Oct 2026 13:31:05 -0700 Subject: [PATCH] fix(ci): write browser coverage without each script's source (#432) Every raw browser coverage file repeated the full source of each mapped script, about 84% of its bytes, and the frontend coverage merge ran out of Node's heap at 1,884 files. c8 reads the source from the mapped file on disk, so the entries keep only the URL, script id and functions. --- tests/vendor/browser-coverage.test.mjs | 20 ++++++++++++++++++++ tools/vendor/browser-coverage.mjs | 5 ++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/vendor/browser-coverage.test.mjs b/tests/vendor/browser-coverage.test.mjs index 39dcde57..a0336c49 100644 --- a/tests/vendor/browser-coverage.test.mjs +++ b/tests/vendor/browser-coverage.test.mjs @@ -27,3 +27,23 @@ test('browser coverage maps nested JS/MJS and excludes unrelated and traversal p const root = fileURLToPath(new URL('../../web/js/', import.meta.url)); assert.deepEqual(report.result.map(entry => fileURLToPath(entry.url)), [join(root, 'gallery.js'), join(root, 'shared/controls/button.mjs'), join(root, 'shared/panel.js')]); }); + +// #432: every raw file repeated each script's full source (about 84% of its bytes) and the coverage merge ran out of +// memory. c8 reads the source from the mapped file on disk, so the entries keep only the URL, id and functions. +test('browser coverage writes no script source, only what c8 maps', async t => { + const directory = mkdtempSync(join(tmpdir(), 'pm-browser-coverage-')); + const previous = process.env.PM_JS_COVERAGE; + process.env.PM_JS_COVERAGE = directory; + t.after(() => { + if (previous === undefined) delete process.env.PM_JS_COVERAGE; + else process.env.PM_JS_COVERAGE = previous; + rmSync(directory, { recursive: true, force: true }); + }); + const functions = [{ functionName: 'f', isBlockCoverage: true, ranges: [{ startOffset: 0, endOffset: 9, count: 1 }] }]; + await saveCoverage({ coverage: { stopJSCoverage: async () => [{ url: 'http://127.0.0.1:8765/prompt_manager/js/gallery.js', + scriptId: '7', source: 'x'.repeat(100_000), functions }] } }, 'chromium'); + const report = JSON.parse(readFileSync(join(directory, readdirSync(directory)[0]), 'utf8')); + assert.equal(report.result.length, 1); + assert.deepEqual(Object.keys(report.result[0]).sort(), ['functions', 'scriptId', 'url']); + assert.deepEqual(report.result[0].functions, functions); +}); diff --git a/tools/vendor/browser-coverage.mjs b/tools/vendor/browser-coverage.mjs index 4aaee07e..6655096c 100644 --- a/tools/vendor/browser-coverage.mjs +++ b/tools/vendor/browser-coverage.mjs @@ -12,7 +12,10 @@ export async function startCoverage(page, browserName) { export async function saveCoverage(page, browserName) { if (!process.env.PM_JS_COVERAGE || browserName !== 'chromium') return; const entries = await page.coverage.stopJSCoverage(); - const result = mapBrowserCoverage(entries, { urlPrefix: '/prompt_manager/js/', sourceRoot: join(repositoryRoot, 'web/js') }); + // The source is left out (#432): c8 reads it from the mapped file on disk, and repeating every script's text in every + // raw file made the coverage merge run out of memory. + const result = mapBrowserCoverage(entries, { urlPrefix: '/prompt_manager/js/', sourceRoot: join(repositoryRoot, 'web/js') }) + .map(({ source: _source, ...entry }) => entry); mkdirSync(process.env.PM_JS_COVERAGE, { recursive: true }); writeFileSync(join(process.env.PM_JS_COVERAGE, `browser-${randomUUID()}.json`), JSON.stringify({ result })); }