diff --git a/swarmer/src/main/kotlin/com/gojuno/swarmer/Emulators.kt b/swarmer/src/main/kotlin/com/gojuno/swarmer/Emulators.kt index fdf26ba..6da5884 100644 --- a/swarmer/src/main/kotlin/com/gojuno/swarmer/Emulators.kt +++ b/swarmer/src/main/kotlin/com/gojuno/swarmer/Emulators.kt @@ -12,10 +12,10 @@ import rx.schedulers.Schedulers import rx.schedulers.Schedulers.io import java.io.File import java.lang.System.nanoTime -import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit.* import java.util.concurrent.TimeoutException +import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicLong val sh: String = "/bin/sh" @@ -33,18 +33,14 @@ fun startEmulators( connectedAdbDevices: () -> Observable> = ::connectedAdbDevices, createAvd: (args: Commands.Start) -> Observable = ::createAvd, applyConfig: (args: Commands.Start) -> Observable = ::applyConfig, - emulator: (args: Commands.Start) -> String = ::emulatorBinary, - findAvailablePortsForNewEmulator: () -> Observable> = ::findAvailablePortsForNewEmulator, + emulatorCmd: (args: Commands.Start) -> String = ::emulatorBinary, + findAvailablePortsForNewEmulator: () -> Observable> = {findAvailablePortsForNewEmulator(connectedAdbDevices)}, startEmulatorProcess: (List, Commands.Start) -> Observable = ::startEmulatorProcess, waitForEmulatorToStart: (Commands.Start, () -> Observable>, Observable, Pair) -> Observable = ::waitForEmulatorToStart, waitForEmulatorToFinishBoot: (Emulator, Commands.Start) -> Observable = ::waitForEmulatorToFinishBoot ) { val startTime = System.nanoTime() - // Sometimes on Linux "emulator -verbose -avd" does not print serial id of started emulator, - // so by allocating ports manually we know which serial id emulator will have. - val availablePortsSemaphore = Semaphore(1) - val startedEmulators = connectedAdbDevices() .doOnNext { log("Already running emulators: $it") } .flatMap { @@ -54,12 +50,11 @@ fun startEmulators( args = command, createAvd = createAvd, applyConfig = applyConfig, - availablePortsSemaphore = availablePortsSemaphore, findAvailablePortsForNewEmulator = findAvailablePortsForNewEmulator, startEmulatorProcess = startEmulatorProcess, waitForEmulatorToStart = waitForEmulatorToStart, connectedAdbDevices = connectedAdbDevices, - emulator = emulator, + emulatorCmd = emulatorCmd, waitForEmulatorToFinishBoot = waitForEmulatorToFinishBoot ) } @@ -87,29 +82,26 @@ private fun startEmulator( args: Commands.Start, createAvd: (args: Commands.Start) -> Observable, applyConfig: (args: Commands.Start) -> Observable, - availablePortsSemaphore: Semaphore, findAvailablePortsForNewEmulator: () -> Observable>, startEmulatorProcess: (List, Commands.Start) -> Observable, waitForEmulatorToStart: (Commands.Start, () -> Observable>, Observable, Pair) -> Observable, connectedAdbDevices: () -> Observable> = ::connectedAdbDevices, - emulator: (Commands.Start) -> String, + emulatorCmd: (Commands.Start) -> String, waitForEmulatorToFinishBoot: (Emulator, Commands.Start) -> Observable ): Observable = createAvd(args) .flatMap { applyConfig(args) } - .map { availablePortsSemaphore.acquire() } .flatMap { findAvailablePortsForNewEmulator() } .doOnNext { log("Ports for emulator ${args.emulatorName}: ${it.first}, ${it.second}.") } .flatMap { ports -> startEmulatorProcess( // Unix only, PR welcome. - listOf(sh, "-c", "${emulator(args)} ${if (args.verbose) "-verbose" else ""} -avd ${args.emulatorName} -ports ${ports.first},${ports.second} ${args.emulatorStartOptions.joinToString(" ")} &"), + listOf(sh, "-c", "${emulatorCmd(args)} ${if (args.verbose) "-verbose" else ""} -avd ${args.emulatorName} -ports ${ports.first},${ports.second} ${args.emulatorStartOptions.joinToString(" ")} &"), args ).let { process -> waitForEmulatorToStart(args, connectedAdbDevices, process, ports) } } - .map { emulator -> availablePortsSemaphore.release().let { emulator } } .flatMap { emulator -> when (args.redirectLogcatTo) { null -> Observable.just(emulator) @@ -233,18 +225,23 @@ private fun emulatorBinary(args: Commands.Start): String = emulator } -private fun findAvailablePortsForNewEmulator(): Observable> = connectedAdbDevices() +private val assignedPortsMax: AtomicInteger = AtomicInteger(5552) +/** + * Sometimes on Linux "emulator -verbose -avd" does not print serial id of started emulator, + * so by allocating ports manually we know which serial id emulator will have. + */ +internal fun findAvailablePortsForNewEmulator(connectedAdbDevices:() -> Observable>): Observable> = + connectedAdbDevices() .map { it.filter { it.isEmulator } } .map { - if (it.isEmpty()) { - 5554 - } else { - it - .map { it.id } - .map { it.substringAfter("emulator-") } - .map { it.toInt() } - .max()!! + 2 - } + it.map { it.id } + .map { it.substringAfter("emulator-") } + .map { it.toInt() }.max() ?: 5552 + .let { runningEmulatorPortsMax -> + assignedPortsMax.updateAndGet { portMax -> + maxOf(runningEmulatorPortsMax, portMax) + 2 + } + } } .map { it to it + 1 } diff --git a/swarmer/src/test/kotlin/com/gojuno/swarmer/EmulatorsSpec.kt b/swarmer/src/test/kotlin/com/gojuno/swarmer/EmulatorsSpec.kt index e863a08..0d75795 100644 --- a/swarmer/src/test/kotlin/com/gojuno/swarmer/EmulatorsSpec.kt +++ b/swarmer/src/test/kotlin/com/gojuno/swarmer/EmulatorsSpec.kt @@ -8,12 +8,14 @@ import com.nhaarman.mockito_kotlin.argumentCaptor import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.verify import com.nhaarman.mockito_kotlin.whenever +import org.assertj.core.api.Assertions.assertThat import org.jetbrains.spek.api.Spek import org.jetbrains.spek.api.dsl.describe import org.jetbrains.spek.api.dsl.it import rx.Completable import rx.Observable import rx.Single +import rx.schedulers.Schedulers import java.io.File import java.util.concurrent.TimeUnit @@ -93,9 +95,10 @@ class EmulatorsSpec : Spek({ ) ) - val EMULATOR_PORTS = Pair(12, 34) - describe("start emulators command called") { + + val EMULATOR_PORTS = Pair(12, 34) + val connectedAdbDevices by memoized { { Observable.just(emptySet()) } } @@ -167,7 +170,7 @@ class EmulatorsSpec : Spek({ connectedAdbDevices = connectedAdbDevices, createAvd = createAvd, applyConfig = applyConfig, - emulator = emulator, + emulatorCmd = emulator, startEmulatorProcess = startEmulatorsProcess, waitForEmulatorToStart = waitForEmulatorToStart, findAvailablePortsForNewEmulator = findAvailablePortsForNewEmulator, @@ -187,4 +190,25 @@ class EmulatorsSpec : Spek({ } } } + + describe("find available ports for new emulator") { + + val connectedAdbDevices by memoized { + { Observable.just(emptySet()) } + } + + it("does not return duplicated ports") { + + + val testSubscriber = Observable.from(1..100) + .flatMap { + findAvailablePortsForNewEmulator(connectedAdbDevices) + .subscribeOn(Schedulers.io()) + }.test().awaitTerminalEvent() + val allocatedPorts = testSubscriber.onNextEvents + + assertThat(allocatedPorts).doesNotHaveDuplicates() + println("Allocated ports: $allocatedPorts") + } + } }) \ No newline at end of file