Home | History | Annotate | Download | only in robolectric
      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 public class ConfigMerger {
     21   private final Map<String, Config> packageConfigCache = new LinkedHashMap<String, Config>() {
     22     @Override
     23     protected boolean removeEldestEntry(Map.Entry eldest) {
     24       return size() > 10;
     25     }
     26   };
     27 
     28   /**
     29    * Calculate the {@link Config} for the given test.
     30    *
     31    * @param testClass the class containing the test
     32    * @param method the test method
     33    * @param globalConfig global configuration values
     34    * @return the effective configuration
     35    * @since 3.2
     36    */
     37   public Config getConfig(Class<?> testClass, Method method, Config globalConfig) {
     38     Config config = Config.Builder.defaults().build();
     39     config = override(config, globalConfig);
     40 
     41     for (String packageName : reverse(packageHierarchyOf(testClass))) {
     42       Config packageConfig = cachedPackageConfig(packageName);
     43       config = override(config, packageConfig);
     44     }
     45 
     46     for (Class clazz : reverse(parentClassesFor(testClass))) {
     47       Config classConfig = (Config) clazz.getAnnotation(Config.class);
     48       config = override(config, classConfig);
     49     }
     50 
     51     Config methodConfig = method.getAnnotation(Config.class);
     52     config = override(config, methodConfig);
     53 
     54     return config;
     55   }
     56 
     57   /**
     58    * Generate {@link Config} for the specified package.
     59    *
     60    * More specific packages, test classes, and test method configurations
     61    * will override values provided here.
     62    *
     63    * The default implementation uses properties provided by {@link #getConfigProperties(String)}.
     64    *
     65    * The returned object is likely to be reused for many tests.
     66    *
     67    * @param packageName the name of the package, or empty string ({@code ""}) for the top level package
     68    * @return {@link Config} object for the specified package
     69    * @since 3.2
     70    */
     71   @Nullable
     72   private Config buildPackageConfig(String packageName) {
     73     return Config.Implementation.fromProperties(getConfigProperties(packageName));
     74   }
     75 
     76   /**
     77    * Return a {@link Properties} file for the given package name, or {@code null} if none is available.
     78    *
     79    * @since 3.2
     80    */
     81   protected Properties getConfigProperties(String packageName) {
     82     List<String> packageParts = new ArrayList<>(Arrays.asList(packageName.split("\\.")));
     83     packageParts.add(RobolectricTestRunner.CONFIG_PROPERTIES);
     84     final String resourceName = Join.join("/", packageParts);
     85     try (InputStream resourceAsStream = getResourceAsStream(resourceName)) {
     86       if (resourceAsStream == null) return null;
     87       Properties properties = new Properties();
     88       properties.load(resourceAsStream);
     89       return properties;
     90     } catch (IOException e) {
     91       throw new RuntimeException(e);
     92     }
     93   }
     94 
     95   @Nonnull @VisibleForTesting
     96   List<String> packageHierarchyOf(Class<?> javaClass) {
     97     Package aPackage = javaClass.getPackage();
     98     String testPackageName = aPackage == null ? "" : aPackage.getName();
     99     List<String> packageHierarchy = new ArrayList<>();
    100     while (!testPackageName.isEmpty()) {
    101       packageHierarchy.add(testPackageName);
    102       int lastDot = testPackageName.lastIndexOf('.');
    103       testPackageName = lastDot > 1 ? testPackageName.substring(0, lastDot) : "";
    104     }
    105     packageHierarchy.add("");
    106     return packageHierarchy;
    107   }
    108 
    109   @Nonnull
    110   private List<Class> parentClassesFor(Class testClass) {
    111     List<Class> testClassHierarchy = new ArrayList<>();
    112     while (testClass != null && !testClass.equals(Object.class)) {
    113       testClassHierarchy.add(testClass);
    114       testClass = testClass.getSuperclass();
    115     }
    116     return testClassHierarchy;
    117   }
    118 
    119   private Config override(Config config, Config classConfig) {
    120     return classConfig != null ? new Config.Builder(config).overlay(classConfig).build() : config;
    121   }
    122 
    123   @Nullable
    124   private Config cachedPackageConfig(String packageName) {
    125     synchronized (packageConfigCache) {
    126       Config config = packageConfigCache.get(packageName);
    127       if (config == null && !packageConfigCache.containsKey(packageName)) {
    128         config = buildPackageConfig(packageName);
    129         packageConfigCache.put(packageName, config);
    130       }
    131       return config;
    132     }
    133   }
    134 
    135   // visible for testing
    136   @SuppressWarnings("WeakerAccess")
    137   InputStream getResourceAsStream(String resourceName) {
    138     return getClass().getClassLoader().getResourceAsStream(resourceName);
    139   }
    140 }
    141