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 { 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 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 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 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 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 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