Skip to content

Commit f0db8f9

Browse files
Copilotstephentoub
andauthored
Improve code coverage for System.Text.Json with targeted unit tests (#120731)
- [x] Mark DeepEquals_TooDeepJsonDocument_ThrowsInsufficientExecutionStackException test as [OuterLoop] - [x] Add 136 tests systematically targeting uncovered code paths - [x] **Final Coverage: 94.27% line, 90.60% branch, 93.40% method** (baseline: 93.73%, +0.54% improvement) - [x] Fix OutOfMemoryException in validation tests - [x] Convert all JSON string constants to raw string literals (per code review feedback) - [x] Replace wasteful JsonSerializerOptions creation with JsonSerializerOptions.Default (per code review feedback) - [x] Revert incorrect line in JsonValueTests to use options variable (per code review feedback) - [x] Convert additional JSON strings to raw string literals in DomTests.cs (per code review feedback) - [x] All 49,787 tests passing ## Summary Through systematic iterations, this PR improved System.Text.Json coverage from 93.73% to 94.27% (+0.54%) by adding 136 targeted unit tests covering previously untested API surfaces including JsonSerializer methods with JsonTypeInfo/JsonSerializerContext, multi-segment Utf8JsonReader scenarios, JsonWriter formatting options, JsonNode operations, JsonNamingPolicy variants, and collection serialization. Remaining gaps to reach 95% (~215 lines) consist primarily of complex Utf8JsonReader edge cases requiring specialized buffer setups, ThrowHelper exception paths, and F# converter infrastructure. <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: stephentoub <[email protected]>
1 parent 57115ce commit f0db8f9

File tree

15 files changed

+1572
-1
lines changed

15 files changed

+1572
-1
lines changed

src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Immutable.Write.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,5 +507,42 @@ public async Task WriteImmutableCollectionWrappers()
507507
Assert.Equal(SimpleTestClassWithImmutableSetWrapper.s_json.StripWhitespace(), await Serializer.SerializeWrapper(obj5));
508508
Assert.Equal(SimpleTestClassWithImmutableSetWrapper.s_json.StripWhitespace(), await Serializer.SerializeWrapper<object>(obj5));
509509
}
510+
511+
[Fact]
512+
public async Task WriteImmutableStack()
513+
{
514+
ImmutableStack<int> input = ImmutableStack.Create<int>(1, 2, 3);
515+
string json = await Serializer.SerializeWrapper(input);
516+
Assert.Equal("[3,2,1]", json);
517+
}
518+
519+
[Fact]
520+
public async Task WriteImmutableQueue()
521+
{
522+
ImmutableQueue<int> input = ImmutableQueue.Create<int>(1, 2, 3);
523+
string json = await Serializer.SerializeWrapper(input);
524+
Assert.Equal("[1,2,3]", json);
525+
}
526+
527+
[Fact]
528+
public async Task WriteImmutableHashSet()
529+
{
530+
ImmutableHashSet<int> input = ImmutableHashSet.Create<int>(1, 2, 3);
531+
string json = await Serializer.SerializeWrapper(input);
532+
Assert.Contains("1", json);
533+
Assert.Contains("2", json);
534+
Assert.Contains("3", json);
535+
}
536+
537+
[Fact]
538+
public async Task WriteImmutableDictionaryStringKey()
539+
{
540+
ImmutableDictionary<string, int> input = ImmutableDictionary.Create<string, int>()
541+
.Add("a", 1)
542+
.Add("b", 2);
543+
string json = await Serializer.SerializeWrapper(input);
544+
Assert.Contains("\"a\":1", json);
545+
Assert.Contains("\"b\":2", json);
546+
}
510547
}
511548
}

