diff --git a/WebAssembly.Tests/Runtime/SpecTestRunner.cs b/WebAssembly.Tests/Runtime/SpecTestRunner.cs index b486543..93870b4 100644 --- a/WebAssembly.Tests/Runtime/SpecTestRunner.cs +++ b/WebAssembly.Tests/Runtime/SpecTestRunner.cs @@ -1,13 +1,12 @@ -using JsonSubTypes; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.ExceptionServices; +using System.Text.Json; +using System.Text.Json.Serialization; // Effect of this is trusting that the source JSONs are valid. #pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. @@ -26,41 +25,19 @@ public static void Run(string pathBase, string json) Run(pathBase, json, null); } + private static readonly RegeneratingWeakReference serializerOptions = + new(() => new JsonSerializerOptions + { + IncludeFields = true, + }); + public static void Run(string pathBase, string json, Func? skip) where TExports : class { TestInfo testInfo; - using (var reader = new StreamReader(Path.Combine(pathBase, json))) + using (var reader = File.OpenRead(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))!; + testInfo = JsonSerializer.Deserialize(reader, serializerOptions)!; } ObjectMethods? methodsByName = null; @@ -68,19 +45,19 @@ public static void Run(string pathBase, string json, Func? // From https://github.com/WebAssembly/spec/blob/master/interpreter/host/spectest.ml var imports = new ImportDictionary - { - { "spectest", "print_i32", new FunctionImport((Action)(i => { })) }, - { "spectest", "print_i32_f32", new FunctionImport((Action)((i, f) => { })) }, - { "spectest", "print_f64_f64", new FunctionImport((Action)((d1, d2) => { })) }, - { "spectest", "print_f32", new FunctionImport((Action)(i => { })) }, - { "spectest", "print_f64", new FunctionImport((Action)(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 FunctionTable(10, 20) }, // 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}) - }; + { + { "spectest", "print_i32", new FunctionImport((Action)(i => { })) }, + { "spectest", "print_i32_f32", new FunctionImport((Action)((i, f) => { })) }, + { "spectest", "print_f64_f64", new FunctionImport((Action)((d1, d2) => { })) }, + { "spectest", "print_f32", new FunctionImport((Action)(i => { })) }, + { "spectest", "print_f64", new FunctionImport((Action)(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 FunctionTable(10, 20) }, // 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(); @@ -130,26 +107,34 @@ void GetMethod(TestAction action, out MethodInfo info, out object host) } if (assert.expected?.Length > 0) { - if (assert.expected[0].BoxedValue.Equals(result)) + var rawExpected = assert.expected[0]; + if (rawExpected.BoxedValue.Equals(result)) continue; - switch (assert.expected[0].type) + switch (rawExpected.type) { + default: + throw new Exception($"{command.line}: Failed to parse expected value type."); + + case RawValueType.i32: + case RawValueType.i64: + break; + case RawValueType.f32: { - var expected = ((Float32Value)assert.expected[0]).ActualValue; + var expected = ((Float32Value)rawExpected).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; + var expected = ((Float64Value)rawExpected).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}"); + throw new AssertFailedException($"{command.line}: Not equal {rawExpected.type}: {rawExpected.BoxedValue} and {result}"); } continue; case AssertReturnCanonicalNan assert: @@ -500,7 +485,7 @@ public ObjectMethods(object host) } } - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] enum CommandType { module, @@ -524,6 +509,20 @@ class TestInfo public Command[] commands; } + + [JsonPolymorphic(TypeDiscriminatorPropertyName = nameof(type))] + [JsonDerivedType(typeof(ModuleCommand), typeDiscriminator: nameof(CommandType.module))] + [JsonDerivedType(typeof(AssertReturn), typeDiscriminator: nameof(CommandType.assert_return))] + [JsonDerivedType(typeof(AssertReturnCanonicalNan), typeDiscriminator: nameof(CommandType.assert_return_canonical_nan))] + [JsonDerivedType(typeof(AssertReturnArithmeticNan), typeDiscriminator: nameof(CommandType.assert_return_arithmetic_nan))] + [JsonDerivedType(typeof(AssertInvalid), typeDiscriminator: nameof(CommandType.assert_invalid))] + [JsonDerivedType(typeof(AssertTrap), typeDiscriminator: nameof(CommandType.assert_trap))] + [JsonDerivedType(typeof(AssertMalformed), typeDiscriminator: nameof(CommandType.assert_malformed))] + [JsonDerivedType(typeof(AssertExhaustion), typeDiscriminator: nameof(CommandType.assert_exhaustion))] + [JsonDerivedType(typeof(AssertUnlinkable), typeDiscriminator: nameof(CommandType.assert_unlinkable))] + [JsonDerivedType(typeof(Register), typeDiscriminator: nameof(CommandType.register))] + [JsonDerivedType(typeof(NoReturn), typeDiscriminator: nameof(CommandType.action))] + [JsonDerivedType(typeof(AssertUninstantiable), typeDiscriminator: nameof(CommandType.assert_uninstantiable))] abstract class Command { public CommandType type; @@ -540,7 +539,7 @@ class ModuleCommand : Command public override string ToString() => $"{base.ToString()}: {filename}"; } - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] enum RawValueType { i32 = WebAssemblyValueType.Int32, @@ -556,6 +555,11 @@ class TypeOnly public override string ToString() => type.ToString(); } + [JsonPolymorphic(TypeDiscriminatorPropertyName = nameof(type))] + [JsonDerivedType(typeof(Int32Value), typeDiscriminator: nameof(RawValueType.i32))] + [JsonDerivedType(typeof(Int64Value), typeDiscriminator: nameof(RawValueType.i64))] + [JsonDerivedType(typeof(Float32Value), typeDiscriminator: nameof(RawValueType.f32))] + [JsonDerivedType(typeof(Float64Value), typeDiscriminator: nameof(RawValueType.f64))] abstract class TypedValue : TypeOnly { public abstract object BoxedValue { get; } @@ -563,6 +567,7 @@ abstract class TypedValue : TypeOnly class Int32Value : TypedValue { + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] public uint value; public override object BoxedValue => (int)value; @@ -572,6 +577,7 @@ class Int32Value : TypedValue class Int64Value : TypedValue { + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] public ulong value; public override object BoxedValue => (long)value; @@ -597,13 +603,16 @@ class Float64Value : Int64Value public override string ToString() => $"{type}: {BoxedValue}"; } - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] enum TestActionType { invoke, get, } + [JsonPolymorphic(TypeDiscriminatorPropertyName = nameof(type))] + [JsonDerivedType(typeof(Invoke), typeDiscriminator: nameof(TestActionType.invoke))] + [JsonDerivedType(typeof(Get), typeDiscriminator: nameof(TestActionType.get))] abstract class TestAction { public TestActionType type; @@ -629,7 +638,7 @@ class Get : TestAction { public override object? Call(MethodInfo methodInfo, object obj) { - return methodInfo.Invoke(obj, Array.Empty()); + return methodInfo.Invoke(obj, []); } } @@ -647,6 +656,11 @@ class AssertReturn : AssertCommand public override string ToString() => $"{base.ToString()} = [{string.Join(',', (IEnumerable)expected)}]"; } + class NoReturn : AssertReturn + { + public override string ToString() => $"{base.ToString()}"; + } + class AssertReturnCanonicalNan : AssertCommand { public TypeOnly[] expected; @@ -702,5 +716,5 @@ class Register : Command public string name; public string @as; } -#pragma warning restore +#pragma warning restore } diff --git a/WebAssembly.Tests/WebAssembly.Tests.csproj b/WebAssembly.Tests/WebAssembly.Tests.csproj index 18af70e..553c3fe 100644 --- a/WebAssembly.Tests/WebAssembly.Tests.csproj +++ b/WebAssembly.Tests/WebAssembly.Tests.csproj @@ -15,11 +15,9 @@ - - - - - + + +