Skip to content

JSON continuation tests - #42393

Merged
layomia merged 25 commits into
dotnet:masterfrom
devsko:streamtest
Nov 2, 2020
Merged

JSON continuation tests#42393
layomia merged 25 commits into
dotnet:masterfrom
devsko:streamtest

Conversation

@devsko

Copy link
Copy Markdown
Contributor

See #42158
comment and comment and comment

Test only

Test several scenarios in System.Text.Json where deserialization has to continue after the next chunk of data is available.

- Test continuation at every position inside the tested object
- Many member with primitive and nullable types
- One more level of nested object
- All combinations of class/struct for tested and nested object
- tested and nested object with parametrized ctor for some properties
Tweak the payload and expect `JsonException`
@devsko

Copy link
Copy Markdown
ContributorAuthor

@layomia This is not yet ready for review. I hope to get done what I have in mind tomorrow.

@devsko
devsko marked this pull request as ready for review September 17, 2020 21:39
@devsko

Copy link
Copy Markdown
ContributorAuthor

Is there anything else about continuation / chunked buffer?

@ahsonkhan

ahsonkhan commented Sep 17, 2020

Copy link
Copy Markdown
Contributor

Is there anything else about continuation / chunked buffer?

Since you asked, how about continuation where the token being split isn't null but other types, like whitespace (\r\n), or true/false boolean, or some large string token, or a number?

In the Utf8JsonReader specific tests, a lot of those scenarios are covered, by building ROSequence with segments splits across a whole variety of locations within the JSON, and with partial data/state to test re-entrancy. But most of those are for relatively small payloads (due to test time), and the async Deserializer API for streams could benefit from that type of extensive coverage too :)

// TestCaseType is only used to give the json strings a descriptive name.
[Theory]
// Skipping large JSON since slicing them (O(n^2)) is too slow.
[MemberData(nameof(SmallTestCases))]
publicstaticvoidTestJsonReaderUtf8SegmentSizeOne(boolcompactData,TestCaseTypetype,stringjsonString)
{
ReadPartialSegmentSizeOne(compactData,type,jsonString);
}
// TestCaseType is only used to give the json strings a descriptive name.
[Theory]
[MemberData(nameof(LargeTestCases))]
publicstaticvoidTestJsonReaderLargeUtf8SegmentSizeOne(boolcompactData,TestCaseTypetype,stringjsonString)
{
// Skipping really large JSON on Browser to prevent OOM
if(PlatformDetection.IsBrowser&&(type==TestCaseType.Json40KB||type==TestCaseType.Json400KB||type==TestCaseType.ProjectLockJson))
{
return;
}
ReadFullySegmentSizeOne(compactData,type,jsonString);
}
// TestCaseType is only used to give the json strings a descriptive name.
[Theory]
[OuterLoop]
[MemberData(nameof(LargeTestCases))]
publicstaticvoidTestJsonReaderLargestUtf8SegmentSizeOne(boolcompactData,TestCaseTypetype,stringjsonString)
{
// Skipping really large JSON since slicing them (O(n^2)) is too slow.
if(type==TestCaseType.Json40KB||type==TestCaseType.Json400KB||type==TestCaseType.ProjectLockJson)
{
return;
}
ReadPartialSegmentSizeOne(compactData,type,jsonString);
}
privatestaticvoidReadPartialSegmentSizeOne(boolcompactData,TestCaseTypetype,stringjsonString)
{
// Remove all formatting/indendation
if(compactData)
{
jsonString=JsonTestHelper.GetCompactString(jsonString);
}
byte[]dataUtf8=Encoding.UTF8.GetBytes(jsonString);
Streamstream=newMemoryStream(dataUtf8);
TextReaderreader=newStreamReader(stream,Encoding.UTF8,false,1024,true);
stringexpectedStr=JsonTestHelper.NewtonsoftReturnStringHelper(reader);
ReadOnlySequence<byte>sequence=JsonTestHelper.GetSequence(dataUtf8,1);
for(intj=0;j<dataUtf8.Length;j++)
{
varutf8JsonReader=newUtf8JsonReader(sequence.Slice(0,j),isFinalBlock:false,default);
byte[]resultSequence=JsonTestHelper.ReaderLoop(dataUtf8.Length,outintlength,refutf8JsonReader);
stringactualStrSequence=Encoding.UTF8.GetString(resultSequence,0,length);
longconsumed=utf8JsonReader.BytesConsumed;
utf8JsonReader=newUtf8JsonReader(sequence.Slice(consumed),isFinalBlock:true,utf8JsonReader.CurrentState);
resultSequence=JsonTestHelper.ReaderLoop(dataUtf8.Length,outlength,refutf8JsonReader);
actualStrSequence+=Encoding.UTF8.GetString(resultSequence,0,length);
stringmessage=$"Expected consumed: {dataUtf8.Length-consumed}, Actual consumed: {utf8JsonReader.BytesConsumed}, Index: {j}";
Assert.True(dataUtf8.Length-consumed==utf8JsonReader.BytesConsumed,message);
Assert.Equal(expectedStr,actualStrSequence);
}
}
privatestaticvoidReadFullySegmentSizeOne(boolcompactData,TestCaseTypetype,stringjsonString)
{
// Remove all formatting/indendation
if(compactData)
{
jsonString=JsonTestHelper.GetCompactString(jsonString);
}
byte[]dataUtf8=Encoding.UTF8.GetBytes(jsonString);
Streamstream=newMemoryStream(dataUtf8);
TextReaderreader=newStreamReader(stream,Encoding.UTF8,false,1024,true);
stringexpectedStr=JsonTestHelper.NewtonsoftReturnStringHelper(reader);
ReadOnlySequence<byte>sequence=JsonTestHelper.GetSequence(dataUtf8,1);
varutf8JsonReader=newUtf8JsonReader(sequence,isFinalBlock:true,default);
byte[]resultSequence=JsonTestHelper.ReaderLoop(dataUtf8.Length,outintlength,refutf8JsonReader);
stringactualStrSequence=Encoding.UTF8.GetString(resultSequence,0,length);
Assert.Equal(expectedStr,actualStrSequence);
}
[Theory]
[MemberData(nameof(SmallTestCases))]
publicstaticvoidTestPartialJsonReaderMultiSegment(boolcompactData,TestCaseTypetype,stringjsonString)
{
_=type;
// Remove all formatting/indendation
if(compactData)
{
jsonString=JsonTestHelper.GetCompactString(jsonString);
}
byte[]dataUtf8=Encoding.UTF8.GetBytes(jsonString);
ReadOnlyMemory<byte>dataMemory=dataUtf8;
List<ReadOnlySequence<byte>>sequences=JsonTestHelper.GetSequences(dataMemory);
for(inti=0;i<sequences.Count;i++)
{
ReadOnlySequence<byte>sequence=sequences[i];
varjson=newUtf8JsonReader(sequence,isFinalBlock:true,default);
while(json.Read())
;
Assert.Equal(sequence.Length,json.BytesConsumed);
Assert.True(sequence.Slice(json.Position).IsEmpty);
}
for(inti=0;i<sequences.Count;i++)
{
ReadOnlySequence<byte>sequence=sequences[i];
varjson=newUtf8JsonReader(sequence);
while(json.Read())
;
Assert.Equal(sequence.Length,json.BytesConsumed);
Assert.True(sequence.Slice(json.Position).IsEmpty);
}
}
[Theory]
[OuterLoop]
[MemberData(nameof(SmallTestCases))]
publicstaticvoidTestPartialJsonReaderSlicesMultiSegment(boolcompactData,TestCaseTypetype,stringjsonString)
{
_=type;
// Remove all formatting/indendation
if(compactData)
{
jsonString=JsonTestHelper.GetCompactString(jsonString);
}
byte[]dataUtf8=Encoding.UTF8.GetBytes(jsonString);
ReadOnlyMemory<byte>dataMemory=dataUtf8;
List<ReadOnlySequence<byte>>sequences=JsonTestHelper.GetSequences(dataMemory);
for(inti=0;i<sequences.Count;i++)
{
ReadOnlySequence<byte>sequence=sequences[i];
for(intj=0;j<dataUtf8.Length;j++)
{
varjson=newUtf8JsonReader(sequence.Slice(0,j),isFinalBlock:false,default);
while(json.Read())
;
longconsumed=json.BytesConsumed;
JsonReaderStatejsonState=json.CurrentState;
byte[]consumedArray=sequence.Slice(0,consumed).ToArray();
Assert.Equal(consumedArray,sequence.Slice(0,json.Position).ToArray());
json=newUtf8JsonReader(sequence.Slice(consumed),isFinalBlock:true,jsonState);
while(json.Read())
;
Assert.Equal(dataUtf8.Length-consumed,json.BytesConsumed);
}
}
}