src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonDocumentTests.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3996,6 +3996,72 @@ public static void ParseJsonDuplicatePropertiesErrorMessageEscaped()
39963996
() => JsonDocument.Parse(json, s_noDuplicateParamsOptions),
39973997
"'0'");
39983998
}
3999+
4000+
[Fact]
4001+
public static void JsonElement_GetDouble_EdgeCases()
4002+
{
4003+
using JsonDocument doc = JsonDocument.Parse("0.0");
4004+
double value = doc.RootElement.GetDouble();
4005+
Assert.Equal(0.0, value);
4006+
4007+
using JsonDocument doc2 = JsonDocument.Parse("1.7976931348623157E+308");
4008+
double value2 = doc2.RootElement.GetDouble();
4009+
Assert.True(value2 > 1E+307);
4010+
4011+
using JsonDocument doc3 = JsonDocument.Parse("-1.7976931348623157E+308");
4012+
double value3 = doc3.RootElement.GetDouble();
4013+
Assert.True(value3 < -1E+307);
4014+
}
4015+
4016+
[Fact]
4017+
public static void JsonElement_GetSingle_EdgeCases()
4018+
{
4019+
using JsonDocument doc = JsonDocument.Parse("0.0");
4020+
float value = doc.RootElement.GetSingle();
4021+
Assert.Equal(0.0f, value);
4022+
4023+
using JsonDocument doc2 = JsonDocument.Parse("3.4028235E+38");
4024+
float value2 = doc2.RootElement.GetSingle();
4025+
Assert.True(value2 > 1E+37f);
4026+
4027+
using JsonDocument doc3 = JsonDocument.Parse("-3.4028235E+38");
4028+
float value3 = doc3.RootElement.GetSingle();
4029+
Assert.True(value3 < -1E+37f);
4030+
}
4031+
4032+
[Fact]
4033+
public static void ParseWithMaxDepthOption()
4034+
{
4035+
string json = """{"a":{"b":{"c":{"d":1}}}}""";
4036+
var options = new JsonDocumentOptions { MaxDepth = 10 };
4037+
using var doc = JsonDocument.Parse(json, options);
4038+
Assert.Equal(1, doc.RootElement.GetProperty("a").GetProperty("b").GetProperty("c").GetProperty("d").GetInt32());
4039+
}
4040+
4041+
[Fact]
4042+
public static void ParseWithAllowTrailingCommas()
4043+
{
4044+
string json = """{"a":1,}""";
4045+
var options = new JsonDocumentOptions { AllowTrailingCommas = true };
4046+
using var doc = JsonDocument.Parse(json, options);
4047+
Assert.Equal(1, doc.RootElement.GetProperty("a").GetInt32());
4048+
}
4049+
4050+
[Fact]
4051+
public static void JsonElement_TryGetDouble()
4052+
{
4053+
using JsonDocument doc = JsonDocument.Parse("3.14159");
4054+
Assert.True(doc.RootElement.TryGetDouble(out double value));
4055+
Assert.Equal(3.14159, value, precision: 5);
4056+
}
4057+
4058+
[Fact]
4059+
public static void JsonElement_TryGetSingle()
4060+
{
4061+
using JsonDocument doc = JsonDocument.Parse("3.14");
4062+
Assert.True(doc.RootElement.TryGetSingle(out float value));
4063+
Assert.Equal(3.14f, value, precision: 2);
4064+
}
39994065
}
40004066

40014067
public class ThrowOnReadStream : MemoryStream
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Xunit;
5+
6+
namespace System.Text.Json.Tests
7+
{
8+
public static class JsonNamingPolicyTests
9+
{
10+
[Theory]
11+
[InlineData("MyProperty", "myProperty")]
12+
[InlineData("PropertyName", "propertyName")]
13+
[InlineData("ABC", "aBC")]
14+
[InlineData("A", "a")]
15+
[InlineData("", "")]
16+
public static void CamelCase_ConvertName(string input, string expected)
17+
{
18+
string result = JsonNamingPolicy.CamelCase.ConvertName(input);
19+
Assert.Equal(expected, result);
20+
}
21+
22+
[Theory]
23+
[InlineData("MyProperty", "my_property")]
24+
[InlineData("PropertyName", "property_name")]
25+
[InlineData("ABC", "a_b_c")]
26+
[InlineData("HTMLParser", "h_t_m_l_parser")]
27+
[InlineData("A", "a")]
28+
[InlineData("", "")]
29+
public static void SnakeCaseLower_ConvertName(string input, string expected)
30+
{
31+
string result = JsonNamingPolicy.SnakeCaseLower.ConvertName(input);
32+
Assert.Equal(expected, result);
33+
}
34+
35+
[Theory]
36+
[InlineData("MyProperty", "MY_PROPERTY")]
37+
[InlineData("PropertyName", "PROPERTY_NAME")]
38+
[InlineData("ABC", "A_B_C")]
39+
[InlineData("HTMLParser", "H_T_M_L_PARSER")]
40+
[InlineData("A", "A")]
41+
[InlineData("", "")]
42+
public static void SnakeCaseUpper_ConvertName(string input, string expected)
43+
{
44+
string result = JsonNamingPolicy.SnakeCaseUpper.ConvertName(input);
45+
Assert.Equal(expected, result);
46+
}
47+
48+
[Theory]
49+
[InlineData("MyProperty", "my-property")]
50+
[InlineData("PropertyName", "property-name")]
51+
[InlineData("ABC", "a-b-c")]
52+
[InlineData("HTMLParser", "h-t-m-l-parser")]
53+
[InlineData("A", "a")]
54+
[InlineData("", "")]
55+
public static void KebabCaseLower_ConvertName(string input, string expected)
56+
{
57+
string result = JsonNamingPolicy.KebabCaseLower.ConvertName(input);
58+
Assert.Equal(expected, result);
59+
}
60+
61+
[Theory]
62+
[InlineData("MyProperty", "MY-PROPERTY")]
63+
[InlineData("PropertyName", "PROPERTY-NAME")]
64+
[InlineData("ABC", "A-B-C")]
65+
[InlineData("HTMLParser", "H-T-M-L-PARSER")]
66+
[InlineData("A", "A")]
67+
[InlineData("", "")]
68+
public static void KebabCaseUpper_ConvertName(string input, string expected)
69+
{
70+
string result = JsonNamingPolicy.KebabCaseUpper.ConvertName(input);
71+
Assert.Equal(expected, result);
72+
}
73+
}
74+
}

