Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(@bazel/jasmine): add coverage reporting #553

Merged
merged 1 commit into from
Mar 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions e2e/jasmine/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,12 @@ jasmine_node_test(
"@npm//zone.js",
],
)

jasmine_node_test(
name = "coverage_test",
srcs = [
"coverage.spec.js",
"coverage_source.js",
],
coverage = True,
)
10 changes: 10 additions & 0 deletions e2e/jasmine/coverage.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const {isString} = require('./coverage_source');

describe('coverage function', () => {
it('should cover one branch', () => {
expect(isString(2)).toBe(false);
});
it('should cover the other branch', () => {
expect(isString('some string')).toBe(true);
});
});
9 changes: 9 additions & 0 deletions e2e/jasmine/coverage_source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function isString(input) {
if (typeof input === 'string') {
return true;
} else {
return false;
}
}

exports.isString = isString;
3 changes: 2 additions & 1 deletion packages/jasmine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
],
"main": "index.js",
"dependencies": {
"jasmine": "~3.3.1"
"jasmine": "~3.3.1",
"v8-coverage": "1.0.8"
},
"bazelWorkspaces": {
"npm_bazel_jasmine": {
Expand Down
11 changes: 10 additions & 1 deletion packages/jasmine/src/jasmine_node_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def jasmine_node_test(
expected_exit_code = 0,
tags = [],
jasmine = Label("@npm//@bazel/jasmine"),
coverage = False,
**kwargs):
"""Runs tests in NodeJS using the Jasmine test runner.

Expand All @@ -42,6 +43,7 @@ def jasmine_node_test(
expected_exit_code: The expected exit code for the test. Defaults to 0.
tags: bazel tags applied to test
jasmine: a label providing the jasmine dependency
coverage: Enables code coverage collection and reporting
**kwargs: remaining arguments are passed to the test rule
"""
devmode_js_sources(
Expand All @@ -52,15 +54,22 @@ def jasmine_node_test(
)

all_data = data + srcs + deps + [jasmine]

all_data += [":%s_devmode_srcs.MF" % name]
all_data += [Label("@bazel_tools//tools/bash/runfiles")]
entry_point = "@bazel/jasmine/src/jasmine_runner.js"

templated_args = ["$(location :%s_devmode_srcs.MF)" % name]
if coverage:
templated_args = templated_args + ["--coverage"]
else:
templated_args = templated_args + ["--nocoverage"]

nodejs_test(
name = name,
data = all_data,
entry_point = entry_point,
templated_args = ["$(location :%s_devmode_srcs.MF)" % name],
templated_args = templated_args,
testonly = 1,
expected_exit_code = expected_exit_code,
tags = tags,
Expand Down
85 changes: 77 additions & 8 deletions packages/jasmine/src/jasmine_runner.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const fs = require('fs');
const path = require('path');

let jasmineCore = null
let JasmineRunner = require('jasmine/lib/jasmine');
Expand Down Expand Up @@ -55,26 +56,51 @@ if (TOTAL_SHARDS) {
// the maximum (See: https://nodejs.org/api/errors.html#errors_error_stacktracelimit)
Error.stackTraceLimit = Infinity;

const IS_TEST_FILE = /[^a-zA-Z0-9](spec|test)\.js$/i;
const IS_NODE_MODULE = /\/node_modules\//

function main(args) {
if (!args.length) {
throw new Error('Spec file manifest expected argument missing');
}
// first args is always the path to the manifest
const manifest = require.resolve(args[0]);
// second is always a flag to enable coverage or not
const coverageArg = args[1];
const enableCoverage = coverageArg === '--coverage';

// Remove the manifest, some tested code may process the argv.
process.argv.splice(2, 1)[0];
// Also remove the --coverage flag
process.argv.splice(2, 2)[0];

// the relative directory the coverage reporter uses to find anf filter the files
const cwd = process.cwd()

const jrunner = new JasmineRunner({jasmineCore: jasmineCore});
fs.readFileSync(manifest, UTF8)
.split('\n')
.filter(l => l.length > 0)
const allFiles = fs.readFileSync(manifest, UTF8)
.split('\n')
.filter(l => l.length > 0)
// Filter out files from node_modules
.filter(f => !IS_NODE_MODULE.test(f))

const sourceFiles = allFiles
// Filter out all .spec and .test files so we only report
// coverage against the source files
.filter(f => !IS_TEST_FILE.test(f))
// the jasmine_runner.js gets in here as a file to run
.filter(f => !f.endsWith('jasmine_runner.js'))
.map(f => require.resolve(f))
// the reporting lib resolves the relative path to our cwd instead of
// using the absolute one so match it here
.map(f => path.relative(cwd, f))

allFiles
// Filter here so that only files ending in `spec.js` and `test.js`
// are added to jasmine as spec files. This is important as other
// deps such as "@npm//typescript" if executed may cause the test to
// fail or have unexpected side-effects. "@npm//typescript" would
// try to execute tsc, print its help, and process.exit(1)
.filter(f => /[^a-zA-Z0-9](spec|test)\.js$/i.test(f))
// Filter out files from node_modules that match test.js or spec.js
.filter(f => !/\/node_modules\//.test(f))
.filter(f => IS_TEST_FILE.test(f))
.forEach(f => jrunner.addSpecFile(f));

var noSpecsFound = true;
Expand All @@ -87,10 +113,53 @@ function main(args) {
// so we need to add it back
jrunner.configureDefaultReporter({});


let covExecutor;
let covDir;
if (enableCoverage) {
// lazily pull these deps in for only when we want to collect coverage
const crypto = require('crypto');
const Execute = require('v8-coverage/src/execute');

// make a tmpdir inside our tmpdir for just this run
covDir = path.join(process.env['TEST_TMPDIR'], String(crypto.randomBytes(4).readUInt32LE(0)));
covExecutor = new Execute({include: sourceFiles, exclude: []});
covExecutor.startProfiler();
}

jrunner.onComplete((passed) => {
let exitCode = passed ? 0 : BAZEL_EXIT_TESTS_FAILED;
if (noSpecsFound) exitCode = BAZEL_EXIT_NO_TESTS_FOUND;
process.exit(exitCode);

if (enableCoverage) {
const Report = require('v8-coverage/src/report');
covExecutor.stopProfiler((err, data) => {
if (err) {
console.error(err);
process.exit(1);
}
const sourceCoverge = covExecutor.filterResult(data.result);
// we could do this all in memory if we wanted
// just take a look at v8-coverage/src/report.js and reimplement some of those methods
// but we're going to have to write a file at some point for bazel coverage
// so may as well support it now
// the lib expects these paths to exist for some reason
fs.mkdirSync(covDir);
fs.mkdirSync(path.join(covDir, 'tmp'));
// only do a text summary for now
// once we know what format bazel coverage wants we can output
// lcov or some other format
const report = new Report(covDir, ['text-summary']);
report.store(sourceCoverge);
report.generateReport();

process.exit(exitCode);
});
} else {
process.exit(exitCode);
}


});

if (TOTAL_SHARDS) {
Expand Down
14 changes: 14 additions & 0 deletions packages/jasmine/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,17 @@ jasmine_node_test(
tags = ["manual"],
deps = ["//:jasmine_runner"],
)

jasmine_node_test(
name = "coverage_test",
srcs = [
"coverage.spec.js",
"coverage_source.js",
],
coverage = True,
jasmine = "@npm//jasmine",
gregmagolan marked this conversation as resolved.
Show resolved Hide resolved
deps = [
"//:jasmine_runner",
"@npm//v8-coverage",
],
)
10 changes: 10 additions & 0 deletions packages/jasmine/test/coverage.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { isString } = require('./coverage_source');

describe('coverage function', () => {
it('should cover one branch', () => {
expect(isString(2)).toBe(false);
});
it('should cover the other branch', () => {
expect(isString('some string')).toBe(true);
});
});
9 changes: 9 additions & 0 deletions packages/jasmine/test/coverage_source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function isString(input) {
if (typeof input === 'string') {
return true;
} else {
return false;
}
}

exports.isString = isString;
Loading