public void Execute_Sample_MDN_Table2() { var tbl = new FunctionTable(2); Assert.AreEqual(2u, tbl.Length); Assert.IsNull(tbl[0]); Assert.IsNull(tbl[1]); using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("WebAssembly.Samples.table2.wasm")) { var imports = new ImportDictionary { { "js", "tbl", tbl }, }; Assert.IsNotNull(stream); Compile.FromBinary <dynamic>(stream !)(imports); } Assert.AreEqual(2u, tbl.Length); var f1 = tbl[0]; Assert.IsNotNull(f1); Assert.IsInstanceOfType(f1, typeof(Func <int>)); Assert.AreEqual(42, ((Func <int>)f1 !).Invoke()); var f2 = tbl[1]; Assert.IsNotNull(f2); Assert.IsInstanceOfType(f1, typeof(Func <int>)); Assert.AreEqual(83, ((Func <int>)f2 !).Invoke()); }
public static void Run <TExports>(string pathBase, string json, Func <uint, bool> skip) where TExports : class { TestInfo testInfo; using (var reader = new StreamReader(Path.Combine(pathBase, json))) { var settings = new JsonSerializerSettings(); settings.Converters.Add(JsonSubtypesConverterBuilder .Of(typeof(Command), "type") .RegisterSubtype(typeof(ModuleCommand), CommandType.module) .RegisterSubtype(typeof(AssertReturn), CommandType.assert_return) .RegisterSubtype(typeof(AssertReturnCanonicalNan), CommandType.assert_return_canonical_nan) .RegisterSubtype(typeof(AssertReturnArithmeticNan), CommandType.assert_return_arithmetic_nan) .RegisterSubtype(typeof(AssertInvalid), CommandType.assert_invalid) .RegisterSubtype(typeof(AssertTrap), CommandType.assert_trap) .RegisterSubtype(typeof(AssertMalformed), CommandType.assert_malformed) .RegisterSubtype(typeof(AssertExhaustion), CommandType.assert_exhaustion) .RegisterSubtype(typeof(AssertUnlinkable), CommandType.assert_unlinkable) .RegisterSubtype(typeof(Register), CommandType.register) .RegisterSubtype(typeof(AssertReturn), CommandType.action) .RegisterSubtype(typeof(AssertUninstantiable), CommandType.assert_uninstantiable) .Build()); settings.Converters.Add(JsonSubtypesConverterBuilder .Of(typeof(TestAction), "type") .RegisterSubtype(typeof(Invoke), TestActionType.invoke) .RegisterSubtype(typeof(Get), TestActionType.get) .Build()); settings.Converters.Add(JsonSubtypesConverterBuilder .Of(typeof(TypedValue), "type") .RegisterSubtype(typeof(Int32Value), RawValueType.i32) .RegisterSubtype(typeof(Int64Value), RawValueType.i64) .RegisterSubtype(typeof(Float32Value), RawValueType.f32) .RegisterSubtype(typeof(Float64Value), RawValueType.f64) .Build()); testInfo = (TestInfo)JsonSerializer.Create(settings).Deserialize(reader, typeof(TestInfo)); } ObjectMethods methodsByName = null; var moduleMethodsByName = new Dictionary <string, ObjectMethods>(); // From https://github.com/WebAssembly/spec/blob/master/interpreter/host/spectest.ml var imports = new ImportDictionary { { "spectest", "print_i32", new FunctionImport((Action <int>)(i => { })) }, { "spectest", "print_i32_f32", new FunctionImport((Action <int, float>)((i, f) => { })) }, { "spectest", "print_f64_f64", new FunctionImport((Action <double, double>)((d1, d2) => { })) }, { "spectest", "print_f32", new FunctionImport((Action <float>)(i => { })) }, { "spectest", "print_f64", new FunctionImport((Action <double>)(i => { })) }, { "spectest", "global_i32", new GlobalImport(() => 666) }, { "spectest", "global_i64", new GlobalImport(() => 666L) }, { "spectest", "global_f32", new GlobalImport(() => 666.0F) }, { "spectest", "global_f64", new GlobalImport(() => 666.0) }, // { "spectest", "table", new TableImport() }, // Table.alloc (TableType ({min = 10l; max = Some 20l}, FuncRefType)) { "spectest", "memory", new MemoryImport(() => new UnmanagedMemory(1, 2)) }, // Memory.alloc (MemoryType {min = 1l; max = Some 2l}) }; var registrationCandidates = new ImportDictionary(); Action trapExpected; object result, obj; MethodInfo methodInfo; TExports exports = null; foreach (var command in testInfo.commands) { if (skip != null && skip(command.line)) { continue; } void GetMethod(TestAction action, out MethodInfo info, out object host) { var methodSource = action.module == null ? methodsByName : moduleMethodsByName[action.module]; Assert.IsNotNull(methodSource, $"{command.line} has no method source."); Assert.IsTrue(methodSource.TryGetValue(NameCleaner.CleanName(action.field), out info), $"{command.line} failed to look up method {action.field}"); host = methodSource.Host; } try { switch (command) { case ModuleCommand module: var path = Path.Combine(pathBase, module.filename); var parsed = Module.ReadFromBinary(path); // Ensure the module parser can read it. Assert.IsNotNull(parsed); methodsByName = new ObjectMethods(exports = Compile.FromBinary <TExports>(path)(imports).Exports); if (module.name != null) { moduleMethodsByName[module.name] = methodsByName; } continue; case AssertReturn assert: GetMethod(assert.action, out methodInfo, out obj); try { result = assert.action.Call(methodInfo, obj); } catch (TargetInvocationException x) { throw new AssertFailedException($"{command.line}: {x.InnerException.Message}", x.InnerException); } catch (Exception x) { throw new AssertFailedException($"{command.line}: {x.Message}", x); } if (assert.expected?.Length > 0) { if (assert.expected[0].BoxedValue.Equals(result)) { continue; } switch (assert.expected[0].type) { case RawValueType.f32: { var expected = ((Float32Value)assert.expected[0]).ActualValue; Assert.AreEqual(expected, (float)result, Math.Abs(expected * 0.000001f), $"{command.line}: f32 compare"); } continue; case RawValueType.f64: { var expected = ((Float64Value)assert.expected[0]).ActualValue; Assert.AreEqual(expected, (double)result, Math.Abs(expected * 0.000001), $"{command.line}: f64 compare"); } continue; } throw new AssertFailedException($"{command.line}: Not equal: {assert.expected[0].BoxedValue} and {result}"); } continue; case AssertReturnCanonicalNan assert: GetMethod(assert.action, out methodInfo, out obj); result = assert.action.Call(methodInfo, obj); switch (assert.expected[0].type) { case RawValueType.f32: Assert.IsTrue(float.IsNaN((float)result)); continue; case RawValueType.f64: Assert.IsTrue(double.IsNaN((double)result)); continue; default: throw new AssertFailedException($"{assert.expected[0].type} doesn't support NaN checks."); } case AssertReturnArithmeticNan assert: GetMethod(assert.action, out methodInfo, out obj); result = assert.action.Call(methodInfo, obj); switch (assert.expected[0].type) { case RawValueType.f32: Assert.IsTrue(float.IsNaN((float)result)); continue; case RawValueType.f64: Assert.IsTrue(double.IsNaN((double)result)); continue; default: throw new AssertFailedException($"{assert.expected[0].type} doesn't support NaN checks."); } case AssertInvalid assert: trapExpected = () => { try { Compile.FromBinary <TExports>(Path.Combine(pathBase, assert.filename)); } catch (TargetInvocationException x) { ExceptionDispatchInfo.Capture(x.InnerException).Throw(); } }; switch (assert.text) { case "type mismatch": try { trapExpected(); } catch (StackTypeInvalidException) { continue; } catch (StackTooSmallException) { continue; } catch (ModuleLoadException) { continue; } catch (StackSizeIncorrectException) { continue; } catch (Exception x) { throw new AssertFailedException($"{command.line} threw an unexpected exception of type {x.GetType().Name}."); } throw new AssertFailedException($"{command.line} should have thrown an exception but did not."); case "alignment must not be larger than natural": Assert.ThrowsException <CompilerException>(trapExpected, $"{command.line}"); continue; case "unknown memory 0": case "constant expression required": case "duplicate export name": case "unknown table": case "unknown local": case "multiple memories": case "size minimum must not be greater than maximum": case "memory size must be at most 65536 pages (4GiB)": case "unknown label": case "invalid result arity": case "unknown type": Assert.ThrowsException <ModuleLoadException>(trapExpected, $"{command.line}"); continue; case "unknown global": case "unknown memory": case "unknown function": case "unknown table 0": try { trapExpected(); } catch (CompilerException) { continue; } catch (ModuleLoadException) { continue; } catch (Exception x) { throw new AssertFailedException($"{command.line} threw an unexpected exception of type {x.GetType().Name}."); } throw new AssertFailedException($"{command.line} should have thrown an exception but did not."); default: throw new AssertFailedException($"{command.line}: {assert.text} doesn't have a test procedure set up."); } case AssertTrap assert: trapExpected = () => { GetMethod(assert.action, out methodInfo, out obj); try { assert.action.Call(methodInfo, obj); } catch (TargetInvocationException x) { ExceptionDispatchInfo.Capture(x.InnerException).Throw(); } }; switch (assert.text) { case "integer divide by zero": Assert.ThrowsException <DivideByZeroException>(trapExpected, $"{command.line}"); continue; case "integer overflow": Assert.ThrowsException <OverflowException>(trapExpected, $"{command.line}"); continue; case "out of bounds memory access": try { trapExpected(); } catch (MemoryAccessOutOfRangeException) { continue; } catch (OverflowException) { continue; } catch (Exception x) { throw new AssertFailedException($"{command.line} threw an unexpected exception of type {x.GetType().Name}."); } throw new AssertFailedException($"{command.line} should have thrown an exception but did not."); case "invalid conversion to integer": Assert.ThrowsException <OverflowException>(trapExpected, $"{command.line}"); continue; case "undefined element": case "uninitialized element 7": try { trapExpected(); throw new AssertFailedException($"{command.line}: Expected ModuleLoadException or IndexOutOfRangeException, but no exception was thrown."); } catch (ModuleLoadException) { } catch (IndexOutOfRangeException) { } catch (Exception x) { throw new AssertFailedException($"{command.line}: Expected ModuleLoadException or IndexOutOfRangeException, but received {x.GetType().Name}."); } continue; case "indirect call type mismatch": Assert.ThrowsException <InvalidCastException>(trapExpected, $"{command.line}"); continue; default: throw new AssertFailedException($"{command.line}: {assert.text} doesn't have a test procedure set up."); } case AssertMalformed assert: continue; // Not writing a WAT parser. case AssertExhaustion assert: trapExpected = () => { GetMethod(assert.action, out methodInfo, out obj); try { assert.action.Call(methodInfo, obj); } catch (TargetInvocationException x) { ExceptionDispatchInfo.Capture(x.InnerException).Throw(); } }; switch (assert.text) { case "call stack exhausted": Assert.ThrowsException <StackOverflowException>(trapExpected, $"{command.line}"); continue; default: throw new AssertFailedException($"{command.line}: {assert.text} doesn't have a test procedure set up."); } case AssertUnlinkable assert: trapExpected = () => { try { Compile.FromBinary <TExports>(Path.Combine(pathBase, assert.filename))(imports); } catch (TargetInvocationException x) { ExceptionDispatchInfo.Capture(x.InnerException).Throw(); } }; switch (assert.text) { case "data segment does not fit": case "elements segment does not fit": Assert.ThrowsException <ModuleLoadException>(trapExpected, $"{command.line}"); continue; case "unknown import": case "incompatible import type": Assert.ThrowsException <ImportException>(trapExpected, $"{command.line}"); continue; default: throw new AssertFailedException($"{command.line}: {assert.text} doesn't have a test procedure set up."); } case Register register: Assert.IsNotNull( moduleMethodsByName[register.@as] = register.name != null ? moduleMethodsByName[register.name] : methodsByName, $"{command.line} tried to register null as a module method source."); imports.AddFromExports(register.@as, exports); continue; case AssertUninstantiable assert: trapExpected = () => { try { Compile.FromBinary <TExports>(Path.Combine(pathBase, assert.filename)); } catch (TargetInvocationException x) { ExceptionDispatchInfo.Capture(x.InnerException).Throw(); } }; switch (assert.text) { case "unreachable": Assert.ThrowsException <ModuleLoadException>(trapExpected, $"{command.line}"); continue; default: throw new AssertFailedException($"{command.line}: {assert.text} doesn't have a test procedure set up."); } default: throw new AssertFailedException($"{command.line}: {command} doesn't have a test procedure set up."); } } catch (Exception x) when(!System.Diagnostics.Debugger.IsAttached && !(x is AssertFailedException)) { throw new AssertFailedException($"{command.line}: {x}", x); } } if (skip != null) { Assert.Inconclusive("Some scenarios were skipped."); } }