diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index e850b42a7..a00f86dcc 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -41,6 +41,7 @@ jobs: skip: --noFirefox runs-on: ${{ matrix.os }} + environment: code-coverage steps: - name: Checkout repository @@ -75,13 +76,13 @@ jobs: if: ${{ matrix.os == 'windows-latest' }} run: Set-DisplayResolution -Width 1920 -Height 1080 -Force - - name: Run integration tests (Windows) + - name: Run integration tests with code coverage (Windows) if: ${{ matrix.os == 'windows-latest' }} - run: npx gulp integrationtest ${{ matrix.skip }} + run: npx gulp integrationtest --coverage --coverage-output build/coverage/integration ${{ matrix.skip }} - - name: Run integration tests (Linux) + - name: Run integration tests with code coverage (Linux) if: ${{ matrix.os == 'ubuntu-latest' }} - run: xvfb-run -a --server-args="-screen 0, 1920x1080x24" npx gulp integrationtest ${{ matrix.skip }} + run: xvfb-run -a --server-args="-screen 0, 1920x1080x24" npx gulp integrationtest --coverage --coverage-output build/coverage/integration ${{ matrix.skip }} - name: Save cached PDF files uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 @@ -89,3 +90,15 @@ jobs: path: test/pdfs/*.pdf key: cached-pdf-files-${{ hashFiles('test/pdfs/*.pdf') }} enableCrossOsArchive: true + + - name: Upload results to Codecov + uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true + files: ./build/coverage/integration/lcov.info + flags: integrationtest + name: codecov-umbrella + disable_search: true + disable_telem: true + verbose: true diff --git a/src/core/worker.js b/src/core/worker.js index 9dd67e783..e089f47d1 100644 --- a/src/core/worker.js +++ b/src/core/worker.js @@ -1036,6 +1036,9 @@ class WorkerMessageHandler { .getPage(data.pageIndex) .then(page => page.annotations.map(a => a.toString())); }); + handler.on("GetWorkerCoverage", function () { + return globalThis.__coverage__ ?? {}; + }); } return workerHandlerName; diff --git a/test/coverage_utils.js b/test/coverage_utils.js index 118e63593..3222e224e 100644 --- a/test/coverage_utils.js +++ b/test/coverage_utils.js @@ -15,15 +15,15 @@ // Istanbul coverage objects use s (statements), b (branches), and f (functions) // as shorthand keys for the hit-count maps. -function mergeWorkerCoverageIntoWindow(coverage) { +function mergeCoverageIntoGlobal(coverage) { if (!coverage || Object.keys(coverage).length === 0) { return; } - window.__coverage__ ??= {}; + globalThis.__coverage__ ??= {}; for (const [key, fileCoverage] of Object.entries(coverage)) { - const existing = window.__coverage__[key]; + const existing = globalThis.__coverage__[key]; if (!existing) { - window.__coverage__[key] = fileCoverage; + globalThis.__coverage__[key] = fileCoverage; continue; } for (const id of Object.keys(fileCoverage.s)) { @@ -49,10 +49,10 @@ async function fetchAndMergeWorkerCoverage(pdfWorker) { "GetWorkerCoverage", null ); - mergeWorkerCoverageIntoWindow(coverage); + mergeCoverageIntoGlobal(coverage); } catch (e) { console.warn(`Failed to collect worker coverage: ${e}`); } } -export { fetchAndMergeWorkerCoverage, mergeWorkerCoverageIntoWindow }; +export { fetchAndMergeWorkerCoverage, mergeCoverageIntoGlobal }; diff --git a/test/integration/test_utils.mjs b/test/integration/test_utils.mjs index b0c16f856..4146fcd7e 100644 --- a/test/integration/test_utils.mjs +++ b/test/integration/test_utils.mjs @@ -13,6 +13,7 @@ * limitations under the License. */ +import { mergeCoverageIntoGlobal } from "../coverage_utils.js"; import os from "os"; const isMac = os.platform() === "darwin"; @@ -149,11 +150,42 @@ function closePages(pages) { } async function closeSinglePage(page) { - // Avoid to keep something from a previous test. - await page.evaluate(async () => { + const coverage = await page.evaluate(async () => { + // Collect coverage data from the worker before the document is closed. + let workerCoverage = null; + const handler = + window.PDFViewerApplication.pdfDocument?._transport?.messageHandler; + if (handler) { + try { + workerCoverage = await handler.sendWithPromise( + "GetWorkerCoverage", + null + ); + } catch {} + } + + // Close the viewer gracefully, and clear local storage to avoid state + // leaking from one test to another. await window.PDFViewerApplication.testingClose(); window.localStorage.clear(); + + // Serialize the coverage data to a JSON string because that is a lot + // faster/cheaper to transfer from the browser to Node.js over the WebDriver + // BiDi protocol, otherwise Puppeteer's (significantly slower) serialization + // logic kicks in (see https://github.com/puppeteer/puppeteer/issues/2427). + return { + page: window.__coverage__ ? JSON.stringify(window.__coverage__) : null, + worker: workerCoverage ? JSON.stringify(workerCoverage) : null, + }; }); + + if (coverage.page) { + mergeCoverageIntoGlobal(JSON.parse(coverage.page)); + } + if (coverage.worker) { + mergeCoverageIntoGlobal(JSON.parse(coverage.worker)); + } + await page.close({ runBeforeUnload: false }); } diff --git a/test/test.mjs b/test/test.mjs index 62d813b34..fde169a81 100644 --- a/test/test.mjs +++ b/test/test.mjs @@ -890,6 +890,7 @@ async function startIntegrationTest() { sessions[0].numRuns = results.runs; sessions[0].numErrors = results.failures; sessions[0].failures = results.failureList; + sessions[0].coverage = globalThis.__coverage__; await Promise.all(sessions.map(session => closeSession(session.name))); } diff --git a/test/unit/jasmine-boot.js b/test/unit/jasmine-boot.js index 6c10293ee..6ffb29784 100644 --- a/test/unit/jasmine-boot.js +++ b/test/unit/jasmine-boot.js @@ -42,7 +42,7 @@ import { GlobalWorkerOptions } from "pdfjs/display/worker_options.js"; import { isNodeJS } from "../../src/shared/util.js"; -import { mergeWorkerCoverageIntoWindow } from "../coverage_utils.js"; +import { mergeCoverageIntoGlobal } from "../coverage_utils.js"; import { MessageHandler } from "pdfjs/shared/message_handler.js"; import { PDFWorker } from "pdfjs/display/api.js"; import { TestReporter } from "../reporter.js"; @@ -156,7 +156,7 @@ function installWorkerCoverageHook() { const handler = new MessageHandler("main", "worker", webWorker); const promise = handler .sendWithPromise("GetWorkerCoverage", null) - .then(mergeWorkerCoverageIntoWindow) + .then(mergeCoverageIntoGlobal) .catch(e => { console.warn(`Failed to collect worker coverage: ${e}`); })