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