Skip to content

Commit

Permalink
checkpoint regression test demonstrating failure of rust issue 101913.
Browse files Browse the repository at this point in the history
  • Loading branch information
pnkfelix committed Oct 21, 2022
1 parent 3344386 commit 75d6b28
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 9 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 130,8 @@ edition = '2018'
name = "concurrent-panics"
required-features = ["std"]
harness = false

[[test]]
name = "current-exe-mismatch"
required-features = ["std"]
harness = false
14 changes: 14 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -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)
}
13 changes: 4 additions & 9 deletions tests/concurrent-panics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 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;
Expand Down
133 changes: 133 additions & 0 deletions tests/current-exe-mismatch.rs
Original file line number Diff line number Diff line change
@@ -0,0 1,133 @@
// 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::process::Command;
use std::path::{Path, PathBuf};
use std::io::{BufRead, BufReader};

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()
.unwrap();
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: ";
if let Some((_, suffix)) = line.split_once(prefix) {
if let Some((found_path, _ignore_suffix)) = suffix.rsplit_once("]") {
return Ok(found_path.into());
}
}
}

Err(EarlyExit::IgnoreTest("could not find interpreter from readelf output".into()))
} else {
Err(EarlyExit::IgnoreTest("readelf invocation failed".into()))
}
}

0 comments on commit 75d6b28

Please sign in to comment.