From 366445b34926647aa044d743f20fbdff7700449d Mon Sep 17 00:00:00 2001 From: Simon Chopin Date: Mon, 17 Aug 2026 19:57:25 +0200 Subject: [PATCH 1/2] tests: forward RUST_BACKTRACE to the Android runner --- .cargo/android-runner.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cargo/android-runner.sh b/.cargo/android-runner.sh index cfd908bf..23b55b85 100755 --- a/.cargo/android-runner.sh +++ b/.cargo/android-runner.sh @@ -10,4 +10,4 @@ TARGET=$(printf '%s' "$BINARY" | sed -E 's#(.*/)?target/([^/]+)/.*#\2#') # Make sure to run the following to copy the test helper binary over. # cargo run --target "$TARGET" --bin test adb push "$BINARY" "/data/local/$BINARY" -adb shell "chmod 777 /data/local/$BINARY && env TEST_HELPER=/data/local/target/$TARGET/debug/test /data/local/$BINARY" "$@" +adb shell "chmod 777 /data/local/$BINARY && env RUST_BACKTRACE='$RUST_BACKTRACE' TEST_HELPER=/data/local/target/$TARGET/debug/test /data/local/$BINARY" "$@" From 9880eda5542b3cfdca440e33502146ec52655d82 Mon Sep 17 00:00:00 2001 From: Simon Chopin Date: Mon, 17 Aug 2026 20:09:36 +0200 Subject: [PATCH 2/2] tests: guard child spawn with a mutex to avoid ptrace() race You can't have multiple children attaching to the parent at the same time, this forces serialization of this part of the tests. I would often see failures on my Android emulator because of this. Funnily enough the issue wasn't triggered on desktop Linux because the tests run through `cargo run` which adds enough delay to avoid the race window. This replaces the previous approach that relied on each test explicitly taking out the lock. --- tests/common/mod.rs | 13 ++++++++++++- tests/ptrace_dumper.rs | 26 -------------------------- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 99d6af7f..5be005ba 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -39,12 +39,23 @@ fn build_command() -> Command { cmd } +/// Must be taken by tests that use ptrace() to avoid racing on attaching to the parent, +/// leading to random failures. +static PTRACE_PARENT_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + #[allow(unused)] pub fn spawn_child(command: &str, args: &[&str]) { let mut cmd = build_command(); cmd.arg(command).args(args); - let child = cmd.output().expect("failed to execute child"); + let child = { + // Panicking is fine as far as ptrace is concerned, + // no need to poison other tests. + let _guard = PTRACE_PARENT_LOCK + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + cmd.output().expect("failed to execute child") + }; println!("Child output:"); println!("===stdout==="); diff --git a/tests/ptrace_dumper.rs b/tests/ptrace_dumper.rs index 75be7002..cdbb6f79 100644 --- a/tests/ptrace_dumper.rs +++ b/tests/ptrace_dumper.rs @@ -12,7 +12,6 @@ use { mem::size_of, os::unix::process::ExitStatusExt, ptr, - sync::Mutex, }, }; @@ -36,22 +35,13 @@ macro_rules! assert_no_soft_errors(($n: ident, $e: expr) => {{ __result }}); -static GLOBAL_LOCK: Mutex<()> = Mutex::new(()); - -macro_rules! one_at_a_time(() => { - let _guard_ = GLOBAL_LOCK.lock().unwrap_or_else(|e| e.into_inner()); -}); - #[test] fn setup() { - one_at_a_time!(); - spawn_child("setup", &[]); } #[test] fn thread_list_from_child() { - one_at_a_time!(); // Child spawns and looks in the parent (== this process) for its own thread-ID let (tx, rx) = std::sync::mpsc::sync_channel(1); @@ -110,8 +100,6 @@ fn thread_list_from_child() { #[test] fn thread_list_from_parent() { - one_at_a_time!(); - let num_of_threads = 5; let mut child = start_child_and_wait_for_threads(num_of_threads); let pid = child.id() as i32; @@ -187,23 +175,17 @@ fn thread_list_from_parent() { #[test] // Ensure that the linux-gate VDSO is included in the mapping list. fn mappings_include_linux_gate() { - one_at_a_time!(); - spawn_child("mappings_include_linux_gate", &[]); } #[test] fn linux_gate_mapping_id() { - one_at_a_time!(); - disabled_on_ci_and_android!(); spawn_child("linux_gate_mapping_id", &[]); } #[test] fn merges_mappings() { - one_at_a_time!(); - let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; assert!(page_size > 0); let page_size = usize::try_from(page_size).unwrap(); @@ -256,8 +238,6 @@ fn merges_mappings() { // Ensure that the linux-gate VDSO is included in the mapping list. #[test] fn file_id() { - one_at_a_time!(); - disabled_on_ci_and_android!(); spawn_child("file_id", &[]); } @@ -265,8 +245,6 @@ fn file_id() { #[cfg(not(target_os = "android"))] #[test] fn finds_mappings() { - one_at_a_time!(); - spawn_child( "find_mappings", &[ @@ -278,8 +256,6 @@ fn finds_mappings() { #[test] fn copies_from_process_self() { - one_at_a_time!(); - disabled_on_ci_and_android!(); let stack_var: libc::c_long = 0x11223344; @@ -296,8 +272,6 @@ fn copies_from_process_self() { // Ensures that we sanitize the stack properly #[test] fn sanitizes_stack_copies() { - one_at_a_time!(); - let num_of_threads = 1; let mut child = start_child_and_return(&["spawn_alloc_wait"]); let pid = child.id() as i32;