1 extern crate backtrace;
2
3 use backtrace::Frame;
4 use std::thread;
5
6 static LIBBACKTRACE: bool = cfg!(feature = "libbacktrace") && !cfg!(target_os = "fuchsia");
7 static GIMLI_SYMBOLIZE: bool = cfg!(all(feature = "gimli-symbolize", unix, target_os = "linux"));
8
9 #[test]
10 // FIXME: shouldn't ignore this test on i686-msvc, unsure why it's failing
11 #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
12 #[rustfmt::skip] // we care about line numbers here
smoke_test_frames()13 fn smoke_test_frames() {
14 frame_1(line!());
15 #[inline(never)] fn frame_1(start_line: u32) { frame_2(start_line) }
16 #[inline(never)] fn frame_2(start_line: u32) { frame_3(start_line) }
17 #[inline(never)] fn frame_3(start_line: u32) { frame_4(start_line) }
18 #[inline(never)] fn frame_4(start_line: u32) {
19 let mut v = Vec::new();
20 backtrace::trace(|cx| {
21 v.push(cx.clone());
22 true
23 });
24
25 // Various platforms have various bits of weirdness about their
26 // backtraces. To find a good starting spot let's search through the
27 // frames
28 let target = frame_4 as usize;
29 let offset = v
30 .iter()
31 .map(|frame| frame.symbol_address() as usize)
32 .enumerate()
33 .filter_map(|(i, sym)| {
34 if sym >= target {
35 Some((sym, i))
36 } else {
37 None
38 }
39 })
40 .min()
41 .unwrap()
42 .1;
43 let mut frames = v[offset..].iter();
44
45 assert_frame(
46 frames.next().unwrap(),
47 frame_4 as usize,
48 "frame_4",
49 "tests/smoke.rs",
50 start_line + 6,
51 );
52 assert_frame(
53 frames.next().unwrap(),
54 frame_3 as usize,
55 "frame_3",
56 "tests/smoke.rs",
57 start_line + 3,
58 );
59 assert_frame(
60 frames.next().unwrap(),
61 frame_2 as usize,
62 "frame_2",
63 "tests/smoke.rs",
64 start_line + 2,
65 );
66 assert_frame(
67 frames.next().unwrap(),
68 frame_1 as usize,
69 "frame_1",
70 "tests/smoke.rs",
71 start_line + 1,
72 );
73 assert_frame(
74 frames.next().unwrap(),
75 smoke_test_frames as usize,
76 "smoke_test_frames",
77 "",
78 0,
79 );
80 }
81
82 fn assert_frame(
83 frame: &Frame,
84 actual_fn_pointer: usize,
85 expected_name: &str,
86 expected_file: &str,
87 expected_line: u32,
88 ) {
89 backtrace::resolve_frame(frame, |sym| {
90 print!("symbol ip:{:?} address:{:?} ", frame.ip(), frame.symbol_address());
91 if let Some(name) = sym.name() {
92 print!("name:{} ", name);
93 }
94 if let Some(file) = sym.filename() {
95 print!("file:{} ", file.display());
96 }
97 if let Some(lineno) = sym.lineno() {
98 print!("lineno:{} ", lineno);
99 }
100 println!();
101 });
102
103 let ip = frame.ip() as usize;
104 let sym = frame.symbol_address() as usize;
105 assert!(ip >= sym);
106 assert!(
107 sym >= actual_fn_pointer,
108 "{:?} < {:?} ({} {}:{})",
109 sym as *const usize,
110 actual_fn_pointer as *const usize,
111 expected_name,
112 expected_file,
113 expected_line,
114 );
115
116 // windows dbghelp is *quite* liberal (and wrong) in many of its reports
117 // right now...
118 //
119 // This assertion can also fail for release builds, so skip it there
120 if cfg!(debug_assertions) {
121 assert!(sym - actual_fn_pointer < 1024);
122 }
123
124 let mut resolved = 0;
125 let can_resolve = LIBBACKTRACE || GIMLI_SYMBOLIZE;
126
127 let mut name = None;
128 let mut addr = None;
129 let mut line = None;
130 let mut file = None;
131 backtrace::resolve_frame(frame, |sym| {
132 resolved += 1;
133 name = sym.name().map(|v| v.to_string());
134 addr = sym.addr();
135 line = sym.lineno();
136 file = sym.filename().map(|v| v.to_path_buf());
137 });
138
139 // dbghelp doesn't always resolve symbols right now
140 match resolved {
141 0 => return assert!(!can_resolve),
142 _ => {}
143 }
144
145 if can_resolve {
146 let name = name.expect("didn't find a name");
147
148 // in release mode names get weird as functions can get merged
149 // together with `mergefunc`, so only assert this in debug mode
150 if cfg!(debug_assertions) {
151 assert!(
152 name.contains(expected_name),
153 "didn't find `{}` in `{}`",
154 expected_name,
155 name
156 );
157 }
158 }
159
160 if can_resolve {
161 addr.expect("didn't find a symbol");
162 }
163
164 if cfg!(debug_assertions) {
165 let line = line.expect("didn't find a line number");
166 let file = file.expect("didn't find a line number");
167 if !expected_file.is_empty() {
168 assert!(
169 file.ends_with(expected_file),
170 "{:?} didn't end with {:?}",
171 file,
172 expected_file
173 );
174 }
175 if expected_line != 0 {
176 assert!(
177 line == expected_line,
178 "bad line number on frame for `{}`: {} != {}",
179 expected_name,
180 line,
181 expected_line
182 );
183 }
184 }
185 }
186 }
187
188 #[test]
many_threads()189 fn many_threads() {
190 let threads = (0..16)
191 .map(|_| {
192 thread::spawn(|| {
193 for _ in 0..16 {
194 backtrace::trace(|frame| {
195 backtrace::resolve(frame.ip(), |symbol| {
196 let _s = symbol.name().map(|s| s.to_string());
197 });
198 true
199 });
200 }
201 })
202 })
203 .collect::<Vec<_>>();
204
205 for t in threads {
206 t.join().unwrap()
207 }
208 }
209
210 #[test]
211 #[cfg(feature = "rustc-serialize")]
is_rustc_serialize()212 fn is_rustc_serialize() {
213 extern crate rustc_serialize;
214
215 fn is_encode<T: rustc_serialize::Encodable>() {}
216 fn is_decode<T: rustc_serialize::Decodable>() {}
217
218 is_encode::<backtrace::Backtrace>();
219 is_decode::<backtrace::Backtrace>();
220 }
221
222 #[test]
223 #[cfg(feature = "serde")]
is_serde()224 fn is_serde() {
225 extern crate serde;
226
227 fn is_serialize<T: serde::ser::Serialize>() {}
228 fn is_deserialize<T: serde::de::DeserializeOwned>() {}
229
230 is_serialize::<backtrace::Backtrace>();
231 is_deserialize::<backtrace::Backtrace>();
232 }
233