1
0
mirror of https://github.com/containers/bootc.git synced 2026-02-05 15:45:53 +01:00

utils: Always print status to stderr

If we were waiting on a lock as part of `bootc status --format=json`
this information message would end up in stderr, corrupting the output.

Signed-off-by: Colin Walters <walters@verbum.org>
This commit is contained in:
Colin Walters
2026-01-13 14:19:51 -05:00
parent 87e20d6b80
commit 3b0f38aa19
2 changed files with 45 additions and 26 deletions

View File

@@ -158,10 +158,10 @@ pub(crate) fn medium_visibility_warning(s: &str) {
std::thread::sleep(std::time::Duration::from_secs(1));
}
/// Call an async task function, and write a message to stdout
/// Call an async task function, and write a message to stderr
/// with an automatic spinner to show that we're not blocked.
/// Note that generally the called function should not output
/// anything to stdout as this will interfere with the spinner.
/// anything to stderr as this will interfere with the spinner.
pub(crate) async fn async_task_with_spinner<F, T>(msg: &str, f: F) -> T
where
F: Future<Output = T>,
@@ -175,8 +175,8 @@ where
// We need to handle the case where we aren't connected to
// a tty, so indicatif would show nothing by default.
if pb.is_hidden() {
print!("{msg}...");
std::io::stdout().flush().unwrap();
eprint!("{msg}...");
std::io::stderr().flush().unwrap();
}
let r = f.await;
let elapsed = HumanDuration(start_time.elapsed());
@@ -185,7 +185,7 @@ where
&format!("completed task in {elapsed}: {msg}"),
);
if pb.is_hidden() {
println!("done ({elapsed})");
eprintln!("done ({elapsed})");
} else {
pb.finish_with_message(format!("{msg}: done ({elapsed})"));
}

View File

@@ -1,32 +1,51 @@
# Verify we can spawn multiple bootc status at the same time
# Verify we can spawn multiple bootc status at the same time and get valid JSON
use std assert
use tap.nu
tap begin "concurrent bootc status"
# Fork via systemd-run
# Create a temporary directory for output files
let tmpdir = mktemp -d
print $"Using temporary directory: ($tmpdir)"
# Number of concurrent invocations
let n = 10
0..$n | each { |v|
# Clean up prior runs
systemctl stop $"bootc-status-($v)" | complete
}
# Fork off a concurrent bootc status
0..$n | each { |v|
systemd-run --no-block -qr -u $"bootc-status-($v)" bootc status
# Create systemd unit files for concurrent bootc status commands.
# Writing actual unit files allows proper dependency tracking.
let units = 0..<$n | each { |v|
let unit_name = $"bootc-status-test-($v).service"
let outpath = $"($tmpdir)/($v).json"
let unit_content = $"[Unit]
Description=Test bootc status ($v)
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'bootc status --format=json > ($outpath)'
"
$unit_content | save -f $"/run/systemd/system/($unit_name)"
$unit_name
}
# Await completion
0..$n | each { |v|
loop {
let r = systemctl is-active $"bootc-status-($v)" | complete
if $r.exit_code == 0 {
break
}
# check status
systemctl status $"bootc-status-($v)" out> /dev/null
# Clean it up
systemctl reset-failed $"bootc-status-($v)"
}
# Reload systemd to pick up the new units.
systemctl daemon-reload
# Use systemd-run to create a transient sync unit with After= and Requires=
# dependencies on all worker units. --wait blocks until completion.
let dep_args = $units | each { |u| [$"--property=After=($u)" $"--property=Requires=($u)"] } | flatten
systemd-run --wait ...$dep_args -- true
# Verify each output file contains valid JSON with the expected structure.
# This is a regression test for spinner output polluting stdout.
for v in 0..<$n {
let path = $"($tmpdir)/($v).json"
# open automatically parses JSON files, so we get a record directly
# If the file had spinner output mixed in, this would fail to parse
let st = open $path
assert equal $st.apiVersion org.containers.bootc/v1 $"($path) should contain valid bootc status JSON"
}
# Clean up
rm -rf $tmpdir
tap ok