From f208f918db2f626ee0d1f13b3141f172bdd677f4 Mon Sep 17 00:00:00 2001 From: Tim Haasdyk Date: Mon, 6 Oct 2025 10:58:54 +0200 Subject: [PATCH] Tidy up test JSON options --- .../ProjectSnapshotSerializationTests.cs | 6 ++++- .../FwLiteProjectSync.Tests/Sena3SyncTests.cs | 9 ++++--- .../Changes/ChangeSerializationTests.cs | 22 ++++++++-------- .../Data/BaseSerializationTest.cs | 25 ++++++++----------- .../LcmCrdt.Tests/Data/MigrationTests.cs | 22 ++++++---------- .../Data/SnapshotDeserializationTests.cs | 18 ++++++------- .../RichText/ColorJsonConverterTests.cs | 6 ++--- 7 files changed, 52 insertions(+), 56 deletions(-) diff --git a/backend/FwLite/FwLiteProjectSync.Tests/ProjectSnapshotSerializationTests.cs b/backend/FwLite/FwLiteProjectSync.Tests/ProjectSnapshotSerializationTests.cs index 17895df6f5..663c8f25ca 100644 --- a/backend/FwLite/FwLiteProjectSync.Tests/ProjectSnapshotSerializationTests.cs +++ b/backend/FwLite/FwLiteProjectSync.Tests/ProjectSnapshotSerializationTests.cs @@ -80,6 +80,10 @@ private static string RelativePath(string name, [CallerFilePath] string sourceFi name); } + private static readonly JsonSerializerOptions IndentedDefaultJsonOptions = new() + { + WriteIndented = true, + }; private async Task GetRoundTrippedIndentedSnapshot(string sourceSnapshotPath, [CallerMemberName] string fwDataProjectName = "") { var fwDataProject = new FwDataProject( @@ -99,6 +103,6 @@ private async Task GetRoundTrippedIndentedSnapshot(string sourceSnapshot { AllowTrailingCommas = true, }) ?? throw new InvalidOperationException("Could not parse json node"); - return node.ToJsonString(new JsonSerializerOptions { WriteIndented = true }); + return node.ToJsonString(IndentedDefaultJsonOptions); } } diff --git a/backend/FwLite/FwLiteProjectSync.Tests/Sena3SyncTests.cs b/backend/FwLite/FwLiteProjectSync.Tests/Sena3SyncTests.cs index ada7423cfc..219a6af763 100644 --- a/backend/FwLite/FwLiteProjectSync.Tests/Sena3SyncTests.cs +++ b/backend/FwLite/FwLiteProjectSync.Tests/Sena3SyncTests.cs @@ -21,6 +21,10 @@ public class Sena3SyncTests : IClassFixture, IAsyncLifetime private FwDataMiniLcmApi _fwDataApi = null!; private TestProject _project = null!; private MiniLcmImport _miniLcmImport = null!; + private static readonly JsonSerializerOptions IndentedDefaultJsonOptions = new() + { + WriteIndented = true, + }; public Sena3SyncTests(Sena3Fixture fixture) @@ -228,10 +232,7 @@ public async Task LiveSena3Sync() var throwAnyVerifyException = () => { if (verifyException is not null) throw verifyException; }; try { - await Verify(JsonSerializer.Serialize(fwHeadlessSnapshot, new JsonSerializerOptions - { - WriteIndented = true, - })) + await Verify(JsonSerializer.Serialize(fwHeadlessSnapshot, IndentedDefaultJsonOptions)) .UseStrictJson() .UseFileName("sena-3-live_snapshot"); } diff --git a/backend/FwLite/LcmCrdt.Tests/Changes/ChangeSerializationTests.cs b/backend/FwLite/LcmCrdt.Tests/Changes/ChangeSerializationTests.cs index 0a67fad05f..a35e467e45 100644 --- a/backend/FwLite/LcmCrdt.Tests/Changes/ChangeSerializationTests.cs +++ b/backend/FwLite/LcmCrdt.Tests/Changes/ChangeSerializationTests.cs @@ -61,8 +61,8 @@ public void CanRoundTripChanges(IChange change) //commit id is not serialized change.CommitId = Guid.Empty; var type = change.GetType(); - var json = JsonSerializer.Serialize(change, Options); - var newChange = JsonSerializer.Deserialize(json, type, Options); + var json = JsonSerializer.Serialize(change, HarmonyJsonOptions); + var newChange = JsonSerializer.Deserialize(json, type, HarmonyJsonOptions); newChange.Should().BeEquivalentTo(change); } @@ -89,7 +89,7 @@ public void CanDeserializeLatestRegressionData() //changes are updated and appended by RegressionDataUpToDate() whenever it finds a "latest" change that doesn't stably round-trip //or when it finds a change type that isn't represented using var jsonFile = File.OpenRead(GetJsonFilePath("ChangeDeserializationRegressionData.latest.verified.txt")); - var changes = JsonSerializer.Deserialize>(jsonFile, Options); + var changes = JsonSerializer.Deserialize>(jsonFile, HarmonyJsonOptions); changes.Should().NotBeNullOrEmpty().And.NotContainNulls(); //ensure that all change types are represented and none should be removed from AllChangeTypes @@ -116,7 +116,7 @@ public void CanDeserializeLegacyRegressionData() // when it detects that they don't stably round-trip and // (2) keeps the round-trip output of the changes up to date using var jsonFile = File.OpenRead(GetJsonFilePath("ChangeDeserializationRegressionData.legacy.verified.txt")); - var changes = JsonSerializer.Deserialize>(jsonFile, Options); + var changes = JsonSerializer.Deserialize>(jsonFile, HarmonyJsonOptions); changes.Should().NotBeNullOrEmpty().And.NotContainNulls(); changes.SelectMany(c => new[] { c.Input, c.Output }) .Should().NotContainNulls() @@ -138,9 +138,9 @@ public async Task RegressionDataUpToDate() legacyJson.Should().NotBeNullOrWhiteSpace(); var legacyOutputJson = ToNormalizedIndentedJsonString(legacyJsonNode[nameof(LegacyChangeRecord.Output)]!); legacyOutputJson.Should().NotBeNullOrWhiteSpace(); - var change = JsonSerializer.Deserialize(legacyJson, Options); + var change = JsonSerializer.Deserialize(legacyJson, HarmonyJsonOptions); change.Should().NotBeNull(); - var newLegacyOutputJson = JsonSerializer.Serialize(change, OptionsIndented); + var newLegacyOutputJson = JsonSerializer.Serialize(change, IndentedHarmonyJsonOptions); if (legacyOutputJson != newLegacyOutputJson) { //the legacy change no longer round-trips to the same output, so we should verify the new output @@ -155,10 +155,10 @@ public async Task RegressionDataUpToDate() latestJsonNode.Should().NotBeNull(); var latestJson = ToNormalizedIndentedJsonString(latestJsonNode); latestJson.Should().NotBeNullOrWhiteSpace(); - var change = JsonSerializer.Deserialize(latestJsonNode, Options); + var change = JsonSerializer.Deserialize(latestJsonNode, HarmonyJsonOptions); change.Should().NotBeNull(); seenChangeTypes.Add(change.GetType()); - var newLatestJson = JsonSerializer.Serialize(change, OptionsIndented); + var newLatestJson = JsonSerializer.Serialize(change, IndentedHarmonyJsonOptions); if (latestJson != newLatestJson) { @@ -177,7 +177,7 @@ public async Task RegressionDataUpToDate() // to generate and insert them manually. We can remove this if it's too noisy. foreach (var generatedChange in GeneratedChangesForType(change.GetType())) { - var serialized = JsonSerializer.Serialize(generatedChange, OptionsIndented); + var serialized = JsonSerializer.Serialize(generatedChange, IndentedHarmonyJsonOptions); newLatestJsonArray.Add(JsonNode.Parse(serialized)); } } @@ -194,7 +194,7 @@ public async Task RegressionDataUpToDate() { foreach (var generatedChange in GeneratedChangesForType(changeType)) { - var serialized = JsonSerializer.Serialize(generatedChange, OptionsIndented); + var serialized = JsonSerializer.Serialize(generatedChange, IndentedHarmonyJsonOptions); newLatestJsonArray.Add(JsonNode.Parse(serialized)); } } @@ -215,6 +215,6 @@ await Task.WhenAll( public static void GenerateNewJsonFile() { using var jsonFile = File.Open(GetJsonFilePath("NewJson.json"), FileMode.Create); - JsonSerializer.Serialize(jsonFile, GeneratedChanges(), OptionsIndented); + JsonSerializer.Serialize(jsonFile, GeneratedChanges(), IndentedHarmonyJsonOptions); } } diff --git a/backend/FwLite/LcmCrdt.Tests/Data/BaseSerializationTest.cs b/backend/FwLite/LcmCrdt.Tests/Data/BaseSerializationTest.cs index 219101ee80..1d44312e4f 100644 --- a/backend/FwLite/LcmCrdt.Tests/Data/BaseSerializationTest.cs +++ b/backend/FwLite/LcmCrdt.Tests/Data/BaseSerializationTest.cs @@ -12,15 +12,11 @@ namespace LcmCrdt.Tests.Data; public abstract class BaseSerializationTest { - protected static readonly Lazy LazyOptions = new(() => + protected static readonly JsonSerializerOptions HarmonyJsonOptions = new(TestJsonOptions.Harmony()) { - var options = TestJsonOptions.Harmony(); - options.ReadCommentHandling = JsonCommentHandling.Skip; - return options; - }); - - protected static readonly JsonSerializerOptions Options = LazyOptions.Value; - protected static readonly JsonSerializerOptions OptionsIndented = new(Options) + ReadCommentHandling = JsonCommentHandling.Skip, + }; + protected static readonly JsonSerializerOptions IndentedHarmonyJsonOptions = new(HarmonyJsonOptions) { WriteIndented = true, }; @@ -72,13 +68,14 @@ protected static string GetJsonFilePath(string name, [CallerFilePath] string sou name); } + private static readonly JsonSerializerOptions RegressionJsonOptions = new() + { + WriteIndented = true, + Encoder = HarmonyJsonOptions.Encoder, + }; protected static string SerializeRegressionData(JsonArray jsonArray) { - return JsonSerializer.Serialize(jsonArray, new JsonSerializerOptions - { - WriteIndented = true, - Encoder = Options.Encoder, - }) + return JsonSerializer.Serialize(jsonArray, RegressionJsonOptions) // The "+" in DateTimeOffsets does not get escaped by our standard crdt serializer, // but it does here. Presumably, because it's reading it as a string and not a DateTimeOffset .Replace("\\u002B", "+"); @@ -87,7 +84,7 @@ protected static string SerializeRegressionData(JsonArray jsonArray) private static readonly JsonWriterOptions GenericJsonWriterOptions = new() { Indented = true, - Encoder = Options.Encoder, + Encoder = HarmonyJsonOptions.Encoder, }; protected static string ToNormalizedIndentedJsonString(JsonNode element) diff --git a/backend/FwLite/LcmCrdt.Tests/Data/MigrationTests.cs b/backend/FwLite/LcmCrdt.Tests/Data/MigrationTests.cs index 51e31b07ee..6db969bab6 100644 --- a/backend/FwLite/LcmCrdt.Tests/Data/MigrationTests.cs +++ b/backend/FwLite/LcmCrdt.Tests/Data/MigrationTests.cs @@ -14,6 +14,10 @@ namespace LcmCrdt.Tests.Data; public class MigrationTests : IAsyncLifetime { private readonly RegressionTestHelper _helper = new("MigrationTest"); + private static readonly JsonSerializerOptions IndentedHarmonyJsonOptions = new(TestJsonOptions.Harmony()) + { + WriteIndented = true + }; [ModuleInitializer] internal static void Init() @@ -56,11 +60,6 @@ public async Task VerifyAfterMigrationFromScriptedDb(RegressionTestHelper.Regres await _helper.InitializeAsync(regressionVersion); var api = _helper.Services.GetRequiredService(); var crdtConfig = _helper.Services.GetRequiredService>().Value; - // todo use TestJsonOptions instead - var jsonSerializerOptions = new JsonSerializerOptions(crdtConfig.JsonSerializerOptions) - { - WriteIndented = true - }; await using var dbContext = await _helper.Services.GetRequiredService().CreateDbContextAsync(); var snapshots = await dbContext.Snapshots.AsNoTracking() @@ -86,9 +85,9 @@ public async Task VerifyAfterMigrationFromScriptedDb(RegressionTestHelper.Regres .ToArray(); var project = await TakeProjectSnapshot(api); - var snapshotsJson = JsonSerializer.Serialize(snapshots, jsonSerializerOptions); - var changesJson = JsonSerializer.Serialize(changes, jsonSerializerOptions); - var projectJson = JsonSerializer.Serialize(project, jsonSerializerOptions); + var snapshotsJson = JsonSerializer.Serialize(snapshots, IndentedHarmonyJsonOptions); + var changesJson = JsonSerializer.Serialize(changes, IndentedHarmonyJsonOptions); + var projectJson = JsonSerializer.Serialize(project, IndentedHarmonyJsonOptions); await Task.WhenAll( Verify(snapshotsJson) @@ -112,11 +111,6 @@ public async Task VerifyRegeneratedSnapshotsAfterMigrationFromScriptedDb(Regress await _helper.InitializeAsync(regressionVersion); var api = _helper.Services.GetRequiredService(); var crdtConfig = _helper.Services.GetRequiredService>().Value; - // todo use TestJsonOptions instead - var jsonSerializerOptions = new JsonSerializerOptions(crdtConfig.JsonSerializerOptions) - { - WriteIndented = true - }; await using var dbContext = await _helper.Services.GetRequiredService().CreateDbContextAsync(); await using var dataModel = _helper.Services.GetRequiredService(); @@ -142,7 +136,7 @@ public async Task VerifyRegeneratedSnapshotsAfterMigrationFromScriptedDb(Regress // this happens to result in null properties being omitted, which is probably fine // strangely VerifyJson(string).UseStrictJson() keeps null properties, but omits empty arrays and objects, which seems bizarre - var snapshotsJson = JsonSerializer.SerializeToDocument(latestSnapshots, jsonSerializerOptions); + var snapshotsJson = JsonSerializer.SerializeToDocument(latestSnapshots, IndentedHarmonyJsonOptions); // it would be nice to only scrub the snapshot Guid, because those are the only ones that should be different, // but Verify doesn't have a way to do that, so we just let it scrub all of them diff --git a/backend/FwLite/LcmCrdt.Tests/Data/SnapshotDeserializationTests.cs b/backend/FwLite/LcmCrdt.Tests/Data/SnapshotDeserializationTests.cs index 343242bb0a..cd429f9330 100644 --- a/backend/FwLite/LcmCrdt.Tests/Data/SnapshotDeserializationTests.cs +++ b/backend/FwLite/LcmCrdt.Tests/Data/SnapshotDeserializationTests.cs @@ -43,7 +43,7 @@ public void CanDeserializeProjectDumpRegressionData() //nothing should ever be removed from this file //this file represents projects which already have snapshots and changes applied, we want to ensure that we don't break anything. using var jsonFile = File.OpenRead(GetJsonFilePath("SnapshotDeserializationRegressionData.ProjectDump.1.json")); - var snapshots = JsonSerializer.Deserialize>(jsonFile, Options); + var snapshots = JsonSerializer.Deserialize>(jsonFile, HarmonyJsonOptions); snapshots.Should().NotBeNullOrEmpty().And.NotContainNulls(); } @@ -56,7 +56,7 @@ public void CanDeserializeLatestRegressionData() //or when it finds a snapshot type that isn't represented //this file was initialized with a small selection of snapshots from a project dump using var jsonFile = File.OpenRead(GetJsonFilePath("SnapshotDeserializationRegressionData.latest.verified.txt")); - var snapshots = JsonSerializer.Deserialize>(jsonFile, Options); + var snapshots = JsonSerializer.Deserialize>(jsonFile, HarmonyJsonOptions); snapshots.Should().NotBeNullOrEmpty().And.NotContainNulls(); //ensure that all snapshot types are represented and none should be removed from AllObjectTypes @@ -83,7 +83,7 @@ public void CanDeserializeLegacyRegressionData() // when it detects that they don't stably round-trip and // (2) keeps the round-trip output of the snapshots up to date using var jsonFile = File.OpenRead(GetJsonFilePath("SnapshotDeserializationRegressionData.legacy.verified.txt")); - var snapshots = JsonSerializer.Deserialize>(jsonFile, Options); + var snapshots = JsonSerializer.Deserialize>(jsonFile, HarmonyJsonOptions); snapshots.Should().NotBeNullOrEmpty().And.NotContainNulls(); snapshots.SelectMany(c => new[] { c.Input, c.Output }) .Should().NotContainNulls() @@ -105,9 +105,9 @@ public async Task RegressionDataUpToDate() legacyJson.Should().NotBeNullOrWhiteSpace(); var legacyOutputJson = ToNormalizedIndentedJsonString(legacyJsonNode[nameof(LegacySnapshotRecord.Output)]!); legacyOutputJson.Should().NotBeNullOrWhiteSpace(); - var snapshot = JsonSerializer.Deserialize(legacyJson, Options); + var snapshot = JsonSerializer.Deserialize(legacyJson, HarmonyJsonOptions); snapshot.Should().NotBeNull(); - var newLegacyOutputJson = JsonSerializer.Serialize(snapshot, OptionsIndented); + var newLegacyOutputJson = JsonSerializer.Serialize(snapshot, IndentedHarmonyJsonOptions); if (legacyOutputJson != newLegacyOutputJson) { //the legacy snapshot no longer round-trips to the same output, so we should verify the new output @@ -122,10 +122,10 @@ public async Task RegressionDataUpToDate() latestJsonNode.Should().NotBeNull(); var latestJson = ToNormalizedIndentedJsonString(latestJsonNode); latestJson.Should().NotBeNullOrWhiteSpace(); - var snapshot = JsonSerializer.Deserialize(latestJsonNode, Options); + var snapshot = JsonSerializer.Deserialize(latestJsonNode, HarmonyJsonOptions); snapshot.Should().NotBeNull(); seenObjectTypes.Add(snapshot.DbObject.GetType()); - var newLatestJson = JsonSerializer.Serialize(snapshot, OptionsIndented); + var newLatestJson = JsonSerializer.Serialize(snapshot, IndentedHarmonyJsonOptions); if (latestJson != newLatestJson) { @@ -143,7 +143,7 @@ public async Task RegressionDataUpToDate() // Anyhow, it's much easier for a dev to remove unwanted snapshots than // to generate and insert them manually. We can remove this if it's too noisy. var generatedSnapshot = GenerateSnapshotForType(snapshot.DbObject.GetType()); - var serialized = JsonSerializer.Serialize(generatedSnapshot, OptionsIndented); + var serialized = JsonSerializer.Serialize(generatedSnapshot, IndentedHarmonyJsonOptions); newLatestJsonArray.Add(JsonNode.Parse(serialized)); } else @@ -158,7 +158,7 @@ public async Task RegressionDataUpToDate() .Where(snapshotType => !seenObjectTypes.Contains(snapshotType))) { var generatedSnapshot = GenerateSnapshotForType(snapshotType); - var serialized = JsonSerializer.Serialize(generatedSnapshot, OptionsIndented); + var serialized = JsonSerializer.Serialize(generatedSnapshot, IndentedHarmonyJsonOptions); newLatestJsonArray.Add(JsonNode.Parse(serialized)); } diff --git a/backend/FwLite/MiniLcm.Tests/RichText/ColorJsonConverterTests.cs b/backend/FwLite/MiniLcm.Tests/RichText/ColorJsonConverterTests.cs index 708549e253..7ef9dd0347 100644 --- a/backend/FwLite/MiniLcm.Tests/RichText/ColorJsonConverterTests.cs +++ b/backend/FwLite/MiniLcm.Tests/RichText/ColorJsonConverterTests.cs @@ -6,7 +6,7 @@ namespace MiniLcm.Tests.RichText; public class ColorJsonConverterTests { - private JsonSerializerOptions options = new JsonSerializerOptions(JsonSerializerDefaults.General) + private static readonly JsonSerializerOptions ColorJsonOptions = new(JsonSerializerDefaults.General) { Converters = { new ColorJsonConverter() } }; @@ -15,13 +15,13 @@ public class ColorJsonConverterTests public void WritesTransparentToHexA() { Color? color = Color.Transparent; - JsonSerializer.Serialize(color, options).Should().Be("\"#00000000\""); + JsonSerializer.Serialize(color, ColorJsonOptions).Should().Be("\"#00000000\""); } [Fact] public void ReadsTransparentToAnUnNamedColor() { - var color = JsonSerializer.Deserialize("\"#00000000\"", options); + var color = JsonSerializer.Deserialize("\"#00000000\"", ColorJsonOptions); color.Should().NotBeNull(); color.Value.IsKnownColor.Should().BeFalse(); color.Value.IsNamedColor.Should().BeFalse();