Build tests as a binary

Hi! I want to build my tests as a separate binary and put it somewhere, e.g. in “zig-out/bin”.

I know that there is “-femit-bin” option for “zig test”. But I want to build tests using my build.zig file (my tests use imports). I tried to do “zig build test -femit-bin=” but that option is missing

Here is my build.zig file:

const std = @import("std");

// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    const lib_mod = b.createModule(.{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
    });

    const exe_mod = b.createModule(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    const test_mod = b.createModule(.{
        .root_source_file = b.path("src/test.zig"),
        .target = target,
        .optimize = optimize,
    });

    test_mod.addImport("lib", lib_mod);
    exe_mod.addImport("lib", lib_mod);

    const lib = b.addStaticLibrary(.{
        .name = "aoc2024",
        .root_module = lib_mod,
    });

    b.installArtifact(lib);

    const exe = b.addExecutable(.{
        .name = "aoc2024",
        .root_module = exe_mod,
    });

    b.installArtifact(exe);

    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);

    const unit_tests = b.addTest(.{ .root_module = test_mod });
    const run_unit_tests = b.addRunArtifact(unit_tests);

    const test_step = b.step("test", "Run unit tests");
    test_step.dependOn(&run_unit_tests.step);
}
const install_test = b.addInstallBinFile(unit_tests.getEmittedBin(), "unit_tests");
b.default_step.dependOn(&install_test.step); // make the install_test a dependency of some other step

Here unit_tests is the result of b.addTest (a *std.Build.Step.Compile) and in the second line you should add it to some step that you want to be the trigger for installing the test.

For example you could add a new install_tests_step step.

If you want to, you also could do this conditionally based on whether a build option is set https://ziggit.dev/docs?topic=3531#h-2-declare-user-options-upfront-3

Welcome to Ziggit, @tymbaca!

2 Likes

a slightly shorter version which does the same:

b.installArtifact(unit_tests);
2 Likes

that should be avoided as it will always install the test exe which you dont want when someone is installing your program for reals

const exe_test = b.addTest(.{
    .root_module = exe_mod,
});

const install_exe_test = b.addInstallArtifact(exe_test, .{});
const run_exe_test = b.addRunArtifact(exe_test);
run_exe_test.step.dependOn(&install_exe_test.step);

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

only installs the test when running zig build test

3 Likes

Thanks to all of you, guys! You are a part of a wonderful community :slight_smile:

2 Likes