Skip to content

Commit ddb69b0

Browse files
authored
feat: Add nyc-test-coverage package (#798)
* add working nyc-test-coverage package * address a couple of comments * avoid using global coverageMap * address Roeys comments * address code review comments, add README * address Roey's comments and add some more details to the README
1 parent 54abb1f commit ddb69b0

File tree

6 files changed

+171
-0
lines changed

6 files changed

+171
-0
lines changed

packages/nyc-test-coverage/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# `@temporalio/nyc-test-coverage`
2+
3+
[![NPM](https://img.shields.io/npm/v/@temporalio/nyc-test-coverage?style=for-the-badge)](https://www.npmjs.com/package/@temporalio/nyc-test-coverage)
4+
5+
[Temporal](https://temporal.io)'s [TypeScript SDK](https://docs.temporal.io/typescript/introduction) interceptors and sinks for code coverage with [nyc](https://npmjs.com/package/nyc).
6+
7+
## Getting Started
8+
9+
1. `npm install mocha nyc`
10+
2. Use [nyc to manually instrument your Workflow code](https://github.com/istanbuljs/nyc/blob/master/docs/instrument.md), for example `nyc instrument lib lib --in-place`. Make sure you instrument your Workflow code _after_ compiling it with `tsc`.
11+
3. Add this package's sinks and interceptors to your test worker, for example:
12+
13+
```ts
14+
import { WorkflowCoverage } from '@temporalio/nyc-test-coverage';
15+
16+
const workflowCoverage = new WorkflowCoverage();
17+
18+
worker = await Worker.create({
19+
connection: nativeConnection,
20+
taskQueue,
21+
workflowsPath: require.resolve("./workflows"),
22+
interceptors: {
23+
workflowModules: [require.resolve("@temporalio/nyc-test-coverage/lib/interceptors")]
24+
},
25+
sinks: workflowCoverage.sinks,
26+
});
27+
```
28+
29+
4. After your tests are done, call `mergeIntoGlobalCoverage()` to merge your Workflows' code coverage into nyc's global coverage.
30+
31+
```ts
32+
after(() => {
33+
workflowCoverage.mergeIntoGlobalCoverage();
34+
});
35+
```
36+
37+
For example, the following is a sample npm script that handles instrumenting and running tests.
38+
The following assumes that `npm run build` produces compiled JavaScript in the `lib` directory.
39+
40+
```
41+
{
42+
"test.coverage": "npm run build && nyc instrument lib lib --in-place && nyc --reporter=lcov --reporter=text-summary mocha lib/*.test.js"
43+
}
44+
```
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "@temporalio/nyc-test-coverage",
3+
"version": "1.0.1",
4+
"description": "Temporal.io SDK code coverage integration",
5+
"main": "lib/index.js",
6+
"types": "./lib/index.d.ts",
7+
"keywords": [
8+
"temporal",
9+
"workflow",
10+
"testing",
11+
"coverage"
12+
],
13+
"author": "Temporal Technologies Inc. <sdk@temporal.io>",
14+
"license": "MIT",
15+
"dependencies": {
16+
"@temporalio/workflow": "file:../workflow",
17+
"@temporalio/worker": "file:../worker",
18+
"istanbul-lib-coverage": "^3.2.0"
19+
},
20+
"devDependencies": {
21+
"@types/istanbul-lib-coverage": "^2.0.4"
22+
},
23+
"bugs": {
24+
"url": "https://github.com/temporalio/sdk-typescript/issues"
25+
},
26+
"homepage": "https://github.com/temporalio/sdk-typescript/tree/main/packages/testing",
27+
"files": [
28+
"lib",
29+
"src"
30+
],
31+
"publishConfig": {
32+
"access": "public"
33+
}
34+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { InjectedSinks } from '@temporalio/worker';
2+
import { CoverageSinks } from './sinks';
3+
import libCoverage from 'istanbul-lib-coverage';
4+
5+
export class WorkflowCoverage {
6+
coverageMap = libCoverage.createCoverageMap();
7+
8+
/**
9+
* Contains sinks that allow Workflows to gather coverage data.
10+
*/
11+
get sinks(): InjectedSinks<CoverageSinks> {
12+
return {
13+
coverage: {
14+
merge: {
15+
fn: (_workflowInfo, testCoverage) => {
16+
this.coverageMap.merge(testCoverage);
17+
},
18+
callDuringReplay: false,
19+
},
20+
},
21+
};
22+
}
23+
24+
/**
25+
* Merge this WorkflowCoverage's coverage map into the global coverage
26+
* map data `global.__coverage__`.
27+
*/
28+
mergeIntoGlobalCoverage(): void {
29+
/* eslint-disable @typescript-eslint/ban-ts-comment */
30+
// @ts-ignore
31+
this.coverageMap.merge(global.__coverage__);
32+
33+
const coverageMapData: libCoverage.CoverageMapData = Object.keys(this.coverageMap.data).reduce(
34+
(cur: libCoverage.CoverageMapData, path) => {
35+
const fileCoverage = this.coverageMap.data[path] as libCoverage.FileCoverage;
36+
37+
cur[path] = fileCoverage.data;
38+
return cur;
39+
},
40+
{}
41+
);
42+
43+
// @ts-ignore
44+
global.__coverage__ = coverageMapData;
45+
}
46+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { proxySinks, WorkflowInterceptors } from '@temporalio/workflow';
2+
import { CoverageSinks } from './sinks';
3+
import libCoverage from 'istanbul-lib-coverage';
4+
5+
const { coverage } = proxySinks<CoverageSinks>();
6+
7+
// Export the interceptors
8+
export const interceptors = (): WorkflowInterceptors => ({
9+
internals: [
10+
{
11+
concludeActivation(input, next) {
12+
/* eslint-disable @typescript-eslint/ban-ts-comment */
13+
// @ts-ignore
14+
const globalCoverage: libCoverage.CoverageMapData = global.__coverage__;
15+
16+
coverage.merge(JSON.parse(JSON.stringify(globalCoverage)));
17+
clearCoverage(globalCoverage);
18+
19+
return next(input);
20+
},
21+
},
22+
],
23+
});
24+
25+
function clearCoverage(coverage: libCoverage.CoverageMapData): void {
26+
for (const path of Object.keys(coverage)) {
27+
for (const index of Object.keys(coverage[path].s)) {
28+
coverage[path].s[index] = 0;
29+
}
30+
}
31+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Sinks } from '@temporalio/workflow';
2+
3+
export interface CoverageSinks extends Sinks {
4+
coverage: {
5+
merge(coverageMap: any): void;
6+
};
7+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "../../tsconfig.base.json",
3+
"compilerOptions": {
4+
"outDir": "./lib",
5+
"rootDir": "./src"
6+
},
7+
"references": [{ "path": "../worker" }, { "path": "../workflow" }],
8+
"include": ["./src/**/*.ts"]
9+
}

0 commit comments

Comments
 (0)