Skip to content

Commit

Permalink
Additional code coverage in System.Text.Json (#33019)
Browse files Browse the repository at this point in the history
* added tests for invalid metadata properties with same length

* moved base64 test data in class, added test case

* added missing cyclic test for max depth of 0

* added test for null converter

* validated exception path

* added comment explaining 0 max depth

* using named parameter for readability

* added license header

* replacing duplicate test data

* removed redunant character assignment

* added explanation for base64 string length
  • Loading branch information
alanisaac authored Mar 4, 2020
1 parent 389835b commit cbd5166
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 102 deletions.
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));
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

0 comments on commit cbd5166

Please sign in to comment.