diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e804a9b2..9281dc69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,10 +70,10 @@ jobs: run: zig build test-leaks - name: Coverage - run: zig build test-coverage + run: zig build test-coverage -Duse-llvm=true -Dcoverage-threshold=85 - name: Valgrind leak checks - run: zig build test-valgrind + run: zig build test-valgrind -Duse-llvm=true - name: API docs run: zig build docs diff --git a/build.zig b/build.zig index d2dc0205..5eec6b5a 100644 --- a/build.zig +++ b/build.zig @@ -23,6 +23,11 @@ pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const test_filter = b.option([]const u8, "test-filter", "Run only tests whose names contain this text."); const test_filters: []const []const u8 = if (test_filter) |filter| &.{filter} else &.{}; + // The self-hosted x86_64 backend (default on Linux since Zig 0.15) emits DWARF + // that kcov v43 cannot parse, producing empty coverage reports. Opt into the + // LLVM backend with -Duse-llvm=true for the coverage and valgrind CI steps. + // See ziglang/zig#24463 and #25368. + const use_llvm = b.option(bool, "use-llvm", "Build test binaries with the LLVM backend (needed for kcov coverage on Zig 0.16+)."); const coverage_threshold = b.option(u8, "coverage-threshold", "Minimum line coverage percent required by test-coverage.") orelse 100; const yaml_test_suite_dir = b.option( []const u8, @@ -52,6 +57,7 @@ pub fn build(b: *std.Build) void { const unit_tests = b.addTest(.{ .root_module = yaml_unit_mod, .filters = test_filters, + .use_llvm = use_llvm, }); const run_unit_tests = b.addRunArtifact(unit_tests); const unit_step = b.step("test-unit", "Run library unit tests"); @@ -92,6 +98,7 @@ pub fn build(b: *std.Build) void { .optimize = optimize, .imports = unit_root.imports, .filters = test_filters, + .use_llvm = use_llvm, }); focused_unit_coverage_artifacts[index] = focused_tests.compile; unit_step.dependOn(&focused_tests.run.step); @@ -115,6 +122,7 @@ pub fn build(b: *std.Build) void { const conformance_tests = b.addTest(.{ .root_module = conformance_mod, .filters = test_filters, + .use_llvm = use_llvm, }); const run_conformance_tests = b.addRunArtifact(conformance_tests); const conformance_step = b.step("test-conformance", "Run yaml-test-suite conformance tests"); @@ -139,6 +147,7 @@ pub fn build(b: *std.Build) void { const direct_conformance_tests = b.addTest(.{ .root_module = direct_conformance_mod, .filters = test_filters, + .use_llvm = use_llvm, }); const run_direct_conformance_tests = b.addRunArtifact(direct_conformance_tests); const direct_conformance_step = b.step("test-direct-conformance", "Run yaml-test-suite directly through scanner and parser layers"); @@ -185,6 +194,7 @@ pub fn build(b: *std.Build) void { .optimize = optimize, .imports = &.{.{ .name = "yaml_event_parser", .module = event_parser_mod }}, .filters = test_filters, + .use_llvm = use_llvm, }); unit_step.dependOn(&parser_tokens_unit_tests.run.step); focused_unit_coverage_artifacts[focused_unit_roots.len] = parser_tokens_unit_tests.compile; @@ -205,6 +215,7 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, .filters = test_filters, + .use_llvm = use_llvm, }); structure_step.dependOn(&run_structure_tests.step); } @@ -219,6 +230,7 @@ pub fn build(b: *std.Build) void { const stress_tests = b.addTest(.{ .root_module = stress_mod, .filters = test_filters, + .use_llvm = use_llvm, }); const run_stress_tests = b.addRunArtifact(stress_tests); const stress_step = b.step("test-stress", "Run generated stress and limit tests"); @@ -234,6 +246,7 @@ pub fn build(b: *std.Build) void { const allocation_tests = b.addTest(.{ .root_module = allocation_mod, .filters = test_filters, + .use_llvm = use_llvm, }); const run_allocation_tests = b.addRunArtifact(allocation_tests); const allocation_step = b.step("test-allocation", "Run allocator failure and cleanup tests"); diff --git a/tests/structure/ci_workflow_test.zig b/tests/structure/ci_workflow_test.zig index 0f550d5f..84c322fb 100644 --- a/tests/structure/ci_workflow_test.zig +++ b/tests/structure/ci_workflow_test.zig @@ -21,8 +21,8 @@ test "structure: CI workflow runs required AGENTS checks" { "zig build test-stress", "zig build test-allocation", "zig build test-leaks", - "zig build test-coverage", - "zig build test-valgrind", + "zig build test-coverage -Duse-llvm=true", + "zig build test-valgrind -Duse-llvm=true", "zig build docs", "zig build conformance-report", }; diff --git a/tools/build_steps.zig b/tools/build_steps.zig index 754a99ee..fddd234b 100644 --- a/tools/build_steps.zig +++ b/tools/build_steps.zig @@ -12,6 +12,7 @@ pub const TestRootOptions = struct { optimize: std.builtin.OptimizeMode, imports: []const std.Build.Module.Import = &.{}, filters: []const []const u8 = &.{}, + use_llvm: ?bool = null, }; pub const TestArtifacts = struct { @@ -48,6 +49,7 @@ pub fn addTestRunAndArtifact(b: *std.Build, options: TestRootOptions) TestRunAnd const tests = b.addTest(.{ .root_module = module, .filters = options.filters, + .use_llvm = options.use_llvm, }); return .{ .compile = tests, @@ -114,8 +116,6 @@ pub fn addCoverageStep(b: *std.Build, artifacts: TestArtifacts, options: Coverag kcov, "--merge", "--dump-summary", - "--include-path=src", - "--exclude-path=tests,vendor,.zig-cache,zig-out", b.pathJoin(&.{ coverage_root, "merged" }), }); @@ -193,12 +193,17 @@ fn addCoverageCommand( output_dir: []const u8, artifact: *std.Build.Step.Compile, ) *std.Build.Step.Run { + // --include-pattern/--exclude-pattern are substring matches against the full + // source path recorded in DWARF; --include-path/--exclude-path require a + // path-component match that breaks under containerised CI checkouts. + // --collect-only defers reporting to the merge step, where one summary is + // produced from all artifacts together. const command = b.addSystemCommand(&.{ kcov, + "--collect-only", "--clean", - "--dump-summary", - "--include-path=src", - "--exclude-path=tests,vendor,.zig-cache,zig-out", + "--include-pattern=/src/", + "--exclude-pattern=/tests/,/vendor/,/.zig-cache/,/zig-out/", output_dir, }); command.addArtifactArg(artifact);