mirror of
https://codeberg.org/ziglings/exercises.git
synced 2024-12-25 09:10:26 +00:00
Refactor testing support
Following the implementation in `std.Build.Step.Compile, add the Kind type to differentiate between a normal executable and a test executable running zig tests. Replace `Exercise.run_test` field with `kind`. Compile the exercise in both the exe and test cases, reducing code duplication. Add the `check_output` and `check_test` methods in ZiglingStep, in order to differentiate the code checking a normal executable and a test executable. Update the tests to correctly check both the exe and test cases. Remove the temporary code added in commit 832772c.
This commit is contained in:
parent
3dafa3518b
commit
9ab9ebf33f
2 changed files with 58 additions and 85 deletions
115
build.zig
115
build.zig
|
@ -13,6 +13,13 @@ const assert = std.debug.assert;
|
|||
const join = std.fs.path.join;
|
||||
const print = std.debug.print;
|
||||
|
||||
const Kind = enum {
|
||||
/// Run the artifact as a normal executable.
|
||||
exe,
|
||||
/// Run the artifact as a test.
|
||||
@"test",
|
||||
};
|
||||
|
||||
pub const Exercise = struct {
|
||||
/// main_file must have the format key_name.zig.
|
||||
/// The key will be used as a shorthand to build just one example.
|
||||
|
@ -34,9 +41,8 @@ pub const Exercise = struct {
|
|||
/// We need to keep track of this, so we compile with libc.
|
||||
link_libc: bool = false,
|
||||
|
||||
/// This exercise doesn't have a main function.
|
||||
/// We only call the test.
|
||||
run_test: bool = false,
|
||||
/// This exercise kind.
|
||||
kind: Kind = .exe,
|
||||
|
||||
/// This exercise is not supported by the current Zig compiler.
|
||||
skip: bool = false,
|
||||
|
@ -225,18 +231,6 @@ const ZiglingStep = struct {
|
|||
return;
|
||||
}
|
||||
|
||||
// Test exercise.
|
||||
if (self.exercise.run_test) {
|
||||
self.is_testing = true;
|
||||
const result_msg = self.testing(prog_node) catch {
|
||||
std.os.exit(2);
|
||||
};
|
||||
const output = try trimLines(self.step.owner.allocator, result_msg);
|
||||
print("\n{s}PASSED:\n{s}{s}\n\n", .{ green_text, output, reset_text });
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal exercise.
|
||||
const exe_path = self.compile(prog_node) catch {
|
||||
if (self.exercise.hint) |hint|
|
||||
print("\n{s}Ziglings hint: {s}{s}", .{ bold_text, hint, reset_text });
|
||||
|
@ -276,10 +270,14 @@ const ZiglingStep = struct {
|
|||
return err;
|
||||
};
|
||||
|
||||
const raw_output = if (self.exercise.check_stdout)
|
||||
result.stdout
|
||||
else
|
||||
result.stderr;
|
||||
switch (self.exercise.kind) {
|
||||
.exe => return self.check_output(result),
|
||||
.@"test" => return self.check_test(result),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_output(self: *ZiglingStep, result: Child.ExecResult) !void {
|
||||
const b = self.step.owner;
|
||||
|
||||
// Make sure it exited cleanly.
|
||||
switch (result.term) {
|
||||
|
@ -299,6 +297,11 @@ const ZiglingStep = struct {
|
|||
},
|
||||
}
|
||||
|
||||
const raw_output = if (self.exercise.check_stdout)
|
||||
result.stdout
|
||||
else
|
||||
result.stderr;
|
||||
|
||||
// Validate the output.
|
||||
// NOTE: exercise.output can never contain a CR character.
|
||||
// See https://ziglang.org/documentation/master/#Source-Encoding.
|
||||
|
@ -323,55 +326,28 @@ const ZiglingStep = struct {
|
|||
print("{s}PASSED:\n{s}{s}\n\n", .{ green_text, output, reset_text });
|
||||
}
|
||||
|
||||
fn testing(self: *ZiglingStep, prog_node: *std.Progress.Node) ![]const u8 {
|
||||
print("Testing {s}...\n", .{self.exercise.main_file});
|
||||
|
||||
const b = self.step.owner;
|
||||
const exercise_path = self.exercise.main_file;
|
||||
const path = join(b.allocator, &.{ self.work_path, exercise_path }) catch
|
||||
@panic("OOM");
|
||||
|
||||
var zig_args = std.ArrayList([]const u8).init(b.allocator);
|
||||
defer zig_args.deinit();
|
||||
|
||||
zig_args.append(b.zig_exe) catch @panic("OOM");
|
||||
zig_args.append("test") catch @panic("OOM");
|
||||
|
||||
zig_args.append(b.pathFromRoot(path)) catch @panic("OOM");
|
||||
|
||||
const argv = zig_args.items;
|
||||
var code: u8 = undefined;
|
||||
_ = self.eval(argv, &code, prog_node) catch |err| {
|
||||
self.printErrors();
|
||||
|
||||
switch (err) {
|
||||
error.FileNotFound => {
|
||||
print("{s}{s}: Unable to spawn the following command: file not found{s}\n", .{
|
||||
red_text, self.exercise.main_file, reset_text,
|
||||
fn check_test(self: *ZiglingStep, result: Child.ExecResult) !void {
|
||||
switch (result.term) {
|
||||
.Exited => |code| {
|
||||
if (code != 0) {
|
||||
// The test failed.
|
||||
print("{s}{s}{s}\n", .{
|
||||
red_text, result.stderr, reset_text,
|
||||
});
|
||||
dumpArgs(argv);
|
||||
},
|
||||
error.ExitCodeFailure => {
|
||||
// Expected when test fails.
|
||||
},
|
||||
error.ProcessTerminated => {
|
||||
print("{s}{s}: The following command terminated unexpectedly:{s}\n", .{
|
||||
red_text, self.exercise.main_file, reset_text,
|
||||
});
|
||||
dumpArgs(argv);
|
||||
},
|
||||
else => {
|
||||
print("{s}{s}: Unexpected error: {s}{s}\n", .{
|
||||
red_text, self.exercise.main_file, @errorName(err), reset_text,
|
||||
});
|
||||
dumpArgs(argv);
|
||||
},
|
||||
}
|
||||
|
||||
return err;
|
||||
};
|
||||
return error.TestFailed;
|
||||
}
|
||||
},
|
||||
else => {
|
||||
print("{s}{s} terminated unexpectedly{s}\n", .{
|
||||
red_text, self.exercise.main_file, reset_text,
|
||||
});
|
||||
|
||||
return self.result_messages;
|
||||
return error.UnexpectedTermination;
|
||||
},
|
||||
}
|
||||
|
||||
print("{s}PASSED{s}\n\n", .{ green_text, reset_text });
|
||||
}
|
||||
|
||||
fn compile(self: *ZiglingStep, prog_node: *std.Progress.Node) ![]const u8 {
|
||||
|
@ -386,7 +362,12 @@ const ZiglingStep = struct {
|
|||
defer zig_args.deinit();
|
||||
|
||||
zig_args.append(b.zig_exe) catch @panic("OOM");
|
||||
zig_args.append("build-exe") catch @panic("OOM");
|
||||
|
||||
const cmd = switch (self.exercise.kind) {
|
||||
.exe => "build-exe",
|
||||
.@"test" => "test",
|
||||
};
|
||||
zig_args.append(cmd) catch @panic("OOM");
|
||||
|
||||
// Enable C support for exercises that use C functions.
|
||||
if (self.exercise.link_libc) {
|
||||
|
@ -1220,7 +1201,7 @@ const exercises = [_]Exercise{
|
|||
.{
|
||||
.main_file = "102_testing.zig",
|
||||
.output = "",
|
||||
.run_test = true,
|
||||
.kind = .@"test",
|
||||
},
|
||||
.{
|
||||
.main_file = "999_the_end.zig",
|
||||
|
|
|
@ -93,7 +93,7 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
|
|||
const case_step = createCase(b, "case-3");
|
||||
|
||||
for (exercises[0 .. exercises.len - 1]) |ex| {
|
||||
if (ex.skip or ex.run_test) continue;
|
||||
if (ex.skip) continue;
|
||||
|
||||
if (ex.hint) |hint| {
|
||||
const n = ex.number();
|
||||
|
@ -249,21 +249,6 @@ fn check_output(step: *Step, exercise: Exercise, reader: Reader) !void {
|
|||
return;
|
||||
}
|
||||
|
||||
if (exercise.run_test) {
|
||||
{
|
||||
const actual = try readLine(reader, &buf) orelse "EOF";
|
||||
const expect = b.fmt("Testing {s}...", .{exercise.main_file});
|
||||
try check(step, exercise, expect, actual);
|
||||
}
|
||||
|
||||
{
|
||||
const actual = try readLine(reader, &buf) orelse "EOF";
|
||||
try check(step, exercise, "", actual);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
const actual = try readLine(reader, &buf) orelse "EOF";
|
||||
const expect = b.fmt("Compiling {s}...", .{exercise.main_file});
|
||||
|
@ -278,12 +263,19 @@ fn check_output(step: *Step, exercise: Exercise, reader: Reader) !void {
|
|||
|
||||
{
|
||||
const actual = try readLine(reader, &buf) orelse "EOF";
|
||||
const expect = "PASSED:";
|
||||
const expect = switch (exercise.kind) {
|
||||
.exe => "PASSED:",
|
||||
.@"test" => "PASSED",
|
||||
};
|
||||
try check(step, exercise, expect, actual);
|
||||
}
|
||||
|
||||
// Skip the exercise output.
|
||||
const nlines = 1 + mem.count(u8, exercise.output, "\n") + 1;
|
||||
const nlines = switch (exercise.kind) {
|
||||
.exe => 1 + mem.count(u8, exercise.output, "\n") + 1,
|
||||
.@"test" => 1,
|
||||
};
|
||||
|
||||
var lineno: usize = 0;
|
||||
while (lineno < nlines) : (lineno += 1) {
|
||||
_ = try readLine(reader, &buf) orelse @panic("EOF");
|
||||
|
|
Loading…
Reference in a new issue