From 610c0eb32119822e3882228abeb31ea3e6998cd0 Mon Sep 17 00:00:00 2001 From: DevomB Date: Thu, 15 Oct 2026 01:02:47 -0700 Subject: [PATCH 1/2] The proxy-calls check traces each proxy alone, so no call of the tests' own is read as the proxy's Traced whole under strace -ff, the live tests left a call of their own in a proxy's file now and then: in one run, a setsockopt(SO_SNDTIMEO) the tests make when they connect followed the proxy's last sendmsg in its file, with no exit line between, though the proxy has no such call. With KRYPTIK_PROXY_TRACE set, the tests start each proxy under a strace of its own, one file a start, and the check reads only those. A test's Drop kills the proxy first, so its strace ends with it, and the descriptor test reads the proxy's own limits. --- compositor/wlproxy/tests/live.rs | 39 +++++++++++++++++++++++++++++--- tools/tests/proxy-calls.sh | 20 ++++++++-------- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/compositor/wlproxy/tests/live.rs b/compositor/wlproxy/tests/live.rs index 0ca0ed26..51f61e55 100644 --- a/compositor/wlproxy/tests/live.rs +++ b/compositor/wlproxy/tests/live.rs @@ -91,6 +91,22 @@ fn decode_string(body: &[u8]) -> String { static COUNTER: AtomicUsize = AtomicUsize::new(0); +/// The proxy as a test starts it, or, for the proxy-calls check (KRYPTIK_PROXY_TRACE names a +/// directory), under a strace of its own writing one file for this start: the tests' own calls +/// are never traced, so none can be read as the proxy's. +fn proxy_command() -> Command { + let bin = env!("CARGO_BIN_EXE_kryptik-wlproxy"); + let Some(dir) = std::env::var_os("KRYPTIK_PROXY_TRACE") else { return Command::new(bin) }; + let n = TRACED.fetch_add(1, Ordering::SeqCst); + let mut cmd = Command::new("strace"); + cmd.args(["-f", "-q", "-s", "256", "-o"]) + .arg(Path::new(&dir).join(format!("proxy.{}.{n}", std::process::id()))) + .arg("--") + .arg(bin); + cmd +} +static TRACED: AtomicUsize = AtomicUsize::new(0); + struct Proxy { child: Child, dir: PathBuf, @@ -113,7 +129,7 @@ impl Proxy { let listen = dir.join("wayland-0"); let upstream = UnixListener::bind(&upstream_path).unwrap(); upstream.set_nonblocking(true).unwrap(); - let mut cmd = Command::new(env!("CARGO_BIN_EXE_kryptik-wlproxy")); + let mut cmd = proxy_command(); if let Some((soft, hard)) = nofile { let rl = libc::rlimit { rlim_cur: soft, rlim_max: hard }; unsafe { @@ -165,7 +181,7 @@ impl Proxy { assert!(up >= 0, "{}: {}", upstream_path.display(), std::io::Error::last_os_error()); let control = UnixListener::bind(dir.join("control")).unwrap(); let keep = [listener.as_raw_fd(), up, control.as_raw_fd()]; - let mut cmd = Command::new(env!("CARGO_BIN_EXE_kryptik-wlproxy")); + let mut cmd = proxy_command(); unsafe { cmd.pre_exec(move || { // All copied above 9 first, so placing one cannot overwrite another. @@ -223,6 +239,18 @@ impl Proxy { (client, up) } + /// The proxy's own pid: the child's, or under the check's strace that child's one child. + fn proxy_pid(&self) -> u32 { + let pid = self.child.id(); + if std::env::var_os("KRYPTIK_PROXY_TRACE").is_none() { + return pid; + } + std::fs::read_to_string(format!("/proc/{pid}/task/{pid}/children")) + .ok() + .and_then(|s| s.split_whitespace().next()?.parse().ok()) + .unwrap_or(pid) + } + fn alive(&mut self) -> bool { self.child.try_wait().unwrap().is_none() } @@ -236,6 +264,11 @@ impl Proxy { impl Drop for Proxy { fn drop(&mut self) { + // Under the check's strace the proxy is killed first, and strace ends with it. + let pid = self.proxy_pid(); + if pid != self.child.id() { + unsafe { libc::kill(pid as libc::pid_t, libc::SIGKILL) }; + } let _ = self.child.kill(); let _ = self.child.wait(); let _ = std::fs::remove_dir_all(&self.dir); @@ -381,7 +414,7 @@ fn send_fds(s: &UnixStream, data: &[u8], fds: &[RawFd]) { fn clients_fit_descriptor_limit() { // (300 - 8) / 130: two sessions. let mut p = Proxy::start_with("work", &["--max-clients", "32"], Some((100, 300))); - let limits = std::fs::read_to_string(format!("/proc/{}/limits", p.child.id())).unwrap(); + let limits = std::fs::read_to_string(format!("/proc/{}/limits", p.proxy_pid())).unwrap(); let files = limits.lines().find(|l| l.starts_with("Max open files")).unwrap(); assert_eq!(files.split_whitespace().nth(3), Some("300"), "the soft limit was not raised: {files}"); diff --git a/tools/tests/proxy-calls.sh b/tools/tests/proxy-calls.sh index 23d0ff9f..e6d4d7dc 100755 --- a/tools/tests/proxy-calls.sh +++ b/tools/tests/proxy-calls.sh @@ -28,13 +28,13 @@ if ! "${build[@]}" --no-run > "$T/build.log" 2>&1; then echo "proxy calls: FAIL, the live tests did not build for $TARGET" exit 1 fi -# strace slows every call the tests and the proxy make, so a failed run is named, test and -# panic, and run once more; the trace checked is the run that passed. -traced() { # traced DIR: the live tests under strace, their output in DIR/test.log +# strace slows every call the proxy makes, so a failed run is named, test and panic, and run once +# more; the trace checked is the run that passed. +traced() { # traced DIR: the live tests, each proxy under strace into DIR/proxy.*, output DIR/test.log mkdir -p "$1" - # -q, not -qq: a process's "+++ exited" or "+++ killed" line ends its run in the file, which a - # later thread given the same id appends to. - strace -f -ff -q -s 256 -o "$1/trace" "${build[@]}" > "$1/test.log" 2>&1 + # The tests start each proxy under a strace of its own (live.rs: proxy_command), one file a + # start: traced whole, the tests' threads could leave their calls in a proxy's file. + KRYPTIK_PROXY_TRACE="$1" "${build[@]}" > "$1/test.log" 2>&1 } failures() { sed -n '/^failures:$/,$p' "$1/test.log" | head -80; } run="$T/run" @@ -59,13 +59,14 @@ if [[ "$rc" -ne 0 ]]; then exit 1 fi -# One file per process id: from the line that execs the proxy to the line that ends it, every +# One file per proxy start: from the line that execs the proxy to the line that ends it, every # call it made, marked "fd" when it was started as the launch daemon starts it (--listen-fd) and # "path" when it was given a path to bind, as only these tests do. awk 'FNR == 1 { on = 0 } + { sub(/^\[pid +[0-9]+\] /, "") } !on && /^execve\("[^"]*\/kryptik-wlproxy", / && / = 0$/ { on = 1; mode = index($0, "\"--listen-fd\"") ? "fd" : "path" } on && /^\+\+\+ (exited|killed) / { on = 0 } - on && /^[a-z_0-9]+\(/ { print mode "\t" $0 }' "$run"/trace.* > "$T/calls" + on && /^[a-z_0-9]+\(/ { print mode "\t" $0 }' "$run"/proxy.* > "$T/calls" cut -f2- "$T/calls" > "$T/lines" runs="$(grep -c '^execve(' "$T/lines")" fd_runs="$(grep -c "^fd execve(" "$T/calls")" @@ -95,6 +96,7 @@ if [[ -n "$missing" || -n "$args" ]]; then # three calls before it. for c in $missing; do awk -v c="$c" 'FNR == 1 { on = 0; n = 0 } + { sub(/^\[pid +[0-9]+\] /, "") } !on && /^execve\("[^"]*\/kryptik-wlproxy", / && / = 0$/ { on = 1; exec = $0; next } on && /^\+\+\+ (exited|killed) / { on = 0; n = 0; next } on && index($0, c "(") == 1 { @@ -102,7 +104,7 @@ if [[ -n "$missing" || -n "$args" ]]; then for (i = n - 3; i < n; i++) if (i >= 0) print " " substr(prev[i % 3], 1, 200) print " > " substr($0, 1, 240); exit } - on { prev[n % 3] = $0; n++ }' "$run"/trace.* + on { prev[n % 3] = $0; n++ }' "$run"/proxy.* done [[ -n "$args" ]] && echo "made with arguments its filter kills: $(sort -u <<<"$args" | head -5 | tr '\n' ';')" echo "proxy calls: FAIL" From 60221dcc051d04d9be247534a5e67e8d91ff99fc Mon Sep 17 00:00:00 2001 From: DevomB Date: Thu, 15 Oct 2026 01:06:31 -0700 Subject: [PATCH 2/2] The proxy-calls check reads strace's lines after the task id it writes before each in a file strace -f -o FILE puts each line after its task's id, so no line began with the proxy's execve and no run was counted. Both forms are dropped, the file's and the terminal's [pid N], and one file a start stays, so a thread the proxy may one day start is read with it. --- tools/tests/proxy-calls.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/tests/proxy-calls.sh b/tools/tests/proxy-calls.sh index e6d4d7dc..b66a67c7 100755 --- a/tools/tests/proxy-calls.sh +++ b/tools/tests/proxy-calls.sh @@ -61,9 +61,10 @@ fi # One file per proxy start: from the line that execs the proxy to the line that ends it, every # call it made, marked "fd" when it was started as the launch daemon starts it (--listen-fd) and -# "path" when it was given a path to bind, as only these tests do. +# "path" when it was given a path to bind, as only these tests do. strace -f writes each line +# after its task's id, so a thread the proxy may one day start is read in the same file. awk 'FNR == 1 { on = 0 } - { sub(/^\[pid +[0-9]+\] /, "") } + { sub(/^(\[pid +[0-9]+\]|[0-9]+) +/, "") } !on && /^execve\("[^"]*\/kryptik-wlproxy", / && / = 0$/ { on = 1; mode = index($0, "\"--listen-fd\"") ? "fd" : "path" } on && /^\+\+\+ (exited|killed) / { on = 0 } on && /^[a-z_0-9]+\(/ { print mode "\t" $0 }' "$run"/proxy.* > "$T/calls" @@ -96,7 +97,7 @@ if [[ -n "$missing" || -n "$args" ]]; then # three calls before it. for c in $missing; do awk -v c="$c" 'FNR == 1 { on = 0; n = 0 } - { sub(/^\[pid +[0-9]+\] /, "") } + { sub(/^(\[pid +[0-9]+\]|[0-9]+) +/, "") } !on && /^execve\("[^"]*\/kryptik-wlproxy", / && / = 0$/ { on = 1; exec = $0; next } on && /^\+\+\+ (exited|killed) / { on = 0; n = 0; next } on && index($0, c "(") == 1 {