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