Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional tests for System.Text.Json #32705

Merged
merged 9 commits into from
Feb 29, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -290,45 +290,27 @@ internal bool TryWriteDataExtensionProperty(Utf8JsonWriter writer, T value, Json
bool success;
JsonDictionaryConverter<T> dictionaryConverter = (JsonDictionaryConverter<T>)this;

if (ClassType == ClassType.Value)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if it is OK to remove this (it's possible that it is OK to do, but is surprising to me). What happens when the user defines and register their own JsonConverter<T> for dictionaries?

@steveharter, @layomia - please review this when you get a chance. I suspect this branch was needed for something (which means we need to provide the test case).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I touched on this in Note 1 in the above comments, but I'm not sure it's possible. The line immediately above this one casts the current instance to a JsonDictionaryConverter<T> which is marked internal, so my assumption was users wouldn't be able to inherit it (unless there's something I'm not thinking of or aware of?)

I can certainly leave this in there if that's the call, but I think then testing and getting coverage on these lines would be difficult without introducing an InternalsVisibleTo relationship (or similar internals-testing-pattern) between the src and test project. That or adding an inherited class in the src project for the purposes of testing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a bug with writing extension data if there is a custom converter for the extension property: #32903. This is the reason why the L291 is not problematic (InvalidCastException) - we throw an NRE up stack.

We'll likely need this branch when we fix that bug, so for this PR we should revert this change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reverted in 256de92

{
Debug.Assert(!state.IsContinuation);

int originalPropertyDepth = writer.CurrentDepth;
bool isContinuation = state.IsContinuation;

// Ignore the naming policy for extension data.
state.Current.IgnoreDictionaryKeyPolicy = true;
state.Push();

success = dictionaryConverter.OnWriteResume(writer, value, options, ref state);
if (success)
{
VerifyWrite(originalPropertyDepth, writer);
}
}
else
if (!isContinuation)
{
bool isContinuation = state.IsContinuation;

state.Push();

if (!isContinuation)
{
Debug.Assert(state.Current.OriginalDepth == 0);
state.Current.OriginalDepth = writer.CurrentDepth;
}
Debug.Assert(state.Current.OriginalDepth == 0);
state.Current.OriginalDepth = writer.CurrentDepth;
}

// Ignore the naming policy for extension data.
state.Current.IgnoreDictionaryKeyPolicy = true;
// Ignore the naming policy for extension data.
state.Current.IgnoreDictionaryKeyPolicy = true;

success = dictionaryConverter.OnWriteResume(writer, value, options, ref state);
if (success)
{
VerifyWrite(state.Current.OriginalDepth, writer);
}

state.Pop(success);
success = dictionaryConverter.OnWriteResume(writer, value, options, ref state);
if (success)
{
VerifyWrite(state.Current.OriginalDepth, writer);
}

state.Pop(success);

return success;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace System.Text.Json.Serialization
/// </summary>
internal abstract class JsonDictionaryConverter<T> : JsonResumableConverter<T>
{
internal override ClassType ClassType => ClassType.Dictionary;
internal sealed override ClassType ClassType => ClassType.Dictionary;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should seal the overrides in JsonCollectionConverter and JsonObjectConverter as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 02ab96b

protected internal abstract bool OnWriteResume(Utf8JsonWriter writer, T dictionary, JsonSerializerOptions options, ref WriteStack state);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public static partial class JsonSerializer
/// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> which may be used to cancel the write operation.</param>
public static Task SerializeAsync<TValue>(Stream utf8Json, TValue value, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default)
{
if (utf8Json == null)
Copy link
Member

@ahsonkhan ahsonkhan Feb 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do, thanks for the pointers to the docs code and example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throw new ArgumentNullException(nameof(utf8Json));

return WriteAsyncCore(utf8Json, value, typeof(TValue), options, cancellationToken);
}

Expand Down Expand Up @@ -65,11 +68,6 @@ private static async Task WriteAsyncCore(Stream utf8Json, object? value, Type in
return;
}

if (inputType == null)
{
inputType = value.GetType();
}

WriteStack state = default;
state.InitializeRoot(inputType, options, supportContinuation: true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ namespace System.Text.Json.Serialization.Tests
public static partial class StreamTests
{
[Fact]
public static async Task NullArgumentFail()
public static async Task ReadNullArgumentFail()
{
await Assert.ThrowsAsync<ArgumentNullException>(async () => await JsonSerializer.DeserializeAsync<string>((Stream)null));
await Assert.ThrowsAsync<ArgumentNullException>(async () => await JsonSerializer.DeserializeAsync((Stream)null, (Type)null));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we are doing this, let's add all permutations. For example: have the stream parameter be null, but the type parameter be non-null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 7a6e0cb

await Assert.ThrowsAsync<ArgumentNullException>(async () => await JsonSerializer.DeserializeAsync((Stream)null, typeof(string)));
await Assert.ThrowsAsync<ArgumentNullException>(async () => await JsonSerializer.DeserializeAsync(new MemoryStream(), (Type)null));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ namespace System.Text.Json.Serialization.Tests
{
public static partial class StreamTests
{
[Fact]
public static async Task WriteNullArgumentFail()
{
await Assert.ThrowsAsync<ArgumentNullException>(async () => await JsonSerializer.SerializeAsync((Stream)null, 1));
await Assert.ThrowsAsync<ArgumentNullException>(async () => await JsonSerializer.SerializeAsync((Stream)null, 1, typeof(int)));
}

[Fact]
public static async Task VerifyValueFail()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,82 @@ public static void TestSingleStringsMultiSegment()
}
}

[Fact]
public static void TestMultiSegmentStringConversionToDateTime()
{
string jsonString = "\"1997-07-16\"";
string expectedString = "1997-07-16";
int expectedTokenLength = 10;
DateTime expectedDateTime = DateTime.Parse(expectedString);

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

ReadOnlySequence<byte> sequence = JsonTestHelper.CreateSegments(utf8);

for (int j = 0; j < utf8.Length; j++)
{
var utf8JsonReader = new Utf8JsonReader(sequence.Slice(0, j), isFinalBlock: false, default);
ReadDateTimeHelper(ref utf8JsonReader, expectedDateTime, expectedTokenLength);

Assert.Equal(0, utf8JsonReader.TokenStartIndex);

long consumed = utf8JsonReader.BytesConsumed;
utf8JsonReader = new Utf8JsonReader(sequence.Slice(consumed), isFinalBlock: true, utf8JsonReader.CurrentState);
ReadDateTimeHelper(ref utf8JsonReader, expectedDateTime, expectedTokenLength);
}
}

private static void ReadDateTimeHelper(ref Utf8JsonReader jsonReader, DateTime expectedValue, long expectedTokenLength)
{
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonTokenType.String)
{
long tokenLength = jsonReader.HasValueSequence ? jsonReader.ValueSequence.Length : jsonReader.ValueSpan.Length;
Assert.Equal(expectedTokenLength, tokenLength);
Assert.Equal(expectedValue, jsonReader.GetDateTime());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We missed the asserts for TryGetDateTime and TryGetDateTimeOffset APIs.

}
}
}

[Fact]
public static void TestMultiSegmentStringConversionToDateTimeOffset()
{
string jsonString = "\"1997-07-16\"";
string expectedString = "1997-07-16";
int expectedTokenLength = 10;
DateTimeOffset expectedDateTimeOffset = DateTimeOffset.Parse(expectedString);

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

ReadOnlySequence<byte> sequence = JsonTestHelper.CreateSegments(utf8);

for (int j = 0; j < utf8.Length; j++)
{
var utf8JsonReader = new Utf8JsonReader(sequence.Slice(0, j), isFinalBlock: false, default);
ReadDateTimeOffsetHelper(ref utf8JsonReader, expectedDateTimeOffset, expectedTokenLength);

Assert.Equal(0, utf8JsonReader.TokenStartIndex);

long consumed = utf8JsonReader.BytesConsumed;
utf8JsonReader = new Utf8JsonReader(sequence.Slice(consumed), isFinalBlock: true, utf8JsonReader.CurrentState);
ReadDateTimeOffsetHelper(ref utf8JsonReader, expectedDateTimeOffset, expectedTokenLength);
}
}

private static void ReadDateTimeOffsetHelper(ref Utf8JsonReader jsonReader, DateTimeOffset expectedValue, long expectedTokenLength)
{
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonTokenType.String)
{
long tokenLength = jsonReader.HasValueSequence ? jsonReader.ValueSequence.Length : jsonReader.ValueSpan.Length;
Assert.Equal(expectedTokenLength, tokenLength);
Assert.Equal(expectedValue, jsonReader.GetDateTimeOffset());
}
}
}

private static void SpanSequenceStatesAreEqualInvalidJson(byte[] dataUtf8, ReadOnlySequence<byte> sequence, int maxDepth, JsonCommentHandling commentHandling)
{
var stateSpan = new JsonReaderState(new JsonReaderOptions { CommentHandling = commentHandling, MaxDepth = maxDepth });
Expand Down