Skip to content

Commit c63fd3d

Browse files
committed
test: add end-to-end tests
This commit adds end-to-end tests using generated fixtures. For each fixture, there are multiple entry points. Coverage is first collected when running each entry point in its own Node process, then when running all of them in the same process. The end-to-end tests check that merging the coverages from the individual entry points returns the coverage of the combined run. The library currently fails these tests: I believe that it indicates bugs in the current implementation. This commit also improves coverage (see bcoe#2).
1 parent d580adb commit c63fd3d

11 files changed

+220
-2
lines changed

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"description": "merge together two V8 coverage reports",
55
"main": "index.js",
66
"scripts": {
7-
"test": "c8 --reporter=html --reporter=text mocha test.js",
7+
"pretest": "node ./test/fixtures/generate-fixtures.js",
8+
"test": "c8 --reporter=html --reporter=text mocha test",
89
"coverage": "c8 --reporter=text-lcov mocha test.js | coveralls",
910
"posttest": "standard",
1011
"release": "standard-version"
@@ -16,6 +17,7 @@
1617
"chai": "^4.1.2",
1718
"coveralls": "^3.0.2",
1819
"mocha": "^5.2.0",
20+
"rimraf": "^2.6.2",
1921
"standard": "^12.0.1",
2022
"standard-version": "^4.4.0"
2123
},

test/end-to-end.spec.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/* global it, describe */
2+
3+
const chai = require('chai')
4+
const fs = require('fs')
5+
const path = require('path')
6+
const merge = require('../index')
7+
8+
const FIXTURES_DIR = path.join(__dirname, 'fixtures')
9+
const COV_RE = /^cov\.(.+)\.json$/
10+
11+
describe('end-to-end', () => {
12+
for (const fixture of getFixtures()) {
13+
const {input, expected} = readFixture(fixture)
14+
15+
it(fixture, () => {
16+
let actual = input.slice(1).reduce((acc, cov) => merge(acc, cov), input[0])
17+
chai.assert.deepEqual(actual, expected)
18+
})
19+
}
20+
})
21+
22+
function getFixtures () {
23+
return fs.readdirSync(FIXTURES_DIR)
24+
.filter((child) => fs.statSync(path.join(FIXTURES_DIR, child)).isDirectory())
25+
}
26+
27+
function readFixture (name) {
28+
const dir = path.join(FIXTURES_DIR, name)
29+
30+
const input = []
31+
let expected
32+
33+
for (const child of fs.readdirSync(dir)) {
34+
const match = COV_RE.exec(child)
35+
if (match === null) {
36+
continue
37+
}
38+
const data = JSON.parse(fs.readFileSync(path.join(dir, child), {encoding: 'UTF-8'}))
39+
if (match[1] === 'all') {
40+
expected = data
41+
} else {
42+
input.push(data)
43+
}
44+
}
45+
46+
if (input.length === 0 || expected === undefined) {
47+
throw new Error(`Invalid fixture: ${dir}`)
48+
}
49+
return {input, expected}
50+
}

test/fixtures/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
cov.*.json
2+
cov-main.*.js/
3+
main.all.js

test/fixtures/generate-fixtures.js

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
const cp = require('child_process')
2+
const fs = require('fs')
3+
const path = require('path')
4+
const rimraf = require('rimraf')
5+
6+
const SIMPLE_MAIN_RE = /^main\.\d+\.m?js$/
7+
const MAIN_RE = /^main\.(.+)\.m?js$/
8+
const MAIN_ALL = 'main.all.js'
9+
10+
/**
11+
* Generates fixtures
12+
*
13+
* Each fixture is a directory with a `lib.js` file and a few `main.X.js`
14+
* files where `X` is an integer.
15+
* For each main file, it will generate a `cov.X.json` file containing coverage
16+
* for `lib.mjs` when using the corresponding main as the entry point.
17+
* It also generates a `cov.all.json` obtained by executing (importing) all
18+
* the entry points.
19+
*
20+
* Merging all the `cov.X.json` coverages should result in `cov.all.json`.
21+
*/
22+
async function main () {
23+
for (const fixtureName of getFixtures()) {
24+
await generateFixture(fixtureName)
25+
}
26+
}
27+
28+
function getFixtures () {
29+
return fs.readdirSync(__dirname)
30+
.filter((child) => fs.statSync(path.join(__dirname, child)).isDirectory())
31+
}
32+
33+
async function generateFixture (name) {
34+
const dir = path.join(__dirname, name)
35+
const simpleMains = getSimpleMains(dir)
36+
const mainAll = generateMainAll(simpleMains)
37+
await fs.promises.writeFile(path.join(dir, MAIN_ALL), mainAll)
38+
for (const simpleMain of [...simpleMains, MAIN_ALL]) {
39+
const id = MAIN_RE.exec(simpleMain)[1]
40+
const libCoverage = await getLibCoverage(dir, simpleMain)
41+
await fs.promises.writeFile(path.join(dir, `cov.${id}.json`), JSON.stringify(libCoverage, null, 2))
42+
}
43+
}
44+
45+
function getSimpleMains (dir) {
46+
return fs.readdirSync(dir)
47+
.filter((child) => SIMPLE_MAIN_RE.test(child))
48+
.sort((a, b) => {
49+
a = parseInt(MAIN_RE.exec(a)[1])
50+
b = parseInt(MAIN_RE.exec(b)[1])
51+
return a - b
52+
})
53+
}
54+
55+
function generateMainAll (mains) {
56+
return [
57+
'// DO NOT EDIT: generated by `generate-fixtures.js`',
58+
'const lib = require.resolve(\'./lib.js\')',
59+
'function main () {',
60+
...mains.map(main => ` require(${JSON.stringify(`./${main}`)})\n delete require.cache[lib]`),
61+
'}',
62+
'main()',
63+
'',
64+
].join('\n')
65+
}
66+
67+
async function getLibCoverage (dir, main) {
68+
const covDirName = `cov-${main}`
69+
const covDir = path.join(dir, covDirName)
70+
await rmDir(covDir)
71+
await spawnWithCoverage(dir, main, covDir)
72+
const coverages = await readCoverage(covDir)
73+
await rmDir(covDir)
74+
const urlEnd = path.join('test', 'fixtures', path.basename(dir), 'lib.js')
75+
for (const coverage of coverages) {
76+
if (coverage.url.endsWith(urlEnd)) {
77+
return {
78+
scriptId: '1',
79+
url: path.posix.join('/', 'test', 'fixtures', path.basename(dir), 'lib.js'),
80+
functions: coverage.functions
81+
}
82+
}
83+
}
84+
throw new Error(`Coverage not found for \`lib.js\` for ${path.join(dir, main)}`)
85+
}
86+
87+
async function spawnWithCoverage (dir, main, covDir) {
88+
return new Promise((resolve, reject) => {
89+
const proc = cp.spawn(
90+
process.execPath,
91+
[main],
92+
{
93+
cwd: dir,
94+
env: {NODE_V8_COVERAGE: covDir},
95+
},
96+
)
97+
proc.once('close', (code, signal) => {
98+
if (code === 0) {
99+
resolve()
100+
} else {
101+
reject(new Error(`Non-zero return code for: ${path.join(dir, main)}`))
102+
}
103+
})
104+
})
105+
}
106+
107+
async function rmDir (dir) {
108+
return new Promise((resolve, reject) => {
109+
rimraf(dir, (err) => {
110+
if (err !== null) {
111+
reject(err)
112+
} else {
113+
resolve()
114+
}
115+
})
116+
})
117+
}
118+
119+
async function readCoverage (covDir) {
120+
const covChildren = fs.readdirSync(covDir)
121+
if (covChildren.length !== 1) {
122+
throw new Error(`Expected a single file in: ${covDir}`)
123+
}
124+
return JSON.parse(await fs.promises.readFile(path.join(covDir, covChildren[0]), {encoding: 'UTF-8'})).result
125+
}
126+
127+
main()

test/fixtures/hello-world/lib.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = function () {
2+
console.log('Hello, World!')
3+
}

test/fixtures/hello-world/main.0.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const lib = require('./lib')
2+
3+
lib()

test/fixtures/hello-world/main.1.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const lib = require('./lib')
2+
3+
lib()

test/fixtures/if/lib.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module.exports = function (test) {
2+
if (test) {
3+
console.log('a')
4+
} else {
5+
console.log('b')
6+
}
7+
8+
if (!test) {
9+
console.log('c')
10+
} else {
11+
console.log('d')
12+
}
13+
14+
if (test) {
15+
console.log('e')
16+
}
17+
18+
if (!test) {
19+
console.log('f')
20+
}
21+
}

test/fixtures/if/main.0.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const lib = require('./lib')
2+
3+
lib(true)

test/fixtures/if/main.1.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const lib = require('./lib')
2+
3+
lib(false)

test.js test/test.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* global it, describe */
22

3-
const merge = require('./')
3+
const merge = require('../index')
44

55
require('chai').should()
66

0 commit comments

Comments
 (0)