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

Combining coverage from 2 test runs? #2418

Closed
tmayr opened this issue Dec 21, 2016 · 23 comments
Closed

Combining coverage from 2 test runs? #2418

tmayr opened this issue Dec 21, 2016 · 23 comments

Comments

@tmayr
Copy link

tmayr commented Dec 21, 2016

I'm currently running tests in two different app settings (we have different tests depending on which country the application is launched).

I'd love to have one coverage with both runs combined as the code goes through different parts on each run.

Is this something remotely possible to do?
What would be the best practice in this scenarios?

@cpojer
Copy link
Member

cpojer commented Dec 26, 2016

@DmitriiAbramov do we have anything for this? I think this could be pretty handy if we decide to kill our internal runner.

@aaronabramov
Copy link
Contributor

it's actually pretty easy to do with istanbul as long as you have a .json coverage report.
we do something similar to this here:
https://github.com/facebook/jest/blob/master/scripts/mapCoverage.js

@cpojer cpojer closed this as completed Dec 28, 2016
@ardok
Copy link

ardok commented Jan 30, 2017

Just in case someone needs step by step to do this (I literally combined my coverage report after finally stumbled upon here).

  1. My original test script was
    "test": "jest --config=src/test/jest.node.json && jest --config=src/test/jest.browser.json && npm run lint"

I also do the code coverage config thing from jest

  1. I changed my config on each to have
  "coverageReporters": [],
  1. I used the mapCoverage file (just copy pasted it to my project) and changed my test script to be:
    "test": "jest --config=src/test/jest.node.json && jest --config=src/test/jest.browser.json && node ./scripts/map-coverage.js && npm run lint"

Of course, don't forget to install the dependencies that you need for mapCoverage.

@tmayr
Copy link
Author

tmayr commented Jan 30, 2017

Finally tackled this today.

Our use case was a bit different, as files tested were exactly the same, under exactly the same paths but took different code paths due to environment variables being set and running the application in different country environments.

package.json | jest.config.json

"coverageReporters": ["json"] // otherwise you don't have any coverage to read`

And we had to make some changes to our test command too, there is probably a better way but a quick 5s google search did not yield any results on how to generate coverages with different file names

package.json

"test": "jest && cp ./coverage/coverage-final.json ./coverage/coverage-chile-final.json && env COUNTRY=peru jest && cp ./coverage/coverage-final.json ./coverage/coverage-peru-final.json && node ./lib/mapCoverage.js"

mapCoverage.js had to have some changes done too, since we're reading from two coverage files with the same paths, instead of one with different paths.

mapCoverage.js

const createReporter = require('istanbul-api').createReporter;
const istanbulCoverage = require('istanbul-lib-coverage');

const map = istanbulCoverage.createCoverageMap();
const reporter = createReporter();

const countries = ['chile', 'peru'];

countries.forEach(country => {
  const coverage = require(`../coverage/coverage-${country}-final.json`);
  Object.keys(coverage).forEach(
    filename => map.addFileCoverage(coverage[filename])
  );
});

reporter.addAll(['json', 'lcov', 'text']);
reporter.write(map);

@DmitriiAbramov @cpojer Thanks for the help!
@ardok Thanks for dropping in with your experience!

@cpojer
Copy link
Member

cpojer commented Jan 30, 2017

@DmitriiAbramov shall we make mapCoverage into a jest-map-coverage package so that it can be re-used?

@aaronabramov
Copy link
Contributor

@cpojer might be a good idea. Istanbul already provides everything needed for that, but it's not documented and requires some additional configuration.

were you thinking about something like this?

const {mergeCoverage} = require('jest-map-coverage');

merge(
  'file1.json',
  'file2.json',
).replacePaths(path => path.toLowerCase());

@ljharb
Copy link
Contributor

ljharb commented Feb 11, 2017

I use https://www.npmjs.com/package/istanbul-merge to merge istanbul reports - with that and #2861, it should be trivial.

@bchenSyd
Copy link
Contributor

thanks @tmayr for sharing. that works for me!

