zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit c61e18006fb6d2beb58c07962a6a8a009806c2f4 (tree)
parent 3223d3a1ac0aa7a5ffb9252c2e8d871f0bf70ec0
Author: Carl Ã…stholm <carl@astholm.se>
Date:   Mon, 26 Jan 2026 23:55:31 +0100

Add standalone tests for debug I/O color detection

Diffstat:
Mtest/standalone/build.zig.zon | 3+++
Atest/standalone/debug_io_color/build.zig | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/standalone/debug_io_color/main.zig | 7+++++++
3 files changed, 105 insertions(+), 0 deletions(-)

diff --git a/test/standalone/build.zig.zon b/test/standalone/build.zig.zon @@ -199,6 +199,9 @@ .posix = .{ .path = "posix", }, + .debug_io_color = .{ + .path = "debug_io_color", + }, }, .paths = .{ "build.zig", diff --git a/test/standalone/debug_io_color/build.zig b/test/standalone/debug_io_color/build.zig @@ -0,0 +1,95 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test"); + b.default_step = test_step; + + // Most targets handle color the same way, regardless of whether libc is linked. + const native_target = b.graph.host; + addTestCases(test_step, native_target, false); + addTestCases(test_step, native_target, true); + + // WASI behaves differently depending on whether libc is linked. + if (b.enable_wasmtime) { + const wasi_target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .wasi }); + addTestCases(test_step, wasi_target, false); + addTestCases(test_step, wasi_target, true); + } +} + +fn addTestCases( + test_step: *std.Build.Step, + target: std.Build.ResolvedTarget, + link_libc: bool, +) void { + const b = test_step.owner; + const exe = b.addExecutable(.{ + .name = b.fmt("{s}{s}", .{ @tagName(target.result.os.tag), if (link_libc) "-libc" else "" }), + .root_module = b.createModule(.{ + .root_source_file = b.path("main.zig"), + .target = target, + .link_libc = link_libc, + }), + }); + + // Should reflect 'std.process.Environ.Block' and 'std.Io.Threaded.init_single_threaded'. + const debug_io_can_read_environ = switch (target.result.os.tag) { + .windows => true, + .wasi, .emscripten => link_libc, + .freestanding, .other => false, + else => true, + }; + + // Don't forget to account for whether the build process's stderr supports color. + const parent_stderr_color_enabled = (std.Io.Terminal.Mode.detect(b.graph.io, .stderr(), false, false) catch unreachable) != .no_color; + + _ = addTestCase(test_step, exe, "neither", .inherit, .manual, parent_stderr_color_enabled); + _ = addTestCase(test_step, exe, "neither", .redirect, .manual, false); + _ = addTestCase(test_step, exe, "no_color", .inherit, .disable, if (debug_io_can_read_environ) false else parent_stderr_color_enabled); + _ = addTestCase(test_step, exe, "no_color", .redirect, .disable, false); + _ = addTestCase(test_step, exe, "clicolor_force", .inherit, .enable, if (debug_io_can_read_environ) true else parent_stderr_color_enabled); + _ = addTestCase(test_step, exe, "clicolor_force", .redirect, .enable, debug_io_can_read_environ); + + const both = addTestCase(test_step, exe, "both", .inherit, .manual, if (debug_io_can_read_environ) false else parent_stderr_color_enabled); + both.setEnvironmentVariable("NO_COLOR", "1"); + both.setEnvironmentVariable("CLICOLOR_FORCE", "1"); + + const both_redirected = addTestCase(test_step, exe, "both", .redirect, .manual, false); + both_redirected.setEnvironmentVariable("NO_COLOR", "1"); + both_redirected.setEnvironmentVariable("CLICOLOR_FORCE", "1"); +} + +fn addTestCase( + test_step: *std.Build.Step, + exe: *std.Build.Step.Compile, + test_case_name: []const u8, + stderr: enum { inherit, redirect }, + run_step_color: std.Build.Step.Run.Color, + expected_color_enabled: bool, +) *std.Build.Step.Run { + const b = test_step.owner; + const step_name = b.fmt("{s} {s}{s}", .{ + exe.name, + test_case_name, + if (stderr == .redirect) "-redirect" else "", + }); + const run_exe = b.addRunArtifact(exe); + run_exe.setName(b.fmt("run {s}", .{step_name})); + + run_exe.failing_to_execute_foreign_is_an_error = false; + if (stderr == .redirect) run_exe.expectStdErrMatch(""); + + run_exe.clearEnvironment(); + run_exe.color = run_step_color; + + // Build system quirk: Currently, Run step stdout checks will also redirect stderr, so as a + // workaround we use a CheckFile step instead. We must also mark the Run step as having side + // effects, to ensure the parent stderr is inherited when not explicitly redirected. + run_exe.has_side_effects = true; + const stdout = run_exe.captureStdOut(.{}); + const check_file = b.addCheckFile(stdout, .{ .expected_exact = if (expected_color_enabled) "true" else "false" }); + check_file.setName(b.fmt("check {s}", .{step_name})); + test_step.dependOn(&check_file.step); + + return run_exe; +} diff --git a/test/standalone/debug_io_color/main.zig b/test/standalone/debug_io_color/main.zig @@ -0,0 +1,7 @@ +const std = @import("std"); + +pub fn main() !void { + const stderr = std.debug.lockStderr(&.{}); + defer std.debug.unlockStderr(); + try std.Io.File.stdout().writeStreamingAll(std.Options.debug_io, if (stderr.terminal_mode != .no_color) "true" else "false"); +}