1 extern crate backtrace;
2
3 use std::os::raw::c_void;
4 use std::thread;
5
6 static LIBUNWIND: bool = cfg!(all(unix, feature = "libunwind"));
7 static UNIX_BACKTRACE: bool = cfg!(all(unix, feature = "unix-backtrace"));
8 static LIBBACKTRACE: bool = cfg!(all(unix, feature = "libbacktrace")) &&
9 !cfg!(target_os = "fuchsia") && !cfg!(target_os = "macos") &&
10 !cfg!(target_os = "ios");
11 static CORESYMBOLICATION: bool = cfg!(all(any(target_os = "macos", target_os = "ios"),
12 feature = "coresymbolication"));
13 static DLADDR: bool = cfg!(all(unix, feature = "dladdr")) && !cfg!(target_os = "fuchsia");
14 static DBGHELP: bool = cfg!(all(windows, feature = "dbghelp"));
15 static MSVC: bool = cfg!(target_env = "msvc");
16 static GIMLI_SYMBOLIZE: bool = cfg!(all(feature = "gimli-symbolize",
17 unix,
18 target_os = "linux"));
19
20 #[test]
21 // FIXME: shouldn't ignore this test on i686-msvc, unsure why it's failing
22 #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
smoke_test_frames()23 fn smoke_test_frames() {
24 frame_1(line!());
25 #[inline(never)] fn frame_1(start_line: u32) { frame_2(start_line) }
26 #[inline(never)] fn frame_2(start_line: u32) { frame_3(start_line) }
27 #[inline(never)] fn frame_3(start_line: u32) { frame_4(start_line) }
28 #[inline(never)] fn frame_4(start_line: u32) {
29 let mut v = Vec::new();
30 backtrace::trace(|cx| {
31 v.push((cx.ip(), cx.symbol_address()));
32 true
33 });
34
35 if v.len() < 5 {
36 assert!(!LIBUNWIND);
37 assert!(!UNIX_BACKTRACE);
38 assert!(!DBGHELP);
39 return
40 }
41
42 // On 32-bit windows apparently the first frame isn't our backtrace
43 // frame but it's actually this frame. I'm not entirely sure why, but at
44 // least it seems consistent?
45 let o = if cfg!(all(windows, target_pointer_width = "32")) {1} else {0};
46 // frame offset 0 is the `backtrace::trace` function, but that's generic
47 assert_frame(&v, o, 1, frame_4 as usize, "frame_4",
48 "tests/smoke.rs", start_line + 6);
49 assert_frame(&v, o, 2, frame_3 as usize, "frame_3", "tests/smoke.rs",
50 start_line + 3);
51 assert_frame(&v, o, 3, frame_2 as usize, "frame_2", "tests/smoke.rs",
52 start_line + 2);
53 assert_frame(&v, o, 4, frame_1 as usize, "frame_1", "tests/smoke.rs",
54 start_line + 1);
55 assert_frame(&v, o, 5, smoke_test_frames as usize,
56 "smoke_test_frames", "", 0);
57 }
58
59 fn assert_frame(syms: &[(*mut c_void, *mut c_void)],
60 offset: usize,
61 idx: usize,
62 actual_fn_pointer: usize,
63 expected_name: &str,
64 expected_file: &str,
65 expected_line: u32) {
66 if offset > idx { return }
67 println!("frame: {}", idx);
68 let (ip, sym) = syms[idx - offset];
69 let ip = ip as usize;
70 let sym = sym as usize;
71 assert!(ip >= sym);
72 assert!(sym >= actual_fn_pointer);
73
74 // windows dbghelp is *quite* liberal (and wrong) in many of its reports
75 // right now...
76 //
77 // This assertion can also fail for release builds, so skip it there
78 if !DBGHELP && cfg!(debug_assertions) {
79 assert!(sym - actual_fn_pointer < 1024);
80 }
81
82 let mut resolved = 0;
83 let can_resolve = DLADDR || LIBBACKTRACE || CORESYMBOLICATION || DBGHELP || GIMLI_SYMBOLIZE;
84
85 let mut name = None;
86 let mut addr = None;
87 let mut line = None;
88 let mut file = None;
89 backtrace::resolve(ip as *mut c_void, |sym| {
90 resolved += 1;
91 name = sym.name().map(|v| v.to_string());
92 addr = sym.addr();
93 line = sym.lineno();
94 file = sym.filename().map(|v| v.to_path_buf());
95 println!(" sym: {:?}", name);
96 });
97
98 // dbghelp doesn't always resolve symbols right now
99 match resolved {
100 0 => return assert!(!can_resolve || DBGHELP),
101 _ => {}
102 }
103
104 // * linux dladdr doesn't work (only consults local symbol table)
105 // * windows dbghelp isn't great for GNU
106 if can_resolve &&
107 !(cfg!(target_os = "linux") && DLADDR) &&
108 !(DBGHELP && !MSVC)
109 {
110 let name = name.expect("didn't find a name");
111
112 // in release mode names get weird as functions can get merged
113 // together with `mergefunc`, so only assert this in debug mode
114 if cfg!(debug_assertions) {
115 assert!(name.contains(expected_name),
116 "didn't find `{}` in `{}`", expected_name, name);
117 }
118 }
119
120 if can_resolve {
121 addr.expect("didn't find a symbol");
122 }
123
124 if (LIBBACKTRACE || CORESYMBOLICATION || (DBGHELP && MSVC)) && cfg!(debug_assertions) {
125 let line = line.expect("didn't find a line number");
126 let file = file.expect("didn't find a line number");
127 if !expected_file.is_empty() {
128 assert!(file.ends_with(expected_file),
129 "{:?} didn't end with {:?}", file, expected_file);
130 }
131 if expected_line != 0 {
132 assert!(line == expected_line,
133 "bad line number on frame for `{}`: {} != {}",
134 expected_name, line, expected_line);
135 }
136 }
137 }
138 }
139
140 #[test]
many_threads()141 fn many_threads() {
142 let threads = (0..16).map(|_| {
143 thread::spawn(|| {
144 for _ in 0..16 {
145 backtrace::trace(|frame| {
146 backtrace::resolve(frame.ip(), |symbol| {
147 let _s = symbol.name().map(|s| s.to_string());
148 });
149 true
150 });
151 }
152 })
153 }).collect::<Vec<_>>();
154
155 for t in threads {
156 t.join().unwrap()
157 }
158 }
159
160 #[test]
161 #[cfg(feature = "rustc-serialize")]
is_rustc_serialize()162 fn is_rustc_serialize() {
163 extern crate rustc_serialize;
164
165 fn is_encode<T: rustc_serialize::Encodable>() {}
166 fn is_decode<T: rustc_serialize::Decodable>() {}
167
168 is_encode::<backtrace::Backtrace>();
169 is_decode::<backtrace::Backtrace>();
170 }
171
172 #[test]
173 #[cfg(feature = "serde")]
is_serde()174 fn is_serde() {
175 extern crate serde;
176
177 fn is_serialize<T: serde::ser::Serialize>() {}
178 fn is_deserialize<T: serde::de::DeserializeOwned>() {}
179
180 is_serialize::<backtrace::Backtrace>();
181 is_deserialize::<backtrace::Backtrace>();
182 }
183