-
-
Notifications
You must be signed in to change notification settings - Fork 846
Expand file tree
/
Copy pathInstancePerMatchingLifetimeScopeTests.cs
More file actions
188 lines (149 loc) · 7.13 KB
/
Copy pathInstancePerMatchingLifetimeScopeTests.cs
File metadata and controls
188 lines (149 loc) · 7.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// Copyright (c) Autofac Project. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
using Autofac.Core;
using Autofac.Core.Lifetime;
namespace Autofac.Specification.Test.Lifetime;
public class InstancePerMatchingLifetimeScopeTests
{
[Fact]
public void LocalRegistration_BindToAncestorTag()
{
// #1460: Reproduces the exact memory-leak scenario from the issue.
// A child scope is created with a local registration whose matching-scope
// tag refers to an ancestor (the outer scope). Each BeginLifetimeScope call
// would hoist a fresh instance into the ancestor indefinitely, so we now
// throw InvalidOperationException at scope-creation time instead.
var builder = new ContainerBuilder();
var container = builder.Build();
using var outer = container.BeginLifetimeScope("outer");
Assert.Throws<InvalidOperationException>(() =>
outer.BeginLifetimeScope("inner", b =>
b.RegisterType<object>().InstancePerMatchingLifetimeScope("outer")));
}
[Fact]
public void LocalRegistration_BindToScopeOwnTag()
{
// #1460: A scope-local registration whose MatchingScopeLifetime tag matches the
// NEW scope's own tag is perfectly legal: the component's lifetime exactly
// matches its reachability, so there is no leak.
var builder = new ContainerBuilder();
var container = builder.Build();
// Must not throw.
using var scope = container.BeginLifetimeScope("x", b =>
b.RegisterType<object>().InstancePerMatchingLifetimeScope("x"));
// The component should be resolvable within the scope.
var instance = scope.Resolve<object>();
Assert.NotNull(instance);
// And it should be shared within the same scope (singleton-within-scope semantics).
Assert.Same(instance, scope.Resolve<object>());
}
[Fact]
public void LocalRegistration_BindToDescendantTag()
{
// #1460: A scope-local registration whose MatchingScopeLifetime tag will only be
// matched by a future descendant scope must NOT throw at registration time —
// this is the documented, intended usage pattern.
var builder = new ContainerBuilder();
var container = builder.Build();
// "child" does not yet exist in the scope chain → must not throw.
using var parent = container.BeginLifetimeScope("parent", b =>
b.RegisterType<object>().InstancePerMatchingLifetimeScope("child"));
// Creating a named child scope with the matching tag should work and resolve correctly.
using var child = parent.BeginLifetimeScope("child");
var instance = child.Resolve<object>();
Assert.NotNull(instance);
}
[Fact]
public void LocalRegistration_BindToGrandchildTag()
{
// #1460: a descendant tag more than one level down is still a future descendant,
// not an ancestor, so it must not throw at scope-creation time.
var builder = new ContainerBuilder();
var container = builder.Build();
using var child = container.BeginLifetimeScope("child", b =>
b.RegisterType<object>().InstancePerMatchingLifetimeScope("grandchild"));
using var grandchild = child.BeginLifetimeScope("grandchild");
Assert.NotNull(grandchild.Resolve<object>());
}
[Fact]
public void LocalRegistration_BindToRootContainerTag()
{
// #1460: The container's root tag is also a strict ancestor; binding to it from a
// scope-local registration should likewise be rejected.
var builder = new ContainerBuilder();
var container = builder.Build();
Assert.Throws<InvalidOperationException>(() =>
container.BeginLifetimeScope("child", b =>
b.RegisterType<object>().InstancePerMatchingLifetimeScope(LifetimeScope.RootTag)));
}
[Fact]
public void LocalRegistration_BindToMultipleTagsIncludingAncestor()
{
// #1460: When multiple tags are supplied to InstancePerMatchingLifetimeScope and at
// least one of them refers to an ancestor, it must still throw.
var builder = new ContainerBuilder();
var container = builder.Build();
using var outer = container.BeginLifetimeScope("outer");
Assert.Throws<InvalidOperationException>(() =>
outer.BeginLifetimeScope("inner", b =>
b.RegisterType<object>().InstancePerMatchingLifetimeScope("inner", "outer")));
}
[Fact]
public void ChildOfNamedScopeGetsSharedInstance()
{
var contextName = "ctx";
var cb = new ContainerBuilder();
cb.RegisterType<object>().InstancePerMatchingLifetimeScope(contextName);
var container = cb.Build();
var ctx1 = container.BeginLifetimeScope(contextName);
var child = ctx1.BeginLifetimeScope();
Assert.Equal(ctx1.Resolve<object>(), child.Resolve<object>());
}
[Fact]
public void InstancePerRequest_AdditionalLifetimeScopeTagsCanBeProvided()
{
var builder = new ContainerBuilder();
const string Tag1 = "Tag1";
const string Tag2 = "Tag2";
builder.Register(c => new object()).InstancePerRequest(Tag1, Tag2);
var container = builder.Build();
var scope1 = container.BeginLifetimeScope(Tag1);
Assert.NotNull(scope1.Resolve<object>());
var scope2 = container.BeginLifetimeScope(Tag2);
Assert.NotNull(scope2.Resolve<object>());
var requestScope = container.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag);
Assert.NotNull(requestScope.Resolve<object>());
}
[Fact]
public void InstancePerRequest_ResolutionSucceedsInRequestLifetime()
{
var builder = new ContainerBuilder();
builder.RegisterType(typeof(object)).InstancePerRequest();
var container = builder.Build();
Assert.Throws<DependencyResolutionException>(() => container.Resolve<object>());
Assert.Throws<DependencyResolutionException>(() => container.BeginLifetimeScope().Resolve<object>());
var apiRequestScope = container.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag);
Assert.NotNull(apiRequestScope.Resolve<object>());
}
[Fact]
public void MustHaveMatchingScope()
{
var cb = new ContainerBuilder();
cb.RegisterType<object>().InstancePerMatchingLifetimeScope("ctx");
var container = cb.Build();
var ctx1 = container.BeginLifetimeScope();
Assert.Throws<DependencyResolutionException>(() => ctx1.Resolve<object>());
}
[Fact]
public void SharingWithinNamedScope()
{
var contextName = "ctx";
var cb = new ContainerBuilder();
cb.RegisterType<object>().InstancePerMatchingLifetimeScope(contextName);
var container = cb.Build();
var ctx1 = container.BeginLifetimeScope(contextName);
var ctx2 = container.BeginLifetimeScope(contextName);
Assert.Equal(ctx1.Resolve<object>(), ctx1.Resolve<object>());
Assert.NotEqual(ctx1.Resolve<object>(), ctx2.Resolve<object>());
}
}