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 code coverage in System.Text.Json #33019

Merged
merged 11 commits into from
Mar 4, 2020
65 changes: 65 additions & 0 deletions src/libraries/System.Text.Json/tests/JsonBase64TestData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;

namespace System.Text.Json.Tests
{
internal class JsonBase64TestData
{
public static IEnumerable<object[]> ValidBase64Tests()
{
yield return new object[] { "\"ABC=\"" };
yield return new object[] { "\"AB+D\"" };
yield return new object[] { "\"ABCD\"" };
yield return new object[] { "\"ABC/\"" };
yield return new object[] { "\"++++\"" };
yield return new object[] { GenerateRandomValidLargeString() };
}

public static IEnumerable<object[]> InvalidBase64Tests()
{
yield return new object[] { "\"ABC===\"" };
yield return new object[] { "\"ABC\"" };
yield return new object[] { "\"ABC!\"" };
yield return new object[] { GenerateRandomInvalidLargeString(includeEscapedCharacter: true) };
yield return new object[] { GenerateRandomInvalidLargeString(includeEscapedCharacter: false) };
}

private static string GenerateRandomValidLargeString()
{
var random = new Random(42);
var charArray = new char[502]; // valid Base64 strings must have length divisible by 4 (not including surrounding quotes)
charArray[0] = '"';
for (int i = 1; i < charArray.Length - 1; i++)
{
charArray[i] = (char)random.Next('A', 'Z'); // ASCII values (between 65 and 90) that constitute valid base 64 string.
}
charArray[charArray.Length - 1] = '"';
var jsonString = new string(charArray);
return jsonString;
}

private static string GenerateRandomInvalidLargeString(bool includeEscapedCharacter)
{
var random = new Random(42);
var charArray = new char[500];
charArray[0] = '"';
for (int i = 1; i < charArray.Length - 1; i++)
{
charArray[i] = (char)random.Next('?', '\\'); // ASCII values (between 63 and 91) that don't need to be escaped.
}

if (includeEscapedCharacter)
{
charArray[256] = '\\';
charArray[257] = '"';
}

charArray[charArray.Length - 1] = '"';
var jsonString = new string(charArray);
return jsonString;
}
}
}
41 changes: 2 additions & 39 deletions src/libraries/System.Text.Json/tests/JsonDocumentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1911,27 +1911,9 @@ public static void GetBase64Unescapes()
}

[Theory]
[InlineData("\"ABC=\"")]
[InlineData("\"AB+D\"")]
[InlineData("\"ABCD\"")]
[InlineData("\"ABC/\"")]
[InlineData("\"++++\"")]
[InlineData(null)] // Large randomly generated string
[MemberData(nameof(JsonBase64TestData.ValidBase64Tests), MemberType = typeof(JsonBase64TestData))]
public static void ReadBase64String(string jsonString)
{
if (jsonString == null)
{
var random = new Random(42);
var charArray = new char[502];
charArray[0] = '"';
for (int i = 1; i < charArray.Length; i++)
{
charArray[i] = (char)random.Next('A', 'Z'); // ASCII values (between 65 and 90) that constitute valid base 64 string.
}
charArray[charArray.Length - 1] = '"';
jsonString = new string(charArray);
}

byte[] expected = Convert.FromBase64String(jsonString.AsSpan(1, jsonString.Length - 2).ToString());

using (JsonDocument doc = JsonDocument.Parse(jsonString))
Expand All @@ -1944,28 +1926,9 @@ public static void ReadBase64String(string jsonString)
}

[Theory]
[InlineData("\"ABC===\"")]
[InlineData("\"ABC\"")]
[InlineData("\"ABC!\"")]
[InlineData(null)] // Large randomly generated string
[MemberData(nameof(JsonBase64TestData.InvalidBase64Tests), MemberType = typeof(JsonBase64TestData))]
public static void InvalidBase64(string jsonString)
{
if (jsonString == null)
{
var random = new Random(42);
var charArray = new char[500];
charArray[0] = '"';
for (int i = 1; i < charArray.Length; i++)
{
charArray[i] = (char)random.Next('?', '\\'); // ASCII values (between 63 and 91) that don't need to be escaped.
}

charArray[256] = '\\';
charArray[257] = '"';
charArray[charArray.Length - 1] = '"';
jsonString = new string(charArray);
}

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

using (JsonDocument doc = JsonDocument.Parse(dataUtf8))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ private class PocoWithInvalidConverter
public int MyInt { get; set; }
}

