Skip to content

Commit f1c3321

Browse files
committed
Split up resolve operation tests.
1 parent 5df69e4 commit f1c3321

7 files changed

Lines changed: 301 additions & 280 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
using System.Linq;
3+
4+
namespace Autofac.Specification.Test.Features.CircularDependency
5+
{
6+
public class DependsByCtor
7+
{
8+
public DependsByCtor(DependsByProp o)
9+
{
10+
this.Dep = o;
11+
}
12+
13+
public DependsByProp Dep { get; private set; }
14+
}
15+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
using System.Linq;
3+
4+
namespace Autofac.Specification.Test.Features.CircularDependency
5+
{
6+
public class DependsByProp
7+
{
8+
public DependsByCtor Dep { get; set; }
9+
}
10+
}

test/Autofac.Specification.Test/Features/CircularDependencyTests.cs

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
24
using Autofac.Core;
35
using Autofac.Specification.Test.Features.CircularDependency;
46
using Xunit;
@@ -7,6 +9,48 @@ namespace Autofac.Specification.Test.Features
79
{
810
public class CircularDependencyTests
911
{
12+
private interface IPlugin
13+
{
14+
}
15+
16+
private interface IService
17+
{
18+
}
19+
20+
private interface IUnavailableComponent
21+
{
22+
}
23+
24+
[Fact]
25+
public void ActivationStackResetsOnFailedLambdaResolve()
26+
{
27+
// Issue #929
28+
var builder = new ContainerBuilder();
29+
builder.RegisterType<ServiceImpl>().AsSelf();
30+
builder.Register<IService>(c =>
31+
{
32+
try
33+
{
34+
// This will fail because ServiceImpl needs a Guid ctor
35+
// parameter and it's not provided.
36+
return c.Resolve<ServiceImpl>();
37+
}
38+
catch (Exception)
39+
{
40+
// This is where the activation stack isn't getting reset.
41+
}
42+
43+
return new ServiceImpl(Guid.Empty);
44+
});
45+
builder.RegisterType<Dependency>().AsSelf();
46+
builder.RegisterType<ComponentConsumer>().AsSelf();
47+
var container = builder.Build();
48+
49+
// This throws a circular dependency exception if the activation stack
50+
// doesn't get reset.
51+
container.Resolve<ComponentConsumer>();
52+
}
53+
1054
[Fact]
1155
public void DetectsCircularDependencies()
1256
{
@@ -20,6 +64,32 @@ public void DetectsCircularDependencies()
2064
var de = Assert.Throws<DependencyResolutionException>(() => container.Resolve<ID>());
2165
}
2266

67+
[Fact]
68+
public void InstancePerDependencyDoesNotAllowCircularDependencies_ConstructorOwnerResolved()
69+
{
70+
var cb = new ContainerBuilder();
71+
var ac = 0;
72+
cb.RegisterType<DependsByCtor>().OnActivating(e => { ac = 2; });
73+
cb.RegisterType<DependsByProp>().OnActivating(e => { ac = 1; })
74+
.PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);
75+
76+
var c = cb.Build();
77+
Assert.Throws<DependencyResolutionException>(() => c.Resolve<DependsByCtor>());
78+
79+
Assert.Equal(2, ac);
80+
}
81+
82+
[Fact]
83+
public void InstancePerDependencyDoesNotAllowCircularDependencies_PropertyOwnerResolved()
84+
{
85+
var cb = new ContainerBuilder();
86+
cb.RegisterType<DependsByCtor>();
87+
cb.RegisterType<DependsByProp>().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);
88+
89+
var c = cb.Build();
90+
Assert.Throws<DependencyResolutionException>(() => c.Resolve<DependsByProp>());
91+
}
92+
2393
[Fact]
2494
public void InstancePerLifetimeScopeServiceCannotCreateSecondInstanceOfSelfDuringConstruction()
2595
{
@@ -31,6 +101,77 @@ public void InstancePerLifetimeScopeServiceCannotCreateSecondInstanceOfSelfDurin
31101
var exception = Assert.Throws<DependencyResolutionException>(() => container.Resolve<AThatDependsOnB>());
32102
}
33103

104+
[Fact]
105+
public void ManualEnumerableRegistrationDoesNotCauseCircularDependency()
106+
{
107+
var builder = new ContainerBuilder();
108+
builder.RegisterType<RootViewModel>().AsSelf().SingleInstance();
109+
builder.RegisterType<PluginsViewModel>().AsSelf().SingleInstance();
110+
111+
builder.RegisterType(typeof(Plugin1)).Named<IPlugin>(nameof(Plugin1));
112+
builder.RegisterType(typeof(Plugin2)).Named<IPlugin>(nameof(Plugin2));
113+
builder.Register(
114+
ctx => new[] { nameof(Plugin1), nameof(Plugin2) }
115+
.Select(name => SafeResolvePlugin(name, ctx))
116+
.Where(p => p != null)
117+
.ToArray())
118+
.As<IEnumerable<IPlugin>>()
119+
.SingleInstance();
120+
121+
var container = builder.Build();
122+
123+
// From issue 648, this resolve call was getting a circular dependency
124+
// detection exception. It shouldn't be getting anything because the "safe resolve"
125+
// eats the dependency resolution issue for Plugin2 and Plugin1 should be
126+
// properly resolved.
127+
Assert.NotNull(container.Resolve<RootViewModel>());
128+
Assert.Single(container.Resolve<IEnumerable<IPlugin>>());
129+
}
130+
131+
[Fact]
132+
public void SingleInstanceAllowsCircularDependencies_ConstructorOwnerResolved()
133+
{
134+
var cb = new ContainerBuilder();
135+
cb.RegisterType<DependsByCtor>().SingleInstance();
136+
cb.RegisterType<DependsByProp>().SingleInstance().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);
137+
138+
var c = cb.Build();
139+
var dbc = c.Resolve<DependsByCtor>();
140+
141+
Assert.NotNull(dbc.Dep);
142+
Assert.NotNull(dbc.Dep.Dep);
143+
Assert.Same(dbc, dbc.Dep.Dep);
144+
}
145+
146+
[Fact]
147+
public void SingleInstanceAllowsCircularDependencies_PropertyOwnerResolved()
148+
{
149+
var cb = new ContainerBuilder();
150+
cb.RegisterType<DependsByCtor>().SingleInstance();
151+
cb.RegisterType<DependsByProp>().SingleInstance().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);
152+
153+
var c = cb.Build();
154+
var dbp = c.Resolve<DependsByProp>();
155+
156+
Assert.NotNull(dbp.Dep);
157+
Assert.NotNull(dbp.Dep.Dep);
158+
Assert.Same(dbp, dbp.Dep.Dep);
159+
}
160+
161+
private static IPlugin SafeResolvePlugin(string pluginName, IComponentContext core)
162+
{
163+
try
164+
{
165+
// Plugin2 will get filtered out because it has an
166+
// unavailable dependency.
167+
return core.ResolveNamed<IPlugin>(pluginName);
168+
}
169+
catch (DependencyResolutionException)
170+
{
171+
return null;
172+
}
173+
}
174+
34175
private class AThatDependsOnB
35176
{
36177
public AThatDependsOnB(BThatCreatesA bThatCreatesA)
@@ -45,5 +186,72 @@ public BThatCreatesA(Func<BThatCreatesA, AThatDependsOnB> factory)
45186
factory(this);
46187
}
47188
}
189+
190+
// Issue #929
191+
// When a resolve operation fails in a lambda registration the activation stack
192+
// doesn't get reset and incorrectly causes a circular dependency exception.
193+
//
194+
// The ComponentConsumer takes IService (ServiceImpl) and a Dependency; the
195+
// Dependency also takes an IService. Normally this wouldn't cause an issue, but
196+
// if the registration for IService is a lambda that has a try/catch around a failing
197+
// resolve, the activation stack won't reset and the IService will be seen as a
198+
// circular dependency.
199+
private class ComponentConsumer
200+
{
201+
private Dependency _dependency;
202+
203+
private IService _service;
204+
205+
public ComponentConsumer(IService service, Dependency dependency)
206+
{
207+
this._service = service;
208+
this._dependency = dependency;
209+
}
210+
}
211+
212+
private class Dependency
213+
{
214+
private IService _service;
215+
216+
public Dependency(IService service)
217+
{
218+
this._service = service;
219+
}
220+
}
221+
222+
private class Plugin1 : IPlugin
223+
{
224+
}
225+
226+
private class Plugin2 : IPlugin
227+
{
228+
public Plugin2(IUnavailableComponent unavailableComponent)
229+
{
230+
}
231+
}
232+
233+
private class PluginsViewModel
234+
{
235+
public PluginsViewModel(IEnumerable<IPlugin> plugins)
236+
{
237+
}
238+
}
239+
240+
private class RootViewModel
241+
{
242+
public RootViewModel(IEnumerable<IPlugin> plugins, PluginsViewModel pluginsViewModel)
243+
{
244+
}
245+
}
246+
247+
private class ServiceImpl : IService
248+
{
249+
private Guid _id;
250+
251+
public ServiceImpl(Guid id)
252+
{
253+
this._id = id;
254+
}
255+
}
48256
}
49257
}

