//! This file will run at build time to autogenerate the Emscripten tests //! It will compile the files indicated in TESTS, to:executable and .wasm //! - Compile using cc and get the output from it (expected output) //! - Compile using emcc and get the .wasm from it (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::Path; use std::path::PathBuf; use std::process::Command; use std::fs::File; use std::io::prelude::*; use std::io::BufReader; static BANNER: &str = "// Rust test file autogenerated with cargo build (build/emtests.rs). // Please do NOT modify it by hand, as it will be reseted on next build.\n"; const EXTENSIONS: [&str; 2] = ["c", "cpp"]; const EXCLUDES: [&str; 0] = []; pub fn compile(file: &str, ignores: &HashSet) -> Option { let mut output_path = PathBuf::from(file); output_path.set_extension("out"); // let output_str = output_path.to_str().unwrap(); // Compile to .out // Command::new("cc") // .arg(file) // .arg("-o") // .arg(output_str) // .output() // .expect("failed to execute process"); // Get the result of .out // let output = Command::new(output_str) // .arg(output_str) // .output() // .expect("failed to execute process"); // Remove executable // fs::remove_file(output_str).unwrap(); let mut output_path = PathBuf::from(file); output_path.set_extension("js"); let output_str = output_path.to_str().unwrap(); let wasm_file_metadata = { let mut wasm_file_path = PathBuf::from(file); wasm_file_path.set_extension("wasm"); File::open(wasm_file_path).and_then(|wf| wf.metadata()).ok() }; let real_file = File::open(file).unwrap(); let file_metadata = real_file.metadata().unwrap(); if wasm_file_metadata.is_none() || file_metadata.modified().unwrap() >= wasm_file_metadata.unwrap().modified().unwrap() { // Compile to wasm let _wasm_compilation = Command::new("emcc") .arg(file) .arg("-s") .arg("WASM=1") .arg("-o") .arg(output_str) .output() .expect("failed to execute process"); // panic!("{:?}", wasm_compilation); // if output.stderr { // panic!("{}", output.stderr); // } // Remove js file if Path::new(output_str).is_file() { fs::remove_file(output_str).unwrap(); } else { println!("Output JS not found: {}", output_str); } } let mut output_path = PathBuf::from(file); output_path.set_extension("output"); let module_name = output_path .file_stem() .unwrap() .to_str() .unwrap() .to_owned(); // // let output_str = output_path.to_str().unwrap(); // Write the output to file // fs::write(output_str, output.stdout).expect("Unable to write file"); let rs_module_name = module_name.to_lowercase(); let rust_test_filepath = format!( concat!(env!("CARGO_MANIFEST_DIR"), "/tests/emtests/{}.rs"), rs_module_name.as_str() ); let output_extension = if file.ends_with("c") || module_name.starts_with("test_") { "out" } else { "txt" }; let ignored = if ignores.contains(&module_name.to_lowercase()) { "\n#[ignore]" } else { "" }; let module_path = format!("emtests/{}.wasm", module_name); let test_output_path = format!("emtests/{}.{}", module_name, output_extension); if !Path::new(&module_path).is_file() { println!("Path not found to test module: {}", module_path); None } else if !Path::new(&test_output_path).is_file() { println!("Path not found to test output: {}", module_path); None } else { let contents = format!( "#[test]{ignore} fn test_{rs_module_name}() {{ assert_emscripten_output!( \"../../{module_path}\", \"{rs_module_name}\", vec![], \"../../{test_output_path}\" ); }} ", ignore = ignored, module_path = module_path, rs_module_name = rs_module_name, test_output_path = test_output_path ); fs::write(&rust_test_filepath, contents.as_bytes()).unwrap(); Some(rs_module_name) } // panic!("OUTPUT: {:?}", output); } pub fn build() { let rust_test_modpath = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/emtests/mod.rs"); let mut modules: Vec = Vec::new(); // modules.reserve_exact(TESTS.len()); let ignores = read_ignore_list(); for ext in EXTENSIONS.iter() { for entry in glob(&format!("emtests/*.{}", ext)).unwrap() { match entry { Ok(path) => { let test = path.to_str().unwrap(); if !EXCLUDES.iter().any(|e| test.ends_with(e)) { 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, as it provides common macros for the emtests\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("emtests/ignores.txt").unwrap(); let f = BufReader::new(f); f.lines() .filter_map(Result::ok) .map(|v| v.to_lowercase()) .collect() }