Skip to content
This repository has been archived by the owner on Oct 26, 2022. It is now read-only.

InstrumentationRunner & test parser key #128

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions src/main/scala/AndroidDefault.scala
Expand Up @@ -34,6 +34,7 @@ object AndroidDefaults {
dxJavaOpts := DefaultDxJavaOpts,
manifestSchema := DefaultManifestSchema,
envs := DefaultEnvs,

// a list of modules which are already included in Android
preinstalledModules := Seq[ModuleID](
ModuleID("org.apache.httpcomponents", "httpcore", null),
Expand Down
11 changes: 6 additions & 5 deletions src/main/scala/AndroidHelpers.scala
@@ -1,3 +1,4 @@
import java.io.InputStream
import sbt._
import Keys._

Expand Down Expand Up @@ -25,7 +26,8 @@ object AndroidHelpers {
(manifest(mpath) \ "uses-sdk").head.attribute(schema, key).map(_.text.toInt)

def adbTask(dPath: String, emulator: Boolean, s: TaskStreams, action: String*) {
val (exit, out) = adbTaskWithOutput(dPath, emulator, s, action:_*)
val out = new StringBuffer
val exit = adbTaskWithOutput(dPath, emulator, s, action:_*) { i => out.append(IO.readStream(i)) }
if (exit != 0 ||
// adb doesn't bother returning a non-zero exit code on failure
out.toString.contains("Failure")) {
Expand All @@ -34,15 +36,14 @@ object AndroidHelpers {
} else s.log.info(out.toString)
}

def adbTaskWithOutput(dPath: String, emulator: Boolean, s: TaskStreams, action: String*) = {
def adbTaskWithOutput(dPath: String, emulator: Boolean, s: TaskStreams, action: String*) (parser:InputStream => Unit) = {
val adb = Seq(dPath, if (emulator) "-e" else "-d") ++ action
s.log.debug(adb.mkString(" "))
val out = new StringBuffer
val exit = adb.run(new ProcessIO(input => (),
output => out.append(IO.readStream(output)),
adb.run(new ProcessIO(input => (),
output => parser(output),
error => out.append(IO.readStream(error)))
).exitValue()
(exit, out.toString)
}

def startTask(emulator: Boolean) =
Expand Down
2 changes: 2 additions & 0 deletions src/main/scala/AndroidInstall.scala
Expand Up @@ -92,6 +92,8 @@ object AndroidInstall {
"-keep public class * extends android.content.ContentProvider" ::
"-keep public class * extends android.view.View" ::
"-keep public class * extends android.app.Application" ::
"-keep public class * extends android.app.Instrumentation" ::
"-keep public class * extends android.test.InstrumentationTestRunner" ::
"-keep public class "+manifestPackage+".** { public protected *; }" ::
"-keep public class * implements junit.framework.Test { public void test*(); }" ::
proguardOption :: Nil )
Expand Down
2 changes: 2 additions & 0 deletions src/main/scala/AndroidKeys.scala
Expand Up @@ -178,6 +178,8 @@ object AndroidKeys {
val testDevice = TaskKey[Unit]("test-device", "runs tests on device")
val testOnlyEmulator = InputKey[Unit]("test-only-emulator", "run a single test on emulator")
val testOnlyDevice = InputKey[Unit]("test-only-device", "run a single test on device")
val instrumentationRunner = SettingKey[String] ("instrumentation-runner", "instrumentation test runner e.g. android.test.InstrumentationTestRunner")
val testOutputParser = TaskKey[Option[AndroidTest.TestParser]] ("test-output-parser", "Optional test parser")

/** Github tasks & keys */
val uploadGithub = TaskKey[Option[String]]("github-upload", "Upload file to github")
Expand Down
30 changes: 20 additions & 10 deletions src/main/scala/AndroidTest.scala
@@ -1,3 +1,4 @@
import java.io.InputStream
import sbt._
import Keys._

Expand All @@ -10,20 +11,27 @@ import Cache.seqFormat
import com.android.ddmlib.testrunner.{InstrumentationResultParser,ITestRunListener}

object AndroidTest {
def instrumentationTestAction(emulator: Boolean) = (dbPath, manifestPackage, streams) map {
(dbPath, manifestPackage, s) =>
val action = Seq("shell", "am", "instrument", "-r", "-w",
manifestPackage+"/android.test.InstrumentationTestRunner")
val (exit, out) = adbTaskWithOutput(dbPath.absolutePath, emulator, s, action:_*)
if (exit == 0) parseTests(out, manifestPackage, s.log)

type TestParser = (InputStream => Unit)

val DefaultInstrumentationRunner = "android.test.InstrumentationTestRunner"

def instrumentationTestAction(emulator: Boolean) = (dbPath, manifestPackage, instrumentationRunner, testOutputParser, streams) map {
(dbPath, manifestPackage, inst, parser, s) =>
val action = Seq("shell", "am", "instrument", "-r", "-w", manifestPackage+"/" + inst)
val out = new StringBuffer
val exit = adbTaskWithOutput(dbPath.absolutePath, emulator, s, action:_*) {i => parser.map(_.apply(i)).getOrElse(out.append(IO.readStream(i))) }
if (exit == 0) parseTests(out.toString, manifestPackage, s.log)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if a parser gets specified out will always be empty thus failing tests? or the parseTests step would not be necessary.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out will be empty string and as such will not log anything - as in parseTests is unnecessary. It s the responsibility of the parser to log error. i.e. scalatest will log an error message with an instrumentation code of error. We could have something nicer of Stream[(Int, String)].

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok, in which case the parser should get passed in the constructed string and not the InputStream - then either invoking parseTests by default or delegating to the specified parser, which makes the intention a bit clearer and avoid the no-op parseTests which follows.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess there could be a possibility of checking if the setting is set prior to call parseTests. This could be cleaned up but a major refactoring on the device management would be better imo (using IDevice from ddms rather then executing ADB calls). I did not have time to fully look into it yet but playing with the idea. There would be a possibility to have something along the lines of:

executeSQL <= (devices in Android) map { d:IDevice => d.executeCommand(...) }

As such, instrumentation could be used as above and so would logging.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i agree - would be nice to make use of ddmslib for executing commands. there is already some code to initialise the debug bridge / device in AndroidDdm.scala.

else sys.error("am instrument returned error %d\n\n%s".format(exit, out))
()
}

def runSingleTest(emulator: Boolean) = (test: TaskKey[String]) => (test, dbPath, manifestPackage, streams) map { (test, dbPath, manifestPackage, s) =>
val action = Seq("shell", "am", "instrument", "-r", "-w", "-e", "class", test, manifestPackage+"/android.test.InstrumentationTestRunner")
val (exit, out) = adbTaskWithOutput(dbPath.absolutePath, emulator, s, action:_*)
if (exit == 0) parseTests(out, manifestPackage, s.log)
def runSingleTest(emulator: Boolean) = (test: TaskKey[String]) => (test, dbPath, manifestPackage, instrumentationRunner, testOutputParser,streams) map {
(test, dbPath, manifestPackage, inst,parser, s) =>
val action = Seq("shell", "am", "instrument", "-r", "-w", "-e", "class", test, manifestPackage+"/" + inst)
val out = new StringBuffer
val exit = adbTaskWithOutput(dbPath.absolutePath, emulator, s, action:_*) {i => parser.map(_.apply(i)).getOrElse(out.append(IO.readStream(i))) }
if (exit == 0) parseTests(out.toString, manifestPackage, s.log)
else sys.error("am instrument returned error %d\n\n%s".format(exit, out))
()
}
Expand Down Expand Up @@ -58,6 +66,8 @@ object AndroidTest {
AndroidBase.settings ++
AndroidInstall.settings ++
inConfig(Android) (Seq (
instrumentationRunner := DefaultInstrumentationRunner,
testOutputParser := None,
testEmulator <<= instrumentationTestAction(true),
testDevice <<= instrumentationTestAction(false),
testOnlyEmulator <<= InputTask(loadForParser(definedTestNames in Test)( (s, i) => testParser(s, i getOrElse Nil))) { test =>
Expand Down