xref: /aosp_15_r20/external/dagger2/javatests/dagger/internal/codegen/MapMultibindingValidationTest.java (revision f585d8a307d0621d6060bd7e80091fdcbf94fe27)
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 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 @RunWith(Parameterized.class)
29 public class MapMultibindingValidationTest {
30   @Parameters(name = "{0}")
parameters()31   public static ImmutableList<Object[]> parameters() {
32     return CompilerMode.TEST_PARAMETERS;
33   }
34 
35   private final CompilerMode compilerMode;
36 
MapMultibindingValidationTest(CompilerMode compilerMode)37   public MapMultibindingValidationTest(CompilerMode compilerMode) {
38     this.compilerMode = compilerMode;
39   }
40 
41   @Test
duplicateMapKeys_UnwrappedMapKey()42   public void duplicateMapKeys_UnwrappedMapKey() {
43     Source module =
44         CompilerTests.javaSource(
45             "test.MapModule",
46             "package test;",
47             "",
48             "import dagger.Module;",
49             "import dagger.Provides;",
50             "import dagger.multibindings.StringKey;",
51             "import dagger.multibindings.IntoMap;",
52             "",
53             "@Module",
54             "final class MapModule {",
55             "  @Provides @IntoMap @StringKey(\"AKey\") Object provideObjectForAKey() {",
56             "    return \"one\";",
57             "  }",
58             "",
59             "  @Provides @IntoMap @StringKey(\"AKey\") Object provideObjectForAKeyAgain() {",
60             "    return \"one again\";",
61             "  }",
62             "}");
63 
64     // If they're all there, report only Map<K, V>.
65     CompilerTests.daggerCompiler(
66             module,
67             component(
68                 "Map<String, Object> objects();",
69                 "Map<String, Provider<Object>> objectProviders();",
70                 "Producer<Map<String, Producer<Object>>> objectProducers();"))
71         .withProcessingOptions(compilerMode.processorOptions())
72         .compile(
73             subject -> {
74               subject.hasErrorCount(1);
75               subject.hasErrorContaining(
76                   "The same map key is bound more than once for Map<String,Object>");
77               subject.hasErrorContaining("provideObjectForAKey()");
78               subject.hasErrorContaining("provideObjectForAKeyAgain()");
79             });
80 
81     CompilerTests.daggerCompiler(module)
82         .withProcessingOptions(
83             ImmutableMap.<String, String>builder()
84                 .putAll(compilerMode.processorOptions())
85                 .put("dagger.fullBindingGraphValidation", "ERROR")
86                 .buildOrThrow())
87         .compile(
88             subject -> {
89               subject.hasErrorCount(1);
90               subject.hasErrorContaining(
91                       "The same map key is bound more than once for Map<String,Provider<Object>>")
92                   .onSource(module)
93                   .onLineContaining("class MapModule");
94               subject.hasErrorContaining("provideObjectForAKey()");
95               subject.hasErrorContaining("provideObjectForAKeyAgain()");
96             });
97 
98     // If there's Map<K, V> and Map<K, Provider<V>>, report only Map<K, V>.
99     CompilerTests.daggerCompiler(
100             module,
101             component(
102                 "Map<String, Object> objects();",
103                 "Map<String, Provider<Object>> objectProviders();"))
104         .withProcessingOptions(compilerMode.processorOptions())
105         .compile(
106             subject -> {
107               subject.hasErrorCount(1);
108               subject.hasErrorContaining(
109                   "The same map key is bound more than once for Map<String,Object>");
110             });
111 
112     // If there's Map<K, V> and Map<K, Producer<V>>, report only Map<K, V>.
113     CompilerTests.daggerCompiler(
114             module,
115             component(
116                 "Map<String, Object> objects();",
117                 "Producer<Map<String, Producer<Object>>> objectProducers();"))
118         .withProcessingOptions(compilerMode.processorOptions())
119         .compile(
120             subject -> {
121               subject.hasErrorCount(1);
122               subject.hasErrorContaining(
123                   "The same map key is bound more than once for Map<String,Object>");
124             });
125 
126     // If there's Map<K, Provider<V>> and Map<K, Producer<V>>, report only Map<K, Provider<V>>.
127     CompilerTests.daggerCompiler(
128             module,
129             component(
130                 "Map<String, Provider<Object>> objectProviders();",
131                 "Producer<Map<String, Producer<Object>>> objectProducers();"))
132         .withProcessingOptions(compilerMode.processorOptions())
133         .compile(
134             subject -> {
135               subject.hasErrorCount(1);
136               subject.hasErrorContaining(
137                   "The same map key is bound more than once for Map<String,Provider<Object>>");
138             });
139 
140     CompilerTests.daggerCompiler(
141             module,
142             component("Map<String, Object> objects();"))
143         .withProcessingOptions(compilerMode.processorOptions())
144         .compile(
145             subject -> {
146               subject.hasErrorCount(1);
147               subject.hasErrorContaining(
148                   "The same map key is bound more than once for Map<String,Object>");
149             });
150 
151     CompilerTests.daggerCompiler(
152             module,
153             component("Map<String, Provider<Object>> objectProviders();"))
154         .withProcessingOptions(compilerMode.processorOptions())
155         .compile(
156             subject -> {
157               subject.hasErrorCount(1);
158               subject.hasErrorContaining(
159                   "The same map key is bound more than once for Map<String,Provider<Object>>");
160             });
161 
162     CompilerTests.daggerCompiler(
163             module,
164             component("Producer<Map<String, Producer<Object>>> objectProducers();"))
165         .withProcessingOptions(compilerMode.processorOptions())
166         .compile(
167             subject -> {
168               subject.hasErrorCount(1);
169               subject.hasErrorContaining(
170                   "The same map key is bound more than once for Map<String,Producer<Object>>");
171             });
172   }
173 
174   @Test
duplicateMapKeys_WrappedMapKey()175   public void duplicateMapKeys_WrappedMapKey() {
176     Source module =
177         CompilerTests.javaSource(
178             "test.MapModule",
179             "package test;",
180             "",
181             "import dagger.Module;",
182             "import dagger.Provides;",
183             "import dagger.multibindings.IntoMap;",
184             "import dagger.MapKey;",
185             "",
186             "@Module",
187             "abstract class MapModule {",
188             "",
189             "  @MapKey(unwrapValue = false)",
190             "  @interface WrappedMapKey {",
191             "    String value();",
192             "  }",
193             "",
194             "  @Provides",
195             "  @IntoMap",
196             "  @WrappedMapKey(\"foo\")",
197             "  static String stringMapEntry1() { return \"\"; }",
198             "",
199             "  @Provides",
200             "  @IntoMap",
201             "  @WrappedMapKey(\"foo\")",
202             "  static String stringMapEntry2() { return \"\"; }",
203             "}");
204 
205     Source component = component("Map<test.MapModule.WrappedMapKey, String> objects();");
206 
207     CompilerTests.daggerCompiler(module, component)
208         .withProcessingOptions(compilerMode.processorOptions())
209         .compile(
210             subject -> {
211               subject.hasErrorCount(1);
212               subject.hasErrorContaining(
213                       String.join(
214                           "\n",
215                           "\033[1;31m[Dagger/MapKeys]\033[0m The same map key is bound more than "
216                               + "once for Map<MapModule.WrappedMapKey,String>",
217                           "    @Provides @IntoMap @MapModule.WrappedMapKey(\"foo\") String "
218                               + "MapModule.stringMapEntry1()",
219                           "    @Provides @IntoMap @MapModule.WrappedMapKey(\"foo\") String "
220                               + "MapModule.stringMapEntry2()"))
221                   .onSource(component)
222                   .onLineContaining("interface TestComponent");
223             });
224   }
225 
226   @Test
inconsistentMapKeyAnnotations()227   public void inconsistentMapKeyAnnotations() {
228     Source module =
229         CompilerTests.javaSource(
230             "test.MapModule",
231             "package test;",
232             "",
233             "import dagger.Module;",
234             "import dagger.Provides;",
235             "import dagger.multibindings.StringKey;",
236             "import dagger.multibindings.IntoMap;",
237             "",
238             "@Module",
239             "final class MapModule {",
240             "  @Provides @IntoMap @StringKey(\"AKey\") Object provideObjectForAKey() {",
241             "    return \"one\";",
242             "  }",
243             "",
244             "  @Provides @IntoMap @StringKeyTwo(\"BKey\") Object provideObjectForBKey() {",
245             "    return \"two\";",
246             "  }",
247             "}");
248     Source stringKeyTwoFile =
249         CompilerTests.javaSource(
250             "test.StringKeyTwo",
251             "package test;",
252             "",
253             "import dagger.MapKey;",
254             "",
255             "@MapKey(unwrapValue = true)",
256             "public @interface StringKeyTwo {",
257             "  String value();",
258             "}");
259 
260     // If they're all there, report only Map<K, V>.
261     CompilerTests.daggerCompiler(
262             module,
263             stringKeyTwoFile,
264             component(
265                 "Map<String, Object> objects();",
266                 "Map<String, Provider<Object>> objectProviders();",
267                 "Producer<Map<String, Producer<Object>>> objectProducers();"))
268         .withProcessingOptions(compilerMode.processorOptions())
269         .compile(
270             subject -> {
271               subject.hasErrorCount(1);
272               subject.hasErrorContaining(
273                   "Map<String,Object> uses more than one @MapKey annotation type");
274               subject.hasErrorContaining("provideObjectForAKey()");
275               subject.hasErrorContaining("provideObjectForBKey()");
276             });
277 
278     CompilerTests.daggerCompiler(module, stringKeyTwoFile)
279         .withProcessingOptions(
280             ImmutableMap.<String, String>builder()
281                 .putAll(compilerMode.processorOptions())
282                 .put("dagger.fullBindingGraphValidation", "ERROR")
283                 .buildOrThrow())
284         .compile(
285             subject -> {
286               subject.hasErrorCount(1);
287               subject.hasErrorContaining(
288                       "Map<String,Provider<Object>> uses more than one @MapKey annotation type")
289                   .onSource(module)
290                   .onLineContaining("class MapModule");
291               subject.hasErrorContaining("provideObjectForAKey()");
292               subject.hasErrorContaining("provideObjectForBKey()");
293             });
294 
295     // If there's Map<K, V> and Map<K, Provider<V>>, report only Map<K, V>.
296     CompilerTests.daggerCompiler(
297             module,
298             stringKeyTwoFile,
299             component(
300                 "Map<String, Object> objects();",
301                 "Map<String, Provider<Object>> objectProviders();"))
302         .withProcessingOptions(compilerMode.processorOptions())
303         .compile(
304             subject -> {
305               subject.hasErrorCount(1);
306               subject.hasErrorContaining(
307                   "Map<String,Object> uses more than one @MapKey annotation type");
308             });
309 
310     // If there's Map<K, V> and Map<K, Producer<V>>, report only Map<K, V>.
311     CompilerTests.daggerCompiler(
312             module,
313             stringKeyTwoFile,
314             component(
315                 "Map<String, Object> objects();",
316                 "Producer<Map<String, Producer<Object>>> objectProducers();"))
317         .withProcessingOptions(compilerMode.processorOptions())
318         .compile(
319             subject -> {
320               subject.hasErrorCount(1);
321               subject.hasErrorContaining(
322                   "Map<String,Object> uses more than one @MapKey annotation type");
323             });
324 
325     // If there's Map<K, Provider<V>> and Map<K, Producer<V>>, report only Map<K, Provider<V>>.
326     CompilerTests.daggerCompiler(
327             module,
328             stringKeyTwoFile,
329             component(
330                 "Map<String, Provider<Object>> objectProviders();",
331                 "Producer<Map<String, Producer<Object>>> objectProducers();"))
332         .withProcessingOptions(compilerMode.processorOptions())
333         .compile(
334             subject -> {
335               subject.hasErrorCount(1);
336               subject.hasErrorContaining(
337                   "Map<String,Provider<Object>> uses more than one @MapKey annotation type");
338             });
339 
340     CompilerTests.daggerCompiler(
341             module,
342             stringKeyTwoFile,
343             component("Map<String, Object> objects();"))
344         .withProcessingOptions(compilerMode.processorOptions())
345         .compile(
346             subject -> {
347               subject.hasErrorCount(1);
348               subject.hasErrorContaining(
349                   "Map<String,Object> uses more than one @MapKey annotation type");
350             });
351 
352     CompilerTests.daggerCompiler(
353             module,
354             stringKeyTwoFile,
355             component("Map<String, Provider<Object>> objectProviders();"))
356         .withProcessingOptions(compilerMode.processorOptions())
357         .compile(
358             subject -> {
359               subject.hasErrorCount(1);
360               subject.hasErrorContaining(
361                   "Map<String,Provider<Object>> uses more than one @MapKey annotation type");
362             });
363 
364     CompilerTests.daggerCompiler(
365             module,
366             stringKeyTwoFile,
367             component("Producer<Map<String, Producer<Object>>> objectProducers();"))
368         .withProcessingOptions(compilerMode.processorOptions())
369         .compile(
370             subject -> {
371               subject.hasErrorCount(1);
372               subject.hasErrorContaining(
373                   "Map<String,Producer<Object>> uses more than one @MapKey annotation type");
374             });
375   }
376 
component(String... entryPoints)377   private static Source component(String... entryPoints) {
378     return CompilerTests.javaSource(
379         "test.TestComponent",
380         ImmutableList.<String>builder()
381             .add(
382                 "package test;",
383                 "",
384                 "import dagger.Component;",
385                 "import dagger.producers.Producer;",
386                 "import java.util.Map;",
387                 "import javax.inject.Provider;",
388                 "",
389                 "@Component(modules = {MapModule.class})",
390                 "interface TestComponent {")
391             .add(entryPoints)
392             .add("}")
393             .build());
394   }
395 }
396