1 #![allow(dead_code)]
2 
3 extern crate glob;
4 extern crate serial_test;
5 extern crate tempfile;
6 
7 use std::collections::HashMap;
8 use std::env;
9 use std::fs;
10 use std::path::PathBuf;
11 use std::sync::Arc;
12 use std::sync::Mutex;
13 
14 use serial_test::serial;
15 use tempfile::TempDir;
16 
17 #[macro_use]
18 #[path = "../build/macros.rs"]
19 mod macros;
20 
21 #[path = "../build/common.rs"]
22 mod common;
23 #[path = "../build/dynamic.rs"]
24 mod dynamic;
25 #[path = "../build/static.rs"]
26 mod r#static;
27 
28 #[derive(Debug, Default)]
29 struct RunCommandMock {
30     invocations: Vec<(String, String, Vec<String>)>,
31     responses: HashMap<Vec<String>, String>,
32 }
33 
34 #[derive(Debug)]
35 struct Env {
36     os: String,
37     pointer_width: String,
38     env: Option<String>,
39     vars: HashMap<String, (Option<String>, Option<String>)>,
40     cwd: PathBuf,
41     tmp: TempDir,
42     files: Vec<String>,
43     commands: Arc<Mutex<RunCommandMock>>,
44 }
45 
46 impl Env {
new(os: &str, pointer_width: &str) -> Self47     fn new(os: &str, pointer_width: &str) -> Self {
48         Env {
49             os: os.into(),
50             pointer_width: pointer_width.into(),
51             env: None,
52             vars: HashMap::new(),
53             cwd: env::current_dir().unwrap(),
54             tmp: tempfile::Builder::new().prefix("clang_sys_test").tempdir().unwrap(),
55             files: vec![],
56             commands: Default::default(),
57         }
58         .var("CLANG_PATH", None)
59         .var("LD_LIBRARY_PATH", None)
60         .var("LIBCLANG_PATH", None)
61         .var("LIBCLANG_STATIC_PATH", None)
62         .var("LLVM_CONFIG_PATH", None)
63         .var("PATH", None)
64     }
65 
env(mut self, env: &str) -> Self66     fn env(mut self, env: &str) -> Self {
67         self.env = Some(env.into());
68         self
69     }
70 
var(mut self, name: &str, value: Option<&str>) -> Self71     fn var(mut self, name: &str, value: Option<&str>) -> Self {
72         let previous = env::var(name).ok();
73         self.vars.insert(name.into(), (value.map(|v| v.into()), previous));
74         self
75     }
76 
dir(mut self, path: &str) -> Self77     fn dir(mut self, path: &str) -> Self {
78         self.files.push(path.into());
79         let path = self.tmp.path().join(path);
80         fs::create_dir_all(path).unwrap();
81         self
82     }
83 
file(mut self, path: &str, contents: &[u8]) -> Self84     fn file(mut self, path: &str, contents: &[u8]) -> Self {
85         self.files.push(path.into());
86         let path = self.tmp.path().join(path);
87         fs::create_dir_all(path.parent().unwrap()).unwrap();
88         fs::write(self.tmp.path().join(path), contents).unwrap();
89         self
90     }
91 
dll(self, path: &str, pointer_width: &str) -> Self92     fn dll(self, path: &str, pointer_width: &str) -> Self {
93         // PE header.
94         let mut contents = [0; 64];
95         contents[0x3C..0x3C + 4].copy_from_slice(&i32::to_le_bytes(10));
96         contents[10..14].copy_from_slice(&[b'P', b'E', 0, 0]);
97         let magic = if pointer_width == "64" { 523 } else { 267 };
98         contents[34..36].copy_from_slice(&u16::to_le_bytes(magic));
99 
100         self.file(path, &contents)
101     }
102 
so(self, path: &str, pointer_width: &str) -> Self103     fn so(self, path: &str, pointer_width: &str) -> Self {
104         // ELF header.
105         let class = if pointer_width == "64" { 2 } else { 1 };
106         let contents = [127, 69, 76, 70, class];
107 
108         self.file(path, &contents)
109     }
110 
command(self, command: &str, args: &[&str], response: &str) -> Self111     fn command(self, command: &str, args: &[&str], response: &str) -> Self {
112         let command = command.to_string();
113         let args = args.iter().map(|a| a.to_string()).collect::<Vec<_>>();
114 
115         let mut key = vec![command];
116         key.extend(args);
117         self.commands.lock().unwrap().responses.insert(key, response.into());
118 
119         self
120     }
121 
enable(self) -> Self122     fn enable(self) -> Self {
123         env::set_var("_CLANG_SYS_TEST", "yep");
124         env::set_var("_CLANG_SYS_TEST_OS", &self.os);
125         env::set_var("_CLANG_SYS_TEST_POINTER_WIDTH", &self.pointer_width);
126         if let Some(env) = &self.env {
127             env::set_var("_CLANG_SYS_TEST_ENV", env);
128         }
129 
130         for (name, (value, _)) in &self.vars {
131             if let Some(value) = value {
132                 env::set_var(name, value);
133             } else {
134                 env::remove_var(name);
135             }
136         }
137 
138         env::set_current_dir(&self.tmp).unwrap();
139 
140         let commands = self.commands.clone();
141         let mock = &mut *common::RUN_COMMAND_MOCK.lock().unwrap();
142         *mock = Some(Box::new(move |command, path, args| {
143             let command = command.to_string();
144             let path = path.to_string();
145             let args = args.iter().map(|a| a.to_string()).collect::<Vec<_>>();
146 
147             let mut commands = commands.lock().unwrap();
148             commands.invocations.push((command.clone(), path, args.clone()));
149 
150             let mut key = vec![command];
151             key.extend(args);
152             commands.responses.get(&key).cloned()
153         }));
154 
155         self
156     }
157 }
158 
159 impl Drop for Env {
drop(&mut self)160     fn drop(&mut self) {
161         env::remove_var("_CLANG_SYS_TEST");
162         env::remove_var("_CLANG_SYS_TEST_OS");
163         env::remove_var("_CLANG_SYS_TEST_POINTER_WIDTH");
164         env::remove_var("_CLANG_SYS_TEST_ENV");
165 
166         for (name, (_, previous)) in &self.vars {
167             if let Some(previous) = previous {
168                 env::set_var(name, previous);
169             } else {
170                 env::remove_var(name);
171             }
172         }
173 
174         if let Err(error) = env::set_current_dir(&self.cwd) {
175             println!("Failed to reset working directory: {:?}", error);
176         }
177     }
178 }
179 
180 //================================================
181 // Dynamic
182 //================================================
183 
184 // Linux -----------------------------------------
185 
186 #[test]
187 #[serial]
test_linux_directory_preference()188 fn test_linux_directory_preference() {
189     let _env = Env::new("linux", "64")
190         .so("usr/lib/libclang.so.1", "64")
191         .so("usr/local/lib/libclang.so.1", "64")
192         .enable();
193 
194     assert_eq!(
195         dynamic::find(true),
196         Ok(("usr/local/lib".into(), "libclang.so.1".into())),
197     );
198 }
199 
200 #[test]
201 #[serial]
test_linux_version_preference()202 fn test_linux_version_preference() {
203     let _env = Env::new("linux", "64")
204         .so("usr/lib/libclang-3.so", "64")
205         .so("usr/lib/libclang-3.5.so", "64")
206         .so("usr/lib/libclang-3.5.0.so", "64")
207         .enable();
208 
209     assert_eq!(
210         dynamic::find(true),
211         Ok(("usr/lib".into(), "libclang-3.5.0.so".into())),
212     );
213 }
214 
215 #[test]
216 #[serial]
test_linux_directory_and_version_preference()217 fn test_linux_directory_and_version_preference() {
218     let _env = Env::new("linux", "64")
219         .so("usr/local/llvm/lib/libclang-3.so", "64")
220         .so("usr/local/lib/libclang-3.5.so", "64")
221         .so("usr/lib/libclang-3.5.0.so", "64")
222         .enable();
223 
224     assert_eq!(
225         dynamic::find(true),
226         Ok(("usr/lib".into(), "libclang-3.5.0.so".into())),
227     );
228 }
229 
230 // Windows ---------------------------------------
231 
232 #[cfg(target_os = "windows")]
233 #[test]
234 #[serial]
test_windows_bin_sibling()235 fn test_windows_bin_sibling() {
236     let _env = Env::new("windows", "64")
237         .dir("Program Files\\LLVM\\lib")
238         .dll("Program Files\\LLVM\\bin\\libclang.dll", "64")
239         .enable();
240 
241     assert_eq!(
242         dynamic::find(true),
243         Ok(("Program Files\\LLVM\\bin".into(), "libclang.dll".into())),
244     );
245 }
246 
247 #[cfg(target_os = "windows")]
248 #[test]
249 #[serial]
test_windows_mingw_gnu()250 fn test_windows_mingw_gnu() {
251     let _env = Env::new("windows", "64")
252         .env("gnu")
253         .dir("MSYS\\MinGW\\lib")
254         .dll("MSYS\\MinGW\\bin\\clang.dll", "64")
255         .dir("Program Files\\LLVM\\lib")
256         .dll("Program Files\\LLVM\\bin\\libclang.dll", "64")
257         .enable();
258 
259     assert_eq!(
260         dynamic::find(true),
261         Ok(("MSYS\\MinGW\\bin".into(), "clang.dll".into())),
262     );
263 }
264 
265 #[cfg(target_os = "windows")]
266 #[test]
267 #[serial]
test_windows_mingw_msvc()268 fn test_windows_mingw_msvc() {
269     let _env = Env::new("windows", "64")
270         .env("msvc")
271         .dir("MSYS\\MinGW\\lib")
272         .dll("MSYS\\MinGW\\bin\\clang.dll", "64")
273         .dir("Program Files\\LLVM\\lib")
274         .dll("Program Files\\LLVM\\bin\\libclang.dll", "64")
275         .enable();
276 
277     assert_eq!(
278         dynamic::find(true),
279         Ok(("Program Files\\LLVM\\bin".into(), "libclang.dll".into())),
280     );
281 }
282