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 
getDefaultLoader()74     private static RobolectricClassLoader getDefaultLoader() {
75         if (defaultLoader == null) {
76             defaultLoader = new RobolectricClassLoader(ShadowWrangler.getInstance());
77         }
78         return defaultLoader;
79     }
80 
setInstrumentDetector(final InstrumentDetector detector)81     public static void setInstrumentDetector(final InstrumentDetector detector) {
82       instrumentDetector = detector;
83     }
84 
setDefaultLoader(Loader robolectricClassLoader)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      */
addClassOrPackageToInstrument(String classOrPackageToBeInstrumented)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      */
RobolectricTestRunner(final Class<?> testClass)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      */
RobolectricTestRunner(final Class<?> testClass, RobolectricClassLoader classLoader)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      */
RobolectricTestRunner(final Class<?> testClass, final RobolectricConfig robolectricConfig)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      */
RobolectricTestRunner(Class<?> testClass, RobolectricConfig robolectricConfig, DatabaseMap databaseMap)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      */
RobolectricTestRunner(final Class<?> testClass, final File androidProjectRoot)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
RobolectricTestRunner(final Class<?> testClass, final String androidProjectRoot)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      */
RobolectricTestRunner(final Class<?> testClass, final File androidManifestPath, final File resourceDirectory)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
RobolectricTestRunner(final Class<?> testClass, final String androidManifestPath, final String resourceDirectory)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 
RobolectricTestRunner(Class<?> testClass, ClassHandler classHandler, RobolectricClassLoader classLoader, RobolectricConfig robolectricConfig)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      */
RobolectricTestRunner(final Class<?> testClass, final ClassHandler classHandler, final RobolectricClassLoader classLoader, final RobolectricConfig robolectricConfig, final DatabaseMap map)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 
ensureClassLoaderNotNull( RobolectricClassLoader classLoader)282     private static RobolectricClassLoader ensureClassLoaderNotNull(
283         RobolectricClassLoader classLoader) {
284         return classLoader == null ? getDefaultLoader() : classLoader;
285     }
286 
isInstrumented()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"})
RobolectricTestRunner(final Class<?> testClass, final ClassHandler classHandler, final RobolectricConfig robolectricConfig)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
setStaticValue(Class<?> clazz, String fieldName, Object value)305     public static void setStaticValue(Class<?> clazz, String fieldName, Object value) {
306         Robolectric.Reflection.setFinalStaticField(clazz, fieldName, value);
307     }
308 
delegateLoadingOf(final String className)309     protected void delegateLoadingOf(final String className) {
310         classLoader.delegateLoadingOf(className);
311     }
312 
methodBlock(final FrameworkMethod method)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