mirror of
https://github.com/fluencelabs/wasmer
synced 2025-06-22 13:11:32 +00:00
Add WASI serializing test
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1742,6 +1742,7 @@ dependencies = [
|
|||||||
"wasmer-clif-backend 0.6.0",
|
"wasmer-clif-backend 0.6.0",
|
||||||
"wasmer-dev-utils 0.6.0",
|
"wasmer-dev-utils 0.6.0",
|
||||||
"wasmer-llvm-backend 0.6.0",
|
"wasmer-llvm-backend 0.6.0",
|
||||||
|
"wasmer-runtime 0.6.0",
|
||||||
"wasmer-runtime-core 0.6.0",
|
"wasmer-runtime-core 0.6.0",
|
||||||
"wasmer-singlepass-backend 0.6.0",
|
"wasmer-singlepass-backend 0.6.0",
|
||||||
"wasmer-wasi 0.6.0",
|
"wasmer-wasi 0.6.0",
|
||||||
|
1
Makefile
1
Makefile
@ -76,6 +76,7 @@ wasitests-llvm: wasitests-setup
|
|||||||
cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features llvm -- --test-threads=1
|
cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features llvm -- --test-threads=1
|
||||||
|
|
||||||
wasitests-unit:
|
wasitests-unit:
|
||||||
|
cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features clif -- --test-threads=1
|
||||||
cargo test --manifest-path lib/wasi/Cargo.toml --release
|
cargo test --manifest-path lib/wasi/Cargo.toml --release
|
||||||
|
|
||||||
wasitests: wasitests-unit wasitests-singlepass wasitests-cranelift wasitests-llvm
|
wasitests: wasitests-unit wasitests-singlepass wasitests-cranelift wasitests-llvm
|
||||||
|
@ -10,6 +10,7 @@ build = "build/mod.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" }
|
wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" }
|
||||||
|
wasmer-runtime = { path = "../runtime", version = "0.6.0" }
|
||||||
wasmer-wasi = { path = "../wasi", version = "0.6.0" }
|
wasmer-wasi = { path = "../wasi", version = "0.6.0" }
|
||||||
# hack to get tests to work
|
# hack to get tests to work
|
||||||
wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.6.0", optional = true }
|
wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.6.0", optional = true }
|
||||||
|
@ -1 +1,60 @@
|
|||||||
// nothing to see here
|
#![cfg(test)]
|
||||||
|
use wasmer_runtime::{compile, Func};
|
||||||
|
use wasmer_runtime_core::vm::Ctx;
|
||||||
|
use wasmer_wasi::{state::*, *};
|
||||||
|
|
||||||
|
use std::ffi::c_void;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serializing_works() {
|
||||||
|
let args = vec![
|
||||||
|
b"program_name".into_iter().cloned().collect(),
|
||||||
|
b"arg1".into_iter().cloned().collect(),
|
||||||
|
];
|
||||||
|
let envs = vec![
|
||||||
|
b"PATH=/bin".into_iter().cloned().collect(),
|
||||||
|
b"GOROOT=$HOME/.cargo/bin".into_iter().cloned().collect(),
|
||||||
|
];
|
||||||
|
let wasm_binary = include_bytes!("../wasitests/fd_read.wasm");
|
||||||
|
let import_object = generate_import_object(
|
||||||
|
args.clone(),
|
||||||
|
envs.clone(),
|
||||||
|
vec![],
|
||||||
|
vec![(
|
||||||
|
".".to_string(),
|
||||||
|
std::path::PathBuf::from("wasitests/test_fs/hamlet"),
|
||||||
|
)],
|
||||||
|
);
|
||||||
|
let module = compile(&wasm_binary[..])
|
||||||
|
.map_err(|e| format!("Can't compile module: {:?}", e))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let state_bytes = {
|
||||||
|
let instance = module.instantiate(&import_object).unwrap();
|
||||||
|
|
||||||
|
let start: Func<(), ()> = instance.func("_start").unwrap();
|
||||||
|
start.call().unwrap();
|
||||||
|
let state = get_wasi_state(instance.context());
|
||||||
|
|
||||||
|
assert_eq!(state.args, args);
|
||||||
|
assert_eq!(state.envs, envs);
|
||||||
|
let bytes = state.freeze().unwrap();
|
||||||
|
|
||||||
|
bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut instance = module.instantiate(&import_object).unwrap();
|
||||||
|
|
||||||
|
let wasi_state = Box::new(WasiState::unfreeze(&state_bytes).unwrap());
|
||||||
|
|
||||||
|
instance.context_mut().data = Box::into_raw(wasi_state) as *mut c_void;
|
||||||
|
|
||||||
|
let second_entry: Func<(), i32> = instance.func("second_entry").unwrap();
|
||||||
|
let result = second_entry.call().unwrap();
|
||||||
|
assert_eq!(result, true as i32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::mut_from_ref)]
|
||||||
|
pub(crate) fn get_wasi_state(ctx: &Ctx) -> &mut WasiState {
|
||||||
|
unsafe { state::get_wasi_state(&mut *(ctx as *const Ctx as *mut Ctx)) }
|
||||||
|
}
|
||||||
|
14
lib/wasi-tests/tests/wasitests/fd_read.rs
Normal file
14
lib/wasi-tests/tests/wasitests/fd_read.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#[test]
|
||||||
|
fn test_fd_read() {
|
||||||
|
assert_wasi_output!(
|
||||||
|
"../../wasitests/fd_read.wasm",
|
||||||
|
"fd_read",
|
||||||
|
vec![],
|
||||||
|
vec![(
|
||||||
|
".".to_string(),
|
||||||
|
::std::path::PathBuf::from("wasitests/test_fs/hamlet")
|
||||||
|
),],
|
||||||
|
vec![],
|
||||||
|
"../../wasitests/fd_read.out"
|
||||||
|
);
|
||||||
|
}
|
@ -9,6 +9,7 @@ mod create_dir;
|
|||||||
mod envvar;
|
mod envvar;
|
||||||
mod fd_allocate;
|
mod fd_allocate;
|
||||||
mod fd_pread;
|
mod fd_pread;
|
||||||
|
mod fd_read;
|
||||||
mod fd_sync;
|
mod fd_sync;
|
||||||
mod file_metadata;
|
mod file_metadata;
|
||||||
mod fs_sandbox_test;
|
mod fs_sandbox_test;
|
||||||
|
3
lib/wasi-tests/wasitests/fd_read.out
Normal file
3
lib/wasi-tests/wasitests/fd_read.out
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
SCENE IV. The Queen's closet.
|
||||||
|
|
||||||
|
Enter QUEEN GERTRUDE and POLO
|
86
lib/wasi-tests/wasitests/fd_read.rs
Normal file
86
lib/wasi-tests/wasitests/fd_read.rs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// Args:
|
||||||
|
// mapdir: .:wasitests/test_fs/hamlet
|
||||||
|
|
||||||
|
// this program is used in the pause/resume test
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
#[cfg(target_os = "wasi")]
|
||||||
|
use std::os::wasi::prelude::AsRawFd;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[cfg(target_os = "wasi")]
|
||||||
|
#[repr(C)]
|
||||||
|
struct WasiIovec {
|
||||||
|
pub buf: u32,
|
||||||
|
pub buf_len: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "wasi")]
|
||||||
|
#[link(wasm_import_module = "wasi_unstable")]
|
||||||
|
extern "C" {
|
||||||
|
fn fd_read(fd: u32, iovs: u32, iovs_len: u32, nread: u32) -> u16;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "wasi")]
|
||||||
|
fn read(fd: u32, iovs: &[&mut [u8]]) -> u32 {
|
||||||
|
let mut nread = 0;
|
||||||
|
let mut processed_iovs = vec![];
|
||||||
|
|
||||||
|
for iov in iovs {
|
||||||
|
processed_iovs.push(WasiIovec {
|
||||||
|
buf: iov.as_ptr() as usize as u32,
|
||||||
|
buf_len: iov.len() as u32,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
fd_read(
|
||||||
|
fd,
|
||||||
|
processed_iovs.as_ptr() as usize as u32,
|
||||||
|
processed_iovs.len() as u32,
|
||||||
|
&mut nread as *mut u32 as usize as u32,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
nread
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[cfg(not(target_os = "wasi"))]
|
||||||
|
let mut base = PathBuf::from("wasitests/test_fs/hamlet");
|
||||||
|
#[cfg(target_os = "wasi")]
|
||||||
|
let mut base = PathBuf::from(".");
|
||||||
|
|
||||||
|
base.push("act3/scene4.txt");
|
||||||
|
let mut file = fs::File::open(&base).expect("Could not open file");
|
||||||
|
let mut buffer = [0u8; 64];
|
||||||
|
|
||||||
|
#[cfg(target_os = "wasi")]
|
||||||
|
{
|
||||||
|
let raw_fd = file.as_raw_fd();
|
||||||
|
assert_eq!(read(raw_fd, &[&mut buffer]), 64);
|
||||||
|
let str_val = std::str::from_utf8(&buffer[..]).unwrap().to_string();
|
||||||
|
println!("{}", &str_val);
|
||||||
|
}
|
||||||
|
// leak the file handle so that we can use it later
|
||||||
|
std::mem::forget(file);
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "wasi"))]
|
||||||
|
{
|
||||||
|
// eh, just print the output directly
|
||||||
|
print!(
|
||||||
|
"SCENE IV. The Queen's closet.
|
||||||
|
|
||||||
|
Enter QUEEN GERTRUDE and POLO"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "wasi")]
|
||||||
|
#[no_mangle]
|
||||||
|
fn second_entry() -> bool {
|
||||||
|
let raw_fd = 5;
|
||||||
|
let mut buffer = [0u8; 8];
|
||||||
|
let result = read(raw_fd, &[&mut buffer]);
|
||||||
|
|
||||||
|
&buffer == b"NIUS \n\nL"
|
||||||
|
}
|
BIN
lib/wasi-tests/wasitests/fd_read.wasm
Executable file
BIN
lib/wasi-tests/wasitests/fd_read.wasm
Executable file
Binary file not shown.
@ -54,7 +54,7 @@ pub fn generate_import_object(
|
|||||||
});
|
});
|
||||||
|
|
||||||
(
|
(
|
||||||
Box::leak(state) as *mut WasiState as *mut c_void,
|
Box::into_raw(state) as *mut c_void,
|
||||||
state_destructor as fn(*mut c_void),
|
state_destructor as fn(*mut c_void),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/// types for use in the WASI filesystem
|
/// types for use in the WASI filesystem
|
||||||
use crate::syscalls::types::*;
|
use crate::syscalls::types::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{de, Deserialize, Serialize};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::{
|
use std::{
|
||||||
@ -349,61 +349,93 @@ pub struct HostFile {
|
|||||||
flags: u16,
|
flags: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HostFileVisitor;
|
|
||||||
|
|
||||||
impl<'de> serde::de::Visitor<'de> for HostFileVisitor {
|
|
||||||
type Value = HostFile;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
formatter.write_str("a HostFile struct")
|
|
||||||
}
|
|
||||||
|
|
||||||
/*fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> {
|
|
||||||
let host_path = unimplemented!();
|
|
||||||
let flags = unimplemented!();
|
|
||||||
Ok(HostFile {
|
|
||||||
inner,
|
|
||||||
host_path,
|
|
||||||
flags,
|
|
||||||
})
|
|
||||||
}*/
|
|
||||||
|
|
||||||
fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
|
|
||||||
where
|
|
||||||
V: serde::de::SeqAccess<'de>,
|
|
||||||
{
|
|
||||||
let host_path = seq
|
|
||||||
.next_element()?
|
|
||||||
.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
|
|
||||||
let flags = seq
|
|
||||||
.next_element()?
|
|
||||||
.ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
|
|
||||||
let inner = std::fs::OpenOptions::new()
|
|
||||||
.read(flags & HostFile::READ != 0)
|
|
||||||
.write(flags & HostFile::WRITE != 0)
|
|
||||||
.append(flags & HostFile::APPEND != 0)
|
|
||||||
.open(&host_path)
|
|
||||||
.map_err(|_| serde::de::Error::custom("Could not open file on this system"))?;
|
|
||||||
Ok(HostFile {
|
|
||||||
inner,
|
|
||||||
host_path,
|
|
||||||
flags,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for HostFile {
|
impl<'de> Deserialize<'de> for HostFile {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<HostFile, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<HostFile, D::Error>
|
||||||
where
|
where
|
||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
deserializer.deserialize_i32(HostFileVisitor)
|
#[derive(Deserialize)]
|
||||||
|
#[serde(field_identifier, rename_all = "snake_case")]
|
||||||
|
enum Field {
|
||||||
|
HostPath,
|
||||||
|
Flags,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HostFileVisitor;
|
||||||
|
|
||||||
|
impl<'de> de::Visitor<'de> for HostFileVisitor {
|
||||||
|
type Value = HostFile;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("struct HostFile")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
|
||||||
|
where
|
||||||
|
V: de::SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
let host_path = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
|
||||||
|
let flags = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
|
||||||
|
let inner = std::fs::OpenOptions::new()
|
||||||
|
.read(flags & HostFile::READ != 0)
|
||||||
|
.write(flags & HostFile::WRITE != 0)
|
||||||
|
.append(flags & HostFile::APPEND != 0)
|
||||||
|
.open(&host_path)
|
||||||
|
.map_err(|_| de::Error::custom("Could not open file on this system"))?;
|
||||||
|
Ok(HostFile {
|
||||||
|
inner,
|
||||||
|
host_path,
|
||||||
|
flags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
|
||||||
|
where
|
||||||
|
V: de::MapAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut host_path = None;
|
||||||
|
let mut flags = None;
|
||||||
|
while let Some(key) = map.next_key()? {
|
||||||
|
match key {
|
||||||
|
Field::HostPath => {
|
||||||
|
if host_path.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("host_path"));
|
||||||
|
}
|
||||||
|
host_path = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
Field::Flags => {
|
||||||
|
if flags.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("flags"));
|
||||||
|
}
|
||||||
|
flags = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let host_path = host_path.ok_or_else(|| de::Error::missing_field("host_path"))?;
|
||||||
|
let flags = flags.ok_or_else(|| de::Error::missing_field("flags"))?;
|
||||||
|
let inner = std::fs::OpenOptions::new()
|
||||||
|
.read(flags & HostFile::READ != 0)
|
||||||
|
.write(flags & HostFile::WRITE != 0)
|
||||||
|
.append(flags & HostFile::APPEND != 0)
|
||||||
|
.open(&host_path)
|
||||||
|
.map_err(|_| de::Error::custom("Could not open file on this system"))?;
|
||||||
|
Ok(HostFile {
|
||||||
|
inner,
|
||||||
|
host_path,
|
||||||
|
flags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FIELDS: &'static [&'static str] = &["host_path", "flags"];
|
||||||
|
deserializer.deserialize_struct("HostFile", FIELDS, HostFileVisitor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// manually implement Deserialize here such that it uses actual data to open a file;
|
|
||||||
// I guess we need to add r/w flags and stuff here too..
|
|
||||||
|
|
||||||
impl HostFile {
|
impl HostFile {
|
||||||
const READ: u16 = 1;
|
const READ: u16 = 1;
|
||||||
const WRITE: u16 = 2;
|
const WRITE: u16 = 2;
|
||||||
|
Reference in New Issue
Block a user