ziglang/zig
262
6.0k
Fork
You've already forked zig
778

build runner prints "failed command" when step has non-failure stderr #31077

Open
opened 2026年02月01日 09:10:32 +01:00 by schweikert · 14 comments

Link to comment describing the enhancement which should be made: #31077 (comment)

Original issue text is below.


Zig Version

0.16.0-dev.2349+204fa8959

Steps to Reproduce and Observed Behavior

I am learning Zig, so apologies if I missed something obvious.

test.zig:

const std = @import("std");
test "crash reproduction" {
 std.debug.print("This output to stderr might cause a crash.\n", .{});
}

When running zig build test I get:

test
└─ run test w
This output to stderr might cause a crash.
failed command: ./.zig-cache/o/24e009cdb31905b1832151517d512a8e/test --cache-dir=./.zig-cache --seed=0x7ca33b43 --listen=-

If I remove the debug statement the problem disappears. I tested with Zig 0.15.2, and it doesn't have this problem.

Note that std.debug.print only outputs to stderr, not stdout (I verified with shell redirection), so this isn't a duplicate of the known issue with stdout: https://github.com/ziglang/zig/issues/15091

Expected Behavior

  • No "failed command" warning.
  • No output when running 'zig build test' (because no failing test). [EDIT: ignore this part, I understand that it is not how it is supposed to work]
Link to comment describing the enhancement which should be made: https://codeberg.org/ziglang/zig/issues/31077#issuecomment-10291088 Original issue text is below. --- ### Zig Version 0.16.0-dev.2349+204fa8959 ### Steps to Reproduce and Observed Behavior I am learning Zig, so apologies if I missed something obvious. test.zig: ``` const std = @import("std"); test "crash reproduction" { std.debug.print("This output to stderr might cause a crash.\n", .{}); } ``` When running zig build test I get: ``` test └─ run test w This output to stderr might cause a crash. failed command: ./.zig-cache/o/24e009cdb31905b1832151517d512a8e/test --cache-dir=./.zig-cache --seed=0x7ca33b43 --listen=- ``` If I remove the debug statement the problem disappears. I tested with Zig 0.15.2, and it doesn't have this problem. Note that std.debug.print only outputs to stderr, not stdout (I verified with shell redirection), so this isn't a duplicate of the known issue with stdout: https://github.com/ziglang/zig/issues/15091 ### Expected Behavior - No "failed command" warning. - No output when running 'zig build test' (because no failing test). [EDIT: ignore this part, I understand that it is not how it is supposed to work]
schweikert changed title from (削除) zig build test printing "failed command:" if the test writes to stderr (削除ここまで) to zig build test printing "failed command:" if the test writes to stderr (0.16 regression) 2026年02月01日 09:13:21 +01:00

Also, I think that the test runner is supposed to capture stderr and only show it in case of failure?

Also, I think that the test runner is supposed to capture stderr and only show it in case of failure?
Owner
Copy link

Working as designed. Unit tests should avoid printing to stderr in success cases.

Working as designed. Unit tests should avoid printing to stderr in success cases.

May I ask why? Even for unit tests having debug logging can be useful, particularly if tests fail. Why prohibit any output on both stdout and stderr? This is not very user friendly, IMO (and it worked in Zig 0.15).

May I ask why? Even for unit tests having debug logging can be useful, particularly if tests fail. Why prohibit any output on **both** stdout and stderr? This is not very user friendly, IMO (and it worked in Zig 0.15).
Owner
Copy link

I mean, what would you want to happen when you write to stderr from a unit test? According to your "expected behavior", you want the build system to completely swallow stderr from a test if it doesn't report failure. That doesn't seem reasonable; stderr is useful for reporting non-fatal failure conditions such as memory leaks (and this is exactly what will happen if you leak memory from std.testing.allocator). More generally, why print to stderr at all if you're not interested in the output?

To be clear, this is not causing the test process or build system to crash. It's not even marking a test as failed or causing zig build to exit with a non-zero status---it's just forwarding the stderr output to you. If you really do want logging output from your tests, then you can leave it in and still use the build system just fine! It's just that your logs will be, well, logged :P

