aboutsummaryrefslogtreecommitdiff
path: root/v1/src/main/java/com/xtremelabs/robolectric/RobolectricTestRunner.java
blob: 50fc33bf57e45de52cfd2eb954e2c63e3df3b92d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
package com.xtremelabs.robolectric;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import javassist.Loader;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import android.app.Application;
import android.net.Uri__FromAndroid;

import com.xtremelabs.robolectric.bytecode.ClassHandler;
import com.xtremelabs.robolectric.bytecode.RobolectricClassLoader;
import com.xtremelabs.robolectric.bytecode.ShadowWrangler;
import com.xtremelabs.robolectric.internal.RealObject;
import com.xtremelabs.robolectric.internal.RobolectricTestRunnerInterface;
import com.xtremelabs.robolectric.res.ResourceLoader;
import com.xtremelabs.robolectric.shadows.ShadowApplication;
import com.xtremelabs.robolectric.shadows.ShadowLog;
import com.xtremelabs.robolectric.util.DatabaseConfig;
import com.xtremelabs.robolectric.util.DatabaseConfig.DatabaseMap;
import com.xtremelabs.robolectric.util.DatabaseConfig.UsingDatabaseMap;
import com.xtremelabs.robolectric.util.SQLiteMap;

/**
 * Installs a {@link RobolectricClassLoader} and {@link com.xtremelabs.robolectric.res.ResourceLoader} in order to
 * provide a simulation of the Android runtime environment.
 */
public class RobolectricTestRunner extends BlockJUnit4ClassRunner implements RobolectricTestRunnerInterface {

    private static final String MANIFEST_PATH_PROPERTY = "robolectric.path.manifest";
    private static final String RES_PATH_PROPERTY = "robolectric.path.res";
    private static final String ASSETS_PATH_PROPERTY = "robolectric.path.assets";
    private static final String DEFAULT_MANIFEST_PATH = "./AndroidManifest.xml";
    private static final String DEFAULT_RES_PATH = "./res";
    private static final String DEFAULT_ASSETS_PATH = "./assets";

    private static final Logger logger =
            Logger.getLogger(RobolectricTestRunner.class.getSimpleName());

    /** Instrument detector. We use it to check whether the current instance is instrumented. */
  	private static InstrumentDetector instrumentDetector = InstrumentDetector.DEFAULT;

    private static RobolectricClassLoader defaultLoader;
    private static Map<RobolectricConfig, ResourceLoader> resourceLoaderForRootAndDirectory = new HashMap<RobolectricConfig, ResourceLoader>();

    // fields in the RobolectricTestRunner in the original ClassLoader
    private RobolectricClassLoader classLoader;
    private ClassHandler classHandler;
    private RobolectricTestRunnerInterface delegate;
    private DatabaseMap databaseMap;

	// fields in the RobolectricTestRunner in the instrumented ClassLoader
    protected RobolectricConfig robolectricConfig;

    private static RobolectricClassLoader getDefaultLoader() {
        if (defaultLoader == null) {
            defaultLoader = new RobolectricClassLoader(ShadowWrangler.getInstance());
        }
        return defaultLoader;
    }

    public static void setInstrumentDetector(final InstrumentDetector detector) {
      instrumentDetector = detector;
    }

    public static void setDefaultLoader(Loader robolectricClassLoader) {
    	//used by the RoboSpecs project to allow for mixed scala\java tests to be run with Maven Surefire (see the RoboSpecs project on github)
        if (defaultLoader == null) {
            defaultLoader = (RobolectricClassLoader)robolectricClassLoader;
        } else throw new RuntimeException("You may not set the default robolectricClassLoader unless it is null!");
    }

    /**
     * Call this if you would like Robolectric to rewrite additional classes and turn them
     * into "do nothing" classes which proxy all method calls to shadow classes, just like it does
     * with the android classes by default.
     *
     * @param classOrPackageToBeInstrumented fully-qualified class or package name
     */
    protected static void addClassOrPackageToInstrument(String classOrPackageToBeInstrumented) {
        if (!isInstrumented()) {
            defaultLoader.addCustomShadowClass(classOrPackageToBeInstrumented);
        }
    }

