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

Conversation

alanisaac
Copy link
Contributor

@alanisaac alanisaac commented Feb 23, 2020

Adding additional tests and removed unreachable code in order to improve code coverage for System.Text.Json, as suggested in #32341.

@dnfclas
Copy link

dnfclas commented Feb 23, 2020

CLA assistant check
All CLA requirements met.

@danmoseley
Copy link
Member

Hi thanks for the contribution! Btw we don't really use "draft" PRs here. Is this ready for review?

@alanisaac alanisaac marked this pull request as ready for review February 23, 2020 13:52
@alanisaac
Copy link
Contributor Author

@danmosemsft

Thanks for letting me know, I've marked it as ready for review.

@alanisaac
Copy link
Contributor Author

alanisaac commented Feb 23, 2020

I added some additional changes. I'll leave this PR alone from here, and continue working on additional coverage improvements in another PR.

Note 1

JsonDictionaryConverter<T> dictionaryConverter = (JsonDictionaryConverter<T>)this;
if (ClassType == ClassType.Value)

In the previous line, we cast the current instance to a JsonDictionaryConverter<T>, whose ClassType in practice is always ClassType.Dictionary. Therefore, this conditional branch should be able to be removed safely. I think this tracks with the purpose of this method of writing data for [JsonExtensionData], which to my understanding always needs to be a Dictionary<> anyway.

However, to make sure this is safely enforced, I thought it would be good to explicitly mark JsonDictionaryConverter<T>.ClassType sealed as well, hence this change.

Note 2

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

I'm fairly confident this is unreachable. There are two calling methods:

  • SerializeAsync(Stream, object?, Type, JsonSerializerOptions?, CancellationToken) already has a defensive check that throws if inputType is null.
  • SerializeAsync<TValue>(Stream, TValue, JsonSerializerOptions?, CancellationToken cancellationToken) determines inputType from typeof(TValue). I tried to think through if there was a way that typeof(TValue) could be null through reflection, but I couldn't come up with anything -- MakeGenericMethod at least requires a non-null type parameter.

Note 3

public static Task SerializeAsync<TValue>(Stream utf8Json, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default)
{
return WriteAsyncCore(utf8Json, value, typeof(TValue), options, cancellationToken);
}

That call chain throws a NRE if Stream utf8Json is null, but the non-generic version checks that argument and throws an ArgumentNullException. For comparison, both the generic and non-generic versions of DeserializeAsync on the read side have a null check, for example:

public static ValueTask<TValue> DeserializeAsync<TValue>(
Stream utf8Json,
JsonSerializerOptions options = null,
CancellationToken cancellationToken = default)
{
if (utf8Json == null)
throw new ArgumentNullException(nameof(utf8Json));
return ReadAsync<TValue>(utf8Json, typeof(TValue), options, cancellationToken);
}

@@ -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.

{
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


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

var json = new Utf8JsonReader(sequence, isFinalBlock: false, state: default);
Copy link
Member

Choose a reason for hiding this comment

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

nit: Consider looping through the entire sequence and creating slices of arbitrary lengths (and re-entering the reader with "partial data"). See other "single value" tests like: TestSingleStringsMultiSegment

Copy link
Contributor Author

@alanisaac alanisaac Feb 26, 2020

Choose a reason for hiding this comment

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

Tried something out with 823f147, let me know if that doesn't cover what you were thinking.

@ahsonkhan ahsonkhan added this to the 5.0 milestone Feb 25, 2020
@ahsonkhan ahsonkhan added the test-enhancement Improvements of test source code label Feb 25, 2020
@@ -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

@@ -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

@@ -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
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

@layomia layomia left a comment

Choose a reason for hiding this comment

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

Thanks, @alanisaac!

{
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.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 10, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Text.Json test-enhancement Improvements of test source code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants