Skip to content
This repository has been archived by the owner on Dec 7, 2019. It is now read-only.

Commit

Permalink
-verbose flag is no more set by default. (#30)
Browse files Browse the repository at this point in the history
* -verbose flag is no more set by default.

* Review fixes.
  • Loading branch information
yunikkk authored Aug 10, 2017
1 parent 661518b commit 7920e36
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 20 deletions.
15 changes: 12 additions & 3 deletions swarmer/src/main/kotlin/com/gojuno/swarmer/Args.kt
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,16 @@ sealed class Commands {
description = "Path either relative or absolute to the file that will be used to redirect logcat of started emulator to. No redirection will happen if parameter is not presented.",
order = 7
)
var redirectLogcatTo: String? = null
var redirectLogcatTo: String? = null,

@Parameter(
names = arrayOf("--verbose-emulator"),
required = false,
description = "Print verbose emulator initialization messages.",
order = 8
)
var verbose: Boolean = false

) : Commands()

@Parameters(
Expand Down Expand Up @@ -119,9 +128,9 @@ fun parseStartArguments(rawArgs: List<String>): List<Commands.Start> =
}
}
}
.map {
.map { args ->
Commands.Start().also { command ->
JCommander(command).parse(*it.toTypedArray())
JCommander(command).parse(*args.toTypedArray())
}
}

Expand Down
65 changes: 53 additions & 12 deletions swarmer/src/main/kotlin/com/gojuno/swarmer/Emulators.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,23 @@ import java.util.concurrent.atomic.AtomicLong

val sh: String = "/bin/sh"
val avdManager: String = "$androidHome/tools/bin/avdmanager"
val emulator: String = "$androidHome/tools/emulator"

data class Emulator(
val id: String,
val name: String
)

fun startEmulators(args: List<Commands.Start>) {
fun startEmulators(
args: List<Commands.Start>,
connectedAdbDevices: () -> Observable<Set<AdbDevice>> = ::connectedAdbDevices,
createAvd: (args: Commands.Start) -> Observable<Unit> = ::createAvd,
applyConfig: (args: Commands.Start) -> Observable<Unit> = ::applyConfig,
emulator: () -> String = { "$androidHome/tools/emulator" },
findAvailablePortsForNewEmulator: () -> Observable<Pair<Int, Int>> = ::findAvailablePortsForNewEmulator,
startEmulatorProcess: (List<String>, File?) -> Observable<Notification> = ::startEmulatorProcess,
waitForEmulatorToStart: (Commands.Start, () -> Observable<Set<AdbDevice>>, Observable<Notification>, Pair<Int, Int>) -> Observable<Emulator> = ::waitForEmulatorToStart,
waitForEmulatorToFinishBoot: (Emulator) -> Observable<Emulator> = ::waitForEmulatorToFinishBoot
) {
val startTime = System.nanoTime()

// Sometimes on Linux "emulator -verbose -avd" does not print serial id of started emulator,
Expand All @@ -38,7 +47,20 @@ fun startEmulators(args: List<Commands.Start>) {
.doOnNext { log("Already running emulators: $it") }
.flatMap {
val startEmulators: List<Observable<Emulator>> = args
.map { startEmulator(it, availablePortsSemaphore) }
.map { command ->
startEmulator(
args = command,
createAvd = createAvd,
applyConfig = applyConfig,
availablePortsSemaphore = availablePortsSemaphore,
findAvailablePortsForNewEmulator = findAvailablePortsForNewEmulator,
startEmulatorProcess = startEmulatorProcess,
waitForEmulatorToStart = waitForEmulatorToStart,
connectedAdbDevices = connectedAdbDevices,
emulator = emulator,
waitForEmulatorToFinishBoot = waitForEmulatorToFinishBoot
)
}
.map { it.subscribeOn(Schedulers.io()) } // So each emulator will start in parallel.
.map { it.doOnNext { log("Emulator $it is ready.") } }

Expand All @@ -51,20 +73,34 @@ fun startEmulators(args: List<Commands.Start>) {
log("Swarmer: - \"My job is done here, took ${(System.nanoTime() - startTime).nanosAsSeconds()} seconds, startedEmulators: $startedEmulators, bye bye.\"")
}

private fun startEmulator(args: Commands.Start, availablePortsSemaphore: Semaphore): Observable<Emulator> =
private fun startEmulatorProcess(args: List<String>, redirectOutputTo: File?) =
process(args, null, redirectOutputTo)

private fun startEmulator(
args: Commands.Start,
createAvd: (args: Commands.Start) -> Observable<Unit>,
applyConfig: (args: Commands.Start) -> Observable<Unit>,
availablePortsSemaphore: Semaphore,
findAvailablePortsForNewEmulator: () -> Observable<Pair<Int, Int>>,
startEmulatorProcess: (List<String>, File?) -> Observable<Notification>,
waitForEmulatorToStart: (Commands.Start, () -> Observable<Set<AdbDevice>>, Observable<Notification>, Pair<Int, Int>) -> Observable<Emulator>,
connectedAdbDevices: () -> Observable<Set<AdbDevice>> = ::connectedAdbDevices,
emulator: () -> String,
waitForEmulatorToFinishBoot: (Emulator) -> Observable<Emulator>
): Observable<Emulator> =
createAvd(args)
.flatMap { applyConfig(args) }
.map { availablePortsSemaphore.acquire() }
.flatMap { findAvailablePortsForNewEmulator() }
.doOnNext { log("Ports for emulator ${args.emulatorName}: ${it.first}, ${it.second}.") }
.flatMap { ports ->
val emulatorProcess = process(
startEmulatorProcess(
// Unix only, PR welcome.
listOf(sh, "-c", "$emulator -verbose -avd ${args.emulatorName} -ports ${ports.first},${ports.second} ${args.emulatorStartOptions.joinToString(" ")} &"),
timeout = null,
redirectOutputTo = outputFileForEmulator(args)
)
waitForEmulatorToStart(args, emulatorProcess, ports)
listOf(sh, "-c", "${emulator()} ${if (args.verbose) "-verbose" else ""} -avd ${args.emulatorName} -ports ${ports.first},${ports.second} ${args.emulatorStartOptions.joinToString(" ")} &"),
outputFileForEmulator(args)
).let { process ->
waitForEmulatorToStart(args, connectedAdbDevices, process, ports)
}
}
.map { emulator -> availablePortsSemaphore.release().let { emulator } }
.flatMap { emulator ->
Expand All @@ -83,7 +119,7 @@ private fun startEmulator(args: Commands.Start, availablePortsSemaphore: Semapho
}
}
}
.flatMap { waitForEmulatorToFinishBoot(it) }
.flatMap(waitForEmulatorToFinishBoot)
.timeout(args.emulatorStartTimeoutSeconds, SECONDS)
.doOnError {
when (it) {
Expand Down Expand Up @@ -180,7 +216,12 @@ private fun findAvailablePortsForNewEmulator(): Observable<Pair<Int, Int>> = con
}
.map { it to it + 1 }

private fun waitForEmulatorToStart(args: Commands.Start, emulatorProcess: Observable<Notification>, ports: Pair<Int, Int>): Observable<Emulator> {
private fun waitForEmulatorToStart(
args: Commands.Start,
connectedAdbDevices: () -> Observable<Set<AdbDevice>>,
emulatorProcess: Observable<Notification>,
ports: Pair<Int, Int>
): Observable<Emulator> {
val startTime = AtomicLong()

return emulatorProcess
Expand Down
27 changes: 27 additions & 0 deletions swarmer/src/test/kotlin/com/gojuno/swarmer/ArgsSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ class ArgsSpec : Spek({
"--path-to-config-ini", "test_path_to_config_ini"
)

val OPTIONAL_ARGS = listOf(
"--emulator-start-options", "-prop option=value",
"--emulator-start-timeout-seconds", "180",
"--redirect-logcat-to", "logcat.txt",
"--verbose-emulator"
)

on("parse args with only required fields") {

val result by memoized {
Expand All @@ -30,6 +37,26 @@ class ArgsSpec : Spek({
}
}

on("parse args with all fields") {

val result by memoized {
parseStartArguments(listOf("start") + REQUIRED_ARGS + OPTIONAL_ARGS)
}

it("parses passed args and uses default values for non-required fields") {
assertThat(result).isEqualTo(listOf(Commands.Start(
emulatorName = "test_emulator_name",
pakage = "test_android_package",
androidAbi = "test_android_abi",
pathToConfigIni = "test_path_to_config_ini",
emulatorStartOptions = listOf("-prop option=value"),
emulatorStartTimeoutSeconds = 180L,
redirectLogcatTo = "logcat.txt",
verbose = true
)))
}
}

on("parse multiple args") {

val result by memoized {
Expand Down
135 changes: 130 additions & 5 deletions swarmer/src/test/kotlin/com/gojuno/swarmer/EmulatorsSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package com.gojuno.swarmer

import com.gojuno.commander.android.AdbDevice
import com.gojuno.commander.android.adb
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import com.gojuno.commander.os.Notification
import com.nhaarman.mockito_kotlin.*
import org.jetbrains.spek.api.Spek
import org.jetbrains.spek.api.dsl.describe
import org.jetbrains.spek.api.dsl.it
import org.mockito.Mockito
import rx.Completable
import rx.Observable
import rx.Single
import java.io.File
import java.util.concurrent.TimeUnit

class EmulatorsSpec : Spek({
Expand Down Expand Up @@ -46,12 +46,137 @@ class EmulatorsSpec : Spek({

ADB_DEVICES.forEach { device ->
it("should call stop command for emulator ${device.id}") {
Mockito.verify(startProcess).invoke(
verify(startProcess).invoke(
listOf(adb, "-s", device.id, "emu", "kill"),
command.timeoutSeconds to TimeUnit.SECONDS
)
}
}
}
}

val START_COMMANDS = listOf(
Commands.Start(
emulatorName = "emulator_1",
pakage = "system-images;android-25;google_apis;x86",
androidAbi = "google_apis/x86_64",
pathToConfigIni = "config.ini",
emulatorStartOptions = listOf("--no-window"),
emulatorStartTimeoutSeconds = 45L,
verbose = true
),
Commands.Start(
emulatorName = "emulator_2",
pakage = "system-images;android-25;google_apis;x86",
androidAbi = "google_apis/x86_64",
pathToConfigIni = "config2.ini",
emulatorStartOptions = listOf("--no-window"),
emulatorStartTimeoutSeconds = 45L,
verbose = true
),
Commands.Start(
emulatorName = "emulator_3",
pakage = "system-images;android-25;google_apis;x86",
androidAbi = "google_apis/x86",
pathToConfigIni = "config3.ini",
emulatorStartOptions = listOf("--no-window --some option"),
emulatorStartTimeoutSeconds = 60L,
verbose = false
)
)

val EMULATOR_PORTS = Pair(12, 34)

describe("start emulators command called") {
val connectedAdbDevices by memoized {
{ Observable.just(emptySet<AdbDevice>()) }
}

val outputFile by memoized {
File("")
}

val process by memoized {
mock<Process>()
}

val createAvd by memoized {
mock<(Commands.Start) -> Observable<Unit>>().apply {
whenever(invoke(any())).thenReturn(Observable.just(Unit))
}
}

val applyConfig by memoized {
mock<(Commands.Start) -> Observable<Unit>>().apply {
whenever(invoke(any())).thenReturn(Observable.just(Unit))
}
}

val emulator by memoized {
mock<() -> String>().apply { whenever(invoke()).thenReturn("/path/to/emulator/binary") }
}

val startEmulatorsProcess by memoized {
mock<(List<String>, File?) -> Observable<Notification>>().apply {
whenever(invoke(any(), any())).thenReturn(Observable.just(
Notification.Start(process, outputFile),
Notification.Exit(outputFile)
))
}
}

val waitForEmulatorToStart by memoized {
val commandCaptor = argumentCaptor<Commands.Start>()

mock<(Commands.Start, () -> Observable<Set<AdbDevice>>, Observable<Notification>, Pair<Int, Int>) -> Observable<Emulator>>().apply {
whenever(invoke(commandCaptor.capture(), any(), any(), any())).thenAnswer {
Observable.just(
Emulator("emulator-${EMULATOR_PORTS.first}", commandCaptor.firstValue.emulatorName)
)
}
}
}

val waitForEmulatorToFinishBoot by memoized {
val emulatorCaptor = argumentCaptor<Emulator>()

mock<(Emulator) -> Observable<Emulator>>().apply {
whenever(invoke(emulatorCaptor.capture())).thenAnswer {
Observable.just(emulatorCaptor.firstValue)
}
}
}

val findAvailablePortsForNewEmulator by memoized {
mock<() -> Observable<Pair<Int, Int>>>().apply {
whenever(invoke()).thenReturn(Observable.just(EMULATOR_PORTS))
}
}

beforeEachTest {
startEmulators(
args = START_COMMANDS,
connectedAdbDevices = connectedAdbDevices,
createAvd = createAvd,
applyConfig = applyConfig,
emulator = emulator,
startEmulatorProcess = startEmulatorsProcess,
waitForEmulatorToStart = waitForEmulatorToStart,
findAvailablePortsForNewEmulator = findAvailablePortsForNewEmulator,
waitForEmulatorToFinishBoot = waitForEmulatorToFinishBoot
)
}

START_COMMANDS.forEach { command ->
it("should start emulators") {
verify(startEmulatorsProcess).invoke(
listOf(
"/bin/sh", "-c",
"${emulator()} ${if (command.verbose) "-verbose" else ""} -avd ${command.emulatorName} -ports ${EMULATOR_PORTS.first},${EMULATOR_PORTS.second} ${command.emulatorStartOptions.joinToString(" ")} &"
),
File("${command.emulatorName}.output")
)
}
}
}
})

0 comments on commit 7920e36

Please sign in to comment.