• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 TransitiveQualifierTest {
38   @Parameters(name = "transitiveDependencyType = {0}, strictSuperficialValidationMode = {1}")
parameters()39   public static Collection<Object[]> parameters() {
40     return Arrays.asList(
41         new Object[][] {
42           { "implementation", "ENABLED" },
43           { "implementation", "DISABLED" },
44           { "api", "ENABLED" },
45           { "api", "DISABLED" }
46         });
47   }
48 
49   @Rule public TemporaryFolder folder = new TemporaryFolder();
50 
51   private final String transitiveDependencyType;
52   private final String strictSuperficialValidationMode;
53 
TransitiveQualifierTest( String transitiveDependencyType, String strictSuperficialValidationMode)54   public TransitiveQualifierTest(
55       String transitiveDependencyType, String strictSuperficialValidationMode) {
56     this.transitiveDependencyType = transitiveDependencyType;
57     this.strictSuperficialValidationMode = strictSuperficialValidationMode;
58   }
59 
60   @Test
testQualifierOnInjectConstructorParameter()61   public void testQualifierOnInjectConstructorParameter() throws IOException {
62     GradleRunner runner =
63         setupRunnerWith(
64             GradleFile.create(
65                 "QualifierUsage.java",
66                 "package library1;",
67                 "",
68                 "import javax.inject.Inject;",
69                 "import library2.MyQualifier;",
70                 "",
71                 "public class QualifierUsage {",
72                 "  @Inject QualifierUsage(@MyQualifier int i) {}",
73                 "}"));
74     BuildResult result;
75     switch (transitiveDependencyType) {
76       case "implementation":
77         switch (strictSuperficialValidationMode) {
78           case "ENABLED":
79             result = runner.buildAndFail();
80             assertThat(result.getOutput()).contains("Task :app:compileJava FAILED");
81             assertThat(result.getOutput())
82                 .contains(
83                     "ComponentProcessingStep was unable to process 'app.MyComponent' because "
84                         + "'library2.MyQualifier' could not be resolved."
85                         + "\n  "
86                         + "\n  Dependency trace:"
87                         + "\n      => element (INTERFACE): library1.MyModule"
88                         + "\n      => element (METHOD): provideInt()"
89                         + "\n      => annotation: @MyQualifier"
90                         + "\n      => type (ERROR annotation type): library2.MyQualifier");
91             break;
92           case "DISABLED":
93             // When strict mode is disabled we fall back to the old behavior where the qualifier is
94             // missing and we do not throw an exception.
95             result = runner.build();
96             assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS);
97             assertThat(result.getOutput()).contains("REQUEST: java.lang.Integer");
98             break;
99           default: throw new AssertionError("Unexpected mode: " + strictSuperficialValidationMode);
100         }
101         break;
102       case "api":
103         result = runner.build();
104         assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS);
105         assertThat(result.getOutput()).contains("REQUEST: @library2.MyQualifier java.lang.Integer");
106         break;
107     }
108   }
109 
110   @Test
testQualifierOnInjectField()111   public void testQualifierOnInjectField() throws IOException {
112     GradleRunner runner =
113         setupRunnerWith(
114             GradleFile.create(
115                 "QualifierUsage.java",
116                 "package library1;",
117                 "",
118                 "import javax.inject.Inject;",
119                 "import library2.MyQualifier;",
120                 "",
121                 "public class QualifierUsage {",
122                 "  @Inject @MyQualifier int i;",
123                 "",
124                 "  @Inject QualifierUsage() {}",
125                 "}"));
126     BuildResult result;
127     switch (transitiveDependencyType) {
128       case "implementation":
129         switch (strictSuperficialValidationMode) {
130           case "ENABLED":
131             result = runner.buildAndFail();
132             assertThat(result.getOutput()).contains("Task :app:compileJava FAILED");
133             assertThat(result.getOutput())
134                 .contains(
135                     "ComponentProcessingStep was unable to process 'app.MyComponent' because "
136                         + "'library2.MyQualifier' could not be resolved."
137                         + "\n  "
138                         + "\n  Dependency trace:"
139                         + "\n      => element (INTERFACE): library1.MyModule"
140                         + "\n      => element (METHOD): provideInt()"
141                         + "\n      => annotation: @MyQualifier"
142                         + "\n      => type (ERROR annotation type): library2.MyQualifier");
143             break;
144           case "DISABLED":
145             // When strict mode is disabled we fall back to the old behavior where the qualifier is
146             // missing and we do not throw an exception.
147             result = runner.build();
148             assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS);
149             assertThat(result.getOutput()).contains("REQUEST: java.lang.Integer");
150             break;
151           default: throw new AssertionError("Unexpected mode: " + strictSuperficialValidationMode);
152         }
153         break;
154       case "api":
155         result = runner.build();
156         assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS);
157         assertThat(result.getOutput()).contains("REQUEST: @library2.MyQualifier java.lang.Integer");
158         break;
159     }
160   }
161 
162   @Test
testQualifierOnInjectMethodParameter()163   public void testQualifierOnInjectMethodParameter() throws IOException {
164     GradleRunner runner =
165         setupRunnerWith(
166             GradleFile.create(
167                 "QualifierUsage.java",
168                 "package library1;",
169                 "",
170                 "import javax.inject.Inject;",
171                 "import library2.MyQualifier;",
172                 "",
173                 "public class QualifierUsage {",
174                 "  @Inject QualifierUsage() {}",
175                 "",
176                 "  @Inject void injectMethod(@MyQualifier int i) {}",
177                 "}"));
178     BuildResult result;
179     switch (transitiveDependencyType) {
180       case "implementation":
181         switch (strictSuperficialValidationMode) {
182           case "ENABLED":
183             result = runner.buildAndFail();
184             assertThat(result.getOutput()).contains("Task :app:compileJava FAILED");
185             assertThat(result.getOutput())
186                 .contains(
187                     "ComponentProcessingStep was unable to process 'app.MyComponent' because "
188                         + "'library2.MyQualifier' could not be resolved."
189                         + "\n  "
190                         + "\n  Dependency trace:"
191                         + "\n      => element (INTERFACE): library1.MyModule"
192                         + "\n      => element (METHOD): provideInt()"
193                         + "\n      => annotation: @MyQualifier"
194                         + "\n      => type (ERROR annotation type): library2.MyQualifier");
195             break;
196           case "DISABLED":
197             // When strict mode is disabled we fall back to the old behavior where the qualifier is
198             // missing and we do not throw an exception.
199             result = runner.build();
200             assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS);
201             assertThat(result.getOutput()).contains("REQUEST: java.lang.Integer");
202             break;
203           default: throw new AssertionError("Unexpected mode: " + strictSuperficialValidationMode);
204         }
205         break;
206       case "api":
207         result = runner.build();
208         assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS);
209         assertThat(result.getOutput()).contains("REQUEST: @library2.MyQualifier java.lang.Integer");
210         break;
211     }
212   }
213 
setupRunnerWith(GradleFile qualifierUsage)214   private GradleRunner setupRunnerWith(GradleFile qualifierUsage) throws IOException {
215     File projectDir = folder.getRoot();
216     GradleModule.create(projectDir)
217         .addSettingsFile(
218             "include 'app'",
219             "include 'library1'",
220             "include 'library2'",
221             "include 'spi-plugin'")
222         .addBuildFile(
223             "buildscript {",
224             "  ext {",
225             String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")),
226             "  }",
227             "}",
228             "",
229             "allprojects {",
230             "  repositories {",
231             "    mavenCentral()",
232             "    mavenLocal()",
233             "  }",
234             "}");
235 
236     GradleModule.create(projectDir, "app")
237         .addBuildFile(
238             "plugins {",
239             "  id 'java'",
240             "  id 'application'",
241             "}",
242             "tasks.withType(JavaCompile) {",
243             String.format(
244                 "    options.compilerArgs += '-Adagger.strictSuperficialValidation=%s'",
245                 strictSuperficialValidationMode),
246             "}",
247             "dependencies {",
248             "  implementation project(':library1')",
249             "  annotationProcessor project(':spi-plugin')",
250             "  implementation \"com.google.dagger:dagger:$dagger_version\"",
251             "  annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"",
252             "}")
253         .addSrcFile(
254             "MyComponent.java",
255             "package app;",
256             "",
257             "import dagger.Component;",
258             "import library1.MyModule;",
259             "import library1.QualifierUsage;",
260             "",
261             "@Component(modules = MyModule.class)",
262             "public interface MyComponent {",
263             "  QualifierUsage qualifierUsage();",
264             "}");
265 
266     GradleModule.create(projectDir, "library1")
267         .addBuildFile(
268             "plugins {",
269             "  id 'java'",
270             "  id 'java-library'",
271             "}",
272             "dependencies {",
273             transitiveDependencyType + " project(':library2')",
274             "  implementation \"com.google.dagger:dagger:$dagger_version\"",
275             "  annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"",
276             "}")
277         .addSrcFile(
278             "MyModule.java",
279             "package library1;",
280             "",
281             "import dagger.Module;",
282             "import dagger.Provides;",
283             "import library2.MyQualifier;",
284             "",
285             "@Module",
286             "public interface MyModule {",
287             "  @Provides",
288             "  @MyQualifier",
289             "  static int provideInt() {",
290             "    return 0;",
291             "  }",
292             "}")
293         .addSrcFile(qualifierUsage);
294 
295     GradleModule.create(projectDir, "library2")
296         .addBuildFile(
297             "plugins {",
298             "  id 'java'",
299             "  id 'java-library'",
300             "}",
301             "dependencies {",
302             "  implementation 'javax.inject:javax.inject:1'",
303             "}")
304         .addSrcFile(
305             "MyQualifier.java",
306             "package library2;",
307             "",
308             "import javax.inject.Qualifier;",
309             "",
310             "@Qualifier",
311             "public @interface MyQualifier {}");
312 
313     // This plugin is used to print output about bindings that we can assert on in tests.
314     GradleModule.create(projectDir, "spi-plugin")
315         .addBuildFile(
316             "plugins {",
317             "  id 'java'",
318             "}",
319             "dependencies {",
320             "  implementation \"com.google.dagger:dagger-spi:$dagger_version\"",
321             "  implementation 'com.google.auto.service:auto-service-annotations:1.0.1'",
322             "  annotationProcessor 'com.google.auto.service:auto-service:1.0.1'",
323             "}")
324         .addSrcFile(
325             "TestBindingGraphPlugin.java",
326             "package spiplugin;",
327             "",
328             "import com.google.auto.service.AutoService;",
329             "import dagger.model.BindingGraph;",
330             "import dagger.model.BindingGraph.DependencyEdge;",
331             "import dagger.model.DependencyRequest;",
332             "import dagger.spi.BindingGraphPlugin;",
333             "import dagger.spi.DiagnosticReporter;",
334             "",
335             "@AutoService(BindingGraphPlugin.class)",
336             "public class TestBindingGraphPlugin implements BindingGraphPlugin {",
337             "  @Override",
338             "  public void visitGraph(",
339             "      BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {",
340             "    bindingGraph.dependencyEdges().stream()",
341             "        .map(DependencyEdge::dependencyRequest)",
342             "        .map(DependencyRequest::key)",
343             "        .forEach(key -> System.out.println(\"REQUEST: \" + key));",
344             "  }",
345             "}");
346 
347     return GradleRunner.create()
348         .withArguments("--stacktrace", "build")
349         .withProjectDir(projectDir);
350   }
351 }
352