test/Autofac.Specification.Test/Features/PropertyInjectionTests.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,24 @@ public void PropertiesNotSetIfNotSpecified()
254254
Assert.Null(instance.Val);
255255
}
256256

257+
[Fact]
258+
public void PropertyInjectionPassesNamedParameterOfTheInstanceTypeBeingInjectedOnto()
259+
{
260+
var captured = Enumerable.Empty<Parameter>();
261+
var cb = new ContainerBuilder();
262+
cb.RegisterType<HasPublicSetter>().SingleInstance().PropertiesAutowired();
263+
cb.Register((context, parameters) =>
264+
{
265+
captured = parameters.ToArray();
266+
return "value";
267+
});
268+
269+
var c = cb.Build();
270+
var instance = c.Resolve<HasPublicSetter>();
271+
var instanceType = captured.Named<Type>(ResolutionExtensions.PropertyInjectedInstanceTypeNamedParameter);
272+
Assert.Equal(instance.GetType(), instanceType);
273+
}
274+
257275
public class EnumProperty
258276
{
259277
public SimpleEnumeration Value { get; set; }

test/Autofac.Specification.Test/Lifetime/LifetimeEventTests.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,55 @@ public void ActivatedAllowsMethodInjection()
2424
Assert.Equal(pval, invokee.Param);
2525
}
2626

