1104b292a58958d310fcb27472fd7e450b3d99946Christian Williamspackage org.robolectric; 2104b292a58958d310fcb27472fd7e450b3d99946Christian Williams 3851f2a9519be23c73a9e2929128179b405e2e7a6Christian Williamsimport static com.google.common.collect.Lists.reverse; 4104b292a58958d310fcb27472fd7e450b3d99946Christian Williams 5851f2a9519be23c73a9e2929128179b405e2e7a6Christian Williamsimport com.google.common.annotations.VisibleForTesting; 6104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.io.IOException; 7104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.io.InputStream; 8104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.lang.reflect.Method; 9104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.util.ArrayList; 10165e6196f75e97d6fe0b63255b38a5292f113357Menny Even Dananimport java.util.Arrays; 11104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.util.LinkedHashMap; 12104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.util.List; 13104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.util.Map; 14104b292a58958d310fcb27472fd7e450b3d99946Christian Williamsimport java.util.Properties; 15851f2a9519be23c73a9e2929128179b405e2e7a6Christian Williamsimport javax.annotation.Nonnull; 16851f2a9519be23c73a9e2929128179b405e2e7a6Christian Williamsimport javax.annotation.Nullable; 17851f2a9519be23c73a9e2929128179b405e2e7a6Christian Williamsimport org.robolectric.annotation.Config; 18851f2a9519be23c73a9e2929128179b405e2e7a6Christian Williamsimport org.robolectric.util.Join; 19104b292a58958d310fcb27472fd7e450b3d99946Christian Williams 20104b292a58958d310fcb27472fd7e450b3d99946Christian Williamspublic class ConfigMerger { 21104b292a58958d310fcb27472fd7e450b3d99946Christian Williams private final Map<String, Config> packageConfigCache = new LinkedHashMap<String, Config>() { 22104b292a58958d310fcb27472fd7e450b3d99946Christian Williams @Override 23104b292a58958d310fcb27472fd7e450b3d99946Christian Williams protected boolean removeEldestEntry(Map.Entry eldest) { 24104b292a58958d310fcb27472fd7e450b3d99946Christian Williams return size() > 10; 25104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 26104b292a58958d310fcb27472fd7e450b3d99946Christian Williams }; 27104b292a58958d310fcb27472fd7e450b3d99946Christian Williams 28a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams /** 29a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams * Calculate the {@link Config} for the given test. 30a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams * 31a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams * @param testClass the class containing the test 32a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams * @param method the test method 33a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams * @param globalConfig global configuration values 34a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams * @return the effective configuration 35a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams * @since 3.2 36a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams */ 37104b292a58958d310fcb27472fd7e450b3d99946Christian Williams public Config getConfig(Class<?> testClass, Method method, Config globalConfig) { 38a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams Config config = Config.Builder.defaults().build(); 39a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams config = override(config, globalConfig); 40104b292a58958d310fcb27472fd7e450b3d99946Christian Williams 418749eceadefcef06481f51a6915b75ec15aba6b2Christian Williams for (String packageName : reverse(packageHierarchyOf(testClass))) { 42104b292a58958d310fcb27472fd7e450b3d99946Christian Williams Config packageConfig = cachedPackageConfig(packageName); 43104b292a58958d310fcb27472fd7e450b3d99946Christian Williams config = override(config, packageConfig); 44104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 45104b292a58958d310fcb27472fd7e450b3d99946Christian Williams 46104b292a58958d310fcb27472fd7e450b3d99946Christian Williams for (Class clazz : reverse(parentClassesFor(testClass))) { 47104b292a58958d310fcb27472fd7e450b3d99946Christian Williams Config classConfig = (Config) clazz.getAnnotation(Config.class); 48104b292a58958d310fcb27472fd7e450b3d99946Christian Williams config = override(config, classConfig); 49104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 50104b292a58958d310fcb27472fd7e450b3d99946Christian Williams 51104b292a58958d310fcb27472fd7e450b3d99946Christian Williams Config methodConfig = method.getAnnotation(Config.class); 52104b292a58958d310fcb27472fd7e450b3d99946Christian Williams config = override(config, methodConfig); 53104b292a58958d310fcb27472fd7e450b3d99946Christian Williams 54104b292a58958d310fcb27472fd7e450b3d99946Christian Williams return config; 55104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 56104b292a58958d310fcb27472fd7e450b3d99946Christian Williams 57104b292a58958d310fcb27472fd7e450b3d99946Christian Williams /** 58104b292a58958d310fcb27472fd7e450b3d99946Christian Williams * Generate {@link Config} for the specified package. 59104b292a58958d310fcb27472fd7e450b3d99946Christian Williams * 60104b292a58958d310fcb27472fd7e450b3d99946Christian Williams * More specific packages, test classes, and test method configurations 61104b292a58958d310fcb27472fd7e450b3d99946Christian Williams * will override values provided here. 62104b292a58958d310fcb27472fd7e450b3d99946Christian Williams * 63104b292a58958d310fcb27472fd7e450b3d99946Christian Williams * The default implementation uses properties provided by {@link #getConfigProperties(String)}. 64104b292a58958d310fcb27472fd7e450b3d99946Christian Williams * 65104b292a58958d310fcb27472fd7e450b3d99946Christian Williams * The returned object is likely to be reused for many tests. 66104b292a58958d310fcb27472fd7e450b3d99946Christian Williams * 677d074bb6c8e8c5e3f73faec0f35a6cf129227ebaChristian Williams * @param packageName the name of the package, or empty string ({@code ""}) for the top level package 68104b292a58958d310fcb27472fd7e450b3d99946Christian Williams * @return {@link Config} object for the specified package 69a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams * @since 3.2 70104b292a58958d310fcb27472fd7e450b3d99946Christian Williams */ 71104b292a58958d310fcb27472fd7e450b3d99946Christian Williams @Nullable 72104b292a58958d310fcb27472fd7e450b3d99946Christian Williams private Config buildPackageConfig(String packageName) { 73104b292a58958d310fcb27472fd7e450b3d99946Christian Williams return Config.Implementation.fromProperties(getConfigProperties(packageName)); 74104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 75104b292a58958d310fcb27472fd7e450b3d99946Christian Williams 76104b292a58958d310fcb27472fd7e450b3d99946Christian Williams /** 777d074bb6c8e8c5e3f73faec0f35a6cf129227ebaChristian Williams * Return a {@link Properties} file for the given package name, or {@code null} if none is available. 78a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams * 79a7f2db77a1a5457867de967e0939b854ac2ab27aChristian Williams * @since 3.2 80104b292a58958d310fcb27472fd7e450b3d99946Christian Williams */ 81104b292a58958d310fcb27472fd7e450b3d99946Christian Williams protected Properties getConfigProperties(String packageName) { 82165e6196f75e97d6fe0b63255b38a5292f113357Menny Even Danan List<String> packageParts = new ArrayList<>(Arrays.asList(packageName.split("\\."))); 83165e6196f75e97d6fe0b63255b38a5292f113357Menny Even Danan packageParts.add(RobolectricTestRunner.CONFIG_PROPERTIES); 84165e6196f75e97d6fe0b63255b38a5292f113357Menny Even Danan final String resourceName = Join.join("/", packageParts); 85104b292a58958d310fcb27472fd7e450b3d99946Christian Williams try (InputStream resourceAsStream = getResourceAsStream(resourceName)) { 86104b292a58958d310fcb27472fd7e450b3d99946Christian Williams if (resourceAsStream == null) return null; 87104b292a58958d310fcb27472fd7e450b3d99946Christian Williams Properties properties = new Properties(); 88104b292a58958d310fcb27472fd7e450b3d99946Christian Williams properties.load(resourceAsStream); 89104b292a58958d310fcb27472fd7e450b3d99946Christian Williams return properties; 90104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } catch (IOException e) { 91104b292a58958d310fcb27472fd7e450b3d99946Christian Williams throw new RuntimeException(e); 92104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 93104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 94104b292a58958d310fcb27472fd7e450b3d99946Christian Williams 9568dc68433d8be4dd7eb7d8a9dda078ad37ce1d16Jonathan Gerrish @Nonnull @VisibleForTesting 968749eceadefcef06481f51a6915b75ec15aba6b2Christian Williams List<String> packageHierarchyOf(Class<?> javaClass) { 97b1bfc1aa31f349fc12da4d7f3052880e3e97536eChristian Williams Package aPackage = javaClass.getPackage(); 98b1bfc1aa31f349fc12da4d7f3052880e3e97536eChristian Williams String testPackageName = aPackage == null ? "" : aPackage.getName(); 99104b292a58958d310fcb27472fd7e450b3d99946Christian Williams List<String> packageHierarchy = new ArrayList<>(); 100104b292a58958d310fcb27472fd7e450b3d99946Christian Williams while (!testPackageName.isEmpty()) { 101104b292a58958d310fcb27472fd7e450b3d99946Christian Williams packageHierarchy.add(testPackageName); 102104b292a58958d310fcb27472fd7e450b3d99946Christian Williams int lastDot = testPackageName.lastIndexOf('.'); 103104b292a58958d310fcb27472fd7e450b3d99946Christian Williams testPackageName = lastDot > 1 ? testPackageName.substring(0, lastDot) : ""; 104104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 105104b292a58958d310fcb27472fd7e450b3d99946Christian Williams packageHierarchy.add(""); 106104b292a58958d310fcb27472fd7e450b3d99946Christian Williams return packageHierarchy; 107104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 108104b292a58958d310fcb27472fd7e450b3d99946Christian Williams 10968dc68433d8be4dd7eb7d8a9dda078ad37ce1d16Jonathan Gerrish @Nonnull 110104b292a58958d310fcb27472fd7e450b3d99946Christian Williams private List<Class> parentClassesFor(Class testClass) { 111104b292a58958d310fcb27472fd7e450b3d99946Christian Williams List<Class> testClassHierarchy = new ArrayList<>(); 112104b292a58958d310fcb27472fd7e450b3d99946Christian Williams while (testClass != null && !testClass.equals(Object.class)) { 113104b292a58958d310fcb27472fd7e450b3d99946Christian Williams testClassHierarchy.add(testClass); 114104b292a58958d310fcb27472fd7e450b3d99946Christian Williams testClass = testClass.getSuperclass(); 115104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 116104b292a58958d310fcb27472fd7e450b3d99946Christian Williams return testClassHierarchy; 117104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 118104b292a58958d310fcb27472fd7e450b3d99946Christian Williams 119104b292a58958d310fcb27472fd7e450b3d99946Christian Williams private Config override(Config config, Config classConfig) { 120104b292a58958d310fcb27472fd7e450b3d99946Christian Williams return classConfig != null ? new Config.Builder(config).overlay(classConfig).build() : config; 121104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 122104b292a58958d310fcb27472fd7e450b3d99946Christian Williams 123104b292a58958d310fcb27472fd7e450b3d99946Christian Williams @Nullable 124104b292a58958d310fcb27472fd7e450b3d99946Christian Williams private Config cachedPackageConfig(String packageName) { 125104b292a58958d310fcb27472fd7e450b3d99946Christian Williams synchronized (packageConfigCache) { 126104b292a58958d310fcb27472fd7e450b3d99946Christian Williams Config config = packageConfigCache.get(packageName); 127104b292a58958d310fcb27472fd7e450b3d99946Christian Williams if (config == null && !packageConfigCache.containsKey(packageName)) { 128104b292a58958d310fcb27472fd7e450b3d99946Christian Williams config = buildPackageConfig(packageName); 129104b292a58958d310fcb27472fd7e450b3d99946Christian Williams packageConfigCache.put(packageName, config); 130104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 131104b292a58958d310fcb27472fd7e450b3d99946Christian Williams return config; 132104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 133104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 134104b292a58958d310fcb27472fd7e450b3d99946Christian Williams 135104b292a58958d310fcb27472fd7e450b3d99946Christian Williams // visible for testing 136104b292a58958d310fcb27472fd7e450b3d99946Christian Williams @SuppressWarnings("WeakerAccess") 137104b292a58958d310fcb27472fd7e450b3d99946Christian Williams InputStream getResourceAsStream(String resourceName) { 138104b292a58958d310fcb27472fd7e450b3d99946Christian Williams return getClass().getClassLoader().getResourceAsStream(resourceName); 139104b292a58958d310fcb27472fd7e450b3d99946Christian Williams } 140104b292a58958d310fcb27472fd7e450b3d99946Christian Williams} 141