Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Support build functions with error return #18

Merged
merged 2 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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`.");
}
}
4 changes: 2 additions & 2 deletions zig/neotest_runner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ pub fn runnerLogFn(

const prefix = "[" ++ comptime level.asText() ++ "] ";

std.debug.getStderrMutex().lock();
defer std.debug.getStderrMutex().unlock();
std.debug.lockStdErr();
defer std.debug.unlockStdErr();
const stderr = std.io.getStdErr().writer();
nosuspend stderr.print(prefix ++ format ++ "\n", args) catch return;
}
Expand Down