• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #![allow(dead_code)]
2 
3 use core::fmt;
4 use std::collections::HashMap;
5 use std::env;
6 use std::fs;
7 use std::path::PathBuf;
8 use std::sync::Arc;
9 use std::sync::Mutex;
10 
11 use tempfile::TempDir;
12 
13 #[macro_use]
14 #[path = "../build/macros.rs"]
15 mod macros;
16 
17 #[path = "../build/common.rs"]
18 mod common;
19 #[path = "../build/dynamic.rs"]
20 mod dynamic;
21 #[path = "../build/static.rs"]
22 mod r#static;
23 
24 #[derive(Debug, Default)]
25 struct RunCommandMock {
26     invocations: Vec<(String, String, Vec<String>)>,
27     responses: HashMap<Vec<String>, String>,
28 }
29 
30 
31 #[derive(Copy, Clone, Debug)]
32 enum Arch {
33     ARM64,
34     X86,
35     X86_64,
36 }
37 
38 impl Arch {
pe_machine_type(self) -> u1639     fn pe_machine_type(self) -> u16 {
40         match self {
41             Arch::ARM64 => 0xAA64,
42             Arch::X86 => 0x014C,
43             Arch::X86_64 => 0x8664,
44         }
45     }
46 }
47 
48 impl fmt::Display for Arch {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result49     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50         match self {
51             Arch::ARM64 => write!(f, "aarch64"),
52             Arch::X86 => write!(f, "x86"),
53             Arch::X86_64 => write!(f, "x86_64"),
54         }
55     }
56 }
57 
58 #[derive(Debug)]
59 struct Env {
60     os: String,
61     arch: Arch,
62     pointer_width: String,
63     env: Option<String>,
64     vars: HashMap<String, (Option<String>, Option<String>)>,
65     cwd: PathBuf,
66     tmp: TempDir,
67     files: Vec<String>,
68     commands: Arc<Mutex<RunCommandMock>>,
69 }
70 
71 impl Env {
new(os: &str, arch: Arch, pointer_width: &str) -> Self72     fn new(os: &str, arch: Arch, pointer_width: &str) -> Self {
73         Env {
74             os: os.into(),
75             arch,
76             pointer_width: pointer_width.into(),
77             env: None,
78             vars: HashMap::new(),
79             cwd: env::current_dir().unwrap(),
80             tmp: tempfile::Builder::new().prefix("clang_sys_test").tempdir().unwrap(),
81             files: vec![],
82             commands: Default::default(),
83         }
84         .var("CLANG_PATH", None)
85         .var("LD_LIBRARY_PATH", None)
86         .var("LIBCLANG_PATH", None)
87         .var("LIBCLANG_STATIC_PATH", None)
88         .var("LLVM_CONFIG_PATH", None)
89         .var("PATH", None)
90     }
91 
env(mut self, env: &str) -> Self92     fn env(mut self, env: &str) -> Self {
93         self.env = Some(env.into());
94         self
95     }
96 
var(mut self, name: &str, value: Option<&str>) -> Self97     fn var(mut self, name: &str, value: Option<&str>) -> Self {
98         let previous = env::var(name).ok();
99         self.vars.insert(name.into(), (value.map(|v| v.into()), previous));
100         self
101     }
102 
dir(mut self, path: &str) -> Self103     fn dir(mut self, path: &str) -> Self {
104         self.files.push(path.into());
105         let path = self.tmp.path().join(path);
106         fs::create_dir_all(path).unwrap();
107         self
108     }
109 
file(mut self, path: &str, contents: &[u8]) -> Self110     fn file(mut self, path: &str, contents: &[u8]) -> Self {
111         self.files.push(path.into());
112         let path = self.tmp.path().join(path);
113         fs::create_dir_all(path.parent().unwrap()).unwrap();
114         fs::write(self.tmp.path().join(path), contents).unwrap();
115         self
116     }
117 
dll(self, path: &str, arch: Arch, pointer_width: &str) -> Self118     fn dll(self, path: &str, arch: Arch, pointer_width: &str) -> Self {
119         // PE header.
120         let mut contents = [0; 64];
121         contents[0x3C..0x3C + 4].copy_from_slice(&i32::to_le_bytes(10));
122         contents[10..14].copy_from_slice(&[b'P', b'E', 0, 0]);
123         contents[14..16].copy_from_slice(&u16::to_le_bytes(arch.pe_machine_type()));
124         let magic = if pointer_width == "64" { 523 } else { 267 };
125         contents[34..36].copy_from_slice(&u16::to_le_bytes(magic));
126 
127         self.file(path, &contents)
128     }
129 
so(self, path: &str, pointer_width: &str) -> Self130     fn so(self, path: &str, pointer_width: &str) -> Self {
131         // ELF header.
132         let class = if pointer_width == "64" { 2 } else { 1 };
133         let contents = [127, 69, 76, 70, class];
134 
135         self.file(path, &contents)
136     }
137 
command(self, command: &str, args: &[&str], response: &str) -> Self138     fn command(self, command: &str, args: &[&str], response: &str) -> Self {
139         let command = command.to_string();
140         let args = args.iter().map(|a| a.to_string()).collect::<Vec<_>>();
141 
142         let mut key = vec![command];
143         key.extend(args);
144         self.commands.lock().unwrap().responses.insert(key, response.into());
145 
146         self
147     }
148 
enable(self) -> Self149     fn enable(self) -> Self {
150         env::set_var("_CLANG_SYS_TEST", "yep");
151         env::set_var("_CLANG_SYS_TEST_OS", &self.os);
152         env::set_var("_CLANG_SYS_TEST_ARCH", &format!("{}", self.arch));
153         env::set_var("_CLANG_SYS_TEST_POINTER_WIDTH", &self.pointer_width);
154         if let Some(env) = &self.env {
155             env::set_var("_CLANG_SYS_TEST_ENV", env);
156         }
157 
158         for (name, (value, _)) in &self.vars {
159             if let Some(value) = value {
160                 env::set_var(name, value);
161             } else {
162                 env::remove_var(name);
163             }
164         }
165 
166         env::set_current_dir(&self.tmp).unwrap();
167 
168         let commands = self.commands.clone();
169         let mock = &mut *common::RUN_COMMAND_MOCK.lock().unwrap();
170         *mock = Some(Box::new(move |command, path, args| {
171             let command = command.to_string();
172             let path = path.to_string();
173             let args = args.iter().map(|a| a.to_string()).collect::<Vec<_>>();
174 
175             let mut commands = commands.lock().unwrap();
176             commands.invocations.push((command.clone(), path, args.clone()));
177 
178             let mut key = vec![command];
179             key.extend(args);
180             commands.responses.get(&key).cloned()
181         }));
182 
183         self
184     }
185 }
186 
187 impl Drop for Env {
drop(&mut self)188     fn drop(&mut self) {
189         env::remove_var("_CLANG_SYS_TEST");
190         env::remove_var("_CLANG_SYS_TEST_OS");
191         env::remove_var("_CLANG_SYS_TEST_ARCH");
192         env::remove_var("_CLANG_SYS_TEST_POINTER_WIDTH");
193         env::remove_var("_CLANG_SYS_TEST_ENV");
194 
195         for (name, (_, previous)) in &self.vars {
196             if let Some(previous) = previous {
197                 env::set_var(name, previous);
198             } else {
199                 env::remove_var(name);
200             }
201         }
202 
203         if let Err(error) = env::set_current_dir(&self.cwd) {
204             println!("Failed to reset working directory: {:?}", error);
205         }
206     }
207 }
208 
209 #[test]
test_all()210 fn test_all() {
211     // Run tests serially since they alter the environment.
212 
213     test_linux_directory_preference();
214     test_linux_version_preference();
215     test_linux_directory_and_version_preference();
216 
217     #[cfg(target_os = "windows")]
218     {
219         test_windows_bin_sibling();
220         test_windows_mingw_gnu();
221         test_windows_mingw_msvc();
222         test_windows_arm64_on_x86_64();
223         test_windows_x86_64_on_arm64();
224     }
225 }
226 
227 macro_rules! assert_error {
228     ($result:expr, $contents:expr $(,)?) => {
229         if let Err(error) = $result {
230             if !error.contains($contents) {
231                 panic!("expected error to contain {:?}, received: {error:?}", $contents);
232             }
233         } else {
234             panic!("expected error, received: {:?}", $result);
235         }
236     };
237 }
238 
239 //================================================
240 // Dynamic
241 //================================================
242 
243 // Linux -----------------------------------------
244 
test_linux_directory_preference()245 fn test_linux_directory_preference() {
246     let _env = Env::new("linux", Arch::X86_64, "64")
247         .so("usr/lib/libclang.so.1", "64")
248         .so("usr/local/lib/libclang.so.1", "64")
249         .enable();
250 
251     assert_eq!(
252         dynamic::find(true),
253         Ok(("usr/local/lib".into(), "libclang.so.1".into())),
254     );
255 }
256 
test_linux_version_preference()257 fn test_linux_version_preference() {
258     let _env = Env::new("linux", Arch::X86_64, "64")
259         .so("usr/lib/libclang-3.so", "64")
260         .so("usr/lib/libclang-3.5.so", "64")
261         .so("usr/lib/libclang-3.5.0.so", "64")
262         .enable();
263 
264     assert_eq!(
265         dynamic::find(true),
266         Ok(("usr/lib".into(), "libclang-3.5.0.so".into())),
267     );
268 }
269 
test_linux_directory_and_version_preference()270 fn test_linux_directory_and_version_preference() {
271     let _env = Env::new("linux", Arch::X86_64, "64")
272         .so("usr/local/llvm/lib/libclang-3.so", "64")
273         .so("usr/local/lib/libclang-3.5.so", "64")
274         .so("usr/lib/libclang-3.5.0.so", "64")
275         .enable();
276 
277     assert_eq!(
278         dynamic::find(true),
279         Ok(("usr/lib".into(), "libclang-3.5.0.so".into())),
280     );
281 }
282 
283 // Windows ---------------------------------------
284 
285 #[cfg(target_os = "windows")]
test_windows_bin_sibling()286 fn test_windows_bin_sibling() {
287     let _env = Env::new("windows", Arch::X86_64, "64")
288         .dir("Program Files\\LLVM\\lib")
289         .dll("Program Files\\LLVM\\bin\\libclang.dll", Arch::X86_64, "64")
290         .enable();
291 
292     assert_eq!(
293         dynamic::find(true),
294         Ok(("Program Files\\LLVM\\bin".into(), "libclang.dll".into())),
295     );
296 }
297 
298 #[cfg(target_os = "windows")]
test_windows_mingw_gnu()299 fn test_windows_mingw_gnu() {
300     let _env = Env::new("windows", Arch::X86_64, "64")
301         .env("gnu")
302         .dir("MSYS\\MinGW\\lib")
303         .dll("MSYS\\MinGW\\bin\\clang.dll", Arch::X86_64, "64")
304         .dir("Program Files\\LLVM\\lib")
305         .dll("Program Files\\LLVM\\bin\\libclang.dll", Arch::X86_64, "64")
306         .enable();
307 
308     assert_eq!(
309         dynamic::find(true),
310         Ok(("MSYS\\MinGW\\bin".into(), "clang.dll".into())),
311     );
312 }
313 
314 #[cfg(target_os = "windows")]
test_windows_mingw_msvc()315 fn test_windows_mingw_msvc() {
316     let _env = Env::new("windows", Arch::X86_64, "64")
317         .env("msvc")
318         .dir("MSYS\\MinGW\\lib")
319         .dll("MSYS\\MinGW\\bin\\clang.dll", Arch::X86_64, "64")
320         .dir("Program Files\\LLVM\\lib")
321         .dll("Program Files\\LLVM\\bin\\libclang.dll", Arch::X86_64, "64")
322         .enable();
323 
324     assert_eq!(
325         dynamic::find(true),
326         Ok(("Program Files\\LLVM\\bin".into(), "libclang.dll".into())),
327     );
328 }
329 
330 #[cfg(target_os = "windows")]
test_windows_arm64_on_x86_64()331 fn test_windows_arm64_on_x86_64() {
332     let _env = Env::new("windows", Arch::X86_64, "64")
333         .env("msvc")
334         .dir("Program Files\\LLVM\\lib")
335         .dll("Program Files\\LLVM\\bin\\libclang.dll", Arch::ARM64, "64")
336         .enable();
337 
338     assert_error!(
339         dynamic::find(true),
340         "invalid: [(Program Files\\LLVM\\bin\\libclang.dll: invalid DLL (ARM64)",
341     );
342 }
343 
344 #[cfg(target_os = "windows")]
test_windows_x86_64_on_arm64()345 fn test_windows_x86_64_on_arm64() {
346     let _env = Env::new("windows", Arch::ARM64, "64")
347         .env("msvc")
348         .dir("Program Files\\LLVM\\lib")
349         .dll("Program Files\\LLVM\\bin\\libclang.dll", Arch::X86_64, "64")
350         .enable();
351 
352     assert_error!(
353         dynamic::find(true),
354         "invalid: [(Program Files\\LLVM\\bin\\libclang.dll: invalid DLL (x86-64)",
355     );
356 }
357