Skip to content

Commit

Permalink
fix: Support build functions with error return
Browse files Browse the repository at this point in the history
Apparently build functions can optionally return an error, which changes
the way the function should be invoked. This adds a check and calls the
functions appropriately.

Also, segregated `neotest-build.zig` parts into separate functions for
easier maintenance.

Resolves #17
  • Loading branch information
lawrence-laz committed Jun 4, 2024
1 parent 0ca82fe commit 3a99e65
Showing 1 changed file with 68 additions and 32 deletions.
100 changes: 68 additions & 32 deletions zig/neotest_build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,80 @@ const std = @import("std");
const original_build_file = @import("build.zig");

pub fn build(b: *std.Build) void {
original_build_file.build(b);
buildOriginalBuildGraph(b);
addNeotestBuildStep(b);
passArguments(b);
replaceTestRunner(b);
}

fn replaceTestRunner(b: *std.Build) void {
/// Builds original graph from user's `build.zig` file.
fn buildOriginalBuildGraph(b: *std.Build) void {
const can_build_function_return_error = @typeInfo(@typeInfo(@TypeOf(original_build_file.build)).Fn.return_type.?) == .ErrorUnion;
if (can_build_function_return_error) {
original_build_file.build(b) catch |err| {
std.log.err("Function `build` from `build.zig` returned an error: {}", .{err});
std.process.exit(1);
};
} else {
original_build_file.build(b);
}
}

// Builds test binaries without running them.
// Used for launching a debuger.
/// Adds a step to build test binaries without running them.
/// Used for launching a debuger.https://codeberg.org/ziglings/exercises.git
fn addNeotestBuildStep(b: *std.Build) void {
const neotest_build_step = b.step("neotest-build", "Build tests without running");
const test_step = getTestStep(b);
for (test_step.dependencies.items) |maybe_test_step_run| {
if (maybe_test_step_run.cast(std.Build.Step.Run) == null) {
// Not interested in non-run steps here.
continue;
}
for (maybe_test_step_run.dependencies.items) |maybe_compile_step| {
const test_step_compile = maybe_compile_step.cast(std.Build.Step.Compile) orelse continue;
const install_test = b.addInstallArtifact(test_step_compile, .{
// TODO: Handle empty names.
// If empty or default, then suggest renaming addTest compile step in build.zig for the user.
// This could be extracted the same way as multiple choices will be with the neotest-dry-run step.
.dest_sub_path = std.fmt.allocPrint(b.allocator, "../test/{s}", .{test_step_compile.name}) catch unreachable,
});
neotest_build_step.dependOn(&install_test.step);
}
}
}

if (b.top_level_steps.get("test")) |test_step| {
const test_runner = b.option([]const u8, "neotest-runner", "Use a custom test runner");

var subpath_index: usize = 1;

for (test_step.step.dependencies.items) |step| {
const run_step = step.cast(std.Build.Step.Run) orelse continue;

// Pass down build arguments into test run
// (ex. --neotest-results-path)
if (b.args) |args| {
run_step.addArgs(args);
}

for (step.dependencies.items) |maybe_compile_step| {
if (maybe_compile_step.cast(std.Build.Step.Compile)) |compile_step| {
const install_test = b.addInstallArtifact(compile_step, .{
// TODO: Handle empty names
// If empty or defualt suggest renaming addTest compile step in build.zig for the user
.dest_sub_path = std.fmt.allocPrint(b.allocator, "../test/{s}", .{compile_step.name}) catch unreachable,
});
neotest_build_step.dependOn(&install_test.step);

compile_step.test_runner = if (test_runner) |x| .{ .cwd_relative = x } else null;
}
}
/// Adds arguments to test run steps.
/// For example `--neotest-results-path`.
fn passArguments(b: *std.Build) void {
const build_args = b.args orelse return;
const test_step = getTestStep(b);
for (test_step.dependencies.items) |substep| {
const test_step_run = substep.cast(std.Build.Step.Run) orelse continue;
test_step_run.addArgs(build_args);
}
}

subpath_index += 1;
/// Replaces default test runner with a custom neotest runner, which uses
/// exact test filtering via input files and provides test results via output files.
fn replaceTestRunner(b: *std.Build) void {
const test_step = getTestStep(b);
const test_runner = b.option([]const u8, "neotest-runner", "Use a custom test runner");
for (test_step.dependencies.items) |maybe_test_step_run| {
if (maybe_test_step_run.cast(std.Build.Step.Run) == null) {
// Not interested in non-run steps here.
continue;
}
} else @panic("Neotest runner needs a 'test' step to be defined in `build.zig`.");
for (maybe_test_step_run.dependencies.items) |maybe_test_step_compile| {
const test_step_compile = maybe_test_step_compile.cast(std.Build.Step.Compile) orelse continue;
test_step_compile.test_runner = if (test_runner) |x| .{ .cwd_relative = x } else null;
}
}
}

inline fn getTestStep(b: *const std.Build) *std.Build.Step {
if (b.top_level_steps.get("test")) |test_step| {
return &test_step.step;
} else {
@panic("Neotest runner requires a 'test' step to be defined in `build.zig`.");
}
}

0 comments on commit 3a99e65

Please sign in to comment.