#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);
});
}
}