From 26447d7ca7f81a0fab0e467ca7ffae715279fda7 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Wed, 6 Apr 2022 15:38:10 +0200 Subject: [PATCH] almost working coverage --- package.json | 2 ++ src/index.js | 57 ++++++++++++++++++++++++++++++++++++++++---- src/worker-runner.js | 20 ++++++++++++++-- yarn.lock | 4 +++- 4 files changed, 76 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index d74e426..c4010ff 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ }, "dependencies": { "@jest/expect": "^29.0.1", + "@jest/transform": "^29.0.1", + "collect-v8-coverage": "^1.0.1", "jest-circus": "^29.0.1", "jest-each": "^29.0.1", "jest-mock": "^29.0.1", diff --git a/src/index.js b/src/index.js index 909d42c..74038f8 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,8 @@ import { Piscina } from "piscina"; import supportsColor from "supports-color"; import { MessageChannel } from "worker_threads"; +import { shouldInstrument } from "@jest/transform"; +import { fileURLToPath } from "url"; /** @typedef {import("@jest/test-result").Test} Test */ @@ -8,9 +10,11 @@ export default class LightRunner { // TODO: Use real private fields when we drop support for Node.js v12 _config; _piscina; + _testContext; - constructor(config) { + constructor(config, testContext) { this._config = config; + this._testContext = testContext; // Jest's logic to decide when to spawn workers and when to run in the // main thread is quite complex: @@ -36,6 +40,36 @@ export default class LightRunner { }); } + filterCoverage(result, projectConfig) { + if (!result.v8Coverage) { + return result; + } + + const coverageOptions = { + changedFiles: this._testContext.changedFiles, + collectCoverage: true, + collectCoverageFrom: this._config.collectCoverageFrom, + collectCoverageOnlyFrom: this._config.collectCoverageOnlyFrom, + coverageProvider: this._config.coverageProvider, + sourcesRelatedToTestsInChangedFiles: + this._testContext.sourcesRelatedToTestsInChangedFiles, + }; + + return { + ...result, + v8Coverage: result.v8Coverage + .filter(res => res.url.startsWith("file://")) + .map(res => ({ ...res, url: fileURLToPath(res.url) })) + .filter( + ({ url }) => + // TODO: will this work on windows? It might be better if `shouldInstrument` deals with it anyways + url.startsWith(projectConfig.rootDir) && + shouldInstrument(url, coverageOptions, projectConfig) + ) + .map(result => ({ result })), + }; + } + /** * @param {Array} tests * @param {*} watcher @@ -44,7 +78,12 @@ export default class LightRunner { * @param {*} onFailure */ runTests(tests, watcher, onStart, onResult, onFailure) { - const { updateSnapshot, testNamePattern } = this._config; + const { + updateSnapshot, + testNamePattern, + collectCoverage, + coverageProvider, + } = this._config; return Promise.all( tests.map(test => { @@ -54,11 +93,21 @@ export default class LightRunner { return this._piscina .run( - { test, updateSnapshot, testNamePattern, port: mc.port1 }, + { + test, + updateSnapshot, + testNamePattern, + port: mc.port1, + collectV8Coverage: collectCoverage && coverageProvider === "v8", + }, { transferList: [mc.port1] } ) .then( - result => void onResult(test, result), + result => + void onResult( + test, + this.filterCoverage(result, test.context.config) + ), error => void onFailure(test, error) ); }) diff --git a/src/worker-runner.js b/src/worker-runner.js index 6dd8db4..19250a1 100644 --- a/src/worker-runner.js +++ b/src/worker-runner.js @@ -6,6 +6,7 @@ import { expect } from "expect"; import * as circus from "jest-circus"; import { inspect } from "util"; import { isWorkerThread } from "piscina"; +import { CoverageInstrumenter } from "collect-v8-coverage"; /** @typedef {{ failures: number, passes: number, pending: number, start: number, end: number }} Stats */ /** @typedef {{ ancestors: string[], title: string, duration: number, errors: Error[], skipped: boolean }} InternalTestResult */ @@ -54,6 +55,7 @@ export default async function run({ updateSnapshot, testNamePattern, port, + collectV8Coverage, }) { const projectSnapshotSerializers = await initialSetup(test.context.config); @@ -67,6 +69,13 @@ export default async function run({ /** @type {Array} */ const results = []; + let instrumenter = null; + if (collectV8Coverage) { + instrumenter = new CoverageInstrumenter(); + + await instrumenter.startInstrumenting(); + } + const { tests, hasFocusedTests } = await loadTests(test.path); const snapshotResolver = await snapshot.buildSnapshotResolver( @@ -86,13 +95,18 @@ export default async function run({ await runTestBlock(tests, hasFocusedTests, testNamePatternRE, results, stats); stats.end = performance.now(); + let v8Coverage = undefined; + if (instrumenter) { + v8Coverage = await instrumenter.stopInstrumenting(); + } + snapshotState.save(); // Restore the project-level serializers, so that serializers // installed by one test file don't leak to other files. arrayReplace(snapshot.getSerializers(), projectSnapshotSerializers); - return toTestResult(stats, results, test); + return toTestResult(stats, results, test, v8Coverage); } async function loadTests(testFile) { @@ -236,9 +250,10 @@ function callAsync(fn) { * @param {Stats} stats * @param {Array} tests * @param {import("@jest/test-result").Test} testInput + * @param {import("@jest/test-result").V8CoverageResult} v8Coverage * @returns {import("@jest/test-result").TestResult} */ -function toTestResult(stats, tests, { path, context }) { +function toTestResult(stats, tests, { path, context }, v8Coverage) { const { start, end } = stats; const runtime = end - start; @@ -285,6 +300,7 @@ function toTestResult(stats, tests, { path, context }) { title: test.title, }; }), + v8Coverage, }; } diff --git a/yarn.lock b/yarn.lock index 4de20ec..6192e58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1031,7 +1031,7 @@ __metadata: languageName: node linkType: hard -"collect-v8-coverage@npm:^1.0.0": +"collect-v8-coverage@npm:^1.0.0, collect-v8-coverage@npm:^1.0.1": version: 1.0.1 resolution: "collect-v8-coverage@npm:1.0.1" checksum: 4efe0a1fccd517b65478a2364b33dadd0a43fc92a56f59aaece9b6186fe5177b2de471253587de7c91516f07c7268c2f6770b6cbcffc0e0ece353b766ec87e55 @@ -1655,6 +1655,8 @@ __metadata: resolution: "jest-light-runner@workspace:." dependencies: "@jest/expect": ^29.0.1 + "@jest/transform": ^29.0.1 + collect-v8-coverage: ^1.0.1 jest-circus: ^29.0.1 jest-each: ^29.0.1 jest-mock: ^29.0.1