    /**
     * Creates a runner to run {@code testClass}. Looks in your working directory for your AndroidManifest.xml file
     * and res directory.
     *
     * @param testClass the test class to be run
     * @throws InitializationError if junit says so
     */
    public RobolectricTestRunner(final Class<?> testClass) throws InitializationError {
        this(testClass, new RobolectricConfig(
                new File(getSystemProperty(MANIFEST_PATH_PROPERTY, DEFAULT_MANIFEST_PATH)),
                new File(getSystemProperty(RES_PATH_PROPERTY, DEFAULT_RES_PATH)),
                new File(getSystemProperty(ASSETS_PATH_PROPERTY, DEFAULT_ASSETS_PATH))));
    }

    /**
     * Creates a runner to run {@code testClass}. Looks in your working directory for your AndroidManifest.xml file
     * and res directory.
     *
     * @param testClass the test class to be run
     * @param classLoader a custom RobolectricClassLoader to be used.
     * @throws InitializationError if junit says so
     */
    public RobolectricTestRunner(final Class<?> testClass, RobolectricClassLoader classLoader)
            throws InitializationError {
        this(testClass,
            isInstrumented() ? null : ShadowWrangler.getInstance(),
            isInstrumented() ? null : classLoader,
            new RobolectricConfig(
                new File(getSystemProperty(MANIFEST_PATH_PROPERTY, DEFAULT_MANIFEST_PATH)),
                new File(getSystemProperty(RES_PATH_PROPERTY, DEFAULT_RES_PATH)),
                new File(getSystemProperty(ASSETS_PATH_PROPERTY, DEFAULT_ASSETS_PATH))));
    }

    /**
     * Call this constructor in subclasses in order to specify non-default configuration (e.g. location of the
     * AndroidManifest.xml file and resource directory).
     *
     * @param testClass         the test class to be run
     * @param robolectricConfig the configuration data
     * @throws InitializationError if junit says so
     */
    protected RobolectricTestRunner(final Class<?> testClass, final RobolectricConfig robolectricConfig)
            throws InitializationError {
        this(testClass,
                isInstrumented() ? null : ShadowWrangler.getInstance(),
                isInstrumented() ? null : getDefaultLoader(),
                robolectricConfig, new SQLiteMap());
    }

    /**
     * Call this constructor in subclasses in order to specify non-default configuration (e.g. location of the
     * AndroidManifest.xml file, resource directory, and DatabaseMap).
     *
     * @param testClass         the test class to be run
     * @param robolectricConfig the configuration data
     * @param databaseMap		the database mapping
     * @throws InitializationError if junit says so
     */
    protected RobolectricTestRunner(Class<?> testClass, RobolectricConfig robolectricConfig, DatabaseMap databaseMap)
            throws InitializationError {
        this(testClass,
                isInstrumented() ? null : ShadowWrangler.getInstance(),
                isInstrumented() ? null : getDefaultLoader(),
                robolectricConfig, databaseMap);
    }

    /**
     * Call this constructor in subclasses in order to specify the project root directory.
     *
     * @param testClass          the test class to be run
     * @param androidProjectRoot the directory containing your AndroidManifest.xml file and res dir
     * @throws InitializationError if the test class is malformed
     */
    public RobolectricTestRunner(final Class<?> testClass, final File androidProjectRoot) throws InitializationError {
        this(testClass, new RobolectricConfig(androidProjectRoot));
    }

    /**
     * Call this constructor in subclasses in order to specify the project root directory.
     *
     * @param testClass          the test class to be run
     * @param androidProjectRoot the directory containing your AndroidManifest.xml file and res dir
     * @throws InitializationError if junit says so
     * @deprecated Use {@link #RobolectricTestRunner(Class, File)} instead.
     */
    @Deprecated
    public RobolectricTestRunner(final Class<?> testClass, final String androidProjectRoot) throws InitializationError {
        this(testClass, new RobolectricConfig(new File(androidProjectRoot)));
    }

