2019-05-17 19:09:31 +00:00
|
|
|
//! 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<String>) -> Option<String> {
|
|
|
|
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");
|
|
|
|
}
|
2019-05-17 22:31:02 +00:00
|
|
|
let rs_module_name = {
|
|
|
|
let temp = PathBuf::from(&normalized_name);
|
|
|
|
temp.file_name().unwrap().to_string_lossy().to_string()
|
|
|
|
};
|
2019-05-17 19:09:31 +00:00
|
|
|
|
|
|
|
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");
|
|
|
|
|
2019-05-17 22:31:02 +00:00
|
|
|
let ignored = if ignores.contains(&rs_module_name) {
|
2019-05-17 19:09:31 +00:00
|
|
|
"\n#[ignore]"
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
};
|
2019-05-17 22:31:02 +00:00
|
|
|
|
2019-05-21 00:43:50 +00:00
|
|
|
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()
|
|
|
|
};
|
|
|
|
|
2019-05-17 19:09:31 +00:00
|
|
|
let contents = format!(
|
|
|
|
"#[test]{ignore}
|
|
|
|
fn test_{rs_module_name}() {{
|
|
|
|
assert_wasi_output!(
|
|
|
|
\"../../{module_path}\",
|
|
|
|
\"{rs_module_name}\",
|
2019-05-21 00:43:50 +00:00
|
|
|
{mapdir_args},
|
2019-05-17 19:09:31 +00:00
|
|
|
\"../../{test_output_path}\"
|
|
|
|
);
|
|
|
|
}}
|
|
|
|
",
|
|
|
|
ignore = ignored,
|
|
|
|
module_path = wasm_out_name,
|
|
|
|
rs_module_name = rs_module_name,
|
|
|
|
test_output_path = format!("{}.out", normalized_name),
|
2019-05-21 00:43:50 +00:00
|
|
|
mapdir_args = mapdir_args,
|
2019-05-17 19:09:31 +00:00
|
|
|
);
|
|
|
|
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<String> = 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<String> = 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");
|
2019-05-17 22:31:02 +00:00
|
|
|
let source = fs::read(&rust_test_modpath).unwrap();
|
2019-05-17 19:09:31 +00:00
|
|
|
// 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<String> {
|
|
|
|
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()
|
|
|
|
}
|
2019-05-21 00:43:50 +00:00
|
|
|
|
|
|
|
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<Args> {
|
|
|
|
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::<Vec<String>>();
|
|
|
|
match tokenized[1].as_ref() {
|
|
|
|
"mapdir" => {
|
|
|
|
if let [alias, real_dir] = &tokenized[2].split(':').collect::<Vec<&str>>()[..] {
|
|
|
|
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
|
|
|
|
}
|