blob: 6d15b284654e386882cbf35c4a780a2ad38c4288 [file] [log] [blame]
extern crate backtrace;
use std::os::raw::c_void;
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!(all(unix, feature = "libbacktrace")) &&
!cfg!(target_os = "fuchsia") && !cfg!(target_os = "macos") &&
!cfg!(target_os = "ios");
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)]
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.ip(), cx.symbol_address()));
true
});
if v.len() < 5 {
assert!(!LIBUNWIND);
assert!(!UNIX_BACKTRACE);
assert!(!DBGHELP);
return
}
// On 32-bit windows apparently the first frame isn't our backtrace
// frame but it's actually this frame. I'm not entirely sure why, but at
// least it seems consistent?
let o = if cfg!(all(windows, target_pointer_width = "32")) {1} else {0};
// frame offset 0 is the `backtrace::trace` function, but that's generic
assert_frame(&v, o, 1, frame_4 as usize, "frame_4",
"tests/smoke.rs", start_line + 6);
assert_frame(&v, o, 2, frame_3 as usize, "frame_3", "tests/smoke.rs",
start_line + 3);
assert_frame(&v, o, 3, frame_2 as usize, "frame_2", "tests/smoke.rs",
start_line + 2);
assert_frame(&v, o, 4, frame_1 as usize, "frame_1", "tests/smoke.rs",
start_line + 1);
assert_frame(&v, o, 5, smoke_test_frames as usize,
"smoke_test_frames", "", 0);
}
fn assert_frame(syms: &[(*mut c_void, *mut c_void)],
offset: usize,
idx: usize,
actual_fn_pointer: usize,
expected_name: &str,
expected_file: &str,
expected_line: u32) {
if offset > idx { return }
println!("frame: {}", idx);
let (ip, sym) = syms[idx - offset];
let ip = ip as usize;
let sym = sym as usize;
assert!(ip >= sym);
assert!(sym >= actual_fn_pointer);
// 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(ip as *mut c_void, |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());
println!(" sym: {:?}", name);
});
// 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>();
}