Skip to content

Commit

Permalink
Split up resolve operation tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
tillig committed Mar 14, 2019
1 parent 5df69e4 commit f1c3321
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 280 deletions.
@@ -0,0 +1,15 @@
using System;
using System.Linq;

namespace Autofac.Specification.Test.Features.CircularDependency
{
public class DependsByCtor
{
public DependsByCtor(DependsByProp o)
{
this.Dep = o;
}

public DependsByProp Dep { get; private set; }
}
}
@@ -0,0 +1,10 @@
using System;
using System.Linq;

namespace Autofac.Specification.Test.Features.CircularDependency
{
public class DependsByProp
{
public DependsByCtor Dep { get; set; }
}
}
208 changes: 208 additions & 0 deletions test/Autofac.Specification.Test/Features/CircularDependencyTests.cs
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Autofac.Core;
using Autofac.Specification.Test.Features.CircularDependency;
using Xunit;
Expand All @@ -7,6 +9,48 @@ namespace Autofac.Specification.Test.Features
{
public class CircularDependencyTests
{
private interface IPlugin
{
}

private interface IService
{
}

private interface IUnavailableComponent
{
}

[Fact]
public void ActivationStackResetsOnFailedLambdaResolve()
{
// Issue #929
var builder = new ContainerBuilder();
builder.RegisterType<ServiceImpl>().AsSelf();
builder.Register<IService>(c =>
{
try
{
// This will fail because ServiceImpl needs a Guid ctor
// parameter and it's not provided.
return c.Resolve<ServiceImpl>();
}
catch (Exception)
{
// This is where the activation stack isn't getting reset.
}
return new ServiceImpl(Guid.Empty);
});
builder.RegisterType<Dependency>().AsSelf();
builder.RegisterType<ComponentConsumer>().AsSelf();
var container = builder.Build();

// This throws a circular dependency exception if the activation stack
// doesn't get reset.
container.Resolve<ComponentConsumer>();
}

[Fact]
public void DetectsCircularDependencies()
{
Expand All @@ -20,6 +64,32 @@ public void DetectsCircularDependencies()
var de = Assert.Throws<DependencyResolutionException>(() => container.Resolve<ID>());
}

[Fact]
public void InstancePerDependencyDoesNotAllowCircularDependencies_ConstructorOwnerResolved()
{
var cb = new ContainerBuilder();
var ac = 0;
cb.RegisterType<DependsByCtor>().OnActivating(e => { ac = 2; });
cb.RegisterType<DependsByProp>().OnActivating(e => { ac = 1; })
.PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);

var c = cb.Build();
Assert.Throws<DependencyResolutionException>(() => c.Resolve<DependsByCtor>());

Assert.Equal(2, ac);
}

[Fact]
public void InstancePerDependencyDoesNotAllowCircularDependencies_PropertyOwnerResolved()
{
var cb = new ContainerBuilder();
cb.RegisterType<DependsByCtor>();
cb.RegisterType<DependsByProp>().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);

var c = cb.Build();
Assert.Throws<DependencyResolutionException>(() => c.Resolve<DependsByProp>());
}

[Fact]
public void InstancePerLifetimeScopeServiceCannotCreateSecondInstanceOfSelfDuringConstruction()
{
Expand All @@ -31,6 +101,77 @@ public void InstancePerLifetimeScopeServiceCannotCreateSecondInstanceOfSelfDurin
var exception = Assert.Throws<DependencyResolutionException>(() => container.Resolve<AThatDependsOnB>());
}

[Fact]
public void ManualEnumerableRegistrationDoesNotCauseCircularDependency()
{
var builder = new ContainerBuilder();
builder.RegisterType<RootViewModel>().AsSelf().SingleInstance();
builder.RegisterType<PluginsViewModel>().AsSelf().SingleInstance();

builder.RegisterType(typeof(Plugin1)).Named<IPlugin>(nameof(Plugin1));
builder.RegisterType(typeof(Plugin2)).Named<IPlugin>(nameof(Plugin2));
builder.Register(
ctx => new[] { nameof(Plugin1), nameof(Plugin2) }
.Select(name => SafeResolvePlugin(name, ctx))
.Where(p => p != null)
.ToArray())
.As<IEnumerable<IPlugin>>()
.SingleInstance();

var container = builder.Build();

// From issue 648, this resolve call was getting a circular dependency
// detection exception. It shouldn't be getting anything because the "safe resolve"
// eats the dependency resolution issue for Plugin2 and Plugin1 should be
// properly resolved.
Assert.NotNull(container.Resolve<RootViewModel>());
Assert.Single(container.Resolve<IEnumerable<IPlugin>>());
}

[Fact]
public void SingleInstanceAllowsCircularDependencies_ConstructorOwnerResolved()
{
var cb = new ContainerBuilder();
cb.RegisterType<DependsByCtor>().SingleInstance();
cb.RegisterType<DependsByProp>().SingleInstance().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);

var c = cb.Build();
var dbc = c.Resolve<DependsByCtor>();

Assert.NotNull(dbc.Dep);
Assert.NotNull(dbc.Dep.Dep);
Assert.Same(dbc, dbc.Dep.Dep);
}

