From 74a3a042990705a1d2406e2ee4b98a2cf75b9317 Mon Sep 17 00:00:00 2001 From: Oreofe Date: Sun, 30 Aug 2026 15:56:28 +0100 Subject: [PATCH 1/3] =?UTF-8?q?test:=20compile=20stdx=20as=20its=20own=20t?= =?UTF-8?q?est=20root=20=E2=80=94=20its=20tests=20had=20never=20run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit stdx is a separate module, and module dependencies contribute no tests to a test root, so every test in stdx (PRNG, log, helpers) was invisible to zig build test. Adding the root immediately caught two casualties of that blind spot: TestLogger no longer compiled on Zig 0.16 (ArrayList init and writer API drift), and no_padding became vacuous (@bitSizeOf now includes struct padding, so the check is always true) — fixed and deleted respectively. 41 stdx tests now run under test/test-unit. --- build.zig | 11 +++++++++++ src/stdx/log.zig | 10 ++++++---- src/stdx/mod.zig | 28 ++++++---------------------- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/build.zig b/build.zig index 19e2574..42f6f0d 100644 --- a/build.zig +++ b/build.zig @@ -105,6 +105,15 @@ pub fn build(b: *std.Build) void { const run_unit_tests = b.addRunArtifact(unit_tests); + // stdx is its own module, and module dependencies contribute no tests to + // a test root — everything in stdx (PRNG, log, helpers) was invisible to + // the src test runner and had never run. Compile stdx as its own root. + const stdx_tests = b.addTest(.{ + .root_module = stdx_module, + .filters = if (test_filter) |f| &.{f} else &.{}, + }); + const run_stdx_tests = b.addRunArtifact(stdx_tests); + // ── E2E Tests ── const e2e_tests = b.addTest(.{ @@ -144,9 +153,11 @@ pub fn build(b: *std.Build) void { const test_step = b.step("test", "Run all tests"); test_step.dependOn(&run_unit_tests.step); + test_step.dependOn(&run_stdx_tests.step); const unit_test_step = b.step("test-unit", "Run unit tests only"); unit_test_step.dependOn(&run_unit_tests.step); + unit_test_step.dependOn(&run_stdx_tests.step); const e2e_test_step = b.step("test-e2e", "Run end-to-end tests"); e2e_test_step.dependOn(&run_e2e_tests.step); diff --git a/src/stdx/log.zig b/src/stdx/log.zig index 7591d23..123e36e 100644 --- a/src/stdx/log.zig +++ b/src/stdx/log.zig @@ -805,7 +805,7 @@ pub const TestLogger = struct { allocator: std.mem.Allocator, pub fn init(allocator: std.mem.Allocator) TestLogger { - return .{ .buffer = .{}, .allocator = allocator }; + return .{ .buffer = .empty, .allocator = allocator }; } pub fn deinit(self: *TestLogger) void { @@ -822,8 +822,10 @@ pub const TestLogger = struct { /// Log to this buffer instead of stdout. pub fn logTo(self: *TestLogger, level: Level, comptime msg: []const u8, fields: anytype) void { - const w = self.buffer.writer(self.allocator); - formatTextEntry(w, level, msg, @import("time.zig").nanoTimestamp(), null, fields); + var aw: std.Io.Writer.Allocating = .init(self.allocator); + defer aw.deinit(); + formatTextEntry(&aw.writer, level, msg, @import("time.zig").nanoTimestamp(), null, fields); + self.buffer.appendSlice(self.allocator, aw.written()) catch {}; } }; @@ -901,7 +903,7 @@ test "test logger buffer" { Field.str("key", "value"), }); - const output = test_log.buffered(); + const output = test_log.getWritten(); try std.testing.expect(std.mem.indexOf(u8, output, "test message") != null); try std.testing.expect(std.mem.indexOf(u8, output, "key=") != null); } diff --git a/src/stdx/mod.zig b/src/stdx/mod.zig index 745c26d..a99b8a9 100644 --- a/src/stdx/mod.zig +++ b/src/stdx/mod.zig @@ -26,6 +26,12 @@ pub const process = @import("process.zig"); pub const Mutex = sync.Mutex; pub const testing = @import("testing/e2e/mod.zig"); + +test { + // The deterministic PRNG's tests live only here; without this + // reference they are never collected. + _ = @import("testing/prng.zig"); +} /// Copy memory from source to destination. /// Asserts that the slices do not overlap. pub fn copy_disjoint( @@ -46,11 +52,6 @@ pub fn copy_disjoint( } } -/// Check if a struct has no padding. -pub fn no_padding(comptime T: type) bool { - return @sizeOf(T) == @bitSizeOf(T) / 8; -} - /// Maybe assertion - only asserts in debug mode. pub fn maybe(condition: bool) void { if (std.debug.runtime_safety) { @@ -99,23 +100,6 @@ test "copy_disjoint: inexact" { try std.testing.expectEqualSlices(u8, &[_]u8{ 1, 2, 0, 0 }, &dest); } -test "no_padding: struct without padding" { - const NoPadding = struct { - a: u32, - b: u32, - }; - try std.testing.expect(no_padding(NoPadding)); -} - -test "no_padding: struct with padding" { - const WithPadding = struct { - a: u8, - // 3 bytes padding here - b: u32, - }; - try std.testing.expect(!no_padding(WithPadding)); -} - test "nullIfEmpty: non-empty string" { const str = "hello"; const result = nullIfEmpty(u8, str); From fee13436765ada94021ff1b9079c02f86c06c2c8 Mon Sep 17 00:00:00 2001 From: Oreofe Date: Sun, 30 Aug 2026 16:01:19 +0100 Subject: [PATCH 2/3] chore(): clean up stdx.time import --- src/stdx/log.zig | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/stdx/log.zig b/src/stdx/log.zig index 123e36e..11ecc8d 100644 --- a/src/stdx/log.zig +++ b/src/stdx/log.zig @@ -44,6 +44,7 @@ const std = @import("std"); const builtin = @import("builtin"); +const time = @import("time.zig"); // Re-exports pub const Field = @import("log/field.zig").Field; @@ -411,7 +412,7 @@ fn writeFmtEntry( var fbs: std.Io.Writer = .fixed(&buf); const writer = &fbs; - const timestamp = @import("time.zig").nanoTimestamp(); + const timestamp = time.nanoTimestamp(); const caller: ?format.Caller = if (global.show_caller) .{ .file = src.file, .line = src.line, .fn_name = src.fn_name } @@ -446,7 +447,7 @@ fn writeEntry( var fbs: std.Io.Writer = .fixed(&buf); const writer = &fbs; - const timestamp = @import("time.zig").nanoTimestamp(); + const timestamp = time.nanoTimestamp(); // Build caller info if enabled const caller: ?format.Caller = if (global.show_caller) @@ -759,7 +760,7 @@ pub fn stdLogFn( var fbs: std.Io.Writer = .fixed(&buf); const writer = &fbs; - const timestamp = @import("time.zig").nanoTimestamp(); + const timestamp = time.nanoTimestamp(); const colors = global.use_colors; // Simple text format for std.log bridge @@ -824,7 +825,7 @@ pub const TestLogger = struct { pub fn logTo(self: *TestLogger, level: Level, comptime msg: []const u8, fields: anytype) void { var aw: std.Io.Writer.Allocating = .init(self.allocator); defer aw.deinit(); - formatTextEntry(&aw.writer, level, msg, @import("time.zig").nanoTimestamp(), null, fields); + formatTextEntry(&aw.writer, level, msg, time.nanoTimestamp(), null, fields); self.buffer.appendSlice(self.allocator, aw.written()) catch {}; } }; From 35d2eb0086955b02232c50fa5ffd5ea7e4791eb4 Mon Sep 17 00:00:00 2001 From: Oreofe Date: Sun, 30 Aug 2026 20:26:09 +0100 Subject: [PATCH 3/3] build: link libc into the stdx module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new stdx test root failed to compile on Linux: std/c.zig: error: dependency on libc must be explicitly specified stdx wraps libc directly — std.c sockets, fcntl, file IO — so it needs the link wherever it is rooted. Every consumer sets link_libc on its own root module, which covers stdx as a dependency but not a test artifact rooted at stdx itself. macOS links libc implicitly, so this only showed up on Linux. Verified test-unit passes on both macOS and Linux. --- build.zig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.zig b/build.zig index 42f6f0d..48b7721 100644 --- a/build.zig +++ b/build.zig @@ -19,6 +19,12 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("src/stdx/mod.zig"), .target = target, .optimize = optimize, + // stdx wraps libc directly (std.c sockets, fcntl, file IO), so it needs + // the link wherever it is rooted. Consumers set this on their own root + // module, which covers stdx as a dependency but not the test artifact + // rooted at stdx itself — that one fails to compile without it on + // targets where libc is not linked implicitly. + .link_libc = true, }); // ── Version (git describe) ──