-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
6 changed files
with
164 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/* -------------------- | ||
* livepack | ||
* Capture V8 coverage | ||
* ------------------*/ | ||
|
||
/* eslint-disable import/order, import/newline-after-import */ | ||
|
||
'use strict'; | ||
|
||
// Use internal module cache because some of same modules (e.g. `pirates`) | ||
// are also used by `register.js`. This avoids loading them twice. | ||
const { | ||
useInternalModuleCache, useGlobalModuleCache, usingInternalModuleCache | ||
} = require('../../lib/shared/moduleCache.js'); | ||
useInternalModuleCache(); | ||
|
||
// Modules | ||
const {join: pathJoin, relative: relativePath} = require('path'), | ||
{fileURLToPath} = require('url'), | ||
{CoverageInstrumenter} = require('collect-v8-coverage'), | ||
{globsToMatcher, replacePathSepForGlob} = require('jest-util'), | ||
{addHook} = require('pirates'), | ||
EXTS = require('@babel/core').DEFAULT_EXTENSIONS; | ||
|
||
// Imports | ||
const {collectCoverageFrom} = require('../../jest.config.js'); | ||
|
||
useGlobalModuleCache(); | ||
|
||
// Constants | ||
const TESTS_DIR = pathJoin(__dirname, '../'), | ||
ROOT_DIR = pathJoin(TESTS_DIR, '../'); | ||
|
||
// Exports | ||
|
||
module.exports = startCoverage; | ||
startCoverage.applyAfterAllHook = applyAfterAllHook; | ||
|
||
let instrumenter; | ||
|
||
/** | ||
* Run by `jest-light-runner` before `./register.js`. | ||
* Then run again in `afterAll` hook at end of each test file. | ||
* Start capturing V8 coverage. | ||
* @async | ||
* @returns {undefined} | ||
*/ | ||
async function startCoverage() { | ||
instrumenter = new CoverageInstrumenter(); | ||
await instrumenter.startInstrumenting(); | ||
} | ||
|
||
/** | ||
* Register `afterAll` test hook to record V8 coverage data to `global.__coverage__`. | ||
* `jest-light-runner` collects this and records it in the test result object as Babel coverage data. | ||
* Custom runner `./runner.mjs` then moves it to the `.v8Coverage` property of result object. | ||
* | ||
* Code to call this function is added to bottom of every test file by pirates hook below. | ||
* | ||
* V8 coverage data is filtered to only files being assessed for coverage. | ||
* This is done here rather than in the custom runner to minimise data transfer between | ||
* worker thread running the test and the runner main thread. | ||
* | ||
* @returns {undefined} | ||
*/ | ||
function applyAfterAllHook() { | ||
afterAll(async () => { | ||
// Record coverage data | ||
global.__coverage__ = filterCoverageData(await instrumenter.stopInstrumenting()); | ||
|
||
// Start coverage inspector again for next test file | ||
startCoverage(true); | ||
}); | ||
} | ||
|
||
/** | ||
* Filter coverage data to files being monitored for coverage only. | ||
* Based on: | ||
* https://github.com/facebook/jest/blob/fb2de8a10f8e808b080af67aa771f67b5ea537ce/packages/jest-runtime/src/index.ts#L1217 | ||
* | ||
* @param {Array<Object>} coverage - Coverage data captured by V8 | ||
* @returns {Array<Object>|undefined} - Conformed coverage data | ||
*/ | ||
function filterCoverageData(coverage) { | ||
if (collectCoverageFrom && collectCoverageFrom.length === 0) return undefined; | ||
|
||
const filenameMatcher = globsToMatcher(collectCoverageFrom); | ||
|
||
coverage = coverage | ||
.filter(res => res.url.startsWith('file://')) | ||
.map(res => ({...res, url: fileURLToPath(res.url)})) | ||
.filter( | ||
res => res.url.startsWith(ROOT_DIR) | ||
&& filenameMatcher(replacePathSepForGlob(relativePath(ROOT_DIR, res.url))) | ||
) | ||
.map(result => ({result})); | ||
|
||
return coverage.length > 0 ? coverage : undefined; | ||
} | ||
|
||
// Install extra require hook to add code to end of test files to install `afterAll` hook. | ||
addHook( | ||
(code, path) => ( | ||
(!usingInternalModuleCache() && path.startsWith(TESTS_DIR) && path.endsWith('.test.js')) | ||
? `${code}\nrequire(${JSON.stringify(__filename)}).applyAfterAllHook()\n` | ||
: code | ||
), | ||
{ignoreNodeModules: false, exts: EXTS} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* -------------------- | ||
* livepack | ||
* Custom test runner to capture V8 coverage | ||
* ------------------*/ | ||
|
||
// Modules | ||
import {join as pathJoin} from 'path'; | ||
import {fileURLToPath} from 'url'; | ||
import LightRunner from 'jest-light-runner'; // eslint-disable-line import/no-unresolved | ||
import assert from 'simple-invariant'; | ||
|
||
// Constants | ||
const COVERAGE_PATH = pathJoin(fileURLToPath(import.meta.url), '../coverage.js'); | ||
|
||
// Exports | ||
|
||
/** | ||
* Modification of `jest-light-runner`. | ||
* If coverage enabled: | ||
* - Adds `./coverage.js` to setup files before `./register.js` | ||
* - Captures V8 coverage data which `./coverage.js` smuggled out via `global.__coverage__` | ||
* and passes back to Jest. | ||
*/ | ||
export default class CustomRunner extends LightRunner { | ||
runTests(tests, watcher, onStart, onResult, onFailure) { | ||
if (this._config.collectCoverage) { | ||
const onResultOriginal = onResult; | ||
onResult = (test, result) => { | ||
result.v8Coverage = result.coverage; | ||
result.coverage = undefined; | ||
return onResultOriginal(test, result); | ||
}; | ||
|
||
if (tests.length > 0) { | ||
const {context} = tests[0]; | ||
const setupFilesAfterEnv = [...context.config.setupFilesAfterEnv]; | ||
setupFilesAfterEnv.splice(setupFilesAfterEnv.length - 1, 0, COVERAGE_PATH); | ||
context.config = {...context.config, setupFilesAfterEnv}; | ||
|
||
assert( | ||
tests.length === 1 || tests[1].context === tests[0].context, | ||
'Differing context objects between tests' | ||
); | ||
} | ||
} | ||
|
||
return super.runTests(tests, watcher, onStart, onResult, onFailure); | ||
} | ||
} |