///|
fn check(condition : Bool, message : String) -> Unit {
guard condition else { abort(message) }
}
///|
fn log(message : StringView) -> Unit {
let data = @utf8.encode("\{message}\n")
ignore(@wasi.stderr.fd_write([data]) catch { _ => @wasi.Size(0) })
}
///|
fn integration_path(name : StringView) -> @path.GuestPath {
@path.GuestPath::new("target/moon-wasi-integration/\{name}") catch {
_ => abort("invalid integration-test path")
}
}
///|
fn clean(path : @path.GuestPath) -> Unit {
if @wasi_fs.exists(path) {
@wasi_fs.remove_tree(path) catch {
_ => ()
}
}
}
///|
fn run_basic_io(root : @path.GuestPath) -> Unit raise {
let nested = root.join("nested")
@wasi_fs.create_dir_all(nested)
let file = nested.join("hello.txt")
@wasi_fs.write_text(file, "hello", sync=Data)
@wasi_fs.append_bytes(file, b" world")
check(@wasi_fs.read_text(file) == "hello world", "text roundtrip failed")
let metadata = @wasi_fs.metadata(file)
check(metadata.kind == @wasi_fs.FileKind::Regular, "unexpected file kind")
check(metadata.size == 11UL, "unexpected file size")
check(metadata.links >= 1UL, "unexpected link count")
let opened = @wasi_fs.open(file, @wasi_fs.OpenOptions::read_only())
check(opened.seek(6L, @wasi_fs.SeekFrom::Start) == 6UL, "seek failed")
check(opened.tell() == 6UL, "tell failed")
check(opened.read_all() == b"world", "read after seek failed")
opened.close()
let file_as_directory = root.join("file-as-directory")
@wasi_fs.write_text(file_as_directory, "not a directory")
let mut rejected_file_directory = false
@wasi_fs.create_dir_all(file_as_directory) catch {
@root.InvalidInput(@root.Operation::CreateDirectory, _) =>
rejected_file_directory = true
_ => ()
}
check(rejected_file_directory, "create_dir_all accepted a regular file")
let many = root.join("many")
@wasi_fs.create_dir(many)
for index in 0..<180 {
@wasi_fs.write_text(many.join("entry-\{index}.txt"), "")
}
let many_entries = @wasi_fs.read_dir(many)
check(many_entries.length() == 180, "paginated read_dir lost entries")
check(many_entries[0].name == "entry-0.txt", "read_dir is not sorted")
check(many_entries[179].name == "entry-99.txt", "read_dir sort is unstable")
}
///|
fn run_tree_operations(root : @path.GuestPath) -> Unit raise {
let source = root.join("source")
let nested = source.join("sub")
@wasi_fs.create_dir_all(nested)
@wasi_fs.write_text(source.join("a.txt"), "A")
@wasi_fs.write_text(nested.join("b.txt"), "B")
let walked = @wasi_fs.walk(source)
log("walked entries: \{@debug.to_string(walked)}")
check(walked.length() == 3, "walk returned the wrong entry count")
check(
walked[1].kind == @wasi_fs.FileKind::Directory,
"walk order is unstable",
)
check(walked[2].depth == 2, "walk depth is incorrect")
let destination = root.join("copy")
log("copying tree")
@wasi_fs.copy_tree(source, destination)
log("checking copied tree")
check(@wasi_fs.read_text(destination.join("sub/b.txt")) == "B", "copy failed")
@wasi_fs.rename(destination.join("a.txt"), destination.join("renamed.txt"))
log("checking renamed file")
check(!@wasi_fs.exists(destination.join("a.txt")), "rename left the source")
check(
@wasi_fs.read_text(destination.join("renamed.txt")) == "A",
"rename failed",
)
let mut rejected_nested_copy = false
@wasi_fs.copy_tree(source, source.join("recursive-copy")) catch {
@root.InvalidInput(@root.Operation::Copy, _) => rejected_nested_copy = true
_ => ()
}
check(rejected_nested_copy, "copy_tree accepted a nested destination")
let existing = root.join("existing.txt")
@wasi_fs.write_text(existing, "keep")
let mut rejected_overwrite = false
ignore(@wasi_fs.copy_file(source.join("a.txt"), existing)) catch {
@root.WasiFailure(_, _, @wasi.Exist) => rejected_overwrite = true
_ => ()
}
check(rejected_overwrite, "copy_file overwrote an existing destination")
check(@wasi_fs.read_text(existing) == "keep", "failed copy changed content")
}
///|
fn run_atomic_write(root : @path.GuestPath) -> Unit raise {
let path = root.join("state.txt")
@wasi_fs.atomic_write_text(path, "version-one")
@wasi_fs.atomic_write_text(path, "version-two")
check(@wasi_fs.read_text(path) == "version-two", "atomic replace failed")
let mut rejected = false
@wasi_fs.atomic_write_text(path, "unexpected", replace=false) catch {
@root.InvalidInput(@root.Operation::AtomicWrite, _) => rejected = true
_ => ()
}
check(rejected, "replace=false accepted an existing path")
check(
@wasi_fs.read_text(path) == "version-two",
"failed write changed content",
)
let create_only = root.join("create-only.txt")
@wasi_fs.atomic_write_text(create_only, "created", replace=false)
check(
@wasi_fs.read_text(create_only) == "created",
"replace=false failed to create a missing file",
)
}
///|
fn run_links(root : @path.GuestPath) -> Unit raise {
let directory = root.join("links")
@wasi_fs.create_dir_all(directory)
let original = directory.join("original.txt")
let hard = directory.join("hard.txt")
@wasi_fs.write_text(original, "linked")
@wasi_fs.hard_link(original, hard)
check(@wasi_fs.read_text(hard) == "linked", "hard link content differs")
let original_metadata = @wasi_fs.metadata(original)
let hard_metadata = @wasi_fs.metadata(hard)
check(original_metadata.links >= 2UL, "hard link count was not updated")
check(
original_metadata.device == hard_metadata.device &&
original_metadata.inode == hard_metadata.inode,
"hard links did not report the same file identity",
)
let mut rejected_alias_copy = false
ignore(@wasi_fs.copy_file(original, hard, overwrite=true)) catch {
@root.InvalidInput(@root.Operation::Copy, _) => rejected_alias_copy = true
_ => ()
}
check(rejected_alias_copy, "copy_file accepted a hard-link alias")
check(@wasi_fs.read_text(original) == "linked", "alias copy truncated source")
let symbolic = directory.join("symbolic.txt")
let mut rejected_escape = false
@wasi_fs.symbolic_link("../escape", @path.GuestPath::new("escape-link")) catch {
@root.InvalidInput(@root.Operation::Link, _) => rejected_escape = true
_ => ()
}
check(rejected_escape, "symbolic link escaped the capability root")
log("creating symbolic link")
let mut symbolic_created = false
try @wasi_fs.symbolic_link("original.txt", symbolic) catch {
@root.WasiFailure(@root.Operation::Link, _, @wasi.Perm) =>
log("symbolic link skipped: host permission denied")
@root.WasiFailure(@root.Operation::Link, _, @wasi.Notsup) =>
log("symbolic link skipped: host does not support it")
@root.WasiFailure(@root.Operation::Link, _, @wasi.Noent) =>
log("symbolic link skipped: host could not resolve the relative target")
error => raise error
} noraise {
_ => symbolic_created = true
}
if symbolic_created {
log("reading symbolic link")
check(
@wasi_fs.read_link(symbolic) == "original.txt",
"read_link returned bad data",
)
}
}
///|
fn main {
try {
let root = integration_path("fs")
clean(root)
defer clean(root)
@wasi_fs.create_dir_all(root)
log("running basic I/O")
run_basic_io(root)
log("running tree operations")
run_tree_operations(root)
log("running link operations")
run_links(root)
log("running atomic write")
run_atomic_write(root)
log("filesystem integration passed")
} catch {
error => {
log("filesystem integration failed: \{@debug.to_string(error)}")
abort("WASI file-system integration test failed")
}
}
}