test: don't run heal during configuration phase

In order to simplify the code, the heal function is called during the
configuration phase, thus resulting in the function being always called
when the build.zig file is run.

This behavior unfortunately causes a serious issue when the user fix a
broken exercise and, during the next step, the heal function tries to heal
the fixed exercise resulting in GNU patch assuming an attempt to reverse
a patch, waiting for input from the terminal.

Run the heal function from the new HealStep step, so that it is called
only during tests.

Rename the outdir constant to work_path, for consistency with build.zig.

Fixes #272
This commit is contained in:
Manlio Perillo 2023-05-02 10:51:59 +02:00
parent f9aec283c8
commit 74b48192e4

View file

@ -20,15 +20,14 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
// We should use a temporary path, but it will make the implementation of // We should use a temporary path, but it will make the implementation of
// `build.zig` more complex. // `build.zig` more complex.
const outdir = "patches/healed"; const work_path = "patches/healed";
fs.cwd().makePath(outdir) catch |err| { fs.cwd().makePath(work_path) catch |err| {
return fail(step, "unable to make '{s}': {s}\n", .{ outdir, @errorName(err) }); return fail(step, "unable to make '{s}': {s}\n", .{ work_path, @errorName(err) });
};
heal(b.allocator, exercises, outdir) catch |err| {
return fail(step, "unable to heal exercises: {s}\n", .{@errorName(err)});
}; };
const heal_step = HealStep.create(b, exercises, work_path);
{ {
// Test that `zig build -Dhealed -Dn=n test` selects the nth exercise. // Test that `zig build -Dhealed -Dn=n test` selects the nth exercise.
const case_step = createCase(b, "case-1"); const case_step = createCase(b, "case-1");
@ -49,6 +48,8 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
else else
expectStdErrMatch(cmd, ex.output); expectStdErrMatch(cmd, ex.output);
cmd.step.dependOn(&heal_step.step);
case_step.dependOn(&cmd.step); case_step.dependOn(&cmd.step);
} }
@ -72,6 +73,8 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
cmd.expectStdOutEqual(""); cmd.expectStdOutEqual("");
expectStdErrMatch(cmd, b.fmt("{s} skipped", .{ex.main_file})); expectStdErrMatch(cmd, b.fmt("{s} skipped", .{ex.main_file}));
cmd.step.dependOn(&heal_step.step);
case_step.dependOn(&cmd.step); case_step.dependOn(&cmd.step);
} }
@ -86,6 +89,7 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
const cmd = b.addSystemCommand(&.{ b.zig_exe, "build", "-Dhealed" }); const cmd = b.addSystemCommand(&.{ b.zig_exe, "build", "-Dhealed" });
cmd.setName("zig build -Dhealed"); cmd.setName("zig build -Dhealed");
cmd.expectExitCode(0); cmd.expectExitCode(0);
cmd.step.dependOn(&heal_step.step);
const stderr = cmd.captureStdErr(); const stderr = cmd.captureStdErr();
const verify = CheckStep.create(b, exercises, stderr, true); const verify = CheckStep.create(b, exercises, stderr, true);
@ -107,6 +111,7 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
); );
cmd.setName("zig build -Dhealed -Dn=1 start"); cmd.setName("zig build -Dhealed -Dn=1 start");
cmd.expectExitCode(0); cmd.expectExitCode(0);
cmd.step.dependOn(&heal_step.step);
const stderr = cmd.captureStdErr(); const stderr = cmd.captureStdErr();
const verify = CheckStep.create(b, exercises, stderr, false); const verify = CheckStep.create(b, exercises, stderr, false);
@ -126,14 +131,16 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
cmd.expectExitCode(1); cmd.expectExitCode(1);
expectStdErrMatch(cmd, exercises[0].hint); expectStdErrMatch(cmd, exercises[0].hint);
cmd.step.dependOn(&heal_step.step);
case_step.dependOn(&cmd.step); case_step.dependOn(&cmd.step);
step.dependOn(case_step); step.dependOn(case_step);
} }
// Don't add the cleanup step, since it may delete outdir while a test case // Don't add the cleanup step, since it may delete work_path while a test
// is running. // case is running.
//const cleanup = b.addRemoveDirTree(outdir); //const cleanup = b.addRemoveDirTree(work_path);
//step.dependOn(&cleanup.step); //step.dependOn(&cleanup.step);
return step; return step;
@ -315,8 +322,38 @@ fn fail(step: *Step, comptime format: []const u8, args: anytype) *Step {
return step; return step;
} }
// A step that heals exercises.
const HealStep = struct {
step: Step,
exercises: []const Exercise,
work_path: []const u8,
pub fn create(owner: *Build, exercises: []const Exercise, work_path: []const u8) *HealStep {
const self = owner.allocator.create(HealStep) catch @panic("OOM");
self.* = .{
.step = Step.init(.{
.id = .custom,
.name = "heal",
.owner = owner,
.makeFn = make,
}),
.exercises = exercises,
.work_path = work_path,
};
return self;
}
fn make(step: *Step, _: *std.Progress.Node) !void {
const b = step.owner;
const self = @fieldParentPtr(HealStep, "step", step);
return heal(b.allocator, self.exercises, self.work_path);
}
};
// Heals all the exercises. // Heals all the exercises.
fn heal(allocator: Allocator, exercises: []const Exercise, outdir: []const u8) !void { fn heal(allocator: Allocator, exercises: []const Exercise, work_path: []const u8) !void {
const join = fs.path.join; const join = fs.path.join;
const exercises_path = "exercises"; const exercises_path = "exercises";
@ -331,7 +368,7 @@ fn heal(allocator: Allocator, exercises: []const Exercise, outdir: []const u8) !
const patch_name = try fmt.allocPrint(allocator, "{s}.patch", .{name}); const patch_name = try fmt.allocPrint(allocator, "{s}.patch", .{name});
break :b try join(allocator, &.{ patches_path, patch_name }); break :b try join(allocator, &.{ patches_path, patch_name });
}; };
const output = try join(allocator, &.{ outdir, ex.main_file }); const output = try join(allocator, &.{ work_path, ex.main_file });
const argv = &.{ "patch", "-i", patch, "-o", output, "-s", file }; const argv = &.{ "patch", "-i", patch, "-o", output, "-s", file };