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