Skip to content

Commit

Permalink
Refactor serialization and deserialization test suite
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Ygg01 committed Jan 22, 2024
1 parent ea5d022 commit 2f25393
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 26 deletions.
45 changes: 26 additions & 19 deletions Linguini.Serialization.Test/SerializeAndDeserializeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<object> 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));
Expand All @@ -37,7 +47,20 @@ public static IEnumerable<object> 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<Attribute>()
{
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"))
Expand All @@ -47,22 +70,6 @@ public static IEnumerable<object> 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>(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()
Expand Down
13 changes: 12 additions & 1 deletion Linguini.Serialization/Converters/JunkSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,18 @@ public class JunkSerializer : JsonConverter<Junk>
{
public override Junk Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
return ProcessJunk(JsonSerializer.Deserialize<JsonElement>(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)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using Linguini.Syntax.Ast;
Expand Down
35 changes: 33 additions & 2 deletions Linguini.Serialization/Converters/MessageSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,46 @@
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
{
public class MessageSerializer : JsonConverter<AstMessage>
{
public override AstMessage Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
var el = JsonSerializer.Deserialize<JsonElement>(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<Attribute>();
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<AstComment>(commentJson.GetRawText(), options);
}

if (el.TryGetProperty("attributes", out var attrsJson) && attrsJson.ValueKind == JsonValueKind.Array)
{
foreach (var attributeJson in attrsJson.EnumerateArray())
{
var attr = JsonSerializer.Deserialize<Attribute>(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)
Expand Down Expand Up @@ -48,4 +79,4 @@ public override void Write(Utf8JsonWriter writer, AstMessage msg, JsonSerializer
writer.WriteEndObject();
}
}
}
}
20 changes: 20 additions & 0 deletions Linguini.Syntax/Ast/Base.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@ public class Attribute : IEquatable<Attribute>
public readonly Identifier Id;
public readonly Pattern Value;

public static AttributeComparer Comparer = new();

public class AttributeComparer : IEqualityComparer<Attribute>
{
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;
Expand Down
53 changes: 50 additions & 3 deletions Linguini.Syntax/Ast/Entry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public Resource(List<IEntry> body, List<ParseError> errors)
}
}

public class AstMessage : IEntry
public class AstMessage : IEntry, IEquatable<AstMessage>
{
public readonly Identifier Id;
public readonly Pattern? Value;
Expand All @@ -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
Expand Down Expand Up @@ -129,20 +151,25 @@ public override int GetHashCode()
}
}

public class Junk : IEntry
public class Junk : IEntry, IEquatable<Junk>
{
public readonly ReadOnlyMemory<char> Content;

public Junk()
{
Content = ReadOnlyMemory<char>.Empty;
}

public Junk(ReadOnlyMemory<char> content)
{
Content = content;
}

public Junk(string content)
{
Content = content.AsMemory();
}

public string AsStr()
{
return Content.Span.ToString();
Expand All @@ -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();
}
}
}

0 comments on commit 2f25393

Please sign in to comment.