From 9b1a9d74de0face51b08ad56d9a56170fa398532 Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Sun, 15 Dec 2024 07:14:22 -0700 Subject: [PATCH] rework tests --- build.zig | 210 +++++++++++++++++++++++++++++++++++++++++++++++ test.zig | 98 +--------------------- win32exelink.zig | 2 +- zigup.zig | 5 +- zigupwithenv.zig | 120 +++++++++++++++++++++++++++ 5 files changed, 337 insertions(+), 98 deletions(-) create mode 100644 zigupwithenv.zig diff --git a/build.zig b/build.zig index 84ebe7f..9cfd1e1 100644 --- a/build.zig +++ b/build.zig @@ -19,6 +19,9 @@ pub fn build(b: *std.Build) !void { }; const test_step = b.step("test", "test the executable"); + + addTests(b, target, zigup_exe_native, test_step); + { const exe = b.addExecutable(.{ .name = "test", @@ -219,3 +222,210 @@ fn makeCiArchiveStep( tar.step.dependOn(&exe_install.step); return &tar.step; } + +fn addTests( + b: *std.Build, + target: std.Build.ResolvedTarget, + zigup_exe: *std.Build.Step.Compile, + test_step: *std.Build.Step, +) void { + const zigupwithenv_exe = b.addExecutable(.{ + .name = "zigupwithenv", + .root_source_file = b.path("zigupwithenv.zig"), + .target = target, + }); + const tests: Tests = .{ + .b = b, + .test_step = test_step, + .zigup_exe = zigup_exe, + .zigupwithenv_exe = zigupwithenv_exe, + }; + + _ = tests.add(.{ + .name = "test-usage-h", + .argv = &.{"-h"}, + .check = .{ .expect_stderr_match = "Usage" }, + }); + _ = tests.add(.{ + .name = "test-usage-help", + .argv = &.{"--help"}, + .check = .{ .expect_stderr_match = "Usage" }, + }); + + _ = tests.add(.{ + .name = "test-fetch-index", + .argv = &.{"fetch-index"}, + .checks = &.{ + .{ .expect_stdout_match = "master" }, + .{ .expect_stdout_match = "version" }, + .{ .expect_stdout_match = "0.13.0" }, + }, + }); + + _ = tests.add(.{ + .name = "test-no-default", + .argv = &.{"default"}, + .check = .{ .expect_stdout_exact = "\n" }, + }); + _ = tests.add(.{ + .name = "test-default-master-not-fetched", + .argv = &.{ "default", "master" }, + .check = .{ .expect_stderr_match = "master has not been fetched" }, + }); + _ = tests.add(.{ + .name = "test-default-0.7.0-not-fetched", + .argv = &.{ "default", "0.7.0" }, + .check = .{ .expect_stderr_match = "error: compiler '0.7.0' is not installed\n" }, + }); + + const _7 = tests.add(.{ + .name = "test-0.7.0", + .argv = &.{"0.7.0"}, + //.check = .{ .expect_stderr_match = "error: compiler '0.7.0' is not installed\n" }, + }); + _ = tests.add(.{ + .name = "test-already-fetched-0.7.0", + .env = _7, + .argv = &.{ "fetch", "0.7.0" }, + .check = .{ .expect_stderr_match = "already installed" }, + }); + _ = tests.add(.{ + .name = "test-get-default-7", + .env = _7, + .argv = &.{"default"}, + .check = .{ .expect_stdout_exact = "0.7.0\n" }, + }); + _ = tests.add(.{ + .name = "test-get-default-7-no-path", + .env = _7, + .add_path = false, + .argv = &.{ "default", "0.7.0" }, + .check = .{ .expect_stderr_exact = " is not in PATH" }, + }); + + const _7_and_8 = tests.add(.{ + .name = "test-fetch-8", + .env = _7, + .argv = &.{ "fetch", "0.8.0" }, + }); + _ = tests.add(.{ + .name = "test-get-default-7-after-fetch-8", + .env = _7_and_8, + .argv = &.{"default"}, + .check = .{ .expect_stdout_exact = "0.7.0\n" }, + }); + _ = tests.add(.{ + .name = "test-already-fetched-8", + .env = _7_and_8, + .argv = &.{ "fetch", "0.8.0" }, + .check = .{ .expect_stderr_match = "already installed" }, + }); + const _7_and_default_8 = tests.add(.{ + .name = "test-set-default-8", + .env = _7_and_8, + .argv = &.{ "default", "0.8.0" }, + .check = .{ .expect_stdout_exact = "" }, + }); + _ = tests.add(.{ + .name = "test-7-after-default-8", + .env = _7_and_default_8, + .argv = &.{"0.7.0"}, + .check = .{ .expect_stdout_exact = "" }, + }); + + const master_7_and_8 = tests.add(.{ + .name = "test-master", + .env = _7_and_8, + .argv = &.{"master"}, + //.check = .{ .expect_stderr_match = "error: compiler '0.7.0' is not installed\n" }, + }); + + // const master_and_8 = tests.add(.{ + // .name = "test-master-and-8", + // .env = master, + // .argv = &.{"0.8.0"}, + // }); + + _ = tests.add(.{ + .name = "test-already-fetched-master", + .env = master_7_and_8, + .argv = &.{ "fetch", "master" }, + .check = .{ .expect_stderr_match = "already installed" }, + }); + + _ = tests.add(.{ + .name = "test-default-after-master", + .env = master_7_and_8, + .argv = &.{"default"}, + // master version could be anything so we won't check + }); + _ = tests.add(.{ + .name = "test-default-not-in-path", + .add_path = false, + .env = master_7_and_8, + .argv = &.{ "default", "master" }, + .check = .{ .expect_stderr_match = " is not in PATH" }, + }); + _ = tests.add(.{ + .name = "test-default-master", + .env = master_7_and_8, + .argv = &.{ "default", "master" }, + }); + + _ = tests.add(.{ + .name = "test-list", + .env = master_7_and_8, + .argv = &.{"list"}, + .checks = &.{ + .{ .expect_stdout_match = "0.7.0\n" }, + .{ .expect_stdout_match = "0.8.0\n" }, + }, + }); + + //_ = master_and_8; + //test_step.dependOn(&.step); +} + +const native_exe_ext = builtin.os.tag.exeFileExt(builtin.cpu.arch); + +const Tests = struct { + b: *std.Build, + test_step: *std.Build.Step, + zigup_exe: *std.Build.Step.Compile, + zigupwithenv_exe: *std.Build.Step.Compile, + + fn add( + tests: Tests, + opt: struct { + name: []const u8, + env: ?std.Build.LazyPath = null, + add_path: bool = true, + argv: []const []const u8, + check: ?std.Build.Step.Run.StdIo.Check = null, + checks: []const std.Build.Step.Run.StdIo.Check = &.{}, + }, + ) std.Build.LazyPath { + const b = tests.b; + const run = std.Build.Step.Run.create(b, b.fmt("run {s}", .{opt.name})); + run.addArtifactArg(tests.zigupwithenv_exe); + run.addArg(if (opt.add_path) "--with-path" else "--no-path"); + if (opt.env) |env| { + run.addDirectoryArg(env); + } else { + run.addArg("--no-input-environment"); + } + const out_env = run.addOutputDirectoryArg("env"); + run.addFileArg(tests.zigup_exe.getEmittedBin()); + run.addArgs(opt.argv); + if (opt.check) |check| { + run.addCheck(check); + } + for (opt.checks) |check| { + run.addCheck(check); + } + + b.step(opt.name, "").dependOn(&run.step); + tests.test_step.dependOn(&run.step); + return out_env; + } +}; diff --git a/test.zig b/test.zig index 45cceb7..d6a5d4f 100644 --- a/test.zig +++ b/test.zig @@ -92,72 +92,7 @@ pub fn main() !u8 { const original_path_env = path_env_ptr.*; setPathEnv(try std.mem.concat(allocator, u8, &.{ bin_dir, path_env_sep, original_path_env })); - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "default", "master" }); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "master has not been fetched")); - } - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"-h"}); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "Usage")); - } - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"--help"}); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "Usage")); - } - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"default"}); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try passOrDumpAndThrow(result); - try testing.expect(std.mem.eql(u8, result.stdout, "\n")); - } - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"fetch-index"}); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try passOrDumpAndThrow(result); - try testing.expect(std.mem.containsAtLeast(u8, result.stdout, 1, "master")); - } - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "default", "0.7.0" }); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - dumpExecResult(result); - switch (result.term) { - .Exited => |code| try testing.expectEqual(@as(u8, 1), code), - else => |term| std.debug.panic("unexpected exit {}", .{term}), - } - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "error: compiler '0.7.0' is not installed\n")); - } try runNoCapture(zigup_args ++ &[_][]const u8{"0.7.0"}); - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"default"}); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try passOrDumpAndThrow(result); - dumpExecResult(result); - try testing.expect(std.mem.eql(u8, result.stdout, "0.7.0\n")); - } // verify we print a nice error message if we can't update the symlink // because it's a directory @@ -194,39 +129,10 @@ pub fn main() !u8 { try std.fs.cwd().deleteDir(zig_exe_link); } - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "fetch", "0.7.0" }); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try passOrDumpAndThrow(result); - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "already installed")); - } try runNoCapture(zigup_args ++ &[_][]const u8{"master"}); try runNoCapture(zigup_args ++ &[_][]const u8{"0.8.0"}); - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"default"}); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try passOrDumpAndThrow(result); - dumpExecResult(result); - try testing.expect(std.mem.eql(u8, result.stdout, "0.8.0\n")); - } - { - const save_path_env = path_env_ptr.*; - defer setPathEnv(save_path_env); - setPathEnv(""); - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "default", "master" }); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, " is not in PATH")); - } - try runNoCapture(zigup_args ++ &[_][]const u8{ "default", "master" }); + + //try runNoCapture(zigup_args ++ &[_][]const u8{ "default", "master" }); { const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"list"}); defer { diff --git a/win32exelink.zig b/win32exelink.zig index ebc382a..dbc2837 100644 --- a/win32exelink.zig +++ b/win32exelink.zig @@ -46,7 +46,7 @@ pub fn main() !u8 { global.child = std.process.Child.init(args, global.arena); if (0 == win32.SetConsoleCtrlHandler(consoleCtrlHandler, 1)) { - log.err("SetConsoleCtrlHandler failed, error={}", .{win32.GetLastError()}); + log.err("SetConsoleCtrlHandler failed, error={}", .{@intFromEnum(win32.GetLastError())}); return 0xff; // fail } diff --git a/zigup.zig b/zigup.zig index dd4235e..c638d56 100644 --- a/zigup.zig +++ b/zigup.zig @@ -736,7 +736,10 @@ fn setDefaultCompiler(allocator: Allocator, compiler_dir: []const u8, exist_veri const path_link = try makeZigPathLinkString(allocator); defer allocator.free(path_link); - const link_target = try std.fs.path.join(allocator, &[_][]const u8{ compiler_dir, "files", comptime "zig" ++ builtin.target.exeFileExt() }); + const link_target = try std.fs.path.join( + allocator, + &[_][]const u8{ compiler_dir, "files", comptime "zig" ++ builtin.target.exeFileExt() }, + ); defer allocator.free(link_target); if (builtin.os.tag == .windows) { try createExeLink(link_target, path_link); diff --git a/zigupwithenv.zig b/zigupwithenv.zig new file mode 100644 index 0000000..5b92ec2 --- /dev/null +++ b/zigupwithenv.zig @@ -0,0 +1,120 @@ +const builtin = @import("builtin"); +const std = @import("std"); + +const fixdeletetree = @import("fixdeletetree.zig"); + +const exe_ext = builtin.os.tag.exeFileExt(builtin.cpu.arch); + +pub fn main() !void { + var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); + const arena = arena_instance.allocator(); + const all_args = try std.process.argsAlloc(arena); + if (all_args.len < 5) @panic("not enough cmdline args"); + + const add_path_option = all_args[1]; + const in_env_dir = all_args[2]; + const out_env_dir = all_args[3]; + const zigup_exe = all_args[4]; + const zigup_args = all_args[5..]; + + const add_path = blk: { + if (std.mem.eql(u8, add_path_option, "--with-path")) break :blk true; + if (std.mem.eql(u8, add_path_option, "--no-path")) break :blk false; + std.log.err("expected '--with-path' or '--no-path' but got '{s}'", .{add_path_option}); + std.process.exit(0xff); + }; + + try fixdeletetree.deleteTree(std.fs.cwd(), out_env_dir); + try std.fs.cwd().makeDir(out_env_dir); + + const install_dir = try std.fs.path.join(arena, &.{ out_env_dir, "install" }); + + if (std.mem.eql(u8, in_env_dir, "--no-input-environment")) { + try std.fs.cwd().makeDir(install_dir); + } else { + try copyEnvDir(arena, in_env_dir, out_env_dir, in_env_dir, out_env_dir); + } + + var argv = std.ArrayList([]const u8).init(arena); + try argv.append(zigup_exe); + try argv.append("--path-link"); + try argv.append(try std.fs.path.join(arena, &.{ out_env_dir, "zig" ++ exe_ext })); + try argv.append("--install-dir"); + try argv.append(install_dir); + try argv.appendSlice(zigup_args); + + var child = std.process.Child.init(argv.items, arena); + + if (add_path) { + var env_map = try std.process.getEnvMap(arena); + // make sure the directory with our path-link comes first in PATH + var new_path = std.ArrayList(u8).init(arena); + try new_path.appendSlice(out_env_dir); + try new_path.append(std.fs.path.delimiter); + if (env_map.get("PATH")) |path| { + try new_path.appendSlice(path); + } + try env_map.put("PATH", new_path.items); + child.env_map = &env_map; + } + + try child.spawn(); + const result = try child.wait(); + switch (result) { + .Exited => |c| std.process.exit(c), + else => |sig| { + std.log.err("zigup terminated from '{s}' with {}", .{ @tagName(result), sig }); + std.process.exit(0xff); + }, + } +} + +fn copyEnvDir( + allocator: std.mem.Allocator, + in_root: []const u8, + out_root: []const u8, + in_path: []const u8, + out_path: []const u8, +) !void { + var in_dir = try std.fs.cwd().openDir(in_path, .{ .iterate = true }); + defer in_dir.close(); + + var it = in_dir.iterate(); + while (try it.next()) |entry| { + const in_sub_path = try std.fs.path.join(allocator, &.{ in_path, entry.name }); + defer allocator.free(in_sub_path); + const out_sub_path = try std.fs.path.join(allocator, &.{ out_path, entry.name }); + defer allocator.free(out_sub_path); + switch (entry.kind) { + .directory => { + try std.fs.cwd().makeDir(out_sub_path); + try copyEnvDir(allocator, in_root, out_root, in_sub_path, out_sub_path); + }, + .file => try std.fs.cwd().copyFile(in_sub_path, std.fs.cwd(), out_sub_path, .{}), + .sym_link => { + var target_buf: [std.fs.max_path_bytes]u8 = undefined; + const in_target = try std.fs.cwd().readLink(in_sub_path, &target_buf); + var out_target_buf: [std.fs.max_path_bytes]u8 = undefined; + const out_target = blk: { + if (std.fs.path.isAbsolute(in_target)) { + if (!std.mem.startsWith(u8, in_target, in_root)) std.debug.panic( + "expected symlink target to start with '{s}' but got '{s}'", + .{ in_root, in_target }, + ); + break :blk try std.fmt.bufPrint( + &out_target_buf, + "{s}{s}", + .{ out_root, in_target[in_root.len..] }, + ); + } + break :blk in_target; + }; + + if (builtin.os.tag == .windows) @panic( + "we got a symlink on windows?", + ) else try std.posix.symlink(out_target, out_sub_path); + }, + else => std.debug.panic("copy {}", .{entry}), + } + } +}