From b62cd36d7b285a4aa50ec4b50cf826d1c584be77 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 | 491 +++++++++++++++++++++++++++++++++++++++++++++-- runtest.zig | 166 ++++++++++++++++ test.zig | 449 ------------------------------------------- win32exelink.zig | 2 +- zigup.zig | 5 +- 5 files changed, 650 insertions(+), 463 deletions(-) create mode 100644 runtest.zig delete mode 100644 test.zig diff --git a/build.zig b/build.zig index 84ebe7f..a9c8be4 100644 --- a/build.zig +++ b/build.zig @@ -19,18 +19,8 @@ pub fn build(b: *std.Build) !void { }; const test_step = b.step("test", "test the executable"); - { - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = b.path("test.zig"), - .target = target, - .optimize = optimize, - }); - const run_cmd = b.addRunArtifact(exe); - run_cmd.addArtifactArg(zigup_exe_native); - run_cmd.addDirectoryArg(b.path("scratch/native")); - test_step.dependOn(&run_cmd.step); - } + + addTests(b, target, zigup_exe_native, test_step); const unzip_step = b.step( "unzip", @@ -219,3 +209,480 @@ 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 runtest_exe = b.addExecutable(.{ + .name = "runtest", + .root_source_file = b.path("runtest.zig"), + .target = target, + }); + const tests: Tests = .{ + .b = b, + .test_step = test_step, + .zigup_exe = zigup_exe, + .runtest_exe = runtest_exe, + }; + + tests.addWithClean(.{ + .name = "test-usage-h", + .argv = &.{"-h"}, + .check = .{ .expect_stderr_match = "Usage" }, + }); + tests.addWithClean(.{ + .name = "test-usage-help", + .argv = &.{"--help"}, + .check = .{ .expect_stderr_match = "Usage" }, + }); + + tests.addWithClean(.{ + .name = "test-fetch-index", + .argv = &.{"fetch-index"}, + .checks = &.{ + .{ .expect_stdout_match = "master" }, + .{ .expect_stdout_match = "version" }, + .{ .expect_stdout_match = "0.13.0" }, + }, + }); + + tests.addWithClean(.{ + .name = "test-no-default", + .argv = &.{"default"}, + .check = .{ .expect_stdout_exact = "\n" }, + }); + tests.addWithClean(.{ + .name = "test-default-master-not-fetched", + .argv = &.{ "default", "master" }, + .check = .{ .expect_stderr_match = "master has not been fetched" }, + }); + tests.addWithClean(.{ + .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" }, + }); + + tests.addWithClean(.{ + .name = "test-bad-version", + .argv = &.{ "THIS_ZIG_VERSION_DOES_NOT_EXIT" }, + .checks = &.{ + .{ .expect_stderr_match = "error: download '" }, + .{ .expect_stderr_match = "' failed: " }, + }, + }); + + // NOTE: this test will eventually break when these builds are cleaned up, + // we should support downloading from bazel and use that instead since + // it should be more permanent + tests.addWithClean(.{ + .name = "test-dev-version", + .argv = &.{ "0.14.0-dev.2465+70de2f3a7" }, + .check = .{ .expect_stdout_exact = "" }, + }); + + const _7 = tests.add(.{ + .name = "test-7", + .argv = &.{"0.7.0"}, + .check = .{ .expect_stdout_match = "" }, + }); + tests.addWithClean(.{ + .name = "test-already-fetched-7", + .env = _7, + .argv = &.{ "fetch", "0.7.0" }, + .check = .{ .expect_stderr_match = "already installed" }, + }); + tests.addWithClean(.{ + .name = "test-get-default-7", + .env = _7, + .argv = &.{"default"}, + .check = .{ .expect_stdout_exact = "0.7.0\n" }, + }); + tests.addWithClean(.{ + .name = "test-get-default-7-no-path", + .env = _7, + .add_path = false, + .argv = &.{ "default", "0.7.0" }, + .check = .{ .expect_stderr_match = " is not in PATH" }, + }); + + // verify we print a nice error message if we can't update the symlink + // because it's a directory + tests.addWithClean(.{ + .name = "test-get-default-7-path-link-is-directory", + .env = _7, + .setup_option = "path-link-is-directory", + .argv = &.{ "default", "0.7.0" }, + .checks = switch (builtin.os.tag) { + .windows => &.{ + .{ .expect_stderr_match = "unable to create the exe link, the path '" }, + .{ .expect_stderr_match = "' is a directory" }, + }, + else => &.{ + .{ .expect_stderr_match = "unable to update/overwrite the 'zig' PATH symlink, the file '" }, + .{ .expect_stderr_match = "' already exists and is not a symlink" }, + }, + }, + }); + + const _7_and_8 = tests.add(.{ + .name = "test-fetch-8", + .env = _7, + .argv = &.{ "fetch", "0.8.0" }, + }); + tests.addWithClean(.{ + .name = "test-get-default-7-after-fetch-8", + .env = _7_and_8, + .argv = &.{"default"}, + .check = .{ .expect_stdout_exact = "0.7.0\n" }, + }); + tests.addWithClean(.{ + .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.addWithClean(.{ + .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_stdout_exact = "" }, + }); + tests.addWithClean(.{ + .name = "test-already-fetched-master", + .env = master_7_and_8, + .argv = &.{ "fetch", "master" }, + .check = .{ .expect_stderr_match = "already installed" }, + }); + + tests.addWithClean(.{ + .name = "test-default-after-master", + .env = master_7_and_8, + .argv = &.{"default"}, + // master version could be anything so we won't check + }); + tests.addWithClean(.{ + .name = "test-default-master", + .env = master_7_and_8, + .argv = &.{ "default", "master" }, + }); + tests.addWithClean(.{ + .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" }, + }); + + // verify that we get an error if there is another compiler in the path + tests.addWithClean(.{ + .name = "test-default-master-with-another-zig", + .setup_option = "another-zig", + .env = master_7_and_8, + .argv = &.{ "default", "master" }, + .checks = &.{ + .{ .expect_stderr_match = "error: zig compiler '" }, + .{ .expect_stderr_match = "' is higher priority in PATH than the path-link '" }, + }, + }); + + { + const default8 = tests.add(.{ + .name = "test-default8-with-another-zig", + .setup_option = "another-zig", + .env = master_7_and_8, + .argv = &.{ "default", "0.8.0" }, + .checks = &.{ + .{ .expect_stderr_match = "error: zig compiler '" }, + .{ .expect_stderr_match = "' is higher priority in PATH than the path-link '" }, + }, + }); + // default compiler should still be set + tests.addWithClean(.{ + .name = "test-default8-even-with-another-zig", + .env = default8, + .argv = &.{ "default" }, + .check = .{ .expect_stdout_exact = "0.8.0\n" }, + }); + } + + tests.addWithClean(.{ + .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" }, + }, + }); + + { + const default_8 = tests.add(.{ + .name = "test-8-with-master", + .env = master_7_and_8, + .argv = &.{"0.8.0"}, + .check = .{ .expect_stdout_exact = "" }, + }); + tests.addWithClean(.{ + .name = "test-default_8", + .env = default_8, + .argv = &.{"default"}, + .check = .{ .expect_stdout_exact = "0.8.0\n" }, + }); + } + + tests.addWithClean(.{ + .name = "test-run-8", + .env = master_7_and_8, + .argv = &.{ "run", "0.8.0", "version" }, + .check = .{ .expect_stdout_exact = "0.8.0\n" }, + }); + tests.addWithClean(.{ + .name = "test-run-doesnotexist", + .env = master_7_and_8, + .argv = &.{ "run", "doesnotexist", "version" }, + .check = .{ .expect_stderr_exact = "error: compiler 'doesnotexist' does not exist, fetch it first with: zigup fetch doesnotexist\n" }, + }); + + + tests.addWithClean(.{ + .name = "test-clean-default-master", + .env = master_7_and_8, + .argv = &.{"clean"}, + .checks = &.{ + .{ .expect_stderr_match = "keeping '" }, + .{ .expect_stderr_match = "' (is default compiler)\n" }, + .{ .expect_stderr_match = "deleting '" }, + .{ .expect_stderr_match = "0.7.0'\n" }, + .{ .expect_stderr_match = "0.8.0'\n" }, + .{ .expect_stdout_exact = "" }, + }, + }); + + { + const default7 = tests.add(.{ + .name = "test-set-default-7", + .env = master_7_and_8, + .argv = &.{ "default", "0.7.0" }, + .checks = &.{ + .{ .expect_stdout_exact = "" }, + }, + }); + tests.addWithClean(.{ + .name = "test-clean-default-7", + .env = default7, + .argv = &.{"clean"}, + .checks = &.{ + .{ .expect_stderr_match = "keeping '" }, + .{ .expect_stderr_match = "' (it is master)\n" }, + .{ .expect_stderr_match = "keeping '0.7.0' (is default compiler)\n" }, + .{ .expect_stderr_match = "deleting '" }, + .{ .expect_stderr_match = "0.8.0'\n" }, + .{ .expect_stdout_exact = "" }, + }, + }); + } + + { + const keep8 = tests.add(.{ + .name = "test-keep8", + .env = master_7_and_8, + .argv = &.{ "keep", "0.8.0" }, + .check = .{ .expect_stdout_exact = "" }, + }); + + { + const keep8_default_7 = tests.add(.{ + .name = "test-set-default-7-keep8", + .env = keep8, + .argv = &.{ "default", "0.7.0" }, + .checks = &.{ + .{ .expect_stdout_exact = "" }, + }, + }); + tests.addWithClean(.{ + .name = "test-clean-default-7-keep8", + .env = keep8_default_7, + .argv = &.{"clean"}, + .checks = &.{ + .{ .expect_stderr_match = "keeping '" }, + .{ .expect_stderr_match = "' (it is master)\n" }, + .{ .expect_stderr_match = "keeping '0.7.0' (is default compiler)\n" }, + .{ .expect_stderr_match = "keeping '0.8.0' (has keep file)\n" }, + .{ .expect_stdout_exact = "" }, + }, + }); + tests.addWithClean(.{ + .name = "test-clean-master", + .env = keep8_default_7, + .argv = &.{"clean", "master"}, + .checks = &.{ + .{ .expect_stderr_match = "deleting '" }, + .{ .expect_stderr_match = "master'\n" }, + .{ .expect_stdout_exact = "" }, + }, + }); + } + + const after_clean = tests.add(.{ + .name = "test-clean-keep8", + .env = keep8, + .argv = &.{"clean"}, + .checks = &.{ + .{ .expect_stderr_match = "keeping '" }, + .{ .expect_stderr_match = "' (is default compiler)\n" }, + .{ .expect_stderr_match = "keeping '0.8.0' (has keep file)\n" }, + .{ .expect_stderr_match = "deleting '" }, + .{ .expect_stderr_match = "0.7.0'\n" }, + }, + }); + + + tests.addWithClean(.{ + .name = "test-set-default-7-after-clean", + .env = after_clean, + .argv = &.{ "default", "0.7.0" }, + .checks = &.{ + .{ .expect_stderr_match = "error: compiler '0.7.0' is not installed\n" }, + }, + }); + + const default8 = tests.add(.{ + .name = "test-set-default-8-after-clean", + .env = after_clean, + .argv = &.{ "default", "0.8.0" }, + .checks = &.{ + .{ .expect_stdout_exact = "" }, + }, + }); + + + tests.addWithClean(.{ + .name = "test-clean8-as-default", + .env = default8, + .argv = &.{ "clean", "0.8.0" }, + .checks = &.{ + .{ .expect_stderr_match = "error: cannot clean '0.8.0' (is default compiler)\n" }, + }, + }); + + const after_clean8 = tests.add(.{ + .name = "test-clean8", + .env = after_clean, + .argv = &.{ "clean", "0.8.0" }, + .checks = &.{ + .{ .expect_stderr_match = "deleting '" }, + .{ .expect_stderr_match = "0.8.0'\n" }, + .{ .expect_stdout_exact = "" }, + }, + }); + tests.addWithClean(.{ + .name = "test-clean-after-clean8", + .env = after_clean8, + .argv = &.{"clean"}, + .checks = &.{ + .{ .expect_stderr_match = "keeping '" }, + .{ .expect_stderr_match = "' (is default compiler)\n" }, + .{ .expect_stdout_exact = "" }, + }, + }); + } +} + +const native_exe_ext = builtin.os.tag.exeFileExt(builtin.cpu.arch); + +const TestOptions = struct { + name: []const u8, + add_path: bool = true, + env: ?std.Build.LazyPath = null, + setup_option: []const u8 = "no-extra-setup", + argv: []const []const u8, + check: ?std.Build.Step.Run.StdIo.Check = null, + checks: []const std.Build.Step.Run.StdIo.Check = &.{}, +}; + +const Tests = struct { + b: *std.Build, + test_step: *std.Build.Step, + zigup_exe: *std.Build.Step.Compile, + runtest_exe: *std.Build.Step.Compile, + + fn addWithClean(tests: Tests, opt: TestOptions) void { + _ = tests.addCommon(opt, .yes_clean); + } + fn add(tests: Tests, opt: TestOptions) std.Build.LazyPath { + return tests.addCommon(opt, .no_clean); + } + fn addCommon(tests: Tests, opt: TestOptions, clean_opt: enum { no_clean, yes_clean }) std.Build.LazyPath { + const b = tests.b; + const run = std.Build.Step.Run.create(b, b.fmt("run {s}", .{opt.name})); + run.addArtifactArg(tests.runtest_exe); + run.addArg(opt.name); + 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(opt.name); + run.addArg(opt.setup_option); + run.addFileArg(tests.zigup_exe.getEmittedBin()); + run.addArgs(opt.argv); + if (opt.check) |check| { + run.addCheck(check); + } + for (opt.checks) |check| { + run.addCheck(check); + } + + const test_step: *std.Build.Step = switch (clean_opt) { + .no_clean => &run.step, + .yes_clean => &CleanDir.create(tests.b, out_env).step, + }; + + b.step(opt.name, "").dependOn(test_step); + tests.test_step.dependOn(test_step); + + return out_env; + } +}; + +const CleanDir = struct { + step: std.Build.Step, + dir_path: std.Build.LazyPath, + pub fn create(owner: *std.Build, path: std.Build.LazyPath) *CleanDir { + const clean_dir = owner.allocator.create(CleanDir) catch @panic("OOM"); + clean_dir.* = .{ + .step = std.Build.Step.init(.{ + .id = .custom, + .name = owner.fmt("CleanDir {s}", .{path.getDisplayName()}), + .owner = owner, + .makeFn = make, + }), + .dir_path = path.dupe(owner), + }; + path.addStepDependencies(&clean_dir.step); + return clean_dir; + } + fn make(step: *std.Build.Step, prog_node: std.Progress.Node) !void { + _ = prog_node; + const b = step.owner; + const clean_dir: *CleanDir = @fieldParentPtr("step", step); + try b.build_root.handle.deleteTree(clean_dir.dir_path.getPath(b)); + } +}; diff --git a/runtest.zig b/runtest.zig new file mode 100644 index 0000000..a9dc9cf --- /dev/null +++ b/runtest.zig @@ -0,0 +1,166 @@ +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 < 7) @panic("not enough cmdline args"); + + const test_name = all_args[1]; + const add_path_option = all_args[2]; + const in_env_dir = all_args[3]; + const out_env_dir = all_args[4]; + const setup_option = all_args[5]; + const zigup_exe = all_args[6]; + const zigup_args = all_args[7..]; + + 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); + + // make a file named after the test so we can find this directory in the cache + _ = test_name; + // { + // const test_marker_file = try std.fs.path.join(arena, &.{ out_env_dir, test_name}); + // defer arena.free(test_marker_file); + // var file = try std.fs.cwd().createFile(test_marker_file, .{}); + // defer file.close(); + // try file.writer().print("this file marks this directory as the output for test: {s}\n", .{test_name}); + // } + + const path_link = try std.fs.path.join(arena, &.{ out_env_dir, "zig" ++ exe_ext }); + 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 maybe_second_bin_dir: ?[]const u8 = null; + + if (std.mem.eql(u8, setup_option, "no-extra-setup")) { + // nothing extra to setup + } else if (std.mem.eql(u8, setup_option, "path-link-is-directory")) { + if (std.fs.cwd().access(path_link, .{})) { + try std.fs.cwd().deleteFile(path_link); + } else |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + } + try std.fs.cwd().makeDir(path_link); + } else if (std.mem.eql(u8, setup_option, "another-zig")) { + maybe_second_bin_dir = try std.fs.path.join(arena, &.{ out_env_dir, "bin2" }); + try std.fs.cwd().makeDir(maybe_second_bin_dir.?); + + const fake_zig = try std.fs.path.join(arena, &.{ + maybe_second_bin_dir.?, + "zig" ++ comptime builtin.target.exeFileExt(), + }); + defer arena.free(fake_zig); + var file = try std.fs.cwd().createFile(fake_zig, .{}); + defer file.close(); + try file.writer().writeAll("a fake executable"); + } else { + std.log.err("unknown setup option '{s}'", .{setup_option}); + std.process.exit(0xff); + } + + var argv = std.ArrayList([]const u8).init(arena); + try argv.append(zigup_exe); + try argv.append("--path-link"); + try argv.append(path_link); + 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); + if (maybe_second_bin_dir) |second_bin_dir| { + try new_path.appendSlice(second_bin_dir); + try new_path.append(std.fs.path.delimiter); + } + 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; + } else if (maybe_second_bin_dir) |_| @panic("invalid config"); + + 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}), + } + } +} diff --git a/test.zig b/test.zig deleted file mode 100644 index 45cceb7..0000000 --- a/test.zig +++ /dev/null @@ -1,449 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const testing = std.testing; - -const sep = std.fs.path.sep_str; -const path_env_sep = if (builtin.os.tag == .windows) ";" else ":"; - -const fixdeletetree = @import("fixdeletetree.zig"); - -var child_env_map: std.process.EnvMap = undefined; -var path_env_ptr: *[]const u8 = undefined; -fn setPathEnv(new_path: []const u8) void { - path_env_ptr.* = new_path; - std.log.info("PATH={s}", .{new_path}); -} - -// For some odd reason, the "zig version" output is different on macos -const expected_zig_version_0_7_0 = if (builtin.os.tag == .macos) "0.7.0+9af53f8e0" else "0.7.0"; - -pub fn main() !u8 { - var allocator_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - //defer allocator_instance.deinit(); - const allocator = allocator_instance.allocator(); - - const all_cmdline_args = try std.process.argsAlloc(allocator); - if (all_cmdline_args.len <= 1) { - try std.io.getStdErr().writer().print("Usage: test ZIGUP_EXE TEST_DIR\n", .{}); - return 0xff; - } - const cmdline_args = all_cmdline_args[1..]; - if (cmdline_args.len != 2) { - std.log.err("expected 1 cmdline arg but got {}", .{cmdline_args.len}); - return 0xff; - } - - const zigup_src_exe = cmdline_args[0]; - const test_dir = cmdline_args[1]; - std.log.info("run zigup tests", .{}); - std.log.info("zigup exe '{s}'", .{zigup_src_exe}); - std.log.info("test directory '{s}'", .{test_dir}); - - if (!std.fs.path.isAbsolute(test_dir)) { - std.log.err("currently the test requires an absolute test directory path", .{}); - return 0xff; - } - - try fixdeletetree.deleteTree(std.fs.cwd(), test_dir); - try std.fs.cwd().makePath(test_dir); - const bin_dir = try std.fs.path.join(allocator, &.{ test_dir, "bin" }); - try std.fs.cwd().makeDir(bin_dir); - const install_sub_path = if (builtin.os.tag == .windows) "bin\\zig" else "install"; - const install_dir = try std.fs.path.join(allocator, &.{ test_dir, install_sub_path }); - try std.fs.cwd().makeDir(install_dir); - - const zigup = try std.fs.path.join(allocator, &.{ - test_dir, - "bin", - "zigup" ++ comptime builtin.target.exeFileExt(), - }); - try std.fs.cwd().copyFile( - zigup_src_exe, - std.fs.cwd(), - zigup, - .{}, - ); - if (builtin.os.tag == .windows) { - const zigup_src_pdb = try std.mem.concat(allocator, u8, &.{ - zigup_src_exe[0 .. zigup_src_exe.len - 4], - ".pdb", - }); - defer allocator.free(zigup_src_pdb); - const zigup_pdb = try std.fs.path.join(allocator, &.{ test_dir, "bin\\zigup.pdb" }); - defer allocator.free(zigup_pdb); - try std.fs.cwd().copyFile(zigup_src_pdb, std.fs.cwd(), zigup_pdb, .{}); - } - - const install_args = if (builtin.os.tag == .windows) [_][]const u8{} else [_][]const u8{ - "--install-dir", install_dir, - }; - const zigup_args = &[_][]const u8{zigup} ++ install_args; - - const path_link = try std.fs.path.join(allocator, &.{ bin_dir, comptime "zig" ++ builtin.target.exeFileExt() }); - defer allocator.free(path_link); - - // add our scratch/bin directory to PATH - child_env_map = try std.process.getEnvMap(allocator); - path_env_ptr = child_env_map.getPtr("PATH") orelse { - std.log.err("the PATH environment variable does not exist?", .{}); - return 1; - }; - - 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 - { - const zig_exe_link = try std.fs.path.join(allocator, &.{ bin_dir, "zig" ++ comptime builtin.target.exeFileExt() }); - defer allocator.free(zig_exe_link); - - if (std.fs.cwd().access(zig_exe_link, .{})) { - try std.fs.cwd().deleteFile(zig_exe_link); - } else |err| switch (err) { - error.FileNotFound => {}, - else => |e| return e, - } - try std.fs.cwd().makeDir(zig_exe_link); - - 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}), - } - if (builtin.os.tag == .windows) { - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "unable to create the exe link, the path '")); - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "' is a directory")); - } else { - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "unable to update/overwrite the 'zig' PATH symlink, the file '")); - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "' already exists and is not a symlink")); - } - - 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" }); - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"list"}); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try passOrDumpAndThrow(result); - try testing.expect(std.mem.containsAtLeast(u8, result.stdout, 1, "0.7.0")); - try testing.expect(std.mem.containsAtLeast(u8, result.stdout, 1, "0.8.0")); - } - try runNoCapture(zigup_args ++ &[_][]const u8{ "default", "0.7.0" }); - try testing.expectEqual(@as(u32, 3), try getCompilerCount(install_dir)); - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "run", "0.8.0", "version" }); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try testing.expectEqualSlices(u8, "0.8.0\n", result.stdout); - } - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "run", "doesnotexist", "version" }); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try testing.expectEqualSlices(u8, "error: compiler 'doesnotexist' does not exist, fetch it first with: zigup fetch doesnotexist\n", result.stderr); - } - try runNoCapture(zigup_args ++ &[_][]const u8{ "keep", "0.8.0" }); - // doesn't delete anything because we have keepfile and master doens't get deleted - try runNoCapture(zigup_args ++ &[_][]const u8{"clean"}); - try testing.expectEqual(@as(u32, 3), try getCompilerCount(install_dir)); - - // Just make a directory to trick zigup into thinking there is another compiler so we don't have to wait for it to download/install - try makeDir(test_dir, install_sub_path ++ sep ++ "0.9.0"); - try testing.expectEqual(@as(u32, 4), try getCompilerCount(install_dir)); - try runNoCapture(zigup_args ++ &[_][]const u8{"clean"}); - try testing.expectEqual(@as(u32, 3), try getCompilerCount(install_dir)); - - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "clean", "0.8.0" }); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try passOrDumpAndThrow(result); - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "deleting ")); - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "0.8.0")); - } - try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir)); - - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"clean"}); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try passOrDumpAndThrow(result); - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "it is master")); - } - try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir)); - - try runNoCapture(zigup_args ++ &[_][]const u8{"master"}); - try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir)); - - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"DOESNOTEXST"}); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "download")); - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "failed")); - } - try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir)); - - // verify that we get an error if there is another compiler in the path - { - const bin2_dir = try std.fs.path.join(allocator, &.{ test_dir, "bin2" }); - defer allocator.free(bin2_dir); - try std.fs.cwd().makeDir(bin2_dir); - - const previous_path = path_env_ptr.*; - - { - const fake_zig = try std.fs.path.join(allocator, &.{ - bin2_dir, - "zig" ++ comptime builtin.target.exeFileExt(), - }); - defer allocator.free(fake_zig); - var file = try std.fs.cwd().createFile(fake_zig, .{}); - defer file.close(); - try file.writer().writeAll("a fake executable"); - } - - setPathEnv(try std.mem.concat(allocator, u8, &.{ bin2_dir, path_env_sep, previous_path })); - defer setPathEnv(previous_path); - - // verify zig isn't currently on 0.7.0 before we set it as the default - try checkZigVersion(allocator, path_link, expected_zig_version_0_7_0, .not_equal); - - { - const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "default", "0.7.0" }); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - std.log.info("output: {s}", .{result.stderr}); - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "error: zig compiler '")); - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "' is higher priority in PATH than the path-link '")); - } - - // the path link should still be updated even though it's in a lower path priority. - // Verify zig points to the new defult version we just set. - try checkZigVersion(allocator, path_link, expected_zig_version_0_7_0, .equal); - } - - // verify a dev build - // NOTE: this test will eventually break when these builds are cleaned up, - // we should support downloading from bazel and use that instead since - // it should be more permanent - try runNoCapture(zigup_args ++ &[_][]const u8{"0.14.0-dev.2465+70de2f3a7"}); - - std.log.info("Success", .{}); - return 0; -} - -fn makeDir(dir_path: []const u8, sub_path: []const u8) !void { - var dir = try std.fs.cwd().openDir(dir_path, .{}); - defer dir.close(); - try dir.makeDir(sub_path); -} - -fn checkZigVersion(allocator: std.mem.Allocator, zig: []const u8, compare: []const u8, want_equal: enum { not_equal, equal }) !void { - const result = try runCaptureOuts(allocator, &[_][]const u8{ zig, "version" }); - defer { - allocator.free(result.stdout); - allocator.free(result.stderr); - } - try passOrDumpAndThrow(result); - - const actual_version = std.mem.trimRight(u8, result.stdout, "\r\n"); - const actual_equal = std.mem.eql(u8, compare, actual_version); - const expected_equal = switch (want_equal) { - .not_equal => false, - .equal => true, - }; - if (expected_equal != actual_equal) { - const prefix: []const u8 = if (expected_equal) "" else " NOT"; - std.log.info("expected zig version to{s} be '{s}', but is '{s}'", .{ prefix, compare, actual_version }); - return error.TestUnexpectedResult; - } -} - -fn getCompilerCount(install_dir: []const u8) !u32 { - var dir = try std.fs.cwd().openDir(install_dir, .{ .iterate = true }); - defer dir.close(); - var it = dir.iterate(); - var count: u32 = 0; - while (try it.next()) |entry| { - if (entry.kind == .directory) { - count += 1; - } else { - if (builtin.os.tag == .windows) { - try testing.expect(entry.kind == .file); - } else { - try testing.expect(entry.kind == .sym_link); - } - } - } - return count; -} - -fn trailNl(s: []const u8) []const u8 { - return if (s.len == 0 or s[s.len - 1] != '\n') "\n" else ""; -} - -fn dumpExecResult(result: std.process.Child.RunResult) void { - if (result.stdout.len > 0) { - std.debug.print("--- STDOUT ---\n{s}{s}--------------\n", .{ result.stdout, trailNl(result.stdout) }); - } - if (result.stderr.len > 0) { - std.debug.print("--- STDERR ---\n{s}{s}--------------\n", .{ result.stderr, trailNl(result.stderr) }); - } -} - -fn runNoCapture(argv: []const []const u8) !void { - var arena_store = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_store.deinit(); - const result = try runCaptureOuts(arena_store.allocator(), argv); - dumpExecResult(result); - try passOrThrow(result.term); -} -fn runCaptureOuts(allocator: std.mem.Allocator, argv: []const []const u8) !std.process.Child.RunResult { - { - const cmd = try std.mem.join(allocator, " ", argv); - defer allocator.free(cmd); - std.log.info("RUN: {s}", .{cmd}); - } - return try std.process.Child.run(.{ .allocator = allocator, .argv = argv, .env_map = &child_env_map }); -} -fn passOrThrow(term: std.process.Child.Term) error{ChildProcessFailed}!void { - if (!execResultPassed(term)) { - std.log.err("child process failed with {}", .{term}); - return error.ChildProcessFailed; - } -} -fn passOrDumpAndThrow(result: std.process.Child.RunResult) error{ChildProcessFailed}!void { - if (!execResultPassed(result.term)) { - dumpExecResult(result); - std.log.err("child process failed with {}", .{result.term}); - return error.ChildProcessFailed; - } -} -fn execResultPassed(term: std.process.Child.Term) bool { - switch (term) { - .Exited => |code| return code == 0, - else => return false, - } -} 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);