Skip to content

Commit

Permalink
feat(@bazel/jasmine): add coverage reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
Fabian Wiles committed Mar 13, 2019
1 parent 8e28710 commit f90f6ef
Show file tree
Hide file tree
Showing 7 changed files with 1,043 additions and 10 deletions.
3 changes: 2 additions & 1 deletion packages/jasmine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"url": "https://github.com/bazelbuild/rules_nodejs/issues"
},
"dependencies": {
"jasmine": "~3.3.1"
"jasmine": "~3.3.1",
"v8-coverage": "1.0.8"
},
"bazelWorkspaces": {
"npm_bazel_jasmine": {
Expand Down
2 changes: 2 additions & 0 deletions packages/jasmine/src/jasmine_node_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ def jasmine_node_test(
)

all_data = data + srcs + deps + [jasmine]
# code coverage reporting dependancy
all_data += ["@npm//v8-coverage"]
all_data += [Label("//:src/jasmine_runner.js")]
all_data += [":%s_devmode_srcs.MF" % name]
all_data += [Label("@bazel_tools//tools/bash/runfiles")]
Expand Down
67 changes: 59 additions & 8 deletions packages/jasmine/src/jasmine_runner.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

let jasmineCore = null
let JasmineRunner = require('jasmine/lib/jasmine');
const Execute = require('v8-coverage/src/execute');
const Report = require('v8-coverage/src/report');


if (global.jasmine) {
// global.jasmine has been initialized which means a bootstrap script
// has already required `jasmine-core` and called jasmineCore.boot()
Expand Down Expand Up @@ -55,6 +61,9 @@ 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');
Expand All @@ -64,17 +73,31 @@ function main(args) {
process.argv.splice(2, 1)[0];

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 that match test.js or spec.js
.filter(f => !IS_NODE_MODULE.test(f))

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

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))
.map(f => require.resolve(f))
// the reporting lib resolves the relative path 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 +110,38 @@ function main(args) {
// so we need to add it back
jrunner.configureDefaultReporter({});

const covDir = path.join(process.env['TEST_TMPDIR'], String(crypto.randomBytes(4).readUInt32LE(0)));

const 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);

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 goign to have to write a file at some point for bazel coverage
// so mayaswell 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);
});

});

// Re-do logic from jrunner.execute() here so that
Expand Down Expand Up @@ -138,5 +189,5 @@ function getAllSpecs(jasmineEnv) {
}

if (require.main === module) {
process.exitCode = main(process.argv.slice(2));
process.exitCode = main(process.argv.slice(2));
}
9 changes: 9 additions & 0 deletions packages/jasmine/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,12 @@ jasmine_node_test(
jasmine = "@npm//jasmine",
shard_count = 3,
)

jasmine_node_test(
name = "coverage_test",
jasmine = "@npm//jasmine",
srcs = [
"coverage.spec.js",
"coverage_source.js",
],
)
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

0 comments on commit f90f6ef

Please sign in to comment.