diff --git a/crates/misc/dotnet/tests/Modules/Wasi.wasm b/crates/misc/dotnet/tests/Modules/Wasi.wasm new file mode 100644 index 0000000000..62644ebfed Binary files /dev/null and b/crates/misc/dotnet/tests/Modules/Wasi.wasm differ diff --git a/crates/misc/dotnet/tests/Modules/Wasi.wat b/crates/misc/dotnet/tests/Modules/Wasi.wat new file mode 100644 index 0000000000..da028cb4dd --- /dev/null +++ b/crates/misc/dotnet/tests/Modules/Wasi.wat @@ -0,0 +1,66 @@ +(module + (type $t0 (func (param i32 i32) (result i32))) + (type $t1 (func (param i32 i32 i32 i32) (result i32))) + (type $t2 (func (param i32) (result i32))) + (type $t3 (func (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "environ_sizes_get" (func $wasi_snapshot_preview1.environ_sizes_get (type $t0))) + (import "wasi_snapshot_preview1" "environ_get" (func $wasi_snapshot_preview1.environ_get (type $t0))) + (import "wasi_snapshot_preview1" "args_sizes_get" (func $wasi_snapshot_preview1.args_sizes_get (type $t0))) + (import "wasi_snapshot_preview1" "args_get" (func $wasi_snapshot_preview1.args_get (type $t0))) + (import "wasi_snapshot_preview1" "fd_write" (func $wasi_snapshot_preview1.fd_write (type $t1))) + (import "wasi_snapshot_preview1" "fd_read" (func $wasi_snapshot_preview1.fd_read (type $t1))) + (import "wasi_snapshot_preview1" "fd_close" (func $wasi_snapshot_preview1.fd_close (type $t2))) + (import "wasi_snapshot_preview1" "path_open" (func $wasi_snapshot_preview1.path_open (type $t3))) + (memory $memory 1) + (export "memory" (memory 0)) + (func $call_environ_sizes_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32) + local.get $p0 + local.get $p1 + call $wasi_snapshot_preview1.environ_sizes_get) + (func $call_environ_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32) + local.get $p0 + local.get $p1 + call $wasi_snapshot_preview1.environ_get) + (func $call_args_sizes_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32) + local.get $p0 + local.get $p1 + call $wasi_snapshot_preview1.args_sizes_get) + (func $call_args_get (type $t0) (param $p0 i32) (param $p1 i32) (result i32) + local.get $p0 + local.get $p1 + call $wasi_snapshot_preview1.args_get) + (func $call_fd_write (type $t1) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (result i32) + local.get $p0 + local.get $p1 + local.get $p2 + local.get $p3 + call $wasi_snapshot_preview1.fd_write) + (func $call_fd_read (type $t1) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (result i32) + local.get $p0 + local.get $p1 + local.get $p2 + local.get $p3 + call $wasi_snapshot_preview1.fd_read) + (func $call_fd_close (type $t2) (param $p0 i32) (result i32) + local.get $p0 + call $wasi_snapshot_preview1.fd_close) + (func $call_path_open (type $t3) (param $p0 i32) (param $p1 i32) (param $p2 i32) (param $p3 i32) (param $p4 i32) (param $p5 i64) (param $p6 i64) (param $p7 i32) (param $p8 i32) (result i32) + local.get $p0 + local.get $p1 + local.get $p2 + local.get $p3 + local.get $p4 + local.get $p5 + local.get $p6 + local.get $p7 + local.get $p8 + call $wasi_snapshot_preview1.path_open) + (export "call_environ_sizes_get" (func $call_environ_sizes_get)) + (export "call_environ_get" (func $call_environ_get)) + (export "call_args_sizes_get" (func $call_args_sizes_get)) + (export "call_args_get" (func $call_args_get)) + (export "call_fd_write" (func $call_fd_write)) + (export "call_fd_read" (func $call_fd_read)) + (export "call_fd_close" (func $call_fd_close)) + (export "call_path_open" (func $call_path_open)) +) diff --git a/crates/misc/dotnet/tests/TempFile.cs b/crates/misc/dotnet/tests/TempFile.cs new file mode 100644 index 0000000000..6456b37b72 --- /dev/null +++ b/crates/misc/dotnet/tests/TempFile.cs @@ -0,0 +1,24 @@ +using System; +using System.IO; + +namespace Wasmtime.Tests +{ + internal class TempFile : IDisposable + { + public TempFile() + { + Path = System.IO.Path.GetTempFileName(); + } + + public void Dispose() + { + if (Path != null) + { + File.Delete(Path); + Path = null; + } + } + + public string Path { get; private set; } + } +} \ No newline at end of file diff --git a/crates/misc/dotnet/tests/WasiTests.cs b/crates/misc/dotnet/tests/WasiTests.cs new file mode 100644 index 0000000000..48736f5ccb --- /dev/null +++ b/crates/misc/dotnet/tests/WasiTests.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.IO; +using FluentAssertions; +using Xunit; + +namespace Wasmtime.Tests +{ + public class WasiFixture : ModuleFixture + { + protected override string ModuleFileName => "Wasi.wasm"; + } + + public class WasiTests : IClassFixture + { + public WasiTests(WasiFixture fixture) + { + Fixture = fixture; + } + + private WasiFixture Fixture { get; set; } + + [Fact] + public void ItHasNoEnvironmentByDefault() + { + using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store)); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + + Assert.Equal(0, inst.call_environ_sizes_get(0, 4)); + Assert.Equal(0, memory.ReadInt32(0)); + Assert.Equal(0, memory.ReadInt32(4)); + } + + [Fact] + public void ItHasSpecifiedEnvironment() + { + var env = new Dictionary() { + {"FOO", "BAR"}, + {"WASM", "IS"}, + {"VERY", "COOL"}, + }; + + var wasi = new WasiBuilder() + .WithEnvironmentVariables(env.Select(kvp => (kvp.Key, kvp.Value))) + .Build(Fixture.Module.Store); + + using var instance = Fixture.Module.Instantiate(wasi); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + + Assert.Equal(0, inst.call_environ_sizes_get(0, 4)); + Assert.Equal(env.Count, memory.ReadInt32(0)); + Assert.Equal(env.Sum(kvp => kvp.Key.Length + kvp.Value.Length + 2), memory.ReadInt32(4)); + Assert.Equal(0, inst.call_environ_get(0, 4 * env.Count)); + + for (int i = 0; i < env.Count; ++i) + { + var kvp = memory.ReadNullTerminatedString(memory.ReadInt32(i * 4)).Split("="); + Assert.Equal(env[kvp[0]], kvp[1]); + } + } + + [Fact] + public void ItInheritsEnvironment() + { + var wasi = new WasiBuilder() + .WithInheritedEnvironment() + .Build(Fixture.Module.Store); + + using var instance = Fixture.Module.Instantiate(wasi); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + + Assert.Equal(0, inst.call_environ_sizes_get(0, 4)); + Assert.Equal(Environment.GetEnvironmentVariables().Keys.Count, memory.ReadInt32(0)); + } + + [Fact] + public void ItHasNoArgumentsByDefault() + { + using var instance = Fixture.Module.Instantiate(new Wasi(Fixture.Module.Store)); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + + Assert.Equal(0, inst.call_args_sizes_get(0, 4)); + Assert.Equal(0, memory.ReadInt32(0)); + Assert.Equal(0, memory.ReadInt32(4)); + } + + [Fact] + public void ItHasSpecifiedArguments() + { + var args = new List() { + "WASM", + "IS", + "VERY", + "COOL" + }; + + var wasi = new WasiBuilder() + .WithArgs(args) + .Build(Fixture.Module.Store); + + using var instance = Fixture.Module.Instantiate(wasi); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + + Assert.Equal(0, inst.call_args_sizes_get(0, 4)); + Assert.Equal(args.Count, memory.ReadInt32(0)); + Assert.Equal(args.Sum(a => a.Length + 1), memory.ReadInt32(4)); + Assert.Equal(0, inst.call_args_get(0, 4 * args.Count)); + + for (int i = 0; i < args.Count; ++i) + { + var arg = memory.ReadNullTerminatedString(memory.ReadInt32(i * 4)); + Assert.Equal(args[i], arg); + } + } + + [Fact] + public void ItInheritsArguments() + { + var wasi = new WasiBuilder() + .WithInheritedArgs() + .Build(Fixture.Module.Store); + + using var instance = Fixture.Module.Instantiate(wasi); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + + Assert.Equal(0, inst.call_args_sizes_get(0, 4)); + Assert.Equal(Environment.GetCommandLineArgs().Length, memory.ReadInt32(0)); + } + + [Fact] + public void ItSetsStdIn() + { + const string MESSAGE = "WASM IS VERY COOL"; + + using (var file = new TempFile()) + { + File.WriteAllText(file.Path, MESSAGE); + + var wasi = new WasiBuilder() + .WithStandardInput(file.Path) + .Build(Fixture.Module.Store); + + using var instance = Fixture.Module.Instantiate(wasi); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + memory.WriteInt32(0, 8); + memory.WriteInt32(4, MESSAGE.Length); + + Assert.Equal(0, inst.call_fd_read(0, 0, 1, 32)); + Assert.Equal(MESSAGE.Length, memory.ReadInt32(32)); + Assert.Equal(MESSAGE, memory.ReadString(8, MESSAGE.Length)); + } + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void ItSetsStdOutAndStdErr(int fd) + { + const string MESSAGE = "WASM IS VERY COOL"; + + using (var file = new TempFile()) + { + var builder = new WasiBuilder(); + if (fd == 1) + { + builder.WithStandardOutput(file.Path); + } + else if (fd == 2) + { + builder.WithStandardError(file.Path); + } + + var wasi = builder.Build(Fixture.Module.Store); + + using var instance = Fixture.Module.Instantiate(wasi); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + memory.WriteInt32(0, 8); + memory.WriteInt32(4, MESSAGE.Length); + memory.WriteString(8, MESSAGE); + + Assert.Equal(0, inst.call_fd_write(fd, 0, 1, 32)); + Assert.Equal(MESSAGE.Length, memory.ReadInt32(32)); + Assert.Equal(MESSAGE, File.ReadAllText(file.Path)); + } + } + + [Fact] + public void ItSetsPreopenDirectories() + { + const string MESSAGE = "WASM IS VERY COOL"; + + using (var file = new TempFile()) + { + var wasi = new WasiBuilder() + .WithPreopenedDirectory(Path.GetDirectoryName(file.Path), "/foo") + .Build(Fixture.Module.Store); + + using var instance = Fixture.Module.Instantiate(wasi); + dynamic inst = instance; + + var memory = instance.Externs.Memories[0]; + var fileName = Path.GetFileName(file.Path); + memory.WriteString(0, fileName); + + Assert.Equal(0, inst.call_path_open( + 3, + 0, + 0, + fileName.Length, + 0, + 0x40 /* RIGHTS_FD_WRITE */, + 0, + 0, + 64 + ) + ); + + var fileFd = (int) memory.ReadInt32(64); + Assert.True(fileFd > 3); + + memory.WriteInt32(0, 8); + memory.WriteInt32(4, MESSAGE.Length); + memory.WriteString(8, MESSAGE); + + Assert.Equal(0, inst.call_fd_write(fileFd, 0, 1, 64)); + Assert.Equal(MESSAGE.Length, memory.ReadInt32(64)); + Assert.Equal(MESSAGE, File.ReadAllText(file.Path)); + + Assert.Equal(0, inst.call_fd_close(fileFd)); + } + } + } +}