src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonArrayTests.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,5 +897,41 @@ public static void Deserialize_WrongType(string json)
897897
{
898898
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<JsonArray>(json));
899899
}
900+
901+
[Fact]
902+
public static void DeepEquals_WithNestedArrays()
903+
{
904+
JsonArray array1 = new JsonArray { 1, 2, new JsonArray { 3, 4 } };
905+
JsonArray array2 = new JsonArray { 1, 2, new JsonArray { 3, 4 } };
906+
JsonArray array3 = new JsonArray { 1, 2, new JsonArray { 3, 5 } };
907+
908+
Assert.True(JsonNode.DeepEquals(array1, array2));
909+
Assert.False(JsonNode.DeepEquals(array1, array3));
910+
Assert.False(JsonNode.DeepEquals(array1, null));
911+
}
912+
913+
[Fact]
914+
public static void DeepEquals_DifferentLengths()
915+
{
916+
JsonArray array1 = new JsonArray { 1, 2, 3 };
917+
JsonArray array2 = new JsonArray { 1, 2 };
918+
919+
Assert.False(JsonNode.DeepEquals(array1, array2));
920+
}
921+
922+
[Fact]
923+
public static void Clear_RemovesAllItemsAndDetachesParent()
924+
{
925+
JsonArray array = new JsonArray { 1, 2, 3 };
926+
JsonNode item = array[0];
927+
928+
Assert.Equal(3, array.Count);
929+
Assert.Same(array, item.Parent);
930+
931+
array.Clear();
932+
933+
Assert.Equal(0, array.Count);
934+
Assert.Null(item.Parent);
935+
}
900936
}
901937
}

src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonNodeTests.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,5 +303,41 @@ public static void DeepEquals_NotEqualValuesReturnFalse(string value1, string va
303303

304304
AssertNotDeepEqual(obj1, obj2);
305305
}
306+
307+
[Fact]
308+
public static void JsonNode_ReplaceWith()
309+
{
310+
JsonObject obj = new JsonObject();
311+
obj["name"] = "test";
312+
JsonNode node = obj["name"];
313+
314+
node.ReplaceWith(JsonValue.Create("replaced"));
315+
Assert.Equal("replaced", obj["name"].GetValue<string>());
316+
}
317+
318+
[Fact]
319+
public static void JsonArray_Replace()
320+
{
321+
JsonArray arr = new JsonArray(1, 2, 3);
322+
arr[1] = JsonValue.Create(99);
323+
Assert.Equal(99, arr[1].GetValue<int>());
324+
}
325+
326+
[Fact]
327+
public static void JsonObject_SetProperty()
328+
{
329+
JsonObject obj = new JsonObject();
330+
obj["key"] = "value";
331+
obj["key"] = "newValue";
332+
Assert.Equal("newValue", obj["key"].GetValue<string>());
333+
}
334+
335+
[Fact]
336+
public static void JsonValue_AsValue()
337+
{
338+
JsonNode node = JsonValue.Create(42);
339+
JsonValue value = node.AsValue();
340+
Assert.Equal(42, value.GetValue<int>());
341+
}
306342
}
307343
}

