1package org.robolectric;
2
3import static com.google.common.collect.ImmutableMap.of;
4import static java.nio.charset.StandardCharsets.UTF_8;
5import static org.assertj.core.api.Assertions.assertThat;
6import static org.robolectric.annotation.Config.DEFAULT_APPLICATION;
7
8import android.app.Application;
9import java.io.ByteArrayInputStream;
10import java.io.InputStream;
11import java.lang.reflect.Method;
12import java.util.HashMap;
13import java.util.Map;
14import org.junit.Ignore;
15import org.junit.Test;
16import org.junit.runner.RunWith;
17import org.junit.runners.JUnit4;
18import org.junit.runners.model.InitializationError;
19import org.robolectric.annotation.Config;
20import org.robolectric.shadows.ShadowView;
21import org.robolectric.shadows.ShadowViewGroup;
22
23@RunWith(JUnit4.class)
24public class ConfigMergerTest {
25
26  @Test public void defaultValuesAreMerged() throws Exception {
27    assertThat(configFor(Test2.class, "withoutAnnotation",
28        new Config.Builder().build()).manifest())
29        .isEqualTo("AndroidManifest.xml");
30  }
31
32  @Test public void globalValuesAreMerged() throws Exception {
33    assertThat(configFor(Test2.class, "withoutAnnotation",
34        new Config.Builder().setManifest("ManifestFromGlobal.xml").build()).manifest())
35        .isEqualTo("ManifestFromGlobal.xml");
36  }
37
38  @Test
39  public void whenClassHasConfigAnnotation_getConfig_shouldMergeClassAndMethodConfig() throws Exception {
40    assertConfig(configFor(Test1.class, "withoutAnnotation"),
41        new int[] {1}, "foo", TestFakeApp.class, "com.example.test", "from-test", "test/res", "test/assets", new Class[]{Test1.class}, new String[]{"com.example.test1"}, new String[]{"libs/test"}, BuildConfigConstants.class);
42
43    assertConfig(configFor(Test1.class, "withDefaultsAnnotation"),
44        new int[] {1}, "foo", TestFakeApp.class, "com.example.test", "from-test", "test/res", "test/assets", new Class[]{Test1.class}, new String[]{"com.example.test1"}, new String[]{"libs/test"}, BuildConfigConstants.class);
45
46    assertConfig(configFor(Test1.class, "withOverrideAnnotation"),
47        new int[] {9}, "furf", TestApplication.class, "com.example.method", "from-method", "method/res", "method/assets", new Class[]{Test1.class, Test2.class}, new String[]{"com.example.test1", "com.example.method1"}, new String[]{"libs/method", "libs/test"}, BuildConfigConstants2.class);
48  }
49
50  @Test
51  public void whenClassDoesntHaveConfigAnnotation_getConfig_shouldUseMethodConfig() throws Exception {
52    assertConfig(configFor(Test2.class, "withoutAnnotation"),
53        new int[0], "AndroidManifest.xml", DEFAULT_APPLICATION, "", "", "res", "assets", new Class[]{}, new String[]{}, new String[]{}, Void.class);
54
55    assertConfig(configFor(Test2.class, "withDefaultsAnnotation"),
56        new int[0], "AndroidManifest.xml", DEFAULT_APPLICATION, "", "", "res", "assets", new Class[]{}, new String[]{}, new String[]{}, Void.class);
57
58    assertConfig(configFor(Test2.class, "withOverrideAnnotation"),
59        new int[] {9}, "furf", TestFakeApp.class, "com.example.method", "from-method", "method/res", "method/assets", new Class[]{Test1.class}, new String[]{"com.example.method2"}, new String[]{"libs/method"}, BuildConfigConstants.class);
60  }
61
62  @Test
63  public void whenClassDoesntHaveConfigAnnotation_getConfig_shouldMergeParentClassAndMethodConfig() throws Exception {
64    assertConfig(configFor(Test5.class, "withoutAnnotation"),
65        new int[] {1}, "foo", TestFakeApp.class, "com.example.test", "from-test", "test/res",
66        "test/assets", new Class[]{Test1.class, Test1.class}, new String[]{"com.example.test1"},
67        new String[]{"libs/test"}, BuildConfigConstants.class);
68
69    assertConfig(configFor(Test5.class, "withDefaultsAnnotation"),
70        new int[] {1}, "foo", TestFakeApp.class, "com.example.test", "from-test", "test/res",
71        "test/assets", new Class[]{Test1.class, Test1.class}, new String[]{"com.example.test1"},
72        new String[]{"libs/test"}, BuildConfigConstants.class);
73
74    assertConfig(configFor(Test5.class, "withOverrideAnnotation"),
75        new int[] {14}, "foo", TestFakeApp.class, "com.example.test", "from-method5", "test/res",
76        "method5/assets", new Class[]{Test1.class, Test1.class, Test5.class},
77        new String[]{"com.example.test1", "com.example.method5"}, new String[]{"libs/test"}, BuildConfigConstants5.class);
78  }
79
80  @Test
81  public void whenClassAndParentClassHaveConfigAnnotation_getConfig_shouldMergeParentClassAndMethodConfig() throws Exception {
82    assertConfig(configFor(Test6.class, "withoutAnnotation"),
83        new int[] {1}, "foo", TestFakeApp.class, "com.example.test", "from-class6", "class6/res",
84        "test/assets", new Class[]{Test1.class, Test1.class, Test6.class},
85        new String[]{"com.example.test1", "com.example.test6"},
86        new String[]{"libs/test"}, BuildConfigConstants6.class);
87
88    assertConfig(configFor(Test6.class, "withDefaultsAnnotation"),
89        new int[] {1}, "foo", TestFakeApp.class, "com.example.test", "from-class6", "class6/res",
90        "test/assets", new Class[]{Test1.class, Test1.class, Test6.class},
91        new String[]{"com.example.test1", "com.example.test6"},
92        new String[]{"libs/test"}, BuildConfigConstants6.class);
93
94    assertConfig(configFor(Test6.class, "withOverrideAnnotation"),
95        new int[] {14}, "foo", TestFakeApp.class, "com.example.test", "from-method5", "class6/res",
96        "method5/assets", new Class[]{Test1.class, Test1.class, Test6.class, Test5.class},
97        new String[]{"com.example.test1", "com.example.method5", "com.example.test6"},
98        new String[]{"libs/test"}, BuildConfigConstants5.class);
99  }
100
101  @Test
102  public void whenClassAndSubclassHaveConfigAnnotation_getConfig_shouldMergeClassSubclassAndMethodConfig() throws Exception {
103    assertConfig(configFor(Test3.class, "withoutAnnotation"),
104        new int[] {1}, "foo", TestFakeApp.class, "com.example.test", "from-subclass", "test/res", "test/assets", new Class[]{Test1.class}, new String[]{"com.example.test1"}, new String[]{"libs/test"}, BuildConfigConstants.class);
105
106    assertConfig(configFor(Test3.class, "withDefaultsAnnotation"),
107        new int[] {1}, "foo", TestFakeApp.class, "com.example.test", "from-subclass", "test/res", "test/assets", new Class[]{Test1.class}, new String[]{"com.example.test1"}, new String[]{"libs/test"}, BuildConfigConstants.class);
108
109    assertConfig(configFor(Test3.class, "withOverrideAnnotation"),
110        new int[] {9},"furf", TestApplication.class, "com.example.method", "from-method", "method/res", "method/assets", new Class[]{Test1.class, Test2.class}, new String[]{"com.example.test1", "com.example.method1"}, new String[]{"libs/method", "libs/test"}, BuildConfigConstants2.class);
111  }
112
113  @Test
114  public void whenClassDoesntHaveConfigAnnotationButSubclassDoes_getConfig_shouldMergeSubclassAndMethodConfig() throws Exception {
115    assertConfig(configFor(Test4.class, "withoutAnnotation"),
116        new int[0],  "AndroidManifest.xml", DEFAULT_APPLICATION, "", "from-subclass", "res", "assets", new Class[]{}, new String[]{}, new String[]{}, Void.class);
117
118    assertConfig(configFor(Test4.class, "withDefaultsAnnotation"),
119        new int[0],  "AndroidManifest.xml", DEFAULT_APPLICATION, "", "from-subclass", "res", "assets", new Class[]{}, new String[]{}, new String[]{}, Void.class);
120
121    assertConfig(configFor(Test4.class, "withOverrideAnnotation"),
122        new int[] {9}, "furf", TestFakeApp.class, "com.example.method", "from-method", "method/res", "method/assets", new Class[]{Test1.class}, new String[]{"com.example.method2"}, new String[]{"libs/method"}, BuildConfigConstants.class);
123  }
124
125  @Test
126  public void shouldLoadDefaultsFromGlobalPropertiesFile() throws Exception {
127    String properties = "sdk: 432\n" +
128            "manifest: --none\n" +
129            "qualifiers: from-properties-file\n" +
130            "resourceDir: from/properties/file/res\n" +
131            "assetDir: from/properties/file/assets\n" +
132            "shadows: org.robolectric.shadows.ShadowView, org.robolectric.shadows.ShadowViewGroup\n" +
133            "application: org.robolectric.TestFakeApp\n" +
134            "packageName: com.example.test\n" +
135            "instrumentedPackages: com.example.test1, com.example.test2\n" +
136            "libraries: libs/test, libs/test2\n" +
137            "constants: " + ConfigMergerTest.BuildConfigConstants3.class.getName();
138
139    assertConfig(configFor(Test2.class, "withoutAnnotation", of("robolectric.properties", properties)),
140        new int[] {432}, "--none", TestFakeApp.class, "com.example.test", "from-properties-file", "from/properties/file/res", "from/properties/file/assets", new Class[] {ShadowView.class, ShadowViewGroup.class}, new String[]{"com.example.test1", "com.example.test2"}, new String[]{"libs/test", "libs/test2"}, BuildConfigConstants3.class);
141  }
142
143  @Test
144  public void shouldMergeConfigFromTestClassPackageProperties() throws Exception {
145    assertConfig(configFor(Test2.class, "withoutAnnotation", of("org/robolectric/robolectric.properties", "sdk: 432\n")),
146        new int[] {432}, "AndroidManifest.xml", DEFAULT_APPLICATION, "", "", "res", "assets", new Class[] {}, new String[]{}, new String[]{}, null);
147  }
148
149  @Test
150  public void shouldMergeConfigUpPackageHierarchy() throws Exception {
151    assertConfig(configFor(Test2.class, "withoutAnnotation",
152        of(
153            "org/robolectric/robolectric.properties", "qualifiers: from-org-robolectric\nlibraries: FromOrgRobolectric\n",
154            "org/robolectric.properties", "sdk: 123\nqualifiers: from-org\nlibraries: FromOrg\n",
155            "robolectric.properties", "sdk: 456\nqualifiers: from-top-level\nlibraries: FromTopLevel\n"
156            )
157        ),
158        new int[] {123}, "AndroidManifest.xml", DEFAULT_APPLICATION, "", "from-org-robolectric", "res", "assets", new Class[] {}, new String[]{},
159        new String[]{"FromOrgRobolectric", "FromOrg", "FromTopLevel"}, null);
160  }
161
162  @Test
163  public void withEmptyShadowList_shouldLoadDefaultsFromGlobalPropertiesFile() throws Exception {
164    assertConfig(configFor(Test2.class, "withoutAnnotation", of("robolectric.properties", "shadows:")),
165        new int[0],  "AndroidManifest.xml", DEFAULT_APPLICATION, "", "", "res", "assets", new Class[] {}, new String[]{}, new String[]{}, null);
166  }
167
168  @Test public void testPackageHierarchyOf() throws Exception {
169    assertThat(new ConfigMerger().packageHierarchyOf(ConfigMergerTest.class))
170        .containsExactly("org.robolectric", "org", "");
171  }
172
173  /////////////////////////////
174
175  private Config configFor(Class<?> testClass, String methodName, final Map<String, String> configProperties) throws InitializationError {
176    return configFor(testClass, methodName, configProperties, Config.Builder.defaults().build());
177  }
178
179  private Config configFor(Class<?> testClass, String methodName) throws InitializationError {
180    Config.Implementation globalConfig = Config.Builder.defaults().build();
181    return configFor(testClass, methodName, globalConfig);
182  }
183
184  private Config configFor(Class<?> testClass, String methodName, Config.Implementation globalConfig) throws InitializationError {
185    return configFor(testClass, methodName, new HashMap<>(), globalConfig);
186  }
187
188  private Config configFor(Class<?> testClass, String methodName, final Map<String, String> configProperties, Config.Implementation globalConfig) throws InitializationError {
189    Method info = getMethod(testClass, methodName);
190    return new ConfigMerger() {
191      @Override
192      InputStream getResourceAsStream(String resourceName) {
193        String properties = configProperties.get(resourceName);
194        return properties == null ? null : new ByteArrayInputStream(properties.getBytes(UTF_8));
195      }
196    }.getConfig(testClass, info, globalConfig);
197  }
198
199  private static Method getMethod(Class<?> testClass, String methodName) {
200    try {
201      return testClass.getMethod(methodName);
202    } catch (NoSuchMethodException e) {
203      throw new RuntimeException(e);
204    }
205  }
206
207  private static void assertConfig(Config config, int[] sdk, String manifest, Class<? extends Application> application, String packageName, String qualifiers, String resourceDir,
208                            String assetsDir, Class<?>[] shadows, String[] instrumentedPackages, String[] libraries, Class<?> constants) {
209    assertThat(config.sdk()).isEqualTo(sdk);
210    assertThat(config.manifest()).isEqualTo(manifest);
211    assertThat(config.application()).isEqualTo(application);
212    assertThat(config.packageName()).isEqualTo(packageName);
213    assertThat(config.qualifiers()).isEqualTo(qualifiers);
214    assertThat(config.resourceDir()).isEqualTo(resourceDir);
215    assertThat(config.assetDir()).isEqualTo(assetsDir);
216    assertThat(config.shadows()).containsExactly(shadows);
217    assertThat(config.instrumentedPackages()).containsExactlyInAnyOrder(instrumentedPackages);
218    assertThat(config.libraries()).containsExactlyInAnyOrder(libraries);
219    assertThat(config.constants()).isEqualTo(constants);
220  }
221
222  @Ignore
223  @Config(sdk = 1, manifest = "foo", application = TestFakeApp.class, packageName = "com.example.test", shadows = Test1.class, instrumentedPackages = "com.example.test1", libraries = "libs/test", qualifiers = "from-test", resourceDir = "test/res", assetDir = "test/assets", constants = BuildConfigConstants.class)
224  public static class Test1 {
225    @Test
226    public void withoutAnnotation() throws Exception {
227    }
228
229    @Test
230    @Config
231    public void withDefaultsAnnotation() throws Exception {
232    }
233
234    @Test
235    @Config(sdk = 9, manifest = "furf", application = TestApplication.class, packageName = "com.example.method", shadows = Test2.class, instrumentedPackages = "com.example.method1", libraries = "libs/method", qualifiers = "from-method", resourceDir = "method/res", assetDir = "method/assets", constants = BuildConfigConstants2.class)
236    public void withOverrideAnnotation() throws Exception {
237    }
238  }
239
240  @Ignore
241  public static class Test2 {
242    @Test
243    public void withoutAnnotation() throws Exception {
244    }
245
246    @Test
247    @Config
248    public void withDefaultsAnnotation() throws Exception {
249    }
250
251    @Test
252    @Config(sdk = 9, manifest = "furf", application = TestFakeApp.class, packageName = "com.example.method", shadows = Test1.class, instrumentedPackages = "com.example.method2", libraries = "libs/method", qualifiers = "from-method", resourceDir = "method/res", assetDir = "method/assets", constants = BuildConfigConstants.class)
253    public void withOverrideAnnotation() throws Exception {
254    }
255  }
256
257  @Ignore
258  @Config(qualifiers = "from-subclass")
259  public static class Test3 extends Test1 {
260  }
261
262  @Ignore
263  @Config(qualifiers = "from-subclass")
264  public static class Test4 extends Test2 {
265  }
266
267  @Ignore
268  public static class Test5 extends Test1 {
269    @Override
270    @Test
271    public void withoutAnnotation() throws Exception {
272    }
273
274    @Override
275    @Test
276    @Config
277    public void withDefaultsAnnotation() throws Exception {
278    }
279
280    @Override
281    @Test
282    @Config(sdk = 14, shadows = Test5.class, instrumentedPackages = "com.example.method5", packageName = "com.example.test", qualifiers = "from-method5", assetDir = "method5/assets", constants = BuildConfigConstants5.class)
283    public void withOverrideAnnotation() throws Exception {
284    }
285  }
286
287  public static class BuildConfigConstants {}
288  public static class BuildConfigConstants2 {}
289  public static class BuildConfigConstants3 {}
290  public static class BuildConfigConstants5 {}
291  public static class BuildConfigConstants6 {}
292
293
294  @Ignore
295  @Config(qualifiers = "from-class6", shadows = Test6.class, instrumentedPackages = "com.example.test6", resourceDir = "class6/res", constants = BuildConfigConstants6.class)
296  public static class Test6 extends Test5 {
297  }
298}