diff options
Diffstat (limited to 'library/backtrace/tests')
-rw-r--r-- | library/backtrace/tests/common/mod.rs | 14 | ||||
-rw-r--r-- | library/backtrace/tests/concurrent-panics.rs | 14 | ||||
-rw-r--r-- | library/backtrace/tests/current-exe-mismatch.rs | 137 | ||||
-rw-r--r-- | library/backtrace/tests/skip_inner_frames.rs | 2 |
4 files changed, 156 insertions, 11 deletions
diff --git a/library/backtrace/tests/common/mod.rs b/library/backtrace/tests/common/mod.rs new file mode 100644 index 000000000..3c07934fd --- /dev/null +++ b/library/backtrace/tests/common/mod.rs @@ -0,0 +1,14 @@ +/// Some tests only make sense in contexts where they can re-exec the test +/// itself. Not all contexts support this, so you can call this method to find +/// out which case you are in. +pub fn cannot_reexec_the_test() -> bool { + // These run in docker containers on CI where they can't re-exec the test, + // so just skip these for CI. No other reason this can't run on those + // platforms though. + // Miri does not have support for re-execing a file + cfg!(unix) + && (cfg!(target_arch = "arm") + || cfg!(target_arch = "aarch64") + || cfg!(target_arch = "s390x")) + || cfg!(miri) +} diff --git a/library/backtrace/tests/concurrent-panics.rs b/library/backtrace/tests/concurrent-panics.rs index 470245cc9..a44a26771 100644 --- a/library/backtrace/tests/concurrent-panics.rs +++ b/library/backtrace/tests/concurrent-panics.rs @@ -9,17 +9,11 @@ const PANICS: usize = 100; const THREADS: usize = 8; const VAR: &str = "__THE_TEST_YOU_ARE_LUKE"; +mod common; + fn main() { - // These run in docker containers on CI where they can't re-exec the test, - // so just skip these for CI. No other reason this can't run on those - // platforms though. - // Miri does not have support for re-execing a file - if cfg!(unix) - && (cfg!(target_arch = "arm") - || cfg!(target_arch = "aarch64") - || cfg!(target_arch = "s390x")) - || cfg!(miri) - { + // If we cannot re-exec this test, there's no point in trying to do it. + if common::cannot_reexec_the_test() { println!("test result: ok"); return; } diff --git a/library/backtrace/tests/current-exe-mismatch.rs b/library/backtrace/tests/current-exe-mismatch.rs new file mode 100644 index 000000000..21c67bcbf --- /dev/null +++ b/library/backtrace/tests/current-exe-mismatch.rs @@ -0,0 +1,137 @@ +// rust-lang/rust#101913: when you run your program explicitly via `ld.so`, +// `std::env::current_exe` will return the path of *that* program, and not +// the Rust program itself. + +use std::io::{BufRead, BufReader}; +use std::path::{Path, PathBuf}; +use std::process::Command; + +mod common; + +fn main() { + if std::env::var(VAR).is_err() { + // the parent waits for the child; then we then handle either printing + // "test result: ok", "test result: ignored", or panicking. + match parent() { + Ok(()) => { + println!("test result: ok"); + } + Err(EarlyExit::IgnoreTest(_)) => { + println!("test result: ignored"); + } + Err(EarlyExit::IoError(e)) => { + println!("{} parent encoutered IoError: {:?}", file!(), e); + panic!(); + } + } + } else { + // println!("{} running child", file!()); + child().unwrap(); + } +} + +const VAR: &str = "__THE_TEST_YOU_ARE_LUKE"; + +#[derive(Debug)] +enum EarlyExit { + IgnoreTest(String), + IoError(std::io::Error), +} + +impl From<std::io::Error> for EarlyExit { + fn from(e: std::io::Error) -> Self { + EarlyExit::IoError(e) + } +} + +fn parent() -> Result<(), EarlyExit> { + // If we cannot re-exec this test, there's no point in trying to do it. + if common::cannot_reexec_the_test() { + return Err(EarlyExit::IgnoreTest("(cannot reexec)".into())); + } + + let me = std::env::current_exe().unwrap(); + let ld_so = find_interpreter(&me)?; + + // use interp to invoke current exe, yielding child test. + // + // (if you're curious what you might compare this against, you can try + // swapping in the below definition for `result`, which is the easy case of + // not using the ld.so interpreter directly that Rust handled fine even + // prior to resolution of rust-lang/rust#101913.) + // + // let result = Command::new(me).env(VAR, "1").output()?; + let result = Command::new(ld_so).env(VAR, "1").arg(&me).output().unwrap(); + + if result.status.success() { + return Ok(()); + } + println!("stdout:\n{}", String::from_utf8_lossy(&result.stdout)); + println!("stderr:\n{}", String::from_utf8_lossy(&result.stderr)); + println!("code: {}", result.status); + panic!(); +} + +fn child() -> Result<(), EarlyExit> { + let bt = backtrace::Backtrace::new(); + println!("{:?}", bt); + + let mut found_my_name = false; + + let my_filename = file!(); + 'frames: for frame in bt.frames() { + let symbols = frame.symbols(); + if symbols.is_empty() { + continue; + } + + for sym in symbols { + if let Some(filename) = sym.filename() { + if filename.ends_with(my_filename) { + // huzzah! + found_my_name = true; + break 'frames; + } + } + } + } + + assert!(found_my_name); + + Ok(()) +} + +// we use the `readelf` command to extract the path to the interpreter requested +// by our binary. +// +// if we cannot `readelf` for some reason, or if we fail to parse its output, +// then we will just give up on this test (and not treat it as a test failure). +fn find_interpreter(me: &Path) -> Result<PathBuf, EarlyExit> { + let result = Command::new("readelf") + .arg("-l") + .arg(me) + .output() + .map_err(|_err| EarlyExit::IgnoreTest("readelf invocation failed".into()))?; + if result.status.success() { + let r = BufReader::new(&result.stdout[..]); + for line in r.lines() { + let line = line?; + let line = line.trim(); + let prefix = "[Requesting program interpreter: "; + // This could use `line.split_once` and `suffix.rsplit_once` once the MSRV passes 1.52 + if let Some(idx) = line.find(prefix) { + let (_, suffix) = line.split_at(idx + prefix.len()); + if let Some(idx) = suffix.rfind("]") { + let (found_path, _ignore_remainder) = suffix.split_at(idx); + return Ok(found_path.into()); + } + } + } + + Err(EarlyExit::IgnoreTest( + "could not find interpreter from readelf output".into(), + )) + } else { + Err(EarlyExit::IgnoreTest("readelf returned non-success".into())) + } +} diff --git a/library/backtrace/tests/skip_inner_frames.rs b/library/backtrace/tests/skip_inner_frames.rs index 8b57bef52..60bba35e6 100644 --- a/library/backtrace/tests/skip_inner_frames.rs +++ b/library/backtrace/tests/skip_inner_frames.rs @@ -4,7 +4,7 @@ use backtrace::Backtrace; // function for frames which reports the starting address of a symbol. As a // result it's only enabled on a few platforms. const ENABLED: bool = cfg!(all( - // Windows hasn't really been tested, and OSX doesn't support actually + // Windows hasn't really been tested, and macOS doesn't support actually // finding an enclosing frame, so disable this target_os = "linux", // On ARM finding the enclosing function is simply returning the ip itself. |