authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-06-10 11:53:48 -07:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-06-10 11:53:48 -07:00
log83fc861e30047ce1964d34ba7b1a1c5794a74d33
tree8c94d56576ba5b2e1083e8d1fc764179fc6996bd
parent3c9d4d8ee6aee72d5cfa302479d5f72c2a7c2105
signaturebadge-check Signed by SSH key SHA256:4hHJbtBRU58AYXwjL7fkz2fnQHdiye8x1QpTCQ0sHNw

add 'zigmod test' subcommand


3 files changed, 169 insertions(+), 0 deletions(-)

docs/commands/test.md created+89
......@@ -0,0 +1,89 @@
1## `test` command
2
3```
4zigmod test
5```
6
7Use this command to easily run your Zigmod-enabled module tests in a debugger.
8`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.
9
10Below is an example of the UX you can get using my https://github.com/nektro/zig-whatwg-url package in a simulated example.
11
12```
13$ gdb --args zigmod test
14GNU gdb (GDB) 16.3
15Copyright (C) 2024 Free Software Foundation, Inc.
16License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
17This is free software: you are free to change and redistribute it.
18There is NO WARRANTY, to the extent permitted by law.
19Type "show copying" and "show warranty" for details.
20This GDB was configured as "x86_64-unknown-linux-gnu".
21Type "show configuration" for configuration details.
22For bug reporting instructions, please see:
23<https://www.gnu.org/software/gdb/bugs/>.
24Find the GDB manual and other documentation resources online at:
25 <http://www.gnu.org/software/gdb/documentation/>.
26
27For help, type "help".
28Type "apropos word" to search for commands related to "word"...
29Reading symbols from zigmod...
30(gdb) r
31Starting program: /home/me/dev/zigmod/zig-out/bin/zigmod test
32[Thread debugging using libthread_db enabled]
33Using host libthread_db library "/nix/store/km4g87jxsqxvcq344ncyb8h1i6f3cqxh-glibc-2.40-218/lib/libthread_db.so.1".
34process 1570642 is executing new program: /home/me/.local/share/zig/0.16.0/zig
35[Detaching after fork from child process 1570645]
36[New LWP 1570646]
37[LWP 1570646 exited]
38process 1570642 is executing new program: /home/me/zig-cache/o/82896c6cdefce8104394f5e8ce6a5138/test
39[Thread debugging using libthread_db enabled]
40Using host libthread_db library "/nix/store/km4g87jxsqxvcq344ncyb8h1i6f3cqxh-glibc-2.40-218/lib/libthread_db.so.1".
41thread 1570642 panic: reached unreachable code
42/home/me/dev/zig-whatwg-url/url.zig:372:25: 0x1294c44 in parseBasic (url.zig)
43 unreachable;
44 ^
45/home/me/dev/zig-whatwg-url/url.zig:42:26: 0x1274b1e in parse (url.zig)
46 return parseBasic(alloc, input, null, null);
47 ^
48/home/me/dev/zig-whatwg-url/test.zig:15:32: 0x1275c61 in test_2 (test.zig)
49 const u = try url.URL.parse(allocator, "https://en.wikipedia.org/w/index.php?title=URL", null);
50 ^
51/home/me/.local/share/zig/0.16.0/lib/compiler/test_runner.zig:291:25: 0x122fde6 in mainTerminal (test_runner.zig)
52 if (test_fn.func()) |_| {
53 ^
54/home/me/.local/share/zig/0.16.0/lib/compiler/test_runner.zig:73:28: 0x122f1e2 in main (test_runner.zig)
55 return mainTerminal(init);
56 ^
57/home/me/.local/share/zig/0.16.0/lib/std/start.zig:699:88: 0x122be0c in callMain (std.zig)
58 if (fn_info.params[0].type.? == std.process.Init.Minimal) return wrapMain(root.main(.{
59 ^
60/home/me/.local/share/zig/0.16.0/lib/std/start.zig:638:20: 0x122bbe6 in callMainWithArgs (std.zig)
61 return callMain(argv[0..argc], env_block);
62 ^
63???:?:?: 0x7ffff7c2a4d7 in __libc_start_call_main (/nix/store/km4g87jxsqxvcq344ncyb8h1i6f3cqxh-glibc-2.40-218/lib/libc.so.6)
64???:?:?: 0x7ffff7c2a59a in __libc_start_main_alias_2 (/nix/store/km4g87jxsqxvcq344ncyb8h1i6f3cqxh-glibc-2.40-218/lib/libc.so.6)
65/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)
66 call *__libc_start_main@GOTPCREL(%rip)
67
68
69Thread 1 "test" received signal SIGABRT, Aborted.
70__pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44
71warning: 44 pthread_kill.c: No such file or directory
72(gdb) bt
73#0 __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44
74#1 0x00007ffff7c9cb13 in __pthread_kill_internal (threadid=<optimized out>, signo=6) at pthread_kill.c:78
75#2 0x00007ffff7c4190e in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
76#3 0x00007ffff7c28942 in __GI_abort () at abort.c:79
77#4 0x00000000010857c3 in process.abort ()
78#5 0x0000000001085481 in debug.defaultPanic (msg=..., first_trace_addr=...)
79#6 0x000000000108bb44 in debug.FullPanic((function 'defaultPanic')).reachedUnreachable ()
80#7 0x0000000001294c45 in url.URL.parseBasic (alloc=..., input=..., base=..., state_override=...)
81#8 0x0000000001274b1f in url.URL.parse (alloc=..., input=..., base=...) at url.zig:42
82#9 0x0000000001275c62 in test.test_2 () at test.zig:15
83#10 0x000000000122fde7 in test_runner.mainTerminal (init=...)
84#11 0x000000000122f1e3 in test_runner.main (init=...)
85#12 0x000000000122be0d in start.callMain (args=..., environ=...)
86#13 0x000000000122bbe7 in start.main (c_argc=2, c_argv=0x7fffffffc408, c_envp=0x7fffffffc420)
87```
88
89You 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).
src/cmd/test.zig created+79
......@@ -0,0 +1,79 @@
1const std = @import("std");
2const nio = @import("nio");
3const nfs = @import("nfs");
4const root = @import("root");
5
6const zigmod = @import("../lib.zig");
7const u = @import("./../util/funcs.zig");
8const common = @import("./../common.zig");
9
10//
11//
12
13pub fn execute(self_name: []const u8, args: []const [:0]const u8) !void {
14 _ = self_name;
15 if (!std.process.can_replace) u.fail("The target OS does not support execv", .{});
16
17 const gpa = std.heap.c_allocator;
18 const cachepath = try u.find_cachepath();
19 const dir = nfs.cwd();
20 const should_lock = args.len >= 1 and std.mem.eql(u8, args[0], "--locked");
21
22 var options = common.CollectOptions{
23 .log = false,
24 .update = false,
25 .alloc = gpa,
26 .lock = if (should_lock) try common.parse_lockfile(gpa, dir) else null,
27 };
28 const top_module = try common.collect_deps_deep(cachepath, dir, &options);
29
30 var argv = std.array_list.Managed([]const u8).init(gpa);
31 defer argv.deinit();
32
33 var seencache = std.array_list.Managed([48]u8).init(gpa);
34 defer seencache.deinit();
35
36 try argv.append(root.environ.get("ZIG") orelse "zig");
37 try argv.append("test");
38 try argv.append("--name");
39 try argv.append("test");
40 try argv.append("--test-execve"); // requires >= https://codeberg.org/ziglang/zig/pulls/30811 or 0.16
41 try argv.append("-lc");
42 try addArgv(gpa, &argv, cachepath, top_module, &seencache);
43
44 const io = root.io;
45 return std.process.replace(io, .{ .argv = argv.items });
46}
47
48fn 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 {
49 for (seencache.items) |item| {
50 if (std.mem.eql(u8, &module.id, &item)) {
51 return;
52 }
53 }
54 try seencache.append(module.id);
55
56 for (module.deps) |d| {
57 if (d.type == .system_lib) {
58 try argv.append(try nio.fmt.allocPrint(allocator, "-l{s}", .{d.name}));
59 continue;
60 }
61 if (d.main.len == 0) {
62 continue;
63 }
64 try argv.append("--dep");
65 try argv.append(d.name);
66 }
67 if (module.type == .system_lib or module.main.len == 0) {
68 //
69 } else if (std.mem.eql(u8, module.name, "root")) {
70 try argv.append(try nio.fmt.allocPrint(allocator, "-M{s}=test.zig", .{module.name}));
71 } else if (module.clean_path.len == 0 or std.mem.eql(u8, module.clean_path, "../..")) {
72 try argv.append(try nio.fmt.allocPrint(allocator, "-M{s}={s}", .{ module.name, module.main }));
73 } else {
74 try argv.append(try nio.fmt.allocPrint(allocator, "-M{s}={s}/{s}/{s}", .{ module.name, cachepath, module.clean_path, module.main }));
75 }
76 for (module.deps) |dep| {
77 try addArgv(allocator, argv, cachepath, dep, seencache);
78 }
79}
src/lib.zig+1
......@@ -14,6 +14,7 @@ pub const commands = struct {
1414 pub const generate = @import("./cmd/generate.zig");
1515 pub const explain = @import("./cmd/explain.zig");
1616 pub const install = @import("./cmd/install.zig");
17 pub const @"test" = @import("./cmd/test.zig");
1718};
1819
1920pub fn init() !void {