sausage-vfs: tests per topic under WASI, nested differential tests, contract through Mounts
This commit is contained in:
+96
-38
@@ -1,9 +1,9 @@
|
||||
//! Compiles a real WASIp2 guest and runs std, SQLite, and tempfile through the adapter.
|
||||
//! Builds one WASIp2 guest and runs each filesystem topic in an isolated instance.
|
||||
use sausage_vfs::{
|
||||
adapter::{self, Adapter, View},
|
||||
*,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use wasmtime::{
|
||||
Engine, Store,
|
||||
component::{Component, Linker, ResourceTable},
|
||||
@@ -22,26 +22,37 @@ impl WasiView for State {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn rust_guest() {
|
||||
let manifest =
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/guests/files/Cargo.toml");
|
||||
let target = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../target/vfs/guests");
|
||||
let status = std::process::Command::new("cargo")
|
||||
.args([
|
||||
"build",
|
||||
"--release",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"--target",
|
||||
"wasm32-wasip2",
|
||||
"--manifest-path",
|
||||
])
|
||||
.arg(manifest)
|
||||
.env("CARGO_TARGET_DIR", &target)
|
||||
.status()
|
||||
fn guest() -> &'static (Engine, Component) {
|
||||
static GUEST: OnceLock<(Engine, Component)> = OnceLock::new();
|
||||
GUEST.get_or_init(|| {
|
||||
let manifest =
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/guests/files/Cargo.toml");
|
||||
let target = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../target/vfs/guests");
|
||||
let status = std::process::Command::new("cargo")
|
||||
.args([
|
||||
"build",
|
||||
"--release",
|
||||
"--offline",
|
||||
"--target",
|
||||
"wasm32-wasip2",
|
||||
"--manifest-path",
|
||||
])
|
||||
.arg(manifest)
|
||||
.env("CARGO_TARGET_DIR", &target)
|
||||
.status()
|
||||
.unwrap();
|
||||
assert!(status.success(), "guest build failed");
|
||||
let engine = Engine::default();
|
||||
let component = Component::from_file(
|
||||
&engine,
|
||||
target.join("wasm32-wasip2/release/vfs-files-guest.wasm"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(status.success());
|
||||
(engine, component)
|
||||
})
|
||||
}
|
||||
async fn run(topic: &str, shared: Arc<Memory>) {
|
||||
let (engine, component) = guest();
|
||||
let world = tempfile::tempdir().unwrap();
|
||||
std::fs::create_dir(world.path().join("tmp")).unwrap();
|
||||
let mut mounts = Mounts::default();
|
||||
@@ -50,45 +61,92 @@ async fn rust_guest() {
|
||||
Arc::new(Host::new(world.path(), Process::default()).unwrap()),
|
||||
false,
|
||||
);
|
||||
mounts.mount("/run", Arc::new(Memory::default()), false);
|
||||
mounts.mount("/run", shared, false);
|
||||
mounts.mount("/self", Arc::new(Memory::default()), false);
|
||||
let mut generated = Generated::default();
|
||||
generated.insert("/value", || Ok(b"snapshot".to_vec()));
|
||||
mounts.mount("/proc", Arc::new(generated), true);
|
||||
let engine = Engine::default();
|
||||
let mut linker = Linker::new(&engine);
|
||||
let mut linker = Linker::new(engine);
|
||||
wasmtime_wasi::p2::add_to_linker_async(&mut linker).unwrap();
|
||||
adapter::add_to_linker(&mut linker, |s: &mut State| View {
|
||||
adapter: &mut s.fs,
|
||||
table: &mut s.table,
|
||||
})
|
||||
.unwrap();
|
||||
let component = Component::from_file(
|
||||
&engine,
|
||||
target.join("wasm32-wasip2/release/vfs-files-guest.wasm"),
|
||||
)
|
||||
.unwrap();
|
||||
let mut store = Store::new(
|
||||
&engine,
|
||||
engine,
|
||||
State {
|
||||
wasi: WasiCtxBuilder::new()
|
||||
.inherit_stdio()
|
||||
.env("TMPDIR", "/tmp")
|
||||
.env("VFS_TOPIC", topic)
|
||||
.build(),
|
||||
table: ResourceTable::new(),
|
||||
fs: Adapter::new(mounts),
|
||||
},
|
||||
);
|
||||
let command =
|
||||
wasmtime_wasi::p2::bindings::Command::instantiate_async(&mut store, &component, &linker)
|
||||
wasmtime_wasi::p2::bindings::Command::instantiate_async(&mut store, component, &linker)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = command.wasi_cli_run().call_run(&mut store).await;
|
||||
assert!(
|
||||
command
|
||||
.wasi_cli_run()
|
||||
.call_run(&mut store)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_ok()
|
||||
matches!(result, Ok(Ok(()))),
|
||||
"guest topic {topic} failed: {result:?}"
|
||||
);
|
||||
}
|
||||
async fn isolated(topic: &str) {
|
||||
run(topic, Arc::new(Memory::default())).await;
|
||||
}
|
||||
|
||||
/// Basic filesystem operations support relative paths and metadata.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn basic_files() {
|
||||
isolated("basic").await;
|
||||
}
|
||||
/// Recursive operations and directory entries preserve nested trees.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn nested_trees() {
|
||||
isolated("trees").await;
|
||||
}
|
||||
/// Open flags and error kinds match Rust's filesystem contract.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn options_and_errors() {
|
||||
isolated("options").await;
|
||||
}
|
||||
/// Renames replace existing files and move complete folders.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn renames() {
|
||||
isolated("rename").await;
|
||||
}
|
||||
/// Sparse writes return zeros for the gap beyond EOF.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn sparse_writes() {
|
||||
isolated("sparse").await;
|
||||
}
|
||||
/// Streams transfer several MiB with a verified checksum.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn large_streams() {
|
||||
isolated("streams").await;
|
||||
}
|
||||
/// Mount listings, root writes, and read-only errors follow the namespace.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn mount_boundaries() {
|
||||
isolated("mounts").await;
|
||||
}
|
||||
/// Separate guest stores share the same memory layer.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn shared_memory_between_guests() {
|
||||
let memory = Arc::new(Memory::default());
|
||||
run("shared_write", memory.clone()).await;
|
||||
run("shared_read", memory).await;
|
||||
}
|
||||
/// SQLite commits many rows and reads them after reopen.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn sqlite_transactions() {
|
||||
isolated("sqlite").await;
|
||||
}
|
||||
/// Named temporary files persist through the filesystem adapter.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn named_temporary_files() {
|
||||
isolated("tempfile").await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user