Skip to content
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ and then:

`cross-env COVERAGE=true ember test`

Coverage also works when running tests in parallel, eg:

`COVERAGE=true ember exam --split=2 --parallel=true`
When running with `parallel` set to true, the final reports can be merged by using `ember coverage-merge`. The final merged output will be stored in the `coverageFolder`.

## Configuration

Expand All @@ -48,6 +46,8 @@ Configuration is optional. It should be put in a file at `config/coverage.js` (`

- `coverageFolder`: Defaults to `coverage`. A folder relative to the root of your project to store coverage results.

- `parallel`: Defaults to `false`. Should be set to true if parallel testing is being used for separate test runs, for example when using [ember-exam](https://github.com/trentmwillis/ember-exam) with the `--partition` flag. This will generate the coverage reports in directories suffixed with `_<random_string>` to avoid overwriting other threads reports. These reports can be joined by using the `ember coverage-merge` command (potentially as part of the [posttest hook](https://docs.npmjs.com/misc/scripts) in your `package.json`).

#### Example
```js
module.exports = {
Expand Down
6 changes: 6 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ module.exports = {
return undefined;
},

includedCommands: function() {
return {
'coverage-merge': require('./lib/coverage-merge')
};
},

/**
* If coverage is enabled attach coverage middleware to the express server run by ember-cli
* @param {Object} startOptions - Express server start options
Expand Down
10 changes: 10 additions & 0 deletions lib/attach-middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const bodyParser = require('body-parser').json({ limit: '50mb' });
const istanbul = require('istanbul-api');
const getConfig = require('./config');
const path = require('path');
const crypto = require('crypto');

const WRITE_COVERAGE = '/write-coverage';

Expand All @@ -27,6 +28,15 @@ function writeCoverage(coverage, fileLookup, map) {
function reportCoverage(map, root, configPath) {
let config = getConfig(configPath);
let reporter = istanbul.createReporter();
if (config.parallel) {
config.coverageFolder = config.coverageFolder + '_' + crypto.randomBytes(4).toString('hex');
if (config.reporters.indexOf('json') === -1) {
config.reporters.push('json');
}
}
if (config.reporters.indexOf('json-summary') === -1) {
config.reporters.push('json-summary');
}
if (config.coverageFolder) {
reporter.dir = path.join(root, config.coverageFolder);
}
Expand Down
60 changes: 60 additions & 0 deletions lib/coverage-merge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use strict';

var path = require('path');
var getConfig = require('./config');
var dir = require('node-dir');
var Promise = require('rsvp').Promise;

/**
* Merge together coverage files created when running in multiple threads,
* for example when being used with ember exam and parallel runs.
*/
module.exports = {
name: 'coverage-merge',
description: 'Merge multiple coverage files together.',
run: function () {
var istanbul = require('istanbul-api');
var config = this._getConfig();

var coverageFolderSplit = config.coverageFolder.split('/');
var coverageFolder = coverageFolderSplit.pop();
var coverageRoot = this.project.root + '/' + coverageFolderSplit.join('/');
var coverageDirRegex = new RegExp(coverageFolder + '_.*');

let reporter = istanbul.createReporter();
reporter.dir = path.join(coverageRoot, coverageFolder);
let map = istanbul.libCoverage.createCoverageMap();

return new Promise(function (resolve, reject) {
dir.readFiles(coverageRoot, { matchDir: coverageDirRegex, match: /coverage-final\.json/ },
function (err, coverageSummary, next) {
if (err) {
reject(err);
}
map.merge(JSON.parse(coverageSummary));
next();
},
function (err) {
if (err) {
reject(err);
}

if (config.reporters.indexOf('json-summary') === -1) {
config.reporters.push('json-summary');
}

reporter.addAll(config.reporters);
reporter.write(map);
resolve();
});
});
},

/**
* Get project configuration
* @returns {Configuration} project configuration
*/
_getConfig: function () {
return getConfig(this.project.configPath());
}
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"fs-extra": "^5.0.0",
"istanbul-api": "^1.1.14",
"lodash.concat": "^4.5.0",
"node-dir": "^0.1.16",
"rsvp": "^4.8.1",
"walk-sync": "^0.3.2"
},
Expand Down
20 changes: 19 additions & 1 deletion test/integration/app-coverage-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,31 @@ describe('app coverage generation', function() {
});
});

it('uses nested coverageFolder', function() {
it('uses parallel configuration and merges coverage when merge-coverage command is issued', function() {
expect(dir(`${app.path}/coverage`)).to.not.exist;
fs.copySync('tests/dummy/config/coverage-parallel.js', `${app.path}/config/coverage.js`);
process.env.COVERAGE = true;
return app.run('ember', 'exam', '--split=2', '--parallel=true').then(function() {
expect(dir(`${app.path}/coverage`)).to.not.exist;
return app.run('ember', 'coverage-merge');
}).then(function() {
expect(file(`${app.path}/coverage/lcov-report/index.html`)).to.not.be.empty;
expect(file(`${app.path}/coverage/index.html`)).to.not.be.empty;
var summary = fs.readJSONSync(`${app.path}/coverage/coverage-summary.json`);
expect(summary.total.lines.pct).to.equal(83.33);
});
});

it('uses nested coverageFolder and parallel configuration and run merge-coverage', function() {
var coverageFolder = `${app.path}/coverage/abc/easy-as/123`;

expect(dir(coverageFolder)).to.not.exist;
fs.copySync('tests/dummy/config/coverage-nested-folder.js', `${app.path}/config/coverage.js`);
process.env.COVERAGE = true;
return app.run('ember', 'exam', '--split=2', '--parallel=true').then(function() {
expect(dir(coverageFolder)).to.not.exist;
return app.run('ember', 'coverage-merge');
}).then(function() {
expect(file(`${coverageFolder}/lcov-report/index.html`)).to.not.be.empty;
expect(file(`${coverageFolder}/index.html`)).to.not.be.empty;
var summary = fs.readJSONSync(`${coverageFolder}/coverage-summary.json`);
Expand Down
1 change: 1 addition & 0 deletions tests/dummy/config/coverage-nested-folder.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@

module.exports = {
coverageFolder: 'coverage/abc/easy-as/123',
parallel: true,
reporters: ['lcov', 'html', 'text', 'json-summary']
};
5 changes: 5 additions & 0 deletions tests/dummy/config/coverage-parallel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* eslint-env node */

module.exports = {
parallel: true
};