Browse Source

Ensure wasi-common tests always have an unreadable stdin

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
parent
commit
2411831964
  1. 4
      wasi-common/Cargo.toml
  2. 10
      wasi-common/build.rs
  3. 6
      wasi-common/src/error.rs
  4. 92
      wasi-common/tests/runtime.rs
  5. 30
      wasi-common/tests/utils.rs
  6. 0
      wasi-common/tests/wasm_tests/main.rs
  7. 114
      wasi-common/tests/wasm_tests/runtime.rs
  8. 30
      wasi-common/tests/wasm_tests/utils.rs
  9. 54
      wasmtime-wasi/src/instantiate.rs
  10. 2
      wasmtime-wasi/src/lib.rs

4
wasi-common/Cargo.toml

@ -17,7 +17,8 @@ wasm_tests = []
[dependencies] [dependencies]
wasi-common-cbindgen = { path = "wasi-common-cbindgen", version = "0.5.0" } wasi-common-cbindgen = { path = "wasi-common-cbindgen", version = "0.5.0" }
failure = "0.1" anyhow = "1.0"
thiserror = "1.0"
libc = "0.2" libc = "0.2"
rand = "0.7" rand = "0.7"
cfg-if = "0.1.9" cfg-if = "0.1.9"
@ -45,6 +46,7 @@ cranelift-codegen = "0.49"
target-lexicon = "0.8.1" target-lexicon = "0.8.1"
pretty_env_logger = "0.3.0" pretty_env_logger = "0.3.0"
tempfile = "3.1.0" tempfile = "3.1.0"
os_pipe = "0.9"
[build-dependencies] [build-dependencies]
cfg-if = "0.1.9" cfg-if = "0.1.9"

10
wasi-common/build.rs

@ -129,17 +129,11 @@ mod wasm_tests {
} }
writeln!( writeln!(
out, out,
" fn {}() -> Result<(), String> {{", " fn {}() -> anyhow::Result<()> {{",
avoid_keywords(&stemstr.replace("-", "_")) avoid_keywords(&stemstr.replace("-", "_"))
)?; )?;
writeln!(out, " setup_log();")?; writeln!(out, " setup_log();")?;
write!(out, " let path = std::path::Path::new(\"")?; write!(out, " let path = std::path::Path::new(r#\"{}\"#);", path.display())?;
// Write out the string with escape_debug to prevent special characters such
// as backslash from being reinterpreted.
for c in path.display().to_string().chars() {
write!(out, "{}", c.escape_debug())?;
}
writeln!(out, "\");")?;
writeln!(out, " let data = utils::read_wasm(path)?;")?; writeln!(out, " let data = utils::read_wasm(path)?;")?;
writeln!( writeln!(
out, out,

6
wasi-common/src/error.rs

@ -1,13 +1,13 @@
// Due to https://github.com/rust-lang/rust/issues/64247 // Due to https://github.com/rust-lang/rust/issues/64247
#![allow(clippy::use_self)] #![allow(clippy::use_self)]
use crate::wasi; use crate::wasi;
use failure::Fail;
use std::convert::Infallible; use std::convert::Infallible;
use std::fmt; use std::fmt;
use std::num::TryFromIntError; use std::num::TryFromIntError;
use std::str; use std::str;
use thiserror::Error;
#[derive(Clone, Copy, Debug, Fail, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
#[repr(u16)] #[repr(u16)]
pub enum WasiError { pub enum WasiError {
ESUCCESS = wasi::__WASI_ESUCCESS, ESUCCESS = wasi::__WASI_ESUCCESS,
@ -103,7 +103,7 @@ impl fmt::Display for WasiError {
} }
} }
#[derive(Debug, Fail)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
Wasi(WasiError), Wasi(WasiError),
Io(std::io::Error), Io(std::io::Error),

92
wasi-common/tests/runtime.rs

@ -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(())
}

30
wasi-common/tests/utils.rs

