wasmer_c_api_test_runner/
lib.rs

1#[cfg(test)]
2use std::error::Error;
3#[cfg(test)]
4use std::process::Stdio;
5
6#[cfg(test)]
7static INCLUDE_REGEX: &str = "#include \"(.*)\"";
8
9#[derive(Debug)]
10pub struct Config {
11    pub wasmer_dir: String,
12    pub root_dir: String,
13}
14
15impl Config {
16    pub fn get() -> Config {
17        let mut config = Config {
18            wasmer_dir: std::env::var("WASMER_DIR").unwrap_or_default(),
19            root_dir: std::env::var("ROOT_DIR").unwrap_or_default(),
20        };
21
22        let wasmer_base_dir = find_wasmer_base_dir();
23        let manifest_dir = env!("CARGO_MANIFEST_DIR");
24
25        if config.wasmer_dir.is_empty() {
26            println!("manifest dir = {manifest_dir}, wasmer root dir = {wasmer_base_dir}");
27            config.wasmer_dir = wasmer_base_dir.clone() + "/package";
28            assert!(std::path::Path::new(&config.wasmer_dir).exists());
29        }
30        if config.root_dir.is_empty() {
31            config.root_dir = wasmer_base_dir + "/lib/c-api/tests";
32        }
33
34        config
35    }
36}
37
38fn find_wasmer_base_dir() -> String {
39    let wasmer_base_dir = env!("CARGO_MANIFEST_DIR");
40    let mut path2 = wasmer_base_dir.split("wasmer").collect::<Vec<_>>();
41    path2.pop();
42    let mut wasmer_base_dir = path2.join("wasmer");
43
44    if wasmer_base_dir.contains("wasmer/lib/c-api") {
45        wasmer_base_dir = wasmer_base_dir
46            .split("wasmer/lib/c-api")
47            .next()
48            .unwrap()
49            .to_string()
50            + "wasmer";
51    } else if wasmer_base_dir.contains("wasmer\\lib\\c-api") {
52        wasmer_base_dir = wasmer_base_dir
53            .split("wasmer\\lib\\c-api")
54            .next()
55            .unwrap()
56            .to_string()
57            + "wasmer";
58    }
59
60    wasmer_base_dir
61}
62
63#[derive(Default)]
64pub struct RemoveTestsOnDrop {}
65
66impl Drop for RemoveTestsOnDrop {
67    fn drop(&mut self) {
68        let manifest_dir = env!("CARGO_MANIFEST_DIR");
69        for entry in std::fs::read_dir(manifest_dir).unwrap() {
70            let entry = entry.unwrap();
71            let path = entry.path();
72            let extension = path.extension().and_then(|s| s.to_str());
73            if extension == Some("obj") || extension == Some("exe") || extension == Some("o") {
74                println!("removing {}", path.display());
75                let _ = std::fs::remove_file(&path);
76            }
77        }
78        if let Some(parent) = std::path::Path::new(&manifest_dir).parent() {
79            for entry in std::fs::read_dir(parent).unwrap() {
80                let entry = entry.unwrap();
81                let path = entry.path();
82                let extension = path.extension().and_then(|s| s.to_str());
83                if extension == Some("obj") || extension == Some("exe") || extension == Some("o") {
84                    println!("removing {}", path.display());
85                    let _ = std::fs::remove_file(&path);
86                }
87            }
88        }
89    }
90}
91
92#[cfg(test)]
93pub const CAPI_BASE_TESTS: &[&str] = &[
94    "wasm-c-api/example/callback",
95    "wasm-c-api/example/memory",
96    "wasm-c-api/example/start",
97    "wasm-c-api/example/global",
98    "wasm-c-api/example/reflect",
99    "wasm-c-api/example/trap",
100    "wasm-c-api/example/hello",
101    "wasm-c-api/example/serialize",
102    "wasm-c-api/example/multi",
103];
104
105#[allow(unused_variables, dead_code)]
106pub const CAPI_BASE_TESTS_NOT_WORKING: &[&str] = &[
107    "wasm-c-api/example/finalize",
108    "wasm-c-api/example/hostref",
109    "wasm-c-api/example/threads",
110    "wasm-c-api/example/table",
111];
112
113// Runs all the tests that are working in the /c directory
114#[test]
115fn test_ok() {
116    let _drop = RemoveTestsOnDrop::default();
117    let config = Config::get();
118    println!("config: {config:#?}");
119
120    let manifest_dir = env!("CARGO_MANIFEST_DIR");
121    let host = target_lexicon::HOST.to_string();
122    let target = &host;
123
124    let wasmer_dll_dir = format!("{}/lib", config.wasmer_dir);
125    let libwasmer_so_path = format!("{}/lib/libwasmer.so", config.wasmer_dir);
126    let exe_dir = format!("{manifest_dir}/../wasm-c-api/example");
127    let path = std::env::var("PATH").unwrap_or_default();
128    let newpath = format!("{};{path}", wasmer_dll_dir.replace('/', "\\"));
129
130    if target.contains("msvc") {
131        for test in CAPI_BASE_TESTS.iter() {
132            let mut build = cc::Build::new();
133            let build = build
134                .cargo_metadata(false)
135                .warnings(true)
136                .static_crt(true)
137                .extra_warnings(true)
138                .warnings_into_errors(false)
139                .debug(true)
140                .host(&host)
141                .target(target)
142                .opt_level(1);
143
144            let compiler = build.try_get_compiler().unwrap();
145
146            println!("compiler {compiler:#?}");
147
148            // run vcvars
149            let vcvars_bat_path = find_vcvars64(&compiler).expect("no vcvars64.bat");
150            let mut vcvars = std::process::Command::new("cmd");
151            vcvars.arg("/C");
152            vcvars.arg(vcvars_bat_path);
153            println!("running {vcvars:?}");
154
155            // cmd /C vcvars64.bat
156            let output = vcvars
157                .output()
158                .expect("could not invoke vcvars64.bat at {vcvars_bat_path}");
159
160            if !output.status.success() {
161                println!();
162                println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
163                println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
164                // print_wasmer_root_to_stdout(&config);
165                panic!("failed to invoke vcvars64.bat {test}");
166            }
167
168            let mut command = compiler.to_command();
169
170            command.arg(format!("{manifest_dir}/../{test}.c"));
171            if !config.wasmer_dir.is_empty() {
172                command.arg("/I");
173                command.arg(format!("{}/wasm-c-api/include/", config.root_dir));
174                command.arg("/I");
175                command.arg(format!("{}/include/", config.wasmer_dir));
176                let mut log = String::new();
177                fixup_symlinks(
178                    &[
179                        format!("{}/include/", config.wasmer_dir),
180                        format!("{}/wasm-c-api/include/", config.root_dir),
181                        config.root_dir.to_string(),
182                    ],
183                    &mut log,
184                    &config.root_dir,
185                )
186                .unwrap_or_else(|_| panic!("failed to fix symlinks: {log}"));
187                println!("{log}");
188            }
189            command.arg("/link");
190            if !config.wasmer_dir.is_empty() {
191                command.arg(format!("/LIBPATH:{}/lib", config.wasmer_dir));
192                command.arg(format!("{}/lib/wasmer.dll.lib", config.wasmer_dir));
193            }
194            command.arg(format!("/OUT:{manifest_dir}/../{test}.exe"));
195
196            println!("compiling {test}: {command:?}");
197
198            // compile
199            let output = command
200                .output()
201                .unwrap_or_else(|_| panic!("failed to compile {command:#?}"));
202            if !output.status.success() {
203                println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
204                println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
205                println!("output: {output:#?}");
206                // print_wasmer_root_to_stdout(&config);
207                panic!("failed to compile {test}");
208            }
209
210            if std::path::Path::new(&format!("{manifest_dir}/../{test}.exe")).exists() {
211                println!("exe does not exist");
212            }
213
214            // execute
215            let mut command = std::process::Command::new(format!("{manifest_dir}/../{test}.exe"));
216            println!("newpath: {}", newpath.clone());
217            command.env("PATH", newpath.clone());
218            command.current_dir(exe_dir.clone());
219            println!("executing {test}: {command:?}");
220            println!("setting current dir = {exe_dir}");
221            let output = command
222                .output()
223                .unwrap_or_else(|_| panic!("failed to run {command:#?}"));
224            if !output.status.success() {
225                println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
226                println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
227                println!("output: {output:#?}");
228                // print_wasmer_root_to_stdout(&config);
229                panic!("failed to execute {test}");
230            }
231
232            // cc -g -IC:/Users/felix/Development/wasmer/lib/c-api/tests/
233            //          -IC:/Users/felix/Development/wasmer/package/include
234            //
235            //          -Wl,-rpath,C:/Users/felix/Development/wasmer/package/lib
236            //
237            //          wasm-c-api/example/callback.c
238            //
239            //          -LC:/Users/felix/Development/wasmer/package/lib -lwasmer
240            //
241            // -o wasm-c-api/example/callback
242        }
243    } else {
244        for test in CAPI_BASE_TESTS.iter() {
245            let compiler_cmd = match std::process::Command::new("cc").output() {
246                Ok(_) => "cc",
247                Err(_) => "gcc",
248            };
249            let mut command = std::process::Command::new(compiler_cmd);
250
251            if !config.wasmer_dir.is_empty() {
252                command.arg("-I");
253                command.arg(format!("{}/wasm-c-api/include/", config.root_dir));
254                command.arg("-I");
255                command.arg(format!("{}/include/", config.wasmer_dir));
256                let mut log = String::new();
257                fixup_symlinks(
258                    &[
259                        format!("{}/include/", config.wasmer_dir),
260                        format!("{}/wasm-c-api/include/", config.root_dir),
261                        config.root_dir.to_string(),
262                    ],
263                    &mut log,
264                    &config.root_dir,
265                )
266                .unwrap_or_else(|_| panic!("failed to fix symlinks: {log}"));
267            }
268            command.arg(format!("{manifest_dir}/../{test}.c"));
269            if !config.wasmer_dir.is_empty() {
270                command.arg("-L");
271                command.arg(format!("{}/lib/", config.wasmer_dir));
272                command.arg("-lwasmer");
273                command.arg(format!("-Wl,-rpath,{}/lib/", config.wasmer_dir));
274            }
275            command.arg("-o");
276            command.arg(format!("{manifest_dir}/../{test}"));
277
278            // print_wasmer_root_to_stdout(&config);
279
280            println!("compile: {command:#?}");
281            // compile
282            let output = command
283                .stdout(Stdio::inherit())
284                .stderr(Stdio::inherit())
285                .current_dir(find_wasmer_base_dir())
286                .output()
287                .unwrap_or_else(|_| panic!("failed to compile {command:#?}"));
288            if !output.status.success() {
289                println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
290                println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
291                // print_wasmer_root_to_stdout(&config);
292                panic!("failed to compile {test}: {command:#?}");
293            }
294
295            // execute
296            let mut command = std::process::Command::new(format!("{manifest_dir}/../{test}"));
297            command.env("LD_PRELOAD", libwasmer_so_path.clone());
298            command.current_dir(exe_dir.clone());
299            println!("execute: {command:#?}");
300            let output = command
301                .output()
302                .unwrap_or_else(|_| panic!("failed to run {command:#?}"));
303            if !output.status.success() {
304                println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
305                println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
306                // print_wasmer_root_to_stdout(&config);
307                panic!("failed to execute {test}: {command:#?}");
308            }
309        }
310    }
311
312    for test in CAPI_BASE_TESTS.iter() {
313        let _ = std::fs::remove_file(format!("{manifest_dir}/{test}.obj"));
314        let _ = std::fs::remove_file(format!("{manifest_dir}/../{test}.exe"));
315        let _ = std::fs::remove_file(format!("{manifest_dir}/../{test}"));
316    }
317}
318
319// #[cfg(test)]
320// fn print_wasmer_root_to_stdout(config: &Config) {
321//     println!("print_wasmer_root_to_stdout");
322
323//     use walkdir::WalkDir;
324
325//     println!(
326//         "wasmer dir: {}",
327//         std::path::Path::new(&config.wasmer_dir)
328//             .canonicalize()
329//             .unwrap()
330//             .display()
331//     );
332
333//     for entry in WalkDir::new(&config.wasmer_dir)
334//         .into_iter()
335//         .filter_map(Result::ok)
336//     {
337//         let f_name = String::from(entry.path().canonicalize().unwrap().to_string_lossy());
338//         println!("{f_name}");
339//     }
340
341//     println!(
342//         "root dir: {}",
343//         std::path::Path::new(&config.root_dir)
344//             .canonicalize()
345//             .unwrap()
346//             .display()
347//     );
348
349//     for entry in WalkDir::new(&config.root_dir)
350//         .into_iter()
351//         .filter_map(Result::ok)
352//     {
353//         let f_name = String::from(entry.path().canonicalize().unwrap().to_string_lossy());
354//         println!("{f_name}");
355//     }
356
357//     println!("printed");
358// }
359
360#[cfg(test)]
361fn fixup_symlinks(
362    include_paths: &[String],
363    log: &mut String,
364    root_dir: &str,
365) -> Result<(), Box<dyn Error>> {
366    let source = std::path::Path::new(root_dir)
367        .join("lib")
368        .join("c-api")
369        .join("tests")
370        .join("wasm-c-api")
371        .join("include")
372        .join("wasm.h");
373    let target = std::path::Path::new(root_dir)
374        .join("lib")
375        .join("c-api")
376        .join("tests")
377        .join("wasm.h");
378    println!("copying {} -> {}", source.display(), target.display());
379    let _ = std::fs::copy(source, target);
380
381    log.push_str(&format!("include paths: {include_paths:?}"));
382    for i in include_paths {
383        let i = i.replacen("-I", "", 1);
384        let i = i.replacen("/I", "", 1);
385        let mut paths_headers = Vec::new();
386        let readdir = match std::fs::read_dir(&i) {
387            Ok(o) => o,
388            Err(_) => continue,
389        };
390        for entry in readdir {
391            let entry = entry?;
392            let path = entry.path();
393            let path_display = format!("{}", path.display());
394            if path_display.ends_with('h') {
395                paths_headers.push(path_display);
396            }
397        }
398        fixup_symlinks_inner(&paths_headers, log)?;
399    }
400
401    Ok(())
402}
403
404#[cfg(test)]
405fn fixup_symlinks_inner(include_paths: &[String], log: &mut String) -> Result<(), Box<dyn Error>> {
406    log.push_str(&format!("fixup symlinks: {include_paths:#?}"));
407    let regex = regex::Regex::new(INCLUDE_REGEX).unwrap();
408    for path in include_paths.iter() {
409        let file = match std::fs::read_to_string(path) {
410            Ok(o) => o,
411            _ => continue,
412        };
413        let lines_3 = file.lines().take(3).collect::<Vec<_>>();
414        log.push_str(&format!("first 3 lines of {path:?}: {lines_3:#?}\n"));
415
416        let parent = std::path::Path::new(&path).parent().unwrap();
417        if let Ok(symlink) = std::fs::read_to_string(parent.join(&file)) {
418            log.push_str(&format!("symlinking {path:?}\n"));
419            std::fs::write(path, symlink)?;
420        }
421
422        // follow #include directives and recurse
423        let filepaths = regex
424            .captures_iter(&file)
425            .map(|c| c[1].to_string())
426            .collect::<Vec<_>>();
427        log.push_str(&format!("regex captures: ({path:?}): {filepaths:#?}\n"));
428        let joined_filepaths = filepaths
429            .iter()
430            .map(|s| {
431                let path = parent.join(s);
432                format!("{}", path.display())
433            })
434            .collect::<Vec<_>>();
435        fixup_symlinks_inner(&joined_filepaths, log)?;
436    }
437    Ok(())
438}
439
440#[cfg(test)]
441fn find_vcvars64(compiler: &cc::Tool) -> Option<String> {
442    if !compiler.is_like_msvc() {
443        return None;
444    }
445
446    let path = compiler.path();
447    let path = format!("{}", path.display());
448    let split = path.split("VC").next()?;
449
450    Some(format!("{split}VC\\Auxiliary\\Build\\vcvars64.bat"))
451}