From fa36a4520fd13b210ab98eb557b5ba96f1493730 Mon Sep 17 00:00:00 2001 From: Chris Boesch Date: Sun, 31 May 2026 23:54:20 +0200 Subject: [PATCH] removed ziglings logic from build file --- build.zig | 1403 +------------------------------------ test/tests.zig | 413 ----------- tools/check-exercises.zig | 108 --- 3 files changed, 38 insertions(+), 1886 deletions(-) delete mode 100644 test/tests.zig delete mode 100644 tools/check-exercises.zig diff --git a/build.zig b/build.zig index e8f05a2..b136583 100644 --- a/build.zig +++ b/build.zig @@ -1,21 +1,13 @@ const std = @import("std"); const builtin = @import("builtin"); -const tests = @import("test/tests.zig"); - const Build = std.Build; -const CompileStep = Build.CompileStep; -const Step = Build.Step; -const Process = std.process; - -const assert = std.debug.assert; -const join = std.fs.path.join; const print = std.debug.print; // When changing this version, be sure to also update README.md in two places: // 1) Getting Started // 2) Version Changes comptime { - const required_zig = "0.16.0-dev.2915"; + const required_zig = "0.17.0-dev.607"; const current_zig = builtin.zig_version; const min_zig = std.SemanticVersion.parse(required_zig) catch unreachable; if (current_zig.order(min_zig) == .lt) { @@ -38,129 +30,22 @@ comptime { } } -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. - main_file: []const u8, - - /// This is the desired output of the program. - /// A program passes if its output, excluding trailing whitespace, is equal - /// to this string. - output: []const u8, - - /// This is an optional hint to give if the program does not succeed. - hint: ?[]const u8 = null, - - /// By default, we verify output against stderr. - /// Set this to true to check stdout instead. - check_stdout: bool = false, - - /// This exercise makes use of C functions. - /// We need to keep track of this, so we compile with libc. - link_libc: bool = false, - - /// This exercise kind. - kind: Kind = .exe, - - /// This exercise is not supported by the current Zig compiler. - skip: bool = false, - - /// Hint to the user, why this has been skipped - skip_hint: ?[]const u8 = null, - - timestamp: bool = false, - - /// Returns the name of the main file with .zig stripped. - pub fn name(self: Exercise) []const u8 { - return std.fs.path.stem(self.main_file); - } - - /// Returns the key of the main file, the string before the '_' with - /// "zero padding" removed. - /// For example, "001_hello.zig" has the key "1". - pub fn key(self: Exercise) []const u8 { - // Main file must be key_description.zig. - const end_index = std.mem.indexOfScalar(u8, self.main_file, '_') orelse - unreachable; - - // Remove zero padding by advancing index past '0's. - var start_index: usize = 0; - while (self.main_file[start_index] == '0') start_index += 1; - return self.main_file[start_index..end_index]; - } - - /// Returns the exercise key as an integer. - pub fn number(self: Exercise) usize { - return std.fmt.parseInt(usize, self.key(), 10) catch unreachable; - } -}; - -/// Build mode. -const Mode = enum { - /// Normal build mode: `zig build` - normal, - /// Named build mode: `zig build -Dn=n` - named, - /// Random build mode: `zig build -Drandom` - random, -}; - -pub const logo = - \\ _ _ _ - \\ ___(_) __ _| (_)_ __ __ _ ___ - \\ |_ | |/ _' | | | '_ \ / _' / __| - \\ / /| | (_| | | | | | | (_| \__ \ - \\ /___|_|\__, |_|_|_| |_|\__, |___/ - \\ |___/ |___/ - \\ - \\ "Look out! Broken programs below!" - \\ - \\ -; - -const progress_filename = ".progress.txt"; - +// Elrond owns the entire Ziglings logic now! +// build.zig only builds it and forwards the chosen options as CLI flags. +// Building just this one Run step keeps the build output readable and lets +// Elrond iterate without the configure-phase cache getting in the way. pub fn build(b: *Build) !void { const io = b.graph.io; - if (!validate_exercises()) std.process.exit(2); - - use_color_escapes = false; - const stderr = std.Io.File.stderr(); - if (try stderr.supportsAnsiEscapeCodes(io)) { - use_color_escapes = true; - } else if (builtin.os.tag == .windows) { - if (stderr.enableAnsiEscapeCodes(io)) { - use_color_escapes = true; - } else |_| {} - } - - if (use_color_escapes) { - red_text = "\x1b[31m"; - red_bold_text = "\x1b[31;1m"; - red_dim_text = "\x1b[31;2m"; - green_text = "\x1b[32m"; - bold_text = "\x1b[1m"; - reset_text = "\x1b[0m"; - } - // Remove the standard install and uninstall steps. b.top_level_steps = .{}; - 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 exno: ?usize = b.option(usize, "n", "Select exercise"); - const rand: ?bool = b.option(bool, "random", "Select random exercise"); - const start: ?usize = b.option(usize, "s", "Start at exercise"); - const reset: ?bool = b.option(bool, "reset", "Reset exercise progress"); + const exno = b.option(usize, "n", "Select exercise"); + const rand = b.option(bool, "random", "Select random exercise"); + const start = b.option(usize, "s", "Start at exercise"); + const reset = b.option(bool, "reset", "Reset exercise progress"); const sep = std.fs.path.sep_str; const healed_path = if (override_healed_path) |path| @@ -169,1254 +54,42 @@ pub fn build(b: *Build) !void { "patches" ++ sep ++ "healed"; const work_path = if (healed) healed_path else "exercises"; - const header_step = PrintStep.create(b, logo); - - if (exno) |n| { - // Named build mode: verifies a single exercise. - if (n == 0 or n > exercises.len - 1) { - print("unknown exercise number: {}\n", .{n}); - std.process.exit(2); - } - const ex = exercises[n - 1]; - - const zigling_step = b.step( - "zigling", - b.fmt("Check the solution of {s}", .{ex.main_file}), - ); - b.default_step = zigling_step; - zigling_step.dependOn(&header_step.step); - - const verify_step = ZiglingStep.create(b, ex, work_path, .named); - verify_step.step.dependOn(&header_step.step); - - zigling_step.dependOn(&verify_step.step); - - return; - } - - if (rand) |_| { - // Random build mode: verifies one random exercise. - // like for 'exno' but chooses a random exercise number. - print("work in progress: check a random exercise\n", .{}); - - var prng = std.Random.DefaultPrng.init(blk: { - var seed: u64 = undefined; - io.random(std.mem.asBytes(&seed)); - break :blk seed; - }); - const rnd = prng.random(); - const num = rnd.intRangeLessThan(usize, 0, exercises.len); - const ex = exercises[num]; - - print("random exercise: {s}\n", .{ex.main_file}); - - const zigling_step = b.step( - "random", - b.fmt("Check the solution of {s}", .{ex.main_file}), - ); - b.default_step = zigling_step; - zigling_step.dependOn(&header_step.step); - const verify_step = ZiglingStep.create(b, ex, work_path, .random); - verify_step.step.dependOn(&header_step.step); - zigling_step.dependOn(&verify_step.step); - return; - } - - if (start) |s| { - if (s == 0 or s > exercises.len - 1) { - print("unknown exercise number: {}\n", .{s}); - std.process.exit(2); - } - const first = exercises[s - 1]; - const ziglings_step = b.step("ziglings", b.fmt("Check ziglings starting with {s}", .{first.main_file})); - b.default_step = ziglings_step; - - var prev_step = &header_step.step; - for (exercises[(s - 1)..]) |ex| { - const verify_stepn = ZiglingStep.create(b, ex, work_path, .normal); - verify_stepn.step.dependOn(prev_step); - - prev_step = &verify_stepn.step; - } - ziglings_step.dependOn(prev_step); - return; - } + const elrond = b.addExecutable(.{ + .name = "elrond", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/elrond.zig"), + .target = b.graph.host, + }), + }); + // -Dreset is a plain file delete; no need to launch Elrond. if (reset) |_| { - std.Io.Dir.cwd().deleteFile(io, progress_filename) catch |err| { - switch (err) { - std.Io.Dir.DeleteFileError.FileNotFound => {}, - else => { - print("Unable to remove progress file, Error: {}\n", .{err}); - return err; - }, - } - }; - - print("Progress reset, {s} removed.\n", .{progress_filename}); - std.process.exit(0); - } - - // Normal build mode: verifies all exercises according to the recommended - // order. - const ziglings_step = b.step("ziglings", "Check all ziglings"); - b.default_step = ziglings_step; - - var prev_step = &header_step.step; - - var starting_exercise: u32 = 0; - - if (std.Io.Dir.cwd().openFile(io, progress_filename, .{})) |progress_file| { - defer progress_file.close(io); - - const progress_file_size = try progress_file.length(io); - - var gpa = b.allocator; - const contents = try gpa.alloc(u8, progress_file_size); - defer gpa.free(contents); - var file_buffer: [1024]u8 = undefined; - var file_reader = progress_file.reader(io, &file_buffer); - // try file_reader.interface.readSliceAll(contents); - const bytes_read = try file_reader.interface.readSliceShort(contents); - if (bytes_read != progress_file_size) { - return error.UnexpectedEOF; - } - - const trimmed_contents = std.mem.trim(u8, contents, "\r\n"); - starting_exercise = try std.fmt.parseInt(u32, trimmed_contents, 10); - } else |err| { - switch (err) { - std.Io.File.OpenError.FileNotFound => { - // This is fine, may be the first time tests are run or progress have been reset - }, + std.Io.Dir.cwd().deleteFile(io, ".progress.txt") catch |err| switch (err) { + error.FileNotFound => {}, else => { - print("Unable to open {s}: {}\n", .{ progress_filename, err }); + print("Unable to remove progress file: {}\n", .{err}); return err; }, - } + }; + print("Progress reset, .progress.txt removed.\n", .{}); + const noop = b.step("ziglings", "Reset progress"); + b.default_step = noop; + return; } - for (exercises) |ex| { - if (starting_exercise < ex.number()) { - const verify_stepn = ZiglingStep.create(b, ex, work_path, .normal); - verify_stepn.step.dependOn(prev_step); + const run = b.addRunArtifact(elrond); + run.addArg(b.fmt("--zig={s}", .{b.graph.zig_exe})); + run.addArg(b.fmt("--work-path={s}", .{work_path})); - prev_step = &verify_stepn.step; - } + if (exno) |n| { + run.addArg(b.fmt("--only={d}", .{n})); + } else if (rand) |_| { + run.addArg("--random"); + } else if (start) |s| { + run.addArg(b.fmt("--start={d}", .{s})); } - ziglings_step.dependOn(prev_step); - const test_step = b.step("test", "Run all the tests"); - test_step.dependOn(tests.addCliTests(b, &exercises)); + const ziglings_step = b.step("ziglings", "Run ziglings"); + ziglings_step.dependOn(&run.step); + b.default_step = ziglings_step; } - -var use_color_escapes = false; -var red_text: []const u8 = ""; -var red_bold_text: []const u8 = ""; -var red_dim_text: []const u8 = ""; -var green_text: []const u8 = ""; -var bold_text: []const u8 = ""; -var reset_text: []const u8 = ""; - -const ZiglingStep = struct { - step: Step, - exercise: Exercise, - work_path: []const u8, - mode: Mode, - - pub fn create( - b: *Build, - exercise: Exercise, - work_path: []const u8, - mode: Mode, - ) *ZiglingStep { - const self = b.allocator.create(ZiglingStep) catch @panic("OOM"); - self.* = .{ - .step = Step.init(.{ - .id = .custom, - .name = exercise.main_file, - .owner = b, - .makeFn = make, - }), - .exercise = exercise, - .work_path = work_path, - .mode = mode, - }; - return self; - } - - fn printProgress(num: usize, max: usize) void { - const bar_width: u32 = 60; - - const filled_len_u64 = (@as(u64, num) * bar_width) / max; - const filled_len = @as(u32, @intCast(filled_len_u64)); - - var bar_buf: [bar_width]u8 = undefined; - - for (0..bar_width) |n| { - const ord = std.math.order(n, filled_len); - bar_buf[n] = switch (ord) { - .lt => '#', - .eq => '>', - .gt => '-', - }; - } - - std.debug.print("\rProgress: [{s}] {d}/{d}\n\n", .{ &bar_buf, num, max }); - } - - fn make(step: *Step, options: Step.MakeOptions) !void { - // NOTE: Using exit code 2 will prevent the Zig compiler to print the message: - // "error: the following build command failed with exit code 1:..." - const self: *ZiglingStep = @alignCast(@fieldParentPtr("step", step)); - - if (self.exercise.skip) { - print("Skipping {s}", .{self.exercise.main_file}); - - if (self.exercise.skip_hint) |hint| - print("\n{s}Reason: {s}{s}\n", .{ bold_text, hint, reset_text }); - - print("\n\n", .{}); - return; - } - - // Progess - printProgress(self.exercise.number(), exercises.len - 1); - - const exe_path = self.compile(options.progress_node) catch { - self.printErrors(); - - if (self.exercise.hint) |hint| - print("\n{s}Ziglings hint: {s}{s}", .{ bold_text, hint, reset_text }); - - self.help(); - std.process.exit(2); - }; - - self.run(exe_path, options.progress_node) catch { - self.printErrors(); - - if (self.exercise.hint) |hint| - print("\n{s}Ziglings hint: {s}{s}", .{ bold_text, hint, reset_text }); - - self.help(); - std.process.exit(2); - }; - - // Print possible warning/debug messages. - self.printErrors(); - } - - fn run(self: *ZiglingStep, exe_path: []const u8, _: std.Progress.Node) !void { - resetLine(); - const b = self.step.owner; - const gpa = b.allocator; - const io = b.graph.io; - - print("Checking: {s}\n", .{self.exercise.main_file}); - - // Allow up to 1 MB of stdout capture. - const max_output_bytes = 1 * 1024 * 1024; - - const result = Process.run(gpa, io, .{ - .argv = &.{exe_path}, - .cwd = .{ .path = b.build_root.path.? }, - .stdout_limit = .limited(max_output_bytes), - }) catch |err| { - return self.step.fail("unable to spawn {s}: {s}", .{ - exe_path, @errorName(err), - }); - }; - - switch (self.exercise.kind) { - .exe => return self.check_output(result), - .@"test" => return self.check_test(result), - } - } - - fn check_output(self: *ZiglingStep, result: Process.RunResult) !void { - const b = self.step.owner; - const gpa = b.allocator; - const io = b.graph.io; - - // Make sure it exited cleanly. - switch (result.term) { - .exited => |code| { - if (code != 0) { - return self.step.fail("{s} exited with error code {d} (expected {})", .{ - self.exercise.main_file, code, 0, - }); - } - }, - else => { - return self.step.fail("{s} terminated unexpectedly", .{ - self.exercise.main_file, - }); - }, - } - - const raw_output = if (self.exercise.check_stdout) - result.stdout - else - result.stderr; - - // NOTE: exercise.output can never contain a CR character. - // See https://ziglang.org/documentation/master/#Source-Encoding. - const output = trimLines(gpa, raw_output) catch @panic("OOM"); - - // Validate the output. - var exercise_output = self.exercise.output; - - // Insert timestamp for exercise 85 - if (self.exercise.timestamp) { - - // Compare timestamp from exercise with now, diff < 5 seconds is valid - var ts_buf: [20]u8 = undefined; - const ts_slice = output[14..24]; - const ts_value = try std.fmt.parseInt(i64, ts_slice, 10); - const ts_build = std.Io.Timestamp.now(io, .real).toSeconds(); - const ts_diff = @abs(ts_build - ts_value); - const timestamp = std.fmt.bufPrint(&ts_buf, "{}", .{if (ts_diff < 5) ts_value else ts_build}) catch unreachable; - - // Insert timestamp into check string - var buf: [100]u8 = undefined; - const prefix_len = 14; - const placeholder_len = 11; - - @memcpy(buf[0..prefix_len], exercise_output[0..prefix_len]); - @memcpy(buf[prefix_len..][0..timestamp.len], timestamp); - const suffix = exercise_output[prefix_len + placeholder_len ..]; - const suffix_dest_start = prefix_len + timestamp.len; - @memcpy(buf[suffix_dest_start..][0..suffix.len], suffix); - - const total_len = prefix_len + timestamp.len + suffix.len; - exercise_output = buf[0..total_len]; - } - - if (!std.mem.eql(u8, output, exercise_output)) { - const red = red_bold_text; - const reset = reset_text; - - // Override the coloring applied by the printError method. - // NOTE: the first red and the last reset are not necessary, they - // are here only for alignment. - return self.step.fail( - \\ - \\{s}========= expected this output: =========={s} - \\{s} - \\{s}========= but found: ====================={s} - \\{s} - \\{s}=========================================={s} - , .{ red, reset, exercise_output, red, reset, output, red, reset }); - } - - const progress = try std.fmt.allocPrint(gpa, "{d}", .{self.exercise.number()}); - defer gpa.free(progress); - - const file = try std.Io.Dir.cwd().createFile( - io, - progress_filename, - .{ .read = true, .truncate = true }, - ); - defer file.close(io); - - try file.writeStreamingAll(io, progress); - try file.sync(io); - - print("{s}PASSED:\n{s}{s}\n\n", .{ green_text, output, reset_text }); - } - - fn check_test(self: *ZiglingStep, result: Process.RunResult) !void { - switch (result.term) { - .exited => |code| { - if (code != 0) { - // The test failed. - const stderr = std.mem.trimEnd(u8, result.stderr, " \r\n"); - - return self.step.fail("\n{s}", .{stderr}); - } - }, - else => { - return self.step.fail("{s} terminated unexpectedly", .{ - self.exercise.main_file, - }); - }, - } - - print("{s}PASSED{s}\n\n", .{ green_text, reset_text }); - } - - fn compile(self: *ZiglingStep, prog_node: std.Progress.Node) ![]const u8 { - print("Compiling: {s}\n", .{self.exercise.main_file}); - - const b = self.step.owner; - const gpa = b.allocator; - const exercise_path = self.exercise.main_file; - const path = join(gpa, &.{ self.work_path, exercise_path }) catch - @panic("OOM"); - - var zig_args = std.ArrayList([]const u8).initCapacity(gpa, 10) catch @panic("OOM"); - defer zig_args.deinit(gpa); - - zig_args.append(gpa, b.graph.zig_exe) catch @panic("OOM"); - - const cmd = switch (self.exercise.kind) { - .exe => "build-exe", - .@"test" => "test", - }; - zig_args.append(gpa, cmd) catch @panic("OOM"); - - // Enable C support for exercises that use C functions. - if (self.exercise.link_libc) { - zig_args.append(gpa, "-lc") catch @panic("OOM"); - zig_args.append(gpa, "-fllvm") catch @panic("OOM"); - } - - if (b.reference_trace) |rt| { - zig_args.append(gpa, b.fmt("-freference-trace={}", .{rt})) catch @panic("OOM"); - } - - zig_args.append(gpa, b.pathFromRoot(path)) catch @panic("OOM"); - - zig_args.append(gpa, "--cache-dir") catch @panic("OOM"); - zig_args.append(gpa, b.pathFromRoot(b.cache_root.path.?)) catch @panic("OOM"); - - zig_args.append(gpa, "--listen=-") catch @panic("OOM"); - - // - // NOTE: After many changes in zig build system, we need to create the cache path manually. - // See https://github.com/ziglang/zig/pull/21115 - // Maybe there is a better way (in the future). - const exe_dir = try self.step.evalZigProcess(zig_args.items, prog_node, false, null, gpa); - const exe_name = switch (self.exercise.kind) { - .exe => self.exercise.name(), - .@"test" => "test", - }; - const sep = std.Io.Dir.path.sep_str; - const root_path = exe_dir.?.root_dir.path.?; - const sub_path = exe_dir.?.subPathOrDot(); - const exe_path = b.fmt("{s}{s}{s}{s}{s}", .{ root_path, sep, sub_path, sep, exe_name }); - - return exe_path; - } - - fn help(self: *ZiglingStep) void { - const b = self.step.owner; - const key = self.exercise.key(); - const path = self.exercise.main_file; - - const cmd = switch (self.mode) { - .normal => "zig build", - .named => b.fmt("zig build -Dn={s}", .{key}), - .random => "zig build -Drandom", - }; - - print("\n{s}Edit exercises/{s} and run '{s}' again.{s}\n", .{ - red_bold_text, path, cmd, reset_text, - }); - } - - fn printErrors(self: *ZiglingStep) void { - resetLine(); - const b = self.step.owner; - const io = b.graph.io; - - // Display error/warning messages. - if (self.step.result_error_msgs.items.len > 0) { - for (self.step.result_error_msgs.items) |msg| { - print("{s}error: {s}{s}{s}{s}\n", .{ - red_bold_text, reset_text, red_dim_text, msg, reset_text, - }); - } - } - - // Render compile errors at the bottom of the terminal. - // TODO: use the same ttyconf from the builder. - const color: std.zig.Color = if (use_color_escapes) - .on - else - .off; - if (self.step.result_error_bundle.errorMessageCount() > 0) { - self.step.result_error_bundle.renderToStderr(io, .{}, color) catch |err| { - print("{}\n", .{err}); - return; - }; - } - } -}; - -/// Clears the entire line and move the cursor to column zero. -/// Used for clearing the compiler and build_runner progress messages. -fn resetLine() void { - if (use_color_escapes) print("{s}", .{"\x1b[2K\r"}); -} - -/// Removes trailing whitespace for each line in buf, also ensuring that there -/// are no trailing LF characters at the end. -pub fn trimLines(gpa: std.mem.Allocator, buf: []const u8) ![]const u8 { - var list = try std.ArrayList(u8).initCapacity(gpa, buf.len); - errdefer list.deinit(gpa); - - var iter = std.mem.splitSequence(u8, buf, " \n"); - while (iter.next()) |line| { - // TODO: trimming CR characters is probably not necessary. - const data = std.mem.trimEnd(u8, line, " \r"); - try list.appendSlice(gpa, data); - try list.append(gpa, '\n'); - } - - // Calls deinit() - const result = try list.toOwnedSlice(gpa); - - // Remove the trailing LF character, that is always present in the exercise output. - return std.mem.trimEnd(u8, result, "\n"); -} - -/// Prints a message to stderr. -const PrintStep = struct { - step: Step, - message: []const u8, - - pub fn create(owner: *Build, message: []const u8) *PrintStep { - const self = owner.allocator.create(PrintStep) catch @panic("OOM"); - self.* = .{ - .step = Step.init(.{ - .id = .custom, - .name = "print", - .owner = owner, - .makeFn = make, - }), - .message = message, - }; - - return self; - } - - fn make(step: *Step, _: Step.MakeOptions) !void { - const self: *PrintStep = @alignCast(@fieldParentPtr("step", step)); - print("{s}", .{self.message}); - } -}; - -/// 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 { - // Don't use the "multi-object for loop" syntax, in order to avoid a syntax - // error with old Zig compilers. - var i: usize = 0; - for (exercises[0..]) |ex| { - const exno = ex.number(); - const last = 999; - i += 1; - - if (exno != i and exno != last) { - print("exercise {s} has an incorrect number: expected {}, got {s}\n", .{ - ex.main_file, i, ex.key(), - }); - - return false; - } - - var iter = std.mem.splitScalar(u8, ex.output, '\n'); - while (iter.next()) |line| { - const output = std.mem.trimEnd(u8, line, " \r"); - if (output.len != line.len) { - print("exercise {s} output field lines have trailing whitespace\n", .{ - ex.main_file, - }); - - return false; - } - } - - if (!std.mem.endsWith(u8, ex.main_file, ".zig")) { - print("exercise {s} is not a zig source file\n", .{ex.main_file}); - - return false; - } - } - - return true; -} - -const exercises = [_]Exercise{ - .{ - .main_file = "001_hello.zig", - .output = "Hello world!", - .hint = - \\DON'T PANIC! - \\Read the compiler messages above. (Something about 'main'?) - \\Open up the source file as noted below and read the comments. - \\ - \\(Hints like these will occasionally show up, but for the - \\most part, you'll be taking directions from the Zig - \\compiler itself.) - \\ - , // pay attention to the comma - }, - .{ - .main_file = "002_std.zig", - .output = "Standard Library.", - }, - .{ - .main_file = "003_assignment.zig", - .output = "55 314159 -11", - .hint = "There are three mistakes in this one!", - }, - .{ - .main_file = "004_arrays.zig", - .output = "First: 2, Fourth: 7, Length: 8", - .hint = "There are two things to complete here.", - }, - .{ - .main_file = "005_arrays2.zig", - .output = "LEET: 1337, Bits: 100110011001", - .hint = "Fill in the two arrays.", - }, - .{ - .main_file = "006_strings.zig", - .output = "d=d Major Tom", - .hint = "Each '???' needs something filled in.", - }, - .{ - .main_file = "007_strings2.zig", - .output = - \\Ziggy played guitar - \\Jamming good with Andrew Kelley - \\And the Spiders from Mars - , // pay attention to the comma - .hint = "Please fix the lyrics!", - }, - .{ - .main_file = "008_quiz.zig", - .output = "Program in Zig!", - .hint = "See if you can fix the program!", - }, - .{ - .main_file = "009_if.zig", - .output = "Foo is 42!", - }, - .{ - .main_file = "010_if2.zig", - .output = "With the discount, the price is $17.", - }, - .{ - .main_file = "011_while.zig", - .output = "2 4 8 16 32 64 128 256 512 n=1024", - .hint = "You probably want a 'less than' condition.", - }, - .{ - .main_file = "012_while2.zig", - .output = "2 4 8 16 32 64 128 256 512 n=1024", - .hint = "It might help to look back at the previous exercise.", - }, - .{ - .main_file = "013_while3.zig", - .output = "1 2 4 7 8 11 13 14 16 17 19", - }, - .{ - .main_file = "014_while4.zig", - .output = "n=4", - }, - .{ - .main_file = "015_for.zig", - .output = "A Dramatic Story: :-) :-) :-( :-| :-) The End.", - }, - .{ - .main_file = "016_for2.zig", - .output = "The value of bits '1101': 13.", - }, - .{ - .main_file = "017_quiz2.zig", - .output = "1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz, 16,", - .hint = "This is a famous game!", - }, - .{ - .main_file = "018_functions.zig", - .output = "Answer to the Ultimate Question: 42", - .hint = "Can you help write the function?", - }, - .{ - .main_file = "019_functions2.zig", - .output = "Powers of two: 2 4 8 16", - }, - .{ - .main_file = "020_quiz3.zig", - .output = "32 64 128 256", - .hint = "Unexpected pop quiz! Help!", - }, - .{ - .main_file = "021_errors.zig", - .output = "2<4. 3<4. 4=4. 5>4. 6>4.", - .hint = "What's the deal with fours?", - }, - .{ - .main_file = "022_errors2.zig", - .output = "I compiled!", - .hint = "Get the error union type right to allow this to compile.", - }, - .{ - .main_file = "023_errors3.zig", - .output = "a=64, b=22", - }, - .{ - .main_file = "024_errors4.zig", - .output = "a=20, b=14, c=10", - }, - .{ - .main_file = "025_errors5.zig", - .output = "a=0, b=19, c=0", - }, - .{ - .main_file = "026_hello2.zig", - .output = "Hello world!", - .hint = "Try using a try!", - .check_stdout = true, - }, - .{ - .main_file = "027_defer.zig", - .output = "One Two", - }, - .{ - .main_file = "028_defer2.zig", - .output = "(Goat) (Cat) (Dog) (Dog) (Goat) (Unknown) done.", - }, - .{ - .main_file = "029_errdefer.zig", - .output = "Getting number...got 5. Getting number...failed!", - }, - .{ - .main_file = "030_switch.zig", - .output = "ZIG?", - }, - .{ - .main_file = "031_switch2.zig", - .output = "ZIG!", - }, - .{ - .main_file = "032_unreachable.zig", - .output = "1 2 3 9 8 7", - }, - .{ - .main_file = "033_iferror.zig", - .output = "2<4. 3<4. 4=4. 5>4. 6>4.", - .hint = "Seriously, what's the deal with fours?", - }, - .{ - .main_file = "034_quiz4.zig", - .output = "my_num=42", - .hint = "Can you make this work?", - .check_stdout = true, - }, - .{ - .main_file = "035_enums.zig", - .output = "1 2 3 9 8 7", - .hint = "This problem seems familiar...", - }, - .{ - .main_file = "036_enums2.zig", - .output = - \\

