#nullable enable using System.Collections.Concurrent; using System.Collections.Immutable; using System.Diagnostics; using System.Text; namespace UnitTests_Parallelizable.ConfigurationTests; /// /// Unit tests for the class, ensuring robust deep cloning for /// Terminal.Gui's configuration system. /// public class DeepClonerTests { // Test classes for complex scenarios private class SimpleValueType { public int Number { get; set; } public bool Flag { get; init; } } private class SimpleReferenceType { public string? Name { get; set; } public int Count { get; set; } public override bool Equals (object? obj) { return obj is SimpleReferenceType other && Name == other.Name && Count == other.Count; } // ReSharper disable twice NonReadonlyMemberInGetHashCode public override int GetHashCode () { return HashCode.Combine (Name, Count); } } private class CollectionContainer { public List? Strings { get; init; } public Dictionary? Counts { get; init; } public int []? Numbers { get; init; } } private class NestedObject { public SimpleReferenceType? Inner { get; init; } public List? Values { get; init; } } private class CircularReference { public CircularReference? Self { get; set; } public string? Name { get; set; } } private class ConfigPropertyMock { public object? PropertyValue { get; init; } public bool Immutable { get; init; } } private class ComplexKey { public int Id { get; init; } public override bool Equals (object? obj) { return obj is ComplexKey key && Id == key.Id; } public override int GetHashCode () { return Id.GetHashCode (); } } private class KeyEqualityComparer : IEqualityComparer { public bool Equals (Key? x, Key? y) { return x?.KeyCode == y?.KeyCode; } public int GetHashCode (Key obj) { return obj.KeyCode.GetHashCode (); } } // Fundamentals [Fact] public void Null_ReturnsNull () { object? source = null; object? result = DeepCloner.DeepClone (source); Assert.Null (result); } [Fact] public void SimpleValueType_ReturnsEqualValue () { var source = 42; int result = DeepCloner.DeepClone (source); Assert.Equal (source, result); } [Fact] public void String_ReturnsSameString () { var source = "Hello"; string? result = DeepCloner.DeepClone (source); Assert.Equal (source, result); Assert.Same (source, result); // Strings are immutable } [Fact] public void Rune_ReturnsEqualRune () { Rune source = new ('A'); Rune result = DeepCloner.DeepClone (source); Assert.Equal (source, result); } [Fact] public void Key_CreatesDeepCopy () { Key? source = new (KeyCode.A); source.Handled = true; Key? result = DeepCloner.DeepClone (source); Assert.NotNull (result); Assert.NotSame (source, result); Assert.Equal (source.KeyCode, result.KeyCode); Assert.Equal (source.Handled, result.Handled); // Modify result, ensure source unchanged result.Handled = false; Assert.True (source.Handled); } [Fact] public void Enum_ReturnsEqualEnum () { var source = DayOfWeek.Monday; DayOfWeek result = DeepCloner.DeepClone (source); Assert.Equal (source, result); } [Fact] public void Boolean_ReturnsEqualValue () { var source = true; bool result = DeepCloner.DeepClone (source); Assert.Equal (source, result); source = false; result = DeepCloner.DeepClone (source); Assert.Equal (source, result); } [Fact] public void Attribute_ReturnsEqualValue () { var source = new Attribute (Color.Black); Attribute result = DeepCloner.DeepClone (source); Assert.Equal (source, result); source = new (Color.White); result = DeepCloner.DeepClone (source); Assert.Equal (source, result); } [Fact] public void Scheme_Normal_Set_ReturnsEqualValue () { var source = new Scheme (new Scheme (new Attribute (Color.Red, Color.Green, TextStyle.Bold))); Scheme? result = DeepCloner.DeepClone (source); Assert.Equal (source, result); source = new Scheme (new Scheme ()); result = DeepCloner.DeepClone (source); Assert.Equal (source, result); } [Fact] public void Scheme_All_Set_ReturnsEqualValue () { Scheme? source = new () { Normal = new ("LightGray", "RaisinBlack", TextStyle.None), Focus = new ("White", "DarkGray", TextStyle.None), HotNormal = new ("Silver", "RaisinBlack", TextStyle.Underline), Disabled = new ("DarkGray", "RaisinBlack", TextStyle.Faint), HotFocus = new ("White", "Green", TextStyle.Underline), Active = new ("White", "Charcoal", TextStyle.Bold), HotActive = new ("White", "Charcoal", TextStyle.Underline | TextStyle.Bold), Highlight = new ("White", "Onyx", TextStyle.None), Editable = new ("LightYellow", "RaisinBlack", TextStyle.None), ReadOnly = new ("Gray", "RaisinBlack", TextStyle.Italic) }; Scheme? result = DeepCloner.DeepClone (source); Assert.True (source.TryGetExplicitlySetAttributeForRole (VisualRole.Normal, out _)); Assert.True (source.TryGetExplicitlySetAttributeForRole (VisualRole.Active, out _)); Assert.True (source.TryGetExplicitlySetAttributeForRole (VisualRole.HotNormal, out _)); Assert.True (source.TryGetExplicitlySetAttributeForRole (VisualRole.Focus, out _)); Assert.True (source.TryGetExplicitlySetAttributeForRole (VisualRole.HotFocus, out _)); Assert.True (source.TryGetExplicitlySetAttributeForRole (VisualRole.Active, out _)); Assert.True (source.TryGetExplicitlySetAttributeForRole (VisualRole.HotActive, out _)); Assert.True (source.TryGetExplicitlySetAttributeForRole (VisualRole.Highlight, out _)); Assert.True (source.TryGetExplicitlySetAttributeForRole (VisualRole.Editable, out _)); Assert.True (source.TryGetExplicitlySetAttributeForRole (VisualRole.ReadOnly, out _)); Assert.True (source.TryGetExplicitlySetAttributeForRole (VisualRole.Disabled, out _)); Assert.Equal (source, result); source = new Scheme (new Scheme ()); result = DeepCloner.DeepClone (source); Assert.Equal (source, result); } // Simple Reference Types [Fact] public void SimpleReferenceType_CreatesDeepCopy () { SimpleReferenceType? source = new () { Name = "Test", Count = 10 }; SimpleReferenceType? result = DeepCloner.DeepClone (source); Assert.NotNull (result); Assert.NotSame (source, result); Assert.Equal (source.Name, result!.Name); Assert.Equal (source.Count, result.Count); Assert.True (source.Equals (result)); // Verify Equals method Assert.Equal (source.GetHashCode (), result.GetHashCode ()); // Verify GetHashCode // Modify result, ensure source unchanged result.Name = "Modified"; result.Count = 20; Assert.Equal ("Test", source.Name); Assert.Equal (10, source.Count); } // Collections [Fact] public void List_CreatesDeepCopy () { List? source = new () { "One", "Two" }; List? result = DeepCloner.DeepClone (source); Assert.NotNull (result); Assert.NotSame (source, result); Assert.Equal (source, result); // Modify result, ensure source unchanged result!.Add ("Three"); Assert.Equal (2, source.Count); Assert.Equal (3, result.Count); } [Fact] public void Dictionary_CreatesDeepCopy () { Dictionary? source = new () { { "A", 1 }, { "B", 2 } }; Dictionary? result = DeepCloner.DeepClone (source); Assert.NotNull (result); Assert.NotSame (source, result); Assert.Equal (source, result); // Modify result, ensure source unchanged result! ["C"] = 3; Assert.Equal (2, source.Count); Assert.Equal (3, result.Count); } [Fact] public void Dictionary_CreatesDeepCopy_Including_Comparer_Options () { Dictionary? source = new (StringComparer.InvariantCultureIgnoreCase) { { "A", 1 }, { "B", 2 } }; Dictionary? result = DeepCloner.DeepClone (source); Assert.NotNull (result); Assert.NotSame (source, result); Assert.Equal (source, result); Assert.Equal (source.Comparer, result.Comparer); // Modify result, ensure source unchanged result! ["C"] = 3; Assert.Equal (2, source.Count); Assert.Equal (3, result.Count); Assert.Contains ("A", result); Assert.Contains ("a", result); } [Fact] public void Dictionary_CreatesDeepCopy_WithCapacity () { // Arrange: Create a dictionary with a specific capacity Dictionary source = new (100) // Set initial capacity to 100 { { "Key1", 1 }, { "Key2", 2 } }; // Act: Clone the dictionary Dictionary? result = DeepCloner.DeepClone (source); // Assert: Verify the dictionary was cloned correctly Assert.NotNull (result); Assert.NotSame (source, result); Assert.Equal (source, result); // Verify key-value pairs are cloned // Verify that the capacity is preserved (if supported) Assert.True (result.Count <= result.EnsureCapacity (0)); // EnsureCapacity(0) returns the current capacity Assert.True (source.Count <= source.EnsureCapacity (0)); // EnsureCapacity(0) returns the current capacity } [Fact] public void ConcurrentDictionary_CreatesDeepCopy () { ConcurrentDictionary? source = new (new Dictionary () { { "A", 1 }, { "B", 2 } }); ConcurrentDictionary? result = DeepCloner.DeepClone (source); Assert.NotNull (result); Assert.NotSame (source, result); Assert.Equal (source, result); // Modify result, ensure source unchanged result! ["C"] = 3; Assert.Equal (2, source.Count); Assert.Equal (3, result.Count); } [Fact] public void Array_CreatesDeepCopy () { int []? source = { 1, 2, 3 }; int []? result = DeepCloner.DeepClone (source); Assert.NotNull (result); Assert.NotSame (source, result); Assert.Equal (source, result); // Modify result, ensure source unchanged result! [0] = 99; Assert.Equal (1, source [0]); Assert.Equal (99, result [0]); } [Fact] public void ImmutableList_ThrowsNotSupported () { ImmutableList source = ImmutableList.Create ("One", "Two"); Assert.Throws (() => DeepCloner.DeepClone (source)); } [Fact] public void ImmutableDictionary_ThrowsNotSupported () { ImmutableDictionary source = ImmutableDictionary.Create ().Add ("A", 1); Assert.Throws (() => DeepCloner.DeepClone (source)); } [Fact] public void Dictionary_SourceAddsItem_ClonesCorrectly () { Dictionary? source = new () { { "Disabled", new (Color.White) }, { "Normal", new (Color.Blue) } }; Dictionary? result = DeepCloner.DeepClone (source); Assert.NotNull (result); Assert.NotSame (source, result); Assert.Equal (2, result.Count); Assert.Equal ((Attribute)source ["Disabled"], (Attribute)result ["Disabled"]); Assert.Equal (source ["Normal"], result ["Normal"]); } [Fact] public void Dictionary_SourceUpdatesOneItem_ClonesCorrectly () { Dictionary? source = new () { { "Disabled", new (Color.White) } }; Dictionary? result = DeepCloner.DeepClone (source); Assert.NotNull (result); Assert.NotSame (source, result); Assert.Single (result); Assert.Equal (source ["Disabled"], result ["Disabled"]); } [Fact] public void Dictionary_WithComplexKeys_ClonesCorrectly () { Dictionary? source = new () { { new() { Id = 1 }, "Value1" } }; Dictionary? result = DeepCloner.DeepClone (source); Assert.NotNull (result); Assert.NotSame (source, result); Assert.Equal (source.Keys.First ().Id, result.Keys.First ().Id); Assert.Equal (source.Values.First (), result.Values.First ()); } [Fact] public void Dictionary_WithCustomKeyComparer_ClonesCorrectly () { Dictionary source = new (new KeyEqualityComparer ()) { { new (KeyCode.Esc), "Esc" } }; Dictionary result = DeepCloner.DeepClone (source)!; Assert.NotNull (result); Assert.NotSame (source, result); Assert.Single (result); Assert.True (result.ContainsKey (new (KeyCode.Esc))); Assert.Equal ("Esc", result [new (KeyCode.Esc)]); // Modify result, ensure source unchanged result [new (KeyCode.Q)] = "Q"; Assert.False (source.ContainsKey (new (KeyCode.Q))); } // Nested Objects [Fact] public void CollectionContainer_CreatesDeepCopy () { CollectionContainer? source = new () { Strings = ["A", "B"], Counts = new () { { "X", 1 }, { "Y", 2 } }, Numbers = [10, 20] }; CollectionContainer? result = DeepCloner.DeepClone (source); Assert.NotNull (result); Assert.NotSame (source, result); Assert.NotSame (source.Strings, result!.Strings); Assert.NotSame (source.Counts, result.Counts); Assert.NotSame (source.Numbers, result.Numbers); Assert.Equal (source.Strings, result.Strings); Assert.Equal (source.Counts, result.Counts); Assert.Equal (source.Numbers, result.Numbers); // Modify result, ensure source unchanged result.Strings!.Add ("C"); result.Counts! ["Z"] = 3; result.Numbers! [0] = 99; Assert.Equal (2, source.Strings.Count); Assert.Equal (2, source.Counts.Count); Assert.Equal (10, source.Numbers [0]); } [Fact] public void NestedObject_CreatesDeepCopy () { NestedObject? source = new () { Inner = new () { Name = "Inner", Count = 5 }, Values = new () { new() { Number = 1, Flag = true }, new() { Number = 2, Flag = false } } }; NestedObject? result = DeepCloner.DeepClone (source); Assert.NotNull (result); Assert.NotSame (source, result); Assert.NotSame (source.Inner, result!.Inner); Assert.NotSame (source.Values, result.Values); Assert.Equal (source.Inner!.Name, result.Inner!.Name); Assert.Equal (source.Inner.Count, result.Inner.Count); Assert.Equal (source.Values! [0].Number, result.Values! [0].Number); Assert.Equal (source.Values [0].Flag, result.Values [0].Flag); // Modify result, ensure source unchanged result.Inner.Name = "Modified"; result.Values [0].Number = 99; Assert.Equal ("Inner", source.Inner.Name); Assert.Equal (1, source.Values [0].Number); } // Circular References [Fact] public void CircularReference_HandlesCorrectly () { CircularReference? source = new () { Name = "Cycle" }; source.Self = source; // Create circular reference CircularReference? result = DeepCloner.DeepClone (source); Assert.NotNull (result); Assert.NotSame (source, result); Assert.Equal (source.Name, result!.Name); Assert.NotNull (result.Self); Assert.Same (result, result.Self); // Circular reference preserved Assert.NotSame (source.Self, result.Self); // Modify result, ensure source unchanged result.Name = "Modified"; Assert.Equal ("Cycle", source.Name); } // Terminal.Gui-Specific Types [Fact] public void ConfigPropertyMock_CreatesDeepCopy () { ConfigPropertyMock? source = new () { PropertyValue = new List { "Red", "Blue" }, Immutable = true }; ConfigPropertyMock? result = DeepCloner.DeepClone (source); Assert.NotNull (result); Assert.NotSame (source, result); Assert.NotSame (source.PropertyValue, result!.PropertyValue); Assert.Equal ((List)source.PropertyValue, (List)result.PropertyValue!); Assert.Equal (source.Immutable, result.Immutable); // Modify result, ensure source unchanged ((List)result.PropertyValue!).Add ("Green"); Assert.Equal (2, ((List)source.PropertyValue).Count); } [Fact] public void ConfigProperty_CreatesDeepCopy () { ConfigProperty? source = ConfigProperty.CreateImmutableWithAttributeInfo (CM.GetHardCodedConfigPropertyCache ()! ["Application.QuitKey"].PropertyInfo!); source.Immutable = false; source.PropertyValue = Key.A; ConfigProperty? result = DeepCloner.DeepClone (source); Assert.NotNull (result); Assert.NotNull (result.PropertyInfo); Assert.NotSame (source, result); Assert.NotSame (source.PropertyValue, result!.PropertyValue); // PropertyInfo is effectively a simple type Assert.Same (source.PropertyInfo, result!.PropertyInfo); Assert.Equal (source.Immutable, result.Immutable); } [Fact] public void LargeObject_PerformsWithinLimit () { List source = new (Enumerable.Range (1, 10000)); var stopwatch = Stopwatch.StartNew (); List result = DeepCloner.DeepClone (source)!; stopwatch.Stop (); Assert.Equal (source, result); Assert.True (stopwatch.ElapsedMilliseconds < 1000); // Ensure it completes within 1 second } [Fact] public void CloneDictionary_ShouldClone_NormalDictionary () { // Arrange: A supported generic dictionary var original = new Dictionary { ["one"] = 1, ["two"] = 2, ["three"] = 3 }; // Act var cloned = DeepCloner.DeepClone (original); // Assert Assert.NotNull (cloned); Assert.NotSame (original, cloned); // must be a new instance Assert.Equal (original, cloned); // must have same contents Assert.Equal (original.Count, cloned.Count); Assert.Equal (original ["one"], cloned ["one"]); Assert.Equal (original ["two"], cloned ["two"]); Assert.Equal (original ["three"], cloned ["three"]); } [Fact] public void CloneDictionary_ShouldClone_ConcurrentDictionary () { // Arrange var original = new ConcurrentDictionary { ["a"] = "alpha", ["b"] = "beta" }; // Act var cloned = DeepCloner.DeepClone (original); // Assert Assert.NotSame (original, cloned); Assert.Equal (original, cloned); } [Fact] public void CloneDictionary_Empty_Dictionary_ShouldWork () { // Arrange var original = new Dictionary (); // Act var cloned = DeepCloner.DeepClone (original); // Assert Assert.NotSame (original, cloned); Assert.Empty (cloned!); } [Fact] public void CloneDictionary_Empty_ConcurrentDictionary_ShouldWork () { // Arrange var original = new ConcurrentDictionary (); // Act var cloned = DeepCloner.DeepClone (original); // Assert Assert.NotSame (original, cloned); Assert.Empty (cloned!); } [Fact] public void CloneDictionary_With_Unsupported_Dictionary_Throws () { // Arrange: A generic dictionary type (SortedDictionary for example) var unsupportedDict = new SortedDictionary { { 1, "A" }, { 2, "B" } }; // Act & Assert: DeepCloner should throw Assert.ThrowsAny (() => { // This should throw, because DeepCloner does not support SortedDictionary _ = DeepCloner.DeepClone (unsupportedDict); }); } }