@ -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
wasi-common/tests/wasm_tests.rs → wasi-common/tests/wasm_tests/main.rs

114
wasi-common/tests/wasm_tests/runtime.rs

@ -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()) }
}

30
wasi-common/tests/wasm_tests/utils.rs

@ -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()
)
})
}

54
wasmtime-wasi/src/instantiate.rs

@ -8,7 +8,7 @@ use cranelift_wasm::DefinedFuncIndex;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::File; use std::fs::File;
use target_lexicon::HOST; use target_lexicon::HOST;
use wasi_common::WasiCtxBuilder; use wasi_common::{WasiCtx, WasiCtxBuilder};
use wasmtime_environ::{translate_signature, Export, Module}; use wasmtime_environ::{translate_signature, Export, Module};
use wasmtime_runtime::{Imports, InstanceHandle, InstantiationError, VMFunctionBody}; use wasmtime_runtime::{Imports, InstanceHandle, InstantiationError, VMFunctionBody};
@ -19,6 +19,37 @@ pub fn instantiate_wasi(
preopened_dirs: &[(String, File)], preopened_dirs: &[(String, File)],
argv: &[String], argv: &[String],
environ: &[(String, String)], environ: &[(String, String)],
) -> Result<InstanceHandle, InstantiationError> {
let mut wasi_ctx_builder = WasiCtxBuilder::new()
.inherit_stdio()
.args(argv)
.envs(environ);
for (dir, f) in preopened_dirs {
wasi_ctx_builder = wasi_ctx_builder.preopened_dir(
f.try_clone().map_err(|err| {
InstantiationError::Resource(format!(
"couldn't clone an instance handle to pre-opened dir: {}",
err
))
})?,
dir,
);
}
let wasi_ctx = wasi_ctx_builder.build().map_err(|err| {
InstantiationError::Resource(format!("couldn't assemble WASI context object: {}", err))
})?;
instantiate_wasi_with_context(prefix, global_exports, wasi_ctx)
}
/// Return an instance implementing the "wasi" interface.
///
/// The wasi context is configured by
pub fn instantiate_wasi_with_context(
prefix: &str,
global_exports: Rc<RefCell<HashMap<String, Option<wasmtime_runtime::Export>>>>,
wasi_ctx: WasiCtx,
) -> Result<InstanceHandle, InstantiationError> { ) -> Result<InstanceHandle, InstantiationError> {
let pointer_type = types::Type::triple_pointer_type(&HOST); let pointer_type = types::Type::triple_pointer_type(&HOST);
let mut module = Module::new(); let mut module = Module::new();
@ -101,27 +132,6 @@ pub fn instantiate_wasi(
let data_initializers = Vec::new(); let data_initializers = Vec::new();
let signatures = PrimaryMap::new(); let signatures = PrimaryMap::new();
let mut wasi_ctx_builder = WasiCtxBuilder::new()
.inherit_stdio()
.args(argv)
.envs(environ);
for (dir, f) in preopened_dirs {
wasi_ctx_builder = wasi_ctx_builder.preopened_dir(
f.try_clone().map_err(|err| {
InstantiationError::Resource(format!(
"couldn't clone an instance handle to pre-opened dir: {}",
err
))
})?,
dir,
);
}
let wasi_ctx = wasi_ctx_builder.build().map_err(|err| {
InstantiationError::Resource(format!("couldn't assemble WASI context object: {}", err))
})?;
InstanceHandle::new( InstanceHandle::new(
Rc::new(module), Rc::new(module),
global_exports, global_exports,

2
wasmtime-wasi/src/lib.rs

@ -3,4 +3,4 @@ extern crate alloc;
mod instantiate; mod instantiate;
mod syscalls; mod syscalls;
pub use instantiate::instantiate_wasi; pub use instantiate::{instantiate_wasi, instantiate_wasi_with_context};

Loading…
Cancel
Save