xref: /aosp_15_r20/external/robolectric/robolectric/src/main/java/org/robolectric/ConfigMerger.java (revision e6ba16074e6af37d123cb567d575f496bf0a58ee)
1 package org.robolectric;
2 
3 import static com.google.common.collect.Lists.reverse;
4 
5 import com.google.common.annotations.VisibleForTesting;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.lang.reflect.Method;
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.LinkedHashMap;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Properties;
15 import javax.annotation.Nonnull;
16 import javax.annotation.Nullable;
17 import org.robolectric.annotation.Config;
18 import org.robolectric.util.Join;
19 
20 /**
21  * Computes the effective Robolectric configuration for a given test method.
22  *
23  * <p>This class is no longer used directly by Robolectric, but is left for convenience during
24  * migration.
25  *
26  * @deprecated Provide an implementation of {@link javax.inject.Provider<Config>}. This class will
27  *     be removed in Robolectric 4.3.
28  * @see <a href="http://robolectric.org/migrating/#migrating-to-40">Migration Notes</a> for more
29  *     details.
30  */
31 @Deprecated
32 public class ConfigMerger {
33   private final Map<String, Config> packageConfigCache =
34       new LinkedHashMap<String, Config>() {
35         @Override
36         protected boolean removeEldestEntry(Map.Entry eldest) {
37           return size() > 10;
38         }
39       };
40 
41   /**
42    * Calculate the {@link Config} for the given test.
43    *
44    * @param testClass the class containing the test
45    * @param method the test method
46    * @param globalConfig global configuration values
47    * @return the effective configuration
48    * @since 3.2
49    */
getConfig(Class<?> testClass, Method method, Config globalConfig)50   public Config getConfig(Class<?> testClass, Method method, Config globalConfig) {
51     Config config = Config.Builder.defaults().build();
52     config = override(config, globalConfig);
53 
54     for (String packageName : reverse(packageHierarchyOf(testClass))) {
55       Config packageConfig = cachedPackageConfig(packageName);
56       config = override(config, packageConfig);
57     }
58 
59     for (Class clazz : reverse(parentClassesFor(testClass))) {
60       Config classConfig = (Config) clazz.getAnnotation(Config.class);
61       config = override(config, classConfig);
62     }
63 
64     Config methodConfig = method.getAnnotation(Config.class);
65     config = override(config, methodConfig);
66 
67     return config;
68   }
69 
70   /**
71    * Generate {@link Config} for the specified package.
72    *
73    * <p>More specific packages, test classes, and test method configurations will override values
74    * provided here.
75    *
76    * <p>The default implementation uses properties provided by {@link #getConfigProperties(String)}.
77    *
78    * <p>The returned object is likely to be reused for many tests.
79    *
80    * @param packageName the name of the package, or empty string ({@code ""}) for the top level
81    *     package
82    * @return {@link Config} object for the specified package
83    * @since 3.2
84    */
85   @Nullable
buildPackageConfig(String packageName)86   private Config buildPackageConfig(String packageName) {
87     return Config.Implementation.fromProperties(getConfigProperties(packageName));
88   }
89 
90   /**
91    * Return a {@link Properties} file for the given package name, or {@code null} if none is
92    * available.
93    *
94    * @since 3.2
95    */
getConfigProperties(String packageName)96   protected Properties getConfigProperties(String packageName) {
97     List<String> packageParts = new ArrayList<>(Arrays.asList(packageName.split("\\.")));
98     packageParts.add(RobolectricTestRunner.CONFIG_PROPERTIES);
99     final String resourceName = Join.join("/", packageParts);
100     try (InputStream resourceAsStream = getResourceAsStream(resourceName)) {
101       if (resourceAsStream == null) return null;
102       Properties properties = new Properties();
103       properties.load(resourceAsStream);
104       return properties;
105     } catch (IOException e) {
106       throw new RuntimeException(e);
107     }
108   }
109 
110   @Nonnull
111   @VisibleForTesting
packageHierarchyOf(Class<?> javaClass)112   List<String> packageHierarchyOf(Class<?> javaClass) {
113     Package aPackage = javaClass.getPackage();
114     String testPackageName = aPackage == null ? "" : aPackage.getName();
115     List<String> packageHierarchy = new ArrayList<>();
116     while (!testPackageName.isEmpty()) {
117       packageHierarchy.add(testPackageName);
118       int lastDot = testPackageName.lastIndexOf('.');
119       testPackageName = lastDot > 1 ? testPackageName.substring(0, lastDot) : "";
120     }
121     packageHierarchy.add("");
122     return packageHierarchy;
123   }
124 
125   @Nonnull
parentClassesFor(Class testClass)126   private List<Class> parentClassesFor(Class testClass) {
127     List<Class> testClassHierarchy = new ArrayList<>();
128     while (testClass != null && !testClass.equals(Object.class)) {
129       testClassHierarchy.add(testClass);
130       testClass = testClass.getSuperclass();
131     }
132     return testClassHierarchy;
133   }
134 
override(Config config, Config classConfig)135   private Config override(Config config, Config classConfig) {
136     return classConfig != null ? new Config.Builder(config).overlay(classConfig).build() : config;
137   }
138 
139   @Nullable
cachedPackageConfig(String packageName)140   private Config cachedPackageConfig(String packageName) {
141     synchronized (packageConfigCache) {
142       Config config = packageConfigCache.get(packageName);
143       if (config == null && !packageConfigCache.containsKey(packageName)) {
144         config = buildPackageConfig(packageName);
145         packageConfigCache.put(packageName, config);
146       }
147       return config;
148     }
149   }
150 
151   // visible for testing
152   @SuppressWarnings("WeakerAccess")
getResourceAsStream(String resourceName)153   InputStream getResourceAsStream(String resourceName) {
154     return getClass().getClassLoader().getResourceAsStream(resourceName);
155   }
156 }
157