290 lines
10 KiB
C#
290 lines
10 KiB
C#
using AyCode.Core.Extensions;
|
|
using AyCode.Core.Serializers.Binaries;
|
|
using static AyCode.Core.Tests.Serialization.AcSerializerModels;
|
|
using static AyCode.Core.Tests.Serialization.AcSerializerTestHelper;
|
|
|
|
namespace AyCode.Core.Tests.Serialization;
|
|
|
|
/// <summary>
|
|
/// Tests for string interning functionality.
|
|
/// </summary>
|
|
[TestClass]
|
|
public class AcBinarySerializerStringInterningTests
|
|
{
|
|
[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 });
|
|
|
|
Assert.IsTrue(binaryWithInterning.Length < binaryWithoutInterning.Length,
|
|
$"With interning: {binaryWithInterning.Length}, Without: {binaryWithoutInterning.Length}");
|
|
|
|
var result1 = AcBinaryDeserializer.Deserialize<TestClassWithRepeatedStrings>(binaryWithInterning);
|
|
var result2 = AcBinaryDeserializer.Deserialize<TestClassWithRepeatedStrings>(binaryWithoutInterning);
|
|
|
|
Assert.AreEqual(obj.Field1, result1!.Field1);
|
|
Assert.AreEqual(obj.Field1, result2!.Field1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// REGRESSION TEST: Comprehensive string interning edge cases.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void StringInterning_PropertyNames_MustBeRegisteredDuringDeserialization()
|
|
{
|
|
var items = Enumerable.Range(0, 10).Select(i => new TestClassWithLongPropertyNames
|
|
{
|
|
FirstProperty = $"Value1_{i}",
|
|
SecondProperty = $"Value2_{i}",
|
|
ThirdProperty = $"Value3_{i}",
|
|
FourthProperty = $"Value4_{i}"
|
|
}).ToList();
|
|
|
|
var binary = items.ToBinary();
|
|
var result = binary.BinaryTo<List<TestClassWithLongPropertyNames>>();
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(10, result.Count);
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
Assert.AreEqual($"Value1_{i}", result[i].FirstProperty);
|
|
Assert.AreEqual($"Value2_{i}", result[i].SecondProperty);
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void StringInterning_MixedShortAndLongStrings_HandledCorrectly()
|
|
{
|
|
var items = Enumerable.Range(0, 20).Select(i => new TestClassWithMixedStrings
|
|
{
|
|
Id = i,
|
|
ShortName = $"A{i % 3}",
|
|
LongName = $"LongName_{i % 5}",
|
|
Description = $"Description_value_{i % 7}",
|
|
Tag = i % 2 == 0 ? "AB" : "XY"
|
|
}).ToList();
|
|
|
|
var binary = items.ToBinary();
|
|
var result = binary.BinaryTo<List<TestClassWithMixedStrings>>();
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(20, result.Count);
|
|
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
Assert.AreEqual(i, result[i].Id);
|
|
Assert.AreEqual($"A{i % 3}", result[i].ShortName);
|
|
Assert.AreEqual($"LongName_{i % 5}", result[i].LongName);
|
|
Assert.AreEqual($"Description_value_{i % 7}", result[i].Description);
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void StringInterning_DeeplyNestedObjects_MaintainsCorrectOrder()
|
|
{
|
|
var root = new TestNestedStructure
|
|
{
|
|
RootName = "RootObject",
|
|
Level1Items = Enumerable.Range(0, 5).Select(i => new TestLevel1
|
|
{
|
|
Level1Name = $"Level1_{i}",
|
|
Level2Items = Enumerable.Range(0, 3).Select(j => new TestLevel2
|
|
{
|
|
Level2Name = $"Level2_{i}_{j}",
|
|
Value = $"Value_{i * 3 + j}"
|
|
}).ToList()
|
|
}).ToList()
|
|
};
|
|
|
|
var binary = root.ToBinary();
|
|
var result = binary.BinaryTo<TestNestedStructure>();
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual("RootObject", result.RootName);
|
|
Assert.AreEqual(5, result.Level1Items.Count);
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
Assert.AreEqual($"Level1_{i}", result.Level1Items[i].Level1Name);
|
|
Assert.AreEqual(3, result.Level1Items[i].Level2Items.Count);
|
|
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
Assert.AreEqual($"Level2_{i}_{j}", result.Level1Items[i].Level2Items[j].Level2Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void StringInterning_RepeatedPropertyValuesAcrossObjects_UsesReferences()
|
|
{
|
|
var items = Enumerable.Range(0, 50).Select(i => new TestClassWithRepeatedValues
|
|
{
|
|
Id = i,
|
|
Status = i % 3 == 0 ? "Pending" : i % 3 == 1 ? "Processing" : "Completed",
|
|
Category = i % 5 == 0 ? "CategoryA" : i % 5 == 1 ? "CategoryB" : "CategoryC",
|
|
Priority = i % 2 == 0 ? "High" : "Low_Priority_Value"
|
|
}).ToList();
|
|
|
|
var binary = items.ToBinary();
|
|
var result = binary.BinaryTo<List<TestClassWithRepeatedValues>>();
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(50, result.Count);
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
Assert.AreEqual(i, result[i].Id);
|
|
var expectedStatus = i % 3 == 0 ? "Pending" : i % 3 == 1 ? "Processing" : "Completed";
|
|
Assert.AreEqual(expectedStatus, result[i].Status, $"Status mismatch at index {i}");
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void StringInterning_ManyUniqueStringsFollowedByRepeats_CorrectIndexLookup()
|
|
{
|
|
var items = new List<TestClassWithNameValue>();
|
|
|
|
for (int i = 0; i < 30; i++)
|
|
{
|
|
items.Add(new TestClassWithNameValue
|
|
{
|
|
Name = $"UniqueName_{i:D4}",
|
|
Value = $"UniqueValue_{i:D4}"
|
|
});
|
|
}
|
|
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
items.Add(new TestClassWithNameValue
|
|
{
|
|
Name = $"UniqueName_{i % 10:D4}",
|
|
Value = $"UniqueValue_{(i + 10) % 30:D4}"
|
|
});
|
|
}
|
|
|
|
var binary = items.ToBinary();
|
|
var result = binary.BinaryTo<List<TestClassWithNameValue>>();
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(50, result.Count);
|
|
|
|
for (int i = 0; i < 30; i++)
|
|
{
|
|
Assert.AreEqual($"UniqueName_{i:D4}", result[i].Name, $"Name mismatch at index {i}");
|
|
Assert.AreEqual($"UniqueValue_{i:D4}", result[i].Value, $"Value mismatch at index {i}");
|
|
}
|
|
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
Assert.AreEqual($"UniqueName_{i % 10:D4}", result[30 + i].Name, $"Name mismatch at index {30 + i}");
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void StringInterning_EmptyAndNullStrings_DoNotAffectInternTable()
|
|
{
|
|
var items = new List<TestClassWithNullableStrings>();
|
|
|
|
for (int i = 0; i < 25; i++)
|
|
{
|
|
items.Add(new TestClassWithNullableStrings
|
|
{
|
|
Id = i,
|
|
RequiredName = $"Required_{i:D3}",
|
|
OptionalName = i % 3 == 0 ? null : i % 3 == 1 ? "" : $"Optional_{i:D3}",
|
|
Description = i % 2 == 0 ? $"Description_{i % 5:D3}" : null
|
|
});
|
|
}
|
|
|
|
var binary = items.ToBinary();
|
|
var result = binary.BinaryTo<List<TestClassWithNullableStrings>>();
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(25, result.Count);
|
|
|
|
for (int i = 0; i < 25; i++)
|
|
{
|
|
Assert.AreEqual(i, result[i].Id);
|
|
Assert.AreEqual($"Required_{i:D3}", result[i].RequiredName);
|
|
|
|
if (i % 3 == 0)
|
|
{
|
|
Assert.IsNull(result[i].OptionalName, $"OptionalName at index {i} should be null");
|
|
}
|
|
else if (i % 3 == 1)
|
|
{
|
|
Assert.IsTrue(string.IsNullOrEmpty(result[i].OptionalName),
|
|
$"OptionalName at index {i} should be null or empty, got: '{result[i].OptionalName}'");
|
|
}
|
|
else
|
|
{
|
|
Assert.AreEqual($"Optional_{i:D3}", result[i].OptionalName,
|
|
$"OptionalName at index {i} mismatch");
|
|
}
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void StringInterning_ProductionLikeCustomerDto_RoundTrip()
|
|
{
|
|
var customers = CreateCustomerLikeItems(25);
|
|
var binary = customers.ToBinary();
|
|
var result = binary.BinaryTo<List<TestCustomerLikeDto>>();
|
|
|
|
Assert.IsNotNull(result, "Result should not be null - deserialization failed");
|
|
Assert.AreEqual(25, result.Count);
|
|
|
|
for (int i = 0; i < 25; i++)
|
|
{
|
|
Assert.AreEqual(i, result[i].Id, $"Id mismatch at index {i}");
|
|
Assert.AreEqual($"FirstName_{i % 10}", result[i].FirstName, $"FirstName mismatch at index {i}");
|
|
Assert.AreEqual($"LastName_{i % 8}", result[i].LastName, $"LastName mismatch at index {i}");
|
|
Assert.AreEqual($"user{i}@example.com", result[i].Email, $"Email mismatch at index {i}");
|
|
Assert.AreEqual(4, result[i].Attributes.Count, $"Attributes count mismatch at index {i}");
|
|
Assert.AreEqual("DateOfReceipt", result[i].Attributes[0].Key);
|
|
Assert.AreEqual("Priority", result[i].Attributes[1].Key);
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void StringInterning_LargeListWithHighStringReuse_HandlesCorrectly()
|
|
{
|
|
const int itemCount = 150;
|
|
var items = Enumerable.Range(0, itemCount).Select(i => new TestHighReuseDto
|
|
{
|
|
Id = i,
|
|
CategoryCode = $"CAT_{i % 10:D2}",
|
|
StatusCode = $"STATUS_{i % 5:D2}",
|
|
TypeCode = $"TYPE_{i % 3:D2}",
|
|
PriorityCode = i % 2 == 0 ? "PRIORITY_HIGH" : "PRIORITY_LOW",
|
|
UniqueField = $"UNIQUE_{i:D4}"
|
|
}).ToList();
|
|
|
|
var binary = items.ToBinary();
|
|
var result = binary.BinaryTo<List<TestHighReuseDto>>();
|
|
|
|
Assert.IsNotNull(result, "Result should not be null");
|
|
Assert.AreEqual(itemCount, result.Count, $"Expected {itemCount} items");
|
|
|
|
for (int i = 0; i < itemCount; i++)
|
|
{
|
|
Assert.AreEqual(i, result[i].Id, $"Id mismatch at {i}");
|
|
Assert.AreEqual($"CAT_{i % 10:D2}", result[i].CategoryCode, $"CategoryCode mismatch at {i}");
|
|
Assert.AreEqual($"STATUS_{i % 5:D2}", result[i].StatusCode, $"StatusCode mismatch at {i}");
|
|
Assert.AreEqual($"TYPE_{i % 3:D2}", result[i].TypeCode, $"TypeCode mismatch at {i}");
|
|
Assert.AreEqual($"UNIQUE_{i:D4}", result[i].UniqueField, $"UniqueField mismatch at {i}");
|
|
}
|
|
}
|
|
}
|