AyCode.Core/AyCode.Core.Tests/Serialization/AcBinarySerializerTests.cs

425 lines
13 KiB
C#

using AyCode.Core.Extensions;
using AyCode.Core.Tests.TestModels;
namespace AyCode.Core.Tests.Serialization;
[TestClass]
public class AcBinarySerializerTests
{
#region Basic Serialization Tests
[TestMethod]
public void Serialize_Null_ReturnsSingleNullByte()
{
var result = AcBinarySerializer.Serialize<object?>(null);
Assert.AreEqual(1, result.Length);
Assert.AreEqual((byte)32, result[0]); // BinaryTypeCode.Null = 32
}
[TestMethod]
public void Serialize_Int32_RoundTrip()
{
var value = 12345;
var binary = AcBinarySerializer.Serialize(value);
var result = AcBinaryDeserializer.Deserialize<int>(binary);
Assert.AreEqual(value, result);
}
[TestMethod]
public void Serialize_Int64_RoundTrip()
{
var value = 123456789012345L;
var binary = AcBinarySerializer.Serialize(value);
var result = AcBinaryDeserializer.Deserialize<long>(binary);
Assert.AreEqual(value, result);
}
[TestMethod]
public void Serialize_Double_RoundTrip()
{
var value = 3.14159265358979;
var binary = AcBinarySerializer.Serialize(value);
var result = AcBinaryDeserializer.Deserialize<double>(binary);
Assert.AreEqual(value, result);
}
[TestMethod]
public void Serialize_String_RoundTrip()
{
var value = "Hello, Binary World!";
var binary = AcBinarySerializer.Serialize(value);
var result = AcBinaryDeserializer.Deserialize<string>(binary);
Assert.AreEqual(value, result);
}
[TestMethod]
public void Serialize_Boolean_RoundTrip()
{
var trueResult = AcBinaryDeserializer.Deserialize<bool>(AcBinarySerializer.Serialize(true));
var falseResult = AcBinaryDeserializer.Deserialize<bool>(AcBinarySerializer.Serialize(false));
Assert.IsTrue(trueResult);
Assert.IsFalse(falseResult);
}
[TestMethod]
public void Serialize_DateTime_RoundTrip()
{
var value = new DateTime(2024, 12, 25, 10, 30, 45, DateTimeKind.Utc);
var binary = AcBinarySerializer.Serialize(value);
var result = AcBinaryDeserializer.Deserialize<DateTime>(binary);
Assert.AreEqual(value, result);
}
[TestMethod]
public void Serialize_Guid_RoundTrip()
{
var value = Guid.NewGuid();
var binary = AcBinarySerializer.Serialize(value);
var result = AcBinaryDeserializer.Deserialize<Guid>(binary);
Assert.AreEqual(value, result);
}
[TestMethod]
public void Serialize_Decimal_RoundTrip()
{
var value = 123456.789012m;
var binary = AcBinarySerializer.Serialize(value);
var result = AcBinaryDeserializer.Deserialize<decimal>(binary);
Assert.AreEqual(value, result);
}
[TestMethod]
public void Serialize_TimeSpan_RoundTrip()
{
var value = TimeSpan.FromHours(2.5);
var binary = AcBinarySerializer.Serialize(value);
var result = AcBinaryDeserializer.Deserialize<TimeSpan>(binary);
Assert.AreEqual(value, result);
}
[TestMethod]
public void Serialize_DateTimeOffset_RoundTrip()
{
var value = new DateTimeOffset(2024, 12, 25, 10, 30, 45, TimeSpan.FromHours(2));
var binary = AcBinarySerializer.Serialize(value);
var result = AcBinaryDeserializer.Deserialize<DateTimeOffset>(binary);
// Compare UTC ticks and offset separately since we store UTC ticks
Assert.AreEqual(value.UtcTicks, result.UtcTicks);
Assert.AreEqual(value.Offset, result.Offset);
}
#endregion
#region Object Serialization Tests
[TestMethod]
public void Serialize_SimpleObject_RoundTrip()
{
var obj = new TestSimpleClass
{
Id = 42,
Name = "Test Object",
Value = 3.14,
IsActive = true
};
var binary = obj.ToBinary();
var result = binary.BinaryTo<TestSimpleClass>();
Assert.IsNotNull(result);
Assert.AreEqual(obj.Id, result.Id);
Assert.AreEqual(obj.Name, result.Name);
Assert.AreEqual(obj.Value, result.Value);
Assert.AreEqual(obj.IsActive, result.IsActive);
}
[TestMethod]
public void Serialize_NestedObject_RoundTrip()
{
var obj = new TestNestedClass
{
Id = 1,
Name = "Parent",
Child = new TestSimpleClass
{
Id = 2,
Name = "Child",
Value = 2.5,
IsActive = true
}
};
var binary = obj.ToBinary();
var result = binary.BinaryTo<TestNestedClass>();
Assert.IsNotNull(result);
Assert.AreEqual(obj.Id, result.Id);
Assert.AreEqual(obj.Name, result.Name);
Assert.IsNotNull(result.Child);
Assert.AreEqual(obj.Child.Id, result.Child.Id);
Assert.AreEqual(obj.Child.Name, result.Child.Name);
}
[TestMethod]
public void Serialize_List_RoundTrip()
{
var list = new List<int> { 1, 2, 3, 4, 5 };
var binary = list.ToBinary();
var result = binary.BinaryTo<List<int>>();
Assert.IsNotNull(result);
CollectionAssert.AreEqual(list, result);
}
[TestMethod]
public void Serialize_ObjectWithList_RoundTrip()
{
var obj = new TestClassWithList
{
Id = 1,
Items = new List<string> { "Item1", "Item2", "Item3" }
};
var binary = obj.ToBinary();
var result = binary.BinaryTo<TestClassWithList>();
Assert.IsNotNull(result);
Assert.AreEqual(obj.Id, result.Id);
Assert.IsNotNull(result.Items);
CollectionAssert.AreEqual(obj.Items, result.Items);
}
[TestMethod]
public void Serialize_Dictionary_RoundTrip()
{
var dict = new Dictionary<string, int>
{
["one"] = 1,
["two"] = 2,
["three"] = 3
};
var binary = dict.ToBinary();
var result = binary.BinaryTo<Dictionary<string, int>>();
Assert.IsNotNull(result);
Assert.AreEqual(dict.Count, result.Count);
foreach (var kvp in dict)
{
Assert.IsTrue(result.ContainsKey(kvp.Key));
Assert.AreEqual(kvp.Value, result[kvp.Key]);
}
}
#endregion
#region Populate Tests
[TestMethod]
public void Populate_UpdatesExistingObject()
{
var target = new TestSimpleClass { Id = 0, Name = "Original" };
var source = new TestSimpleClass { Id = 42, Name = "Updated", Value = 3.14 };
var binary = source.ToBinary();
binary.BinaryTo(target);
Assert.AreEqual(42, target.Id);
Assert.AreEqual("Updated", target.Name);
Assert.AreEqual(3.14, target.Value);
}
[TestMethod]
public void PopulateMerge_MergesNestedObjects()
{
var target = new TestNestedClass
{
Id = 1,
Name = "Original",
Child = new TestSimpleClass { Id = 10, Name = "OriginalChild", Value = 1.0 }
};
var source = new TestNestedClass
{
Id = 2,
Name = "Updated",
Child = new TestSimpleClass { Id = 20, Name = "UpdatedChild", Value = 2.0 }
};
var binary = source.ToBinary();
binary.BinaryToMerge(target);
Assert.AreEqual(2, target.Id);
Assert.AreEqual("Updated", target.Name);
Assert.IsNotNull(target.Child);
// Child object should be merged, not replaced
Assert.AreEqual(20, target.Child.Id);
Assert.AreEqual("UpdatedChild", target.Child.Name);
}
#endregion
#region String Interning Tests
[TestMethod]
public void Serialize_RepeatedStrings_UsesInterning()
{
var obj = new TestClassWithRepeatedStrings
{
Field1 = "Repeated",
Field2 = "Repeated",
Field3 = "Repeated",
Field4 = "Unique"
};
var binaryWithInterning = AcBinarySerializer.Serialize(obj, AcBinarySerializerOptions.Default);
var binaryWithoutInterning = AcBinarySerializer.Serialize(obj,
new AcBinarySerializerOptions { UseStringInterning = false });
// With interning should be smaller
Assert.IsTrue(binaryWithInterning.Length < binaryWithoutInterning.Length,
$"With interning: {binaryWithInterning.Length}, Without: {binaryWithoutInterning.Length}");
// Both should deserialize correctly
var result1 = AcBinaryDeserializer.Deserialize<TestClassWithRepeatedStrings>(binaryWithInterning);
var result2 = AcBinaryDeserializer.Deserialize<TestClassWithRepeatedStrings>(binaryWithoutInterning);
Assert.AreEqual(obj.Field1, result1!.Field1);
Assert.AreEqual(obj.Field1, result2!.Field1);
}
#endregion
#region Size Comparison Tests
[TestMethod]
public void Serialize_IsSmallerThanJson()
{
var obj = TestDataFactory.CreateBenchmarkOrder(itemCount: 3, palletsPerItem: 2, measurementsPerPallet: 2, pointsPerMeasurement: 3);
var jsonBytes = System.Text.Encoding.UTF8.GetBytes(obj.ToJson());
var binaryBytes = obj.ToBinary();
Console.WriteLine($"JSON size: {jsonBytes.Length} bytes");
Console.WriteLine($"Binary size: {binaryBytes.Length} bytes");
Console.WriteLine($"Ratio: {(double)binaryBytes.Length / jsonBytes.Length:P2}");
Assert.IsTrue(binaryBytes.Length < jsonBytes.Length,
$"Binary ({binaryBytes.Length}) should be smaller than JSON ({jsonBytes.Length})");
// Verify roundtrip works
var result = binaryBytes.BinaryTo<TestOrder>();
Assert.IsNotNull(result);
Assert.AreEqual(obj.Id, result.Id);
Assert.AreEqual(obj.Items.Count, result.Items.Count);
}
#endregion
#region Extension Method Tests
[TestMethod]
public void BinaryCloneTo_CreatesDeepCopy()
{
var original = new TestNestedClass
{
Id = 1,
Name = "Original",
Child = new TestSimpleClass { Id = 2, Name = "Child" }
};
var clone = original.BinaryCloneTo();
Assert.IsNotNull(clone);
Assert.AreNotSame(original, clone);
Assert.AreNotSame(original.Child, clone.Child);
Assert.AreEqual(original.Id, clone.Id);
Assert.AreEqual(original.Child.Id, clone.Child!.Id);
// Modify clone, original should be unchanged
clone.Id = 999;
clone.Child.Id = 888;
Assert.AreEqual(1, original.Id);
Assert.AreEqual(2, original.Child.Id);
}
#endregion
#region Test Models
private class TestSimpleClass
{
public int Id { get; set; }
public string Name { get; set; } = "";
public double Value { get; set; }
public bool IsActive { get; set; }
}
private class TestNestedClass
{
public int Id { get; set; }
public string Name { get; set; } = "";
public TestSimpleClass? Child { get; set; }
}
private class TestClassWithList
{
public int Id { get; set; }
public List<string> Items { get; set; } = new();
}
private class TestClassWithRepeatedStrings
{
public string Field1 { get; set; } = "";
public string Field2 { get; set; } = "";
public string Field3 { get; set; } = "";
public string Field4 { get; set; } = "";
}
#endregion
#region Benchmark Order Tests
[TestMethod]
public void Serialize_BenchmarkOrder_RoundTrip()
{
// This is the exact same data that causes stack overflow in benchmarks
var order = TestDataFactory.CreateBenchmarkOrder(
itemCount: 3,
palletsPerItem: 2,
measurementsPerPallet: 2,
pointsPerMeasurement: 5);
// Should not throw stack overflow
var binary = AcBinarySerializer.Serialize(order);
Assert.IsTrue(binary.Length > 0, "Binary data should not be empty");
var result = AcBinaryDeserializer.Deserialize<TestOrder>(binary);
Assert.IsNotNull(result);
Assert.AreEqual(order.Id, result.Id);
Assert.AreEqual(order.OrderNumber, result.OrderNumber);
Assert.AreEqual(order.Items.Count, result.Items.Count);
}
[TestMethod]
public void Serialize_BenchmarkOrder_SmallData_RoundTrip()
{
// Smaller test to isolate the issue
var order = TestDataFactory.CreateBenchmarkOrder(
itemCount: 1,
palletsPerItem: 1,
measurementsPerPallet: 1,
pointsPerMeasurement: 1);
var binary = AcBinarySerializer.Serialize(order);
Assert.IsTrue(binary.Length > 0);
var result = AcBinaryDeserializer.Deserialize<TestOrder>(binary);
Assert.IsNotNull(result);
Assert.AreEqual(order.Id, result.Id);
}
#endregion
}