1 /* 2 * Copyright (C) 2022 The Dagger Authors. 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 17 package buildtests; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; 21 22 import java.io.File; 23 import java.io.IOException; 24 import java.util.Arrays; 25 import java.util.Collection; 26 import org.gradle.testkit.runner.BuildResult; 27 import org.gradle.testkit.runner.GradleRunner; 28 import org.junit.Rule; 29 import org.junit.Test; 30 import org.junit.rules.TemporaryFolder; 31 import org.junit.runner.RunWith; 32 import org.junit.runners.Parameterized; 33 import org.junit.runners.Parameterized.Parameters; 34 35 // This is a regression test for https://github.com/google/dagger/issues/3136 36 @RunWith(Parameterized.class) 37 public class TransitiveSubcomponentQualifierTest { 38 @Parameters(name = "{0}") parameters()39 public static Collection<Object[]> parameters() { 40 return Arrays.asList(new Object[][] {{"implementation"}, {"api"}}); 41 } 42 43 @Rule public TemporaryFolder folder = new TemporaryFolder(); 44 45 private final String transitiveDependencyType; 46 TransitiveSubcomponentQualifierTest(String transitiveDependencyType)47 public TransitiveSubcomponentQualifierTest(String transitiveDependencyType) { 48 this.transitiveDependencyType = transitiveDependencyType; 49 } 50 51 @Test testQualifierWithFactory()52 public void testQualifierWithFactory() throws IOException { 53 GradleRunner runner = 54 setupRunnerWith( 55 GradleFile.create( 56 "MySubcomponent.java", 57 "package library1;", 58 "", 59 "import dagger.BindsInstance;", 60 "import dagger.Subcomponent;", 61 "import library2.MyQualifier;", 62 "", 63 "@Subcomponent", 64 "public abstract class MySubcomponent {", 65 " @MyQualifier", 66 " public abstract int getQualifiedInt();", 67 "", 68 " @Subcomponent.Factory", 69 " public abstract static class Creator {", 70 " public abstract MySubcomponent create(", 71 " @BindsInstance @MyQualifier int qualifiedInt);", 72 " }", 73 "}")); 74 BuildResult result; 75 switch (transitiveDependencyType) { 76 case "implementation": 77 result = runner.buildAndFail(); 78 assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); 79 assertThat(result.getOutput()) 80 .contains( 81 "error: ComponentProcessingStep was unable to process 'app.MyComponent' because " 82 + "'library2.MyQualifier' could not be resolved." 83 + "\n " 84 + "\n Dependency trace:" 85 + "\n => element (CLASS): library1.MySubcomponent" 86 + "\n => element (METHOD): getQualifiedInt()" 87 + "\n => annotation: @MyQualifier" 88 + "\n => type (ERROR annotation type): library2.MyQualifier"); 89 break; 90 case "api": 91 result = runner.build(); 92 assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); 93 assertThat(result.getOutput()) 94 .contains("ENTRY_POINT_REQUEST: @library2.MyQualifier java.lang.Integer"); 95 break; 96 } 97 } 98 99 @Test testQualifierOnBaseClassWithFactory()100 public void testQualifierOnBaseClassWithFactory() throws IOException { 101 GradleRunner runner = 102 setupRunnerWith( 103 GradleFile.create( 104 "MySubcomponent.java", 105 "package library1;", 106 "", 107 "import dagger.Subcomponent;", 108 "import library2.MyQualifier;", 109 "", 110 "@Subcomponent", 111 "public abstract class MySubcomponent extends MyBaseSubcomponent {", 112 " @Subcomponent.Factory", 113 " public abstract static class Creator extends MyBaseSubcomponent.Creator {}", 114 "}"), 115 GradleFile.create( 116 "MyBaseSubcomponent.java", 117 "package library1;", 118 "", 119 "import dagger.BindsInstance;", 120 "import library2.MyQualifier;", 121 "", 122 "public abstract class MyBaseSubcomponent {", 123 " @MyQualifier", 124 " public abstract int getQualifiedInt();", 125 "", 126 " public abstract static class Creator {", 127 " public abstract MySubcomponent create(", 128 " @BindsInstance @MyQualifier int qualifiedInt);", 129 " }", 130 "}")); 131 BuildResult result; 132 switch (transitiveDependencyType) { 133 case "implementation": 134 result = runner.buildAndFail(); 135 assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); 136 assertThat(result.getOutput()) 137 .contains( 138 "error: ComponentProcessingStep was unable to process 'app.MyComponent' because " 139 + "'library2.MyQualifier' could not be resolved." 140 + "\n " 141 + "\n Dependency trace:" 142 + "\n => element (CLASS): library1.MyBaseSubcomponent" 143 + "\n => element (METHOD): getQualifiedInt()" 144 + "\n => annotation: @MyQualifier" 145 + "\n => type (ERROR annotation type): library2.MyQualifier"); 146 break; 147 case "api": 148 result = runner.build(); 149 assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); 150 assertThat(result.getOutput()) 151 .contains("ENTRY_POINT_REQUEST: @library2.MyQualifier java.lang.Integer"); 152 break; 153 } 154 } 155 156 @Test testQualifierWithBuilder()157 public void testQualifierWithBuilder() throws IOException { 158 GradleRunner runner = 159 setupRunnerWith( 160 GradleFile.create( 161 "MySubcomponent.java", 162 "package library1;", 163 "", 164 "import dagger.BindsInstance;", 165 "import dagger.Subcomponent;", 166 "import library2.MyQualifier;", 167 "", 168 "@Subcomponent", 169 "public abstract class MySubcomponent {", 170 " @MyQualifier", 171 " public abstract int getQualifiedInt();", 172 "", 173 " @Subcomponent.Builder", 174 " public abstract static class Creator {", 175 " public abstract MySubcomponent build();", 176 " public abstract Creator qualifiedInt(", 177 " @BindsInstance @MyQualifier int qualifiedInt);", 178 " }", 179 "}")); 180 BuildResult result; 181 switch (transitiveDependencyType) { 182 case "implementation": 183 result = runner.buildAndFail(); 184 assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); 185 assertThat(result.getOutput()) 186 .contains( 187 "error: ComponentProcessingStep was unable to process 'app.MyComponent' because " 188 + "'library2.MyQualifier' could not be resolved." 189 + "\n " 190 + "\n Dependency trace:" 191 + "\n => element (CLASS): library1.MySubcomponent" 192 + "\n => element (METHOD): getQualifiedInt()" 193 + "\n => annotation: @MyQualifier" 194 + "\n => type (ERROR annotation type): library2.MyQualifier"); 195 break; 196 case "api": 197 result = runner.build(); 198 assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); 199 assertThat(result.getOutput()) 200 .contains("ENTRY_POINT_REQUEST: @library2.MyQualifier java.lang.Integer"); 201 break; 202 } 203 } 204 205 @Test testQualifierOnBaseClassWithBuilder()206 public void testQualifierOnBaseClassWithBuilder() throws IOException { 207 GradleRunner runner = 208 setupRunnerWith( 209 GradleFile.create( 210 "MySubcomponent.java", 211 "package library1;", 212 "", 213 "import dagger.Subcomponent;", 214 "import library2.MyQualifier;", 215 "", 216 "@Subcomponent", 217 "public abstract class MySubcomponent extends MyBaseSubcomponent {", 218 " @Subcomponent.Builder", 219 " public abstract static class Creator extends MyBaseSubcomponent.Creator {}", 220 "}"), 221 GradleFile.create( 222 "MyBaseSubcomponent.java", 223 "package library1;", 224 "", 225 "import dagger.BindsInstance;", 226 "import library2.MyQualifier;", 227 "", 228 "public abstract class MyBaseSubcomponent {", 229 " @MyQualifier", 230 " public abstract int getQualifiedInt();", 231 "", 232 " public abstract static class Creator {", 233 " public abstract MySubcomponent build();", 234 " public abstract Creator qualifiedInt(", 235 " @BindsInstance @MyQualifier int qualifiedInt);", 236 " }", 237 "}")); 238 BuildResult result; 239 switch (transitiveDependencyType) { 240 case "implementation": 241 result = runner.buildAndFail(); 242 assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); 243 // TODO(bcorso): Give more context about what couldn't be resolved once we've fixed the 244 // issue described in https://github.com/google/dagger/issues/2208. 245 assertThat(result.getOutput()) 246 .contains( 247 "error: ComponentProcessingStep was unable to process 'app.MyComponent' because " 248 + "'library2.MyQualifier' could not be resolved." 249 + "\n " 250 + "\n Dependency trace:" 251 + "\n => element (CLASS): library1.MyBaseSubcomponent" 252 + "\n => element (METHOD): getQualifiedInt()" 253 + "\n => annotation: @MyQualifier" 254 + "\n => type (ERROR annotation type): library2.MyQualifier"); 255 break; 256 case "api": 257 result = runner.build(); 258 assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); 259 assertThat(result.getOutput()) 260 .contains("ENTRY_POINT_REQUEST: @library2.MyQualifier java.lang.Integer"); 261 break; 262 } 263 } 264 setupRunnerWith(GradleFile... library1Files)265 private GradleRunner setupRunnerWith(GradleFile... library1Files) throws IOException { 266 File projectDir = folder.getRoot(); 267 GradleModule.create(projectDir) 268 .addSettingsFile( 269 "include 'app'", "include 'library1'", "include 'library2'", "include 'spi-plugin'") 270 .addBuildFile( 271 "buildscript {", 272 " ext {", 273 String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), 274 " }", 275 "}", 276 "", 277 "allprojects {", 278 " repositories {", 279 " mavenCentral()", 280 " mavenLocal()", 281 " }", 282 "}"); 283 284 GradleModule.create(projectDir, "app") 285 .addBuildFile( 286 "plugins {", 287 " id 'java'", 288 " id 'application'", 289 "}", 290 "tasks.withType(JavaCompile) {", 291 " options.compilerArgs += '-Adagger.experimentalDaggerErrorMessages=ENABLED'", 292 "}", 293 "dependencies {", 294 " implementation project(':library1')", 295 " annotationProcessor project(':spi-plugin')", 296 " implementation \"com.google.dagger:dagger:$dagger_version\"", 297 " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", 298 "}") 299 .addSrcFile( 300 "MyComponent.java", 301 "package app;", 302 "", 303 "import dagger.Component;", 304 "import library1.MySubcomponent;", 305 "", 306 "@Component", 307 "public interface MyComponent {", 308 " MySubcomponent.Creator mySubcomponentCreator();", 309 "}"); 310 311 GradleModule.create(projectDir, "library1") 312 .addBuildFile( 313 "plugins {", 314 " id 'java'", 315 " id 'java-library'", 316 "}", 317 "dependencies {", 318 transitiveDependencyType + " project(':library2')", 319 " implementation \"com.google.dagger:dagger:$dagger_version\"", 320 " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", 321 "}") 322 .addSrcFiles(library1Files); 323 324 GradleModule.create(projectDir, "library2") 325 .addBuildFile( 326 "plugins {", 327 " id 'java'", 328 " id 'java-library'", 329 "}", 330 "dependencies {", 331 " implementation 'javax.inject:javax.inject:1'", 332 "}") 333 .addSrcFile( 334 "MyQualifier.java", 335 "package library2;", 336 "", 337 "import javax.inject.Qualifier;", 338 "", 339 "@Qualifier", 340 "public @interface MyQualifier {}"); 341 342 // This plugin is used to print output about bindings that we can assert on in tests. 343 GradleModule.create(projectDir, "spi-plugin") 344 .addBuildFile( 345 "plugins {", 346 " id 'java'", 347 "}", 348 "dependencies {", 349 " implementation \"com.google.dagger:dagger-spi:$dagger_version\"", 350 " implementation 'com.google.auto.service:auto-service-annotations:1.0.1'", 351 " annotationProcessor 'com.google.auto.service:auto-service:1.0.1'", 352 "}") 353 .addSrcFile( 354 "TestBindingGraphPlugin.java", 355 "package spiplugin;", 356 "", 357 "import com.google.auto.service.AutoService;", 358 "import dagger.model.BindingGraph;", 359 "import dagger.model.BindingGraph.DependencyEdge;", 360 "import dagger.model.DependencyRequest;", 361 "import dagger.spi.BindingGraphPlugin;", 362 "import dagger.spi.DiagnosticReporter;", 363 "", 364 "@AutoService(BindingGraphPlugin.class)", 365 "public class TestBindingGraphPlugin implements BindingGraphPlugin {", 366 " @Override", 367 " public void visitGraph(", 368 " BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {", 369 " bindingGraph.entryPointEdges().stream()", 370 " .map(DependencyEdge::dependencyRequest)", 371 " .map(DependencyRequest::key)", 372 " .forEach(key -> System.out.println(\"ENTRY_POINT_REQUEST: \" + key));", 373 " }", 374 "}"); 375 376 return GradleRunner.create().withArguments("--stacktrace", "build").withProjectDir(projectDir); 377 } 378 } 379