Browse Source

Merge branch 'ratfactor:main' into testing

Chris Boesch 2 years ago
parent
commit
cee64c13e0
2 changed files with 89 additions and 93 deletions
  1. 79 63
      build.zig
  2. 10 30
      test/tests.zig

+ 79 - 63
build.zig

@@ -78,9 +78,22 @@ pub const Exercise = struct {
     }
     }
 };
 };
 
 
+pub const logo =
+    \\         _       _ _
+    \\     ___(_) __ _| (_)_ __   __ _ ___
+    \\    |_  | |/ _' | | | '_ \ / _' / __|
+    \\     / /| | (_| | | | | | | (_| \__ \
+    \\    /___|_|\__, |_|_|_| |_|\__, |___/
+    \\           |___/           |___/
+    \\
+    \\    "Look out! Broken programs below!"
+    \\
+    \\
+;
+
 pub fn build(b: *Build) !void {
 pub fn build(b: *Build) !void {
     if (!compat.is_compatible) compat.die();
     if (!compat.is_compatible) compat.die();
-    if (!validate_exercises()) std.os.exit(1);
+    if (!validate_exercises()) std.os.exit(2);
 
 
     use_color_escapes = false;
     use_color_escapes = false;
     if (std.io.getStdErr().supportsAnsiEscapeCodes()) {
     if (std.io.getStdErr().supportsAnsiEscapeCodes()) {
@@ -110,24 +123,16 @@ pub fn build(b: *Build) !void {
         reset_text = "\x1b[0m";
         reset_text = "\x1b[0m";
     }
     }
 
 
-    const logo =
-        \\         _       _ _
-        \\     ___(_) __ _| (_)_ __   __ _ ___
-        \\    |_  | |/ _' | | | '_ \ / _' / __|
-        \\     / /| | (_| | | | | | | (_| \__ \
-        \\    /___|_|\__, |_|_|_| |_|\__, |___/
-        \\           |___/           |___/
-        \\
-        \\    "Look out! Broken programs below!"
-        \\
-        \\
-    ;
-
-    const healed = b.option(bool, "healed", "Run exercises from patches/healed") orelse false;
+    const healed = b.option(bool, "healed", "Run exercises from patches/healed") orelse
+        false;
     const override_healed_path = b.option([]const u8, "healed-path", "Override healed path");
     const override_healed_path = b.option([]const u8, "healed-path", "Override healed path");
     const exno: ?usize = b.option(usize, "n", "Select exercise");
     const exno: ?usize = b.option(usize, "n", "Select exercise");
 
 
-    const healed_path = if (override_healed_path) |path| path else "patches/healed";
+    const sep = std.fs.path.sep_str;
+    const healed_path = if (override_healed_path) |path|
+        path
+    else
+        "patches" ++ sep ++ "healed";
     const work_path = if (healed) healed_path else "exercises";
     const work_path = if (healed) healed_path else "exercises";
 
 
     const header_step = PrintStep.create(b, logo);
     const header_step = PrintStep.create(b, logo);
@@ -135,19 +140,25 @@ pub fn build(b: *Build) !void {
     if (exno) |n| {
     if (exno) |n| {
         if (n == 0 or n > exercises.len - 1) {
         if (n == 0 or n > exercises.len - 1) {
             print("unknown exercise number: {}\n", .{n});
             print("unknown exercise number: {}\n", .{n});
-            std.os.exit(1);
+            std.os.exit(2);
         }
         }
-
         const ex = exercises[n - 1];
         const ex = exercises[n - 1];
 
 
         const build_step = ex.addExecutable(b, work_path);
         const build_step = ex.addExecutable(b, work_path);
-        b.installArtifact(build_step);
+
+        const skip_step = SkipStep.create(b, ex);
+        if (!ex.skip)
+            b.installArtifact(build_step)
+        else
+            b.getInstallStep().dependOn(&skip_step.step);
 
 
         const run_step = b.addRunArtifact(build_step);
         const run_step = b.addRunArtifact(build_step);
 
 
-        const test_step = b.step("test", b.fmt("Run {s} without checking output", .{ex.main_file}));
+        const test_step = b.step(
+            "test",
+            b.fmt("Run {s} without checking output", .{ex.main_file}),
+        );
         if (ex.skip) {
         if (ex.skip) {
-            const skip_step = SkipStep.create(b, ex);
             test_step.dependOn(&skip_step.step);
             test_step.dependOn(&skip_step.step);
         } else {
         } else {
             test_step.dependOn(&run_step.step);
             test_step.dependOn(&run_step.step);
@@ -155,11 +166,17 @@ pub fn build(b: *Build) !void {
 
 
         const verify_step = ZiglingStep.create(b, ex, work_path);
         const verify_step = ZiglingStep.create(b, ex, work_path);
 
 
-        const zigling_step = b.step("zigling", b.fmt("Check the solution of {s}", .{ex.main_file}));
+        const zigling_step = b.step(
+            "zigling",
+            b.fmt("Check the solution of {s}", .{ex.main_file}),
+        );
         zigling_step.dependOn(&verify_step.step);
         zigling_step.dependOn(&verify_step.step);
         b.default_step = zigling_step;
         b.default_step = zigling_step;
 
 
-        const start_step = b.step("start", b.fmt("Check all solutions starting at {s}", .{ex.main_file}));
+        const start_step = b.step(
+            "start",
+            b.fmt("Check all solutions starting at {s}", .{ex.main_file}),
+        );
 
 
         var prev_step = verify_step;
         var prev_step = verify_step;
         for (exercises) |exn| {
         for (exercises) |exn| {
@@ -202,12 +219,15 @@ pub fn build(b: *Build) !void {
     const ziglings_step = b.step("ziglings", "Check all ziglings");
     const ziglings_step = b.step("ziglings", "Check all ziglings");
     b.default_step = ziglings_step;
     b.default_step = ziglings_step;
 
 
-    // Don't use the "multi-object for loop" syntax, in order to avoid a syntax
-    // error with old Zig compilers.
     var prev_step = &header_step.step;
     var prev_step = &header_step.step;
     for (exercises) |ex| {
     for (exercises) |ex| {
-        const build_step = ex.addExecutable(b, "exercises");
-        b.installArtifact(build_step);
+        const build_step = ex.addExecutable(b, work_path);
+
+        const skip_step = SkipStep.create(b, ex);
+        if (!ex.skip)
+            b.installArtifact(build_step)
+        else
+            b.getInstallStep().dependOn(&skip_step.step);
 
 
         const verify_stepn = ZiglingStep.create(b, ex, work_path);
         const verify_stepn = ZiglingStep.create(b, ex, work_path);
         verify_stepn.step.dependOn(prev_step);
         verify_stepn.step.dependOn(prev_step);
@@ -264,10 +284,10 @@ const ZiglingStep = struct {
 
 
             self.help();
             self.help();
 
 
-            // NOTE: Returning 0 'success' status because the *exercise* failed,
-            // but Ziglings did not. Otherwise the learner will see this message:
-            //   "error: the following build command failed with exit code 1:..."
-            std.os.exit(0);
+            // NOTE: Using exit code 2 will prevent the Zig compiler to print
+            // the message:
+            // "error: the following build command failed with exit code 1:..."
+            std.os.exit(2);
         };
         };
 
 
         self.run(exe_path, prog_node) catch {
         self.run(exe_path, prog_node) catch {
@@ -277,7 +297,7 @@ const ZiglingStep = struct {
             self.help();
             self.help();
 
 
             // NOTE: See note above!
             // NOTE: See note above!
-            std.os.exit(0);
+            std.os.exit(2);
         };
         };
     }
     }
 
 
@@ -386,37 +406,32 @@ const ZiglingStep = struct {
                     print("{s}{s}: Unable to spawn the following command: file not found{s}\n", .{
                     print("{s}{s}: Unable to spawn the following command: file not found{s}\n", .{
                         red_text, self.exercise.main_file, reset_text,
                         red_text, self.exercise.main_file, reset_text,
                     });
                     });
-                    for (argv) |v| print("{s} ", .{v});
-                    print("\n", .{});
+                    dumpArgs(argv);
                 },
                 },
                 error.ExitCodeFailure => {
                 error.ExitCodeFailure => {
                     print("{s}{s}: The following command exited with error code {}:{s}\n", .{
                     print("{s}{s}: The following command exited with error code {}:{s}\n", .{
                         red_text, self.exercise.main_file, code, reset_text,
                         red_text, self.exercise.main_file, code, reset_text,
                     });
                     });
-                    for (argv) |v| print("{s} ", .{v});
-                    print("\n", .{});
+                    dumpArgs(argv);
                 },
                 },
                 error.ProcessTerminated => {
                 error.ProcessTerminated => {
                     print("{s}{s}: The following command terminated unexpectedly:{s}\n", .{
                     print("{s}{s}: The following command terminated unexpectedly:{s}\n", .{
                         red_text, self.exercise.main_file, reset_text,
                         red_text, self.exercise.main_file, reset_text,
                     });
                     });
-                    for (argv) |v| print("{s} ", .{v});
-                    print("\n", .{});
+                    dumpArgs(argv);
                 },
                 },
                 error.ZigIPCError => {
                 error.ZigIPCError => {
                     // Commenting this out for now. It always shows up when compilation fails.
                     // Commenting this out for now. It always shows up when compilation fails.
                     //print("{s}{s}: The following command failed to communicate the compilation result:{s}\n", .{
                     //print("{s}{s}: The following command failed to communicate the compilation result:{s}\n", .{
                     //    red_text, self.exercise.main_file, reset_text,
                     //    red_text, self.exercise.main_file, reset_text,
                     //});
                     //});
-                    //for (argv) |v| print("{s} ", .{v});
-                    //print("\n", .{});
+                    //dumpArgs(argv);
                 },
                 },
                 else => {
                 else => {
                     print("{s}{s}: Unexpected error: {s}{s}\n", .{
                     print("{s}{s}: Unexpected error: {s}{s}\n", .{
                         red_text, self.exercise.main_file, @errorName(err), reset_text,
                         red_text, self.exercise.main_file, @errorName(err), reset_text,
                     });
                     });
-                    for (argv) |v| print("{s} ", .{v});
-                    print("\n", .{});
+                    dumpArgs(argv);
                 },
                 },
             }
             }
 
 
@@ -566,13 +581,18 @@ const ZiglingStep = struct {
     }
     }
 };
 };
 
 
-// Clear the entire line and move the cursor to column zero.
-// Used for clearing the compiler and build_runner progress messages.
+fn dumpArgs(args: []const []const u8) void {
+    for (args) |arg| print("{s} ", .{arg});
+    print("\n", .{});
+}
+
+/// Clears the entire line and move the cursor to column zero.
+/// Used for clearing the compiler and build_runner progress messages.
 fn resetLine() void {
 fn resetLine() void {
     if (use_color_escapes) print("{s}", .{"\x1b[2K\r"});
     if (use_color_escapes) print("{s}", .{"\x1b[2K\r"});
 }
 }
 
 
-/// Remove trailing whitespace for each line in buf, also ensuring that there
+/// Removes trailing whitespace for each line in buf, also ensuring that there
 /// are no trailing LF characters at the end.
 /// are no trailing LF characters at the end.
 pub fn trimLines(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 {
 pub fn trimLines(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 {
     var list = try std.ArrayList(u8).initCapacity(allocator, buf.len);
     var list = try std.ArrayList(u8).initCapacity(allocator, buf.len);
@@ -592,7 +612,7 @@ pub fn trimLines(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 {
     return std.mem.trimRight(u8, result, "\n");
     return std.mem.trimRight(u8, result, "\n");
 }
 }
 
 
-// Print a message to stderr.
+/// Prints a message to stderr.
 const PrintStep = struct {
 const PrintStep = struct {
     step: Step,
     step: Step,
     message: []const u8,
     message: []const u8,
@@ -612,15 +632,14 @@ const PrintStep = struct {
         return self;
         return self;
     }
     }
 
 
-    fn make(step: *Step, prog_node: *std.Progress.Node) !void {
-        _ = prog_node;
-        const p = @fieldParentPtr(PrintStep, "step", step);
+    fn make(step: *Step, _: *std.Progress.Node) !void {
+        const self = @fieldParentPtr(PrintStep, "step", step);
 
 
-        print("{s}", .{p.message});
+        print("{s}", .{self.message});
     }
     }
 };
 };
 
 
-// Skip an exercise.
+/// Skips an exercise.
 const SkipStep = struct {
 const SkipStep = struct {
     step: Step,
     step: Step,
     exercise: Exercise,
     exercise: Exercise,
@@ -640,20 +659,19 @@ const SkipStep = struct {
         return self;
         return self;
     }
     }
 
 
-    fn make(step: *Step, prog_node: *std.Progress.Node) !void {
-        _ = prog_node;
-        const p = @fieldParentPtr(SkipStep, "step", step);
+    fn make(step: *Step, _: *std.Progress.Node) !void {
+        const self = @fieldParentPtr(SkipStep, "step", step);
 
 
-        if (p.exercise.skip) {
-            print("{s} skipped\n", .{p.exercise.main_file});
+        if (self.exercise.skip) {
+            print("{s} skipped\n", .{self.exercise.main_file});
         }
         }
     }
     }
 };
 };
 
 
-// Check that each exercise number, excluding the last, forms the sequence
-// `[1, exercise.len)`.
-//
-// Additionally check that the output field lines doesn't have trailing whitespace.
+/// Checks that each exercise number, excluding the last, forms the sequence
+/// `[1, exercise.len)`.
+///
+/// Additionally check that the output field lines doesn't have trailing whitespace.
 fn validate_exercises() bool {
 fn validate_exercises() bool {
     // Don't use the "multi-object for loop" syntax, in order to avoid a syntax
     // Don't use the "multi-object for loop" syntax, in order to avoid a syntax
     // error with old Zig compilers.
     // error with old Zig compilers.
@@ -665,9 +683,7 @@ fn validate_exercises() bool {
 
 
         if (exno != i and exno != last) {
         if (exno != i and exno != last) {
             print("exercise {s} has an incorrect number: expected {}, got {s}\n", .{
             print("exercise {s} has an incorrect number: expected {}, got {s}\n", .{
-                ex.main_file,
-                i,
-                ex.key(),
+                ex.main_file, i, ex.key(),
             });
             });
 
 
             return false;
             return false;

+ 10 - 30
test/tests.zig

@@ -84,10 +84,11 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
                 b.fmt("-Dn={}", .{n}),
                 b.fmt("-Dn={}", .{n}),
                 "test",
                 "test",
             });
             });
+            const expect = b.fmt("{s} skipped", .{ex.main_file});
             cmd.setName(b.fmt("zig build -Dhealed -Dn={} test", .{n}));
             cmd.setName(b.fmt("zig build -Dhealed -Dn={} test", .{n}));
             cmd.expectExitCode(0);
             cmd.expectExitCode(0);
-            cmd.expectStdOutEqual("");
-            expectStdErrMatch(cmd, b.fmt("{s} skipped", .{ex.main_file}));
+            cmd.addCheck(.{ .expect_stdout_exact = "" });
+            cmd.addCheck(.{ .expect_stderr_match = expect });
 
 
             cmd.step.dependOn(&heal_step.step);
             cmd.step.dependOn(&heal_step.step);
 
 
@@ -172,9 +173,10 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
         const case_step = createCase(b, "case-5");
         const case_step = createCase(b, "case-5");
 
 
         const cmd = b.addSystemCommand(&.{ b.zig_exe, "build", "-Dn=1" });
         const cmd = b.addSystemCommand(&.{ b.zig_exe, "build", "-Dn=1" });
+        const expect = exercises[0].hint orelse "";
         cmd.setName("zig build -Dn=1");
         cmd.setName("zig build -Dn=1");
-        cmd.expectExitCode(1);
-        expectStdErrMatch(cmd, exercises[0].hint orelse "");
+        cmd.expectExitCode(2);
+        cmd.addCheck(.{ .expect_stderr_match = expect });
 
 
         cmd.step.dependOn(case_step);
         cmd.step.dependOn(case_step);
 
 
@@ -280,10 +282,11 @@ const CheckStep = struct {
         for (exercises) |ex| {
         for (exercises) |ex| {
             if (ex.number() == 1 and self.has_logo) {
             if (ex.number() == 1 and self.has_logo) {
                 // Skip the logo.
                 // Skip the logo.
+                const nlines = mem.count(u8, root.logo, "\n");
                 var buf: [80]u8 = undefined;
                 var buf: [80]u8 = undefined;
 
 
                 var lineno: usize = 0;
                 var lineno: usize = 0;
-                while (lineno < 8) : (lineno += 1) {
+                while (lineno < nlines) : (lineno += 1) {
                     _ = try readLine(stderr, &buf);
                     _ = try readLine(stderr, &buf);
                 }
                 }
             }
             }
@@ -433,10 +436,11 @@ const HealStep = struct {
 
 
 /// Heals all the exercises.
 /// Heals all the exercises.
 fn heal(allocator: Allocator, exercises: []const Exercise, work_path: []const u8) !void {
 fn heal(allocator: Allocator, exercises: []const Exercise, work_path: []const u8) !void {
+    const sep = std.fs.path.sep_str;
     const join = fs.path.join;
     const join = fs.path.join;
 
 
     const exercises_path = "exercises";
     const exercises_path = "exercises";
-    const patches_path = "patches/patches";
+    const patches_path = "patches" ++ sep ++ "patches";
 
 
     for (exercises) |ex| {
     for (exercises) |ex| {
         const name = ex.name();
         const name = ex.name();
@@ -466,27 +470,3 @@ pub fn makeTempPath(b: *Build) ![]const u8 {
 
 
     return path;
     return path;
 }
 }
-
-//
-// Missing functions from std.Build.RunStep
-//
-
-/// Adds a check for stderr match. Does not add any other checks.
-pub fn expectStdErrMatch(self: *RunStep, bytes: []const u8) void {
-    const new_check: RunStep.StdIo.Check = .{
-        .expect_stderr_match = self.step.owner.dupe(bytes),
-    };
-    self.addCheck(new_check);
-}
-
-/// Adds a check for stdout match as well as a check for exit code 0, if
-/// there is not already an expected termination check.
-pub fn expectStdOutMatch(self: *RunStep, bytes: []const u8) void {
-    const new_check: RunStep.StdIo.Check = .{
-        .expect_stdout_match = self.step.owner.dupe(bytes),
-    };
-    self.addCheck(new_check);
-    if (!self.hasTermCheck()) {
-        self.expectExitCode(0);
-    }
-}