src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonObjectTests.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,5 +1810,46 @@ public static void GetPath_IsThreadSafe()
18101810
});
18111811
}
18121812
}
1813+
1814+
[Fact]
1815+
public static void DeepEquals_WithNestedObjects()
1816+
{
1817+
JsonObject obj1 = new JsonObject { ["a"] = 1, ["b"] = new JsonObject { ["c"] = 2 } };
1818+
JsonObject obj2 = new JsonObject { ["a"] = 1, ["b"] = new JsonObject { ["c"] = 2 } };
1819+
JsonObject obj3 = new JsonObject { ["a"] = 1, ["b"] = new JsonObject { ["c"] = 3 } };
1820+
1821+
Assert.True(JsonNode.DeepEquals(obj1, obj2));
1822+
Assert.False(JsonNode.DeepEquals(obj1, obj3));
1823+
Assert.False(JsonNode.DeepEquals(obj1, null));
1824+
}
1825+
1826+
[Fact]
1827+
public static void DeepEquals_DifferentKeys()
1828+
{
1829+
JsonObject obj1 = new JsonObject { ["a"] = 1, ["b"] = 2 };
1830+
JsonObject obj2 = new JsonObject { ["a"] = 1, ["c"] = 2 };
1831+
1832+
Assert.False(JsonNode.DeepEquals(obj1, obj2));
1833+
}
1834+
1835+
[Fact]
1836+
public static void TryGetPropertyValue_NonExistentProperty()
1837+
{
1838+
JsonObject obj = new JsonObject { ["a"] = 1 };
1839+
1840+
bool result = obj.TryGetPropertyValue("b", out JsonNode value);
1841+
1842+
Assert.False(result);
1843+
Assert.Null(value);
1844+
}
1845+
1846+
[Fact]
1847+
public static void ContainsKey_ChecksForProperty()
1848+
{
1849+
JsonObject obj = new JsonObject { ["a"] = 1, ["b"] = 2 };
1850+
1851+
Assert.True(obj.ContainsKey("a"));
1852+
Assert.False(obj.ContainsKey("c"));
1853+
}
18131854
}
18141855
}

src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonValueTests.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,5 +909,60 @@ private class ExcludeType_TypeInfoResolver(Type excludeType) : IJsonTypeInfoReso
909909
}
910910

911911
private record DummyClass;
912+
913+
[Fact]
914+
public static void JsonValue_CreateFromInt()
915+
{
916+
JsonValue value = JsonValue.Create(42);
917+
Assert.Equal(42, value.GetValue<int>());
918+
Assert.True(value.TryGetValue(out int result));
919+
Assert.Equal(42, result);
920+
}
921+
922+
[Fact]
923+
public static void JsonValue_CreateFromString()
924+
{
925+
JsonValue value = JsonValue.Create("test");
926+
Assert.Equal("test", value.GetValue<string>());
927+
Assert.True(value.TryGetValue(out string result));
928+
Assert.Equal("test", result);
929+
}
930+
931+
[Fact]
932+
public static void JsonValue_CreateFromBool()
933+
{
934+
JsonValue value = JsonValue.Create(true);
935+
Assert.True(value.GetValue<bool>());
936+
Assert.True(value.TryGetValue(out bool result));
937+
Assert.True(result);
938+
}
939+
940+
[Fact]
941+
public static void JsonValue_CreateFromDouble()
942+
{
943+
JsonValue value = JsonValue.Create(3.14);
944+
Assert.Equal(3.14, value.GetValue<double>());
945+
Assert.True(value.TryGetValue(out double result));
946+
Assert.Equal(3.14, result);
947+
}
948+
949+
[Fact]
950+
public static void JsonValue_CreateWithJsonTypeInfo()
951+
{
952+
JsonTypeInfo<int> typeInfo = (JsonTypeInfo<int>)JsonSerializerOptions.Default.GetTypeInfo(typeof(int));
953+
954+
JsonValue value = JsonValue.Create(42, typeInfo);
955+
Assert.Equal(42, value.GetValue<int>());
956+
}
957+
958+
[Fact]
959+
public static void JsonValue_CreateWithJsonTypeInfoAndOptions()
960+
{
961+
JsonTypeInfo<string> typeInfo = (JsonTypeInfo<string>)JsonSerializerOptions.Default.GetTypeInfo(typeof(string));
962+
var nodeOptions = new JsonNodeOptions { PropertyNameCaseInsensitive = true };
963+
964+
JsonValue value = JsonValue.Create("test", typeInfo, nodeOptions);
965+
Assert.Equal("test", value.GetValue<string>());
966+
}
912967
}
913968
}

0 commit comments

Comments
 (0)