Bug Report
Summary
On Windows, running merge-coverage produces a merged coverage-final.json where every source file appears twice — once with a Windows-style path (backslashes) and once with a POSIX-style path (forward slashes). This results in doubled entry counts (e.g. 388 instead of the expected 194).
Root Cause
Two separate path-format issues combine to cause duplication:
1. Inconsistent path separators across coverage tools
nyc generates Windows-style absolute paths on Windows:
D:\gith1\tripwire\core\src\assert\assertClass.ts
karma-typescript generates POSIX-style paths even on Windows:
D:/gith1/tripwire/core/src/assert/assertClass.ts
istanbul-lib-coverage's merge() uses exact string key comparison, so it treats these as different files and includes both in the merged output.
2. The coverage/coverage-final.json exclusion filter fails on Windows
The merge-coverage glob returns backslash-separated paths on Windows (e.g. D:\...\coverage\coverage-final.json), but the exclusion filter uses a forward-slash substring match (indexOf("coverage/coverage-final.json")). The match fails, so the previously-generated merged output is re-included in the next merge run — adding another full copy of all entries.
Steps to Reproduce
- On Windows, run tests that produce coverage via both
nyc (node) and karma-typescript (browser/worker)
- Run
merge-coverage
- Count keys in the output
coverage-final.json — it will be 2× the expected count
Workaround (applied in consumer project)
Added a pre-merge normalization script that:
- Converts all backslash path keys (and
path properties) to forward slashes in every intermediate coverage-final.json
- Deletes the stale root-level
coverage-final.json before merging (bypassing the broken exclusion filter)
// normalize-coverage.js
var fs = require("fs"), path = require("path");
function normalizePaths(filePath) {
var data = JSON.parse(fs.readFileSync(filePath, "utf8"));
var normalized = {}, changed = false;
Object.keys(data).forEach(function(key) {
var nk = key.split("\\").join("/");
if (nk !== key) { changed = true; }
normalized[nk] = data[key];
if (normalized[nk].path) {
normalized[nk].path = normalized[nk].path.split("\\").join("/");
}
});
if (changed) { fs.writeFileSync(filePath, JSON.stringify(normalized)); }
}
function walk(dir, rootMerged) {
fs.readdirSync(dir).forEach(function(entry) {
var p = path.join(dir, entry);
if (fs.statSync(p).isDirectory()) { walk(p, rootMerged); }
else if (entry === "coverage-final.json" && p !== rootMerged) { normalizePaths(p); }
});
}
var coverageDir = path.resolve(__dirname, "coverage");
var rootMerged = path.join(coverageDir, "coverage-final.json");
if (fs.existsSync(rootMerged)) { fs.unlinkSync(rootMerged); }
walk(coverageDir, rootMerged);
Bug Report
Summary
On Windows, running
merge-coverageproduces a mergedcoverage-final.jsonwhere every source file appears twice — once with a Windows-style path (backslashes) and once with a POSIX-style path (forward slashes). This results in doubled entry counts (e.g. 388 instead of the expected 194).Root Cause
Two separate path-format issues combine to cause duplication:
1. Inconsistent path separators across coverage tools
nycgenerates Windows-style absolute paths on Windows:D:\gith1\tripwire\core\src\assert\assertClass.tskarma-typescriptgenerates POSIX-style paths even on Windows:D:/gith1/tripwire/core/src/assert/assertClass.tsistanbul-lib-coverage'smerge()uses exact string key comparison, so it treats these as different files and includes both in the merged output.2. The
coverage/coverage-final.jsonexclusion filter fails on WindowsThe
merge-coverageglob returns backslash-separated paths on Windows (e.g.D:\...\coverage\coverage-final.json), but the exclusion filter uses a forward-slash substring match (indexOf("coverage/coverage-final.json")). The match fails, so the previously-generated merged output is re-included in the next merge run — adding another full copy of all entries.Steps to Reproduce
nyc(node) andkarma-typescript(browser/worker)merge-coveragecoverage-final.json— it will be 2× the expected countWorkaround (applied in consumer project)
Added a pre-merge normalization script that:
pathproperties) to forward slashes in every intermediatecoverage-final.jsoncoverage-final.jsonbefore merging (bypassing the broken exclusion filter)