Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 87 additions & 27 deletions src/main/scala/RoboSpecs.scala
Original file line number Diff line number Diff line change
@@ -1,68 +1,128 @@
package com.github.jbrechtel.robospecs

import com.xtremelabs.robolectric.res.ResourceLoader
import org.specs2.mutable.Specification
import com.xtremelabs.robolectric.bytecode.{RobolectricClassLoader, ShadowWrangler}
import android.net.Uri__FromAndroid
import org.specs2.specification._
import com.xtremelabs.robolectric.util.DatabaseConfig
import org.specs2.mutable.SpecificationFeatures
import com.xtremelabs.robolectric.internal.RealObject
import android.R
import java.io.File
import com.xtremelabs.robolectric.res.ResourceLoader
import com.xtremelabs.robolectric.shadows.ShadowApplication
import com.xtremelabs.robolectric.{ApplicationResolver, Robolectric, RobolectricConfig}
import org.specs2.specification._
import com.xtremelabs.robolectric.util.DatabaseConfig.DatabaseMap
import com.xtremelabs.robolectric.util.H2Map
import com.xtremelabs.robolectric.internal.RobolectricTestRunnerInterface
import javassist.Loader
import com.xtremelabs.robolectric.RobolectricTestRunner

trait RoboSpecs extends Specification with RoboSpecsWithInstrumentation {
lazy val instrumentedClass = RoboSpecs.classLoader.bootstrap(this.getClass)
lazy val instrumentedInstance = instrumentedClass.newInstance.asInstanceOf[RoboSpecsWithInstrumentation]
def instrumentedFragments = super.is
override def is = instrumentedInstance.setup(instrumentedInstance.instrumentedFragments)
trait RoboDbMap {def dbMap: DatabaseMap}

/**
* Requires robolectric-sqlite.jar (com.seventheye.robolectric.sqlite.util.SQLiteMap)
*/
trait RoboSQLiteMap extends RoboDbMap {
//support for SQLite (https://github.com/cessationoftime/robolectric-sqlite)
def dbMap = Class.forName("com.seventheye.robolectric.sqlite.util.SQLiteMap").newInstance().asInstanceOf[DatabaseMap]
}

//Support for H2 Database
trait RoboH2Map extends RoboDbMap {
def dbMap = new H2Map()
}

trait RoboAcceptanceSpecs extends org.specs2.Specification with RoboSpecsWithInstrumentation {
lazy val instrumentedClass = RoboSpecs.classLoader.bootstrap(this.getClass)
//extend mutable Specification
trait RoboSpecs extends Specification with RoboSpecsWithInstrumentation { this:RoboDbMap =>
val loader = RoboSpecs.getClassLoader(this.getClass)
val instrumentedClass = loader.bootstrap(this.getClass)
Thread.currentThread().setContextClassLoader(loader);
lazy val instrumentedInstance = instrumentedClass.newInstance.asInstanceOf[RoboSpecsWithInstrumentation]
def instrumentedFragments = super.is //to get the instrumentedFragments from the instrumentedInstance
override def is = instrumentedInstance.setup(instrumentedInstance.instrumentedFragments) //Called By SpecificationStructure.content
}

//extend immutable Specification
trait RoboAcceptanceSpecs extends org.specs2.Specification with RoboSpecsWithInstrumentation { this:RoboDbMap =>
val loader = RoboSpecs.getClassLoader(this.getClass)
val instrumentedClass = loader.bootstrap(this.getClass)
Thread.currentThread().setContextClassLoader(loader);

lazy val instrumentedInstance = instrumentedClass.newInstance.asInstanceOf[RoboSpecsWithInstrumentation]
override def map(f: => Fragments) = instrumentedInstance.setup(instrumentedInstance.is)
def instrumentedFragments = is
}

trait RoboSpecsWithInstrumentation extends SpecificationStructure {
trait RoboSpecsWithInstrumentation extends SpecificationStructure { this:RoboDbMap =>
lazy val setup = new BeforeEach {
def before { setupApplicationState() }
def ^(fs: Fragments) = this(fs)
def ^(fs: Fragments) = this(fs)
}
def instrumentedFragments: Fragments
lazy val robolectricConfig = new RobolectricConfig(new File("./src/main"))
lazy val resourceLoader = {

lazy val robolectricConfig = new RobolectricConfig(new File("."))

//Use option to box and obscure the ResourceLoader type, this makes Maven Surefire (JUnit) happier when running individual tests with the -Dtest option
private lazy val resourceLoader : Option[ResourceLoader] = {
val rClassName: String = robolectricConfig.getRClassName
val rClass: Class[_] = Class.forName(rClassName)
new ResourceLoader(robolectricConfig.getSdkVersion, rClass, robolectricConfig.getResourceDirectory, robolectricConfig.getAssetsDirectory)
Some(new ResourceLoader(robolectricConfig.getSdkVersion, rClass, robolectricConfig.getResourceDirectory, robolectricConfig.getAssetsDirectory))
}

def setupApplicationState() {
robolectricConfig.validate()
Robolectric.bindDefaultShadowClasses()
Robolectric.resetStaticState()
Robolectric.application = ShadowApplication.bind(new ApplicationResolver(robolectricConfig).resolveApplication, resourceLoader)
Robolectric.bindDefaultShadowClasses()
Robolectric.resetStaticState()
DatabaseConfig.setDatabaseMap(dbMap);//Set static DatabaseMap in DBConfig
Robolectric.application = ShadowApplication.bind(new ApplicationResolver(robolectricConfig).resolveApplication, resourceLoader.get)
}
}

object RoboSpecs {
type RoboLoader = Loader {def bootstrap(testClass : Class[_] ) : Class[_]}

lazy val classHandler = ShadowWrangler.getInstance
lazy val classLoader = {
val loader = new RobolectricClassLoader(classHandler)
loader.delegateLoadingOf("org.specs2.")
loader.delegateLoadingOf("org.mockito.")
loader.delegateLoadingOf("scala.")

private def isInstrumentedClass(clazz : Class[_]) =
clazz.getClassLoader().getClass().getName().contains("RobolectricClassLoader")

private var loader :RoboLoader = null;


def getClassLoader(clazz : Class[_]) : RoboLoader = {
//Handle the case where normal Robolectric and Robospecs tests are both run by Maven Surefire and they must share an instrumented classloader
if (loader == null) {
loader = useCurrentClassLoader(clazz).orElse(getNewClassLoader).get;

("org.specs2." :: "org.mockito." :: "scala."
:: classOf[RoboSpecsWithInstrumentation].getName() :: Nil)
.foreach { x => loader.delegateLoadingOf(x) }
}
loader
}

private def useCurrentClassLoader(clazz : Class[_]) :Option[RoboLoader] =
if (isInstrumentedClass(clazz)) {
Some(clazz.getClassLoader().asInstanceOf[RoboLoader])
} else None


private def getNewClassLoader :Option[RoboLoader] = {
val loader = new RobolectricClassLoader(classHandler)

List(classOf[Uri__FromAndroid],
classOf[RoboSpecsWithInstrumentation],
classOf[RobolectricTestRunnerInterface],
classOf[RobolectricClassLoader],
classOf[RealObject],
classOf[ShadowWrangler],
classOf[RobolectricConfig],
classOf[R]).foreach { classToDelegate => loader.delegateLoadingOf(classToDelegate.getName) }

loader

//this line can be commented out until robolectric accepts my pull request so that there is a method to call.
//when using mixed java\scala it is likely needed if the scala tests are the first ones to run.
RobolectricTestRunner.setDefaultLoader(loader);

Some(loader)
}

}
}
4 changes: 2 additions & 2 deletions src/test/scala/InternalRoboSpecsSpecs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.github.jbrechtel.robospecs
import java.io.File
import com.xtremelabs.robolectric.{RobolectricConfig, Robolectric}

class InternalRoboSpecsSpecs extends RoboSpecs {
class InternalRoboSpecsSpecs extends RoboSpecs with RoboH2Map {
override lazy val robolectricConfig = new RobolectricConfig(new File("./src/test/emptyAndroidProject"))

"a running spec" should {
Expand All @@ -13,7 +13,7 @@ class InternalRoboSpecsSpecs extends RoboSpecs {
}
}

class InternalRoboSpecsAcceptanceSpecs extends RoboAcceptanceSpecs { def is =
class InternalRoboSpecsAcceptanceSpecs extends RoboAcceptanceSpecs with RoboH2Map { def is =

"a running spec should" ^
"have an Android application" ! e1
Expand Down