• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 Google, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package dagger.internal.codegen;
17 
18 import com.google.testing.compile.JavaFileObjects;
19 import javax.tools.JavaFileObject;
20 import org.junit.Test;
21 import org.junit.runner.RunWith;
22 import org.junit.runners.JUnit4;
23 
24 import static com.google.common.truth.Truth.assert_;
25 import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;
26 import static java.util.Arrays.asList;
27 
28 @RunWith(JUnit4.class)
29 public class GraphValidationScopingTest {
componentWithoutScopeIncludesScopedBindings_Fail()30   @Test public void componentWithoutScopeIncludesScopedBindings_Fail() {
31     JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.MyComponent",
32         "package test;",
33         "",
34         "import dagger.Component;",
35         "import javax.inject.Singleton;",
36         "",
37         "@Component(modules = ScopedModule.class)",
38         "interface MyComponent {",
39         "  ScopedType string();",
40         "}");
41     JavaFileObject typeFile = JavaFileObjects.forSourceLines("test.ScopedType",
42         "package test;",
43         "",
44         "import javax.inject.Inject;",
45         "import javax.inject.Singleton;",
46         "",
47         "@Singleton",
48         "class ScopedType {",
49         "  @Inject ScopedType(String s, long l, float f) {}",
50         "}");
51     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.ScopedModule",
52         "package test;",
53         "",
54         "import dagger.Module;",
55         "import dagger.Provides;",
56         "import javax.inject.Singleton;",
57         "",
58         "@Module",
59         "class ScopedModule {",
60         "  @Provides @Singleton String string() { return \"a string\"; }",
61         "  @Provides long integer() { return 0L; }",
62         "  @Provides float floatingPoint() { return 0.0f; }",
63         "}");
64     String errorMessage = "test.MyComponent (unscoped) may not reference scoped bindings:\n"
65         + "      @Provides @Singleton String test.ScopedModule.string()\n"
66         + "      @Singleton class test.ScopedType";
67     assert_().about(javaSources()).that(asList(componentFile, typeFile, moduleFile))
68         .processedWith(new ComponentProcessor())
69         .failsToCompile()
70         .withErrorContaining(errorMessage);
71   }
72 
componentWithScopeIncludesIncompatiblyScopedBindings_Fail()73   @Test public void componentWithScopeIncludesIncompatiblyScopedBindings_Fail() {
74     JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.MyComponent",
75         "package test;",
76         "",
77         "import dagger.Component;",
78         "import javax.inject.Singleton;",
79         "",
80         "@Singleton",
81         "@Component(modules = ScopedModule.class)",
82         "interface MyComponent {",
83         "  ScopedType string();",
84         "}");
85     JavaFileObject scopeFile = JavaFileObjects.forSourceLines("test.PerTest",
86         "package test;",
87         "",
88         "import javax.inject.Scope;",
89         "",
90         "@Scope",
91         "@interface PerTest {}");
92     JavaFileObject typeFile = JavaFileObjects.forSourceLines("test.ScopedType",
93         "package test;",
94         "",
95         "import javax.inject.Inject;",
96         "",
97         "@PerTest", // incompatible scope
98         "class ScopedType {",
99         "  @Inject ScopedType(String s, long l, float f) {}",
100         "}");
101     JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.ScopedModule",
102         "package test;",
103         "",
104         "import dagger.Module;",
105         "import dagger.Provides;",
106         "import javax.inject.Singleton;",
107         "",
108         "@Module",
109         "class ScopedModule {",
110         "  @Provides @PerTest String string() { return \"a string\"; }", // incompatible scope
111         "  @Provides long integer() { return 0L; }", // unscoped - valid
112         "  @Provides @Singleton float floatingPoint() { return 0.0f; }", // same scope - valid
113         "}");
114     String errorMessage = "test.MyComponent scoped with @Singleton "
115         + "may not reference bindings with different scopes:\n"
116         + "      @Provides @test.PerTest String test.ScopedModule.string()\n"
117         + "      @test.PerTest class test.ScopedType";
118     assert_().about(javaSources()).that(asList(componentFile, scopeFile, typeFile, moduleFile))
119         .processedWith(new ComponentProcessor())
120         .failsToCompile()
121         .withErrorContaining(errorMessage);
122   }
123 
componentWithScopeMayDependOnOnlyOneScopedComponent()124   @Test public void componentWithScopeMayDependOnOnlyOneScopedComponent() {
125     // If a scoped component will have dependencies, they must only include, at most, a single
126     // scoped component
127     JavaFileObject type = JavaFileObjects.forSourceLines("test.SimpleType",
128         "package test;",
129         "",
130         "import javax.inject.Inject;",
131         "",
132         "class SimpleType {",
133         "  @Inject SimpleType() {}",
134         "  static class A { @Inject A() {} }",
135         "  static class B { @Inject B() {} }",
136         "}");
137     JavaFileObject simpleScope = JavaFileObjects.forSourceLines("test.SimpleScope",
138         "package test;",
139         "",
140         "import javax.inject.Scope;",
141         "",
142         "@Scope @interface SimpleScope {}");
143     JavaFileObject singletonScopedA = JavaFileObjects.forSourceLines("test.SingletonComponentA",
144         "package test;",
145         "",
146         "import dagger.Component;",
147         "import javax.inject.Singleton;",
148         "",
149         "@Singleton",
150         "@Component",
151         "interface SingletonComponentA {",
152         "  SimpleType.A type();",
153         "}");
154     JavaFileObject singletonScopedB = JavaFileObjects.forSourceLines("test.SingletonComponentB",
155         "package test;",
156         "",
157         "import dagger.Component;",
158         "import javax.inject.Singleton;",
159         "",
160         "@Singleton",
161         "@Component",
162         "interface SingletonComponentB {",
163         "  SimpleType.B type();",
164         "}");
165     JavaFileObject scopeless = JavaFileObjects.forSourceLines("test.ScopelessComponent",
166         "package test;",
167         "",
168         "import dagger.Component;",
169         "",
170         "@Component",
171         "interface ScopelessComponent {",
172         "  SimpleType type();",
173         "}");
174     JavaFileObject simpleScoped = JavaFileObjects.forSourceLines("test.SimpleScopedComponent",
175         "package test;",
176         "",
177         "import dagger.Component;",
178         "",
179         "@SimpleScope",
180         "@Component(dependencies = {SingletonComponentA.class, SingletonComponentB.class})",
181         "interface SimpleScopedComponent {",
182         "  SimpleType.A type();",
183         "}");
184     String errorMessage =
185         "@test.SimpleScope test.SimpleScopedComponent depends on more than one scoped component:\n"
186         + "      @Singleton test.SingletonComponentA\n"
187         + "      @Singleton test.SingletonComponentB";
188     assert_().about(javaSources())
189         .that(
190             asList(type, simpleScope, simpleScoped, singletonScopedA, singletonScopedB, scopeless))
191         .processedWith(new ComponentProcessor())
192         .failsToCompile()
193         .withErrorContaining(errorMessage);
194   }
195 
componentWithoutScopeCannotDependOnScopedComponent()196   @Test public void componentWithoutScopeCannotDependOnScopedComponent() {
197     JavaFileObject type = JavaFileObjects.forSourceLines("test.SimpleType",
198         "package test;",
199         "",
200         "import javax.inject.Inject;",
201         "",
202         "class SimpleType {",
203         "  @Inject SimpleType() {}",
204         "}");
205     JavaFileObject scopedComponent = JavaFileObjects.forSourceLines("test.ScopedComponent",
206         "package test;",
207         "",
208         "import dagger.Component;",
209         "import javax.inject.Singleton;",
210         "",
211         "@Singleton",
212         "@Component",
213         "interface ScopedComponent {",
214         "  SimpleType type();",
215         "}");
216     JavaFileObject unscopedComponent = JavaFileObjects.forSourceLines("test.UnscopedComponent",
217         "package test;",
218         "",
219         "import dagger.Component;",
220         "import javax.inject.Singleton;",
221         "",
222         "@Component(dependencies = ScopedComponent.class)",
223         "interface UnscopedComponent {",
224         "  SimpleType type();",
225         "}");
226     String errorMessage =
227         "test.UnscopedComponent (unscoped) cannot depend on scoped components:\n"
228         + "      @Singleton test.ScopedComponent";
229     assert_().about(javaSources())
230         .that(asList(type, scopedComponent, unscopedComponent))
231         .processedWith(new ComponentProcessor())
232         .failsToCompile()
233         .withErrorContaining(errorMessage);
234   }
235 
componentWithSingletonScopeMayNotDependOnOtherScope()236   @Test public void componentWithSingletonScopeMayNotDependOnOtherScope() {
237     // Singleton must be the widest lifetime of present scopes.
238     JavaFileObject type = JavaFileObjects.forSourceLines("test.SimpleType",
239         "package test;",
240         "",
241         "import javax.inject.Inject;",
242         "",
243         "class SimpleType {",
244         "  @Inject SimpleType() {}",
245         "}");
246     JavaFileObject simpleScope = JavaFileObjects.forSourceLines("test.SimpleScope",
247         "package test;",
248         "",
249         "import javax.inject.Scope;",
250         "",
251         "@Scope @interface SimpleScope {}");
252     JavaFileObject simpleScoped = JavaFileObjects.forSourceLines("test.SimpleScopedComponent",
253         "package test;",
254         "",
255         "import dagger.Component;",
256         "",
257         "@SimpleScope",
258         "@Component",
259         "interface SimpleScopedComponent {",
260         "  SimpleType type();",
261         "}");
262     JavaFileObject singletonScoped = JavaFileObjects.forSourceLines("test.SingletonComponent",
263         "package test;",
264         "",
265         "import dagger.Component;",
266         "import javax.inject.Singleton;",
267         "",
268         "@Singleton",
269         "@Component(dependencies = SimpleScopedComponent.class)",
270         "interface SingletonComponent {",
271         "  SimpleType type();",
272         "}");
273     String errorMessage =
274         "This @Singleton component cannot depend on scoped components:\n"
275         + "      @test.SimpleScope test.SimpleScopedComponent";
276     assert_().about(javaSources())
277         .that(asList(type, simpleScope, simpleScoped, singletonScoped))
278         .processedWith(new ComponentProcessor())
279         .failsToCompile()
280         .withErrorContaining(errorMessage);
281   }
282 
componentScopeAncestryMustNotCycle()283   @Test public void componentScopeAncestryMustNotCycle() {
284     // The dependency relationship of components is necessarily from shorter lifetimes to
285     // longer lifetimes.  The scoping annotations must reflect this, and so one cannot declare
286     // scopes on components such that they cycle.
287     JavaFileObject type = JavaFileObjects.forSourceLines("test.SimpleType",
288         "package test;",
289         "",
290         "import javax.inject.Inject;",
291         "",
292         "class SimpleType {",
293         "  @Inject SimpleType() {}",
294         "}");
295     JavaFileObject scopeA = JavaFileObjects.forSourceLines("test.ScopeA",
296         "package test;",
297         "",
298         "import javax.inject.Scope;",
299         "",
300         "@Scope @interface ScopeA {}");
301     JavaFileObject scopeB = JavaFileObjects.forSourceLines("test.ScopeB",
302         "package test;",
303         "",
304         "import javax.inject.Scope;",
305         "",
306         "@Scope @interface ScopeB {}");
307     JavaFileObject longLifetime = JavaFileObjects.forSourceLines("test.ComponentLong",
308         "package test;",
309         "",
310         "import dagger.Component;",
311         "",
312         "@ScopeA",
313         "@Component",
314         "interface ComponentLong {",
315         "  SimpleType type();",
316         "}");
317     JavaFileObject mediumLifetime = JavaFileObjects.forSourceLines("test.ComponentMedium",
318         "package test;",
319         "",
320         "import dagger.Component;",
321         "",
322         "@ScopeB",
323         "@Component(dependencies = ComponentLong.class)",
324         "interface ComponentMedium {",
325         "  SimpleType type();",
326         "}");
327     JavaFileObject shortLifetime = JavaFileObjects.forSourceLines("test.ComponentShort",
328         "package test;",
329         "",
330         "import dagger.Component;",
331         "",
332         "@ScopeA",
333         "@Component(dependencies = ComponentMedium.class)",
334         "interface ComponentShort {",
335         "  SimpleType type();",
336         "}");
337     String errorMessage =
338         "test.ComponentShort depends on scoped components in a non-hierarchical scope ordering:\n"
339         + "      @test.ScopeA test.ComponentLong\n"
340         + "      @test.ScopeB test.ComponentMedium\n"
341         + "      @test.ScopeA test.ComponentShort";
342     assert_().about(javaSources())
343         .that(asList(type, scopeA, scopeB, longLifetime, mediumLifetime, shortLifetime))
344         .processedWith(new ComponentProcessor())
345         .failsToCompile()
346         .withErrorContaining(errorMessage);
347   }
348 }
349