std.Build: extend test_runner option to specify whether runner uses std.zig.Server

The previous logic here was trying to assume that custom test runners
never used `std.zig.Server` to communicate with the build runner;
however, it was flawed, because modifying the `test_runner` field on
`Step.Compile` would not update this flag. That might have been
intentional (allowing a way for the user to specify a custom test runner
which *does* use the compiler server protocol), but if so, it was a
flawed API, since it was too easy to update one field without updating
the other.

Instead, bundle these two pieces of state into a new type
`std.Build.Step.Compile.TestRunner`. When passing a custom test runner,
you are now *provided* to specify whether it is a "simple" runner, or
whether it uses the compiler server protocol.

This is a breaking change, but is unlikely to affect many people, since
custom test runners are seldom used in the wild.
This commit is contained in:
mlugg 2025-01-10 10:48:52 +00:00
parent b074fb7dda
commit b8e568504e
No known key found for this signature in database
GPG Key ID: 3F5B7DCCBF4AF02E
4 changed files with 29 additions and 15 deletions

View File

@ -979,7 +979,7 @@ pub const TestOptions = struct {
/// Deprecated; use `.filters = &.{filter}` instead of `.filter = filter`. /// Deprecated; use `.filters = &.{filter}` instead of `.filter = filter`.
filter: ?[]const u8 = null, filter: ?[]const u8 = null,
filters: []const []const u8 = &.{}, filters: []const []const u8 = &.{},
test_runner: ?LazyPath = null, test_runner: ?Step.Compile.TestRunner = null,
use_llvm: ?bool = null, use_llvm: ?bool = null,
use_lld: ?bool = null, use_lld: ?bool = null,
zig_lib_dir: ?LazyPath = null, zig_lib_dir: ?LazyPath = null,
@ -1136,9 +1136,8 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
run_step.addArtifactArg(exe); run_step.addArtifactArg(exe);
} }
if (exe.test_server_mode) { const test_server_mode = if (exe.test_runner) |r| r.mode == .server else true;
run_step.enableTestRunnerMode(); if (test_server_mode) run_step.enableTestRunnerMode();
}
} else { } else {
run_step.addArtifactArg(exe); run_step.addArtifactArg(exe);
} }

View File

@ -56,8 +56,7 @@ global_base: ?u64 = null,
zig_lib_dir: ?LazyPath, zig_lib_dir: ?LazyPath,
exec_cmd_args: ?[]const ?[]const u8, exec_cmd_args: ?[]const ?[]const u8,
filters: []const []const u8, filters: []const []const u8,
test_runner: ?LazyPath, test_runner: ?TestRunner,
test_server_mode: bool,
wasi_exec_model: ?std.builtin.WasiExecModel = null, wasi_exec_model: ?std.builtin.WasiExecModel = null,
installed_headers: ArrayList(HeaderInstallation), installed_headers: ArrayList(HeaderInstallation),
@ -268,7 +267,7 @@ pub const Options = struct {
version: ?std.SemanticVersion = null, version: ?std.SemanticVersion = null,
max_rss: usize = 0, max_rss: usize = 0,
filters: []const []const u8 = &.{}, filters: []const []const u8 = &.{},
test_runner: ?LazyPath = null, test_runner: ?TestRunner = null,
use_llvm: ?bool = null, use_llvm: ?bool = null,
use_lld: ?bool = null, use_lld: ?bool = null,
zig_lib_dir: ?LazyPath = null, zig_lib_dir: ?LazyPath = null,
@ -347,6 +346,14 @@ pub const HeaderInstallation = union(enum) {
} }
}; };
pub const TestRunner = struct {
path: LazyPath,
/// Test runners can either be "simple", running tests when spawned and terminating when the
/// tests are complete, or they can use `std.zig.Server` over stdio to interact more closely
/// with the build system.
mode: enum { simple, server },
};
pub fn create(owner: *std.Build, options: Options) *Compile { pub fn create(owner: *std.Build, options: Options) *Compile {
const name = owner.dupe(options.name); const name = owner.dupe(options.name);
if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) { if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) {
@ -411,8 +418,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
.zig_lib_dir = null, .zig_lib_dir = null,
.exec_cmd_args = null, .exec_cmd_args = null,
.filters = options.filters, .filters = options.filters,
.test_runner = null, .test_runner = null, // set below
.test_server_mode = options.test_runner == null,
.rdynamic = false, .rdynamic = false,
.installed_path = null, .installed_path = null,
.force_undefined_symbols = StringHashMap(void).init(owner.allocator), .force_undefined_symbols = StringHashMap(void).init(owner.allocator),
@ -438,9 +444,12 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
lp.addStepDependencies(&compile.step); lp.addStepDependencies(&compile.step);
} }
if (options.test_runner) |lp| { if (options.test_runner) |runner| {
compile.test_runner = lp.dupe(compile.step.owner); compile.test_runner = .{
lp.addStepDependencies(&compile.step); .path = runner.path.dupe(compile.step.owner),
.mode = runner.mode,
};
runner.path.addStepDependencies(&compile.step);
} }
// Only the PE/COFF format has a Resource Table which is where the manifest // Only the PE/COFF format has a Resource Table which is where the manifest
@ -1399,7 +1408,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
if (compile.test_runner) |test_runner| { if (compile.test_runner) |test_runner| {
try zig_args.append("--test-runner"); try zig_args.append("--test-runner");
try zig_args.append(test_runner.getPath2(b, step)); try zig_args.append(test_runner.path.getPath2(b, step));
} }
for (b.debug_log_scopes) |log_scope| { for (b.debug_log_scopes) |log_scope| {

View File

@ -13,7 +13,10 @@ pub fn build(b: *std.Build) void {
const t = b.addTest(.{ const t = b.addTest(.{
.root_module = test_mod, .root_module = test_mod,
.test_runner = b.path("test_runner/main.zig"), .test_runner = .{
.path = b.path("test_runner/main.zig"),
.mode = .simple,
},
}); });
const test_step = b.step("test", "Run unit tests"); const test_step = b.step("test", "Run unit tests");

View File

@ -8,7 +8,10 @@ pub fn build(b: *std.Build) void {
.target = b.graph.host, .target = b.graph.host,
.root_source_file = b.path("test.zig"), .root_source_file = b.path("test.zig"),
}) }); }) });
test_exe.test_runner = b.path("test_runner.zig"); test_exe.test_runner = .{
.path = b.path("test_runner.zig"),
.mode = .simple,
};
const test_run = b.addRunArtifact(test_exe); const test_run = b.addRunArtifact(test_exe);
test_step.dependOn(&test_run.step); test_step.dependOn(&test_run.step);