Skip to main content

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    "wasm-c-api/example/hostref",
104];
105
106// Tests that only work against the sys backend. The V8 backend's module
107// parser rejects modules exporting externref-typed items ("ExternRef is not
108// supported by this backend yet", lib/api/src/utils/polyfill.rs), and its
109// `ExternRef` bindings are unimplemented.
110#[cfg(test)]
111pub const CAPI_SYS_ONLY_TESTS: &[&str] = &["wasm-c-api/example/hostref"];
112
113#[cfg(test)]
114fn capi_tests_for_backend() -> Vec<&'static str> {
115    let backend = std::env::var("WASMER_CAPI_CONFIG").unwrap_or_default();
116    CAPI_BASE_TESTS
117        .iter()
118        .copied()
119        .filter(|test| {
120            if backend == "v8" && CAPI_SYS_ONLY_TESTS.contains(test) {
121                println!("skipping {test}: not supported by the {backend} backend");
122                false
123            } else {
124                true
125            }
126        })
127        .collect()
128}
129
130#[allow(unused_variables, dead_code)]
131pub const CAPI_BASE_TESTS_NOT_WORKING: &[&str] = &[
132    "wasm-c-api/example/finalize",
133    "wasm-c-api/example/threads",
134    // The externref/funcref reference surface `table.c` exercises is implemented
135    // (see `hostref.c`, enabled above). `table.c` additionally stores a *dynamic*
136    // host function into a funcref table (line ~167), which the sys VM rejects
137    // ("dynamic functions cannot be used in tables or as funcrefs") — a separate
138    // VM limitation, out of scope for the C API reference surface.
139    "wasm-c-api/example/table",
140];
141
142// Runs all the tests that are working in the /c directory
143#[test]
144fn test_ok() {
145    let _drop = RemoveTestsOnDrop::default();
146    let config = Config::get();
147    println!("config: {config:#?}");
148
149    let manifest_dir = env!("CARGO_MANIFEST_DIR");
150    let host = target_lexicon::HOST.to_string();
151    let target = &host;
152
153    let wasmer_dll_dir = format!("{}/lib", config.wasmer_dir);
154    let libwasmer_so_path = format!("{}/lib/libwasmer.so", config.wasmer_dir);
155    let exe_dir = format!("{manifest_dir}/../wasm-c-api/example");
156    let path = std::env::var("PATH").unwrap_or_default();
157    let newpath = format!("{};{path}", wasmer_dll_dir.replace('/', "\\"));
158
159    let tests = capi_tests_for_backend();
160
161    if target.contains("msvc") {
162        for test in tests.iter() {
163            let mut build = cc::Build::new();
164            let build = build
165                .cargo_metadata(false)
166                .warnings(true)
167                .static_crt(true)
168                .extra_warnings(true)
169                .warnings_into_errors(false)
170                .debug(true)
171                .host(&host)
172                .target(target)
173                .opt_level(1);
174
175            let compiler = build.try_get_compiler().unwrap();
176
177            println!("compiler {compiler:#?}");
178
179            // run vcvars
180            let vcvars_bat_path = find_vcvars64(&compiler).expect("no vcvars64.bat");
181            let mut vcvars = std::process::Command::new("cmd");
182            vcvars.arg("/C");
183            vcvars.arg(vcvars_bat_path);
184            println!("running {vcvars:?}");
185
186            // cmd /C vcvars64.bat
187            let output = vcvars
188                .output()
189                .expect("could not invoke vcvars64.bat at {vcvars_bat_path}");
190
191            if !output.status.success() {
192                println!();
193                println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
194                println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
195                // print_wasmer_root_to_stdout(&config);
196                panic!("failed to invoke vcvars64.bat {test}");
197            }
198
199            let mut command = compiler.to_command();
200
201            command.arg(format!("{manifest_dir}/../{test}.c"));
202            if !config.wasmer_dir.is_empty() {
203                command.arg("/I");
204                command.arg(format!("{}/wasm-c-api/include/", config.root_dir));
205                command.arg("/I");
206                command.arg(format!("{}/include/", config.wasmer_dir));
207                let mut log = String::new();
208                fixup_symlinks(
209                    &[
210                        format!("{}/include/", config.wasmer_dir),
211                        format!("{}/wasm-c-api/include/", config.root_dir),
212                        config.root_dir.to_string(),
213                    ],
214                    &mut log,
215                    &config.root_dir,
216                )
217                .unwrap_or_else(|_| panic!("failed to fix symlinks: {log}"));
218                println!("{log}");
219            }
220            command.arg("/link");
221            if !config.wasmer_dir.is_empty() {
222                command.arg(format!("/LIBPATH:{}/lib", config.wasmer_dir));
223                command.arg(format!("{}/lib/wasmer.dll.lib", config.wasmer_dir));
224            }
225            command.arg(format!("/OUT:{manifest_dir}/../{test}.exe"));
226
227            println!("compiling {test}: {command:?}");
228
229            // compile
230            let output = command
231                .output()
232                .unwrap_or_else(|_| panic!("failed to compile {command:#?}"));
233            if !output.status.success() {
234                println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
235                println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
236                println!("output: {output:#?}");
237                // print_wasmer_root_to_stdout(&config);
238                panic!("failed to compile {test}");
239            }
240
241            if std::path::Path::new(&format!("{manifest_dir}/../{test}.exe")).exists() {
242                println!("exe does not exist");
243            }
244
245            // execute
246            let mut command = std::process::Command::new(format!("{manifest_dir}/../{test}.exe"));
247            println!("newpath: {}", newpath.clone());
248            command.env("PATH", newpath.clone());
249            command.current_dir(exe_dir.clone());
250            println!("executing {test}: {command:?}");
251            println!("setting current dir = {exe_dir}");
252            let output = command
253                .output()
254                .unwrap_or_else(|_| panic!("failed to run {command:#?}"));
255            if !output.status.success() {
256                println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
257                println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
258                println!("output: {output:#?}");
259                // print_wasmer_root_to_stdout(&config);
260                panic!("failed to execute {test}");
261            }
262
263            // cc -g -IC:/Users/felix/Development/wasmer/lib/c-api/tests/
264            //          -IC:/Users/felix/Development/wasmer/package/include
265            //
266            //          -Wl,-rpath,C:/Users/felix/Development/wasmer/package/lib
267            //
268            //          wasm-c-api/example/callback.c
269            //
270            //          -LC:/Users/felix/Development/wasmer/package/lib -lwasmer
271            //
272            // -o wasm-c-api/example/callback
273        }
274    } else {
275        for test in tests.iter() {
276            let compiler_cmd = match std::process::Command::new("cc").output() {
277                Ok(_) => "cc",
278                Err(_) => "gcc",
279            };
280            let mut command = std::process::Command::new(compiler_cmd);
281
282            if !config.wasmer_dir.is_empty() {
283                command.arg("-I");
284                command.arg(format!("{}/wasm-c-api/include/", config.root_dir));
285                command.arg("-I");
286                command.arg(format!("{}/include/", config.wasmer_dir));
287                let mut log = String::new();
288                fixup_symlinks(
289                    &[
290                        format!("{}/include/", config.wasmer_dir),
291                        format!("{}/wasm-c-api/include/", config.root_dir),
292                        config.root_dir.to_string(),
293                    ],
294                    &mut log,
295                    &config.root_dir,
296                )
297                .unwrap_or_else(|_| panic!("failed to fix symlinks: {log}"));
298            }
299            command.arg(format!("{manifest_dir}/../{test}.c"));
300            if !config.wasmer_dir.is_empty() {
301                command.arg("-L");
302                command.arg(format!("{}/lib/", config.wasmer_dir));
303                command.arg("-lwasmer");
304                command.arg(format!("-Wl,-rpath,{}/lib/", config.wasmer_dir));
305            }
306            command.arg("-o");
307            command.arg(format!("{manifest_dir}/../{test}"));
308
309            // print_wasmer_root_to_stdout(&config);
310
311            println!("compile: {command:#?}");
312            // compile
313            let output = command
314                .stdout(Stdio::inherit())
315                .stderr(Stdio::inherit())
316                .current_dir(find_wasmer_base_dir())
317                .output()
318                .unwrap_or_else(|_| panic!("failed to compile {command:#?}"));
319            if !output.status.success() {
320                println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
321                println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
322                // print_wasmer_root_to_stdout(&config);
323                panic!("failed to compile {test}: {command:#?}");
324            }
325
326            // execute
327            let mut command = std::process::Command::new(format!("{manifest_dir}/../{test}"));
328            command.env("LD_PRELOAD", libwasmer_so_path.clone());
329            command.current_dir(exe_dir.clone());
330            println!("execute: {command:#?}");
331            let output = command
332                .output()
333                .unwrap_or_else(|_| panic!("failed to run {command:#?}"));
334            if !output.status.success() {
335                println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
336                println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
337                // print_wasmer_root_to_stdout(&config);
338                panic!("failed to execute {test}: {command:#?}");
339            }
340        }
341    }
342
343    for test in tests.iter() {
344        let _ = std::fs::remove_file(format!("{manifest_dir}/{test}.obj"));
345        let _ = std::fs::remove_file(format!("{manifest_dir}/../{test}.exe"));
346        let _ = std::fs::remove_file(format!("{manifest_dir}/../{test}"));
347    }
348}
349
350// #[cfg(test)]
351// fn print_wasmer_root_to_stdout(config: &Config) {
352//     println!("print_wasmer_root_to_stdout");
353
354//     use walkdir::WalkDir;
355
356//     println!(
357//         "wasmer dir: {}",
358//         std::path::Path::new(&config.wasmer_dir)
359//             .canonicalize()
360//             .unwrap()
361//             .display()
362//     );
363
364//     for entry in WalkDir::new(&config.wasmer_dir)
365//         .into_iter()
366//         .filter_map(Result::ok)
367//     {
368//         let f_name = String::from(entry.path().canonicalize().unwrap().to_string_lossy());
369//         println!("{f_name}");
370//     }
371
372//     println!(
373//         "root dir: {}",
374//         std::path::Path::new(&config.root_dir)
375//             .canonicalize()
376//             .unwrap()
377//             .display()
378//     );
379
380//     for entry in WalkDir::new(&config.root_dir)
381//         .into_iter()
382//         .filter_map(Result::ok)
383//     {
384//         let f_name = String::from(entry.path().canonicalize().unwrap().to_string_lossy());
385//         println!("{f_name}");
386//     }
387
388//     println!("printed");
389// }
390
391#[cfg(test)]
392fn fixup_symlinks(
393    include_paths: &[String],
394    log: &mut String,
395    root_dir: &str,
396) -> Result<(), Box<dyn Error>> {
397    let source = std::path::Path::new(root_dir)
398        .join("lib")
399        .join("c-api")
400        .join("tests")
401        .join("wasm-c-api")
402        .join("include")
403        .join("wasm.h");
404    let target = std::path::Path::new(root_dir)
405        .join("lib")
406        .join("c-api")
407        .join("tests")
408        .join("wasm.h");
409    println!("copying {} -> {}", source.display(), target.display());
410    let _ = std::fs::copy(source, target);
411
412    log.push_str(&format!("include paths: {include_paths:?}"));
413    for i in include_paths {
414        let i = i.replacen("-I", "", 1);
415        let i = i.replacen("/I", "", 1);
416        let mut paths_headers = Vec::new();
417        let readdir = match std::fs::read_dir(&i) {
418            Ok(o) => o,
419            Err(_) => continue,
420        };
421        for entry in readdir {
422            let entry = entry?;
423            let path = entry.path();
424            let path_display = format!("{}", path.display());
425            if path_display.ends_with('h') {
426                paths_headers.push(path_display);
427            }
428        }
429        fixup_symlinks_inner(&paths_headers, log)?;
430    }
431
432    Ok(())
433}
434
435#[cfg(test)]
436fn fixup_symlinks_inner(include_paths: &[String], log: &mut String) -> Result<(), Box<dyn Error>> {
437    log.push_str(&format!("fixup symlinks: {include_paths:#?}"));
438    let regex = regex::Regex::new(INCLUDE_REGEX).unwrap();
439    for path in include_paths.iter() {
440        let file = match std::fs::read_to_string(path) {
441            Ok(o) => o,
442            _ => continue,
443        };
444        let lines_3 = file.lines().take(3).collect::<Vec<_>>();
445        log.push_str(&format!("first 3 lines of {path:?}: {lines_3:#?}\n"));
446
447        let parent = std::path::Path::new(&path).parent().unwrap();
448        if let Ok(symlink) = std::fs::read_to_string(parent.join(&file)) {
449            log.push_str(&format!("symlinking {path:?}\n"));
450            std::fs::write(path, symlink)?;
451        }
452
453        // follow #include directives and recurse
454        let filepaths = regex
455            .captures_iter(&file)
456            .map(|c| c[1].to_string())
457            .collect::<Vec<_>>();
458        log.push_str(&format!("regex captures: ({path:?}): {filepaths:#?}\n"));
459        let joined_filepaths = filepaths
460            .iter()
461            .map(|s| {
462                let path = parent.join(s);
463                format!("{}", path.display())
464            })
465            .collect::<Vec<_>>();
466        fixup_symlinks_inner(&joined_filepaths, log)?;
467    }
468    Ok(())
469}
470
471#[cfg(test)]
472fn find_vcvars64(compiler: &cc::Tool) -> Option<String> {
473    if !compiler.is_like_msvc() {
474        return None;
475    }
476
477    let path = compiler.path();
478    let path = format!("{}", path.display());
479    let split = path.split("VC").next()?;
480
481    Some(format!("{split}VC\\Auxiliary\\Build\\vcvars64.bat"))
482}