Skip to content

Commit fe74771

Browse files
jwoodman510jeremydmiller
authored andcommitted
add unit tests
1 parent 2a93dc4 commit fe74771

File tree

3 files changed

+156
-2
lines changed

3 files changed

+156
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
using Lamar.IoC;
2+
using Shouldly;
3+
using System;
4+
using Xunit;
5+
6+
namespace Lamar.Testing.Bugs;
7+
8+
public class Bug_398_service_registry_hash_collisions
9+
{
10+
[Fact]
11+
public void GetHashCollisions_RegistryWithCollisions()
12+
{
13+
var registry = GetRegistryWithCollisions();
14+
15+
var collisions = registry.GetInstanceHashCollisions();
16+
17+
collisions.ShouldNotBeEmpty();
18+
}
19+
20+
[Fact]
21+
public void GetHashCollisions_RegistryWithoutCollisions()
22+
{
23+
var registry = GetRegistryWithoutCollisions();
24+
25+
var collisions = registry.GetInstanceHashCollisions();
26+
27+
collisions.ShouldBeEmpty();
28+
}
29+
30+
[Fact]
31+
public void TryRemoveHashCollisions_RegistryWithCollisions()
32+
{
33+
var registry = GetRegistryWithCollisions();
34+
35+
var removed = registry.TryRemoveInstanceHashCollisions();
36+
37+
removed.ShouldBeTrue();
38+
}
39+
40+
[Fact]
41+
public void TryRemoveHashCollisions_RegistryWithoutCollisions()
42+
{
43+
var registry = GetRegistryWithoutCollisions();
44+
45+
var removed = registry.TryRemoveInstanceHashCollisions();
46+
47+
removed.ShouldBeFalse();
48+
}
49+
50+
[Fact]
51+
public void HandleInstanceHashCollisions()
52+
{
53+
var registry = GetRegistryWithCollisions();
54+
55+
registry.TryRemoveInstanceHashCollisions();
56+
57+
registry.GetInstanceHashCollisions().ShouldBeEmpty();
58+
59+
var container = new Container(registry);
60+
container.GetInstance<IFoo>().ShouldBeOfType<Foo>();
61+
container.GetInstance<IBar>().ShouldBeOfType<Bar>();
62+
}
63+
64+
[Fact]
65+
public void MitigateInstanceHashCollisions()
66+
{
67+
var registry = GetRegistryWithCollisions();
68+
69+
registry.MitigateInstanceHashCollisions();
70+
71+
registry.GetInstanceHashCollisions().ShouldBeEmpty();
72+
73+
var container = new Container(registry);
74+
container.GetInstance<IFoo>().ShouldBeOfType<Foo>();
75+
container.GetInstance<IBar>().ShouldBeOfType<Bar>();
76+
}
77+
78+
[Fact]
79+
public void MitigateInstanceHashCollisionsThrowsAfterLimitReached()
80+
{
81+
var registry = GetRegistryWithCollisions();
82+
83+
var mitigateCollisions = () => registry.MitigateInstanceHashCollisions(0);
84+
85+
mitigateCollisions.ShouldThrow<LamarInstanceHashCollisionException>();
86+
}
87+
88+
[Fact]
89+
public void MitigateInstanceHashCollisionsUsesCustomRenamePolicy()
90+
{
91+
var registry = GetRegistryWithCollisions();
92+
93+
var instanceFoo = registry.For<IFoo>().Use<Foo>();
94+
var instanceBar = registry.For<IBar>().Use<Bar>();
95+
96+
instanceFoo.Hash = instanceBar.Hash = 1;
97+
98+
registry.MitigateInstanceHashCollisions(instanceRenamePolicy: x => $"{x}.updated");
99+
100+
instanceFoo.Name.ShouldEndWith(".updated");
101+
instanceBar.Name.ShouldEndWith(".updated");
102+
}
103+
104+
[Fact]
105+
public void NamedCollisionThrows()
106+
{
107+
var registry = new ServiceRegistry();
108+
109+
registry.For<IFoo>().Use<Foo>().Named("foo").Hash = 1;
110+
registry.For<IBar>().Use<Bar>().Named("bar").Hash = 1;
111+
112+
Action handleCollisions = () => registry.TryRemoveInstanceHashCollisions();
113+
114+
handleCollisions.ShouldThrow<LamarInstanceHashCollisionException>();
115+
}
116+
117+
118+
private ServiceRegistry GetRegistryWithCollisions()
119+
{
120+
var registry = new ServiceRegistry();
121+
122+
registry.For<IFoo>().Use<Foo>().Hash = 1;
123+
registry.For<IBar>().Use<Bar>().Hash = 1;
124+
125+
return registry;
126+
}
127+
128+
private ServiceRegistry GetRegistryWithoutCollisions()
129+
{
130+
var registry = new ServiceRegistry();
131+
132+
registry.For<IFoo>().Use<Foo>().Hash = 1;
133+
registry.For<IBar>().Use<Bar>().Hash = 2;
134+
135+
return registry;
136+
}
137+
138+
public interface IFoo { }
139+
public interface IBar { }
140+
public class Foo : IFoo { }
141+
public class Bar : IBar { }
142+
}