private class NullConverterAttribute : JsonConverterAttribute
{
public NullConverterAttribute() : base(null) { }

public override JsonConverter CreateConverter(Type typeToConvert)
{
return null;
}
}

private class PocoWithNullConverter
{
[NullConverter]
public int MyInt { get; set; }
}

[Fact]
public static void AttributeCreateConverterFail()
{
Expand All @@ -81,6 +97,15 @@ public static void AttributeCreateConverterFail()

ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<PocoWithInvalidConverter>("{}"));
Assert.Contains("'System.Text.Json.Serialization.Tests.CustomConverterTests+PocoWithInvalidConverter.MyInt'", ex.Message);

ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(new PocoWithNullConverter()));
// Message should be in the form "The converter specified on 'System.Text.Json.Serialization.Tests.CustomConverterTests+PocoWithNullConverter.MyInt' is not compatible with the type 'System.Int32'."
Assert.Contains("'System.Text.Json.Serialization.Tests.CustomConverterTests+PocoWithNullConverter.MyInt'", ex.Message);
Assert.Contains("'System.Int32'", ex.Message);

ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<PocoWithNullConverter>("{}"));
Assert.Contains("'System.Text.Json.Serialization.Tests.CustomConverterTests+PocoWithNullConverter.MyInt'", ex.Message);
Assert.Contains("'System.Int32'", ex.Message);
}

private class InvalidTypeConverterClass
Expand Down
56 changes: 32 additions & 24 deletions src/libraries/System.Text.Json/tests/Serialization/CyclicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,43 @@ public static void WriteCyclicFailDefault()
}

[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(10)]
[InlineData(70)]
public static void WriteCyclicFail(int depth)
[InlineData(1, 2)]
[InlineData(2, 3)]
[InlineData(10, 11)]
[InlineData(70, 71)]
[InlineData(10, 0)] // 0 (or default) max depth is treated as 64
public static void WriteCyclic(int objectHierarchyDepth, int maxDepth)
{
var rootObj = new TestClassWithCycle("root");
CreateObjectHierarchy(1, depth, rootObj);
CreateObjectHierarchy(1, objectHierarchyDepth, rootObj);

{
var options = new JsonSerializerOptions();
options.MaxDepth = depth + 1;
var options = new JsonSerializerOptions();
options.MaxDepth = maxDepth;

// No exception since depth was not greater than MaxDepth.
string json = JsonSerializer.Serialize(rootObj, options);
Assert.False(string.IsNullOrEmpty(json));
}
string json = JsonSerializer.Serialize(rootObj, options);
Assert.False(string.IsNullOrEmpty(json));
}

{
var options = new JsonSerializerOptions();
options.MaxDepth = depth;
JsonException ex = Assert.Throws<JsonException>(() => JsonSerializer.Serialize(rootObj, options));

// Exception should contain the path and MaxDepth.
// Since the last Parent property is null, the serializer moves onto the Children property.
string expectedPath = "$" + string.Concat(Enumerable.Repeat(".Parent", depth - 1));
Assert.Contains(expectedPath, ex.Path);
Assert.Contains(depth.ToString(), ex.ToString());
}
[Theory]
[InlineData(1, 1, 0)]
[InlineData(2, 2, 1)]
[InlineData(10, 10, 9)]
[InlineData(70, 70, 69)]
[InlineData(70, 0, 63)] // 0 (or default) max depth is treated as 64
public static void WriteCyclicFail(int objectHierarchyDepth, int maxDepth, int expectedPathDepth)
{
var rootObj = new TestClassWithCycle("root");
CreateObjectHierarchy(1, objectHierarchyDepth, rootObj);

var options = new JsonSerializerOptions();
options.MaxDepth = maxDepth;
JsonException ex = Assert.Throws<JsonException>(() => JsonSerializer.Serialize(rootObj, options));

// Exception should contain the path and MaxDepth.
// Since the last Parent property is null, the serializer moves onto the Children property.
string expectedPath = "$" + string.Concat(Enumerable.Repeat(".Parent", expectedPathDepth));
Assert.Contains(expectedPath, ex.Path);
Assert.Contains(maxDepth.ToString(), ex.ToString());
}

