Skip to content

Commit

Permalink
add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jwoodman510 authored and jeremydmiller committed Aug 16, 2024
1 parent 2a93dc4 commit fe74771
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 2 deletions.
142 changes: 142 additions & 0 deletions src/Lamar.Testing/Bugs/Bug_398_service_registry_hash_collisions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using Lamar.IoC;
using Shouldly;
using System;
using Xunit;

namespace Lamar.Testing.Bugs;

public class Bug_398_service_registry_hash_collisions
{
[Fact]
public void GetHashCollisions_RegistryWithCollisions()
{
var registry = GetRegistryWithCollisions();

var collisions = registry.GetInstanceHashCollisions();

collisions.ShouldNotBeEmpty();
}

[Fact]
public void GetHashCollisions_RegistryWithoutCollisions()
{
var registry = GetRegistryWithoutCollisions();

var collisions = registry.GetInstanceHashCollisions();

collisions.ShouldBeEmpty();
}

[Fact]
public void TryRemoveHashCollisions_RegistryWithCollisions()
{
var registry = GetRegistryWithCollisions();

var removed = registry.TryRemoveInstanceHashCollisions();

removed.ShouldBeTrue();
}

[Fact]
public void TryRemoveHashCollisions_RegistryWithoutCollisions()
{
var registry = GetRegistryWithoutCollisions();

var removed = registry.TryRemoveInstanceHashCollisions();

removed.ShouldBeFalse();
}

[Fact]
public void HandleInstanceHashCollisions()
{
var registry = GetRegistryWithCollisions();

registry.TryRemoveInstanceHashCollisions();

registry.GetInstanceHashCollisions().ShouldBeEmpty();

var container = new Container(registry);
container.GetInstance<IFoo>().ShouldBeOfType<Foo>();
container.GetInstance<IBar>().ShouldBeOfType<Bar>();
}

[Fact]
public void MitigateInstanceHashCollisions()
{
var registry = GetRegistryWithCollisions();

registry.MitigateInstanceHashCollisions();

registry.GetInstanceHashCollisions().ShouldBeEmpty();

var container = new Container(registry);
container.GetInstance<IFoo>().ShouldBeOfType<Foo>();
container.GetInstance<IBar>().ShouldBeOfType<Bar>();
}

[Fact]
public void MitigateInstanceHashCollisionsThrowsAfterLimitReached()
{
var registry = GetRegistryWithCollisions();

var mitigateCollisions = () => registry.MitigateInstanceHashCollisions(0);

mitigateCollisions.ShouldThrow<LamarInstanceHashCollisionException>();
}

[Fact]
public void MitigateInstanceHashCollisionsUsesCustomRenamePolicy()
{
var registry = GetRegistryWithCollisions();

var instanceFoo = registry.For<IFoo>().Use<Foo>();
var instanceBar = registry.For<IBar>().Use<Bar>();

instanceFoo.Hash = instanceBar.Hash = 1;

registry.MitigateInstanceHashCollisions(instanceRenamePolicy: x => $"{x}.updated");

instanceFoo.Name.ShouldEndWith(".updated");
instanceBar.Name.ShouldEndWith(".updated");
}

[Fact]
public void NamedCollisionThrows()
{
var registry = new ServiceRegistry();

registry.For<IFoo>().Use<Foo>().Named("foo").Hash = 1;
registry.For<IBar>().Use<Bar>().Named("bar").Hash = 1;

Action handleCollisions = () => registry.TryRemoveInstanceHashCollisions();

handleCollisions.ShouldThrow<LamarInstanceHashCollisionException>();
}


private ServiceRegistry GetRegistryWithCollisions()
{
var registry = new ServiceRegistry();

registry.For<IFoo>().Use<Foo>().Hash = 1;
registry.For<IBar>().Use<Bar>().Hash = 1;

return registry;
}

private ServiceRegistry GetRegistryWithoutCollisions()
{
var registry = new ServiceRegistry();

registry.For<IFoo>().Use<Foo>().Hash = 1;
registry.For<IBar>().Use<Bar>().Hash = 2;

return registry;
}

public interface IFoo { }
public interface IBar { }
public class Foo : IFoo { }
public class Bar : IBar { }
}
5 changes: 5 additions & 0 deletions src/Lamar/IoC/LamarInstanceHashCollisionException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ namespace Lamar.IoC;

public class LamarInstanceHashCollisionException : LamarException
{
public int InstanceHash { get; }
public IEnumerable<Type> ServiceTypes { get; }

public LamarInstanceHashCollisionException(int instanceHash, IEnumerable<Type> serviceTypes) : base(
$"Duplicate hash '{instanceHash}' generated for services: {string.Join(", ", serviceTypes.Select(x => x.FullNameInCode()))}")
{
InstanceHash = instanceHash;
ServiceTypes = serviceTypes;
}
}
11 changes: 9 additions & 2 deletions src/Lamar/ServiceRegistry.HashCollisionMitigation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ namespace Lamar;

public partial class ServiceRegistry
{
internal static readonly Func<string, string> DefaultInstanceRenamePolicy = name => $"{name}_x";

/// <summary>
/// Finds and mitigates <see cref="Instance"/> hash code collisions in the current <see cref="ServiceRegistry"/> instance.<br/>
/// Hash codes are regenerated by renaming the instances where collisions exist with the provided <paramref name="instanceRenamePolicy"/>.<br/>
/// When the <paramref name="retryLimit"/> is met, and a hash collision is detected, a <see cref="LamarInstanceHashCollisionException" /> is thrown.
/// When the <paramref name="retryLimit"/> is met, and a hash collision is detected, a <see cref="LamarInstanceHashCollisionException" /> is thrown.<br/>
/// If no <paramref name="instanceRenamePolicy"/> is specified, the <see cref="DefaultInstanceRenamePolicy"/> will be used.
/// </summary>
/// <param name="retryLimit"></param>
/// <param name="instanceRenamePolicy"></param>
Expand All @@ -21,7 +24,7 @@ public void MitigateInstanceHashCollisions(int retryLimit = 3, Func<string, stri
bool shouldMitigateCollisions = true;
int remaininingRetries = retryLimit;

Func<string, string> renameInstance = instanceRenamePolicy ?? (name => $"{name}_x");
Func<string, string> renameInstance = instanceRenamePolicy ?? DefaultInstanceRenamePolicy;

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

internal bool TryRemoveInstanceHashCollisions() => TryRemoveInstanceHashCollisions(DefaultInstanceRenamePolicy);

internal bool TryRemoveInstanceHashCollisions(Func<string, string> instanceRenamePolicy)
{
var hasCollision = false;
Expand All @@ -50,6 +55,8 @@ internal bool TryRemoveInstanceHashCollisions(Func<string, string> instanceRenam
return hasCollision;
}

internal void HandleInstanceHashCollisions(IGrouping<int, Instance> collision) => HandleInstanceHashCollisions(collision, DefaultInstanceRenamePolicy);

internal void HandleInstanceHashCollisions(IGrouping<int, Instance> collision, Func<string, string> instanceRenamePolicy)
{
var namedInstances = collision.Where(x => x.IsExplicitlyNamed).ToList();
Expand Down

0 comments on commit fe74771

Please sign in to comment.