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
28 changes: 26 additions & 2 deletions packages/istanbul-lib-report/src/file-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,38 @@ class ConsoleWriter extends ContentWriter {
high: "32;1",
};

/* istanbul ignore next: different modes for CI and local */
if (process.stdout.isTTY && process.stdout.hasColors() && colors[clazz as string]) {
if (colors[clazz as string] && supportsColor()) {
return `\u001b[${colors[clazz as string]}m${str}\u001b[0m`;
}
return str;
}
}

/**
* Whether ANSI colour codes should be emitted on `stream`. Mirrors the
* `supports-color` semantics upstream istanbul relied on:
*
* - `FORCE_COLOR` wins over everything: `0`/`false` disables colours, any
* other value (including an empty string) enables them even when the
* stream is not a TTY, e.g. when a CI runner pipes the output.
* - `NO_COLOR` or `NODE_DISABLE_COLORS` being set disables colours.
* - Otherwise colours are used only for a TTY that reports colour support.
*/
export function supportsColor(
stream: NodeJS.WriteStream = process.stdout,
env: NodeJS.ProcessEnv = process.env,
): boolean {
const force = env.FORCE_COLOR;
if (force !== undefined) {
return force !== "0" && force !== "false";
}
if (env.NO_COLOR !== undefined || env.NODE_DISABLE_COLORS !== undefined) {
return false;
}
/* istanbul ignore next: depends on whether the test runner's stdout is a TTY */
return Boolean(stream.isTTY && stream.hasColors?.());
}

/**
* utility for writing files under a specific directory
* @class FileWriter
Expand Down
7 changes: 5 additions & 2 deletions packages/istanbul-lib-report/src/reports/cobertura/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,11 @@ class CoberturaReport extends ReportBase {

this.xml!.openTag("methods");
const fnMap = fileCoverage.fnMap;
Object.entries(fnMap).forEach(([k, { name, decl }]) => {
Object.entries(fnMap).forEach(([k, { name, decl, loc }]) => {
const hits = fileCoverage.f[k];
// Some versions of the instrumenter in the wild populate 'loc'
// but not 'decl':
const start = (decl || loc).start;
this.xml!.openTag("method", {
name: escape(name),
hits,
Expand All @@ -125,7 +128,7 @@ class CoberturaReport extends ReportBase {
this.xml!.openTag("lines");
//Add the function definition line and hits so that jenkins cobertura plugin records method hits
this.xml!.inlineTag("line", {
number: decl.start.line,
number: start.line,
hits,
});
this.xml!.closeTag("lines");
Expand Down
47 changes: 45 additions & 2 deletions packages/istanbul-lib-report/test/file-writer.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import fs from "node:fs";
import path from "node:path";

import { describe, it, assert, beforeEach, afterEach } from "vitest";
import { describe, it, assert, beforeEach, afterEach, afterAll, vi } from "vitest";

import FileWriter from "../src/file-writer";
import FileWriter, { supportsColor } from "../src/file-writer";

const dataDir = path.resolve(import.meta.dirname, ".data");

Expand Down Expand Up @@ -82,3 +82,46 @@ describe("file-writer", () => {
});
});
});

describe("supportsColor", () => {
afterAll(() => {
vi.unstubAllEnvs();
});

const tty = { isTTY: true, hasColors: () => true } as unknown as NodeJS.WriteStream;
const pipe = { isTTY: false } as unknown as NodeJS.WriteStream;

it("uses colors for a tty that supports them", () => {
assert.isTrue(supportsColor(tty, {}));
assert.isFalse(supportsColor({ ...tty, hasColors: () => false } as NodeJS.WriteStream, {}));
});

it("does not use colors when the stream is not a tty", () => {
assert.isFalse(supportsColor(pipe, {}));
});

it("honors FORCE_COLOR even when the stream is not a tty", () => {
assert.isTrue(supportsColor(pipe, { FORCE_COLOR: "1" }));
assert.isTrue(supportsColor(pipe, { FORCE_COLOR: "" }));
assert.isTrue(supportsColor(pipe, { FORCE_COLOR: "true" }));
assert.isFalse(supportsColor(tty, { FORCE_COLOR: "0" }));
assert.isFalse(supportsColor(tty, { FORCE_COLOR: "false" }));
});

it("honors NO_COLOR and NODE_DISABLE_COLORS", () => {
assert.isFalse(supportsColor(tty, { NO_COLOR: "1" }));
assert.isFalse(supportsColor(tty, { NO_COLOR: "" }));
assert.isFalse(supportsColor(tty, { NODE_DISABLE_COLORS: "1" }));
// FORCE_COLOR takes precedence
assert.isTrue(supportsColor(pipe, { NO_COLOR: "1", FORCE_COLOR: "1" }));
});

it("colorizes console output according to FORCE_COLOR", () => {
const cw = new FileWriter("/").writeFile("-");
vi.stubEnv("FORCE_COLOR", "1");
assert.equal(cw.colorize("foo", "low"), "\u001b[31;1mfoo\u001b[0m");
assert.equal(cw.colorize("foo", "unknown"), "foo");
vi.stubEnv("FORCE_COLOR", "0");
assert.equal(cw.colorize("foo", "low"), "foo");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,24 @@ import { createRequire } from "node:module";
import path from "node:path";

import * as istanbulLibCoverage from "@vitest/istanbul-lib-coverage";
import { afterAll as after, beforeAll as before, beforeEach, describe, it, should } from "vitest";
import { afterAll as after, assert, beforeAll as before, beforeEach, describe, it } from "vitest";

import * as istanbulLibReport from "../../../src/index";
import { FileWriter } from "../../../src/index";
import CoberturaReport from "../../../src/reports/cobertura/index";

const require = createRequire(import.meta.url);

should();
/**
* `filename` attributes come from `path.relative()`, which uses backslashes
* on Windows. The fixtures were generated on POSIX, so compare with forward
* slashes on every platform.
*/
function normalizeFilenames(xml: string): string {
return xml.replace(/filename="([^"]*)"/g, (_, filename: string) => {
return `filename="${filename.replaceAll("\\", "/")}"`;
});
}

