diff --git a/src/NHibernate.Test/NHSpecificTest/NH3957/ResultTransformerEqualityFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3957/ResultTransformerEqualityFixture.cs new file mode 100644 index 00000000000..cd0db9a6f14 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3957/ResultTransformerEqualityFixture.cs @@ -0,0 +1,136 @@ +using System.Collections.Generic; +using System.Reflection; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH3957 +{ + [TestFixture] + public class ResultTransformerEqualityFixture + { + /// + /// Allows to simulate a hashcode collision. Issue would be unpractical to test otherwise. + /// Hashcode collision must be supported for avoiding unexpected and hard to reproduce failures. + /// + private void TweakHashcode(System.Type transformerToTweak, object hasher) + { + var hasherTargetField = transformerToTweak.GetField("Hasher", BindingFlags.Static | BindingFlags.NonPublic); + if (!_hasherBackup.ContainsKey(transformerToTweak)) + _hasherBackup.Add(transformerToTweak, hasherTargetField.GetValue(null)); + + // Though hasher is a readonly field, this works at the time of this writing. If it starts breaking and cannot be fixed, + // ignore those tests or throw them away. + hasherTargetField.SetValue(null, hasher); + } + + private Dictionary _hasherBackup = new Dictionary(); + + [SetUp] + public void Setup() + { + var hasherForAll = typeof(AliasToEntityMapResultTransformer) + .GetField("Hasher", BindingFlags.Static | BindingFlags.NonPublic) + .GetValue(null); + TweakHashcode(typeof(DistinctRootEntityResultTransformer), hasherForAll); + TweakHashcode(typeof(PassThroughResultTransformer), hasherForAll); + TweakHashcode(typeof(RootEntityResultTransformer), hasherForAll); + TweakHashcode(typeof(ToListResultTransformer), hasherForAll); + } + + [TearDown] + public void TearDown() + { + // Restore those types hashcode. (Avoid impacting perf of other tests, avoid second level query cache + // issues if it was holding cached entries (but would mean some tests have not cleaned up properly).) + foreach(var backup in _hasherBackup) + { + TweakHashcode(backup.Key, backup.Value); + } + } + + // Non reg test case + [Test] + public void AliasToEntityMapEquality() + { + var transf1 = new AliasToEntityMapResultTransformer(); + var transf2 = new AliasToEntityMapResultTransformer(); + + Assert.IsTrue(transf1.Equals(transf2)); + Assert.IsTrue(transf2.Equals(transf1)); + } + + [Test] + public void AliasToEntityMapAndDistinctRootEntityInequality() + { + var transf1 = new AliasToEntityMapResultTransformer(); + var transf2 = new DistinctRootEntityResultTransformer(); + + Assert.IsFalse(transf1.Equals(transf2)); + Assert.IsFalse(transf2.Equals(transf1)); + } + + // Non reg test case + [Test] + public void DistinctRootEntityEquality() + { + var transf1 = new DistinctRootEntityResultTransformer(); + var transf2 = new DistinctRootEntityResultTransformer(); + + Assert.IsTrue(transf1.Equals(transf2)); + Assert.IsTrue(transf2.Equals(transf1)); + } + + // Non reg test case + [Test] + public void PassThroughEquality() + { + var transf1 = new PassThroughResultTransformer(); + var transf2 = new PassThroughResultTransformer(); + + Assert.IsTrue(transf1.Equals(transf2)); + Assert.IsTrue(transf2.Equals(transf1)); + } + + [Test] + public void PassThroughAndRootEntityInequality() + { + var transf1 = new PassThroughResultTransformer(); + var transf2 = new RootEntityResultTransformer(); + + Assert.IsFalse(transf1.Equals(transf2)); + Assert.IsFalse(transf2.Equals(transf1)); + } + + // Non reg test case + [Test] + public void RootEntityEquality() + { + var transf1 = new RootEntityResultTransformer(); + var transf2 = new RootEntityResultTransformer(); + + Assert.IsTrue(transf1.Equals(transf2)); + Assert.IsTrue(transf2.Equals(transf1)); + } + + [Test] + public void RootEntityAndToListInequality() + { + var transf1 = new RootEntityResultTransformer(); + var transf2 = new ToListResultTransformer(); + + Assert.IsFalse(transf1.Equals(transf2)); + Assert.IsFalse(transf2.Equals(transf1)); + } + + // Non reg test case + [Test] + public void ToListEquality() + { + var transf1 = new ToListResultTransformer(); + var transf2 = new ToListResultTransformer(); + + Assert.IsTrue(transf1.Equals(transf2)); + Assert.IsTrue(transf2.Equals(transf1)); + } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index b60144583fe..faa4c8486b9 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -940,6 +940,7 @@ + diff --git a/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs b/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs index d9fceb20077..3fe474856fe 100644 --- a/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs +++ b/src/NHibernate/Transform/AliasToEntityMapResultTransformer.cs @@ -30,18 +30,20 @@ public override IList TransformList(IList collection) } - public override bool IsTransformedValueATupleElement(String[] aliases, int tupleLength) + public override bool IsTransformedValueATupleElement(string[] aliases, int tupleLength) { return false; } public override bool Equals(object obj) { - if (obj == null) + if (obj == null || obj.GetHashCode() != Hasher.GetHashCode()) { return false; } - return obj.GetHashCode() == Hasher.GetHashCode(); + // NH-3957: do not rely on hashcode alone. + // Must be the exact same type + return obj.GetType() == typeof(AliasToEntityMapResultTransformer); } public override int GetHashCode() diff --git a/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs b/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs index 8167ff95d64..24f4ebac408 100644 --- a/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs +++ b/src/NHibernate/Transform/DistinctRootEntityResultTransformer.cs @@ -78,11 +78,13 @@ public bool IsTransformedValueATupleElement(String[] aliases, int tupleLength) public override bool Equals(object obj) { - if (obj == null) + if (obj == null || obj.GetHashCode() != Hasher.GetHashCode()) { return false; } - return obj.GetHashCode() == Hasher.GetHashCode(); + // NH-3957: do not rely on hashcode alone. + // Must be the exact same type + return obj.GetType() == typeof(DistinctRootEntityResultTransformer); } public override int GetHashCode() diff --git a/src/NHibernate/Transform/PassThroughResultTransformer.cs b/src/NHibernate/Transform/PassThroughResultTransformer.cs index 37715b387c3..0a116b51aff 100644 --- a/src/NHibernate/Transform/PassThroughResultTransformer.cs +++ b/src/NHibernate/Transform/PassThroughResultTransformer.cs @@ -61,11 +61,13 @@ internal object[] UntransformToTuple(object transformed, bool isSingleResult) public override bool Equals(object obj) { - if (obj == null) + if (obj == null || obj.GetHashCode() != Hasher.GetHashCode()) { return false; } - return obj.GetHashCode() == Hasher.GetHashCode(); + // NH-3957: do not rely on hashcode alone. + // Must be the exact same type + return obj.GetType() == typeof(PassThroughResultTransformer); } public override int GetHashCode() diff --git a/src/NHibernate/Transform/RootEntityResultTransformer.cs b/src/NHibernate/Transform/RootEntityResultTransformer.cs index 0de6bf502c5..b8468ebbc0c 100644 --- a/src/NHibernate/Transform/RootEntityResultTransformer.cs +++ b/src/NHibernate/Transform/RootEntityResultTransformer.cs @@ -43,11 +43,13 @@ public bool[] IncludeInTransform(String[] aliases, int tupleLength) public override bool Equals(object obj) { - if (obj == null) + if (obj == null || obj.GetHashCode() != Hasher.GetHashCode()) { return false; } - return obj.GetHashCode() == Hasher.GetHashCode(); + // NH-3957: do not rely on hashcode alone. + // Must be the exact same type + return obj.GetType() == typeof(RootEntityResultTransformer); } public override int GetHashCode() diff --git a/src/NHibernate/Transform/ToListResultTransformer.cs b/src/NHibernate/Transform/ToListResultTransformer.cs index 137f330b30d..e6ccc1977bd 100644 --- a/src/NHibernate/Transform/ToListResultTransformer.cs +++ b/src/NHibernate/Transform/ToListResultTransformer.cs @@ -25,11 +25,13 @@ public IList TransformList(IList list) public override bool Equals(object obj) { - if (obj == null) + if (obj == null || obj.GetHashCode() != Hasher.GetHashCode()) { return false; } - return obj.GetHashCode() == Hasher.GetHashCode(); + // NH-3957: do not rely on hashcode alone. + // Must be the exact same type + return obj.GetType() == typeof(ToListResultTransformer); } public override int GetHashCode()