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