1 /*
2  * Copyright (C) 2020 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 dagger.internal.codegen;
18 
19 import androidx.room.compiler.processing.util.Source;
20 import com.google.common.collect.ImmutableList;
21 import com.google.common.collect.ImmutableMap;
22 import dagger.testing.compile.CompilerTests;
23 import org.junit.Test;
24 import org.junit.runner.RunWith;
25 import org.junit.runners.Parameterized;
26 import org.junit.runners.Parameterized.Parameters;
27 
28 /**
29  * Tests to make sure that delegate bindings where the impl depends on a binding in a subcomponent
30  * properly fail. These are regression tests for b/147020838.
31  */
32 @RunWith(Parameterized.class)
33 public class BindsDependsOnSubcomponentValidationTest {
34   @Parameters(name = "{0}")
parameters()35   public static ImmutableList<Object[]> parameters() {
36     return CompilerMode.TEST_PARAMETERS;
37   }
38 
39   private final CompilerMode compilerMode;
40 
BindsDependsOnSubcomponentValidationTest(CompilerMode compilerMode)41   public BindsDependsOnSubcomponentValidationTest(CompilerMode compilerMode) {
42     this.compilerMode = compilerMode;
43   }
44 
45   @Test
testBinds()46   public void testBinds() {
47     Source parentComponent =
48         CompilerTests.javaSource(
49             "test.ParentComponent",
50             "package test;",
51             "",
52             "import dagger.Component;",
53             "",
54             "@Component(modules = ParentModule.class)",
55             "interface ParentComponent {",
56             "  ChildComponent getChild();",
57             "}");
58     Source parentModule =
59         CompilerTests.javaSource(
60             "test.ParentModule",
61             "package test;",
62             "",
63             "import dagger.Binds;",
64             "import dagger.Module;",
65             "",
66             "@Module",
67             "interface ParentModule {",
68             "  @Binds Foo bindFoo(FooImpl impl);",
69             "}");
70     Source childComponent =
71         CompilerTests.javaSource(
72             "test.ChildComponent",
73             "package test;",
74             "",
75             "import dagger.Subcomponent;",
76             "",
77             "@Subcomponent(modules = ChildModule.class)",
78             "interface ChildComponent {",
79             "  Foo getFoo();",
80             "}");
81     Source childModule =
82         CompilerTests.javaSource(
83             "test.ChildModule",
84             "package test;",
85             "",
86             "import dagger.Module;",
87             "import dagger.Provides;",
88             "",
89             "@Module",
90             "interface ChildModule {",
91             "  @Provides static Long providLong() {",
92             "    return 0L;",
93             "  }",
94             "}");
95     Source iface =
96         CompilerTests.javaSource(
97             "test.Foo",
98             "package test;",
99             "",
100             "interface Foo {}");
101     Source impl =
102         CompilerTests.javaSource(
103             "test.FooImpl",
104             "package test;",
105             "",
106             "import javax.inject.Inject;",
107             "",
108             "class FooImpl implements Foo {",
109             "  @Inject FooImpl(Long l) {}",
110             "}");
111     CompilerTests.daggerCompiler(
112             parentComponent, parentModule, childComponent, childModule, iface, impl)
113         .withProcessingOptions(compilerMode.processorOptions())
114         .compile(
115             subject -> {
116               subject.hasErrorCount(1);
117               subject.hasErrorContaining("Long cannot be provided without an @Inject constructor")
118                   .onSource(parentComponent)
119                   .onLineContaining("interface ParentComponent");
120             });
121   }
122 
123   @Test
testSetBindings()124   public void testSetBindings() {
125     Source parentComponent =
126         CompilerTests.javaSource(
127             "test.ParentComponent",
128             "package test;",
129             "",
130             "import dagger.Component;",
131             "",
132             "@Component(modules = ParentModule.class)",
133             "interface ParentComponent {",
134             "  ChildComponent getChild();",
135             "}");
136     Source parentModule =
137         CompilerTests.javaSource(
138             "test.ParentModule",
139             "package test;",
140             "",
141             "import dagger.Binds;",
142             "import dagger.Module;",
143             "import dagger.multibindings.IntoSet;",
144             "",
145             "@Module",
146             "interface ParentModule {",
147             "  @Binds @IntoSet Foo bindFoo(FooImpl impl);",
148             "}");
149     Source childComponent =
150         CompilerTests.javaSource(
151             "test.ChildComponent",
152             "package test;",
153             "",
154             "import dagger.Subcomponent;",
155             "import java.util.Set;",
156             "",
157             "@Subcomponent(modules = ChildModule.class)",
158             "interface ChildComponent {",
159             "  Set<Foo> getFooSet();",
160             "}");
161     Source childModule =
162         CompilerTests.javaSource(
163             "test.ChildModule",
164             "package test;",
165             "",
166             "import dagger.Module;",
167             "import dagger.Provides;",
168             "",
169             "@Module",
170             "interface ChildModule {",
171             "  @Provides static Long providLong() {",
172             "    return 0L;",
173             "  }",
174             "}");
175     Source iface =
176         CompilerTests.javaSource(
177             "test.Foo",
178             "package test;",
179             "",
180             "interface Foo {}");
181     Source impl =
182         CompilerTests.javaSource(
183             "test.FooImpl",
184             "package test;",
185             "",
186             "import javax.inject.Inject;",
187             "",
188             "class FooImpl implements Foo {",
189             "  @Inject FooImpl(Long l) {}",
190             "}");
191     CompilerTests.daggerCompiler(
192             parentComponent, parentModule, childComponent, childModule, iface, impl)
193         .withProcessingOptions(compilerMode.processorOptions())
194         .compile(
195             subject -> {
196               subject.hasErrorCount(1);
197               subject.hasErrorContaining("Long cannot be provided without an @Inject constructor")
198                   .onSource(parentComponent)
199                   .onLineContaining("interface ParentComponent");
200             });
201   }
202 
203   @Test
testSetValueBindings()204   public void testSetValueBindings() {
205     Source parentComponent =
206         CompilerTests.javaSource(
207             "test.ParentComponent",
208             "package test;",
209             "",
210             "import dagger.Component;",
211             "",
212             "@Component(modules = ParentModule.class)",
213             "interface ParentComponent {",
214             "  ChildComponent getChild();",
215             "}");
216     Source parentModule =
217         CompilerTests.javaSource(
218             "test.ParentModule",
219             "package test;",
220             "",
221             "import dagger.Module;",
222             "import dagger.Provides;",
223             "import dagger.multibindings.ElementsIntoSet;",
224             "import java.util.Collections;",
225             "import java.util.Set;",
226             "",
227             "@Module",
228             "interface ParentModule {",
229             "  @Provides @ElementsIntoSet",
230             "  static Set<Foo> provideFoo(FooImpl impl) {",
231             "    return Collections.singleton(impl);",
232             "  }",
233             "}");
234     Source childComponent =
235         CompilerTests.javaSource(
236             "test.ChildComponent",
237             "package test;",
238             "",
239             "import dagger.Subcomponent;",
240             "import java.util.Set;",
241             "",
242             "@Subcomponent(modules = ChildModule.class)",
243             "interface ChildComponent {",
244             "  Set<Foo> getFooSet();",
245             "}");
246     Source childModule =
247         CompilerTests.javaSource(
248             "test.ChildModule",
249             "package test;",
250             "",
251             "import dagger.Module;",
252             "import dagger.Provides;",
253             "",
254             "@Module",
255             "interface ChildModule {",
256             "  @Provides static Long providLong() {",
257             "    return 0L;",
258             "  }",
259             "}");
260     Source iface =
261         CompilerTests.javaSource(
262             "test.Foo",
263             "package test;",
264             "",
265             "interface Foo {}");
266     Source impl =
267         CompilerTests.javaSource(
268             "test.FooImpl",
269             "package test;",
270             "",
271             "import javax.inject.Inject;",
272             "",
273             "class FooImpl implements Foo {",
274             "  @Inject FooImpl(Long l) {}",
275             "}");
276     CompilerTests.daggerCompiler(
277             parentComponent, parentModule, childComponent, childModule, iface, impl)
278         .withProcessingOptions(compilerMode.processorOptions())
279         .compile(
280             subject -> {
281               subject.hasErrorCount(1);
282               subject.hasErrorContaining("Long cannot be provided without an @Inject constructor")
283                   .onSource(parentComponent)
284                   .onLineContaining("interface ParentComponent");
285             });
286   }
287 
288   @Test
testMapBindings()289   public void testMapBindings() {
290     Source parentComponent =
291         CompilerTests.javaSource(
292             "test.ParentComponent",
293             "package test;",
294             "",
295             "import dagger.Component;",
296             "",
297             "@Component(modules = ParentModule.class)",
298             "interface ParentComponent {",
299             "  ChildComponent getChild();",
300             "}");
301     Source parentModule =
302         CompilerTests.javaSource(
303             "test.ParentModule",
304             "package test;",
305             "",
306             "import dagger.Binds;",
307             "import dagger.Module;",
308             "import dagger.multibindings.IntoMap;",
309             "import dagger.multibindings.StringKey;",
310             "",
311             "@Module",
312             "interface ParentModule {",
313             "  @Binds @IntoMap @StringKey(\"foo\") Foo bindFoo(FooImpl impl);",
314             "}");
315     Source childComponent =
316         CompilerTests.javaSource(
317             "test.ChildComponent",
318             "package test;",
319             "",
320             "import dagger.Subcomponent;",
321             "import java.util.Map;",
322             "",
323             "@Subcomponent(modules = ChildModule.class)",
324             "interface ChildComponent {",
325             "  Map<String, Foo> getFooSet();",
326             "}");
327     Source childModule =
328         CompilerTests.javaSource(
329             "test.ChildModule",
330             "package test;",
331             "",
332             "import dagger.Module;",
333             "import dagger.Provides;",
334             "",
335             "@Module",
336             "interface ChildModule {",
337             "  @Provides static Long providLong() {",
338             "    return 0L;",
339             "  }",
340             "}");
341     Source iface =
342         CompilerTests.javaSource(
343             "test.Foo",
344             "package test;",
345             "",
346             "interface Foo {}");
347     Source impl =
348         CompilerTests.javaSource(
349             "test.FooImpl",
350             "package test;",
351             "",
352             "import javax.inject.Inject;",
353             "",
354             "class FooImpl implements Foo {",
355             "  @Inject FooImpl(Long l) {}",
356             "}");
357     CompilerTests.daggerCompiler(
358             parentComponent, parentModule, childComponent, childModule, iface, impl)
359         .withProcessingOptions(
360             ImmutableMap.<String, String>builder()
361                 .putAll(compilerMode.processorOptions())
362                 // TODO(erichang): make this flag the default and remove this
363                 .put("dagger.strictMultibindingValidation", "enabled")
364                 .build())
365         .compile(
366             subject -> {
367               subject.hasErrorCount(1);
368               subject.hasErrorContaining("Long cannot be provided without an @Inject constructor")
369                   .onSource(parentComponent)
370                   .onLineContaining("interface ParentComponent");
371             });
372   }
373 }
374