    /**
     * Call this constructor in subclasses in order to specify the location of the AndroidManifest.xml file and the
     * resource directory. The #androidManifestPath is used to locate the AndroidManifest.xml file which, in turn,
     * contains package name for the {@code R} class which contains the identifiers for all of the resources. The
     * resource directory is where the resource loader will look for resources to load.
     *
     * @param testClass           the test class to be run
     * @param androidManifestPath the AndroidManifest.xml file
     * @param resourceDirectory   the directory containing the project's resources
     * @throws InitializationError if junit says so
     */
    protected RobolectricTestRunner(final Class<?> testClass, final File androidManifestPath, final File resourceDirectory)
            throws InitializationError {
        this(testClass, new RobolectricConfig(androidManifestPath, resourceDirectory));
    }

    /**
     * Call this constructor in subclasses in order to specify the location of the AndroidManifest.xml file and the
     * resource directory. The #androidManifestPath is used to locate the AndroidManifest.xml file which, in turn,
     * contains package name for the {@code R} class which contains the identifiers for all of the resources. The
     * resource directory is where the resource loader will look for resources to load.
     *
     * @param testClass           the test class to be run
     * @param androidManifestPath the relative path to the AndroidManifest.xml file
     * @param resourceDirectory   the relative path to the directory containing the project's resources
     * @throws InitializationError if junit says so
     * @deprecated Use {@link #RobolectricTestRunner(Class, File, File)} instead.
     */
    @Deprecated
    protected RobolectricTestRunner(final Class<?> testClass, final String androidManifestPath, final String resourceDirectory)
            throws InitializationError {
        this(testClass, new RobolectricConfig(new File(androidManifestPath), new File(resourceDirectory)));
    }

    protected RobolectricTestRunner(Class<?> testClass, ClassHandler classHandler, RobolectricClassLoader classLoader, RobolectricConfig robolectricConfig) throws InitializationError {
        this(testClass, classHandler, classLoader, robolectricConfig, new SQLiteMap());
    }


