Browse Source

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.
Manlio Perillo 2 years ago
parent
commit
9ab9ebf33f
2 changed files with 58 additions and 85 deletions
  1. 48 67
      build.zig
  2. 10 18
      test/tests.zig

+ 48 - 67
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,
-                    });
-                    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,
+    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);
-                },
-            }
 
-            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",

+ 10 - 18
test/tests.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");