using System; using System.Collections.Generic; using System.Linq; using AyCode.Core.Extensions; using AyCode.Core.Serializers; using AyCode.Core.Serializers.Binaries; using AyCode.Core.Tests.TestModels; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace AyCode.Core.Tests.Serialization; /// /// Tests for IId-based reference handling in Binary serializer. /// Two scenarios: /// 1. Same instance referenced multiple times (object identity) /// 2. Different instances with same IId.Id (IId-based deduplication) /// /// Tests verify BOTH: /// - Serialized output uses ObjectRef (not redundant full objects) /// - Deserialized result maintains reference identity /// [TestClass] public class AcBinarySerializerIIdReferenceTests { #region Helper Methods /// /// Counts occurrences of ObjectRef in binary data. /// Uses BinaryTypeCode.ObjectRef constant to stay in sync with format changes. /// private static int CountObjectRefs(byte[] binary, bool writeBinaryToConsole = true) { if (writeBinaryToConsole) WriteBinaryToConsole(binary); var count = 0; for (var i = 0; i < binary.Length; i++) { if (binary[i] == BinaryTypeCode.ObjectRef) count++; } return count; } private static void WriteBinaryToConsole(byte[] binary) { Console.WriteLine(); Console.WriteLine(BitConverter.ToString(binary)); Console.WriteLine(); } /// /// Counts occurrences of a string in binary data (UTF8). /// private static int CountStringOccurrences(byte[] binary, string searchString) { var searchBytes = System.Text.Encoding.UTF8.GetBytes(searchString); var count = 0; for (var i = 0; i <= binary.Length - searchBytes.Length; i++) { var match = true; for (var j = 0; j < searchBytes.Length; j++) { if (binary[i + j] != searchBytes[j]) { match = false; break; } } if (match) count++; } return count; } #endregion #region Scenario 1: Same Instance (Object Identity) /// /// SCENARIO 1: Same instance referenced multiple times. /// Tests all ReferenceHandling modes: None, OnlyId, All /// [TestMethod] [DataRow(true, true)] [DataRow(false, true)] [DataRow(true, false)] [DataRow(false, false)] public void SameInstance_SerializeAndDeserialize(bool useSgen, bool useMeta) { var modes = new[] { ReferenceHandlingMode.None, ReferenceHandlingMode.OnlyId, ReferenceHandlingMode.All }; foreach (var mode in modes) { // Arrange: SAME instance used multiple times var userPreferences = new UserPreferences_All_True(); var sharedTag = new SharedTag_All_True { Id = 1, Name = "ImportantTag", Color = "#FF0000" }; var sharedUser = new SharedUser_All_True { Id = 1, Preferences = userPreferences }; var order = new TestOrder_Circ_Ref { Id = 1, OrderNumber = "ORD-001", PrimaryTag = sharedTag, Owner = sharedUser, Items = [ new TestOrderItem_Circ_Ref { Id = 1, ProductName = "Product-A", Tag = sharedTag, Assignee = sharedUser }, new TestOrderItem_Circ_Ref { Id = 2, ProductName = "Product-B", Tag = sharedTag, Assignee = new SharedUser_All_True { Id = 2, Preferences = userPreferences }}, new TestOrderItem_Circ_Ref { Id = 3, ProductName = "Product-C", Tag = sharedTag, Assignee = new SharedUser_All_True { Id = 3, Preferences = userPreferences } } ] }; //order.Parent = order.Items[1]; if (mode != ReferenceHandlingMode.None) order.Parent = order.Items[1]; else order.Parent = userPreferences; order.Items[1].ParentOrder = order; var options = new AcBinarySerializerOptions { ReferenceHandling = mode, UseGeneratedCode = useSgen, UseMetadata = useMeta, MaxDepth = 10, // None mode has no ref tracking → the cycle (Items[1].ParentOrder = order) is unprotected. // Use Truncate so the recursion silently bottoms out with Null at the depth limit instead of throwing. MaxDepthBehavior = mode == ReferenceHandlingMode.None ? MaxDepthBehavior.Truncate : MaxDepthBehavior.Throw }; Console.WriteLine($"\n========== ReferenceHandling: {options.ReferenceHandling}, UseSgen: {options.UseGeneratedCode}, UseMeta: {options.UseMetadata} =========="); // Act var binary = AcBinarySerializer.Serialize(order, options); if (mode == ReferenceHandlingMode.None) WriteBinaryToConsole(binary); var result = binary.BinaryTo(); // Options from header var objectRefCount = CountObjectRefs(binary, false); Console.WriteLine($"Binary size: {binary.Length} bytes"); Console.WriteLine($"ObjectRef count: {objectRefCount}"); Assert.IsNotNull(result, $"[{mode}] Deserialized result is null"); //Assert.IsNotNull(result.Parent); Assert.IsNotNull(result.Owner); // Assert based on mode switch (mode) { case ReferenceHandlingMode.None: // Truncate semantic: cycle bottoms out with Null at MaxDepth=10 → serialize succeeds, deserialize // produces a partial graph where deep cyclic references read as null. Data integrity at root + // first few levels still holds (verified below after the switch). CountObjectRefs raw byte scan // is unreliable in None mode — byte 65 (ObjectRef) == ASCII 'A', so "Product-A" produces false // positives. Skip count assertion; rely on data integrity checks instead. break; case ReferenceHandlingMode.OnlyId: // sharedTag (Id=1) 4x → 3 ObjectRefs, sharedUser (Id=1) 2x → 1 ObjectRef = 4 total Assert.IsTrue(objectRefCount >= 4, $"[{mode}] Expected at least 4 ObjectRefs, found {objectRefCount}"); // IId types should have reference identity Assert.AreSame(result.PrimaryTag, result.Items[0].Tag, $"[{mode}] Tag reference identity failed"); Assert.AreSame(result.Owner, result.Items[0].Assignee, $"[{mode}] User reference identity failed"); Assert.AreSame(result, result.Items[1].ParentOrder); Assert.AreSame(result.Parent, result.Items[1]); break; case ReferenceHandlingMode.All: // IId types + Non-IId (UserPreferences_All_True) should have ObjectRefs Assert.IsTrue(objectRefCount >= 4, $"[{mode}] Expected at least 4 ObjectRefs, found {objectRefCount}"); Assert.AreSame(result.PrimaryTag, result.Items[0].Tag, $"[{mode}] Tag reference identity failed"); Assert.AreSame(result.Owner, result.Items[0].Assignee, $"[{mode}] User reference identity failed"); Assert.AreSame(result, result.Items[1].ParentOrder); Assert.AreSame(result.Parent, result.Items[1]); // Non-IId should also have reference identity in All mode Assert.AreSame(result.Owner.Preferences, result.Items[0].Assignee.Preferences, $"[{mode}] UserPreferences_All_True reference identity failed - Non-IId should work in All mode!"); break; } // Data integrity - always check Assert.IsNotNull(result.PrimaryTag, $"[{mode}] PrimaryTag is null"); Assert.AreEqual(1, result.PrimaryTag.Id, $"[{mode}] PrimaryTag.Id incorrect"); Assert.AreEqual("ImportantTag", result.PrimaryTag.Name, $"[{mode}] PrimaryTag.Name incorrect"); Assert.AreEqual(3, result.Items.Count, $"[{mode}] Items count incorrect"); Console.WriteLine($"[{mode}] PASSED ✓"); } } #endregion #region Scenario 2: Different Instances with Same IId (IId-Based Deduplication) /// /// SCENARIO 2: DIFFERENT instances with SAME IId.Id value. /// CRITICAL test - if IId-based deduplication works: /// - ObjectRef should be used in binary /// - Data should be complete after deserialize /// - References should be identical (AreSame) /// - Different TYPES with same int Id should NOT be confused! /// [TestMethod] public void DifferentInstances_SameIId_SerializeAndDeserialize() { // Arrange: DIFFERENT instances but SAME IId.Id // CRITICAL: Multiple DIFFERENT TYPES all have Id=1 - must not be confused! var sharedTag = new SharedTag_All_True { Id = 55, Name = "ImportantTag_55", Color = "#FF0000" }; var order = new TestOrder_All_True { Id = 1, OrderNumber = "ORD-001", // All three types have Id=1 - tests (Type, Id) keying, not just Id PrimaryTag = new SharedTag_All_True { Id = 1, Name = "Tag_Id1", Color = "#FF0000" }, Owner = new SharedUser_All_True { Id = 1, Username = "User_Id1", Email = "user1@test.com" }, Category = new SharedCategory_All_True { Id = 1, Name = "Category_Id1", SortOrder = 10 }, Items = [ new TestOrderItem_All_True { Id = 1, ProductName = "Product-A", Tag = new SharedTag_All_True { Id = 1, Name = "Tag_Id1", Color = "#FF0000" }, Assignee = new SharedUser_All_True { Id = 1, Username = "User_Id1", Email = "user1@test.com" } }, new TestOrderItem_All_True { Id = 2, ProductName = "Product-B", Tag = new SharedTag_All_True { Id = 1, Name = "Tag_Id1", Color = "#FF0000" }, Assignee = new SharedUser_All_True { Id = 1, Username = "User_Id1", Email = "user1@test.com" } }, new TestOrderItem_All_True { Id = 3, ProductName = "Product-C", Tag = new SharedTag_All_True { Id = 1, Name = "Tag_Id1", Color = "#FF0000" }, Assignee = new SharedUser_All_True { Id = 1, Username = "User_Id1", Email = "user1@test.com" } } ] }; // Act var binary = order.ToBinary(); var result = binary.BinaryTo(); // Assert 1: Check if ObjectRef is used (IId-based deduplication active) var objectRefCount = CountObjectRefs(binary); Console.WriteLine($"\nBinary size: {binary.Length} bytes (WithRef)"); Console.WriteLine($"ObjectRef count: {objectRefCount}"); // Assert 3: Reference identity - same TYPE with same Id should be same reference // Tags with Id=1 should all be same reference Assert.AreSame(result.PrimaryTag, result.Items[0].Tag, "CRITICAL: Item[0].Tag should be same reference as PrimaryTag (same SharedTag_All_True.Id=1)"); Assert.AreSame(result.PrimaryTag, result.Items[1].Tag, "CRITICAL: Item[1].Tag should be same reference as PrimaryTag (same SharedTag_All_True.Id=1)"); Assert.AreSame(result.PrimaryTag, result.Items[2].Tag, "CRITICAL: Item[2].Tag should be same reference as PrimaryTag (same SharedTag_All_True.Id=1)"); // Users with Id=1 should all be same reference Assert.AreSame(result.Owner, result.Items[0].Assignee, "CRITICAL: Item[0].Assignee should be same reference as Owner (same SharedUser.Id=1)"); Assert.AreSame(result.Owner, result.Items[1].Assignee, "CRITICAL: Item[1].Assignee should be same reference as Owner (same SharedUser.Id=1)"); Assert.AreSame(result.Owner, result.Items[2].Assignee, "CRITICAL: Item[2].Assignee should be same reference as Owner (same SharedUser.Id=1)"); // Assert 4: Different TYPES with same Id should NOT be same reference! Assert.AreNotSame(result.PrimaryTag, result.Owner, "CRITICAL BUG: Tag and User are same reference! Types with same int Id were confused!"); Assert.AreNotSame(result.PrimaryTag, result.Category, "CRITICAL BUG: Tag and Category are same reference! Types with same int Id were confused!"); Assert.AreNotSame(result.Owner, result.Category, "CRITICAL BUG: User and Category are same reference! Types with same int Id were confused!"); // 4 Tags, 4 Users - each should have 3 ObjectRefs = 6 total minimum Assert.IsTrue(objectRefCount >= 6, $"CRITICAL: Expected at least 6 ObjectRef entries (3 per type for Tag and User), found {objectRefCount}. " + "IId-based reference deduplication is NOT working!"); // Assert 2: Data integrity - ALL data present and correct Assert.IsNotNull(result, "Deserialized result is null"); // Tag data Assert.IsNotNull(result.PrimaryTag, "PrimaryTag is null - data lost!"); Assert.AreEqual(1, result.PrimaryTag.Id, "PrimaryTag.Id incorrect"); Assert.AreEqual("Tag_Id1", result.PrimaryTag.Name, "PrimaryTag.Name incorrect - might be confused with User!"); Assert.AreEqual("#FF0000", result.PrimaryTag.Color, "PrimaryTag.Color incorrect"); // User data - MUST NOT be confused with Tag (both have Id=1) Assert.IsNotNull(result.Owner, "Owner is null - data lost!"); Assert.AreEqual(1, result.Owner.Id, "Owner.Id incorrect"); Assert.AreEqual("User_Id1", result.Owner.Username, "Owner.Username incorrect - might be confused with Tag!"); Assert.AreEqual("user1@test.com", result.Owner.Email, "Owner.Email incorrect"); // Category data - MUST NOT be confused with Tag or User (all have Id=1) Assert.IsNotNull(result.Category, "Category is null - data lost!"); Assert.AreEqual(1, result.Category.Id, "Category.Id incorrect"); Assert.AreEqual("Category_Id1", result.Category.Name, "Category.Name incorrect - might be confused with Tag!"); Assert.AreEqual(10, result.Category.SortOrder, "Category.SortOrder incorrect"); Assert.IsNotNull(result.Items, "Items is null"); Assert.AreEqual(3, result.Items.Count, "Items count incorrect"); for (var i = 0; i < 3; i++) { // Tag in items Assert.IsNotNull(result.Items[i].Tag, $"Items[{i}].Tag is null - data lost!"); Assert.AreEqual(1, result.Items[i].Tag!.Id, $"Items[{i}].Tag.Id incorrect"); Assert.AreEqual("Tag_Id1", result.Items[i].Tag.Name, $"Items[{i}].Tag.Name incorrect - confused with User?"); // User in items - MUST NOT be confused with Tag Assert.IsNotNull(result.Items[i].Assignee, $"Items[{i}].Assignee is null - data lost!"); Assert.AreEqual(1, result.Items[i].Assignee!.Id, $"Items[{i}].Assignee.Id incorrect"); Assert.AreEqual("User_Id1", result.Items[i].Assignee.Username, $"Items[{i}].Assignee.Username incorrect - confused with Tag?"); } } /// /// Size comparison: Same IId should result in smaller binary + verify data integrity. /// [TestMethod] public void DifferentInstances_SameIId_SmallerBinaryWithDataIntegrity() { // Arrange: 10 different instances with SAME IId var orderWithSameIId = new TestOrder_All_True { Id = 1, OrderNumber = "SAME-IID", Items = Enumerable.Range(1, 10).Select(i => new TestOrderItem_All_True { Id = i, ProductName = $"Product-{i}", // All have SAME IId.Id = 1, but DIFFERENT instances Assignee = new SharedUser_All_True { Id = 1, Username = "shared_user_name", Email = "shared@test.com" } }).ToList() }; // Arrange: 10 different instances with DIFFERENT IIds var orderWithDifferentIIds = new TestOrder_All_True { Id = 1, OrderNumber = "DIFF-IID", Items = Enumerable.Range(1, 10).Select(i => new TestOrderItem_All_True { Id = i, ProductName = $"Product-{i}", // All have DIFFERENT IId.Id Assignee = new SharedUser_All_True { Id = i * 100, Username = "unique_user_name", Email = "unique@test.com" } }).ToList() }; // Act var sameIIdBinary = orderWithSameIId.ToBinary(); var diffIIdBinary = orderWithDifferentIIds.ToBinary(); var sameIIdResult = sameIIdBinary.BinaryTo(); var diffIIdResult = diffIIdBinary.BinaryTo(); // Assert 1: Size comparison Console.WriteLine($"Same IId binary size: {sameIIdBinary.Length} bytes"); Console.WriteLine($"Different IId binary size: {diffIIdBinary.Length} bytes"); Console.WriteLine($"Size difference: {diffIIdBinary.Length - sameIIdBinary.Length} bytes"); Assert.IsTrue(sameIIdBinary.Length < diffIIdBinary.Length, $"Same IId ({sameIIdBinary.Length}b) should be smaller than different IIds ({diffIIdBinary.Length}b). " + "IId-based deduplication NOT working!"); // Assert 2: Data integrity for sameIId result Assert.IsNotNull(sameIIdResult, "sameIIdResult is null"); Assert.IsNotNull(sameIIdResult.Items, "sameIIdResult.Items is null"); Assert.AreEqual(10, sameIIdResult.Items.Count, "sameIIdResult should have 10 items"); for (var i = 0; i < 10; i++) { Assert.IsNotNull(sameIIdResult.Items[i].Assignee, $"sameIIdResult.Items[{i}].Assignee is null - data lost!"); Assert.AreEqual(1, sameIIdResult.Items[i].Assignee!.Id, $"sameIIdResult.Items[{i}].Assignee.Id should be 1"); Assert.AreEqual("shared_user_name", sameIIdResult.Items[i].Assignee.Username, $"sameIIdResult.Items[{i}].Assignee.Username incorrect"); } // Assert 3: Reference identity for sameIId var firstAssignee = sameIIdResult.Items[0].Assignee; for (var i = 1; i < 10; i++) { Assert.AreSame(firstAssignee, sameIIdResult.Items[i].Assignee, $"CRITICAL: Items[{i}].Assignee should be same reference as Items[0].Assignee (same IId.Id=1)"); } } #endregion #region Guid-Based IId Tests /// /// Guid IId with same instance - validates ObjectRef + data integrity + reference identity. /// [TestMethod] public void GuidIId_SameInstance_SerializeAndDeserialize() { // Arrange: SAME instance var sharedGuid = Guid.NewGuid(); var sharedItem = new TestGuidItem { Id = sharedGuid, Name = "SharedGuidItem" }; var order = new TestGuidOrder { Id = Guid.NewGuid(), Code = "GUID-001", Count = 3, Items = [sharedItem, sharedItem, sharedItem] }; // Act var binary = order.ToBinary(); var result = binary.BinaryTo(); // Assert 1: ObjectRef should be present var objectRefCount = CountObjectRefs(binary); Console.WriteLine($"Binary size: {binary.Length} bytes"); Console.WriteLine($"ObjectRef count: {objectRefCount}"); Assert.IsTrue(objectRefCount >= 2, $"Expected at least 2 ObjectRefs, found {objectRefCount}"); // Assert 2: Data integrity - all items present and correct Assert.IsNotNull(result, "Result is null"); Assert.IsNotNull(result.Items, "Items is null"); Assert.AreEqual(3, result.Items.Count, "Items count incorrect"); for (var i = 0; i < 3; i++) { Assert.IsNotNull(result.Items[i], $"Items[{i}] is null"); Assert.AreEqual(sharedGuid, result.Items[i].Id, $"Items[{i}].Id incorrect"); Assert.AreEqual("SharedGuidItem", result.Items[i].Name, $"Items[{i}].Name incorrect"); } // Assert 3: Reference identity Assert.AreSame(result.Items[0], result.Items[1], "Items[0] and Items[1] should be same reference"); Assert.AreSame(result.Items[1], result.Items[2], "Items[1] and Items[2] should be same reference"); } /// /// CRITICAL: Guid IId with different instances but same Id - tests IId-based deduplication. /// [TestMethod] public void GuidIId_DifferentInstances_SameId_SerializeAndDeserialize() { // Arrange: DIFFERENT instances but SAME Guid var sharedGuid = Guid.NewGuid(); var order = new TestGuidOrder { Id = Guid.NewGuid(), Code = "GUID-001", Count = 3, Items = [ new TestGuidItem { Id = sharedGuid, Name = "SharedGuidItem" }, new TestGuidItem { Id = sharedGuid, Name = "SharedGuidItem" }, new TestGuidItem { Id = sharedGuid, Name = "SharedGuidItem" } ] }; // Act var binary = order.ToBinary(); var result = binary.BinaryTo(); // Assert 1: ObjectRef should be present if IId-based dedup works var objectRefCount = CountObjectRefs(binary); Console.WriteLine($"Binary size: {binary.Length} bytes"); Console.WriteLine($"ObjectRef count: {objectRefCount}"); Assert.IsTrue(objectRefCount >= 2, $"CRITICAL: Expected at least 2 ObjectRefs for same Guid IId, found {objectRefCount}. " + "Guid-based IId deduplication NOT working!"); // Assert 2: Data integrity - all items present and correct Assert.IsNotNull(result, "Result is null"); Assert.IsNotNull(result.Items, "Items is null"); Assert.AreEqual(3, result.Items.Count, "Items count incorrect"); for (var i = 0; i < 3; i++) { Assert.IsNotNull(result.Items[i], $"Items[{i}] is null - data lost!"); Assert.AreEqual(sharedGuid, result.Items[i].Id, $"Items[{i}].Id incorrect"); Assert.AreEqual("SharedGuidItem", result.Items[i].Name, $"Items[{i}].Name incorrect"); } // Assert 3: Reference identity - if IId-based, should be same reference Assert.AreSame(result.Items[0], result.Items[1], "CRITICAL: Items[0] and Items[1] should be same reference (same Guid)"); Assert.AreSame(result.Items[1], result.Items[2], "CRITICAL: Items[1] and Items[2] should be same reference (same Guid)"); } #endregion #region Data Integrity Tests /// /// Diagnostic test to verify IId detection works correctly. /// [TestMethod] public void IIdDetection_Diagnostic() { // Test GetIdInfo directly var sharedTagType = typeof(SharedTag_All_True); var idInfo = AyCode.Core.Helpers.JsonUtilities.GetIdInfo(sharedTagType); Console.WriteLine($"SharedTag_All_True GetIdInfo: IsId={idInfo.IsId}, IdType={idInfo.IdType?.Name}"); Assert.IsTrue(idInfo.IsId, "SharedTag_All_True should be detected as IId"); Assert.AreEqual(typeof(int), idInfo.IdType, "SharedTag_All_True Id type should be int"); // Test SharedUser var sharedUserType = typeof(SharedUser_All_True); var userIdInfo = AyCode.Core.Helpers.JsonUtilities.GetIdInfo(sharedUserType); Console.WriteLine($"SharedUser GetIdInfo: IsId={userIdInfo.IsId}, IdType={userIdInfo.IdType?.Name}"); Assert.IsTrue(userIdInfo.IsId, "SharedUser should be detected as IId"); // Test TestGuidItem var guidItemType = typeof(TestGuidItem); var guidIdInfo = AyCode.Core.Helpers.JsonUtilities.GetIdInfo(guidItemType); Console.WriteLine($"TestGuidItem GetIdInfo: IsId={guidIdInfo.IsId}, IdType={guidIdInfo.IdType?.Name}"); Assert.IsTrue(guidIdInfo.IsId, "TestGuidItem should be detected as IId"); } /// /// Verify data is correct regardless of reference handling. /// [TestMethod] public void SharedCategory_DataIntegrity() { var categories = new List { new() { Id = 1, Name = "Category1", SortOrder = 1, IsDefault = true }, new() { Id = 2, Name = "Category2", SortOrder = 2, ParentCategoryId = 1 }, new() { Id = 3, Name = "Category3", SortOrder = 3, ParentCategoryId = 1 } }; var binary = categories.ToBinary(); var result = binary.BinaryTo>(); Assert.IsNotNull(result); Assert.AreEqual(3, result.Count); Assert.AreEqual(1, result[0].Id); Assert.AreEqual("Category1", result[0].Name); Assert.IsTrue(result[0].IsDefault); Assert.AreEqual(2, result[1].Id); Assert.AreEqual(1, result[1].ParentCategoryId); Assert.AreEqual(3, result[2].Id); Assert.AreEqual(1, result[2].ParentCategoryId); } #endregion #region SGen-emit writer/reader ref-handling asymmetry — regression target /// /// Target test for the SGen-emit writer/reader asymmetry hypothesis — covers BOTH /// collection-element AND dictionary-value ref-marker paths in a single graph. /// /// Setup: /// /// TestRefAsymParent [AcBinarySerializable(false)] — parent EnableRefHandlingFeature=false. /// TestRefAsymChild [AcBinarySerializable(true)] — child IId<int>, all features ON. /// Same child instance referenced twice in the parent's Children list /// AND twice as VALUES in the parent's ChildrenMap dictionary. /// Runtime ReferenceHandling=All + Interning=All (via Default options). /// MarkerDecimal property AFTER the list — drift detection slot (decimal = 16 fixed bytes). /// MarkerDecimal2 property AFTER the dictionary — second drift detection slot, /// catches the symmetric dict-value emit asymmetry (EmitReadDictionary:482). /// /// /// /// Expected if the asymmetry-hypothesis holds: the writer (runtime via /// WriteObjectGenerated bridge) emits ObjectRefFirst+ObjectRef for the duplicates; the SGen /// reader-emit's zero-branch path (parent flag false guarding out the ref-aware switch) /// misreads the VarUInt cacheIdx as a property-marker byte → DECIMAL_DRIFT exception or /// value-mismatch on MarkerDecimal / MarkerDecimal2. /// /// /// Expected if the hypothesis is WRONG: the test passes — different fix direction needed. /// /// [TestMethod] public void Serialize_RefMarkerCollectionElement_ParentRefHandlingFeatureOff_DriftReproduction() { var sharedChild = new TestRefAsymChild { Id = 1, Name = "Shared" }; var parent = new TestRefAsymParent { Id = 100, Children = new List { sharedChild, sharedChild }, MarkerDecimal = 999.99m, ChildrenMap = new Dictionary { { 10, sharedChild }, { 20, sharedChild }, }, MarkerDecimal2 = 888.88m, }; var options = AcBinarySerializerOptions.Default; // RefHandling=All, Interning=All options.UseGeneratedCode = true; var bytes = AcBinarySerializer.Serialize(parent, options); // Sanity check: did the writer actually emit an ObjectRef marker for the duplicates? var objectRefCount = CountObjectRefs(bytes, writeBinaryToConsole: false); Console.WriteLine($"Wire size: {bytes.Length}, ObjectRef occurrences: {objectRefCount}"); var result = AcBinaryDeserializer.Deserialize(bytes, options); Assert.IsNotNull(result, "Deserialize returned null — wire corruption"); Assert.AreEqual(parent.Id, result.Id, "Parent.Id mismatch — possible drift before the list"); // --- Collection-element path (EmitReadCollectionElement) --- Assert.IsNotNull(result.Children, "Children list was null after round-trip"); Assert.AreEqual(2, result.Children.Count, "Children count mismatch"); Assert.IsNotNull(result.Children[0]); Assert.IsNotNull(result.Children[1]); Assert.AreEqual(sharedChild.Id, result.Children[0].Id, "Children[0].Id mismatch"); Assert.AreEqual(sharedChild.Name, result.Children[0].Name, "Children[0].Name mismatch"); Assert.AreEqual(sharedChild.Id, result.Children[1].Id, "Children[1].Id mismatch — drift on the duplicate"); Assert.AreEqual(sharedChild.Name, result.Children[1].Name, "Children[1].Name mismatch — drift on the duplicate"); Assert.AreEqual(parent.MarkerDecimal, result.MarkerDecimal, "MarkerDecimal drift — wire-position desync after the Children list (smoking gun for collection-element SGen-emit asymmetry)"); // --- Dictionary-value path (EmitReadDictionary) --- Assert.IsNotNull(result.ChildrenMap, "ChildrenMap was null after round-trip"); Assert.AreEqual(2, result.ChildrenMap.Count, "ChildrenMap count mismatch"); Assert.IsTrue(result.ChildrenMap.ContainsKey(10), "ChildrenMap missing key=10"); Assert.IsTrue(result.ChildrenMap.ContainsKey(20), "ChildrenMap missing key=20"); Assert.IsNotNull(result.ChildrenMap[10]); Assert.IsNotNull(result.ChildrenMap[20]); Assert.AreEqual(sharedChild.Id, result.ChildrenMap[10].Id, "ChildrenMap[10].Id mismatch"); Assert.AreEqual(sharedChild.Name, result.ChildrenMap[10].Name, "ChildrenMap[10].Name mismatch"); Assert.AreEqual(sharedChild.Id, result.ChildrenMap[20].Id, "ChildrenMap[20].Id mismatch — drift on the dict-value duplicate"); Assert.AreEqual(sharedChild.Name, result.ChildrenMap[20].Name, "ChildrenMap[20].Name mismatch — drift on the dict-value duplicate"); Assert.AreEqual(parent.MarkerDecimal2, result.MarkerDecimal2, "MarkerDecimal2 drift — wire-position desync after the ChildrenMap dictionary (smoking gun for dict-value SGen-emit asymmetry)"); } #endregion }