[Fact]
public void SingleInstanceAllowsCircularDependencies_PropertyOwnerResolved()
{
var cb = new ContainerBuilder();
cb.RegisterType<DependsByCtor>().SingleInstance();
cb.RegisterType<DependsByProp>().SingleInstance().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);

var c = cb.Build();
var dbp = c.Resolve<DependsByProp>();

Assert.NotNull(dbp.Dep);
Assert.NotNull(dbp.Dep.Dep);
Assert.Same(dbp, dbp.Dep.Dep);
}

private static IPlugin SafeResolvePlugin(string pluginName, IComponentContext core)
{
try
{
// Plugin2 will get filtered out because it has an
// unavailable dependency.
return core.ResolveNamed<IPlugin>(pluginName);
}
catch (DependencyResolutionException)
{
return null;
}
}

private class AThatDependsOnB
{
public AThatDependsOnB(BThatCreatesA bThatCreatesA)
Expand All @@ -45,5 +186,72 @@ public BThatCreatesA(Func<BThatCreatesA, AThatDependsOnB> factory)
factory(this);
}
}

// Issue #929
// When a resolve operation fails in a lambda registration the activation stack
// doesn't get reset and incorrectly causes a circular dependency exception.
//
// The ComponentConsumer takes IService (ServiceImpl) and a Dependency; the
// Dependency also takes an IService. Normally this wouldn't cause an issue, but
// if the registration for IService is a lambda that has a try/catch around a failing
// resolve, the activation stack won't reset and the IService will be seen as a
// circular dependency.
private class ComponentConsumer
{
private Dependency _dependency;

private IService _service;

public ComponentConsumer(IService service, Dependency dependency)
{
this._service = service;
this._dependency = dependency;
}
}

private class Dependency
{
private IService _service;

public Dependency(IService service)
{
this._service = service;
}
}

private class Plugin1 : IPlugin
{
}

private class Plugin2 : IPlugin
{
public Plugin2(IUnavailableComponent unavailableComponent)
{
}
}

private class PluginsViewModel
{
public PluginsViewModel(IEnumerable<IPlugin> plugins)
{
}
}

private class RootViewModel
{
public RootViewModel(IEnumerable<IPlugin> plugins, PluginsViewModel pluginsViewModel)
{
}
}

private class ServiceImpl : IService
{
private Guid _id;

public ServiceImpl(Guid id)
{
this._id = id;
}
}
}
}
18 changes: 18 additions & 0 deletions test/Autofac.Specification.Test/Features/PropertyInjectionTests.cs
Expand Up @@ -254,6 +254,24 @@ public void PropertiesNotSetIfNotSpecified()
Assert.Null(instance.Val);
}

[Fact]
public void PropertyInjectionPassesNamedParameterOfTheInstanceTypeBeingInjectedOnto()
{
var captured = Enumerable.Empty<Parameter>();
var cb = new ContainerBuilder();
cb.RegisterType<HasPublicSetter>().SingleInstance().PropertiesAutowired();
cb.Register((context, parameters) =>
{
captured = parameters.ToArray();
return "value";
});

var c = cb.Build();
var instance = c.Resolve<HasPublicSetter>();
var instanceType = captured.Named<Type>(ResolutionExtensions.PropertyInjectedInstanceTypeNamedParameter);
Assert.Equal(instance.GetType(), instanceType);
}

public class EnumProperty
{
public SimpleEnumeration Value { get; set; }
Expand Down
49 changes: 49 additions & 0 deletions test/Autofac.Specification.Test/Lifetime/LifetimeEventTests.cs
Expand Up @@ -24,6 +24,55 @@ public void ActivatedAllowsMethodInjection()
Assert.Equal(pval, invokee.Param);
}

[Fact]
public void ActivatedCanReceiveParameters()
{
const int provided = 12;
var passed = 0;

var builder = new ContainerBuilder();
builder.RegisterType<object>()
.OnActivated(e => passed = e.Parameters.TypedAs<int>());
var container = builder.Build();

container.Resolve<object>(TypedParameter.From(provided));
Assert.Equal(provided, passed);
}

[Fact]
public void ActivatingCanReceiveParameters()
{
const int provided = 12;
var passed = 0;

var builder = new ContainerBuilder();
builder.RegisterType<object>()
.OnActivating(e => passed = e.Parameters.TypedAs<int>());
var container = builder.Build();

container.Resolve<object>(TypedParameter.From(provided));
Assert.Equal(provided, passed);
}

[Fact]
public void ChainedOnActivatedEventsAreInvokedWithinASingleResolveOperation()
{
var builder = new ContainerBuilder();

var secondEventRaised = false;
builder.RegisterType<object>()
.Named<object>("second")
.OnActivated(e => secondEventRaised = true);

builder.RegisterType<object>()
.OnActivated(e => e.Context.ResolveNamed<object>("second"));

var container = builder.Build();
container.Resolve<object>();

Assert.True(secondEventRaised);
}

[Fact]
public void PreparingCanProvideParametersToActivator()
{
Expand Down

0 comments on commit f1c3321

Please sign in to comment.