diff --git a/docs/test/coverage.md b/docs/test/coverage.md index bd24f391e6..333eb3fda8 100644 --- a/docs/test/coverage.md +++ b/docs/test/coverage.md @@ -68,16 +68,24 @@ coverageIgnoreSourcemaps = true # default false By default, coverage reports will be printed to the console. -You can specify the reporters and the directory where the reports will be saved. -This is needed, especially when you integrate coverages with tools like CodeCov, CodeClimate, Coveralls and so on. +For persistent code coverage reports in CI environments and for other tools, you can pass a `--coverage-reporter=lcov` CLI option or `coverageReporter` option in `bunfig.toml`. ```toml -coverageReporters = ["console", "lcov"] # default ["console"] +[test] +coverageReporter = ["text", "lcov"] # default ["text"] coverageDir = "path/to/somewhere" # default "coverage" ``` -| Reporter | Description | -|-----------|-------------| -| `console` | Prints a text summary of the coverage to the console. | -| `lcov` | Save coverage in [lcov](https://github.com/linux-test-project/lcov) format. | +| Reporter | Description | +| -------- | --------------------------------------------------------------------------- | +| `text` | Prints a text summary of the coverage to the console. | +| `lcov` | Save coverage in [lcov](https://github.com/linux-test-project/lcov) format. | +#### lcov coverage reporter + +To generate an lcov report, you can use the `lcov` reporter. This will generate an `lcov.info` file in the `coverage` directory. + +```toml +[test] +coverageReporter = "lcov" +``` diff --git a/src/bunfig.zig b/src/bunfig.zig index 66b12e1d6b..0b6002ff09 100644 --- a/src/bunfig.zig +++ b/src/bunfig.zig @@ -258,15 +258,28 @@ pub const Bunfig = struct { this.ctx.test_options.coverage.enabled = expr.data.e_boolean.value; } - if (test_.get("coverageReporters")) |expr| { - this.ctx.test_options.coverage.reporters = .{ .console = false, .lcov = false }; + if (test_.get("coverageReporter")) |expr| brk: { + this.ctx.test_options.coverage.reporters = .{ .text = false, .lcov = false }; + if (expr.data == .e_string) { + const item_str = expr.asString(bun.default_allocator) orelse ""; + if (bun.strings.eqlComptime(item_str, "text")) { + this.ctx.test_options.coverage.reporters.text = true; + } else if (bun.strings.eqlComptime(item_str, "lcov")) { + this.ctx.test_options.coverage.reporters.lcov = true; + } else { + try this.addErrorFormat(expr.loc, allocator, "Invalid coverage reporter \"{s}\"", .{item_str}); + } + + break :brk; + } + try this.expect(expr, .e_array); const items = expr.data.e_array.items.slice(); for (items) |item| { try this.expectString(item); const item_str = item.asString(bun.default_allocator) orelse ""; - if (bun.strings.eqlComptime(item_str, "console")) { - this.ctx.test_options.coverage.reporters.console = true; + if (bun.strings.eqlComptime(item_str, "text")) { + this.ctx.test_options.coverage.reporters.text = true; } else if (bun.strings.eqlComptime(item_str, "lcov")) { this.ctx.test_options.coverage.reporters.lcov = true; } else { diff --git a/src/cli.zig b/src/cli.zig index 09a4b617b4..f4944fbfed 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -249,7 +249,7 @@ pub const Arguments = struct { clap.parseParam("--only Only run tests that are marked with \"test.only()\"") catch unreachable, clap.parseParam("--todo Include tests that are marked with \"test.todo()\"") catch unreachable, clap.parseParam("--coverage Generate a coverage profile") catch unreachable, - clap.parseParam("--coverage-reporter ... Report coverage in 'console' and/or 'lcov'. Defaults to 'console'.") catch unreachable, + clap.parseParam("--coverage-reporter ... Report coverage in 'text' and/or 'lcov'. Defaults to 'text'.") catch unreachable, clap.parseParam("--coverage-dir Directory for coverage files. Defaults to 'coverage'.") catch unreachable, clap.parseParam("--bail ? Exit the test suite after failures. If you do not specify a number, it defaults to 1.") catch unreachable, clap.parseParam("-t, --test-name-pattern Run only tests with a name that matches the given regex.") catch unreachable, @@ -444,10 +444,10 @@ pub const Arguments = struct { } if (args.options("--coverage-reporter").len > 0) { - ctx.test_options.coverage.reporters = .{ .console = false, .lcov = false }; + ctx.test_options.coverage.reporters = .{ .text = false, .lcov = false }; for (args.options("--coverage-reporter")) |reporter| { - if (bun.strings.eqlComptime(reporter, "console")) { - ctx.test_options.coverage.reporters.console = true; + if (bun.strings.eqlComptime(reporter, "text")) { + ctx.test_options.coverage.reporters.text = true; } else if (bun.strings.eqlComptime(reporter, "lcov")) { ctx.test_options.coverage.reporters.lcov = true; } else { diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig index 89fdccda7c..e24d54c84b 100644 --- a/src/cli/test_command.zig +++ b/src/cli/test_command.zig @@ -272,7 +272,7 @@ pub const CommandLineReporter = struct { } pub fn generateCodeCoverage(this: *CommandLineReporter, vm: *JSC.VirtualMachine, opts: *TestCommand.CodeCoverageOptions, comptime reporters: TestCommand.Reporters, comptime enable_ansi_colors: bool) !void { - if (comptime !reporters.console and !reporters.lcov) { + if (comptime !reporters.text and !reporters.lcov) { return; } @@ -296,12 +296,12 @@ pub const CommandLineReporter = struct { pub fn printCodeCoverage(this: *CommandLineReporter, vm: *JSC.VirtualMachine, opts: *TestCommand.CodeCoverageOptions, byte_ranges: []bun.sourcemap.ByteRangeMapping, comptime reporters: TestCommand.Reporters, comptime enable_ansi_colors: bool) !void { _ = this; // autofix const trace = bun.tracy.traceNamed(@src(), comptime brk: { - if (reporters.console and reporters.lcov) { - break :brk "TestCommand.printCodeCoverageLCovAndConsole"; + if (reporters.text and reporters.lcov) { + break :brk "TestCommand.printCodeCoverageLCovAndText"; } - if (reporters.console) { - break :brk "TestCommand.printCodeCoverageConsole"; + if (reporters.text) { + break :brk "TestCommand.printCodeCoverageText"; } if (reporters.lcov) { @@ -312,14 +312,14 @@ pub const CommandLineReporter = struct { }); defer trace.end(); - if (comptime !reporters.console and !reporters.lcov) { + if (comptime !reporters.text and !reporters.lcov) { @compileError("No reporters enabled"); } const relative_dir = vm.bundler.fs.top_level_dir; - // --- Console --- - const max_filepath_length: usize = if (reporters.console) brk: { + // --- Text --- + const max_filepath_length: usize = if (reporters.text) brk: { var len = "All files".len; for (byte_ranges) |*entry| { const utf8 = entry.source_url.slice(); @@ -333,7 +333,7 @@ pub const CommandLineReporter = struct { const base_fraction = opts.fractions; var failing = false; - if (comptime reporters.console) { + if (comptime reporters.text) { console.writeAll(Output.prettyFmt("", enable_ansi_colors)) catch return; console.writeByteNTimes('-', max_filepath_length + 2) catch return; console.writeAll(Output.prettyFmt("|---------|---------|-------------------\n", enable_ansi_colors)) catch return; @@ -356,7 +356,7 @@ pub const CommandLineReporter = struct { .stmts = 0.0, }; var avg_count: f64 = 0; - // --- Console --- + // --- Text --- // --- LCOV --- var lcov_name_buf: bun.PathBuffer = undefined; @@ -429,9 +429,9 @@ pub const CommandLineReporter = struct { var report = CodeCoverageReport.generate(vm.global, bun.default_allocator, entry, opts.ignore_sourcemap) orelse continue; defer report.deinit(bun.default_allocator); - if (comptime reporters.console) { + if (comptime reporters.text) { var fraction = base_fraction; - CodeCoverageReport.Console.writeFormat(&report, max_filepath_length, &fraction, relative_dir, console_writer, enable_ansi_colors) catch continue; + CodeCoverageReport.Text.writeFormat(&report, max_filepath_length, &fraction, relative_dir, console_writer, enable_ansi_colors) catch continue; avg.functions += fraction.functions; avg.lines += fraction.lines; avg.stmts += fraction.stmts; @@ -452,13 +452,13 @@ pub const CommandLineReporter = struct { } } - if (comptime reporters.console) { + if (comptime reporters.text) { { avg.functions /= avg_count; avg.lines /= avg_count; avg.stmts /= avg_count; - try CodeCoverageReport.Console.writeFormatWithValues( + try CodeCoverageReport.Text.writeFormatWithValues( "All files", max_filepath_length, avg, @@ -705,7 +705,7 @@ pub const TestCommand = struct { pub const name = "test"; pub const CodeCoverageOptions = struct { skip_test_files: bool = !Environment.allow_assert, - reporters: Reporters = .{ .console = true, .lcov = false }, + reporters: Reporters = .{ .text = true, .lcov = false }, reports_directory: string = "coverage", fractions: bun.sourcemap.CoverageFraction = .{}, ignore_sourcemap: bool = false, @@ -713,11 +713,11 @@ pub const TestCommand = struct { fail_on_low_coverage: bool = false, }; pub const Reporter = enum { - console, + text, lcov, }; const Reporters = struct { - console: bool, + text: bool, lcov: bool, }; @@ -1002,10 +1002,10 @@ pub const TestCommand = struct { if (coverage.enabled) { switch (Output.enable_ansi_colors_stderr) { - inline else => |colors| switch (coverage.reporters.console) { + inline else => |colors| switch (coverage.reporters.text) { inline else => |console| switch (coverage.reporters.lcov) { inline else => |lcov| { - try reporter.generateCodeCoverage(vm, &coverage, .{ .console = console, .lcov = lcov }, colors); + try reporter.generateCodeCoverage(vm, &coverage, .{ .text = console, .lcov = lcov }, colors); }, }, }, diff --git a/src/sourcemap/CodeCoverage.zig b/src/sourcemap/CodeCoverage.zig index be2deaafed..20db0f1ac0 100644 --- a/src/sourcemap/CodeCoverage.zig +++ b/src/sourcemap/CodeCoverage.zig @@ -65,7 +65,7 @@ pub const CodeCoverageReport = struct { return (@as(f64, @floatFromInt(this.functions_which_have_executed.count())) / total_count); } - pub const Console = struct { + pub const Text = struct { pub fn writeFormatWithValues( filename: []const u8, max_filename_length: usize, @@ -685,7 +685,7 @@ pub const ByteRangeMapping = struct { var buffered_writer = mutable_str.bufferedWriter(); var writer = buffered_writer.writer(); - CodeCoverageReport.Console.writeFormat(&report, source_url.utf8ByteLength(), &coverage_fraction, "", &writer, false) catch { + CodeCoverageReport.Text.writeFormat(&report, source_url.utf8ByteLength(), &coverage_fraction, "", &writer, false) catch { globalThis.throwOutOfMemory(); return .zero; };