@devsko

Copy link
Copy Markdown
ContributorAuthor

how about continuation where the token being split isn't null but other types, like whitespace (\r\n), or true/false boolean, or some large string token, or a number?

All tests here split the payload once on every single character. Thus all tokens are tested how they work when split into 2 chunks including all mentioned examples except whitespaces. I will add them by enabling WriteIndented

Comment threadsrc/libraries/System.Text.Json/tests/Serialization/ContinuationTests.cs Outdated
Comment threadsrc/libraries/System.Text.Json/tests/Serialization/ContinuationTests.cs Outdated
Comment threadsrc/libraries/System.Text.Json/tests/Serialization/ContinuationTests.cs Outdated
Comment threadsrc/libraries/System.Text.Json/tests/Serialization/ContinuationTests.cs Outdated
Comment threadsrc/libraries/System.Text.Json/tests/Serialization/ContinuationTests.cs Outdated
Added dictionary test
Comment threadsrc/libraries/System.Text.Json/tests/Serialization/ContinuationTests.cs Outdated
@devsko

Copy link
Copy Markdown
ContributorAuthor

I'd say that's it. Thanks for your suggestions, help and reviews. Really appreciated. Feel free to change whatever you want - or wait 2 weeks. See you - peace

@stephentoub

Copy link
Copy Markdown
Member

@devsko, thanks for your efforts here. Are you still working on this?

@layomialayomia left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM - @devsko we can merge this once conflicts and #42393 (comment) are resolved.

@layomialayomia added the NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) label Oct 29, 2020
@layomialayomia self-assigned this Nov 2, 2020
@layomialayomia removed the NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) label Nov 2, 2020
@layomia

Copy link
Copy Markdown
Contributor

I pushed a commit to finish this PR.

@layomia
layomia merged commit e691753 into dotnet:masterNov 2, 2020
@ghostghost locked as resolved and limited conversation to collaborators Dec 7, 2020
@devsko
devsko deleted the streamtest branch March 5, 2021 18:11
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@devsko@ahsonkhan@stephentoub@layomia@steveharter@Dotnet-GitSync-Bot