private static TestClassWithCycle CreateObjectHierarchy(int i, int max, TestClassWithCycle previous)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,17 @@ public static void ThrowOnStructWithReference()
Assert.Equal("$[1].$ref", ex.Path);
Assert.Contains($"'{typeof(EmployeeStruct)}'", ex.Message);
}

[Theory]
[InlineData(@"{""$iz"": ""1""}", "$.$iz")]
[InlineData(@"{""$rez"": ""1""}", "$.$rez")]
[InlineData(@"{""$valuez"": []}", "$.$valuez")]
public static void InvalidMetadataPropertyNameWithSameLengthIsNotRecognized(string json, string expectedPath)
{
JsonException ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Employee>(json, s_deserializerOptionsPreserve));
Copy link
Member

Choose a reason for hiding this comment

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

Do we want to assert anything about the returned JsonException itself and its properties?
If not:

Suggested change
JsonException ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Employee>(json, s_deserializerOptionsPreserve));
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Employee>(json, s_deserializerOptionsPreserve));

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point -- added validation of the expected path in 9c0af54

Assert.Equal(expectedPath, ex.Path);
}

#endregion

#region Throw on immutables
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<Compile Include="DebuggerTests.cs" />
<Compile Include="FixedSizedBufferWriter.cs" />
<Compile Include="InvalidBufferWriter.cs" />
<Compile Include="JsonBase64TestData.cs" />
<Compile Include="JsonDateTimeTestData.cs" />
<Compile Include="JsonDocumentTests.cs" />
<Compile Include="JsonElementCloneTests.cs" />
Expand Down
41 changes: 2 additions & 39 deletions src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.TryGet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1157,27 +1157,9 @@ public static void GetBase64Unescapes()
}

[Theory]
[InlineData("\"ABC=\"")]
[InlineData("\"AB+D\"")]
[InlineData("\"ABCD\"")]
[InlineData("\"ABC/\"")]
[InlineData("\"++++\"")]
[InlineData(null)] // Large randomly generated string
[MemberData(nameof(JsonBase64TestData.ValidBase64Tests), MemberType = typeof(JsonBase64TestData))]
public static void ValidBase64(string jsonString)
{
if (jsonString == null)
{
var random = new Random(42);
var charArray = new char[502];
charArray[0] = '"';
for (int i = 1; i < charArray.Length; i++)
{
charArray[i] = (char)random.Next('A', 'Z'); // ASCII values (between 65 and 90) that constitute valid base 64 string.
}
charArray[charArray.Length - 1] = '"';
jsonString = new string(charArray);
}

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

var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default);
Expand All @@ -1192,28 +1174,9 @@ public static void ValidBase64(string jsonString)
}

[Theory]
[InlineData("\"ABC===\"")]
[InlineData("\"ABC\"")]
[InlineData("\"ABC!\"")]
[InlineData(null)] // Large randomly generated string
[MemberData(nameof(JsonBase64TestData.InvalidBase64Tests), MemberType = typeof(JsonBase64TestData))]
public static void InvalidBase64(string jsonString)
{
if (jsonString == null)
{
var random = new Random(42);
var charArray = new char[500];
charArray[0] = '"';
for (int i = 1; i < charArray.Length; i++)
{
charArray[i] = (char)random.Next('?', '\\'); // ASCII values (between 63 and 91) that don't need to be escaped.
}

charArray[256] = '\\';
charArray[257] = '"';
charArray[charArray.Length - 1] = '"';
jsonString = new string(charArray);
}

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

var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default);
Expand Down