27+
[Fact]
28+
public void ActivatedCanReceiveParameters()
29+
{
30+
const int provided = 12;
31+
var passed = 0;
32+
33+
var builder = new ContainerBuilder();
34+
builder.RegisterType<object>()
35+
.OnActivated(e => passed = e.Parameters.TypedAs<int>());
36+
var container = builder.Build();
37+
38+
container.Resolve<object>(TypedParameter.From(provided));
39+
Assert.Equal(provided, passed);
40+
}
41+
42+
[Fact]
43+
public void ActivatingCanReceiveParameters()
44+
{
45+
const int provided = 12;
46+
var passed = 0;
47+
48+
var builder = new ContainerBuilder();
49+
builder.RegisterType<object>()
50+
.OnActivating(e => passed = e.Parameters.TypedAs<int>());
51+
var container = builder.Build();
52+
53+
container.Resolve<object>(TypedParameter.From(provided));
54+
Assert.Equal(provided, passed);
55+
}
56+
57+
[Fact]
58+
public void ChainedOnActivatedEventsAreInvokedWithinASingleResolveOperation()
59+
{
60+
var builder = new ContainerBuilder();
61+
62+
var secondEventRaised = false;
63+
builder.RegisterType<object>()
64+
.Named<object>("second")
65+
.OnActivated(e => secondEventRaised = true);
66+
67+
builder.RegisterType<object>()
68+
.OnActivated(e => e.Context.ResolveNamed<object>("second"));
69+
70+
var container = builder.Build();
71+
container.Resolve<object>();
72+
73+
Assert.True(secondEventRaised);
74+
}
75+
2776
[Fact]
2877
public void PreparingCanProvideParametersToActivator()
2978
{

0 commit comments

Comments
 (0)