1 /*
2  * Copyright (C) 2014 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 dagger.testing.compile.CompilerTests;
22 import org.junit.Test;
23 import org.junit.runner.RunWith;
24 import org.junit.runners.Parameterized;
25 import org.junit.runners.Parameterized.Parameters;
26 
27 /**
28  * Tests that errors are reported correctly when a {@code @Binds} method's delegate (the type of its
29  * parameter) is missing.
30  */
31 @RunWith(Parameterized.class)
32 public class BindsMissingDelegateValidationTest {
33   @Parameters(name = "{0}")
parameters()34   public static ImmutableList<Object[]> parameters() {
35     return CompilerMode.TEST_PARAMETERS;
36   }
37 
38   private final CompilerMode compilerMode;
39 
BindsMissingDelegateValidationTest(CompilerMode compilerMode)40   public BindsMissingDelegateValidationTest(CompilerMode compilerMode) {
41     this.compilerMode = compilerMode;
42   }
43 
44   @Test
bindsMissingDelegate()45   public void bindsMissingDelegate() {
46     Source component =
47         CompilerTests.javaSource(
48             "test.C",
49             "package test;",
50             "",
51             "import dagger.Binds;",
52             "import dagger.Component;",
53             "import dagger.Module;",
54             "",
55             "@Component(modules = C.TestModule.class)",
56             "interface C {",
57             "  Object object();",
58             "",
59             "  static class NotBound {}",
60             "",
61             "  @Module",
62             "  abstract static class TestModule {",
63             "    @Binds abstract Object bindObject(NotBound notBound);",
64             "  }",
65             "}");
66 
67     CompilerTests.daggerCompiler(component)
68         .withProcessingOptions(compilerMode.processorOptions())
69         .compile(
70             subject -> {
71               subject.hasErrorCount(1);
72               subject.hasErrorContaining("test.C.NotBound cannot be provided")
73                   .onSource(component)
74                   .onLineContaining("interface C");
75             });
76   }
77 
78   @Test
bindsMissingDelegate_duplicateBinding()79   public void bindsMissingDelegate_duplicateBinding() {
80     Source component =
81         CompilerTests.javaSource(
82             "test.C",
83             "package test;",
84             "",
85             "import dagger.Binds;",
86             "import dagger.Component;",
87             "import dagger.Module;",
88             "import dagger.Provides;",
89             "",
90             "@Component(modules = C.TestModule.class)",
91             "interface C {",
92             "  Object object();",
93             "",
94             "  static class NotBound {}",
95             "",
96             "  @Module",
97             "  abstract static class TestModule {",
98             "    @Binds abstract Object bindObject(NotBound notBound);",
99             "    @Provides static Object provideObject() { return new Object(); }",
100             "  }",
101             "}");
102 
103     CompilerTests.daggerCompiler(component)
104         .withProcessingOptions(compilerMode.processorOptions())
105         .compile(
106             subject -> {
107               subject.hasErrorCount(1);
108               // Some versions of javacs report only the first error for each source line so we
109               // allow 1 of the assertions below to fail.
110               // TODO(bcorso): Add CompilationResultSubject#hasErrorContainingMatch() to do this
111               // more elegantly (see CL/469765892).
112               java.util.List<Error> errors = new java.util.ArrayList<>();
113               try {
114                 subject.hasErrorContaining("test.C.NotBound cannot be provided")
115                     .onSource(component)
116                     .onLineContaining("interface C");
117               } catch (Error e) {
118                 errors.add(e);
119               }
120               try {
121                 subject.hasErrorContaining("Object is bound multiple times:")
122                     .onSource(component)
123                     .onLineContaining("interface C");
124                 subject.hasErrorContaining(
125                     "@Binds Object test.C.TestModule.bindObject(test.C.NotBound)");
126                 subject.hasErrorContaining("@Provides Object test.C.TestModule.provideObject()");
127               } catch (Error e) {
128                 errors.add(e);
129               }
130               com.google.common.truth.Truth.assertThat(errors.size()).isAtMost(1);
131             });
132   }
133 
134   @Test
bindsMissingDelegate_setBinding()135   public void bindsMissingDelegate_setBinding() {
136     Source component =
137         CompilerTests.javaSource(
138             "test.C",
139             "package test;",
140             "",
141             "import dagger.Binds;",
142             "import dagger.Component;",
143             "import dagger.Module;",
144             "import dagger.multibindings.IntoSet;",
145             "import java.util.Set;",
146             "",
147             "@Component(modules = C.TestModule.class)",
148             "interface C {",
149             "  Set<Object> objects();",
150             "",
151             "  static class NotBound {}",
152             "",
153             "  @Module",
154             "  abstract static class TestModule {",
155             "    @Binds @IntoSet abstract Object bindObject(NotBound notBound);",
156             "  }",
157             "}");
158 
159     CompilerTests.daggerCompiler(component)
160         .withProcessingOptions(compilerMode.processorOptions())
161         .compile(
162             subject -> {
163               subject.hasErrorCount(1);
164               subject.hasErrorContaining("test.C.NotBound cannot be provided")
165                   .onSource(component)
166                   .onLineContaining("interface C");
167             });
168   }
169 
170   @Test
bindsMissingDelegate_mapBinding()171   public void bindsMissingDelegate_mapBinding() {
172     Source component =
173         CompilerTests.javaSource(
174             "test.C",
175             "package test;",
176             "",
177             "import dagger.Binds;",
178             "import dagger.Component;",
179             "import dagger.Module;",
180             "import dagger.multibindings.IntoMap;",
181             "import dagger.multibindings.StringKey;",
182             "import java.util.Map;",
183             "",
184             "@Component(modules = C.TestModule.class)",
185             "interface C {",
186             "  Map<String, Object> objects();",
187             "",
188             "  static class NotBound {}",
189             "",
190             "  @Module",
191             "  abstract static class TestModule {",
192             "    @Binds @IntoMap @StringKey(\"key\")",
193             "    abstract Object bindObject(NotBound notBound);",
194             "  }",
195             "}");
196 
197     CompilerTests.daggerCompiler(component)
198         .withProcessingOptions(compilerMode.processorOptions())
199         .compile(
200             subject -> {
201               subject.hasErrorCount(1);
202               subject.hasErrorContaining("test.C.NotBound cannot be provided")
203                   .onSource(component)
204                   .onLineContaining("interface C");
205             });
206   }
207 
208   @Test
bindsMissingDelegate_mapBinding_sameKey()209   public void bindsMissingDelegate_mapBinding_sameKey() {
210     Source component =
211         CompilerTests.javaSource(
212             "test.C",
213             "package test;",
214             "",
215             "import dagger.Binds;",
216             "import dagger.Component;",
217             "import dagger.Module;",
218             "import dagger.Provides;",
219             "import dagger.multibindings.IntoMap;",
220             "import dagger.multibindings.StringKey;",
221             "import java.util.Map;",
222             "",
223             "@Component(modules = C.TestModule.class)",
224             "interface C {",
225             "  Map<String, Object> objects();",
226             "",
227             "  static class NotBound {}",
228             "",
229             "  @Module",
230             "  abstract static class TestModule {",
231             "    @Binds @IntoMap @StringKey(\"key\")",
232             "    abstract Object bindObject(NotBound notBound);",
233             "",
234             "    @Provides @IntoMap @StringKey(\"key\")",
235             "    static Object provideObject() { return new Object(); }",
236             "  }",
237             "}");
238 
239 
240     CompilerTests.daggerCompiler(component)
241         .withProcessingOptions(compilerMode.processorOptions())
242         .compile(
243             subject -> {
244               subject.hasErrorCount(1);
245               // Some versions of javacs report only the first error for each source line so we
246               // allow 1 of the assertions below to fail.
247               // TODO(bcorso): Add CompilationResultSubject#hasErrorContainingMatch() to do this
248               // more elegantly (see CL/469765892).
249               java.util.List<Error> errors = new java.util.ArrayList<>();
250               try {
251                 subject.hasErrorContaining("test.C.NotBound cannot be provided")
252                     .onSource(component)
253                     .onLineContaining("interface C");
254               } catch (Error e) {
255                 errors.add(e);
256               }
257               try {
258                 subject.hasErrorContaining("same map key is bound more than once")
259                     .onSource(component)
260                     .onLineContaining("interface C");
261               } catch (Error e) {
262                 errors.add(e);
263               }
264               com.google.common.truth.Truth.assertThat(errors.size()).isAtMost(1);
265             });
266   }
267 }
268