Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions FastCloner.Tests/ArrayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

namespace FastCloner.Tests;

[TestFixture]
public class ArrayTests
[TestFixture(Low)]
[TestFixture(High)]
public class ArrayTests(int maxRecursionDepth) : BaseTestFixture(maxRecursionDepth)
{
struct MyIntStruct
{
Expand Down
32 changes: 32 additions & 0 deletions FastCloner.Tests/BaseTestFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace FastCloner.Tests;

/// <summary>
/// Base class for test fixtures that should run with multiple MaxRecursionDepth values.
/// Inherit from this class and add [TestFixture(1)] and [TestFixture(1000)] to your test class.
/// </summary>
public abstract class BaseTestFixture
{
public const int Low = 1;
public const int High = 1_000;

protected int MaxRecursionDepth { get; private set; }

protected BaseTestFixture(int maxRecursionDepth)
{
MaxRecursionDepth = maxRecursionDepth;
}

[OneTimeSetUp]
public void BaseOneTimeSetUp()
{
FastCloner.MaxRecursionDepth = MaxRecursionDepth;
}

[OneTimeTearDown]
public void BaseOneTimeTearDown()
{
// Reset to default
FastCloner.MaxRecursionDepth = 1000;
}
}

5 changes: 3 additions & 2 deletions FastCloner.Tests/CircularTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
namespace FastCloner.Tests;

[TestFixture]
public class CircularTests
[TestFixture(Low)]
[TestFixture(High)]
public class CircularTests(int maxRecursionDepth) : BaseTestFixture(maxRecursionDepth)
{
public class C1
{
Expand Down
8 changes: 6 additions & 2 deletions FastCloner.Tests/ConcurrentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

namespace FastCloner.Tests;

[TestFixture]
public class ConcurrentTests
[TestFixture(Low)]
[TestFixture(High)]
public class ConcurrentTests(int maxRecursionDepth) : BaseTestFixture(maxRecursionDepth)
{
private class TestClass
{
Expand All @@ -15,6 +16,9 @@ private class TestClass
public void GenerateCloner_IsCalledOnlyOnce()
{
// Arrange
// clear cache between fixtures
FastClonerCache.ClearCache();

CountHolder generatorCallCount = new CountHolder();
Type testType = typeof(TestClassForSingleCallTest);

Expand Down
5 changes: 3 additions & 2 deletions FastCloner.Tests/CopyToObjectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

namespace FastCloner.Tests;

[TestFixture]
public class CopyToObjectTests
[TestFixture(Low)]
[TestFixture(High)]
public class CopyToObjectTests(int maxRecursionDepth) : BaseTestFixture(maxRecursionDepth)
{
[Test]
public void InterfaceTest()
Expand Down
5 changes: 3 additions & 2 deletions FastCloner.Tests/CtorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

namespace FastCloner.Tests;

[TestFixture]
public class CtorTests
[TestFixture(Low)]
[TestFixture(High)]
public class CtorTests(int maxRecursionDepth) : BaseTestFixture(maxRecursionDepth)
{
public class T1
{
Expand Down
5 changes: 3 additions & 2 deletions FastCloner.Tests/DbTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ namespace FastCloner.Tests;
using NHibernate;
using NHibernate.Proxy;

[TestFixture]
public class DbTests
[TestFixture(Low)]
[TestFixture(High)]
public class DbTests(int maxRecursionDepth) : BaseTestFixture(maxRecursionDepth)
{
private ISessionFactory sessionFactory;
private const string DbFile = "test.db";
Expand Down
5 changes: 3 additions & 2 deletions FastCloner.Tests/GenericTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
namespace FastCloner.Tests;

[TestFixture]
public class GenericTests
[TestFixture(Low)]
[TestFixture(High)]
public class GenericTests(int maxRecursionDepth) : BaseTestFixture(maxRecursionDepth)
{
[Test]
public void Tuple_Should_Be_Cloned()
Expand Down
5 changes: 3 additions & 2 deletions FastCloner.Tests/IgnoredTypesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

namespace FastCloner.Tests;

[TestFixture, NonParallelizable]
public class IgnoredTypesTests
[TestFixture(Low)]
[TestFixture(High)]
public class IgnoredTypesTests(int maxRecursionDepth) : BaseTestFixture(maxRecursionDepth)
{
public class SimpleClass
{
Expand Down
5 changes: 3 additions & 2 deletions FastCloner.Tests/InheritanceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

namespace FastCloner.Tests;

[TestFixture]
public class InheritanceTests
[TestFixture(Low)]
[TestFixture(High)]
public class InheritanceTests(int maxRecursionDepth) : BaseTestFixture(maxRecursionDepth)
{
public class C1 : IDisposable
{
Expand Down
5 changes: 3 additions & 2 deletions FastCloner.Tests/ObjectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

namespace FastCloner.Tests;

[TestFixture]
public class ObjectTests
[TestFixture(Low)]
[TestFixture(High)]
public class ObjectTests(int maxRecursionDepth) : BaseTestFixture(maxRecursionDepth)
{
[Test]
public void SimpleObject_Should_Be_Cloned()
Expand Down
5 changes: 3 additions & 2 deletions FastCloner.Tests/ShallowCloneTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

namespace FastCloner.Tests;

[TestFixture]
public class ShallowCloneTests
[TestFixture(Low)]
[TestFixture(High)]
public class ShallowCloneTests(int maxRecursionDepth) : BaseTestFixture(maxRecursionDepth)
{
[Test]
public void SimpleObject_Should_Be_Cloned()
Expand Down
90 changes: 88 additions & 2 deletions FastCloner.Tests/SpecialCaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@

namespace FastCloner.Tests;

[TestFixture]
public class SpecialCaseTests
[TestFixture(Low)]
[TestFixture(High)]
public class SpecialCaseTests(int maxRecursionDepth) : BaseTestFixture(maxRecursionDepth)
{
[OneTimeSetUp]
public void Setup()
Expand Down Expand Up @@ -3179,4 +3180,89 @@ private class SelfReferencedWithInitOnlyField

public ClassWithReadOnlyField WithReadOnlyField { get; set; }
}

[Test]
public void SelfReferenced_WithInitOnlyValueTypeField_Test()
{
SelfReferencedWithInitOnlyValueTypeField original = new SelfReferencedWithInitOnlyValueTypeField
{
WithReadOnlyValueTypeField = new ClassWithReadOnlyValueField()
};

SelfReferencedWithInitOnlyValueTypeField clone = original.DeepClone();

Assert.That(clone, Is.Not.SameAs(original));
Assert.That(clone.WithReadOnlyValueTypeField, Is.Not.SameAs(original.WithReadOnlyValueTypeField));
Assert.That(clone.WithReadOnlyValueTypeField.ReadOnlyValue, Is.EqualTo(original.WithReadOnlyValueTypeField.ReadOnlyValue));
}

private class SelfReferencedWithInitOnlyValueTypeField
{
public SelfReferencedWithInitOnlyValueTypeField? Predecessor { get; set; }

public ClassWithReadOnlyValueField WithReadOnlyValueTypeField { get; set; }
}

private class ClassWithReadOnlyValueField
{
private readonly decimal readOnlyField = 1m;
public decimal ReadOnlyValue => readOnlyField;
}

[Test]
public void SelfReferenced_WithWritableValueTypeField_Test()
{
SelfReferencedWithWritableValueTypeField original = new SelfReferencedWithWritableValueTypeField
{
WithWritableValueTypeField = new ClassWithWritableValueTypeField()
};

SelfReferencedWithWritableValueTypeField clone = original.DeepClone();

Assert.That(clone, Is.Not.SameAs(original));
Assert.That(clone.WithWritableValueTypeField, Is.Not.SameAs(original.WithWritableValueTypeField));
Assert.That(clone.WithWritableValueTypeField.ReadOnlyValue, Is.EqualTo(original.WithWritableValueTypeField.ReadOnlyValue));
}

private class SelfReferencedWithWritableValueTypeField
{
public SelfReferencedWithWritableValueTypeField? Predecessor { get; set; }

public ClassWithWritableValueTypeField WithWritableValueTypeField { get; set; }
}

private class ClassWithWritableValueTypeField
{
private decimal readOnlyField = 1m;
public decimal ReadOnlyValue => readOnlyField;
}

[Test]
public void SelfReferenced_WithMultipleReadOnlyProperties_Test()
{
SelfReferencedWithMultipleReadOnlyProperties original = new SelfReferencedWithMultipleReadOnlyProperties
{
WithMultipleReadOnlyProperties = new ClassWithMultipleReadOnlyProperties()
};

SelfReferencedWithMultipleReadOnlyProperties clone = original.DeepClone();

Assert.That(clone, Is.Not.SameAs(original));
Assert.That(clone.WithMultipleReadOnlyProperties, Is.Not.SameAs(original.WithMultipleReadOnlyProperties));
Assert.That(clone.WithMultipleReadOnlyProperties.Name, Is.EqualTo(original.WithMultipleReadOnlyProperties.Name));
Assert.That(clone.WithMultipleReadOnlyProperties.Id, Is.EqualTo(original.WithMultipleReadOnlyProperties.Id));
}

private class SelfReferencedWithMultipleReadOnlyProperties
{
public SelfReferencedWithMultipleReadOnlyProperties? Predecessor { get; set; }

public ClassWithMultipleReadOnlyProperties WithMultipleReadOnlyProperties { get; set; }
}

private class ClassWithMultipleReadOnlyProperties
{
public int Id { get; } = 1;
public string Name { get; } = "Test";
}
}
5 changes: 3 additions & 2 deletions FastCloner.Tests/TypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

namespace FastCloner.Tests;

[TestFixture]
public class TypeTests
[TestFixture(Low)]
[TestFixture(High)]
public class TypeTests(int maxRecursionDepth) : BaseTestFixture(maxRecursionDepth)
{
[Test]
public void StandardTypes_Should_Be_Cloned()
Expand Down
4 changes: 2 additions & 2 deletions FastCloner/Code/FastClonerCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ internal static bool IsTypeIgnored(Type type)
private static readonly ClrCache<object> deepClassToCache = new ClrCache<object>();
private static readonly ClrCache<object> shallowClassToCache = new ClrCache<object>();
private static readonly ConcurrentLazyCache<object> typeConvertCache = new ConcurrentLazyCache<object>();
private static readonly ClrCache<object?> fieldCache = new ClrCache<object?>();
private static readonly GenericClrCache<Tuple<Type, string>, object?> fieldCache = new GenericClrCache<Tuple<Type, string>, object?>();
private static readonly ClrCache<Dictionary<string, Type>> ignoredEventInfoCache = new ClrCache<Dictionary<string, Type>>();
private static readonly ClrCache<List<MemberInfo>> allMembersCache = new ClrCache<List<MemberInfo>>();
private static readonly GenericClrCache<MemberInfo, bool> memberIgnoreStatusCache = new GenericClrCache<MemberInfo, bool>();
private static readonly ClrCache<bool> typeContainsIgnoredMembersCache = new ClrCache<bool>();
private static readonly ClrCache<object> specialTypesCache = new ClrCache<object>();

public static object? GetOrAddField(Type type, Func<Type, object?> valueFactory) => fieldCache.GetOrAdd(type, valueFactory);
public static object? GetOrAddField(Type type, string name, Func<Type, object?> valueFactory) => fieldCache.GetOrAdd(new Tuple<Type, string>(type, name), k => valueFactory(k.Item1));
public static object? GetOrAddClass(Type type, Func<Type, object?> valueFactory) => classCache.GetOrAdd(type, valueFactory);
public static object? GetOrAddStructAsObject(Type type, Func<Type, object?> valueFactory) => structCache.GetOrAdd(type, valueFactory);
public static object GetOrAddDeepClassTo(Type type, Func<Type, object> valueFactory) => deepClassToCache.GetOrAdd(type, valueFactory);
Expand Down
2 changes: 1 addition & 1 deletion FastCloner/Code/FastClonerExprGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ private static LabelTarget CreateLoopLabel(ExpressionPosition position)

internal static object? GenerateProcessMethod(Type realType, bool asObject) => GenerateProcessMethod(realType, asObject && realType.IsValueType(), new ExpressionPosition(0, 0));
public static bool IsSetType(Type type) => type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ISet<>));
private static bool IsDictionaryType(Type type) => typeof(IDictionary).IsAssignableFrom(type) || type.GetInterfaces().Any(i => i.IsGenericType && (i.GetGenericTypeDefinition() == typeof(IDictionary<,>) || i.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)));
public static bool IsDictionaryType(Type type) => typeof(IDictionary).IsAssignableFrom(type) || type.GetInterfaces().Any(i => i.IsGenericType && (i.GetGenericTypeDefinition() == typeof(IDictionary<,>) || i.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)));

private readonly struct ConstructorInfoEx
{
Expand Down
Loading