Home | History | Annotate | Download | only in robolectric
      1 package com.xtremelabs.robolectric;
      2 
      3 import java.io.File;
      4 import java.io.FileOutputStream;
      5 import java.io.IOException;
      6 import java.io.PrintStream;
      7 import java.lang.annotation.Annotation;
      8 import java.lang.reflect.Constructor;
      9 import java.lang.reflect.Method;
     10 import java.util.HashMap;
     11 import java.util.Map;
     12 import java.util.logging.Logger;
     13 
     14 import javassist.Loader;
     15 
     16 import javax.xml.parsers.DocumentBuilder;
     17 import javax.xml.parsers.DocumentBuilderFactory;
     18 import javax.xml.parsers.ParserConfigurationException;
     19 
     20 import org.junit.runners.BlockJUnit4ClassRunner;
     21 import org.junit.runners.model.FrameworkMethod;
     22 import org.junit.runners.model.InitializationError;
     23 import org.junit.runners.model.Statement;
     24 import org.w3c.dom.Document;
     25 import org.xml.sax.SAXException;
     26 
     27 import android.app.Application;
     28 import android.net.Uri__FromAndroid;
     29 
     30 import com.xtremelabs.robolectric.bytecode.ClassHandler;
     31 import com.xtremelabs.robolectric.bytecode.RobolectricClassLoader;
     32 import com.xtremelabs.robolectric.bytecode.ShadowWrangler;
     33 import com.xtremelabs.robolectric.internal.RealObject;
     34 import com.xtremelabs.robolectric.internal.RobolectricTestRunnerInterface;
     35 import com.xtremelabs.robolectric.res.ResourceLoader;
     36 import com.xtremelabs.robolectric.shadows.ShadowApplication;
     37 import com.xtremelabs.robolectric.shadows.ShadowLog;
     38 import com.xtremelabs.robolectric.util.DatabaseConfig;
     39 import com.xtremelabs.robolectric.util.DatabaseConfig.DatabaseMap;
     40 import com.xtremelabs.robolectric.util.DatabaseConfig.UsingDatabaseMap;
     41 import com.xtremelabs.robolectric.util.SQLiteMap;
     42 
     43 /**
     44  * Installs a {@link RobolectricClassLoader} and {@link com.xtremelabs.robolectric.res.ResourceLoader} in order to
     45  * provide a simulation of the Android runtime environment.
     46  */
     47 public class RobolectricTestRunner extends BlockJUnit4ClassRunner implements RobolectricTestRunnerInterface {
     48 
     49     private static final String MANIFEST_PATH_PROPERTY = "robolectric.path.manifest";
     50     private static final String RES_PATH_PROPERTY = "robolectric.path.res";
     51     private static final String ASSETS_PATH_PROPERTY = "robolectric.path.assets";
     52     private static final String DEFAULT_MANIFEST_PATH = "./AndroidManifest.xml";
     53     private static final String DEFAULT_RES_PATH = "./res";
     54     private static final String DEFAULT_ASSETS_PATH = "./assets";
     55 
     56     private static final Logger logger =
     57             Logger.getLogger(RobolectricTestRunner.class.getSimpleName());
     58 
     59     /** Instrument detector. We use it to check whether the current instance is instrumented. */
     60   	private static InstrumentDetector instrumentDetector = InstrumentDetector.DEFAULT;
     61 
     62     private static RobolectricClassLoader defaultLoader;
     63     private static Map<RobolectricConfig, ResourceLoader> resourceLoaderForRootAndDirectory = new HashMap<RobolectricConfig, ResourceLoader>();
     64 
     65     // fields in the RobolectricTestRunner in the original ClassLoader
     66     private RobolectricClassLoader classLoader;
     67     private ClassHandler classHandler;
     68     private RobolectricTestRunnerInterface delegate;
     69     private DatabaseMap databaseMap;
     70 
     71 	// fields in the RobolectricTestRunner in the instrumented ClassLoader
     72     protected RobolectricConfig robolectricConfig;
     73 
     74     private static RobolectricClassLoader getDefaultLoader() {
     75         if (defaultLoader == null) {
     76             defaultLoader = new RobolectricClassLoader(ShadowWrangler.getInstance());
     77         }
     78         return defaultLoader;
     79     }
     80 
     81     public static void setInstrumentDetector(final InstrumentDetector detector) {
     82       instrumentDetector = detector;
     83     }
     84 
     85     public static void setDefaultLoader(Loader robolectricClassLoader) {
     86     	//used by the RoboSpecs project to allow for mixed scala\java tests to be run with Maven Surefire (see the RoboSpecs project on github)
     87         if (defaultLoader == null) {
     88             defaultLoader = (RobolectricClassLoader)robolectricClassLoader;
     89         } else throw new RuntimeException("You may not set the default robolectricClassLoader unless it is null!");
     90     }
     91 
     92     /**
     93      * Call this if you would like Robolectric to rewrite additional classes and turn them
     94      * into "do nothing" classes which proxy all method calls to shadow classes, just like it does
     95      * with the android classes by default.
     96      *
     97      * @param classOrPackageToBeInstrumented fully-qualified class or package name
     98      */
     99     protected static void addClassOrPackageToInstrument(String classOrPackageToBeInstrumented) {
    100         if (!isInstrumented()) {
    101             defaultLoader.addCustomShadowClass(classOrPackageToBeInstrumented);
    102         }
    103     }
    104 
    105     /**
    106      * Creates a runner to run {@code testClass}. Looks in your working directory for your AndroidManifest.xml file
    107      * and res directory.
    108      *
    109      * @param testClass the test class to be run
    110      * @throws InitializationError if junit says so
    111      */
    112     public RobolectricTestRunner(final Class<?> testClass) throws InitializationError {
    113         this(testClass, new RobolectricConfig(
    114                 new File(getSystemProperty(MANIFEST_PATH_PROPERTY, DEFAULT_MANIFEST_PATH)),
    115                 new File(getSystemProperty(RES_PATH_PROPERTY, DEFAULT_RES_PATH)),
    116                 new File(getSystemProperty(ASSETS_PATH_PROPERTY, DEFAULT_ASSETS_PATH))));
    117     }
    118 
    119     /**
    120      * Creates a runner to run {@code testClass}. Looks in your working directory for your AndroidManifest.xml file
    121      * and res directory.
    122      *
    123      * @param testClass the test class to be run
    124      * @param classLoader a custom RobolectricClassLoader to be used.
    125      * @throws InitializationError if junit says so
    126      */
    127     public RobolectricTestRunner(final Class<?> testClass, RobolectricClassLoader classLoader)
    128             throws InitializationError {
    129         this(testClass,
    130             isInstrumented() ? null : ShadowWrangler.getInstance(),
    131             isInstrumented() ? null : classLoader,
    132             new RobolectricConfig(
    133                 new File(getSystemProperty(MANIFEST_PATH_PROPERTY, DEFAULT_MANIFEST_PATH)),
    134                 new File(getSystemProperty(RES_PATH_PROPERTY, DEFAULT_RES_PATH)),
    135                 new File(getSystemProperty(ASSETS_PATH_PROPERTY, DEFAULT_ASSETS_PATH))));
    136     }
    137 
    138     /**
    139      * Call this constructor in subclasses in order to specify non-default configuration (e.g. location of the
    140      * AndroidManifest.xml file and resource directory).
    141      *
    142      * @param testClass         the test class to be run
    143      * @param robolectricConfig the configuration data
    144      * @throws InitializationError if junit says so
    145      */
    146     protected RobolectricTestRunner(final Class<?> testClass, final RobolectricConfig robolectricConfig)
    147             throws InitializationError {
    148         this(testClass,
    149                 isInstrumented() ? null : ShadowWrangler.getInstance(),
    150                 isInstrumented() ? null : getDefaultLoader(),
    151                 robolectricConfig, new SQLiteMap());
    152     }
    153 
    154     /**
    155      * Call this constructor in subclasses in order to specify non-default configuration (e.g. location of the
    156      * AndroidManifest.xml file, resource directory, and DatabaseMap).
    157      *
    158      * @param testClass         the test class to be run
    159      * @param robolectricConfig the configuration data
    160      * @param databaseMap		the database mapping
    161      * @throws InitializationError if junit says so
    162      */
    163     protected RobolectricTestRunner(Class<?> testClass, RobolectricConfig robolectricConfig, DatabaseMap databaseMap)
    164             throws InitializationError {
    165         this(testClass,
    166                 isInstrumented() ? null : ShadowWrangler.getInstance(),
    167                 isInstrumented() ? null : getDefaultLoader(),
    168                 robolectricConfig, databaseMap);
    169     }
    170 
    171     /**
    172      * Call this constructor in subclasses in order to specify the project root directory.
    173      *
    174      * @param testClass          the test class to be run
    175      * @param androidProjectRoot the directory containing your AndroidManifest.xml file and res dir
    176      * @throws InitializationError if the test class is malformed
    177      */
    178     public RobolectricTestRunner(final Class<?> testClass, final File androidProjectRoot) throws InitializationError {
    179         this(testClass, new RobolectricConfig(androidProjectRoot));
    180     }
    181 
    182     /**
    183      * Call this constructor in subclasses in order to specify the project root directory.
    184      *
    185      * @param testClass          the test class to be run
    186      * @param androidProjectRoot the directory containing your AndroidManifest.xml file and res dir
    187      * @throws InitializationError if junit says so
    188      * @deprecated Use {@link #RobolectricTestRunner(Class, File)} instead.
    189      */
    190     @Deprecated
    191     public RobolectricTestRunner(final Class<?> testClass, final String androidProjectRoot) throws InitializationError {
    192         this(testClass, new RobolectricConfig(new File(androidProjectRoot)));
    193     }
    194 
    195     /**
    196      * Call this constructor in subclasses in order to specify the location of the AndroidManifest.xml file and the
    197      * resource directory. The #androidManifestPath is used to locate the AndroidManifest.xml file which, in turn,
    198      * contains package name for the {@code R} class which contains the identifiers for all of the resources. The
    199      * resource directory is where the resource loader will look for resources to load.
    200      *
    201      * @param testClass           the test class to be run
    202      * @param androidManifestPath the AndroidManifest.xml file
    203      * @param resourceDirectory   the directory containing the project's resources
    204      * @throws InitializationError if junit says so
    205      */
    206     protected RobolectricTestRunner(final Class<?> testClass, final File androidManifestPath, final File resourceDirectory)
    207             throws InitializationError {
    208         this(testClass, new RobolectricConfig(androidManifestPath, resourceDirectory));
    209     }
    210 
    211     /**
    212      * Call this constructor in subclasses in order to specify the location of the AndroidManifest.xml file and the
    213      * resource directory. The #androidManifestPath is used to locate the AndroidManifest.xml file which, in turn,
    214      * contains package name for the {@code R} class which contains the identifiers for all of the resources. The
    215      * resource directory is where the resource loader will look for resources to load.
    216      *
    217      * @param testClass           the test class to be run
    218      * @param androidManifestPath the relative path to the AndroidManifest.xml file
    219      * @param resourceDirectory   the relative path to the directory containing the project's resources
    220      * @throws InitializationError if junit says so
    221      * @deprecated Use {@link #RobolectricTestRunner(Class, File, File)} instead.
    222      */
    223     @Deprecated
    224     protected RobolectricTestRunner(final Class<?> testClass, final String androidManifestPath, final String resourceDirectory)
    225             throws InitializationError {
    226         this(testClass, new RobolectricConfig(new File(androidManifestPath), new File(resourceDirectory)));
    227     }
    228 
    229     protected RobolectricTestRunner(Class<?> testClass, ClassHandler classHandler, RobolectricClassLoader classLoader, RobolectricConfig robolectricConfig) throws InitializationError {
    230         this(testClass, classHandler, classLoader, robolectricConfig, new SQLiteMap());
    231     }
    232 
    233 
    234     /**
    235      * This is not the constructor you are looking for... probably. This constructor creates a bridge between the test
    236      * runner called by JUnit and a second instance of the test runner that is loaded via the instrumenting class
    237      * loader. This instrumented instance of the test runner, along with the instrumented instance of the actual test,
    238      * provides access to Robolectric's features and the un-instrumented instance of the test runner delegates most of
    239      * the interesting test runner behavior to it. Providing your own class handler and class loader here in order to
    240      * get different functionality is a difficult and dangerous project. If you need to customize the project root and
    241      * resource directory, use {@link #RobolectricTestRunner(Class, String, String)}. For other extensions, consider
    242      * creating a subclass and overriding the documented methods of this class.
    243      *
    244      * @param testClass         the test class to be run
    245      * @param classHandler      the {@link ClassHandler} to use to in shadow delegation
    246      * @param classLoader       the {@link RobolectricClassLoader}
    247      * @param robolectricConfig the configuration
    248      * @throws InitializationError if junit says so
    249      */
    250     protected RobolectricTestRunner(final Class<?> testClass, final ClassHandler classHandler, final RobolectricClassLoader classLoader, final RobolectricConfig robolectricConfig, final DatabaseMap map) throws InitializationError {
    251         super(isInstrumented() ? testClass
    252             : ensureClassLoaderNotNull(classLoader).bootstrap(testClass));
    253 
    254         if (!isInstrumented()) {
    255             this.classHandler = classHandler;
    256             this.classLoader = ensureClassLoaderNotNull(classLoader);
    257             this.robolectricConfig = robolectricConfig;
    258             this.databaseMap = setupDatabaseMap(testClass, map);
    259 
    260             Thread.currentThread().setContextClassLoader(classLoader);
    261 
    262             delegateLoadingOf(Uri__FromAndroid.class.getName());
    263             delegateLoadingOf(RobolectricTestRunnerInterface.class.getName());
    264             delegateLoadingOf(RealObject.class.getName());
    265             delegateLoadingOf(ShadowWrangler.class.getName());
    266             delegateLoadingOf(RobolectricConfig.class.getName());
    267             delegateLoadingOf(DatabaseMap.class.getName());
    268             delegateLoadingOf(android.R.class.getName());
    269 
    270             Class<?> delegateClass = classLoader.bootstrap(this.getClass());
    271             try {
    272                 Constructor<?> constructorForDelegate = delegateClass.getConstructor(Class.class);
    273                 this.delegate = (RobolectricTestRunnerInterface) constructorForDelegate.newInstance(classLoader.bootstrap(testClass));
    274                 this.delegate.setRobolectricConfig(robolectricConfig);
    275                 this.delegate.setDatabaseMap(databaseMap);
    276             } catch (Exception e) {
    277                 throw new RuntimeException(e);
    278             }
    279         }
    280     }
    281 
    282     private static RobolectricClassLoader ensureClassLoaderNotNull(
    283         RobolectricClassLoader classLoader) {
    284         return classLoader == null ? getDefaultLoader() : classLoader;
    285     }
    286 
    287     protected static boolean isInstrumented() {
    288         return instrumentDetector.isInstrumented();
    289     }
    290 
    291     /**
    292      * Only used when creating the delegate instance within the instrumented ClassLoader.
    293      * <p/>
    294      * This is not the constructor you are looking for.
    295      */
    296     @SuppressWarnings({"UnusedDeclaration", "JavaDoc"})
    297     protected RobolectricTestRunner(final Class<?> testClass, final ClassHandler classHandler, final RobolectricConfig robolectricConfig) throws InitializationError {
    298         super(testClass);
    299         this.classHandler = classHandler;
    300         this.robolectricConfig = robolectricConfig;
    301     }
    302 
    303     /** @deprecated use {@link Robolectric.Reflection#setFinalStaticField(Class, String, Object)} */
    304     @Deprecated
    305     public static void setStaticValue(Class<?> clazz, String fieldName, Object value) {
    306         Robolectric.Reflection.setFinalStaticField(clazz, fieldName, value);
    307     }
    308 
    309     protected void delegateLoadingOf(final String className) {
    310         classLoader.delegateLoadingOf(className);
    311     }
    312 
    313     @Override protected Statement methodBlock(final FrameworkMethod method) {
    314         setupI18nStrictState(method.getMethod(), robolectricConfig);
    315         lookForLocaleAnnotation( method.getMethod(), robolectricConfig );
    316 
    317     	if (classHandler != null) {
    318             classHandler.configure(robolectricConfig);
    319             classHandler.beforeTest();
    320         }
    321         delegate.internalBeforeTest(method.getMethod());
    322 
    323         final Statement statement = super.methodBlock(method);
    324         return new Statement() {
    325             @Override public void evaluate() throws Throwable {
    326                 // todo: this try/finally probably isn't right -- should mimic RunAfters? [xw]
    327                 try {
    328                     statement.evaluate();
    329                 } finally {
    330                     delegate.internalAfterTest(method.getMethod());
    331                     if (classHandler != null) {
    332                         classHandler.afterTest();
    333                     }
    334                 }
    335             }
    336         };
    337     }
    338 
    339     /*
    340      * Called before each test method is run. Sets up the simulation of the Android runtime environment.
    341      */
    342     @Override public void internalBeforeTest(final Method method) {
    343         setupApplicationState(robolectricConfig);
    344 
    345         beforeTest(method);
    346     }
    347 
    348     @Override public void internalAfterTest(final Method method) {
    349         afterTest(method);
    350     }
    351 
    352     @Override public void setRobolectricConfig(final RobolectricConfig robolectricConfig) {
    353         this.robolectricConfig = robolectricConfig;
    354     }
    355 
    356     /**
    357      * Called before each test method is run.
    358      *
    359      * @param method the test method about to be run
    360      */
    361     public void beforeTest(final Method method) {
    362     }
    363 
    364     /**
    365      * Called after each test method is run.
    366      *
    367      * @param method the test method that just ran.
    368      */
    369     public void afterTest(final Method method) {
    370     }
    371 
    372     /**
    373      * You probably don't want to override this method. Override #prepareTest(Object) instead.
    374      *
    375      * @see BlockJUnit4ClassRunner#createTest()
    376      */
    377     @Override
    378     public Object createTest() throws Exception {
    379         if (delegate != null) {
    380             return delegate.createTest();
    381         } else {
    382             Object test = super.createTest();
    383             prepareTest(test);
    384             return test;
    385         }
    386     }
    387 
    388     public void prepareTest(final Object test) {
    389     }
    390 
    391     public void setupApplicationState(final RobolectricConfig robolectricConfig) {
    392         setupLogging();
    393 
    394         ResourceLoader resourceLoader = createResourceLoader(robolectricConfig );
    395 
    396         Robolectric.bindDefaultShadowClasses();
    397         bindShadowClasses();
    398 
    399         resourceLoader.setLayoutQualifierSearchPath();
    400         Robolectric.resetStaticState();
    401         resetStaticState();
    402 
    403         DatabaseConfig.setDatabaseMap(this.databaseMap);//Set static DatabaseMap in DBConfig
    404 
    405         Robolectric.application = ShadowApplication.bind(createApplication(), resourceLoader);
    406     }
    407 
    408     /**
    409      * Override this method to bind your own shadow classes
    410      */
    411     protected void bindShadowClasses() {
    412     }
    413 
    414     /**
    415      * Override this method to reset the state of static members before each test.
    416      */
    417     protected void resetStaticState() {
    418     }
    419 
    420     private static String getSystemProperty(String propertyName, String defaultValue) {
    421         String property = System.getProperty(propertyName);
    422         if (property == null) {
    423             property = defaultValue;
    424             logger.info("No system property " + propertyName + " found, default to "
    425                     + defaultValue);
    426         }
    427         return property;
    428     }
    429 
    430     /**
    431      * Sets Robolectric config to determine if Robolectric should blacklist API calls that are not
    432      * I18N/L10N-safe.
    433      * <p/>
    434      * I18n-strict mode affects suitably annotated shadow methods. Robolectric will throw exceptions
    435      * if these methods are invoked by application code. Additionally, Robolectric's ResourceLoader
    436      * will throw exceptions if layout resources use bare string literals instead of string resource IDs.
    437      * <p/>
    438      * To enable or disable i18n-strict mode for specific test cases, annotate them with
    439      * {@link com.xtremelabs.robolectric.annotation.EnableStrictI18n} or
    440      * {@link com.xtremelabs.robolectric.annotation.DisableStrictI18n}.
    441      * <p/>
    442      *
    443      * By default, I18n-strict mode is disabled.
    444      *
    445      * @param method
    446      * @param robolectricConfig
    447      */
    448     private void setupI18nStrictState(Method method, RobolectricConfig robolectricConfig) {
    449     	// Global
    450     	boolean strictI18n = globalI18nStrictEnabled();
    451 
    452     	// Test case class
    453     	Annotation[] annos = method.getDeclaringClass().getAnnotations();
    454     	strictI18n = lookForI18nAnnotations(strictI18n, annos);
    455 
    456     	// Test case methods
    457     	annos = method.getAnnotations();
    458     	strictI18n = lookForI18nAnnotations(strictI18n, annos);
    459 
    460 		robolectricConfig.setStrictI18n(strictI18n);
    461     }
    462 
    463     /**
    464      * Default implementation of global switch for i18n-strict mode.
    465      * To enable i18n-strict mode globally, set the system property
    466      * "robolectric.strictI18n" to true. This can be done via java
    467      * system properties in either Ant or Maven.
    468      * <p/>
    469      * Subclasses can override this method and establish their own policy
    470      * for enabling i18n-strict mode.
    471      *
    472      * @return
    473      */
    474     protected boolean globalI18nStrictEnabled() {
    475     	return Boolean.valueOf(System.getProperty("robolectric.strictI18n"));
    476     }
    477 
    478     /**
    479      * As test methods are loaded by the delegate's class loader, the normal
    480  	 * method#isAnnotationPresent test fails. Look at string versions of the
    481      * annotation names to test for their presence.
    482      *
    483      * @param strictI18n
    484      * @param annos
    485      * @return
    486      */
    487 	private boolean lookForI18nAnnotations(boolean strictI18n, Annotation[] annos) {
    488 		for ( int i = 0; i < annos.length; i++ ) {
    489     		String name = annos[i].annotationType().getName();
    490     		if (name.equals("com.xtremelabs.robolectric.annotation.EnableStrictI18n")) {
    491     			strictI18n = true;
    492     			break;
    493     		}
    494     		if (name.equals("com.xtremelabs.robolectric.annotation.DisableStrictI18n")) {
    495     			strictI18n = false;
    496     			break;
    497     		}
    498     	}
    499 		return strictI18n;
    500 	}
    501 
    502 	private void lookForLocaleAnnotation( Method method, RobolectricConfig robolectricConfig ){
    503 		String locale = "";
    504 		// TODO: there are maybe better implementation for getAnnotation
    505 		// Have tried to use several other simple ways, but failed.
    506 		Annotation[] annos = method.getDeclaredAnnotations();
    507 		for( Annotation anno: annos ){
    508 
    509 			if( anno.annotationType().getName().equals( "com.xtremelabs.robolectric.annotation.Values" )){
    510 				String annotationString = anno.toString();
    511 				int startIndex = annotationString.indexOf( '=' );
    512 				int endIndex = annotationString.indexOf( ')' );
    513 
    514 				if( startIndex < 0 || endIndex < 0 ){ return; }
    515 
    516 				locale = annotationString.substring( startIndex + 1, endIndex );
    517 			}
    518 		}
    519 
    520 		robolectricConfig.setLocale( locale );
    521 	}
    522 
    523     private void setupLogging() {
    524         String logging = System.getProperty("robolectric.logging");
    525         if (logging != null && ShadowLog.stream == null) {
    526             PrintStream stream = null;
    527             if ("stdout".equalsIgnoreCase(logging)) {
    528                 stream = System.out;
    529             } else if ("stderr".equalsIgnoreCase(logging)) {
    530                 stream = System.err;
    531             } else {
    532                 try {
    533                     final PrintStream file = new PrintStream(new FileOutputStream(logging));
    534                     stream = file;
    535                     Runtime.getRuntime().addShutdownHook(new Thread() {
    536                         @Override public void run() {
    537                             try { file.close(); } catch (Exception ignored) { }
    538                         }
    539                     });
    540                 } catch (IOException e) {
    541                     e.printStackTrace();
    542                 }
    543             }
    544             ShadowLog.stream = stream;
    545         }
    546     }
    547 
    548     /**
    549      * Override this method if you want to provide your own implementation of Application.
    550      * <p/>
    551      * This method attempts to instantiate an application instance as specified by the AndroidManifest.xml.
    552      *
    553      * @return An instance of the Application class specified by the ApplicationManifest.xml or an instance of
    554      *         Application if not specified.
    555      */
    556     protected Application createApplication() {
    557         return new ApplicationResolver(robolectricConfig).resolveApplication();
    558     }
    559 
    560     private ResourceLoader createResourceLoader(final RobolectricConfig robolectricConfig) {
    561         ResourceLoader resourceLoader = resourceLoaderForRootAndDirectory.get(robolectricConfig);
    562         // When locale has changed, reload the resource files.
    563         if (resourceLoader == null || robolectricConfig.isLocaleChanged() ) {
    564             try {
    565                 robolectricConfig.validate();
    566 
    567                 String rClassName = robolectricConfig.getRClassName();
    568                 Class rClass;
    569                 try {
    570                     rClass = Class.forName(rClassName);
    571                 } catch (ClassNotFoundException e) {
    572                     rClass = null;
    573                 }
    574                 resourceLoader = new ResourceLoader(robolectricConfig.getRealSdkVersion(), rClass, robolectricConfig.getResourceDirectory(), robolectricConfig.getAssetsDirectory(), robolectricConfig.getLocale() );
    575                 resourceLoaderForRootAndDirectory.put(robolectricConfig, resourceLoader);
    576             } catch (Exception e) {
    577                 throw new RuntimeException(e);
    578             }
    579         }
    580 
    581         resourceLoader.setStrictI18n(robolectricConfig.getStrictI18n());
    582         return resourceLoader;
    583     }
    584 
    585     private String findResourcePackageName(final File projectManifestFile) throws ParserConfigurationException, IOException, SAXException {
    586         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    587         DocumentBuilder db = dbf.newDocumentBuilder();
    588         Document doc = db.parse(projectManifestFile);
    589 
    590         String projectPackage = doc.getElementsByTagName("manifest").item(0).getAttributes().getNamedItem("package").getTextContent();
    591 
    592         return projectPackage + ".R";
    593     }
    594 
    595     /*
    596      * Specifies what database to use for testing (ex: H2 or Sqlite),
    597      * this will load H2 by default, the SQLite TestRunner version will override this.
    598      */
    599     protected DatabaseMap setupDatabaseMap(Class<?> testClass, DatabaseMap map) {
    600     	DatabaseMap dbMap = map;
    601 
    602     	if (testClass.isAnnotationPresent(UsingDatabaseMap.class)) {
    603 	    	UsingDatabaseMap usingMap = testClass.getAnnotation(UsingDatabaseMap.class);
    604 	    	if(usingMap.value()!=null){
    605 	    		dbMap = Robolectric.newInstanceOf(usingMap.value());
    606 	    	} else {
    607 	    		if (dbMap==null)
    608 		    		throw new RuntimeException("UsingDatabaseMap annotation value must provide a class implementing DatabaseMap");
    609 	    	}
    610     	}
    611     	return dbMap;
    612     }
    613 
    614     public DatabaseMap getDatabaseMap() {
    615 		return databaseMap;
    616 	}
    617 
    618 	@Override
    619   public void setDatabaseMap(DatabaseMap databaseMap) {
    620 		this.databaseMap = databaseMap;
    621 	}
    622 
    623 	/**
    624 	 * Detects whether current instance is already instrumented.
    625 	 */
    626 	public interface InstrumentDetector {
    627 
    628 	    /** Default detector. */
    629 	    InstrumentDetector DEFAULT = new InstrumentDetector() {
    630 	        @Override
    631 	        public boolean isInstrumented() {
    632 	            return RobolectricTestRunner.class.getClassLoader().getClass().getName().contains(RobolectricClassLoader.class.getName());
    633 	        }
    634 	    };
    635 
    636 	    /**
    637 	     * @return true if current instance is already instrumented
    638 	     */
    639 	    boolean isInstrumented();
    640 
    641 	}
    642 
    643 }
    644