|
| 1 | +// rust-lang/rust#101913: when you run your program explicitly via `ld.so`, |
| 2 | +// `std::env::current_exe` will return the path of *that* program, and not |
| 3 | +// the Rust program itself. |
| 4 | + |
| 5 | +use std::process::Command; |
| 6 | +use std::path::{Path, PathBuf}; |
| 7 | +use std::io::{BufRead, BufReader}; |
| 8 | + |
| 9 | +mod common; |
| 10 | + |
| 11 | +fn main() { |
| 12 | + if std::env::var(VAR).is_err() { |
| 13 | + // the parent waits for the child; then we then handle either printing |
| 14 | + // "test result: ok", "test result: ignored", or panicking. |
| 15 | + match parent() { |
| 16 | + Ok(()) => { |
| 17 | + println!("test result: ok"); |
| 18 | + } |
| 19 | + Err(EarlyExit::IgnoreTest(_)) => { |
| 20 | + println!("test result: ignored"); |
| 21 | + } |
| 22 | + Err(EarlyExit::IoError(e)) => { |
| 23 | + println!("{} parent encoutered IoError: {:?}", file!(), e); |
| 24 | + panic!(); |
| 25 | + } |
| 26 | + } |
| 27 | + } else { |
| 28 | + // println!("{} running child", file!()); |
| 29 | + child().unwrap(); |
| 30 | + } |
| 31 | +} |
| 32 | + |
| 33 | +const VAR: &str = "__THE_TEST_YOU_ARE_LUKE"; |
| 34 | + |
| 35 | +#[derive(Debug)] |
| 36 | +enum EarlyExit { |
| 37 | + IgnoreTest(String), |
| 38 | + IoError(std::io::Error), |
| 39 | +} |
| 40 | + |
| 41 | +impl From<std::io::Error> for EarlyExit { |
| 42 | + fn from(e: std::io::Error) -> Self { |
| 43 | + EarlyExit::IoError(e) |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +fn parent() -> Result<(), EarlyExit> { |
| 48 | + // If we cannot re-exec this test, there's no point in trying to do it. |
| 49 | + if common::cannot_reexec_the_test() |
| 50 | + { |
| 51 | + return Err(EarlyExit::IgnoreTest("(cannot reexec)".into())); |
| 52 | + } |
| 53 | + |
| 54 | + let me = std::env::current_exe().unwrap(); |
| 55 | + let ld_so = find_interpreter(&me)?; |
| 56 | + |
| 57 | + // use interp to invoke current exe, yielding child test. |
| 58 | + // |
| 59 | + // (if you're curious what you might compare this against, you can try |
| 60 | + // swapping in the below definition for `result`, which is the easy case of |
| 61 | + // not using the ld.so interpreter directly that Rust handled fine even |
| 62 | + // prior to resolution of rust-lang/rust#101913.) |
| 63 | + // |
| 64 | + // let result = Command::new(me).env(VAR, "1").output()?; |
| 65 | + let result = Command::new(ld_so).env(VAR, "1").arg(&me).output().unwrap(); |
| 66 | + |
| 67 | + if result.status.success() { |
| 68 | + return Ok(()); |
| 69 | + } |
| 70 | + println!("stdout:\n{}", String::from_utf8_lossy(&result.stdout)); |
| 71 | + println!("stderr:\n{}", String::from_utf8_lossy(&result.stderr)); |
| 72 | + println!("code: {}", result.status); |
| 73 | + panic!(); |
| 74 | +} |
| 75 | + |
| 76 | +fn child() -> Result<(), EarlyExit> { |
| 77 | + let bt = backtrace::Backtrace::new(); |
| 78 | + println!("{:?}", bt); |
| 79 | + |
| 80 | + let mut found_my_name = false; |
| 81 | + |
| 82 | + let my_filename = file!(); |
| 83 | + 'frames: for frame in bt.frames() { |
| 84 | + let symbols = frame.symbols(); |
| 85 | + if symbols.is_empty() { |
| 86 | + continue; |
| 87 | + } |
| 88 | + |
| 89 | + for sym in symbols { |
| 90 | + if let Some(filename) = sym.filename() { |
| 91 | + if filename.ends_with(my_filename) { |
| 92 | + // huzzah! |
| 93 | + found_my_name = true; |
| 94 | + break 'frames; |
| 95 | + } |
| 96 | + } |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + assert!(found_my_name); |
| 101 | + |
| 102 | + Ok(()) |
| 103 | +} |
| 104 | + |
| 105 | +// we use the `readelf` command to extract the path to the interpreter requested |
| 106 | +// by our binary. |
| 107 | +// |
| 108 | +// if we cannot `readelf` for some reason, or if we fail to parse its output, |
| 109 | +// then we will just give up on this test (and not treat it as a test failure). |
| 110 | +fn find_interpreter(me: &Path) -> Result<PathBuf, EarlyExit> { |
| 111 | + let result = Command::new("readelf") |
| 112 | + .arg("-l") |
| 113 | + .arg(me) |
| 114 | + .output() |
| 115 | + .unwrap(); |
| 116 | + if result.status.success() { |
| 117 | + let r = BufReader::new(&result.stdout[..]); |
| 118 | + for line in r.lines() { |
| 119 | + let line = line?; |
| 120 | + let line = line.trim(); |
| 121 | + let prefix = "[Requesting program interpreter: "; |
| 122 | + if let Some((_, suffix)) = line.split_once(prefix) { |
| 123 | + if let Some((found_path, _ignore_suffix)) = suffix.rsplit_once("]") { |
| 124 | + return Ok(found_path.into()); |
| 125 | + } |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + Err(EarlyExit::IgnoreTest("could not find interpreter from readelf output".into())) |
| 130 | + } else { |
| 131 | + Err(EarlyExit::IgnoreTest("readelf invocation failed".into())) |
| 132 | + } |
| 133 | +} |
0 commit comments