1package com.xtremelabs.robolectric;
2
3import java.io.File;
4import java.io.FileOutputStream;
5import java.io.IOException;
6import java.io.PrintStream;
7import java.lang.annotation.Annotation;
8import java.lang.reflect.Constructor;
9import java.lang.reflect.Method;
10import java.util.HashMap;
11import java.util.Map;
12import java.util.logging.Logger;
13
14import javassist.Loader;
15
16import javax.xml.parsers.DocumentBuilder;
17import javax.xml.parsers.DocumentBuilderFactory;
18import javax.xml.parsers.ParserConfigurationException;
19
20import org.junit.runners.BlockJUnit4ClassRunner;
21import org.junit.runners.model.FrameworkMethod;
22import org.junit.runners.model.InitializationError;
23import org.junit.runners.model.Statement;
24import org.w3c.dom.Document;
25import org.xml.sax.SAXException;
26
27import android.app.Application;
28import android.net.Uri__FromAndroid;
29
30import com.xtremelabs.robolectric.bytecode.ClassHandler;
31import com.xtremelabs.robolectric.bytecode.RobolectricClassLoader;
32import com.xtremelabs.robolectric.bytecode.ShadowWrangler;
33import com.xtremelabs.robolectric.internal.RealObject;
34import com.xtremelabs.robolectric.internal.RobolectricTestRunnerInterface;
35import com.xtremelabs.robolectric.res.ResourceLoader;
36import com.xtremelabs.robolectric.shadows.ShadowApplication;
37import com.xtremelabs.robolectric.shadows.ShadowLog;
38import com.xtremelabs.robolectric.util.DatabaseConfig;
39import com.xtremelabs.robolectric.util.DatabaseConfig.DatabaseMap;
40import com.xtremelabs.robolectric.util.DatabaseConfig.UsingDatabaseMap;
41import 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 */
47public 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