From 2f2539352288e4e269a51b3c186a0846cccff7d4 Mon Sep 17 00:00:00 2001 From: Ygg01 Date: Mon, 22 Jan 2024 22:36:03 +0100 Subject: [PATCH] Refactor serialization and deserialization test suite The serialization and deserialization test suite has been updated for clarity and performance. The process is now consolidated into a single method called `RoundTripTest()`. Additionally, equality implementations for `AstMessage` and `Junk` classes have been added, allowing for more reliable object comparison in tests. Implementations for processing `Junk` and `AstMessage` have been tweaked for correctness and better error reporting. --- .../SerializeAndDeserializeTest.cs | 45 +++++++++------- .../Converters/JunkSerializer.cs | 13 ++++- .../Converters/MessageReferenceSerializer.cs | 1 - .../Converters/MessageSerializer.cs | 35 +++++++++++- Linguini.Syntax/Ast/Base.cs | 20 +++++++ Linguini.Syntax/Ast/Entry.cs | 53 +++++++++++++++++-- 6 files changed, 141 insertions(+), 26 deletions(-) diff --git a/Linguini.Serialization.Test/SerializeAndDeserializeTest.cs b/Linguini.Serialization.Test/SerializeAndDeserializeTest.cs index d9b8327..ac91a89 100644 --- a/Linguini.Serialization.Test/SerializeAndDeserializeTest.cs +++ b/Linguini.Serialization.Test/SerializeAndDeserializeTest.cs @@ -16,18 +16,28 @@ public class SerializeAndDeserializeTest [Test] [TestCaseSource(nameof(AstExamples))] [Parallelizable] - public void SerializeDeserializeTest(object x) + public void RoundTripTest(object x) { - SerializeAndDeserializeTest.SerializeDeserializeTest(x); + // Serialize the object to JSON string. + var jsonString = JsonSerializer.Serialize(x, Options); + + // Deserialize the JSON string back into an object. + Debug.Assert(x != null, nameof(x) + " != null"); + var deserializedObject = JsonSerializer.Deserialize(jsonString, x.GetType(), Options); + + // Now you have a 'deserializedObject' which should be equivalent to the original 'expected' object. + Assert.That(deserializedObject, Is.Not.Null); + Assert.That(deserializedObject, Is.EqualTo(x)); } public static IEnumerable AstExamples() { + yield return new Attribute("desc", new PatternBuilder("description")); yield return new CallArgumentsBuilder() .AddPositionalArg(InlineExpressionBuilder.CreateMessageReference("x")) .AddNamedArg("y", 3) .Build(); - yield return new Attribute("desc", new PatternBuilder("description")); + yield return new AstComment(CommentLevel.Comment, new() { "test".AsMemory() }); yield return new DynamicReference("dyn", "attr", new CallArgumentsBuilder() .AddPositionalArg(InlineExpressionBuilder.CreateMessageReference("x")) .AddNamedArg("y", 3)); @@ -37,7 +47,20 @@ public static IEnumerable AstExamples() .Build() ); yield return new Identifier("test"); + yield return new Junk("Test".AsMemory()); yield return new MessageReference("message", "attribute"); + yield return new AstMessage( + new Identifier("x"), + new PatternBuilder(3).Build(), + new List() + { + new("attr1", new PatternBuilder("value1")), + new("attr2", new PatternBuilder("value2")) + }, + new(CommentLevel.ResourceComment, new() + { + "test".AsMemory() + })); yield return new PatternBuilder("text ").AddMessage("x").AddText(" more text").Build(); yield return new SelectExpressionBuilder(new VariableReference("x")) .AddVariant("one", new PatternBuilder("select 1")) @@ -47,22 +70,6 @@ public static IEnumerable AstExamples() yield return new TermReference("x", "y"); yield return new VariableReference("x"); yield return new Variant(2.0f, new PatternBuilder(3)); - yield return new AstComment(CommentLevel.Comment, new() { "test".AsMemory() }); - yield return new Junk("Test".AsMemory()); - } - - private static void SerializeDeserializeTest(T expected) - { - // Serialize the object to JSON string. - var jsonString = JsonSerializer.Serialize(expected, Options); - - // Deserialize the JSON string back into an object. - Debug.Assert(expected != null, nameof(expected) + " != null"); - var deserializedObject = JsonSerializer.Deserialize(jsonString, expected.GetType(), Options); - - // Now you have a 'deserializedObject' which should be equivalent to the original 'expected' object. - Assert.That(deserializedObject, Is.Not.Null); - Assert.That(deserializedObject, Is.EqualTo(expected)); } private static readonly JsonSerializerOptions Options = new() diff --git a/Linguini.Serialization/Converters/JunkSerializer.cs b/Linguini.Serialization/Converters/JunkSerializer.cs index b9ece27..ef0702d 100644 --- a/Linguini.Serialization/Converters/JunkSerializer.cs +++ b/Linguini.Serialization/Converters/JunkSerializer.cs @@ -10,7 +10,18 @@ public class JunkSerializer : JsonConverter { public override Junk Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - throw new NotImplementedException(); + return ProcessJunk(JsonSerializer.Deserialize(ref reader, options), options); + } + + private Junk ProcessJunk(JsonElement el, JsonSerializerOptions options) + { + if (el.TryGetProperty("content", out var content)) + { + var str = content.GetString() ?? ""; + return new Junk(str); + } + + throw new JsonException("Junk must have content"); } public override void Write(Utf8JsonWriter writer, Junk value, JsonSerializerOptions options) diff --git a/Linguini.Serialization/Converters/MessageReferenceSerializer.cs b/Linguini.Serialization/Converters/MessageReferenceSerializer.cs index 50aaed6..e7d6b69 100644 --- a/Linguini.Serialization/Converters/MessageReferenceSerializer.cs +++ b/Linguini.Serialization/Converters/MessageReferenceSerializer.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; using Linguini.Syntax.Ast; diff --git a/Linguini.Serialization/Converters/MessageSerializer.cs b/Linguini.Serialization/Converters/MessageSerializer.cs index b4b7fb6..438c30c 100644 --- a/Linguini.Serialization/Converters/MessageSerializer.cs +++ b/Linguini.Serialization/Converters/MessageSerializer.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; using Linguini.Syntax.Ast; +using Attribute = Linguini.Syntax.Ast.Attribute; namespace Linguini.Serialization.Converters { @@ -9,7 +11,36 @@ public class MessageSerializer : JsonConverter { public override AstMessage Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - throw new NotImplementedException(); + var el = JsonSerializer.Deserialize(ref reader, options); + if (!el.TryGetProperty("id", out var jsonId) || + !IdentifierSerializer.TryGetIdentifier(jsonId, options, out var identifier)) + { + throw new JsonException("AstMessage must have at least `id` element"); + } + + Pattern? value = null; + AstComment? comment = null; + var attrs = new List(); + if (el.TryGetProperty("value", out var patternJson) && patternJson.ValueKind == JsonValueKind.Object) + { + PatternSerializer.TryReadPattern(patternJson, options, out value); + } + + if (el.TryGetProperty("comment", out var commentJson) && patternJson.ValueKind == JsonValueKind.Object) + { + comment = JsonSerializer.Deserialize(commentJson.GetRawText(), options); + } + + if (el.TryGetProperty("attributes", out var attrsJson) && attrsJson.ValueKind == JsonValueKind.Array) + { + foreach (var attributeJson in attrsJson.EnumerateArray()) + { + var attr = JsonSerializer.Deserialize(attributeJson.GetRawText(), options); + if (attr != null) attrs.Add(attr); + } + } + + return new AstMessage(identifier, value, attrs, comment); } public override void Write(Utf8JsonWriter writer, AstMessage msg, JsonSerializerOptions options) @@ -48,4 +79,4 @@ public override void Write(Utf8JsonWriter writer, AstMessage msg, JsonSerializer writer.WriteEndObject(); } } -} +} \ No newline at end of file diff --git a/Linguini.Syntax/Ast/Base.cs b/Linguini.Syntax/Ast/Base.cs index 2d6ca66..644de8b 100644 --- a/Linguini.Syntax/Ast/Base.cs +++ b/Linguini.Syntax/Ast/Base.cs @@ -13,6 +13,26 @@ public class Attribute : IEquatable public readonly Identifier Id; public readonly Pattern Value; + public static AttributeComparer Comparer = new(); + + public class AttributeComparer : IEqualityComparer + { + public bool Equals(Attribute? x, Attribute? y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + if (x.GetType() != y.GetType()) return false; + return Identifier.Comparator.Equals(x.Id, y.Id) && + x.Value.Equals(y.Value); + } + + public int GetHashCode(Attribute obj) + { + return HashCode.Combine(obj.Id, obj.Value); + } + } + public Attribute(Identifier id, Pattern value) { Id = id; diff --git a/Linguini.Syntax/Ast/Entry.cs b/Linguini.Syntax/Ast/Entry.cs index a91c23a..970787a 100644 --- a/Linguini.Syntax/Ast/Entry.cs +++ b/Linguini.Syntax/Ast/Entry.cs @@ -18,7 +18,7 @@ public Resource(List body, List errors) } } - public class AstMessage : IEntry + public class AstMessage : IEntry, IEquatable { public readonly Identifier Id; public readonly Pattern? Value; @@ -39,6 +39,28 @@ public string GetId() { return Id.ToString(); } + + public bool Equals(AstMessage? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Identifier.Comparator.Equals(Id, other.Id) && Equals(Value, other.Value) && + Attributes.SequenceEqual(other.Attributes, Attribute.Comparer) && + Equals(InternalComment, other.InternalComment); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((AstMessage)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Id, Value, Attributes, InternalComment); + } } public class AstTerm : IEntry @@ -129,7 +151,7 @@ public override int GetHashCode() } } - public class Junk : IEntry + public class Junk : IEntry, IEquatable { public readonly ReadOnlyMemory Content; @@ -137,12 +159,17 @@ public Junk() { Content = ReadOnlyMemory.Empty; } - + public Junk(ReadOnlyMemory content) { Content = content; } + public Junk(string content) + { + Content = content.AsMemory(); + } + public string AsStr() { return Content.Span.ToString(); @@ -152,5 +179,25 @@ public string GetId() { return Content.Span.ToString(); } + + public bool Equals(Junk? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Content.Span.SequenceEqual(other.Content.Span); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Junk)obj); + } + + public override int GetHashCode() + { + return Content.GetHashCode(); + } } } \ No newline at end of file