describe("CoberturaReport", () => {
before(() => {
Expand All @@ -26,11 +35,7 @@ describe("CoberturaReport", () => {

function createTest(file: string) {
const fixture = require(path.resolve(import.meta.dirname, "../fixtures/specs/" + file));
it(fixture.title, function (this: { skip(): void }) {
if (process.platform === "win32") {
// appveyor does not render console color.
return this.skip();
}
it(fixture.title, () => {
const context = istanbulLibReport.createContext({
dir: "./",
coverageMap: istanbulLibCoverage.createCoverageMap(fixture.map),
Expand All @@ -43,7 +48,7 @@ describe("CoberturaReport", () => {
});
tree.visit(report, context);
const output = FileWriter.getOutput();
(output as any).should.equal(fixture.coberturaCoverageData);
assert.equal(normalizeFilenames(output), fixture.coberturaCoverageData);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
}
]
},
"coberturaCoverageData": "<?xml version=\"1.0\" ?>\n<!DOCTYPE coverage SYSTEM \"http://cobertura.sourceforge.net/xml/coverage-04.dtd\">\n<coverage lines-valid=\"46\" lines-covered=\"46\" line-rate=\"1\" branches-valid=\"43\" branches-covered=\"41\" branch-rate=\"0.9534999999999999\" timestamp=\"123456789\" complexity=\"0\" version=\"0.1\">\n <sources>\n <source>/Users/benjamincoe/oss/</source>\n </sources>\n <packages>\n <package name=\"main\" line-rate=\"1\" branch-rate=\"0.9534999999999999\">\n <classes>\n <class name=\"index.js\" filename=\"test-exclude/index.js\" line-rate=\"1\" branch-rate=\"0.9534999999999999\">\n <methods>\n <method name=\"TestExclude\" hits=\"21\" signature=\"()V\">\n <lines>\n <line number=\"12\" hits=\"21\"/>\n </lines>\n </method>\n <method name=\"(anonymous_1)\" hits=\"21\" signature=\"()V\">\n <lines>\n <line number=\"50\" hits=\"21\"/>\n </lines>\n </method>\n <method name=\"(anonymous_2)\" hits=\"95\" signature=\"()V\">\n <lines>\n <line number=\"52\" hits=\"95\"/>\n </lines>\n </method>\n <method name=\"(anonymous_3)\" hits=\"56\" signature=\"()V\">\n <lines>\n <line number=\"61\" hits=\"56\"/>\n </lines>\n </method>\n <method name=\"(anonymous_4)\" hits=\"7\" signature=\"()V\">\n <lines>\n <line number=\"71\" hits=\"7\"/>\n </lines>\n </method>\n <method name=\"prepGlobPatterns\" hits=\"26\" signature=\"()V\">\n <lines>\n <line number=\"84\" hits=\"26\"/>\n </lines>\n </method>\n <method name=\"(anonymous_6)\" hits=\"105\" signature=\"()V\">\n <lines>\n <line number=\"85\" hits=\"105\"/>\n </lines>\n </method>\n <method name=\"(anonymous_7)\" hits=\"21\" signature=\"()V\">\n <lines>\n <line number=\"100\" hits=\"21\"/>\n </lines>\n </method>\n </methods>\n <lines>\n <line number=\"1\" hits=\"1\" branch=\"false\"/>\n <line number=\"2\" hits=\"1\" branch=\"false\"/>\n <line number=\"3\" hits=\"1\" branch=\"false\"/>\n <line number=\"4\" hits=\"1\" branch=\"false\"/>\n <line number=\"5\" hits=\"1\" branch=\"false\"/>\n <line number=\"13\" hits=\"21\" branch=\"false\"/>\n <line number=\"21\" hits=\"21\" branch=\"true\" condition-coverage=\"75% (3/4)\"/>\n <line number=\"22\" hits=\"21\" branch=\"true\" condition-coverage=\"100% (2/2)\"/>\n <line number=\"24\" hits=\"21\" branch=\"true\" condition-coverage=\"100% (5/5)\"/>\n <line number=\"25\" hits=\"7\" branch=\"false\"/>\n <line number=\"28\" hits=\"21\" branch=\"true\" condition-coverage=\"100% (4/4)\"/>\n <line number=\"29\" hits=\"15\" branch=\"true\" condition-coverage=\"50% (1/2)\"/>\n <line number=\"32\" hits=\"21\" branch=\"true\" condition-coverage=\"100% (4/4)\"/>\n <line number=\"33\" hits=\"5\" branch=\"false\"/>\n <line number=\"35\" hits=\"16\" branch=\"false\"/>\n <line number=\"38\" hits=\"21\" branch=\"true\" condition-coverage=\"100% (4/4)\"/>\n <line number=\"39\" hits=\"5\" branch=\"false\"/>\n <line number=\"42\" hits=\"21\" branch=\"false\"/>\n <line number=\"50\" hits=\"1\" branch=\"false\"/>\n <line number=\"51\" hits=\"21\" branch=\"false\"/>\n <line number=\"52\" hits=\"21\" branch=\"false\"/>\n <line number=\"53\" hits=\"95\" branch=\"false\"/>\n <line number=\"55\" hits=\"95\" branch=\"true\" condition-coverage=\"100% (2/2)\"/>\n <line number=\"56\" hits=\"95\" branch=\"false\"/>\n <line number=\"58\" hits=\"21\" branch=\"false\"/>\n <line number=\"61\" hits=\"1\" branch=\"false\"/>\n <line number=\"62\" hits=\"56\" branch=\"true\" condition-coverage=\"100% (2/2)\"/>\n <line number=\"65\" hits=\"56\" branch=\"true\" condition-coverage=\"100% (2/2)\"/>\n <line number=\"67\" hits=\"55\" branch=\"false\"/>\n <line number=\"68\" hits=\"55\" branch=\"true\" condition-coverage=\"100% (3/3)\"/>\n <line number=\"71\" hits=\"1\" branch=\"false\"/>\n <line number=\"72\" hits=\"7\" branch=\"false\"/>\n <line number=\"76\" hits=\"7\" branch=\"true\" condition-coverage=\"100% (5/5)\"/>\n <line number=\"77\" hits=\"6\" branch=\"false\"/>\n <line number=\"78\" hits=\"6\" branch=\"false\"/>\n <line number=\"80\" hits=\"1\" branch=\"false\"/>\n <line number=\"85\" hits=\"26\" branch=\"false\"/>\n <line number=\"87\" hits=\"105\" branch=\"true\" condition-coverage=\"100% (2/2)\"/>\n <line number=\"88\" hits=\"39\" branch=\"false\"/>\n <line number=\"92\" hits=\"105\" branch=\"true\" condition-coverage=\"100% (2/2)\"/>\n <line number=\"93\" hits=\"50\" branch=\"false\"/>\n <line number=\"96\" hits=\"105\" branch=\"false\"/>\n <line number=\"100\" hits=\"1\" branch=\"false\"/>\n <line number=\"101\" hits=\"21\" branch=\"false\"/>\n <line number=\"104\" hits=\"1\" branch=\"false\"/>\n <line number=\"113\" hits=\"1\" branch=\"false\"/>\n </lines>\n </class>\n </classes>\n </package>\n </packages>\n</coverage>\n",
"coberturaCoverageData": "<?xml version=\"1.0\" ?>\n<!DOCTYPE coverage SYSTEM \"http://cobertura.sourceforge.net/xml/coverage-04.dtd\">\n<coverage lines-valid=\"46\" lines-covered=\"46\" line-rate=\"1\" branches-valid=\"43\" branches-covered=\"41\" branch-rate=\"0.9534\" timestamp=\"123456789\" complexity=\"0\" version=\"0.1\">\n <sources>\n <source>/Users/benjamincoe/oss/</source>\n </sources>\n <packages>\n <package name=\"main\" line-rate=\"1\" branch-rate=\"0.9534\">\n <classes>\n <class name=\"index.js\" filename=\"test-exclude/index.js\" line-rate=\"1\" branch-rate=\"0.9534\">\n <methods>\n <method name=\"TestExclude\" hits=\"21\" signature=\"()V\">\n <lines>\n <line number=\"12\" hits=\"21\"/>\n </lines>\n </method>\n <method name=\"(anonymous_1)\" hits=\"21\" signature=\"()V\">\n <lines>\n <line number=\"50\" hits=\"21\"/>\n </lines>\n </method>\n <method name=\"(anonymous_2)\" hits=\"95\" signature=\"()V\">\n <lines>\n <line number=\"52\" hits=\"95\"/>\n </lines>\n </method>\n <method name=\"(anonymous_3)\" hits=\"56\" signature=\"()V\">\n <lines>\n <line number=\"61\" hits=\"56\"/>\n </lines>\n </method>\n <method name=\"(anonymous_4)\" hits=\"7\" signature=\"()V\">\n <lines>\n <line number=\"71\" hits=\"7\"/>\n </lines>\n </method>\n <method name=\"prepGlobPatterns\" hits=\"26\" signature=\"()V\">\n <lines>\n <line number=\"84\" hits=\"26\"/>\n </lines>\n </method>\n <method name=\"(anonymous_6)\" hits=\"105\" signature=\"()V\">\n <lines>\n <line number=\"85\" hits=\"105\"/>\n </lines>\n </method>\n <method name=\"(anonymous_7)\" hits=\"21\" signature=\"()V\">\n <lines>\n <line number=\"100\" hits=\"21\"/>\n </lines>\n </method>\n </methods>\n <lines>\n <line number=\"1\" hits=\"1\" branch=\"false\"/>\n <line number=\"2\" hits=\"1\" branch=\"false\"/>\n <line number=\"3\" hits=\"1\" branch=\"false\"/>\n <line number=\"4\" hits=\"1\" branch=\"false\"/>\n <line number=\"5\" hits=\"1\" branch=\"false\"/>\n <line number=\"13\" hits=\"21\" branch=\"false\"/>\n <line number=\"21\" hits=\"21\" branch=\"true\" condition-coverage=\"75% (3/4)\"/>\n <line number=\"22\" hits=\"21\" branch=\"true\" condition-coverage=\"100% (2/2)\"/>\n <line number=\"24\" hits=\"21\" branch=\"true\" condition-coverage=\"100% (5/5)\"/>\n <line number=\"25\" hits=\"7\" branch=\"false\"/>\n <line number=\"28\" hits=\"21\" branch=\"true\" condition-coverage=\"100% (4/4)\"/>\n <line number=\"29\" hits=\"15\" branch=\"true\" condition-coverage=\"50% (1/2)\"/>\n <line number=\"32\" hits=\"21\" branch=\"true\" condition-coverage=\"100% (4/4)\"/>\n <line number=\"33\" hits=\"5\" branch=\"false\"/>\n <line number=\"35\" hits=\"16\" branch=\"false\"/>\n <line number=\"38\" hits=\"21\" branch=\"true\" condition-coverage=\"100% (4/4)\"/>\n <line number=\"39\" hits=\"5\" branch=\"false\"/>\n <line number=\"42\" hits=\"21\" branch=\"false\"/>\n <line number=\"50\" hits=\"1\" branch=\"false\"/>\n <line number=\"51\" hits=\"21\" branch=\"false\"/>\n <line number=\"52\" hits=\"21\" branch=\"false\"/>\n <line number=\"53\" hits=\"95\" branch=\"false\"/>\n <line number=\"55\" hits=\"95\" branch=\"true\" condition-coverage=\"100% (2/2)\"/>\n <line number=\"56\" hits=\"95\" branch=\"false\"/>\n <line number=\"58\" hits=\"21\" branch=\"false\"/>\n <line number=\"61\" hits=\"1\" branch=\"false\"/>\n <line number=\"62\" hits=\"56\" branch=\"true\" condition-coverage=\"100% (2/2)\"/>\n <line number=\"65\" hits=\"56\" branch=\"true\" condition-coverage=\"100% (2/2)\"/>\n <line number=\"67\" hits=\"55\" branch=\"false\"/>\n <line number=\"68\" hits=\"55\" branch=\"true\" condition-coverage=\"100% (3/3)\"/>\n <line number=\"71\" hits=\"1\" branch=\"false\"/>\n <line number=\"72\" hits=\"7\" branch=\"false\"/>\n <line number=\"76\" hits=\"7\" branch=\"true\" condition-coverage=\"100% (5/5)\"/>\n <line number=\"77\" hits=\"6\" branch=\"false\"/>\n <line number=\"78\" hits=\"6\" branch=\"false\"/>\n <line number=\"80\" hits=\"1\" branch=\"false\"/>\n <line number=\"85\" hits=\"26\" branch=\"false\"/>\n <line number=\"87\" hits=\"105\" branch=\"true\" condition-coverage=\"100% (2/2)\"/>\n <line number=\"88\" hits=\"39\" branch=\"false\"/>\n <line number=\"92\" hits=\"105\" branch=\"true\" condition-coverage=\"100% (2/2)\"/>\n <line number=\"93\" hits=\"50\" branch=\"false\"/>\n <line number=\"96\" hits=\"105\" branch=\"false\"/>\n <line number=\"100\" hits=\"1\" branch=\"false\"/>\n <line number=\"101\" hits=\"21\" branch=\"false\"/>\n <line number=\"104\" hits=\"1\" branch=\"false\"/>\n <line number=\"113\" hits=\"1\" branch=\"false\"/>\n </lines>\n </class>\n </classes>\n </package>\n </packages>\n</coverage>\n",
"map": {
"/Users/benjamincoe/oss/test-exclude/index.js": {
"path": "/Users/benjamincoe/oss/test-exclude/index.js",
Expand Down
Loading
Loading