Skip to content

Commit 9d81858

Browse files
NH-3954: fix and test cases.
1 parent 0f88aa1 commit 9d81858

File tree

5 files changed

+294
-3
lines changed

5 files changed

+294
-3
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace NHibernate.Test.NHSpecificTest.NH3954
5+
{
6+
class Entity1
7+
{
8+
}
9+
class Entity1FakeProxy
10+
{
11+
}
12+
class Entity1FakeProxy2
13+
{
14+
}
15+
16+
class Entity2
17+
{
18+
}
19+
class Entity2FakeProxy
20+
{
21+
}
22+
class Entity2FakeProxy2
23+
{
24+
}
25+
26+
class Entity3
27+
{
28+
}
29+
class Entity3FakeProxy
30+
{
31+
}
32+
class Entity3FakeProxy2
33+
{
34+
}
35+
36+
class Entity4
37+
{
38+
}
39+
class Entity4FakeProxy
40+
{
41+
}
42+
class Entity4FakeProxy2
43+
{
44+
}
45+
46+
class Entity5
47+
{
48+
}
49+
class Entity5FakeProxy
50+
{
51+
}
52+
class Entity5FakeProxy2
53+
{
54+
}
55+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System.Reflection;
2+
using NHibernate.Proxy;
3+
using NHibernate.Proxy.DynamicProxy;
4+
using NUnit.Framework;
5+
6+
namespace NHibernate.Test.NHSpecificTest.NH3954
7+
{
8+
[TestFixture]
9+
public class EqualsFixture
10+
{
11+
private static readonly FieldInfo HashCodeField =
12+
typeof(ProxyCacheEntry).GetField("hashCode", BindingFlags.Instance | BindingFlags.NonPublic);
13+
14+
/// <summary>
15+
/// Allows to simulate a hashcode collision. Issue would be unpractical to test otherwise.
16+
/// Hashcode collision must be supported for avoiding unexpected and hard to reproduce failures.
17+
/// </summary>
18+
private void TweakEntry(ProxyCacheEntry entryToTweak, int hashcode)
19+
{
20+
// Though hashCode is a readonly field, this works at the time of this writing. If it starts breaking and cannot be fixed,
21+
// ignore those tests or throw them away.
22+
HashCodeField.SetValue(entryToTweak, hashcode);
23+
}
24+
25+
// Non reg test case
26+
[Test]
27+
public void TypeEquality()
28+
{
29+
var entry1 = new ProxyCacheEntry(typeof(Entity1), null);
30+
var entry2 = new ProxyCacheEntry(typeof(Entity1), new System.Type[0]);
31+
Assert.IsTrue(entry1.Equals(entry2));
32+
Assert.IsTrue(entry2.Equals(entry1));
33+
}
34+
35+
[Test]
36+
public void TypeInequality()
37+
{
38+
var entry1 = new ProxyCacheEntry(typeof(Entity1), null);
39+
var entry2 = new ProxyCacheEntry(typeof(Entity2), null);
40+
TweakEntry(entry2, entry1.GetHashCode());
41+
Assert.IsFalse(entry1.Equals(entry2));
42+
Assert.IsFalse(entry2.Equals(entry1));
43+
}
44+
45+
// Non reg test case
46+
[Test]
47+
public void InterfaceEquality()
48+
{
49+
var entry1 = new ProxyCacheEntry(typeof(Entity1), new[] { typeof(INHibernateProxy), typeof(IProxy) });
50+
// Interfaces order inverted on purpose: must be supported.
51+
var entry2 = new ProxyCacheEntry(typeof(Entity1), new[] { typeof(IProxy), typeof(INHibernateProxy) });
52+
Assert.IsTrue(entry1.Equals(entry2));
53+
Assert.IsTrue(entry2.Equals(entry1));
54+
}
55+
56+
[Test]
57+
public void InterfaceInequality()
58+
{
59+
var entry1 = new ProxyCacheEntry(typeof(Entity1), new[] { typeof(INHibernateProxy), typeof(IProxy) });
60+
var entry2 = new ProxyCacheEntry(typeof(Entity1), new[] { typeof(IProxy) });
61+
TweakEntry(entry2, entry1.GetHashCode());
62+
Assert.IsFalse(entry1.Equals(entry2));
63+
Assert.IsFalse(entry2.Equals(entry1));
64+
}
65+
}
66+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
using System.Collections.Concurrent;
2+
using System.Reflection;
3+
using NHibernate.Proxy;
4+
using NHibernate.Proxy.DynamicProxy;
5+
using NUnit.Framework;
6+
7+
namespace NHibernate.Test.NHSpecificTest.NH3954
8+
{
9+
[TestFixture, Explicit("Demonstrates bug impact on cache, but which tests will fail is a bit unpredictable")]
10+
public class ProxyCacheFixture
11+
{
12+
private ProxyCache _cache;
13+
private ConcurrentDictionary<ProxyCacheEntry, System.Type> _internalCache;
14+
private int _hashCode1;
15+
private int _hashCode2;
16+
17+
private static readonly FieldInfo InternalCacheField =
18+
typeof(ProxyCache).GetField("cache", BindingFlags.Static | BindingFlags.NonPublic);
19+
20+
[SetUp]
21+
public void SetUp()
22+
{
23+
_cache = new ProxyCache();
24+
25+
_internalCache = (ConcurrentDictionary<ProxyCacheEntry, System.Type>)InternalCacheField.GetValue(null);
26+
27+
_cache.StoreProxyType(typeof(Entity1FakeProxy), typeof(Entity1));
28+
_cache.StoreProxyType(typeof(Entity2FakeProxy), typeof(Entity2), typeof(INHibernateProxy));
29+
_cache.StoreProxyType(typeof(Entity3FakeProxy), typeof(Entity3));
30+
_cache.StoreProxyType(typeof(Entity4FakeProxy), typeof(Entity4), typeof(IProxy));
31+
_cache.StoreProxyType(typeof(Entity5FakeProxy), typeof(Entity5), typeof(INHibernateProxy), typeof(IProxy));
32+
33+
// Artificially inject other entries with same hashcodes
34+
_hashCode1 = new ProxyCacheEntry(typeof(Entity1), null).GetHashCode();
35+
Inject(new ProxyCacheEntry(typeof(Entity1), new[] { typeof(INHibernateProxy) }), _hashCode1, typeof(Entity1FakeProxy2));
36+
Inject(new ProxyCacheEntry(typeof(Entity3), null), _hashCode1, typeof(Entity3FakeProxy2));
37+
38+
_hashCode2 = new ProxyCacheEntry(typeof(Entity2), new[] { typeof(INHibernateProxy) }).GetHashCode();
39+
Inject(new ProxyCacheEntry(typeof(Entity2), null), _hashCode2, typeof(Entity2FakeProxy2));
40+
Inject(new ProxyCacheEntry(typeof(Entity4), new[] { typeof(IProxy) }), _hashCode2, typeof(Entity4FakeProxy2));
41+
Inject(new ProxyCacheEntry(typeof(Entity5), new[] { typeof(INHibernateProxy), typeof(IProxy) }), _hashCode2, typeof(Entity5FakeProxy2));
42+
}
43+
44+
private void Inject(ProxyCacheEntry entryToTweak, int hashcode, System.Type result)
45+
{
46+
TweakEntry(entryToTweak, hashcode);
47+
_internalCache[entryToTweak] = result;
48+
}
49+
50+
private static readonly FieldInfo HashCodeField =
51+
typeof(ProxyCacheEntry).GetField("hashCode", BindingFlags.Instance | BindingFlags.NonPublic);
52+
53+
/// <summary>
54+
/// Allows to simulate a hashcode collision. Issue would be unpractical to test otherwise.
55+
/// Hashcode collision must be supported for avoiding unexpected and hard to reproduce failures.
56+
/// </summary>
57+
private void TweakEntry(ProxyCacheEntry entryToTweak, int hashcode)
58+
{
59+
// Though hashCode is a readonly field, this works at the time of this writing. If it starts breaking and cannot be fixed,
60+
// ignore those tests or throw them away.
61+
HashCodeField.SetValue(entryToTweak, hashcode);
62+
}
63+
64+
[Test]
65+
public void ProxyCacheEntity1FakeProxy()
66+
{
67+
var result = _cache.GetProxyType(typeof(Entity1));
68+
Assert.AreEqual(typeof(Entity1FakeProxy), result);
69+
}
70+
71+
[Test]
72+
public void ProxyCacheEntity1FakeProxy2()
73+
{
74+
var entry = new ProxyCacheEntry(typeof(Entity1), new[] { typeof(INHibernateProxy) });
75+
TweakEntry(entry, _hashCode1);
76+
var result = _internalCache[entry];
77+
Assert.AreEqual(typeof(Entity1FakeProxy2), result);
78+
}
79+
80+
[Test]
81+
public void ProxyCacheEntity2FakeProxy()
82+
{
83+
var result = _cache.GetProxyType(typeof(Entity2), typeof(INHibernateProxy));
84+
Assert.AreEqual(typeof(Entity2FakeProxy), result);
85+
}
86+
87+
[Test]
88+
public void ProxyCacheEntity2FakeProxy2()
89+
{
90+
var entry = new ProxyCacheEntry(typeof(Entity2), null);
91+
TweakEntry(entry, _hashCode2);
92+
var result = _internalCache[entry];
93+
Assert.AreEqual(typeof(Entity2FakeProxy2), result);
94+
}
95+
96+
[Test]
97+
public void ProxyCacheEntity3FakeProxy()
98+
{
99+
var result = _cache.GetProxyType(typeof(Entity3));
100+
Assert.AreEqual(typeof(Entity3FakeProxy), result);
101+
}
102+
103+
[Test]
104+
public void ProxyCacheEntity3FakeProxy2()
105+
{
106+
var entry = new ProxyCacheEntry(typeof(Entity3), null);
107+
TweakEntry(entry, _hashCode1);
108+
var result = _internalCache[entry];
109+
Assert.AreEqual(typeof(Entity3FakeProxy2), result);
110+
}
111+
112+
[Test]
113+
public void ProxyCacheEntity4FakeProxy()
114+
{
115+
var result = _cache.GetProxyType(typeof(Entity4), typeof(IProxy));
116+
Assert.AreEqual(typeof(Entity4FakeProxy), result);
117+
}
118+
119+
[Test]
120+
public void ProxyCacheEntity4FakeProxy2()
121+
{
122+
var entry = new ProxyCacheEntry(typeof(Entity4), new[] { typeof(IProxy) });
123+
TweakEntry(entry, _hashCode2);
124+
var result = _internalCache[entry];
125+
Assert.AreEqual(typeof(Entity4FakeProxy2), result);
126+
}
127+
128+
[Test]
129+
public void ProxyCacheEntity5FakeProxy()
130+
{
131+
var result = _cache.GetProxyType(typeof(Entity5), typeof(IProxy), typeof(INHibernateProxy));
132+
Assert.AreEqual(typeof(Entity5FakeProxy), result);
133+
}
134+
135+
[Test]
136+
public void ProxyCacheEntity5FakeProxy2()
137+
{
138+
// Interfaces order inverted on purpose: must be supported.
139+
var entry = new ProxyCacheEntry(typeof(Entity5), new[] { typeof(IProxy), typeof(INHibernateProxy) });
140+
TweakEntry(entry, _hashCode2);
141+
var result = _internalCache[entry];
142+
Assert.AreEqual(typeof(Entity5FakeProxy2), result);
143+
}
144+
145+
[Test]
146+
public void ProxyCacheNone()
147+
{
148+
// Beware not testing the lookup failure of any combination, even tweaked, actually added in cache.
149+
// (Otherwise the test may starts failing unexpectedly sometimes, as the original bug ...)
150+
// This one was not added in anyway.
151+
System.Type result;
152+
Assert.IsFalse(_cache.TryGetProxyType(typeof(Entity2), new[] { typeof(IProxy) }, out result));
153+
}
154+
}
155+
}

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,9 @@
742742
<Compile Include="NHSpecificTest\NH3961\DateParametersComparedTo.cs" />
743743
<Compile Include="NHSpecificTest\NH3963\Entity.cs" />
744744
<Compile Include="NHSpecificTest\NH3963\MappedAsFixture.cs" />
745+
<Compile Include="NHSpecificTest\NH3954\Entity.cs" />
746+
<Compile Include="NHSpecificTest\NH3954\EqualsFixture.cs" />
747+
<Compile Include="NHSpecificTest\NH3954\ProxyCacheFixture.cs" />
745748
<Compile Include="NHSpecificTest\NH3950\Entity.cs" />
746749
<Compile Include="NHSpecificTest\NH3950\Fixture.cs" />
747750
<Compile Include="NHSpecificTest\NH3952\Entity.cs" />

src/NHibernate/Proxy/DynamicProxy/ProxyCacheEntry.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public ProxyCacheEntry(System.Type baseType, System.Type[] interfaces)
2424
}
2525
BaseType = baseType;
2626
Interfaces = interfaces ?? new System.Type[0];
27+
_uniqueInterfaces = new HashSet<System.Type>(Interfaces);
2728

2829
if (Interfaces.Length == 0)
2930
{
@@ -32,7 +33,7 @@ public ProxyCacheEntry(System.Type baseType, System.Type[] interfaces)
3233
}
3334

3435
// duplicated type exclusion
35-
var set = new HashSet<System.Type>(Interfaces) { baseType };
36+
var set = new HashSet<System.Type>(_uniqueInterfaces) { baseType };
3637
hashCode = 59;
3738
foreach (System.Type type in set)
3839
{
@@ -43,15 +44,26 @@ public ProxyCacheEntry(System.Type baseType, System.Type[] interfaces)
4344
public System.Type BaseType { get; private set; }
4445
public System.Type[] Interfaces { get; private set; }
4546

47+
[NonSerialized]
48+
private HashSet<System.Type> _uniqueInterfaces;
49+
4650
public override bool Equals(object obj)
4751
{
4852
var that = obj as ProxyCacheEntry;
49-
if (ReferenceEquals(null, that))
53+
if (ReferenceEquals(null, that) ||
54+
hashCode != that.hashCode ||
55+
BaseType != that.BaseType)
5056
{
5157
return false;
5258
}
5359

54-
return hashCode == that.GetHashCode();
60+
// In case the instance was de-serialized, the _uniqueInterfaces will be null. Re-fetch its _uniqueInterfaces if needed.
61+
if (_uniqueInterfaces == null)
62+
_uniqueInterfaces = new HashSet<System.Type>(Interfaces);
63+
if (that._uniqueInterfaces == null)
64+
that._uniqueInterfaces = new HashSet<System.Type>(that.Interfaces);
65+
66+
return _uniqueInterfaces.SetEquals(that._uniqueInterfaces);
5567
}
5668

5769
public override int GetHashCode()

0 commit comments

Comments
 (0)