test: keep cargo test output quiet with per-test logs and a failure matrix

cargo test used to be unreadable: subprocesses inherited the terminal, so
dpkg-buildpackage, apt and configure output interleaved with the harness
summary, and env_logger lines from parallel tests crossed each other.

New test_support module, compiled into test binaries only (inert stubs
otherwise) and initialized before main via .init_array:

- all log output goes to target/pkh-test-logs/<test>.log, one file per
  test thread, so concurrent tests never interleave
- context-launched commands are captured line by line into the same file
  (driver-level wrapper); test-code spawns use run_logged()
- a panic hook records failures and an atexit callback prints a matrix
  (test name, panic location, message, log path) after the libtest
  summary; tests panicking on purpose can opt out with a guard

Also fixes two test bugs found on the way:

- diff_checkbuilddeps_matrix compared dpkg-checkbuilddeps diagnostics
  against English messages without pinning the locale
- run_source_build in differential tests now captures output like the
  live-UI path does
This commit is contained in:
2026-09-17 15:18:23 +02:00
parent 3ed95725e4
commit 4c26122357
6 changed files with 532 additions and 25 deletions
+32 -24
View File
@@ -210,7 +210,10 @@ pub fn run_source_build(
opts: &SourceBuildOptions,
ui: Option<Arc<DebUi>>,
) -> Result<SourceBuildOutput, Box<dyn Error>> {
let sink: Option<Arc<dyn LineSink>> = ui.as_ref().map(|u| u.sink());
// Without a live UI, test runs still capture command output into the
// per-test log file instead of letting it inherit the terminal
let sink: Option<Arc<dyn LineSink>> =
ui.as_ref().map(|u| u.sink()).or_else(crate::test_support::subprocess_sink);
// ------------------------------------------------------------------
// 1. Sanity checks
// ------------------------------------------------------------------
@@ -1304,7 +1307,7 @@ mod differential_tests {
}
}
cmd.arg("-f").arg(&tarball).arg(&dir_name);
let status = cmd.status().expect("run tar");
let status = crate::test_support::run_logged(&mut cmd).expect("run tar");
assert!(status.success(), "tar failed for {}", tarball.display());
}
@@ -1312,12 +1315,10 @@ mod differential_tests {
}
fn copy_path(src: &Path, dst_root: &Path) {
let status = Command::new("cp")
.arg("-a")
.arg(src)
.arg(dst_root)
.status()
.expect("run cp -a");
let status = crate::test_support::run_logged(
Command::new("cp").arg("-a").arg(src).arg(dst_root),
)
.expect("run cp -a");
assert!(
status.success(),
"cp -a {} {} failed",
@@ -1327,11 +1328,12 @@ mod differential_tests {
}
fn run_dpkg(tree: &Path) {
let status = Command::new("dpkg-buildpackage")
.current_dir(tree)
.args(["-S", "-I", "-i", "-nc", "-d", "--no-sign"])
.status()
.expect("failed to run dpkg-buildpackage (is dpkg-dev installed?)");
let status = crate::test_support::run_logged(
Command::new("dpkg-buildpackage")
.current_dir(tree)
.args(["-S", "-I", "-i", "-nc", "-d", "--no-sign"]),
)
.expect("failed to run dpkg-buildpackage (is dpkg-dev installed?)");
assert!(status.success(), "dpkg-buildpackage failed");
}
@@ -1551,8 +1553,12 @@ mod differential_tests {
// builtin dependencies (build-essential:native), matching the
// native checker which knows no builtins. All options must precede
// the control-file operand (POSIX-style option parsing).
// The diagnostics are compared against the native checker's English
// messages, so the tool must run under the C locale regardless of
// the host configuration.
let output = Command::new("dpkg-checkbuilddeps")
.current_dir(dir.path())
.env("LC_ALL", "C")
.arg("--admindir")
.arg(&admindir)
.args(args)
@@ -1782,11 +1788,12 @@ Provides: virtual-thing (= 2.0), plain-virtual
let ours_tree = write_tree(&ours_root);
// Golden side: real dpkg-buildpackage binary build.
let status = Command::new("dpkg-buildpackage")
.current_dir(&golden_tree)
.args(["-b", "-d", "--no-sign"])
.status()
.expect("run dpkg-buildpackage (is dpkg-dev installed?)");
let status = crate::test_support::run_logged(
Command::new("dpkg-buildpackage")
.current_dir(&golden_tree)
.args(["-b", "-d", "--no-sign"]),
)
.expect("run dpkg-buildpackage (is dpkg-dev installed?)");
assert!(status.success(), "golden dpkg-buildpackage -b failed");
// Ours: emulate the pkh deb flow (rules build + rules binary with a
@@ -1811,12 +1818,13 @@ Provides: virtual-thing (= 2.0), plain-virtual
.collect();
for target in ["build", "binary"] {
let status = Command::new("debian/rules")
.current_dir(&ours_tree)
.envs(build_env_vars.clone())
.arg(target)
.status()
.expect("run rules target");
let status = crate::test_support::run_logged(
Command::new("debian/rules")
.current_dir(&ours_tree)
.envs(build_env_vars.clone())
.arg(target),
)
.expect("run rules target");
assert!(status.success(), "debian/rules {target} failed");
}