I mean, what would you *want* to happen when you write to stderr from a unit test? According to your "expected behavior", you want the build system to *completely swallow* stderr from a test if it doesn't report failure. That doesn't seem reasonable; stderr is useful for reporting non-fatal failure conditions such as memory leaks (and this is exactly what will happen if you leak memory from `std.testing.allocator`). More generally, why print to stderr at all if you're not interested in the output? To be clear, this is not causing the test process or build system to crash. It's not even marking a test as failed or causing `zig build` to exit with a non-zero status---it's just forwarding the stderr output to you. If you really do want logging output from your tests, then you can leave it in and still use the build system just fine! It's just that your logs will be, well, *logged* :P

OK, I can understand the part about not swallowing stderr and outputting in case of failure. I thought that it was how it was supposed to work, but it is fine if it doesn't do that.

Did you see the part that it outputs a "failed command" line? That's what actually confused me and the actual problem.

I am reopening one last time because I think that the report was misunderstood (I won't reopen it again after that).

OK, I can understand the part about not swallowing stderr and outputting in case of failure. I thought that it was how it was supposed to work, but it is fine if it doesn't do that. Did you see the part that it outputs a "failed command" line? That's what actually confused me and the actual problem. I am reopening one last time because I think that the report was misunderstood (I won't reopen it again after that).
Owner
Copy link

Hm, okay, I can see how that could cause confusion. The command line should definitely still be printed, but that part could be reworded in the case where the step has output messages but is not considered to have failed. Perhaps it should print "from command: ..." instead of "failed command: ..." in that case.

Hm, okay, I can see how that could cause confusion. The command line should definitely still be printed, but that part could be reworded in the case where the step has output messages but is not considered to have failed. Perhaps it should print "from command: ..." instead of "failed command: ..." in that case.
mlugg added this to the Upcoming milestone 2026年02月01日 12:48:51 +01:00
mlugg changed title from (削除) zig build test printing "failed command:" if the test writes to stderr (0.16 regression) (削除ここまで) to build runner prints "failed command" when step has non-failure stderr 2026年02月01日 12:49:47 +01:00
Contributor
Copy link

imo it printing "failed command" isn't something that necessarily should be fixed because had it not been printed you never would have reached out and learned that what you were doing was illegal to do in a test

imo it printing "failed command" isn't something that necessarily should be fixed because had it not been printed you never would have reached out and learned that what you were doing was illegal to do in a test

Writing to stdout is illegal, writing to stderr isn't (AFAIK).

Writing to stdout is illegal, writing to stderr isn't (AFAIK).
Contributor
Copy link

i was under the understanding using stderr is only legal if you go through std.log

i was under the understanding using stderr is only legal if you go through `std.log`

@nektro wrote in #31077 (comment):

i was under the understanding using stderr is only legal if you go through std.log

From my own tests, I believe the difference in 0.16.0 is that now std.log.warn will fail a test wheres it didn't in 0.15.2. When I change my warn's to info's my tests pass.

I think it would be nice if the test failure mentioned "logged to stderr" was the reason for the test failure, or something to that effect. I can see why that behavior makes sense, but I don't think it's super common among test runners. When my tests failed after migrating my code to 0.16.0, my first thought was: "I see some logs, but where's the error?". It took me a while to understand why the failure was happening.

@nektro wrote in https://codeberg.org/ziglang/zig/issues/31077#issuecomment-10298348: > i was under the understanding using stderr is only legal if you go through `std.log` From my own tests, I believe the difference in 0.16.0 is that now `std.log.warn` will fail a test wheres it didn't in `0.15.2`. When I change my `warn`'s to `info`'s my tests pass. I think it would be nice if the test failure mentioned "logged to stderr" was the reason for the test failure, or something to that effect. I can see why that behavior makes sense, but I don't think it's super common among test runners. When my tests failed after migrating my code to 0.16.0, my first thought was: "I see some logs, but where's the error?". It took me a while to understand why the failure was happening.

@mlugg wrote in #31077 (comment):

Working as designed. Unit tests should avoid printing to stderr in success cases.

How can I test functions of dependency code, which output to stderr?

How can I test expected failure?

@mlugg wrote in https://codeberg.org/ziglang/zig/issues/31077#issuecomment-10288078: > Working as designed. Unit tests should avoid printing to stderr in success cases. How can I test functions of dependency code, which output to stderr? How can I test expected failure?

I have same problem, but std.debug.print was inside tested function. And failed command: did go away after commenting print statement out

pub fn take(self: SchedulerRandom) !Task {
 const amount = try self.bus.inbox.get(self.io, self.buf, 1);
 const needle = self.random_engine.intRangeAtMost(u64, 0, amount);
 std.debug.print("needle {}\n", .{needle}); // here was the problem
 assert(0 <= needle and needle <= amount);
 return self.buf[needle];
}

Test itself

test "test fixed random scheduler" {
 var fixed_random_engine = FixedRandom{ .value = 0 };
 const random_engine = fixed_random_engine.random();
 var inbox_buf: [3]Task = undefined;
 var outbox_buf: [3]Task = undefined;
 var bus = try Bus.init(testing.io, &inbox_buf, &outbox_buf);
 defer bus.close();
 const args: []const []const u8 = &.{"hel"};
 const expected_task = Task{
 .command = .get,
 .args = args,
 .conn_writer = undefined,
 };
 try bus.inbox.putOne(testing.io, expected_task);
 var buf: [3]Task = undefined;
 var s = SchedulerRandom.init(random_engine, &bus, testing.io, &buf);
 const task = try s.take();
 try testing.expectEqual(expected_task.command, task.command);
 try testing.expectEqualSlices(u8, expected_task.args[0], task.args[0]);
}
I have same problem, but std.debug.print was inside tested function. And `failed command: ` did go away after commenting print statement out ``` pub fn take(self: SchedulerRandom) !Task { const amount = try self.bus.inbox.get(self.io, self.buf, 1); const needle = self.random_engine.intRangeAtMost(u64, 0, amount); std.debug.print("needle {}\n", .{needle}); // here was the problem assert(0 <= needle and needle <= amount); return self.buf[needle]; } ``` Test itself ``` test "test fixed random scheduler" { var fixed_random_engine = FixedRandom{ .value = 0 }; const random_engine = fixed_random_engine.random(); var inbox_buf: [3]Task = undefined; var outbox_buf: [3]Task = undefined; var bus = try Bus.init(testing.io, &inbox_buf, &outbox_buf); defer bus.close(); const args: []const []const u8 = &.{"hel"}; const expected_task = Task{ .command = .get, .args = args, .conn_writer = undefined, }; try bus.inbox.putOne(testing.io, expected_task); var buf: [3]Task = undefined; var s = SchedulerRandom.init(random_engine, &bus, testing.io, &buf); const task = try s.take(); try testing.expectEqual(expected_task.command, task.command); try testing.expectEqualSlices(u8, expected_task.args[0], task.args[0]); } ```

@jasperw wrote in #31077 (comment):

From my own tests, I believe the difference in 0.16.0 is that now std.log.warn will fail a test wheres it didn't in 0.15.2. When I change my warn's to info's my tests pass.

I think it would be nice if the test failure mentioned "logged to stderr" was the reason for the test failure, or something to that effect. [...]

This is closely tied with std.testing.log_level, so you could alternatively also set that to .err and only error logs would fail your tests. And setting it to .debug will cause this failure on any log level.

That being said, I think the existence of this dedicated global and its options should be respected in the encompassing framework. If there's a flag to control the expected log level, how should that log level not be acceptable. The current docs mention a github thread where the idea of logs in tests via flag was also mentioned.

@jasperw wrote in https://codeberg.org/ziglang/zig/issues/31077#issuecomment-13295139: > From my own tests, I believe the difference in 0.16.0 is that now `std.log.warn` will fail a test wheres it didn't in `0.15.2`. When I change my `warn`'s to `info`'s my tests pass. > > I think it would be nice if the test failure mentioned "logged to stderr" was the reason for the test failure, or something to that effect. [...] This is closely tied with `std.testing.log_level`, so you could alternatively also set that to `.err` and only error logs would fail your tests. And setting it to `.debug` will cause this failure on any log level. That being said, I think the existence of this dedicated global and its options should be respected in the encompassing framework. If there's a flag to control the expected log level, how should that log level not be acceptable. The current docs mention a github thread where the idea of logs in tests via flag was also [mentioned](https://github.com/ziglang/zig/issues/5738#issuecomment-3426968252).
test "simple math" {
 std.testing.log_level = .debug;
 std.log.info("Simple math info.", .{});
 try std.testing.expectEqual(@as(u8, 2), @as(u8, 1) + 1);
}

image

For me simply renaming the 'failed' to 'from' as suggested is fine by me and maybe the red w to a blue i assuming w stand for warning ?

PS Looking at https://codeberg.org/ziglang/zig/src/branch/master/lib/compiler/Maker.zig#L1656 seems like this simple change is a significant refactor? As in a complete new 'pub fn printInfoMessages(...' and rewrite everything around it?

``` test "simple math" { std.testing.log_level = .debug; std.log.info("Simple math info.", .{}); try std.testing.expectEqual(@as(u8, 2), @as(u8, 1) + 1); } ``` ![image](/attachments/966b0056-6628-48b3-af66-cc1f90654041) For me simply renaming the 'failed' to 'from' as suggested is fine by me and maybe the red w to a blue i assuming w stand for warning ? PS Looking at https://codeberg.org/ziglang/zig/src/branch/master/lib/compiler/Maker.zig#L1656 seems like this simple change is a significant refactor? As in a complete new 'pub fn printInfoMessages(...' and rewrite everything around it?
164 KiB
Sign in to join this conversation.
No Branch/Tag specified
master
elfv2
spork8
restricted
0.16.x
panic-rewrite
parking-futex-lockfree
windows-Io-cleanup
Io-watch
ParseCommandLineOptions
windows-async-files
poll-ring
debug-file-leaks-differently
debug-file-leaks
ProcessPrng
elfv2-dyn
jobserver
threadtheft
io-threaded-no-queue
0.15.x
Io.net
comptime-allocator
restricted-function-pointers
cli
wasm-linker-writer
wrangle-writer-buffering
sha1-stream
async-await-demo
fixes
0.14.x
ast-node-methods
macos-debug-info
make-vs-configure
fuzz-macos
sans-aro
ArrayList-reserve
incr-bug
llvm-ir-nosanitize-metadata
ci-tarballs
ci-scripts
threadpool
0.12.x
new-pkg-hash
json-diagnostics
more-doctests
rework-comptime-mutation
0.11.x
ci-perf-comment
stage2-async
0.10.x
autofix
0.9.x
aro
hcs
0.8.x
0.7.x
0.16.0
0.15.2
0.15.1
0.15.0
0.14.1
0.14.0
0.12.1
0.13.0
0.12.0
0.11.0
0.10.1
0.10.0
0.9.1
0.9.0
0.8.1
0.8.0
0.7.1
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.1
0.1.0
Labels
Clear labels
abi/f32
abi/ilp32
abi/sf
accepted
This proposal is planned.
arch/21k
arch/6502
arch/aarch64
arch/alpha
arch/amdgcn
arch/arc
arch/arc32
arch/arc64
arch/arm
arch/avr
arch/bfin
arch/bpf
arch/colossus
arch/cris
arch/csky
arch/dlx
arch/epiphany
arch/fr30
arch/frv
arch/hexagon
arch/hppa
arch/hppa64
arch/ia64
arch/kalimba
arch/kvx
arch/lanai
arch/lm32
arch/loongarch32
arch/loongarch64
arch/m32r
arch/m68k
arch/m88k
arch/mcore
arch/microblaze
arch/mips
arch/mips64
arch/mmix
arch/moxie
arch/mrisc32
arch/msp430
arch/nds32
arch/ns32k
arch/nvptx
arch/or1k
arch/powerpc
arch/powerpc64
arch/propeller
arch/riscv32
arch/riscv64
arch/rl78
arch/rx
arch/s390x
arch/sh
arch/sparc
arch/sparc64
arch/spirv
arch/spu
arch/tricore
arch/v850
arch/vax
arch/vc4
arch/ve
arch/wasm
arch/x86
arch/x86_64
arch/xcore
arch/xtensa
autodoc
The web application for interactive documentation and generation of its assets.
backend/c
The C backend outputs C source code.
backend/llvm
The LLVM backend outputs an LLVM bitcode module.
backend/self-hosted
The self-hosted backends produce machine code directly.
binutils
Zig's included binary utilities: zig ar, zig dlltool, zig lib, zig ranlib, zig objcopy, and zig rc.
breaking
Implementing this issue could cause existing code to no longer compile or have different behavior.
build system
The Zig build system - zig build, std.Build, the build runner, and package management.
debug info
An issue related to debug information (e.g. DWARF) produced by the Zig compiler.
docs
An issue with documentation, e.g. the language reference or standard library doc comments.
error message
This issue points out an error message that is unhelpful and should be improved.
frontend
Tokenization, parsing, AstGen, ZonGen, Sema, Legalize, and Liveness.
fuzzing
An issue related to Zig's integrated fuzz testing.
incremental
Reuse of internal compiler state for faster compilation.
lib/c
This issue relates to Zig's libc implementation and/or vendored libcs.
lib/compiler-rt
This issue relates to Zig's compiler-rt library.
lib/cxx
This issue relates to Zig's vendored libc++ and/or libc++abi.
lib/std
This issue relates to Zig's standard library.
lib/tsan
This issue relates to Zig's vendored libtsan.
lib/ubsan-rt
This issue relates to Zig's ubsan-rt library.
lib/unwind
This issue relates to Zig's vendored libunwind.
linking
Zig's integrated object file and incremental linker.
miscompilation
The compiler reports success but produces semantically incorrect code.
os/android
os/contiki
os/dragonfly
os/driverkit
os/emscripten
os/freebsd
os/fuchsia
os/haiku
os/hermit
os/hurd
os/illumos
os/ios
os/linux
os/maccatalyst
os/macos
os/managarm
os/netbsd
os/ohos
os/openbsd
os/plan9
os/redox
os/rtems
os/serenity
os/tvos
os/uefi
os/visionos
os/wasi
os/watchos
os/windows
proposal
This issue suggests language modifications. If it also has the "accepted" label then it is planned.
release notes
This issue or pull request should be mentioned in the release notes.
testing
This issue is related to testing the compiler, standard library, or other parts of Zig.
zig cc
Zig as a drop-in C-family compiler.
zig fmt
The Zig source code formatter.
zig reduce
The Zig source code reduction tool.
bounty
https://ziglang.org/news/announcing-donor-bounties
bug
Observed behavior contradicts documented or intended behavior.
contributor-friendly
This issue is limited in scope and/or knowledge of project internals.
downstream
An issue with a third-party project that uses this project.
enhancement
Solving this issue will likely involve adding new logic or components to the codebase.
infra
An issue related to project infrastructure, e.g. continuous integration.
optimization
A task to improve performance and/or resource usage.
question
No questions on the issue tracker; use a community space instead.
regression
Something that used to work in a previous version stopped working
upstream
An issue with a third-party project that this project uses.
use case
Describes a real use case that is difficult or impossible, but does not propose a solution.
No labels
abi/f32
abi/ilp32
abi/sf
accepted
arch/21k
arch/6502
arch/aarch64
arch/alpha
arch/amdgcn
arch/arc
arch/arc32
arch/arc64
arch/arm
arch/avr
arch/bfin
arch/bpf
arch/colossus
arch/cris
arch/csky
arch/dlx
arch/epiphany
arch/fr30
arch/frv
arch/hexagon
arch/hppa
arch/hppa64
arch/ia64
arch/kalimba
arch/kvx
arch/lanai
arch/lm32
arch/loongarch32
arch/loongarch64
arch/m32r
arch/m68k
arch/m88k
arch/mcore
arch/microblaze
arch/mips
arch/mips64
arch/mmix
arch/moxie
arch/mrisc32
arch/msp430
arch/nds32
arch/ns32k
arch/nvptx
arch/or1k
arch/powerpc
arch/powerpc64
arch/propeller
arch/riscv32
arch/riscv64
arch/rl78
arch/rx
arch/s390x
arch/sh
arch/sparc
arch/sparc64
arch/spirv
arch/spu
arch/tricore
arch/v850
arch/vax
arch/vc4
arch/ve
arch/wasm
arch/x86
arch/x86_64
arch/xcore
arch/xtensa
autodoc
backend/c
backend/llvm
backend/self-hosted
binutils
breaking
build system
debug info
docs
error message
frontend
fuzzing
incremental
lib/c
lib/compiler-rt
lib/cxx
lib/std
lib/tsan
lib/ubsan-rt
lib/unwind
linking
miscompilation
os/android
os/contiki
os/dragonfly
os/driverkit
os/emscripten
os/freebsd
os/fuchsia
os/haiku
os/hermit
os/hurd
os/illumos
os/ios
os/linux
os/maccatalyst
os/macos
os/managarm
os/netbsd
os/ohos
os/openbsd
os/plan9
os/redox
os/rtems
os/serenity
os/tvos
os/uefi
os/visionos
os/wasi
os/watchos
os/windows
proposal
release notes
testing
zig cc
zig fmt
zig reduce
bounty
bug
contributor-friendly
downstream
enhancement
infra
optimization
question
regression
upstream
use case
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
8 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ziglang/zig#31077
Reference in a new issue
ziglang/zig
No description provided.
Delete branch "%!s()"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?