From 83fc861e30047ce1964d34ba7b1a1c5794a74d33 Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Wed, 10 Jun 2026 11:53:48 -0700 Subject: [PATCH] add 'zigmod test' subcommand --- docs/commands/test.md | 89 +++++++++++++++++++++++++++++++++++++++++++ src/cmd/test.zig | 79 ++++++++++++++++++++++++++++++++++++++ src/lib.zig | 1 + 3 files changed, 169 insertions(+) create mode 100644 docs/commands/test.md create mode 100644 src/cmd/test.zig diff --git a/docs/commands/test.md b/docs/commands/test.md new file mode 100644 index 0000000000000000000000000000000000000000..c2ff4dad48360cd1e8c2c8b30a00d15c5d8053b9 --- /dev/null +++ b/docs/commands/test.md @@ -0,0 +1,89 @@ +## `test` command + +``` +zigmod test +``` + +Use this command to easily run your Zigmod-enabled module tests in a debugger. +`zig build test` is designed as a task runner and so it does not do the full-process replacement necessary for debuggers like GDB and LLDB to follow when an error occurs. + +Below is an example of the UX you can get using my https://github.com/nektro/zig-whatwg-url package in a simulated example. + +``` +$ gdb --args zigmod test +GNU gdb (GDB) 16.3 +Copyright (C) 2024 Free Software Foundation, Inc. +License GPLv3+: GNU GPL version 3 or later +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. +Type "show copying" and "show warranty" for details. +This GDB was configured as "x86_64-unknown-linux-gnu". +Type "show configuration" for configuration details. +For bug reporting instructions, please see: +. +Find the GDB manual and other documentation resources online at: + . + +For help, type "help". +Type "apropos word" to search for commands related to "word"... +Reading symbols from zigmod... +(gdb) r +Starting program: /home/me/dev/zigmod/zig-out/bin/zigmod test +[Thread debugging using libthread_db enabled] +Using host libthread_db library "/nix/store/km4g87jxsqxvcq344ncyb8h1i6f3cqxh-glibc-2.40-218/lib/libthread_db.so.1". +process 1570642 is executing new program: /home/me/.local/share/zig/0.16.0/zig +[Detaching after fork from child process 1570645] +[New LWP 1570646] +[LWP 1570646 exited] +process 1570642 is executing new program: /home/me/zig-cache/o/82896c6cdefce8104394f5e8ce6a5138/test +[Thread debugging using libthread_db enabled] +Using host libthread_db library "/nix/store/km4g87jxsqxvcq344ncyb8h1i6f3cqxh-glibc-2.40-218/lib/libthread_db.so.1". +thread 1570642 panic: reached unreachable code +/home/me/dev/zig-whatwg-url/url.zig:372:25: 0x1294c44 in parseBasic (url.zig) + unreachable; + ^ +/home/me/dev/zig-whatwg-url/url.zig:42:26: 0x1274b1e in parse (url.zig) + return parseBasic(alloc, input, null, null); + ^ +/home/me/dev/zig-whatwg-url/test.zig:15:32: 0x1275c61 in test_2 (test.zig) + const u = try url.URL.parse(allocator, "https://en.wikipedia.org/w/index.php?title=URL", null); + ^ +/home/me/.local/share/zig/0.16.0/lib/compiler/test_runner.zig:291:25: 0x122fde6 in mainTerminal (test_runner.zig) + if (test_fn.func()) |_| { + ^ +/home/me/.local/share/zig/0.16.0/lib/compiler/test_runner.zig:73:28: 0x122f1e2 in main (test_runner.zig) + return mainTerminal(init); + ^ +/home/me/.local/share/zig/0.16.0/lib/std/start.zig:699:88: 0x122be0c in callMain (std.zig) + if (fn_info.params[0].type.? == std.process.Init.Minimal) return wrapMain(root.main(.{ + ^ +/home/me/.local/share/zig/0.16.0/lib/std/start.zig:638:20: 0x122bbe6 in callMainWithArgs (std.zig) + return callMain(argv[0..argc], env_block); + ^ +???:?:?: 0x7ffff7c2a4d7 in __libc_start_call_main (/nix/store/km4g87jxsqxvcq344ncyb8h1i6f3cqxh-glibc-2.40-218/lib/libc.so.6) +???:?:?: 0x7ffff7c2a59a in __libc_start_main_alias_2 (/nix/store/km4g87jxsqxvcq344ncyb8h1i6f3cqxh-glibc-2.40-218/lib/libc.so.6) +/home/me/.local/share/zig/0.16.0/lib/libc/glibc/sysdeps/x86_64/start.S:115: 0x164b0f0 in ??? (/home/me/.local/share/zig/0.16.0/lib/libc/glibc/sysdeps/x86_64/start.S) + call *__libc_start_main@GOTPCREL(%rip) + + +Thread 1 "test" received signal SIGABRT, Aborted. +__pthread_kill_implementation (threadid=, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44 +warning: 44 pthread_kill.c: No such file or directory +(gdb) bt +#0 __pthread_kill_implementation (threadid=, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44 +#1 0x00007ffff7c9cb13 in __pthread_kill_internal (threadid=, signo=6) at pthread_kill.c:78 +#2 0x00007ffff7c4190e in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26 +#3 0x00007ffff7c28942 in __GI_abort () at abort.c:79 +#4 0x00000000010857c3 in process.abort () +#5 0x0000000001085481 in debug.defaultPanic (msg=..., first_trace_addr=...) +#6 0x000000000108bb44 in debug.FullPanic((function 'defaultPanic')).reachedUnreachable () +#7 0x0000000001294c45 in url.URL.parseBasic (alloc=..., input=..., base=..., state_override=...) +#8 0x0000000001274b1f in url.URL.parse (alloc=..., input=..., base=...) at url.zig:42 +#9 0x0000000001275c62 in test.test_2 () at test.zig:15 +#10 0x000000000122fde7 in test_runner.mainTerminal (init=...) +#11 0x000000000122f1e3 in test_runner.main (init=...) +#12 0x000000000122be0d in start.callMain (args=..., environ=...) +#13 0x000000000122bbe7 in start.main (c_argc=2, c_argv=0x7fffffffc408, c_envp=0x7fffffffc420) +``` + +You can also use this with GUI debuggers such as [VS Code](https://code.visualstudio.com/docs/debugtest/debugging-configuration) and [Zed](https://zed.dev/docs/debugger). diff --git a/src/cmd/test.zig b/src/cmd/test.zig new file mode 100644 index 0000000000000000000000000000000000000000..b9976ddb2e8ae8cbe799cb6838aaa822abd88e83 --- /dev/null +++ b/src/cmd/test.zig @@ -0,0 +1,79 @@ +const std = @import("std"); +const nio = @import("nio"); +const nfs = @import("nfs"); +const root = @import("root"); + +const zigmod = @import("../lib.zig"); +const u = @import("./../util/funcs.zig"); +const common = @import("./../common.zig"); + +// +// + +pub fn execute(self_name: []const u8, args: []const [:0]const u8) !void { + _ = self_name; + if (!std.process.can_replace) u.fail("The target OS does not support execv", .{}); + + const gpa = std.heap.c_allocator; + const cachepath = try u.find_cachepath(); + const dir = nfs.cwd(); + const should_lock = args.len >= 1 and std.mem.eql(u8, args[0], "--locked"); + + var options = common.CollectOptions{ + .log = false, + .update = false, + .alloc = gpa, + .lock = if (should_lock) try common.parse_lockfile(gpa, dir) else null, + }; + const top_module = try common.collect_deps_deep(cachepath, dir, &options); + + var argv = std.array_list.Managed([]const u8).init(gpa); + defer argv.deinit(); + + var seencache = std.array_list.Managed([48]u8).init(gpa); + defer seencache.deinit(); + + try argv.append(root.environ.get("ZIG") orelse "zig"); + try argv.append("test"); + try argv.append("--name"); + try argv.append("test"); + try argv.append("--test-execve"); // requires >= https://codeberg.org/ziglang/zig/pulls/30811 or 0.16 + try argv.append("-lc"); + try addArgv(gpa, &argv, cachepath, top_module, &seencache); + + const io = root.io; + return std.process.replace(io, .{ .argv = argv.items }); +} + +fn addArgv(allocator: std.mem.Allocator, argv: *std.array_list.Managed([]const u8), cachepath: []const u8, module: zigmod.Module, seencache: *std.array_list.Managed([48]u8)) !void { + for (seencache.items) |item| { + if (std.mem.eql(u8, &module.id, &item)) { + return; + } + } + try seencache.append(module.id); + + for (module.deps) |d| { + if (d.type == .system_lib) { + try argv.append(try nio.fmt.allocPrint(allocator, "-l{s}", .{d.name})); + continue; + } + if (d.main.len == 0) { + continue; + } + try argv.append("--dep"); + try argv.append(d.name); + } + if (module.type == .system_lib or module.main.len == 0) { + // + } else if (std.mem.eql(u8, module.name, "root")) { + try argv.append(try nio.fmt.allocPrint(allocator, "-M{s}=test.zig", .{module.name})); + } else if (module.clean_path.len == 0 or std.mem.eql(u8, module.clean_path, "../..")) { + try argv.append(try nio.fmt.allocPrint(allocator, "-M{s}={s}", .{ module.name, module.main })); + } else { + try argv.append(try nio.fmt.allocPrint(allocator, "-M{s}={s}/{s}/{s}", .{ module.name, cachepath, module.clean_path, module.main })); + } + for (module.deps) |dep| { + try addArgv(allocator, argv, cachepath, dep, seencache); + } +} diff --git a/src/lib.zig b/src/lib.zig index af5863de017791b9f75c1b721580be190a4a2491..6e56dee716b54ff3764821c96a46f0ec1506a375 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -14,6 +14,7 @@ pub const commands = struct { pub const generate = @import("./cmd/generate.zig"); pub const explain = @import("./cmd/explain.zig"); pub const install = @import("./cmd/install.zig"); + pub const @"test" = @import("./cmd/test.zig"); }; pub fn init() !void { -- 2.54.0