Build: how to rerun tests when a non-zig file changes?

Hi!

I have a directory structure like this…

test_inputs/
   test1/
       file1.txt
       ....
src/
   main.zig

and I’d like to rerun the tests in main.zig whenever anything in test_inputs/test1 changes.

Is this possible? Ideally, I wouldn’t have to specify the files manually, just the directory as the whole thing is scanned as a part of the test.

I tried looking through bits of the build interface but can’t seem to find what I’m looking for.

Also, if anyone known where I can find an up–to–date guide to the build system that would also be great!

Thanks :slight_smile:

I am using entr:
find . | entr -c 'zig build test --summary new'

Ah, sorry, I want to run the test manually, but because only the zig files are known to the build system it doesn’t know anything has changed, so just reiterates the cached result.

entr might be useful later though, so thanks for that.

You’ll need

run_unit_tests.has_side_effects = true;

to make sure that build.zig doesn’t falsely cache test results, if you read the files at runtime:

    const unit_tests = b.addTest(.{
        .root_source_file = b.path("src/unit_tests.zig"),
        .target = options.target,
        .optimize = options.mode,
        .filters = b.args orelse &.{},
    });
    const run_unit_tests = b.addRunArtifact(unit_tests);
    run_unit_tests.has_side_effects = true;

I don’t think there’s an out-of-the-box way to make build system walk&timestamp the files at the configure time, so that the tests are re-run only when a file changed. You could do that by writing a custom step, but that requires a fair amount of code.

Consider if you could @embedFile in the tests? That would mean that you’d have to repeat the name of each file twice, but then you’ll get precise incremental tracking for free

4 Likes

Thanks, I’ll probably use @embedFile — I’m assuming it will strip the data from the non-test build.

Is this is an unusual thing to do, or would it be worth making a feature request?

To clarify, I believe the build.zig existing APIs already provide all the necessary hooks to implement proper runtime tracking in user space:

You should be able to do something like

    const unit_tests = b.addTest(.{
        .root_source_file = b.path("src/unit_tests.zig"),
        .target = options.target,
        .optimize = options.mode,
        .filters = b.args orelse &.{},
    });
    const run_unit_tests = b.addRunArtifact(unit_tests);
    
    const unit_tests_inputs = MyCustomCheckFileFreshnessStep.create(b);
    run_unit_tests.step.dependsOn(&run_unin_tests.step);

where MyCustomCheckFileFreshnessStep is the code you write. Which code specifcally is tricky, as the build system is not super well documented. But looking at zig/lib/std/Build/Step/InstallDir.zig at master · ziglang/zig · GitHub should be a good start, it implements similar file-system walking logic.

So, in this sense, I don’t think there’s necessary needing to change anything on the Zig side here.

There could be a feature to have this as a built-in step, but this does feel somewhat niche to be honest, and perhaps at the moment left better to the ecosystem to experiment around.

1 Like

I have not tested this, but there is std.Build.Step.Run.addFileInput which appears to let you add additional files that the run step should check when determining whether any of its inputs has changes. In theory, you might be able to do something like this

const tests = b.addTest(...);

const run_tests = b.addRunArtifact(tests);
run_tests.addFileInput(b.path("test_inputs/test1/file1.txt"));

You can only add files, not directories, so you would need to add each file individually.

4 Likes

This works perfectly, thanks!

I’m making a vm and need to make sure the ‘prelude’ compiles and creates all the inbuilt types needed for compiling user code.

1 Like