From 909d1b3f55afc3c21bd99accd22ca8b81cce5817 Mon Sep 17 00:00:00 2001 From: Sergei Petunin Date: Sat, 30 May 2026 14:32:18 +0200 Subject: [PATCH] test: add comptime smoke checks --- build.zig | 16 +++++++- docs/development.md | 2 + src/comptime_checks.zig | 87 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 src/comptime_checks.zig diff --git a/build.zig b/build.zig index a3c0ab5..098b830 100644 --- a/build.zig +++ b/build.zig @@ -27,6 +27,11 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); + const comptime_checks_mod = b.createModule(.{ + .root_source_file = b.path("src/comptime_checks.zig"), + .target = target, + .optimize = optimize, + }); mcp_mod.addImport("control", control_mod); const assets_mod = b.createModule(.{ .root_source_file = b.path("assets/terminfo.zig"), @@ -56,7 +61,9 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, })) |dep| { - exe_mod.addImport("toml", dep.module("toml")); + const toml_mod = dep.module("toml"); + exe_mod.addImport("toml", toml_mod); + comptime_checks_mod.addImport("toml", toml_mod); } const exe = b.addExecutable(.{ @@ -118,14 +125,19 @@ pub fn build(b: *std.Build) void { const mcp_unit_tests = b.addTest(.{ .root_module = mcp_mod, }); + const comptime_checks_tests = b.addTest(.{ + .root_module = comptime_checks_mod, + }); mcp_unit_tests.linkLibC(); const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); const run_mcp_unit_tests = b.addRunArtifact(mcp_unit_tests); + const run_comptime_checks_tests = b.addRunArtifact(comptime_checks_tests); - const test_step = b.step("test", "Run unit tests"); + const test_step = b.step("test", "Run unit and compile-time smoke tests"); test_step.dependOn(&run_exe_unit_tests.step); test_step.dependOn(&run_mcp_unit_tests.step); + test_step.dependOn(&run_comptime_checks_tests.step); // Lint step using zwanzig. Always build the linter with ReleaseFast: it is a // build-time tool and Debug-mode safety/allocator overhead makes analysis diff --git a/docs/development.md b/docs/development.md index ed1a994..4929116 100644 --- a/docs/development.md +++ b/docs/development.md @@ -72,6 +72,8 @@ just test zig build test ``` +The `zig build test` step runs the application and MCP unit tests plus a dedicated compile-time smoke suite in `src/comptime_checks.zig`. Add to that suite when a helper is expected to stay evaluable at comptime, especially for configuration defaults, layout math, or other pure code used by generated tables and compile-time validation. + Check formatting and script linting: ```bash just lint diff --git a/src/comptime_checks.zig b/src/comptime_checks.zig new file mode 100644 index 0000000..d6a20a6 --- /dev/null +++ b/src/comptime_checks.zig @@ -0,0 +1,87 @@ +//! Compile-time smoke tests for pure helpers that are intentionally usable from +//! comptime code. Keeping these checks in a dedicated test root makes regressions +//! visible even when the same helpers are mostly exercised by runtime tests. + +const config = @import("config.zig"); +const grid_layout = @import("app/grid_layout.zig"); + +const Color = config.Color; +const GridLayout = grid_layout.GridLayout; +const GridPosition = grid_layout.GridPosition; + +fn expectColorEqual(comptime expected: Color, comptime actual: Color) void { + if (actual.r != expected.r or actual.g != expected.g or actual.b != expected.b) { + @compileError("comptime color mismatch"); + } +} + +fn expectUsizeEqual(comptime expected: usize, comptime actual: usize) void { + if (actual != expected) { + @compileError("comptime usize mismatch"); + } +} + +fn expectBool(comptime condition: bool) void { + if (!condition) { + @compileError("comptime boolean check failed"); + } +} + +test "color parsing and theme fallback remain comptime-evaluable" { + comptime { + expectColorEqual( + .{ .r = 0x0e, .g = 0x11, .b = 0x16 }, + Color.fromHex("#0E1116") orelse @compileError("valid color did not parse"), + ); + expectColorEqual( + .{ .r = 0xcd, .g = 0xd6, .b = 0xe0 }, + Color.fromHex("cdd6e0") orelse @compileError("valid color without # did not parse"), + ); + expectBool(Color.fromHex("#12345") == null); + expectBool(Color.fromHex("#12xx56") == null); + + const themed = config.ThemeConfig{ + .background = "#010203", + .foreground = "not-a-color", + .selection = "", + .accent = "#AABBCC", + }; + expectColorEqual(.{ .r = 1, .g = 2, .b = 3 }, themed.getBackground()); + expectColorEqual(Color.default_foreground, themed.getForeground()); + expectColorEqual(Color.default_selection, themed.getSelection()); + expectColorEqual(.{ .r = 0xaa, .g = 0xbb, .b = 0xcc }, themed.getAccent()); + + const palette = config.PaletteConfig{ + .black = "#000102", + .bright_white = "#FEFDFC", + }; + expectColorEqual(.{ .r = 0, .g = 1, .b = 2 }, palette.getColor(0)); + expectColorEqual(config.default_palette[1], palette.getColor(1)); + expectColorEqual(.{ .r = 0xfe, .g = 0xfd, .b = 0xfc }, palette.getColor(15)); + } +} + +test "grid dimension helpers remain comptime-evaluable and bounded" { + comptime { + inline for (0..(grid_layout.max_terminals + 8)) |count| { + const dims = GridLayout.calculateDimensions(count); + expectBool(dims.cols >= 1); + expectBool(dims.rows >= 1); + expectBool(dims.cols <= grid_layout.max_grid_size); + expectBool(dims.rows <= grid_layout.max_grid_size); + expectBool(dims.cols >= dims.rows); + if (count <= grid_layout.max_terminals) { + expectBool(dims.cols * dims.rows >= @max(count, 1)); + } + } + + const dims_10 = GridLayout.calculateDimensions(10); + expectUsizeEqual(4, dims_10.cols); + expectUsizeEqual(3, dims_10.rows); + + const pos = GridPosition.fromIndex(17, 5); + expectUsizeEqual(2, pos.col); + expectUsizeEqual(3, pos.row); + expectUsizeEqual(17, pos.toIndex(5)); + } +}