Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions compositor/wlproxy/tests/live.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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()
}
Expand All @@ -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);
Expand Down Expand Up @@ -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}");

Expand Down
23 changes: 13 additions & 10 deletions tools/tests/proxy-calls.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -59,13 +59,15 @@ 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.
# "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]+\]|[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")"
Expand Down Expand Up @@ -95,14 +97,15 @@ 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]+\]|[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 {
print " first " c ", in " FILENAME ":"; print " " substr(exec, 1, 240)
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"
Expand Down
Loading