Skip to content

Commit

Permalink
Rewrite System.Text.Json stream tests to be async friendly and enable…
Browse files Browse the repository at this point in the history
… on WASM (#38663)

The tests dealing are using a (De)SerializationWrapper so the same code can be used both for String and Stream types.
It does that by wrapping the async Stream serialization calls in `Task.Run().GetAwaiter().GetResult()` to turn them into sync calls.
However that doesn't work on WebAssembly since we can't wait on tasks as there's only a single thread.

To fix this inverse the wrapper so the synchronous String calls are turned into async and use normal awaits for the Stream calls.

This allows the test suite to pass on WebAssembly: `Tests run: 8349, Errors: 0, Failures: 0, Skipped: 11. Time: 475.528706s`
  • Loading branch information
akoeplinger authored Jul 10, 2020
1 parent a0987db commit 95e3dcc
Show file tree
Hide file tree
Showing 17 changed files with 596 additions and 594 deletions.
89 changes: 43 additions & 46 deletions src/libraries/System.Text.Json/tests/JsonDocumentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,77 +231,75 @@ private static string ReadJson400KB(JsonElement obj)
// If the internals change such that one of these is exercising substantially different
// code, then it should switch to the full variation set.
[MemberData(nameof(TestCases))]
public static void ParseJson_MemoryBytes(bool compactData, TestCaseType type, string jsonString)
public static async Task ParseJson_MemoryBytes(bool compactData, TestCaseType type, string jsonString)
{
ParseJson(
await ParseJsonAsync(
compactData,
type,
jsonString,
null,
bytes => JsonDocument.Parse(bytes.AsMemory()));
bytes => Task.FromResult(JsonDocument.Parse(bytes.AsMemory())));
}

[Theory]
[MemberData(nameof(ReducedTestCases))]
public static void ParseJson_String(bool compactData, TestCaseType type, string jsonString)
public static async Task ParseJson_String(bool compactData, TestCaseType type, string jsonString)
{
ParseJson(
await ParseJsonAsync(
compactData,
type,
jsonString,
str => JsonDocument.Parse(str),
str => Task.FromResult(JsonDocument.Parse(str)),
null);
}

[Theory]
[MemberData(nameof(ReducedTestCases))]
public static void ParseJson_SeekableStream(bool compactData, TestCaseType type, string jsonString)
public static async Task ParseJson_SeekableStream(bool compactData, TestCaseType type, string jsonString)
{
ParseJson(
await ParseJsonAsync(
compactData,
type,
jsonString,
null,
bytes => JsonDocument.Parse(new MemoryStream(bytes)));
bytes => Task.FromResult(JsonDocument.Parse(new MemoryStream(bytes))));
}

[Theory]
[MemberData(nameof(ReducedTestCases))]
public static void ParseJson_SeekableStream_Async(bool compactData, TestCaseType type, string jsonString)
public static async Task ParseJson_SeekableStream_Async(bool compactData, TestCaseType type, string jsonString)
{
ParseJson(
await ParseJsonAsync(
compactData,
type,
jsonString,
null,
bytes => JsonDocument.ParseAsync(new MemoryStream(bytes)).GetAwaiter().GetResult());
bytes => JsonDocument.ParseAsync(new MemoryStream(bytes)));
}

[Theory]
[MemberData(nameof(ReducedTestCases))]
public static void ParseJson_UnseekableStream(bool compactData, TestCaseType type, string jsonString)
public static async Task ParseJson_UnseekableStream(bool compactData, TestCaseType type, string jsonString)
{
ParseJson(
await ParseJsonAsync(
compactData,
type,
jsonString,
null,
bytes => JsonDocument.Parse(
bytes => JsonDocument.ParseAsync(
new WrappedMemoryStream(canRead: true, canWrite: false, canSeek: false, bytes)));
}

[Theory]
[MemberData(nameof(ReducedTestCases))]
public static void ParseJson_UnseekableStream_Async(bool compactData, TestCaseType type, string jsonString)
public static async Task ParseJson_UnseekableStream_Async(bool compactData, TestCaseType type, string jsonString)
{
ParseJson(
await ParseJsonAsync(
compactData,
type,
jsonString,
null,
bytes => JsonDocument.ParseAsync(
new WrappedMemoryStream(canRead: true, canWrite: false, canSeek: false, bytes)).
GetAwaiter().GetResult());
bytes => JsonDocument.ParseAsync(new WrappedMemoryStream(canRead: true, canWrite: false, canSeek: false, bytes)));
}


Expand Down Expand Up @@ -361,53 +359,52 @@ public static async Task ParseJson_UnseekableStream_Small_Async()

[Theory]
[MemberData(nameof(ReducedTestCases))]
public static void ParseJson_SeekableStream_WithBOM(bool compactData, TestCaseType type, string jsonString)
public static async Task ParseJson_SeekableStream_WithBOM(bool compactData, TestCaseType type, string jsonString)
{
ParseJson(
await ParseJsonAsync(
compactData,
type,
jsonString,
null,
bytes => JsonDocument.Parse(new MemoryStream(Utf8Bom.Concat(bytes).ToArray())));
bytes => Task.FromResult(JsonDocument.Parse(new MemoryStream(Utf8Bom.Concat(bytes).ToArray()))));
}

[Theory]
[MemberData(nameof(ReducedTestCases))]
public static void ParseJson_SeekableStream_Async_WithBOM(bool compactData, TestCaseType type, string jsonString)
public static async Task ParseJson_SeekableStream_Async_WithBOM(bool compactData, TestCaseType type, string jsonString)
{
ParseJson(
await ParseJsonAsync(
compactData,
type,
jsonString,
null,
bytes => JsonDocument.ParseAsync(new MemoryStream(Utf8Bom.Concat(bytes).ToArray())).GetAwaiter().GetResult());
bytes => JsonDocument.ParseAsync(new MemoryStream(Utf8Bom.Concat(bytes).ToArray())));
}

[Theory]
[MemberData(nameof(ReducedTestCases))]
public static void ParseJson_UnseekableStream_WithBOM(bool compactData, TestCaseType type, string jsonString)
public static async Task ParseJson_UnseekableStream_WithBOM(bool compactData, TestCaseType type, string jsonString)
{
ParseJson(
await ParseJsonAsync(
compactData,
type,
jsonString,
null,
bytes => JsonDocument.Parse(
new WrappedMemoryStream(canRead: true, canWrite: false, canSeek: false, Utf8Bom.Concat(bytes).ToArray())));
bytes => Task.FromResult(JsonDocument.Parse(
new WrappedMemoryStream(canRead: true, canWrite: false, canSeek: false, Utf8Bom.Concat(bytes).ToArray()))));
}

[Theory]
[MemberData(nameof(ReducedTestCases))]
public static void ParseJson_UnseekableStream_Async_WithBOM(bool compactData, TestCaseType type, string jsonString)
public static async Task ParseJson_UnseekableStream_Async_WithBOM(bool compactData, TestCaseType type, string jsonString)
{
ParseJson(
await ParseJsonAsync(
compactData,
type,
jsonString,
null,
bytes => JsonDocument.ParseAsync(
new WrappedMemoryStream(canRead: true, canWrite: false, canSeek: false, Utf8Bom.Concat(bytes).ToArray())).
GetAwaiter().GetResult());
new WrappedMemoryStream(canRead: true, canWrite: false, canSeek: false, Utf8Bom.Concat(bytes).ToArray())));
}

[Fact]
Expand Down Expand Up @@ -486,34 +483,34 @@ public static Task ParseJson_UnseekableStream_Async_BadBOM(string json)

[Theory]
[MemberData(nameof(ReducedTestCases))]
public static void ParseJson_SequenceBytes_Single(bool compactData, TestCaseType type, string jsonString)
public static async Task ParseJson_SequenceBytes_Single(bool compactData, TestCaseType type, string jsonString)
{
ParseJson(
await ParseJsonAsync(
compactData,
type,
jsonString,
null,
bytes => JsonDocument.Parse(new ReadOnlySequence<byte>(bytes)));
bytes => Task.FromResult(JsonDocument.Parse(new ReadOnlySequence<byte>(bytes))));
}

[Theory]
[MemberData(nameof(ReducedTestCases))]
public static void ParseJson_SequenceBytes_Multi(bool compactData, TestCaseType type, string jsonString)
public static async Task ParseJson_SequenceBytes_Multi(bool compactData, TestCaseType type, string jsonString)
{
ParseJson(
await ParseJsonAsync(
compactData,
type,
jsonString,
null,
bytes => JsonDocument.Parse(JsonTestHelper.SegmentInto(bytes, 31)));
bytes => Task.FromResult(JsonDocument.Parse(JsonTestHelper.SegmentInto(bytes, 31))));
}

private static void ParseJson(
private static async Task ParseJsonAsync(
bool compactData,
TestCaseType type,
string jsonString,
Func<string, JsonDocument> stringDocBuilder,
Func<byte[], JsonDocument> bytesDocBuilder)
Func<string, Task<JsonDocument>> stringDocBuilder,
Func<byte[], Task<JsonDocument>> bytesDocBuilder)
{
// One, but not both, must be null.
if ((stringDocBuilder == null) == (bytesDocBuilder == null))
Expand All @@ -527,7 +524,7 @@ private static void ParseJson(

byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString);

using (JsonDocument doc = stringDocBuilder?.Invoke(jsonString) ?? bytesDocBuilder?.Invoke(dataUtf8))
using (JsonDocument doc = await (stringDocBuilder?.Invoke(jsonString) ?? bytesDocBuilder?.Invoke(dataUtf8)))
{
Assert.NotNull(doc);

Expand Down Expand Up @@ -3650,7 +3647,7 @@ private static string GetCompactJson(TestCaseType testCaseType, string jsonStrin
}

[Fact]
public static void VerifyMultiThreadedDispose()
public static async Task VerifyMultiThreadedDispose()
{
Action<object> disposeAction = (object document) => ((JsonDocument)document).Dispose();

Expand All @@ -3669,7 +3666,7 @@ public static void VerifyMultiThreadedDispose()
}
}

Task.WaitAll(tasks);
await Task.WhenAll(tasks);

// When ArrayPool gets corrupted, the Rent method might return an already rented array, which is incorrect.
// So we will rent as many arrays as calls to JsonElement.Dispose and check they are unique.
Expand Down
28 changes: 14 additions & 14 deletions src/libraries/System.Text.Json/tests/Serialization/CacheTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
Expand All @@ -11,18 +11,18 @@ namespace System.Text.Json.Serialization.Tests
public static class CacheTests
{
[Fact, OuterLoop]
public static void MultipleThreads_SameType_DifferentJson_Looping()
public static async Task MultipleThreads_SameType_DifferentJson_Looping()
{
const int Iterations = 100;

for (int i = 0; i < Iterations; i++)
{
MultipleThreads_SameType_DifferentJson();
await MultipleThreads_SameType_DifferentJson();
}
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
public static void MultipleThreads_SameType_DifferentJson()
[Fact]
public static async Task MultipleThreads_SameType_DifferentJson()
{
// Use local options to avoid obtaining already cached metadata from the default options.
var options = new JsonSerializerOptions();
Expand Down Expand Up @@ -69,22 +69,22 @@ void SerializeObject()
tasks[i + 3] = Task.Run(() => SerializeObject());
};

Task.WaitAll(tasks);
await Task.WhenAll(tasks);
}

[Fact, OuterLoop]
public static void MultipleThreads_DifferentTypes_Looping()
public static async Task MultipleThreads_DifferentTypes_Looping()
{
const int Iterations = 100;

for (int i = 0; i < Iterations; i++)
{
MultipleThreads_DifferentTypes();
await MultipleThreads_DifferentTypes();
}
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
public static void MultipleThreads_DifferentTypes()
[Fact]
public static async Task MultipleThreads_DifferentTypes()
{
// Use local options to avoid obtaining already cached metadata from the default options.
var options = new JsonSerializerOptions();
Expand Down Expand Up @@ -121,7 +121,7 @@ void Test(int i)
tasks[i + 1] = Task.Run(() => Test(TestClassCount - 2));
}

Task.WaitAll(tasks);
await Task.WhenAll(tasks);
}

[Fact]
Expand Down Expand Up @@ -164,9 +164,9 @@ public static void PropertyCacheWithMinInputsLast()
// this options is not the default options instance the tests will not use previously cached metadata.
private static JsonSerializerOptions s_options = new JsonSerializerOptions();

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
[Theory]
[MemberData(nameof(WriteSuccessCases))]
public static void MultipleTypes(ITestClass testObj)
public static async Task MultipleTypes(ITestClass testObj)
{
Type type = testObj.GetType();

Expand Down Expand Up @@ -199,7 +199,7 @@ void Deserialize()
tasks[i + 1] = Task.Run(() => Serialize());
};

Task.WaitAll(tasks);
await Task.WhenAll(tasks);
}

public static IEnumerable<object[]> WriteSuccessCases
Expand Down
Loading

0 comments on commit 95e3dcc

Please sign in to comment.