//! This file will run at build time to autogenerate the WASI regression tests //! It will compile the files indicated in TESTS, to:executable and .wasm //! - Compile with the native rust target to get the expected output //! - Compile with the latest WASI target to get the wasm //! - Generate the test that will compare the output of running the .wasm file //! with wasmer with the expected output use glob::glob; use std::collections::HashSet; use std::fs; use std::path::PathBuf; use std::process::Command; use std::fs::File; use std::io::prelude::*; use std::io::BufReader; static BANNER: &str = "// !!! THIS IS A GENERATED FILE !!! // ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME // Files autogenerated with cargo build (build/wasitests.rs).\n"; pub fn compile(file: &str, ignores: &HashSet) -> Option { dbg!(file); let mut output_path = PathBuf::from(file); output_path.set_extension("out"); assert!(file.ends_with(".rs")); let normalized_name = { let mut nn = file.to_lowercase(); nn.truncate(file.len() - 3); nn }; Command::new("rustc") .arg("+nightly") .arg(file) .arg("-o") .arg(&normalized_name) .output() .expect("Failed to compile program to native code"); #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let normal_path = PathBuf::from(&normalized_name); let mut perm = normal_path .metadata() .expect("native executable") .permissions(); perm.set_mode(0o766); fs::set_permissions(normal_path, perm).expect("set permissions"); } let rs_module_name = { let temp = PathBuf::from(&normalized_name); temp.file_name().unwrap().to_string_lossy().to_string() }; let result = Command::new(&normalized_name) .output() .expect("Failed to execute native program"); let wasm_out_name = format!("{}.wasm", &normalized_name); Command::new("rustc") .arg("+nightly") .arg("--target=wasm32-wasi") .arg(file) .arg("-o") .arg(&wasm_out_name) .output() .expect("Failed to compile program to native code"); let ignored = if ignores.contains(&rs_module_name) { "\n#[ignore]" } else { "" }; let contents = format!( "#[test]{ignore} fn test_{rs_module_name}() {{ assert_wasi_output!( \"../../{module_path}\", \"{rs_module_name}\", vec![], \"../../{test_output_path}\" ); }} ", ignore = ignored, module_path = wasm_out_name, rs_module_name = rs_module_name, test_output_path = format!("{}.out", normalized_name), ); let rust_test_filepath = format!( concat!(env!("CARGO_MANIFEST_DIR"), "/tests/{}.rs"), normalized_name, ); fs::write(&rust_test_filepath, contents.as_bytes()).expect("writing test file"); fs::write(&output_path, result.stdout).expect("writing output to file"); Some(rs_module_name) } pub fn build() { let rust_test_modpath = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/wasitests/mod.rs"); let mut modules: Vec = Vec::new(); let ignores = read_ignore_list(); for entry in glob("wasitests/*.rs").unwrap() { match entry { Ok(path) => { let test = path.to_str().unwrap(); if let Some(module_name) = compile(test, &ignores) { modules.push(module_name); } } Err(e) => println!("{:?}", e), } } modules.sort(); let mut modules: Vec = modules.iter().map(|m| format!("mod {};", m)).collect(); assert!(modules.len() > 0, "Expected > 0 modules found"); modules.insert(0, BANNER.to_string()); modules.insert(1, "// The _common module is not autogenerated. It provides common macros for the wasitests\n#[macro_use]\nmod _common;".to_string()); // We add an empty line modules.push("".to_string()); let modfile: String = modules.join("\n"); let source = fs::read(&rust_test_modpath).unwrap(); // We only modify the mod file if has changed if source != modfile.as_bytes() { fs::write(&rust_test_modpath, modfile.as_bytes()).unwrap(); } } fn read_ignore_list() -> HashSet { let f = File::open("wasitests/ignores.txt").unwrap(); let f = BufReader::new(f); f.lines() .filter_map(Result::ok) .map(|v| v.to_lowercase()) .collect() }