How to filter test using --test-filter 'test-name' in conjunction with build.zig

I would like to run the following command :
zig test --test-filter ‘Curve’ src/curve.zig

But when I do this I get the folllowing error message :

I linked my depedency with the run exe and the test exe in build.zig, however it won’t compile.

Btw if I use :
zig build test

It works.

1 Like

I think I have found a crude solution I’m sure there is a better one but here we go

    const maybe_filters = b.args;

    if (maybe_filters) |filters| {
        const exe_unit_test_compile = b.addTest(.{
            .name = "Project",
            .root_source_file = b.path("src/main.zig"),
            .optimize = optimize,
            .target = target,
            .filter = filters[0],
        });
        exe_unit_test_compile.linkLibrary(raylib_artifact);
        exe_unit_test_compile.root_module.addImport("raylib", raylib);
        const exe_unit_test_run_from_artifact = b.addRunArtifact(exe_unit_test_compile);
        const exe_unit_test_step = b.step("test", "Test Project");
        exe_unit_test_step.dependOn(&exe_unit_test_run_from_artifact.step);
    } else {
        const exe_unit_test_compile = b.addTest(.{
            .name = "Project",
            .root_source_file = b.path("src/main.zig"),
            .optimize = optimize,
            .target = target,
        });
        exe_unit_test_compile.linkLibrary(raylib_artifact);
        exe_unit_test_compile.root_module.addImport("raylib", raylib);
        const exe_unit_test_run_from_artifact = b.addRunArtifact(exe_unit_test_compile);
        const exe_unit_test_step = b.step("test", "Test Project");
        exe_unit_test_step.dependOn(&exe_unit_test_run_from_artifact.step);
    }

When building (and running) a test executable via the build system you use the filters field of std.Build.TestOption (which is passed to b.addTest) to filter the test cases. You can of course hard code the filter list, but if you want to take filters from the command line, you need to manually define a -D option that takes a []const []const u8, for example:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // ...

    const test_filters: []const []const u8 = b.option(
        []const []const u8,
        "test_filter",
        "Skip tests that do not match any of the specified filters",
    ) orelse &.{};

    const lib_unit_tests = b.addTest(.{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .filters = test_filters,
        .optimize = optimize,
    });

    const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);

    const test_step = b.step("test", "Run unit tests");
    test_step.dependOn(&run_lib_unit_tests.step);
}

With this option defined, you can invoke the build like zig build test -Dtest_filter=one -Dtest_filter=two to only run tests with names containing the substrings one and two.

2 Likes

At tigerbeetle, we do

        const unit_tests = b.addTest(.{
            .root_source_file = b.path("src/unit_tests.zig"),
            .target = target,
            .optimize = mode,
            .filters = b.args orelse &.{}, // <- this
        }); 

the usage looks like

./zig/zig build test -- state_sync
7 Likes

That’s clever, though one drawback is that it’s less automatically discoverable by random users since they can’t just clone the project, run zig build --help and discover it as a project-specific option that way. I think most users would assume that args after -- will be forwarded to the program being run.