Skip to content

Commit

Permalink
JSON is now read with System.Text.Json instead of Newtonsoft. Updated…
Browse files Browse the repository at this point in the history
… MSTest.
  • Loading branch information
RyanLamansky committed Jul 14, 2024
1 parent cdf03b1 commit a2f47ca
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 62 deletions.
128 changes: 71 additions & 57 deletions WebAssembly.Tests/Runtime/SpecTestRunner.cs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -26,61 +25,39 @@ public static void Run<TExports>(string pathBase, string json)
Run<TExports>(pathBase, json, null);
}

private static readonly RegeneratingWeakReference<JsonSerializerOptions> serializerOptions =
new(() => new JsonSerializerOptions
{
IncludeFields = true,
});

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)))
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<TestInfo>(reader, serializerOptions)!;
}

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 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<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 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();

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -500,7 +485,7 @@ public ObjectMethods(object host)
}
}

[JsonConverter(typeof(StringEnumConverter))]
[JsonConverter(typeof(JsonStringEnumConverter<CommandType>))]
enum CommandType
{
module,
Expand All @@ -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;
Expand All @@ -540,7 +539,7 @@ class ModuleCommand : Command
public override string ToString() => $"{base.ToString()}: {filename}";
}

[JsonConverter(typeof(StringEnumConverter))]
[JsonConverter(typeof(JsonStringEnumConverter<RawValueType>))]
enum RawValueType
{
i32 = WebAssemblyValueType.Int32,
Expand All @@ -556,13 +555,19 @@ 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; }
}

class Int32Value : TypedValue
{
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public uint value;

public override object BoxedValue => (int)value;
Expand All @@ -572,6 +577,7 @@ class Int32Value : TypedValue

class Int64Value : TypedValue
{
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public ulong value;

public override object BoxedValue => (long)value;
Expand All @@ -597,13 +603,16 @@ class Float64Value : Int64Value
public override string ToString() => $"{type}: {BoxedValue}";
}

[JsonConverter(typeof(StringEnumConverter))]
[JsonConverter(typeof(JsonStringEnumConverter<TestActionType>))]
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;
Expand All @@ -629,7 +638,7 @@ class Get : TestAction
{
public override object? Call(MethodInfo methodInfo, object obj)
{
return methodInfo.Invoke(obj, Array.Empty<object>());
return methodInfo.Invoke(obj, []);
}
}

Expand All @@ -647,6 +656,11 @@ class AssertReturn : AssertCommand
public override string ToString() => $"{base.ToString()} = [{string.Join(',', (IEnumerable<TypedValue>)expected)}]";
}

class NoReturn : AssertReturn
{
public override string ToString() => $"{base.ToString()}";
}

class AssertReturnCanonicalNan : AssertCommand
{
public TypeOnly[] expected;
Expand Down Expand Up @@ -702,5 +716,5 @@ class Register : Command
public string name;
public string @as;
}
#pragma warning restore
#pragma warning restore
}
8 changes: 3 additions & 5 deletions WebAssembly.Tests/WebAssembly.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="JsonSubTypes" Version="1.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.3" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.4.3" />
<PackageReference Include="MSTest.TestFramework" Version="3.4.3" />
</ItemGroup>

<ItemGroup>
Expand Down

0 comments on commit a2f47ca

Please sign in to comment.