summaryrefslogtreecommitdiffstats
path: root/library/backtrace/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /library/backtrace/tests
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/backtrace/tests')
-rw-r--r--library/backtrace/tests/accuracy/auxiliary.rs15
-rw-r--r--library/backtrace/tests/accuracy/main.rs117
-rw-r--r--library/backtrace/tests/concurrent-panics.rs78
-rw-r--r--library/backtrace/tests/long_fn_name.rs48
-rw-r--r--library/backtrace/tests/skip_inner_frames.rs44
-rw-r--r--library/backtrace/tests/smoke.rs323
6 files changed, 625 insertions, 0 deletions
diff --git a/library/backtrace/tests/accuracy/auxiliary.rs b/library/backtrace/tests/accuracy/auxiliary.rs
new file mode 100644
index 000000000..9c8015d9a
--- /dev/null
+++ b/library/backtrace/tests/accuracy/auxiliary.rs
@@ -0,0 +1,15 @@
+#[inline(never)]
+pub fn callback<F>(f: F)
+where
+ F: FnOnce((&'static str, u32)),
+{
+ f((file!(), line!()))
+}
+
+#[inline(always)]
+pub fn callback_inlined<F>(f: F)
+where
+ F: FnOnce((&'static str, u32)),
+{
+ f((file!(), line!()))
+}
diff --git a/library/backtrace/tests/accuracy/main.rs b/library/backtrace/tests/accuracy/main.rs
new file mode 100644
index 000000000..149203a1b
--- /dev/null
+++ b/library/backtrace/tests/accuracy/main.rs
@@ -0,0 +1,117 @@
+mod auxiliary;
+
+macro_rules! pos {
+ () => {
+ (file!(), line!())
+ };
+}
+
+macro_rules! check {
+ ($($pos:expr),*) => ({
+ verify(&[$($pos,)* pos!()]);
+ })
+}
+
+type Pos = (&'static str, u32);
+
+#[test]
+fn doit() {
+ if
+ // Skip musl which is by default statically linked and doesn't support
+ // dynamic libraries.
+ !cfg!(target_env = "musl")
+ // Skip Miri, since it doesn't support dynamic libraries.
+ && !cfg!(miri)
+ {
+ // TODO(#238) this shouldn't have to happen first in this function, but
+ // currently it does.
+ let mut dir = std::env::current_exe().unwrap();
+ dir.pop();
+ if cfg!(windows) {
+ dir.push("dylib_dep.dll");
+ } else if cfg!(target_os = "macos") {
+ dir.push("libdylib_dep.dylib");
+ } else {
+ dir.push("libdylib_dep.so");
+ }
+ unsafe {
+ let lib = libloading::Library::new(&dir).unwrap();
+ let api = lib.get::<extern "C" fn(Pos, fn(Pos, Pos))>(b"foo").unwrap();
+ api(pos!(), |a, b| {
+ check!(a, b);
+ });
+ }
+ }
+
+ outer(pos!());
+}
+
+#[inline(never)]
+fn outer(main_pos: Pos) {
+ inner(main_pos, pos!());
+ inner_inlined(main_pos, pos!());
+}
+
+#[inline(never)]
+#[rustfmt::skip]
+fn inner(main_pos: Pos, outer_pos: Pos) {
+ check!(main_pos, outer_pos);
+ check!(main_pos, outer_pos);
+ let inner_pos = pos!(); auxiliary::callback(|aux_pos| {
+ check!(main_pos, outer_pos, inner_pos, aux_pos);
+ });
+ let inner_pos = pos!(); auxiliary::callback_inlined(|aux_pos| {
+ check!(main_pos, outer_pos, inner_pos, aux_pos);
+ });
+}
+
+#[inline(always)]
+#[rustfmt::skip]
+fn inner_inlined(main_pos: Pos, outer_pos: Pos) {
+ check!(main_pos, outer_pos);
+ check!(main_pos, outer_pos);
+
+ #[inline(always)]
+ fn inner_further_inlined(main_pos: Pos, outer_pos: Pos, inner_pos: Pos) {
+ check!(main_pos, outer_pos, inner_pos);
+ }
+ inner_further_inlined(main_pos, outer_pos, pos!());
+
+ let inner_pos = pos!(); auxiliary::callback(|aux_pos| {
+ check!(main_pos, outer_pos, inner_pos, aux_pos);
+ });
+ let inner_pos = pos!(); auxiliary::callback_inlined(|aux_pos| {
+ check!(main_pos, outer_pos, inner_pos, aux_pos);
+ });
+
+ // this tests a distinction between two independent calls to the inlined function.
+ // (un)fortunately, LLVM somehow merges two consecutive such calls into one node.
+ inner_further_inlined(main_pos, outer_pos, pos!());
+}
+
+fn verify(filelines: &[Pos]) {
+ let trace = backtrace::Backtrace::new();
+ println!("-----------------------------------");
+ println!("looking for:");
+ for (file, line) in filelines.iter().rev() {
+ println!("\t{}:{}", file, line);
+ }
+ println!("found:\n{:?}", trace);
+ let mut symbols = trace.frames().iter().flat_map(|frame| frame.symbols());
+ let mut iter = filelines.iter().rev();
+ while let Some((file, line)) = iter.next() {
+ loop {
+ let sym = match symbols.next() {
+ Some(sym) => sym,
+ None => panic!("failed to find {}:{}", file, line),
+ };
+ if let Some(filename) = sym.filename() {
+ if let Some(lineno) = sym.lineno() {
+ if filename.ends_with(file) && lineno == *line {
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/library/backtrace/tests/concurrent-panics.rs b/library/backtrace/tests/concurrent-panics.rs
new file mode 100644
index 000000000..470245cc9
--- /dev/null
+++ b/library/backtrace/tests/concurrent-panics.rs
@@ -0,0 +1,78 @@
+use std::env;
+use std::panic;
+use std::process::Command;
+use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
+use std::sync::Arc;
+use std::thread;
+
+const PANICS: usize = 100;
+const THREADS: usize = 8;
+const VAR: &str = "__THE_TEST_YOU_ARE_LUKE";
+
+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)
+ {
+ println!("test result: ok");
+ return;
+ }
+
+ if env::var(VAR).is_err() {
+ parent();
+ } else {
+ child();
+ }
+}
+
+fn parent() {
+ let me = env::current_exe().unwrap();
+ let result = Command::new(&me)
+ .env("RUST_BACKTRACE", "1")
+ .env(VAR, "1")
+ .output()
+ .unwrap();
+ if result.status.success() {
+ println!("test result: ok");
+ return;
+ }
+ 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() {
+ let done = Arc::new(AtomicBool::new(false));
+ let done2 = done.clone();
+ let a = thread::spawn(move || {
+ while !done2.load(SeqCst) {
+ format!("{:?}", backtrace::Backtrace::new());
+ }
+ });
+
+ let threads = (0..THREADS)
+ .map(|_| {
+ thread::spawn(|| {
+ for _ in 0..PANICS {
+ assert!(panic::catch_unwind(|| {
+ panic!();
+ })
+ .is_err());
+ }
+ })
+ })
+ .collect::<Vec<_>>();
+ for thread in threads {
+ thread.join().unwrap();
+ }
+
+ done.store(true, SeqCst);
+ a.join().unwrap();
+}
diff --git a/library/backtrace/tests/long_fn_name.rs b/library/backtrace/tests/long_fn_name.rs
new file mode 100644
index 000000000..fa4cfda15
--- /dev/null
+++ b/library/backtrace/tests/long_fn_name.rs
@@ -0,0 +1,48 @@
+use backtrace::Backtrace;
+
+// 50-character module name
+mod _234567890_234567890_234567890_234567890_234567890 {
+ // 50-character struct name
+ #[allow(non_camel_case_types)]
+ pub struct _234567890_234567890_234567890_234567890_234567890<T>(T);
+ impl<T> _234567890_234567890_234567890_234567890_234567890<T> {
+ #[allow(dead_code)]
+ pub fn new() -> crate::Backtrace {
+ crate::Backtrace::new()
+ }
+ }
+}
+
+// Long function names must be truncated to (MAX_SYM_NAME - 1) characters.
+// Only run this test for msvc, since gnu prints "<no info>" for all frames.
+#[test]
+#[cfg(all(windows, target_env = "msvc"))]
+fn test_long_fn_name() {
+ use _234567890_234567890_234567890_234567890_234567890::_234567890_234567890_234567890_234567890_234567890 as S;
+
+ // 10 repetitions of struct name, so fully qualified function name is
+ // atleast 10 * (50 + 50) * 2 = 2000 characters long.
+ // It's actually longer since it also includes `::`, `<>` and the
+ // name of the current module
+ let bt = S::<S<S<S<S<S<S<S<S<S<i32>>>>>>>>>>::new();
+ println!("{:?}", bt);
+
+ let mut found_long_name_frame = false;
+
+ for frame in bt.frames() {
+ let symbols = frame.symbols();
+ if symbols.is_empty() {
+ continue;
+ }
+
+ if let Some(function_name) = symbols[0].name() {
+ let function_name = function_name.as_str().unwrap();
+ if function_name.contains("::_234567890_234567890_234567890_234567890_234567890") {
+ found_long_name_frame = true;
+ assert!(function_name.len() > 200);
+ }
+ }
+ }
+
+ assert!(found_long_name_frame);
+}
diff --git a/library/backtrace/tests/skip_inner_frames.rs b/library/backtrace/tests/skip_inner_frames.rs
new file mode 100644
index 000000000..8b57bef52
--- /dev/null
+++ b/library/backtrace/tests/skip_inner_frames.rs
@@ -0,0 +1,44 @@
+use backtrace::Backtrace;
+
+// This test only works on platforms which have a working `symbol_address`
+// 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
+ // finding an enclosing frame, so disable this
+ target_os = "linux",
+ // On ARM finding the enclosing function is simply returning the ip itself.
+ not(target_arch = "arm"),
+));
+
+#[test]
+fn backtrace_new_unresolved_should_start_with_call_site_trace() {
+ if !ENABLED {
+ return;
+ }
+ let mut b = Backtrace::new_unresolved();
+ b.resolve();
+ println!("{:?}", b);
+
+ assert!(!b.frames().is_empty());
+
+ let this_ip = backtrace_new_unresolved_should_start_with_call_site_trace as usize;
+ println!("this_ip: {:?}", this_ip as *const usize);
+ let frame_ip = b.frames().first().unwrap().symbol_address() as usize;
+ assert_eq!(this_ip, frame_ip);
+}
+
+#[test]
+fn backtrace_new_should_start_with_call_site_trace() {
+ if !ENABLED {
+ return;
+ }
+ let b = Backtrace::new();
+ println!("{:?}", b);
+
+ assert!(!b.frames().is_empty());
+
+ let this_ip = backtrace_new_should_start_with_call_site_trace as usize;
+ let frame_ip = b.frames().first().unwrap().symbol_address() as usize;
+ assert_eq!(this_ip, frame_ip);
+}
diff --git a/library/backtrace/tests/smoke.rs b/library/backtrace/tests/smoke.rs
new file mode 100644
index 000000000..683a6f0db
--- /dev/null
+++ b/library/backtrace/tests/smoke.rs
@@ -0,0 +1,323 @@
+use backtrace::Frame;
+use std::thread;
+
+#[test]
+// FIXME: shouldn't ignore this test on i686-msvc, unsure why it's failing
+#[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
+#[rustfmt::skip] // we care about line numbers here
+fn smoke_test_frames() {
+ frame_1(line!());
+ #[inline(never)] fn frame_1(start_line: u32) { frame_2(start_line) }
+ #[inline(never)] fn frame_2(start_line: u32) { frame_3(start_line) }
+ #[inline(never)] fn frame_3(start_line: u32) { frame_4(start_line) }
+ #[inline(never)] fn frame_4(start_line: u32) {
+ let mut v = Vec::new();
+ backtrace::trace(|cx| {
+ v.push(cx.clone());
+ true
+ });
+
+ // Various platforms have various bits of weirdness about their
+ // backtraces. To find a good starting spot let's search through the
+ // frames
+ let target = frame_4 as usize;
+ let offset = v
+ .iter()
+ .map(|frame| frame.symbol_address() as usize)
+ .enumerate()
+ .filter_map(|(i, sym)| {
+ if sym >= target {
+ Some((sym, i))
+ } else {
+ None
+ }
+ })
+ .min()
+ .unwrap()
+ .1;
+ let mut frames = v[offset..].iter();
+
+ assert_frame(
+ frames.next().unwrap(),
+ frame_4 as usize,
+ "frame_4",
+ "tests/smoke.rs",
+ start_line + 6,
+ 9,
+ );
+ assert_frame(
+ frames.next().unwrap(),
+ frame_3 as usize,
+ "frame_3",
+ "tests/smoke.rs",
+ start_line + 3,
+ 52,
+ );
+ assert_frame(
+ frames.next().unwrap(),
+ frame_2 as usize,
+ "frame_2",
+ "tests/smoke.rs",
+ start_line + 2,
+ 52,
+ );
+ assert_frame(
+ frames.next().unwrap(),
+ frame_1 as usize,
+ "frame_1",
+ "tests/smoke.rs",
+ start_line + 1,
+ 52,
+ );
+ assert_frame(
+ frames.next().unwrap(),
+ smoke_test_frames as usize,
+ "smoke_test_frames",
+ "",
+ 0,
+ 0,
+ );
+ }
+
+ fn assert_frame(
+ frame: &Frame,
+ actual_fn_pointer: usize,
+ expected_name: &str,
+ expected_file: &str,
+ expected_line: u32,
+ expected_col: u32,
+ ) {
+ backtrace::resolve_frame(frame, |sym| {
+ print!("symbol ip:{:?} address:{:?} ", frame.ip(), frame.symbol_address());
+ if let Some(name) = sym.name() {
+ print!("name:{} ", name);
+ }
+ if let Some(file) = sym.filename() {
+ print!("file:{} ", file.display());
+ }
+ if let Some(lineno) = sym.lineno() {
+ print!("lineno:{} ", lineno);
+ }
+ if let Some(colno) = sym.colno() {
+ print!("colno:{} ", colno);
+ }
+ println!();
+ });
+
+ let ip = frame.ip() as usize;
+ let sym = frame.symbol_address() as usize;
+ assert!(ip >= sym);
+ assert!(
+ sym >= actual_fn_pointer,
+ "{:?} < {:?} ({} {}:{}:{})",
+ sym as *const usize,
+ actual_fn_pointer as *const usize,
+ expected_name,
+ expected_file,
+ expected_line,
+ expected_col,
+ );
+
+ // windows dbghelp is *quite* liberal (and wrong) in many of its reports
+ // right now...
+ //
+ // This assertion can also fail for release builds, so skip it there
+ if cfg!(debug_assertions) {
+ assert!(sym - actual_fn_pointer < 1024);
+ }
+
+ let mut resolved = 0;
+
+ let mut name = None;
+ let mut addr = None;
+ let mut col = None;
+ let mut line = None;
+ let mut file = None;
+ backtrace::resolve_frame(frame, |sym| {
+ resolved += 1;
+ name = sym.name().map(|v| v.to_string());
+ addr = sym.addr();
+ col = sym.colno();
+ line = sym.lineno();
+ file = sym.filename().map(|v| v.to_path_buf());
+ });
+ assert!(resolved > 0);
+
+ let name = name.expect("didn't find a name");
+
+ // in release mode names get weird as functions can get merged
+ // together with `mergefunc`, so only assert this in debug mode
+ if cfg!(debug_assertions) {
+ assert!(
+ name.contains(expected_name),
+ "didn't find `{}` in `{}`",
+ expected_name,
+ name
+ );
+ }
+
+ addr.expect("didn't find a symbol");
+
+ if cfg!(debug_assertions) {
+ let line = line.expect("didn't find a line number");
+ let file = file.expect("didn't find a line number");
+ if !expected_file.is_empty() {
+ assert!(
+ file.ends_with(expected_file),
+ "{:?} didn't end with {:?}",
+ file,
+ expected_file
+ );
+ }
+ if expected_line != 0 {
+ assert!(
+ line == expected_line,
+ "bad line number on frame for `{}`: {} != {}",
+ expected_name,
+ line,
+ expected_line
+ );
+ }
+
+ // dbghelp on MSVC doesn't support column numbers
+ if !cfg!(target_env = "msvc") {
+ let col = col.expect("didn't find a column number");
+ if expected_col != 0 {
+ assert!(
+ col == expected_col,
+ "bad column number on frame for `{}`: {} != {}",
+ expected_name,
+ col,
+ expected_col
+ );
+ }
+ }
+ }
+ }
+}
+
+#[test]
+fn many_threads() {
+ let threads = (0..16)
+ .map(|_| {
+ thread::spawn(|| {
+ for _ in 0..16 {
+ backtrace::trace(|frame| {
+ backtrace::resolve(frame.ip(), |symbol| {
+ let _s = symbol.name().map(|s| s.to_string());
+ });
+ true
+ });
+ }
+ })
+ })
+ .collect::<Vec<_>>();
+
+ for t in threads {
+ t.join().unwrap()
+ }
+}
+
+#[test]
+#[cfg(feature = "rustc-serialize")]
+fn is_rustc_serialize() {
+ extern crate rustc_serialize;
+
+ fn is_encode<T: rustc_serialize::Encodable>() {}
+ fn is_decode<T: rustc_serialize::Decodable>() {}
+
+ is_encode::<backtrace::Backtrace>();
+ is_decode::<backtrace::Backtrace>();
+}
+
+#[test]
+#[cfg(feature = "serde")]
+fn is_serde() {
+ extern crate serde;
+
+ fn is_serialize<T: serde::ser::Serialize>() {}
+ fn is_deserialize<T: serde::de::DeserializeOwned>() {}
+
+ is_serialize::<backtrace::Backtrace>();
+ is_deserialize::<backtrace::Backtrace>();
+}
+
+#[test]
+fn sp_smoke_test() {
+ let mut refs = vec![];
+ recursive_stack_references(&mut refs);
+ return;
+
+ #[inline(never)]
+ fn recursive_stack_references(refs: &mut Vec<usize>) {
+ assert!(refs.len() < 5);
+
+ let x = refs.len();
+ refs.push(&x as *const _ as usize);
+
+ if refs.len() < 5 {
+ recursive_stack_references(refs);
+ eprintln!("exiting: {}", x);
+ return;
+ }
+
+ backtrace::trace(make_trace_closure(refs));
+ eprintln!("exiting: {}", x);
+ }
+
+ // NB: the following `make_*` functions are pulled out of line, rather than
+ // defining their results as inline closures at their call sites, so that
+ // the resulting closures don't have "recursive_stack_references" in their
+ // mangled names.
+
+ fn make_trace_closure<'a>(
+ refs: &'a mut Vec<usize>,
+ ) -> impl FnMut(&backtrace::Frame) -> bool + 'a {
+ let mut child_sp = None;
+ let mut child_ref = None;
+ move |frame| {
+ eprintln!("\n=== frame ===================================");
+
+ let mut is_recursive_stack_references = false;
+ backtrace::resolve(frame.ip(), |sym| {
+ is_recursive_stack_references |=
+ sym.name()
+ .and_then(|name| name.as_str())
+ .map_or(false, |name| {
+ eprintln!("name = {}", name);
+ name.contains("recursive_stack_references")
+ })
+ });
+
+ let sp = frame.sp() as usize;
+ eprintln!("sp = {:p}", sp as *const u8);
+ if sp == 0 {
+ // If the SP is null, then we don't have an implementation for
+ // getting the SP on this target. Just keep walking the stack,
+ // but don't make our assertions about the on-stack pointers and
+ // SP values.
+ return true;
+ }
+
+ // The stack grows down.
+ if let Some(child_sp) = child_sp {
+ assert!(child_sp <= sp);
+ }
+
+ if is_recursive_stack_references {
+ let r = refs.pop().unwrap();
+ eprintln!("ref = {:p}", r as *const u8);
+ if sp != 0 {
+ assert!(r > sp);
+ if let Some(child_ref) = child_ref {
+ assert!(sp >= child_ref);
+ }
+ }
+ child_ref = Some(r);
+ }
+
+ child_sp = Some(sp);
+ true
+ }
+ }
+}