Skip to content

Commit

Permalink
🐛 CopyPropertiesTo should copy values from non-nullable to nullable w…
Browse files Browse the repository at this point in the history
…ith same underlying type
  • Loading branch information
fluffynuts committed Dec 17, 2024
1 parent 62ae8a6 commit 65f644c
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
using System.Collections.Generic;
using NUnit.Framework;
using static PeanutButter.RandomGenerators.RandomValueGen;
using NExpect;
using PeanutButter.RandomGenerators.Core.Tests.Domain;
using static NExpect.Expectations;

namespace PeanutButter.RandomGenerators.Core.Tests
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
using System;
using System.Collections.Generic;
using NSubstitute;
using NUnit.Framework;

namespace PeanutButter.RandomGenerators.Core.Tests;

[TestFixture]
public partial class TestObjectExtensions
public class TestObjectExtensions
{
[TestFixture]
public class Randomized
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,10 @@ private static string ZeroPad(int value, int places = 2)
{
var result = value.ToString();
while (result.Length < places)
{
result = "0" + result;
}

return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,7 @@ public class CopyingPropertiesFromOneObjectToAnother
[Test]
public void GivenDestWithSameProperty_CopiesValue()
{
TestCopyFor<Numbers>();
TestCopyFor<string>();
TestCopyFor<int>();
TestCopyFor<byte>();
Expand Down Expand Up @@ -1145,7 +1146,67 @@ private static void TestCopyFor<T>()
var src = new Simple<T>();
var dst = new Simple<T>();
while (dst.prop.Equals(src.prop))
{
dst.Randomize();
}

src.CopyPropertiesTo(dst);

//---------------Test Result -----------------------
Expect(dst.prop)
.To.Equal(src.prop);
}

[Test]
public void ShouldCopyNullableWithValueToNonNullable()
{
// Arrange
// Act
TestNullableCopyFor<Numbers>();
TestNullableCopyFor<int>();
TestNullableCopyFor<byte>();
TestNullableCopyFor<char>();
TestNullableCopyFor<long>();
TestNullableCopyFor<float>();
TestNullableCopyFor<double>();
TestNullableCopyFor<decimal>();
TestNullableCopyFor<DateTime>();
TestNullableCopyFor<bool>();
// Assert
}

public class Foo1
{
public Numbers Numbers { get; set; }
}

public class Foo2
{
public Numbers? Numbers { get; set; }
}

public enum Numbers
{
One,
Two,
Three
}

private static void TestNullableCopyFor<T>()
where T : struct
{
//---------------Set up test pack-------------------

//---------------Assert Precondition----------------

//---------------Execute Test ----------------------
var src = GetRandom<Simple<T>>();
var dst = new NullableSimple<T>();
while (dst.prop.Equals(src.prop))
{
dst.Randomize();
}

src.CopyPropertiesTo(dst);

//---------------Test Result -----------------------
Expand All @@ -1161,7 +1222,9 @@ public void ComplexTypesAreTraversedButOnlySimplePropertiesAreCopied()
var src = new Complex<int>();
var dst = new Complex<int>();
while (dst.prop.prop.Equals(src.prop.prop))
{
dst.prop.Randomize();
}

//---------------Assert Precondition----------------
Expect(dst)
Expand All @@ -1187,7 +1250,9 @@ public void WhenDeepIsFalse_ComplexTypesAreTraversedAndRefCopied()
var src = new Complex<int>();
var dst = new Complex<int>();
while (dst.prop.prop.Equals(src.prop.prop))
{
dst.prop.Randomize();
}

//---------------Assert Precondition----------------
Expect(dst)
Expand Down Expand Up @@ -3687,6 +3752,22 @@ public void Randomize()
}
}

public class NullableSimple<T>
where T : struct
{
public T? prop { get; set; }

public NullableSimple()
{
Randomize();
}

public void Randomize()
{
prop = GetRandom<T?>();
}
}

public class Complex<T>
{
public Simple<T> prop { get; set; }
Expand Down
26 changes: 26 additions & 0 deletions source/Utils/PeanutButter.Utils/ObjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,30 @@ public static void CopyPropertiesTo(this object src, object dst, bool deep, para
pi => Tuple.Create(pi.Name, pi.PropertyType),
pi => pi
);
foreach (var item in targetPropertyCache.ToArray())
{
if (!item.Value.PropertyType.IsNullableType())
{
continue;
}

var underlyingType = Nullable.GetUnderlyingType(
item.Value.PropertyType
);
if (underlyingType is null)
{
continue;
}

var key = Tuple.Create(
item.Key.Item1,
underlyingType
);
targetPropertyCache.TryAdd(
key,
item.Value
);
}

foreach (var srcPropInfo in srcPropInfos.Where(
pi => pi.CanRead &&
Expand Down Expand Up @@ -457,6 +481,8 @@ out var matchingTarget
}
}

private static readonly Type NullableType = typeof(Nullable<>);

private static readonly ConcurrentDictionary<Type, PropertyInfo[]> PropertyCache = new();

private static readonly Func<bool, PropertyInfo, PropertyInfo, object, object, bool>[]
Expand Down

0 comments on commit 65f644c

Please sign in to comment.