- \\ Red - \\ Green - \\ Blue - \\

- , // pay attention to the comma - .hint = "I'm feeling blue about this.", - }, - .{ - .main_file = "037_structs.zig", - .output = "Your wizard has 90 health and 25 gold.", - }, - .{ - .main_file = "038_structs2.zig", - .output = - \\Character 1 - G:20 H:100 XP:10 - \\Character 2 - G:10 H:100 XP:20 - , // pay attention to the comma - }, - .{ - .main_file = "039_pointers.zig", - .output = "num1: 5, num2: 5", - .hint = "Pointers aren't so bad.", - }, - .{ - .main_file = "040_pointers2.zig", - .output = "a: 12, b: 12", - }, - .{ - .main_file = "041_pointers3.zig", - .output = "foo=6, bar=11", - }, - .{ - .main_file = "042_pointers4.zig", - .output = "num: 5, more_nums: 1 1 5 1", - }, - .{ - .main_file = "043_pointers5.zig", - .output = - \\Wizard (G:10 H:100 XP:20) - \\ Mentor: Wizard (G:10000 H:100 XP:2340) - , // pay attention to the comma - }, - .{ - .main_file = "044_quiz5.zig", - .output = "Elephant A. Elephant B. Elephant C.", - .hint = "Oh no! We forgot Elephant B!", - }, - .{ - .main_file = "045_optionals.zig", - .output = "The Ultimate Answer: 42.", - }, - .{ - .main_file = "046_optionals2.zig", - .output = "Elephant A. Elephant B. Elephant C.", - .hint = "Elephants again!", - }, - .{ - .main_file = "047_methods.zig", - .output = "5 aliens. 4 aliens. 1 aliens. 0 aliens. Earth is saved!", - .hint = "Use the heat ray. And the method!", - }, - .{ - .main_file = "048_methods2.zig", - .output = "A B C", - .hint = "This just needs one little fix.", - }, - .{ - .main_file = "049_quiz6.zig", - .output = "A B C Cv Bv Av", - .hint = "Now you're writing Zig!", - }, - .{ - .main_file = "050_no_value.zig", - .output = "That is not dead which can eternal lie / And with strange aeons even death may die.", - }, - .{ - .main_file = "051_values.zig", - .output = "1:false!. 2:true!. 3:true!. XP before:0, after:200.", - }, - .{ - .main_file = "052_slices.zig", - .output = - \\Hand1: A 4 K 8 - \\Hand2: 5 2 Q J - , // pay attention to the comma - }, - .{ - .main_file = "053_slices2.zig", - .output = "'all your base are belong to us.' 'for great justice.'", - }, - .{ - .main_file = "054_manypointers.zig", - .output = "Memory is a resource.", - }, - .{ - .main_file = "055_unions.zig", - .output = "Insect report! Ant alive is: true. Bee visited 15 flowers.", - }, - .{ - .main_file = "056_unions2.zig", - .output = "Insect report! Ant alive is: true. Bee visited 16 flowers.", - }, - .{ - .main_file = "057_unions3.zig", - .output = "Insect report! Ant alive is: true. Bee visited 17 flowers.", - }, - .{ - .main_file = "058_quiz7.zig", - .output = "Archer's Point--2->Bridge--1->Dogwood Grove--3->Cottage--2->East Pond--1->Fox Pond", - .hint = "This is the biggest program we've seen yet. But you can do it!", - }, - .{ - .main_file = "059_integers.zig", - .output = "Zig is cool.", - }, - .{ - .main_file = "060_floats.zig", - .output = "Shuttle liftoff weight: 2.032e3 metric tons", - }, - .{ - .main_file = "061_coercions.zig", - .output = "Letter: A", - }, - .{ - .main_file = "062_loop_expressions.zig", - .output = "Current language: Zig", - .hint = "Surely the current language is 'Zig'!", - }, - .{ - .main_file = "063_labels.zig", - .output = "Enjoy your Cheesy Chili!", - }, - .{ - .main_file = "064_builtins.zig", - .output = "1101 + 0101 = 0010 (true). Without overflow: 00010010. Furthermore, 11110000 backwards is 00001111.", - }, - .{ - .main_file = "065_builtins2.zig", - .output = "A Narcissus loves all Narcissuses. He has room in his heart for: me myself.", - }, - .{ - .main_file = "066_comptime.zig", - .output = "Immutable: 12345, 987.654; Mutable: 54321, 456.789; Types: comptime_int, comptime_float, u32, f32", - .hint = "It may help to read this one out loud to your favorite stuffed animal until it sinks in completely.", - }, - .{ - .main_file = "067_comptime2.zig", - .output = "A BB CCC DDDD", - }, - .{ - .main_file = "068_comptime3.zig", - .output = - \\Minnow (1:32, 4 x 2) - \\Shark (1:16, 8 x 5) - \\Whale (1:1, 143 x 95) - , - }, - .{ - .main_file = "069_comptime4.zig", - .output = "s1={ 1, 2, 3 }, s2={ 1, 2, 3, 4, 5 }, s3={ 1, 2, 3, 4, 5, 6, 7 }", - }, - .{ - .main_file = "070_comptime5.zig", - .output = - \\"Quack." ducky1: true, "Squeek!" ducky2: true, ducky3: false - , - .hint = "Have you kept the wizard hat on?", - }, - .{ - .main_file = "071_comptime6.zig", - .output = "Narcissus has room in his heart for: me myself.", - }, - .{ - .main_file = "072_comptime7.zig", - .output = "26", - }, - .{ - .main_file = "073_comptime8.zig", - .output = "My llama value is 25.", - }, - .{ - .main_file = "074_comptime9.zig", - .output = "MouseLlama joins the crew!", - }, - .{ - .main_file = "075_quiz8.zig", - .output = "Archer's Point--2->Bridge--1->Dogwood Grove--3->Cottage--2->East Pond--1->Fox Pond", - .hint = "Roll up those sleeves. You get to WRITE some code for this one.", - }, - .{ - .main_file = "076_sentinels.zig", - .output = "Array:123056. Many-item pointer:123.", - }, - .{ - .main_file = "077_sentinels2.zig", - .output = "Weird Data!", - }, - .{ - .main_file = "078_sentinels3.zig", - .output = "Weird Data!", - }, - .{ - .main_file = "079_quoted_identifiers.zig", - .output = "Sweet freedom: 55, false.", - .hint = "Help us, Zig Programmer, you're our only hope!", - }, - .{ - .main_file = "080_anonymous_structs.zig", - .output = "[Circle(i32): 25,70,15] [Circle(f32): 25.2,71.0,15.7]", - }, - .{ - .main_file = "081_anonymous_structs2.zig", - .output = "x:205 y:187 radius:12", - }, - .{ - .main_file = "082_anonymous_structs3.zig", - .output = - \\"0"(bool):true "1"(bool):false "2"(i32):42 "3"(f32):3.141592 - , // pay attention to the comma - .hint = "This one is a challenge! But you have everything you need.", - }, - .{ - .main_file = "083_anonymous_lists.zig", - .output = "I say hello!", - }, - .{ - .main_file = "084_interfaces.zig", - .output = - \\=== Doctor Zoraptera's Insect Report === - \\Ant is alive. - \\Bee visited 17 flowers. - \\Grasshopper hopped 32 meters. - , // pay attention to the comma - }, - - // Skipped because of https://github.com/ratfactor/ziglings/issues/163 - // direct link: https://github.com/ziglang/zig/issues/6025 - .{ - .main_file = "085_async.zig", - .output = "Current time: s since epoch", - .timestamp = true, - }, - .{ - .main_file = "086_async2.zig", - .output = "Computing... The answer is: 42", - }, - .{ - .main_file = "087_async3.zig", - .output = - \\1 + 2 = 3 - \\6 * 7 = 42 - \\Total: 45 - , // pay attention to the comma - }, - .{ - .main_file = "088_async4.zig", - .output = - \\Task 1 done. - \\Task 2 done. - \\Task 3 done. - \\All tasks finished! - , // pay attention to the comma - }, - .{ - .main_file = "089_async5.zig", - .output = - \\Starting long computation... - \\Canceling slow task... - \\Task was canceled, cleaning up. - \\Task returned: 0 - , // pay attention to the comma - }, - .{ - .main_file = "090_async6.zig", - .output = "Hare: I'm fast!", - }, - .{ - .main_file = "091_async7.zig", - .output = "Counter: 400", - }, - .{ - .main_file = "092_async8.zig", - .output = "Sum of 1..10 = 55", - }, - .{ - .main_file = "093_async9.zig", - .output = "Worker 1 found signal start over threshold at index 12!", - }, - .{ - .main_file = "094_async10.zig", - .output = - \\Starting critical section... - \\Critical section completed safely. - \\Task result: All data saved. - , // pay attention to the comma - }, - .{ - .main_file = "095_quiz_async.zig", - .output = - \\=== Doctor Zoraptera's Garden Report === - \\Temperature : 23C - \\Humidity : 63% - \\Wind : 13 km/h - \\Readings : 9 - \\Bee-friendly conditions! Expect high pollination. - , // pay attention to the comma - }, - .{ - .main_file = "096_hello_c.zig", - .output = "Hello C from Zig! - C result is 17 chars written.", - .link_libc = true, - .skip = true, - .skip_hint = "Skipped until we have found a solution for the removed '@cImport'", - }, - .{ - .main_file = "097_c_math.zig", - .output = "The normalized angle of 765.2 degrees is 45.2 degrees.", - .link_libc = true, - .skip = true, - .skip_hint = "Skipped until we have found a solution for the removed '@cImport'", - }, - .{ - .main_file = "098_for3.zig", - .output = "1 2 4 7 8 11 13 14 16 17 19\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15", - }, - .{ - .main_file = "099_memory_allocation.zig", - .output = "Running Average: 0.30 0.25 0.20 0.18 0.22", - }, - .{ - .main_file = "100_bit_manipulation.zig", - .output = "x = 1011; y = 1101", - }, - .{ - .main_file = "101_bit_manipulation2.zig", - .output = "Is this a pangram? true!", - }, - .{ - .main_file = "102_formatting.zig", - .output = - \\ - \\ X | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - \\---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - \\ 1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - \\ - \\ 2 | 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 - \\ - \\ 3 | 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 - \\ - \\ 4 | 4 8 12 16 20 24 28 32 36 40 44 48 52 56 60 - \\ - \\ 5 | 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 - \\ - \\ 6 | 6 12 18 24 30 36 42 48 54 60 66 72 78 84 90 - \\ - \\ 7 | 7 14 21 28 35 42 49 56 63 70 77 84 91 98 105 - \\ - \\ 8 | 8 16 24 32 40 48 56 64 72 80 88 96 104 112 120 - \\ - \\ 9 | 9 18 27 36 45 54 63 72 81 90 99 108 117 126 135 - \\ - \\10 | 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 - \\ - \\11 | 11 22 33 44 55 66 77 88 99 110 121 132 143 154 165 - \\ - \\12 | 12 24 36 48 60 72 84 96 108 120 132 144 156 168 180 - \\ - \\13 | 13 26 39 52 65 78 91 104 117 130 143 156 169 182 195 - \\ - \\14 | 14 28 42 56 70 84 98 112 126 140 154 168 182 196 210 - \\ - \\15 | 15 30 45 60 75 90 105 120 135 150 165 180 195 210 225 - , - }, - .{ - .main_file = "103_for4.zig", - .output = "Arrays match!", - }, - .{ - .main_file = "104_for5.zig", - .output = - \\1. Wizard (Gold: 25, XP: 40) - \\2. Bard (Gold: 11, XP: 17) - \\3. Bard (Gold: 5, XP: 55) - \\4. Warrior (Gold: 7392, XP: 21) - , // pay attention to the comma - }, - .{ - .main_file = "105_testing.zig", - .output = "", - .kind = .@"test", - }, - .{ - .main_file = "106_tokenization.zig", - .output = - \\My - \\name - \\is - \\Ozymandias - \\King - \\of - \\Kings - \\Look - \\on - \\my - \\Works - \\ye - \\Mighty - \\and - \\despair - \\This little poem has 15 words! - , // pay attention to the comma - }, - .{ - .main_file = "107_threading.zig", - .output = - \\Starting work... - \\thread 1: started. - \\thread 2: started. - \\thread 3: started. - \\Some weird stuff, after starting the threads. - \\thread 2: finished. - \\thread 1: finished. - \\thread 3: finished. - \\Zig is cool! - , // pay attention to the comma - }, - .{ - .main_file = "108_threading2.zig", - .output = "PI ≈ 3.14159265", - }, - .{ - .main_file = "109_files.zig", - .output = "Successfully wrote 18 bytes.", - }, - .{ - .main_file = "110_files2.zig", - .output = - \\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - \\Successfully Read 18 bytes: It's zigling time! - , // pay attention to the comma - }, - .{ - .main_file = "111_labeled_switch.zig", - .output = "The pull request has been merged.", - }, - .{ - .main_file = "112_vectors.zig", - .output = - \\Max difference (old fn): 0.014 - \\Max difference (new fn): 0.014 - , // pay attention to the comma - }, - .{ .main_file = "113_quiz9.zig", .output = - \\Toggle pins with XOR on PORTB - \\----------------------------- - \\ 1100 // (initial state of PORTB) - \\^ 0101 // (bitmask) - \\= 1001 - \\ - \\ 1100 // (initial state of PORTB) - \\^ 0011 // (bitmask) - \\= 1111 - \\ - \\Set pins with OR on PORTB - \\------------------------- - \\ 1001 // (initial state of PORTB) - \\| 0100 // (bitmask) - \\= 1101 - \\ - \\ 1001 // (reset state) - \\| 0100 // (bitmask) - \\= 1101 - \\ - \\Clear pins with AND and NOT on PORTB - \\------------------------------------ - \\ 1110 // (initial state of PORTB) - \\& 1011 // (bitmask) - \\= 1010 - \\ - \\ 0111 // (reset state) - \\& 1110 // (bitmask) - \\= 0110 - }, - .{ - .main_file = "114_packed.zig", - .output = "", - }, - .{ - .main_file = "115_packed2.zig", - .output = "", - }, - .{ - .main_file = "999_the_end.zig", - .output = - \\ - \\This is the end for now! - \\We hope you had fun and were able to learn a lot, so visit us again when the next exercises are available. - , // pay attention to the comma - }, -}; diff --git a/test/tests.zig b/test/tests.zig deleted file mode 100644 index 509cb54..0000000 --- a/test/tests.zig +++ /dev/null @@ -1,413 +0,0 @@ -const std = @import("std"); -const root = @import("../build.zig"); - -const debug = std.debug; -const fmt = std.fmt; -const mem = std.mem; - -const Allocator = std.mem.Allocator; -const Process = std.process; -const Build = std.Build; -const Step = Build.Step; -const RunStep = Build.RunStep; -const LazyPath = Build.LazyPath; - -const Exercise = root.Exercise; - -pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step { - const step = b.step("test-cli", "Test the command line interface"); - - { - // Test that `zig build -Dhealed -Dn=n` selects the nth exercise. - const case_step = createCase(b, "case-1"); - - const tmp_path = createTempPath(b) catch |err| { - return fail(step, "unable to make tmp path: {s}\n", .{@errorName(err)}); - }; - defer deleteTmpPath(b, tmp_path); - - const heal_step = HealStep.create(b, exercises, tmp_path); - - for (exercises[0 .. exercises.len - 1]) |ex| { - const n = ex.number(); - - const cmd = b.addSystemCommand(&.{ - b.graph.zig_exe, - "build", - "-Dhealed", - b.fmt("-Dhealed-path={s}", .{tmp_path}), - b.fmt("-Dn={}", .{n}), - }); - cmd.setName(b.fmt("zig build -Dhealed -Dn={}", .{n})); - cmd.expectExitCode(0); - cmd.step.dependOn(&heal_step.step); - - const stderr = cmd.captureStdErr(.{}); - const verify = CheckNamedStep.create(b, ex, stderr); - verify.step.dependOn(&cmd.step); - - case_step.dependOn(&verify.step); - } - } - - { - // Test that `zig build -Dhealed` processes all the exercises in order. - const case_step = createCase(b, "case-2"); - - const tmp_path = createTempPath(b) catch |err| { - return fail(step, "unable to make tmp path: {s}\n", .{@errorName(err)}); - }; - defer deleteTmpPath(b, tmp_path); - - const heal_step = HealStep.create(b, exercises, tmp_path); - heal_step.step.dependOn(case_step); - - // TODO: when an exercise is modified, the cache is not invalidated. - const cmd = b.addSystemCommand(&.{ - b.graph.zig_exe, - "build", - "-Dhealed", - b.fmt("-Dhealed-path={s}", .{tmp_path}), - }); - cmd.setName("zig build -Dhealed"); - cmd.expectExitCode(0); - cmd.step.dependOn(&heal_step.step); - - const stderr = cmd.captureStdErr(.{}); - const verify = CheckStep.create(b, exercises, stderr); - verify.step.dependOn(&cmd.step); - } - - { - // Test that `zig build -Dn=n` prints the hint. - const case_step = createCase(b, "case-3"); - - for (exercises[0 .. exercises.len - 1]) |ex| { - if (ex.skip) continue; - - if (ex.hint) |hint| { - const n = ex.number(); - - const cmd = b.addSystemCommand(&.{ - b.graph.zig_exe, - "build", - b.fmt("-Dn={}", .{n}), - }); - cmd.setName(b.fmt("zig build -Dn={}", .{n})); - cmd.expectExitCode(2); - cmd.addCheck(.{ .expect_stderr_match = hint }); - - case_step.dependOn(&cmd.step); - } - } - - step.dependOn(case_step); - } - - return step; -} - -fn createCase(b: *Build, name: []const u8) *Step { - const case_step = b.allocator.create(Step) catch @panic("OOM"); - case_step.* = Step.init(.{ - .id = .custom, - .name = name, - .owner = b, - }); - - return case_step; -} - -/// Checks the output of `zig build -Dn=n`. -const CheckNamedStep = struct { - step: Step, - exercise: Exercise, - stderr: LazyPath, - - pub fn create(owner: *Build, exercise: Exercise, stderr: LazyPath) *CheckNamedStep { - const self = owner.allocator.create(CheckNamedStep) catch @panic("OOM"); - self.* = .{ - .step = Step.init(.{ - .id = .custom, - .name = "check-named", - .owner = owner, - .makeFn = make, - }), - .exercise = exercise, - .stderr = stderr, - }; - - return self; - } - - fn make(step: *Step, _: Step.MakeOptions) !void { - const b = step.owner; - const io = b.graph.io; - const self: *CheckNamedStep = @alignCast(@fieldParentPtr("step", step)); - const ex = self.exercise; - - const stderr_file = try std.Io.Dir.cwd().openFile( - io, - self.stderr.getPath(b), - .{ .mode = .read_only }, - ); - defer stderr_file.close(io); - - var stderr = stderr_file.readerStreaming(io, &.{}); - { - // Skip the logo. - const nlines = mem.count(u8, root.logo, "\n"); - var buf: [80]u8 = undefined; - - var lineno: usize = 0; - while (lineno < nlines) : (lineno += 1) { - _ = try readLine(&stderr, &buf); - } - } - try check_output(step, ex, &stderr); - } -}; - -/// Checks the output of `zig build`. -const CheckStep = struct { - step: Step, - exercises: []const Exercise, - stderr: LazyPath, - - pub fn create( - owner: *Build, - exercises: []const Exercise, - stderr: LazyPath, - ) *CheckStep { - const self = owner.allocator.create(CheckStep) catch @panic("OOM"); - self.* = .{ - .step = Step.init(.{ - .id = .custom, - .name = "check", - .owner = owner, - .makeFn = make, - }), - .exercises = exercises, - .stderr = stderr, - }; - - return self; - } - - fn make(step: *Step, _: Step.MakeOptions) !void { - const b = step.owner; - const io = b.graph.io; - const self: *CheckStep = @alignCast(@fieldParentPtr("step", step)); - const exercises = self.exercises; - - const stderr_file = try std.Io.Dir.cwd().openFile( - io, - self.stderr.getPath(b), - .{ .mode = .read_only }, - ); - defer stderr_file.close(io); - - var stderr = stderr_file.readerStreaming(io, &.{}); - for (exercises) |ex| { - if (ex.number() == 1) { - // Skip the logo. - const nlines = mem.count(u8, root.logo, "\n"); - var buf: [80]u8 = undefined; - - var lineno: usize = 0; - while (lineno < nlines) : (lineno += 1) { - _ = try readLine(&stderr, &buf); - } - } - try check_output(step, ex, &stderr); - } - } -}; - -fn check_output(step: *Step, exercise: Exercise, reader: *std.Io.File.Reader) !void { - const b = step.owner; - - var buf: [1024]u8 = undefined; - if (exercise.skip) { - { - const actual = try readLine(reader, &buf) orelse "EOF"; - const expect = b.fmt("Skipping {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}); - try check(step, exercise, expect, actual); - } - - { - const actual = try readLine(reader, &buf) orelse "EOF"; - const expect = b.fmt("Checking {s}...", .{exercise.main_file}); - try check(step, exercise, expect, actual); - } - - { - const actual = try readLine(reader, &buf) orelse "EOF"; - const expect = switch (exercise.kind) { - .exe => "PASSED:", - .@"test" => "PASSED", - }; - try check(step, exercise, expect, actual); - } - - // Skip the exercise output. - 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"); - } -} - -fn check( - step: *Step, - exercise: Exercise, - expect: []const u8, - actual: []const u8, -) !void { - if (!mem.eql(u8, expect, actual)) { - return step.fail("{s}: expected to see \"{s}\", found \"{s}\"", .{ - exercise.main_file, - expect, - actual, - }); - } -} - -fn readLine(reader: *std.Io.File.Reader, buf: []u8) !?[]const u8 { - try reader.interface.readSliceAll(buf); - return mem.trimEnd(u8, buf, " \r\n"); -} - -/// Fails with a custom error message. -const FailStep = struct { - step: Step, - error_msg: []const u8, - - pub fn create(owner: *Build, error_msg: []const u8) *FailStep { - const self = owner.allocator.create(FailStep) catch @panic("OOM"); - self.* = .{ - .step = Step.init(.{ - .id = .custom, - .name = "fail", - .owner = owner, - .makeFn = make, - }), - .error_msg = error_msg, - }; - - return self; - } - - fn make(step: *Step, _: Step.MakeOptions) !void { - const b = step.owner; - const self: *FailStep = @alignCast(@fieldParentPtr("step", step)); - - try step.result_error_msgs.append(b.allocator, self.error_msg); - return error.MakeFailed; - } -}; - -/// A variant of `std.Build.Step.fail` that does not return an error so that it -/// can be used in the configuration phase. It returns a FailStep, so that the -/// error will be cleanly handled by the build runner. -fn fail(step: *Step, comptime format: []const u8, args: anytype) *Step { - const b = step.owner; - - const fail_step = FailStep.create(b, b.fmt(format, args)); - step.dependOn(&fail_step.step); - - return step; -} - -/// Heals the 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, _: Step.MakeOptions) !void { - const b = step.owner; - const self: *HealStep = @alignCast(@fieldParentPtr("step", step)); - - return heal(b.allocator, self.exercises, self.work_path); - } -}; - -/// Heals all the exercises. -fn heal(allocator: Allocator, exercises: []const Exercise, work_path: []const u8) !void { - const io = std.Options.debug_io; - const sep = std.Io.Dir.path.sep_str; - const join = std.Io.Dir.path.join; - - const exercises_path = "exercises"; - const patches_path = "patches" ++ sep ++ "patches"; - - for (exercises) |ex| { - const name = ex.name(); - - const file = try join(allocator, &.{ exercises_path, ex.main_file }); - const patch = b: { - const patch_name = try fmt.allocPrint(allocator, "{s}.patch", .{name}); - break :b try join(allocator, &.{ patches_path, patch_name }); - }; - const output = try join(allocator, &.{ work_path, ex.main_file }); - - const argv = &.{ "patch", "-i", patch, "-o", output, "-s", file }; - - _ = try Process.run(allocator, io, .{ .argv = argv }); - } -} - -fn createTempPath(b: *Build) ![]const u8 { - const io = b.graph.io; - const rand_int = r: { - var x: u64 = undefined; - io.random(@ptrCast(&x)); - break :r x; - }; - const tmp_dir_sub_path = "tmp" ++ std.Io.Dir.path.sep_str ++ std.fmt.hex(rand_int); - const result_path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM"); - try b.cache_root.handle.createDirPath(io, tmp_dir_sub_path); - return result_path; -} - -fn deleteTmpPath(b: *Build, path: []const u8) void { - const io = b.graph.io; - std.Io.Dir.cwd().deleteTree(io, path) catch |err| { - std.log.warn("failed to delete {s}: {t}", .{ path, err }); - }; -} diff --git a/tools/check-exercises.zig b/tools/check-exercises.zig deleted file mode 100644 index 8bfeb34..0000000 --- a/tools/check-exercises.zig +++ /dev/null @@ -1,108 +0,0 @@ -const std = @import("std"); -const print = std.debug.print; -const string = []const u8; - -const cwd = std.fs.cwd(); -const Dir = std.fs.Dir; -const Allocator = std.mem.Allocator; - -const EXERCISES_PATH = "exercises"; -const HEALED_PATH = "patches/healed"; -const TEMP_PATH = "patches/healed/tmp"; -const PATCHES_PATH = "patches/patches"; - -// Heals all the exercises. -fn heal(alloc: Allocator) !void { - try cwd.makePath(HEALED_PATH); - - const org_path = try cwd.realpathAlloc(alloc, EXERCISES_PATH); - const patch_path = try cwd.realpathAlloc(alloc, PATCHES_PATH); - const healed_path = try cwd.realpathAlloc(alloc, HEALED_PATH); - - var idir = try cwd.openIterableDir(EXERCISES_PATH, Dir.OpenDirOptions{}); - defer idir.close(); - - var it = idir.iterate(); - while (try it.next()) |entry| { - - // create filenames - const healed_file = try concat(alloc, &.{ healed_path, "/", entry.name }); - const patch_file = try concat(alloc, &.{ patch_path, "/", try patch_name(alloc, entry.name) }); - - // patch file - const result = try std.ChildProcess.exec(.{ - .allocator = alloc, - .argv = &.{ "patch", "-i", patch_file, "-o", healed_file, entry.name }, - .cwd = org_path, - }); - - print("{s}", .{result.stderr}); - } -} - -// Yields all the healed exercises that are not correctly formatted. -fn check_healed(alloc: Allocator) !void { - try cwd.makePath(TEMP_PATH); - - const temp_path = try cwd.realpathAlloc(alloc, TEMP_PATH); - const healed_path = try cwd.realpathAlloc(alloc, HEALED_PATH); - - var idir = try cwd.openIterableDir(HEALED_PATH, Dir.OpenDirOptions{}); - defer idir.close(); - - var it = idir.iterate(); - while (try it.next()) |entry| { - - // Check the healed file - const result = try std.ChildProcess.exec(.{ - .allocator = alloc, - .argv = &.{ "zig", "fmt", "--check", entry.name }, - .cwd = healed_path, - }); - - // Is there something to fix? - if (result.stdout.len > 0) { - const temp_file = try concat(alloc, &.{ temp_path, "/", entry.name }); - const healed_file = try concat(alloc, &.{ healed_path, "/", entry.name }); - try std.fs.copyFileAbsolute(healed_file, temp_file, std.fs.CopyFileOptions{}); - - // Formats the temp file - _ = try std.ChildProcess.exec(.{ - .allocator = alloc, - .argv = &.{ "zig", "fmt", entry.name }, - .cwd = temp_path, - }); - - // Show the differences - const diff = try std.ChildProcess.exec(.{ - .allocator = alloc, - .argv = &.{ "diff", "-c", healed_file, entry.name }, - .cwd = temp_path, - }); - - print("{s}", .{diff.stdout}); - try std.fs.deleteFileAbsolute(temp_file); - } - } -} - -fn concat(alloc: Allocator, slices: []const string) !string { - const buf = try std.mem.concat(alloc, u8, slices); - return buf; -} - -fn patch_name(alloc: Allocator, path: string) !string { - var filename = path; - const index = std.mem.lastIndexOfScalar(u8, path, '.') orelse return path; - if (index > 0) filename = path[0..index]; - return try concat(alloc, &.{ filename, ".patch" }); -} - -pub fn main() !void { - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - try heal(alloc); - try check_healed(alloc); -}