actually istanbul has new api, like below

 var istanbul = require('istanbul'),
        collector = new istanbul.Collector(),
        reporter = new istanbul.Reporter(),
        sync = false;

    const countries = ['chile', 'peru'];

countries.forEach(country => {
  const coverage = require(`../coverage/coverage-${country}-final.json`);
  collector.add(coverage);
});
   
    reporter.addAll([ 'text', 'lcov', 'clover' ]);
    reporter.write(collector, sync, function () {
        console.log('All reports generated');
    });

Above code should work. but I"m getting Cannot read property 'text' of undefined which I believe is a defect in istanbul

@fahrenq
Copy link

fahrenq commented Sep 23, 2018

For everyone who struggles with these nowadays and receives errors like

        Object.keys(second.s).forEach(function (k) {
               ^

TypeError: Cannot convert undefined or null to object

or

        throw new Error('Invalid file coverage object, missing keys, found:' +
        ^
Error: Invalid file coverage object, missing keys, found:data

For some reason Istanbul tooling not working with jest-generated coverage JSONs because of some weird "data" key that appears in some entries of coverage JSONs files.

I.e. these entries in the same coverage json.

"<...>/src/app.js": {"path":"<...>/src/app.js","statementMap":{"0":{"start"...
"<...>/src/aws.js": {"data":{"path":"<...>/src/aws.js","statementMap":{"0":{"start"...

I solved this by just deleting this "data" key and passing its value directly to the file-path key.

Here's my full 'mapCoverage' function.

/* eslint-disable import/no-extraneous-dependencies */
const libCoverage = require('istanbul-lib-coverage');
const { createReporter } = require('istanbul-api');

const integrationCoverage = require('../coverage-integration/coverage-final.json');
const unitCoverage = require('../coverage-unit/coverage-final.json');

const normalizeJestCoverage = (obj) => {
  const result = obj;
  Object.entries(result).forEach(([k, v]) => {
    if (v.data) result[k] = v.data;
  });
  return result;
};

const map = libCoverage.createCoverageMap();
map.merge(normalizeJestCoverage(integrationCoverage));
map.merge(normalizeJestCoverage(unitCoverage));

const reporter = createReporter();
reporter.addAll(['json', 'lcov', 'text']);
reporter.write(map);

I assume that whole Lerna powered Istanbul repository is the latest version, so, why doesn't it assume coverage files to have 'data' key? And what this 'data' key is? Why it only appears in some entries?

@cpojer if you have a little bit of time, can you please give any comments? Maybe there's a newer version of istanbul exists that respects this "data" key or something? At least I couldn't find anything. Thank you very much!

@mummybot
Copy link

We had this issue as well, digging into where the data attribute was coming from it was a typings.d.ts file to allow importing of .json modules:

// typings.d.ts
declare module '*.json' {
    const value: any;
    export default value;
}

Removing this file, and updating the Typescript ^2.9.2 tsconfig to import JSON fixed our issues

"resolveJsonModule": true,
"esModuleInterop": true,

https://stackoverflow.com/a/50674344/158815

@Morikko
Copy link

Morikko commented Oct 23, 2018

FYI: nyc report does it natively while you put all your coverage.json in the same folder.

More explanation on this other issue message.

@samboylett
Copy link

If you're using istanbul, the CLI seems to automatically merge any JSON files you use for you:

istanbul report html --include=*.coverage.out.json

With that my map coverage command just needs to clean up the JSON from Jest as described by @fahrenq


const normalizeJestCoverage = (obj) => {
    const result = { ...obj };

    Object.entries(result).forEach(([k, v]) => {
        if (v.data) result[k] = v.data;
    });

    return result;
};

const integrationCoverage = normalizeJestCoverage(require('./jest.integration.out.json').coverageMap);
const unitCoverage = normalizeJestCoverage(require('./jest.unit.out.json').coverageMap);

fs.writeFileSync('./unit.coverage.out.json', JSON.stringify(unitCoverage, null, 4));
fs.writeFileSync('./integration.coverage.out.json', JSON.stringify(integrationCoverage, null, 4));

dleve123 added a commit to artsy/positron that referenced this issue Jan 18, 2019
https://artsyproduct.atlassian.net/browse/PLATFORM-1136

Problem:

One part of the product health matrix story [spike][spike] is code
coverage for each service. However, there is no pattern at Artsy for
measuring code coverage for Javascript applications.

While there are existing tools out there for tracking and reporting code
coverage, such as [istanbul.js][istanbul-js], [coveralls][coveralls],
and [codecov][codecov], the complexities:

1. A multi-runner test suite (jest and mocha), and
2. A Circle CI -> Docker -> Docker test runtime

have resulted in a tough problem to solution around.

Solution:

After spending a lot of time trying to get Coveralls instrumentation to
work, we pivoted to using Codecov. At a high level, this commit does the
following:

1. Updates our calls to `jest` and `mocha` to write code coverage
information to a `.nyc_output` directory (default for nyc/istanbul)

2. Manipulates the `jest` coverage information to be compatible with
`mocha` coverage information via a JS script. See [motivating GH
issue][gh-issue].

3. Merges the `jest` and `mocha` coverage information together using the
`nyc` CLI

4. Introduces a bash executable provided by codecov to instrument the
merged information to codecov

Co-authored-by: Kieran Gillen <kgillen@gmail.com>

[spike]:https://artsyproduct.atlassian.net/browse/PLATFORM-1098
[istanbul-js]:https://github.com/istanbuljs/nyc
[coveralls]:https://coveralls.io/
[codecov]:https://codecov.io/gh/artsy/positron/
[gh-issue]:jestjs/jest#2418

Add coveralls and mocha-lcov-report packages via `yarn add --dev`

Add Istanbul CLI, nyc, via `yarn add --dev nyc`

* nyc is a CLI for a code coverage reporting tool

Downgrade coveralls to 2.12

Attempt to get coverage reporting working

- add yarn coverage
- use prefix mocha with `nyc`
- attempt to output jest coverage data in .nyc_output
- create a coverage mapper that removes the incompatible “data” key
  from Jest’s built-in `—coverage` output

Try wrapping jest in nyc to generate coverage data

Revert "Try wrapping jest in nyc to generate coverage data"

This reverts commit 0600861.

Remove custom placement of jest coverage data

Try ignoring src/client/collections from coverage collection

Revert "Downgrade coveralls to 2.12"

This reverts commit 04dee17.

Install codecov to run on Docker

* Add bash to Dockerimage
* Explicity pass in commit hash as git isn’t present in the container
* Move manipulated jest coverage back to .nyc_output
  + possible improvement: pass directory directly to Reporter
* Tell jest to write all of it’s coverage info to .nyc

Restructure coverage build pipeline

Break up components of codecov installation into component scripts, and
call components from a "main" `publish-coverage` script.

* Specify the root path of the application in hopes that codecov does
better in the absence of Git

Wrap `jest` with options into a script/jest.sh

* Mimics pattern used to run mocha tests

Remove coveralls in favor of codecov

Report test coverage from script/test.sh

Because we're in docker, test coverage needs to be reported within the
same container; otherwise, we'll have to figure out how to share assets
across build steps. Because we're `set -e`ing this script, code coverage
shouldn't be instrumented unless the entire test suite passes, which the
typical behavior of code coverage tooling.

Inject the CODECOV_TOKEN into call to `hokusai test`

* Token is required to authenticate with Codecov
* Add token ENV key name to .env.test to provide a place to document
it's use
  + not needed during typical dev, only useful on CI

Add codecov badge to README

Host codecov locally

* Add script/codecov
* Add coverage.lcov to .gitignore
* Remove obsolete install-codecov yarn script
dleve123 added a commit to artsy/positron that referenced this issue Jan 18, 2019
https://artsyproduct.atlassian.net/browse/PLATFORM-1136

Problem:

One part of the product health matrix story [spike][spike] is code
coverage for each service. However, there is no pattern at Artsy for
measuring code coverage for Javascript applications.

While there are existing tools out there for tracking and reporting code
coverage, such as [istanbul.js][istanbul-js], [coveralls][coveralls],
and [codecov][codecov], the complexities:

1. A multi-runner test suite (jest and mocha), and
2. A Circle CI -> Docker -> Docker test runtime

have resulted in a tough problem to solution around.

Solution:

After spending a lot of time trying to get Coveralls instrumentation to
work, we pivoted to using Codecov. At a high level, this commit does the
following:

1. Updates our calls to `jest` and `mocha` to write code coverage
information to a `.nyc_output` directory (default for nyc/istanbul)

2. Manipulates the `jest` coverage information to be compatible with
`mocha` coverage information via a JS script. See [motivating GH
issue][gh-issue].

3. Merges the `jest` and `mocha` coverage information together using the
`nyc` CLI

4. Introduces a bash executable provided by codecov to instrument the
merged information to codecov

Co-authored-by: Kieran Gillen <kgillen@gmail.com>

[spike]:https://artsyproduct.atlassian.net/browse/PLATFORM-1098
[istanbul-js]:https://github.com/istanbuljs/nyc
[coveralls]:https://coveralls.io/
[codecov]:https://codecov.io/gh/artsy/positron/
[gh-issue]:jestjs/jest#2418
dleve123 added a commit to artsy/positron that referenced this issue Jan 18, 2019
https://artsyproduct.atlassian.net/browse/PLATFORM-1136

Problem:

One part of the product health matrix story [spike][spike] is code
coverage for each service. However, there is no pattern at Artsy for
measuring code coverage for Javascript applications.

While there are existing tools out there for tracking and reporting code
coverage, such as [istanbul.js][istanbul-js], [coveralls][coveralls],
and [codecov][codecov], the complexities:

1. A multi-runner test suite (jest and mocha), and
2. A Circle CI -> Docker -> Docker test runtime

have resulted in a tough problem to solution around.

Solution:

After spending a lot of time trying to get Coveralls instrumentation to
work, we pivoted to using Codecov. At a high level, this commit does the
following:

1. Updates our calls to `jest` and `mocha` to write code coverage
information to a `.nyc_output` directory (default for nyc/istanbul)

2. Manipulates the `jest` coverage information to be compatible with
`mocha` coverage information via a JS script. See [motivating GH
issue][gh-issue].

3. Merges the `jest` and `mocha` coverage information together using the
`nyc` CLI

4. Introduces a bash executable provided by codecov to instrument the
merged information to codecov

Co-authored-by: Kieran Gillen <kgillen@gmail.com>

[spike]:https://artsyproduct.atlassian.net/browse/PLATFORM-1098
[istanbul-js]:https://github.com/istanbuljs/nyc
[coveralls]:https://coveralls.io/
[codecov]:https://codecov.io/gh/artsy/positron/
[gh-issue]:jestjs/jest#2418
dleve123 added a commit to artsy/positron that referenced this issue Jan 22, 2019
https://artsyproduct.atlassian.net/browse/PLATFORM-1136

Problem:

One part of the product health matrix story [spike][spike] is code
coverage for each service. However, there is no pattern at Artsy for
measuring code coverage for Javascript applications.

While there are existing tools out there for tracking and reporting code
coverage, such as [istanbul.js][istanbul-js], [coveralls][coveralls],
and [codecov][codecov], the complexities:

1. A multi-runner test suite (jest and mocha), and
2. A Circle CI -> Docker -> Docker test runtime

have resulted in a tough problem to solution around.

Solution:

After spending a lot of time trying to get Coveralls instrumentation to
work, we pivoted to using Codecov. At a high level, this commit does the
following:

1. Updates our calls to `jest` and `mocha` to write code coverage
information to a `.nyc_output` directory (default for nyc/istanbul)

2. Manipulates the `jest` coverage information to be compatible with
`mocha` coverage information via a JS script. See [motivating GH
issue][gh-issue].

3. Merges the `jest` and `mocha` coverage information together using the
`nyc` CLI

4. Introduces a bash executable provided by codecov to instrument the
merged information to codecov

Co-authored-by: Kieran Gillen <kgillen@gmail.com>

[spike]:https://artsyproduct.atlassian.net/browse/PLATFORM-1098
[istanbul-js]:https://github.com/istanbuljs/nyc
[coveralls]:https://coveralls.io/
[codecov]:https://codecov.io/gh/artsy/positron/
[gh-issue]:jestjs/jest#2418
@kirillgroshkov
Copy link

kirillgroshkov commented Apr 2, 2019

Just to summarize. I've used different suggestions here and there and this is the final script that works for me as of today that allows to combine 2 coverage reports in json format into one combined report (with set of formats: json, lcov).

/*

yarn tsn-script ./scripts/mergeCoverage.ts --report ./coverage0/coverage-final.json --report ./coverage1/coverage-final.json

*/

import * as fs from 'fs-extra'
import * as yargs from 'yargs'
const { createCoverageMap } = require('istanbul-lib-coverage')
const { createReporter } = require('istanbul-api');

main().catch(err => {
  console.error(err)
  process.exit(1)
})

async function main () {
  const argv = yargs
    .options({
      report: {
        type: 'array', // array of string
        desc: 'Path of json coverage report file',
        demandOption: true,
      },
      reporters: {
        type: 'array',
        default: ['json', 'lcov'],
      }
    })
    .argv

  const reportFiles = argv.report as string[]
  const reporters = argv.reporters as string[]

  const map = createCoverageMap({})

  reportFiles.forEach(file => {
    const r = fs.readJsonSync(file)
    map.merge(r)
  })

  const reporter = createReporter();
  // reporter.addAll(['json', 'lcov', 'text']);
  // reporter.addAll(['json', 'lcov']);
  reporter.addAll(reporters)
  reporter.write(map)
  console.log('Created a merged coverage report in ./coverage')
}

@James-Smith83
Copy link

James-Smith83 commented Sep 27, 2019

I noticed that it can be pretty difficult to try and merge multiple coverage data from various tests. It is clear that some people have issues with trying to do this as well.
If you want to see an alternative way of doing this rather than using the "isntabul" tool then this article talks about a different methods of how to merge coverage data from multiple test runs.

https://www.rapitasystems.com/blog/merging-coverage-data-multiple-test-runs

@ochemli
Copy link

ochemli commented Oct 5, 2019

How do you check-coverage after generating a merged report?

@rivy
Copy link

rivy commented Oct 6, 2019

One of the issues I've run into (solved through some unknown means by CodeCov, see "Merging Reports") when trying to combine runs from different machines is differing path prefixes (especially *nix vs Windows machines).

I don't know of a simple way to combine the outputs under that differing path prefix circumstance. And without that merge, the coverage metric will continually flip-flip between wildly incorrect values.

@ochemli
Copy link

ochemli commented Oct 7, 2019

Thanks @OshriBa, @rivy. I was asking about enforcing coverage thresholds on a merged report. I was able to get merged coverage from jest + mocha + newman tests with the help of this thread. However, running nyc check-coverage on the merged report failed with
Error: Invalid file coverage object, missing keys
Turns out nyc will checks all coverage*.json file, including coverage-summary.json.
I solved this by moving coverage-summary.json before running nyc check-coverage

@mcblum
Copy link

mcblum commented Jan 17, 2020

@fahrenq hey did you figure this out within Lerna? We have Angular 1 along side Angular 8 and jest, and we're getting that weird data attr.

@fahrenq
Copy link

fahrenq commented Jan 18, 2020

@mcblum Sorry, I completely forgot context as by now. Good luck :)

@mcblum
Copy link

mcblum commented Jan 18, 2020

All good, thank you!

@hongbo-miao
Copy link

Since istanbul is deprecated: https://www.npmjs.com/package/istanbul
I am ending up with the way using nyc by this answer at Stack Overflow: https://stackoverflow.com/a/63008134/2000548

@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests