//! 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 src_code = fs::read_to_string(file).expect("read src file"); let args = extract_args_from_source_file(&src_code); let mapdir_args = if let Some(a) = args { if !a.mapdir.is_empty() { let mut out_str = String::new(); out_str.push_str("vec!["); for (alias, real_dir) in a.mapdir { out_str.push_str(&format!( "(\"{}\".to_string(), \"{}\".to_string()),", alias, real_dir )); } out_str.push_str("]"); out_str } else { "vec![]".to_string() } } else { "vec![]".to_string() }; let contents = format!( "#[test]{ignore} fn test_{rs_module_name}() {{ assert_wasi_output!( \"../../{module_path}\", \"{rs_module_name}\", {mapdir_args}, \"../../{test_output_path}\" ); }} ", ignore = ignored, module_path = wasm_out_name, rs_module_name = rs_module_name, test_output_path = format!("{}.out", normalized_name), mapdir_args = mapdir_args, ); 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() } struct Args { pub mapdir: Vec<(String, String)>, } /// Pulls args to the program out of a comment at the top of the file starting with "// Args:" fn extract_args_from_source_file(source_code: &str) -> Option { if source_code.starts_with("// Args:") { let mut args = Args { mapdir: vec![] }; for arg_line in source_code .lines() .skip(1) .take_while(|line| line.starts_with("// ")) { let tokenized = arg_line .split_whitespace() .skip(1) .map(String::from) .collect::>(); match tokenized[1].as_ref() { "mapdir" => { if let [alias, real_dir] = &tokenized[2].split(':').collect::>()[..] { args.mapdir.push((alias.to_string(), real_dir.to_string())); } else { eprintln!( "Parse error in mapdir {} not parsed correctly", &tokenized[2] ); } } e => { eprintln!("WARN: comment arg: {} is not supported", e); } } } return Some(args); } None }