Diff for: src/Lamar/IoC/LamarInstanceHashCollisionException.cs

+5
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ namespace Lamar.IoC;
77

88
public class LamarInstanceHashCollisionException : LamarException
99
{
10+
public int InstanceHash { get; }
11+
public IEnumerable<Type> ServiceTypes { get; }
12+
1013
public LamarInstanceHashCollisionException(int instanceHash, IEnumerable<Type> serviceTypes) : base(
1114
$"Duplicate hash '{instanceHash}' generated for services: {string.Join(", ", serviceTypes.Select(x => x.FullNameInCode()))}")
1215
{
16+
InstanceHash = instanceHash;
17+
ServiceTypes = serviceTypes;
1318
}
1419
}

Diff for: src/Lamar/ServiceRegistry.HashCollisionMitigation.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ namespace Lamar;
88

99
public partial class ServiceRegistry
1010
{
11+
internal static readonly Func<string, string> DefaultInstanceRenamePolicy = name => $"{name}_x";
12+
1113
/// <summary>
1214
/// Finds and mitigates <see cref="Instance"/> hash code collisions in the current <see cref="ServiceRegistry"/> instance.<br/>
1315
/// Hash codes are regenerated by renaming the instances where collisions exist with the provided <paramref name="instanceRenamePolicy"/>.<br/>
14-
/// When the <paramref name="retryLimit"/> is met, and a hash collision is detected, a <see cref="LamarInstanceHashCollisionException" /> is thrown.
16+
/// When the <paramref name="retryLimit"/> is met, and a hash collision is detected, a <see cref="LamarInstanceHashCollisionException" /> is thrown.<br/>
17+
/// If no <paramref name="instanceRenamePolicy"/> is specified, the <see cref="DefaultInstanceRenamePolicy"/> will be used.
1518
/// </summary>
1619
/// <param name="retryLimit"></param>
1720
/// <param name="instanceRenamePolicy"></param>
@@ -21,7 +24,7 @@ public void MitigateInstanceHashCollisions(int retryLimit = 3, Func<string, stri
2124
bool shouldMitigateCollisions = true;
2225
int remaininingRetries = retryLimit;
2326

24-
Func<string, string> renameInstance = instanceRenamePolicy ?? (name => $"{name}_x");
27+
Func<string, string> renameInstance = instanceRenamePolicy ?? DefaultInstanceRenamePolicy;
2528

2629
while (shouldMitigateCollisions && remaininingRetries > 0)
2730
{
@@ -37,6 +40,8 @@ public void MitigateInstanceHashCollisions(int retryLimit = 3, Func<string, stri
3740
}
3841
}
3942

43+
internal bool TryRemoveInstanceHashCollisions() => TryRemoveInstanceHashCollisions(DefaultInstanceRenamePolicy);
44+
4045
internal bool TryRemoveInstanceHashCollisions(Func<string, string> instanceRenamePolicy)
4146
{
4247
var hasCollision = false;
@@ -50,6 +55,8 @@ internal bool TryRemoveInstanceHashCollisions(Func<string, string> instanceRenam
5055
return hasCollision;
5156
}
5257

58+
internal void HandleInstanceHashCollisions(IGrouping<int, Instance> collision) => HandleInstanceHashCollisions(collision, DefaultInstanceRenamePolicy);
59+
5360
internal void HandleInstanceHashCollisions(IGrouping<int, Instance> collision, Func<string, string> instanceRenamePolicy)
5461
{
5562
var namedInstances = collision.Where(x => x.IsExplicitlyNamed).ToList();

0 commit comments

Comments
 (0)