    /**
     * This is not the constructor you are looking for... probably. This constructor creates a bridge between the test
     * runner called by JUnit and a second instance of the test runner that is loaded via the instrumenting class
     * loader. This instrumented instance of the test runner, along with the instrumented instance of the actual test,
     * provides access to Robolectric's features and the un-instrumented instance of the test runner delegates most of
     * the interesting test runner behavior to it. Providing your own class handler and class loader here in order to
     * get different functionality is a difficult and dangerous project. If you need to customize the project root and
     * resource directory, use {@link #RobolectricTestRunner(Class, String, String)}. For other extensions, consider
     * creating a subclass and overriding the documented methods of this class.
     *
     * @param testClass         the test class to be run
     * @param classHandler      the {@link ClassHandler} to use to in shadow delegation
     * @param classLoader       the {@link RobolectricClassLoader}
     * @param robolectricConfig the configuration
     * @throws InitializationError if junit says so
     */
    protected RobolectricTestRunner(final Class<?> testClass, final ClassHandler classHandler, final RobolectricClassLoader classLoader, final RobolectricConfig robolectricConfig, final DatabaseMap map) throws InitializationError {
        super(isInstrumented() ? testClass
            : ensureClassLoaderNotNull(classLoader).bootstrap(testClass));

        if (!isInstrumented()) {
            this.classHandler = classHandler;
            this.classLoader = ensureClassLoaderNotNull(classLoader);
            this.robolectricConfig = robolectricConfig;
            this.databaseMap = setupDatabaseMap(testClass, map);

            Thread.currentThread().setContextClassLoader(classLoader);

            delegateLoadingOf(Uri__FromAndroid.class.getName());
            delegateLoadingOf(RobolectricTestRunnerInterface.class.getName());
            delegateLoadingOf(RealObject.class.getName());
            delegateLoadingOf(ShadowWrangler.class.getName());
            delegateLoadingOf(RobolectricConfig.class.getName());
            delegateLoadingOf(DatabaseMap.class.getName());
            delegateLoadingOf(android.R.class.getName());

            Class<?> delegateClass = classLoader.bootstrap(this.getClass());
            try {
                Constructor<?> constructorForDelegate = delegateClass.getConstructor(Class.class);
                this.delegate = (RobolectricTestRunnerInterface) constructorForDelegate.newInstance(classLoader.bootstrap(testClass));
                this.delegate.setRobolectricConfig(robolectricConfig);
                this.delegate.setDatabaseMap(databaseMap);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private static RobolectricClassLoader ensureClassLoaderNotNull(
        RobolectricClassLoader classLoader) {
        return classLoader == null ? getDefaultLoader() : classLoader;
    }

    protected static boolean isInstrumented() {
        return instrumentDetector.isInstrumented();
    }

    /**
     * Only used when creating the delegate instance within the instrumented ClassLoader.
     * <p/>
     * This is not the constructor you are looking for.
     */
    @SuppressWarnings({"UnusedDeclaration", "JavaDoc"})
    protected RobolectricTestRunner(final Class<?> testClass, final ClassHandler classHandler, final RobolectricConfig robolectricConfig) throws InitializationError {
        super(testClass);
        this.classHandler = classHandler;
        this.robolectricConfig = robolectricConfig;
    }

    /** @deprecated use {@link Robolectric.Reflection#setFinalStaticField(Class, String, Object)} */
    @Deprecated
    public static void setStaticValue(Class<?> clazz, String fieldName, Object value) {
        Robolectric.Reflection.setFinalStaticField(clazz, fieldName, value);
    }

    protected void delegateLoadingOf(final String className) {
        classLoader.delegateLoadingOf(className);
    }

    @Override protected Statement methodBlock(final FrameworkMethod method) {
        setupI18nStrictState(method.getMethod(), robolectricConfig);
        lookForLocaleAnnotation( method.getMethod(), robolectricConfig );

    	if (classHandler != null) {
            classHandler.configure(robolectricConfig);
            classHandler.beforeTest();
        }
        delegate.internalBeforeTest(method.getMethod());

        final Statement statement = super.methodBlock(method);
        return new Statement() {
            @Override public void evaluate() throws Throwable {
                // todo: this try/finally probably isn't right -- should mimic RunAfters? [xw]
                try {
                    statement.evaluate();
                } finally {
                    delegate.internalAfterTest(method.getMethod());
                    if (classHandler != null) {
                        classHandler.afterTest();
                    }
                }
            }
        };
    }

    /*
     * Called before each test method is run. Sets up the simulation of the Android runtime environment.
     */
    @Override public void internalBeforeTest(final Method method) {
        setupApplicationState(robolectricConfig);

        beforeTest(method);
    }

    @Override public void internalAfterTest(final Method method) {
        afterTest(method);
    }

    @Override public void setRobolectricConfig(final RobolectricConfig robolectricConfig) {
        this.robolectricConfig = robolectricConfig;
    }

    /**
     * Called before each test method is run.
     *
     * @param method the test method about to be run
     */
    public void beforeTest(final Method method) {
    }

    /**
     * Called after each test method is run.
     *
     * @param method the test method that just ran.
     */
    public void afterTest(final Method method) {
    }

    /**
     * You probably don't want to override this method. Override #prepareTest(Object) instead.
     *
     * @see BlockJUnit4ClassRunner#createTest()
     */
    @Override
    public Object createTest() throws Exception {
        if (delegate != null) {
            return delegate.createTest();
        } else {
            Object test = super.createTest();
            prepareTest(test);
            return test;
        }
    }

    public void prepareTest(final Object test) {
    }

    public void setupApplicationState(final RobolectricConfig robolectricConfig) {
        setupLogging();

        ResourceLoader resourceLoader = createResourceLoader(robolectricConfig );

        Robolectric.bindDefaultShadowClasses();
        bindShadowClasses();

        resourceLoader.setLayoutQualifierSearchPath();
        Robolectric.resetStaticState();
        resetStaticState();

        DatabaseConfig.setDatabaseMap(this.databaseMap);//Set static DatabaseMap in DBConfig

        Robolectric.application = ShadowApplication.bind(createApplication(), resourceLoader);
    }

    /**
     * Override this method to bind your own shadow classes
     */
    protected void bindShadowClasses() {
    }

    /**
     * Override this method to reset the state of static members before each test.
     */
    protected void resetStaticState() {
    }

    private static String getSystemProperty(String propertyName, String defaultValue) {
        String property = System.getProperty(propertyName);
        if (property == null) {
            property = defaultValue;
            logger.info("No system property " + propertyName + " found, default to "
                    + defaultValue);
        }
        return property;
    }

    /**
     * Sets Robolectric config to determine if Robolectric should blacklist API calls that are not
     * I18N/L10N-safe.
     * <p/>
     * I18n-strict mode affects suitably annotated shadow methods. Robolectric will throw exceptions
     * if these methods are invoked by application code. Additionally, Robolectric's ResourceLoader
     * will throw exceptions if layout resources use bare string literals instead of string resource IDs.
     * <p/>
     * To enable or disable i18n-strict mode for specific test cases, annotate them with
     * {@link com.xtremelabs.robolectric.annotation.EnableStrictI18n} or
     * {@link com.xtremelabs.robolectric.annotation.DisableStrictI18n}.
     * <p/>
     *
     * By default, I18n-strict mode is disabled.
     *
     * @param method
     * @param robolectricConfig
     */
    private void setupI18nStrictState(Method method, RobolectricConfig robolectricConfig) {
    	// Global
    	boolean strictI18n = globalI18nStrictEnabled();

    	// Test case class
    	Annotation[] annos = method.getDeclaringClass().getAnnotations();
    	strictI18n = lookForI18nAnnotations(strictI18n, annos);

    	// Test case methods
    	annos = method.getAnnotations();
    	strictI18n = lookForI18nAnnotations(strictI18n, annos);

		robolectricConfig.setStrictI18n(strictI18n);
    }

    /**
     * Default implementation of global switch for i18n-strict mode.
     * To enable i18n-strict mode globally, set the system property
     * "robolectric.strictI18n" to true. This can be done via java
     * system properties in either Ant or Maven.
     * <p/>
     * Subclasses can override this method and establish their own policy
     * for enabling i18n-strict mode.
     *
     * @return
     */
    protected boolean globalI18nStrictEnabled() {
    	return Boolean.valueOf(System.getProperty("robolectric.strictI18n"));
    }

    /**
     * As test methods are loaded by the delegate's class loader, the normal
 	 * method#isAnnotationPresent test fails. Look at string versions of the
     * annotation names to test for their presence.
     *
     * @param strictI18n
     * @param annos
     * @return
     */
	private boolean lookForI18nAnnotations(boolean strictI18n, Annotation[] annos) {
		for ( int i = 0; i < annos.length; i++ ) {
    		String name = annos[i].annotationType().getName();
    		if (name.equals("com.xtremelabs.robolectric.annotation.EnableStrictI18n")) {
    			strictI18n = true;
    			break;
    		}
    		if (name.equals("com.xtremelabs.robolectric.annotation.DisableStrictI18n")) {
    			strictI18n = false;
    			break;
    		}
    	}
		return strictI18n;
	}

	private void lookForLocaleAnnotation( Method method, RobolectricConfig robolectricConfig ){
		String locale = "";
		// TODO: there are maybe better implementation for getAnnotation
		// Have tried to use several other simple ways, but failed.
		Annotation[] annos = method.getDeclaredAnnotations();
		for( Annotation anno: annos ){

			if( anno.annotationType().getName().equals( "com.xtremelabs.robolectric.annotation.Values" )){
				String annotationString = anno.toString();
				int startIndex = annotationString.indexOf( '=' );
				int endIndex = annotationString.indexOf( ')' );

				if( startIndex < 0 || endIndex < 0 ){ return; }

				locale = annotationString.substring( startIndex + 1, endIndex );
			}
		}

		robolectricConfig.setLocale( locale );
	}

    private void setupLogging() {
        String logging = System.getProperty("robolectric.logging");
        if (logging != null && ShadowLog.stream == null) {
            PrintStream stream = null;
            if ("stdout".equalsIgnoreCase(logging)) {
                stream = System.out;
            } else if ("stderr".equalsIgnoreCase(logging)) {
                stream = System.err;
            } else {
                try {
                    final PrintStream file = new PrintStream(new FileOutputStream(logging));
                    stream = file;
                    Runtime.getRuntime().addShutdownHook(new Thread() {
                        @Override public void run() {
                            try { file.close(); } catch (Exception ignored) { }
                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            ShadowLog.stream = stream;
        }
    }

    /**
     * Override this method if you want to provide your own implementation of Application.
     * <p/>
     * This method attempts to instantiate an application instance as specified by the AndroidManifest.xml.
     *
     * @return An instance of the Application class specified by the ApplicationManifest.xml or an instance of
     *         Application if not specified.
     */
    protected Application createApplication() {
        return new ApplicationResolver(robolectricConfig).resolveApplication();
    }

    private ResourceLoader createResourceLoader(final RobolectricConfig robolectricConfig) {
        ResourceLoader resourceLoader = resourceLoaderForRootAndDirectory.get(robolectricConfig);
        // When locale has changed, reload the resource files.
        if (resourceLoader == null || robolectricConfig.isLocaleChanged() ) {
            try {
                robolectricConfig.validate();

                String rClassName = robolectricConfig.getRClassName();
                Class rClass;
                try {
                    rClass = Class.forName(rClassName);
                } catch (ClassNotFoundException e) {
                    rClass = null;
                }
                resourceLoader = new ResourceLoader(robolectricConfig.getRealSdkVersion(), rClass, robolectricConfig.getResourceDirectory(), robolectricConfig.getAssetsDirectory(), robolectricConfig.getLocale() );
                resourceLoaderForRootAndDirectory.put(robolectricConfig, resourceLoader);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        resourceLoader.setStrictI18n(robolectricConfig.getStrictI18n());
        return resourceLoader;
    }

    private String findResourcePackageName(final File projectManifestFile) throws ParserConfigurationException, IOException, SAXException {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.parse(projectManifestFile);

        String projectPackage = doc.getElementsByTagName("manifest").item(0).getAttributes().getNamedItem("package").getTextContent();

        return projectPackage + ".R";
    }

    /*
     * Specifies what database to use for testing (ex: H2 or Sqlite),
     * this will load H2 by default, the SQLite TestRunner version will override this.
     */
    protected DatabaseMap setupDatabaseMap(Class<?> testClass, DatabaseMap map) {
    	DatabaseMap dbMap = map;

    	if (testClass.isAnnotationPresent(UsingDatabaseMap.class)) {
	    	UsingDatabaseMap usingMap = testClass.getAnnotation(UsingDatabaseMap.class);
	    	if(usingMap.value()!=null){
	    		dbMap = Robolectric.newInstanceOf(usingMap.value());
	    	} else {
	    		if (dbMap==null)
		    		throw new RuntimeException("UsingDatabaseMap annotation value must provide a class implementing DatabaseMap");
	    	}
    	}
    	return dbMap;
    }

    public DatabaseMap getDatabaseMap() {
		return databaseMap;
	}

	@Override
  public void setDatabaseMap(DatabaseMap databaseMap) {
		this.databaseMap = databaseMap;
	}

	/**
	 * Detects whether current instance is already instrumented.
	 */
	public interface InstrumentDetector {

	    /** Default detector. */
	    InstrumentDetector DEFAULT = new InstrumentDetector() {
	        @Override
	        public boolean isInstrumented() {
	            return RobolectricTestRunner.class.getClassLoader().getClass().getName().contains(RobolectricClassLoader.class.getName());
	        }
	    };

	    /**
	     * @return true if current instance is already instrumented
	     */
	    boolean isInstrumented();

	}

}