Browse Source
Some wasi-common tests assume that stdin is never ready to be read, but on CI stdin is closed so it's always ready to be read. Work around this by guaranteeing that wasi-common tests always have an unreadable stdin pipe by creating our own pipe.pull/502/head
Alex Crichton
5 years ago
10 changed files with 185 additions and 157 deletions
@ -1,92 +0,0 @@ |
|||||
use cranelift_codegen::settings::{self, Configurable}; |
|
||||
use std::{collections::HashMap, path::Path}; |
|
||||
use wasmtime_api::{Config, Engine, HostRef, Instance, Module, Store}; |
|
||||
use wasmtime_jit::{CompilationStrategy, Features}; |
|
||||
|
|
||||
pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> Result<(), String> { |
|
||||
// Prepare runtime
|
|
||||
let mut flag_builder = settings::builder(); |
|
||||
|
|
||||
// Enable proper trap for division
|
|
||||
flag_builder |
|
||||
.enable("avoid_div_traps") |
|
||||
.map_err(|err| format!("error while enabling proper division trap: {}", err))?; |
|
||||
|
|
||||
let config = Config::new( |
|
||||
settings::Flags::new(flag_builder), |
|
||||
Features::default(), |
|
||||
false, |
|
||||
CompilationStrategy::Auto, |
|
||||
); |
|
||||
let engine = HostRef::new(Engine::new(config)); |
|
||||
let store = HostRef::new(Store::new(engine)); |
|
||||
|
|
||||
let mut module_registry = HashMap::new(); |
|
||||
let global_exports = store.borrow().global_exports().clone(); |
|
||||
let get_preopens = |workspace: Option<&Path>| -> Result<Vec<_>, String> { |
|
||||
if let Some(workspace) = workspace { |
|
||||
let preopen_dir = wasi_common::preopen_dir(workspace).map_err(|e| { |
|
||||
format!( |
|
||||
"error while preopening directory '{}': {}", |
|
||||
workspace.display(), |
|
||||
e |
|
||||
) |
|
||||
})?; |
|
||||
|
|
||||
Ok(vec![(".".to_owned(), preopen_dir)]) |
|
||||
} else { |
|
||||
Ok(vec![]) |
|
||||
} |
|
||||
}; |
|
||||
module_registry.insert( |
|
||||
"wasi_unstable".to_owned(), |
|
||||
Instance::from_handle( |
|
||||
store.clone(), |
|
||||
wasmtime_wasi::instantiate_wasi( |
|
||||
"", |
|
||||
global_exports.clone(), |
|
||||
&get_preopens(workspace)?, |
|
||||
&[bin_name.to_owned(), ".".to_owned()], |
|
||||
&[], |
|
||||
) |
|
||||
.map_err(|e| format!("error instantiating WASI: {}", e))?, |
|
||||
) |
|
||||
.map_err(|err| format!("error instantiating from handle: {}", err))?, |
|
||||
); |
|
||||
|
|
||||
let module = HostRef::new( |
|
||||
Module::new(store.clone(), &data) |
|
||||
.map_err(|err| format!("error while creating Wasm module '{}': {}", bin_name, err))?, |
|
||||
); |
|
||||
let imports = module |
|
||||
.borrow() |
|
||||
.imports() |
|
||||
.iter() |
|
||||
.map(|i| { |
|
||||
let module_name = i.module().to_string(); |
|
||||
if let Some((instance, map)) = module_registry.get(&module_name) { |
|
||||
let field_name = i.name().to_string(); |
|
||||
if let Some(export_index) = map.get(&field_name) { |
|
||||
Ok(instance.exports()[*export_index].clone()) |
|
||||
} else { |
|
||||
Err(format!( |
|
||||
"import {} was not found in module {}", |
|
||||
field_name, module_name |
|
||||
)) |
|
||||
} |
|
||||
} else { |
|
||||
Err(format!("import module {} was not found", module_name)) |
|
||||
} |
|
||||
}) |
|
||||
.collect::<Result<Vec<_>, _>>()?; |
|
||||
let _ = HostRef::new( |
|
||||
Instance::new(store.clone(), module.clone(), &imports).map_err(|err| { |
|
||||
format!( |
|
||||
"error while instantiating Wasm module '{}': {}", |
|
||||
bin_name, err |
|
||||
) |
|
||||
})?, |
|
||||
); |
|
||||
|
|
||||
Ok(()) |
|
||||
} |
|
@ -1,30 +0,0 @@ |
|||||
use std::fs; |
|
||||
use std::path::Path; |
|
||||
use tempfile::{Builder, TempDir}; |
|
||||
|
|
||||
pub fn read_wasm(path: &Path) -> Result<Vec<u8>, String> { |
|
||||
let data = fs::read(path).map_err(|err| err.to_string())?; |
|
||||
if data.starts_with(&[b'\0', b'a', b's', b'm']) { |
|
||||
Ok(data) |
|
||||
} else { |
|
||||
Err("Invalid Wasm file encountered".to_owned()) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
pub fn prepare_workspace(exe_name: &str) -> Result<TempDir, String> { |
|
||||
let prefix = format!("wasi_common_{}", exe_name); |
|
||||
Builder::new() |
|
||||
.prefix(&prefix) |
|
||||
.tempdir() |
|
||||
.map_err(|e| format!("couldn't create workspace in temp files: {}", e)) |
|
||||
} |
|
||||
|
|
||||
pub fn extract_exec_name_from_path(path: &Path) -> Result<String, String> { |
|
||||
path.file_stem() |
|
||||
.and_then(|s| s.to_str()) |
|
||||
.map(String::from) |
|
||||
.ok_or(format!( |
|
||||
"couldn't extract the file stem from path {}", |
|
||||
path.display() |
|
||||
)) |
|
||||
} |
|
@ -0,0 +1,114 @@ |
|||||
|
use anyhow::{bail, Context}; |
||||
|
use cranelift_codegen::settings::{self, Configurable}; |
||||
|
use std::fs::File; |
||||
|
use std::{collections::HashMap, path::Path}; |
||||
|
use wasmtime_api::{Config, Engine, HostRef, Instance, Module, Store}; |
||||
|
use wasmtime_jit::{CompilationStrategy, Features}; |
||||
|
|
||||
|
pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> anyhow::Result<()> { |
||||
|
// Prepare runtime
|
||||
|
let mut flag_builder = settings::builder(); |
||||
|
|
||||
|
// Enable proper trap for division
|
||||
|
flag_builder |
||||
|
.enable("avoid_div_traps") |
||||
|
.context("error while enabling proper division trap")?; |
||||
|
|
||||
|
let config = Config::new( |
||||
|
settings::Flags::new(flag_builder), |
||||
|
Features::default(), |
||||
|
false, |
||||
|
CompilationStrategy::Auto, |
||||
|
); |
||||
|
let engine = HostRef::new(Engine::new(config)); |
||||
|
let store = HostRef::new(Store::new(engine)); |
||||
|
|
||||
|
let mut module_registry = HashMap::new(); |
||||
|
let global_exports = store.borrow().global_exports().clone(); |
||||
|
let get_preopens = |workspace: Option<&Path>| -> anyhow::Result<Vec<_>> { |
||||
|
if let Some(workspace) = workspace { |
||||
|
let preopen_dir = wasi_common::preopen_dir(workspace) |
||||
|
.context(format!("error while preopening {:?}", workspace))?; |
||||
|
|
||||
|
Ok(vec![(".".to_owned(), preopen_dir)]) |
||||
|
} else { |
||||
|
Ok(vec![]) |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// Create our wasi context with pretty standard arguments/inheritance/etc.
|
||||
|
// Additionally register andy preopened directories if we have them.
|
||||
|
let mut builder = wasi_common::WasiCtxBuilder::new() |
||||
|
.arg(bin_name) |
||||
|
.arg(".") |
||||
|
.inherit_stdio(); |
||||
|
for (dir, file) in get_preopens(workspace)? { |
||||
|
builder = builder.preopened_dir(file, dir); |
||||
|
} |
||||
|
|
||||
|
// The nonstandard thing we do with `WasiCtxBuilder` is to ensure that
|
||||
|
// `stdin` is always an unreadable pipe. This is expected in the test suite
|
||||
|
// where `stdin` is never ready to be read. In some CI systems, however,
|
||||
|
// stdin is closed which causes tests to fail.
|
||||
|
let (reader, _writer) = os_pipe::pipe()?; |
||||
|
builder = builder.stdin(reader_to_file(reader)); |
||||
|
|
||||
|
module_registry.insert( |
||||
|
"wasi_unstable".to_owned(), |
||||
|
Instance::from_handle( |
||||
|
store.clone(), |
||||
|
wasmtime_wasi::instantiate_wasi_with_context( |
||||
|
"", |
||||
|
global_exports.clone(), |
||||
|
builder.build().context("failed to build wasi context")?, |
||||
|
) |
||||
|
.context("failed to instantiate wasi")?, |
||||
|
) |
||||
|
.context("failed to create instance from handle")?, |
||||
|
); |
||||
|
|
||||
|
let module = |
||||
|
HostRef::new(Module::new(store.clone(), &data).context("failed to create wasm module")?); |
||||
|
let imports = module |
||||
|
.borrow() |
||||
|
.imports() |
||||
|
.iter() |
||||
|
.map(|i| { |
||||
|
let module_name = i.module().to_string(); |
||||
|
if let Some((instance, map)) = module_registry.get(&module_name) { |
||||
|
let field_name = i.name().to_string(); |
||||
|
if let Some(export_index) = map.get(&field_name) { |
||||
|
Ok(instance.exports()[*export_index].clone()) |
||||
|
} else { |
||||
|
bail!( |
||||
|
"import {} was not found in module {}", |
||||
|
field_name, |
||||
|
module_name |
||||
|
) |
||||
|
} |
||||
|
} else { |
||||
|
bail!("import module {} was not found", module_name) |
||||
|
} |
||||
|
}) |
||||
|
.collect::<Result<Vec<_>, _>>()?; |
||||
|
let _ = HostRef::new( |
||||
|
Instance::new(store.clone(), module.clone(), &imports).context(format!( |
||||
|
"error while instantiating Wasm module '{}'", |
||||
|
bin_name, |
||||
|
))?, |
||||
|
); |
||||
|
|
||||
|
Ok(()) |
||||
|
} |
||||
|
|
||||
|
#[cfg(unix)] |
||||
|
fn reader_to_file(reader: os_pipe::PipeReader) -> File { |
||||
|
use std::os::unix::prelude::*; |
||||
|
unsafe { File::from_raw_fd(reader.into_raw_fd()) } |
||||
|
} |
||||
|
|
||||
|
#[cfg(windows)] |
||||
|
fn reader_to_file(reader: os_pipe::PipeReader) -> File { |
||||
|
use std::os::windows::prelude::*; |
||||
|
unsafe { File::from_raw_handle(reader.into_raw_handle()) } |
||||
|
} |
@ -0,0 +1,30 @@ |
|||||
|
use std::fs; |
||||
|
use std::path::Path; |
||||
|
use tempfile::{Builder, TempDir}; |
||||
|
|
||||
|
pub fn read_wasm(path: &Path) -> anyhow::Result<Vec<u8>> { |
||||
|
let data = fs::read(path)?; |
||||
|
if data.starts_with(&[b'\0', b'a', b's', b'm']) { |
||||
|
Ok(data) |
||||
|
} else { |
||||
|
anyhow::bail!("Invalid Wasm file encountered") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
pub fn prepare_workspace(exe_name: &str) -> anyhow::Result<TempDir> { |
||||
|
let prefix = format!("wasi_common_{}", exe_name); |
||||
|
let tempdir = Builder::new().prefix(&prefix).tempdir()?; |
||||
|
Ok(tempdir) |
||||
|
} |
||||
|
|
||||
|
pub fn extract_exec_name_from_path(path: &Path) -> anyhow::Result<String> { |
||||
|
path.file_stem() |
||||
|
.and_then(|s| s.to_str()) |
||||
|
.map(String::from) |
||||
|
.ok_or_else(|| { |
||||
|
anyhow::anyhow!( |
||||
|
"couldn't extract the file stem from path {}", |
||||
|
path.display() |
||||
|
) |
||||
|
}) |
||||
|
} |
Loading…
Reference in new issue