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