blob: 7f90d2947ae340c8ea48bce749881aa86b43bfa5 [file] [log] [blame]
extern crate backtrace;
use backtrace::Frame;
use std::thread;
static LIBUNWIND: bool = cfg!(all(unix, feature = "libunwind"));
static UNIX_BACKTRACE: bool = cfg!(all(unix, feature = "unix-backtrace"));
static LIBBACKTRACE: bool = cfg!(feature = "libbacktrace") && !cfg!(target_os = "fuchsia");
static CORESYMBOLICATION: bool = cfg!(all(
any(target_os = "macos", target_os = "ios"),
feature = "coresymbolication"
));
static DLADDR: bool = cfg!(all(unix, feature = "dladdr")) && !cfg!(target_os = "fuchsia");
static DBGHELP: bool = cfg!(all(windows, feature = "dbghelp"));
static MSVC: bool = cfg!(target_env = "msvc");
static GIMLI_SYMBOLIZE: bool = cfg!(all(feature = "gimli-symbolize", unix, target_os = "linux"));
#[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
});
if v.len() < 5 {
assert!(!LIBUNWIND);
assert!(!UNIX_BACKTRACE);
assert!(!DBGHELP);
return;
}
// 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,
);
assert_frame(
frames.next().unwrap(),
frame_3 as usize,
"frame_3",
"tests/smoke.rs",
start_line + 3,
);
assert_frame(
frames.next().unwrap(),
frame_2 as usize,
"frame_2",
"tests/smoke.rs",
start_line + 2,
);
assert_frame(
frames.next().unwrap(),
frame_1 as usize,
"frame_1",
"tests/smoke.rs",
start_line + 1,
);
assert_frame(
frames.next().unwrap(),
smoke_test_frames as usize,
"smoke_test_frames",
"",
0,
);
}
fn assert_frame(
frame: &Frame,
actual_fn_pointer: usize,
expected_name: &str,
expected_file: &str,
expected_line: 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);
}
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,
);
// 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 !DBGHELP && cfg!(debug_assertions) {
assert!(sym - actual_fn_pointer < 1024);
}
let mut resolved = 0;
let can_resolve = DLADDR || LIBBACKTRACE || CORESYMBOLICATION || DBGHELP || GIMLI_SYMBOLIZE;
let mut name = None;
let mut addr = 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();
line = sym.lineno();
file = sym.filename().map(|v| v.to_path_buf());
});
// dbghelp doesn't always resolve symbols right now
match resolved {
0 => return assert!(!can_resolve || DBGHELP),
_ => {}
}
// * linux dladdr doesn't work (only consults local symbol table)
// * windows dbghelp isn't great for GNU
if can_resolve && !(cfg!(target_os = "linux") && DLADDR) && !(DBGHELP && !MSVC) {
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
);
}
}
if can_resolve {
addr.expect("didn't find a symbol");
}
if (LIBBACKTRACE || CORESYMBOLICATION || (DBGHELP && MSVC)) && 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
);
}
}
}
}
#[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>();
}