From b42cbb291f0907ea70ac3b6e37b5d580e95f45aa Mon Sep 17 00:00:00 2001 From: Anton Malinskiy Date: Tue, 5 Sep 2023 17:55:31 +1000 Subject: [PATCH] feat(android): support boot timeout + fix android device borrowing when devices are not ready + deviceInitializationTimeoutMillis should work all retries for remote parsing, not individual --- .../vendor/android/TimeoutConfiguration.kt | 1 + .../kotlin/com/malinskiy/marathon/Marathon.kt | 8 ++-- .../marathon/android/BaseAndroidDevice.kt | 38 ++++++------------- .../android/adam/AdamDeviceProvider.kt | 4 +- 4 files changed, 19 insertions(+), 32 deletions(-) diff --git a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/TimeoutConfiguration.kt b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/TimeoutConfiguration.kt index 9f1410ffb..3216adf4a 100644 --- a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/TimeoutConfiguration.kt +++ b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/TimeoutConfiguration.kt @@ -14,4 +14,5 @@ data class TimeoutConfiguration( var screencapturer: Duration = Duration.ofMillis(300), var socketIdleTimeout: Duration = Duration.ofSeconds(30), var portForward: Duration = shell, + var boot: Duration = Duration.ofSeconds(30), ) diff --git a/core/src/main/kotlin/com/malinskiy/marathon/Marathon.kt b/core/src/main/kotlin/com/malinskiy/marathon/Marathon.kt index 87813b1ae..933c3b09b 100644 --- a/core/src/main/kotlin/com/malinskiy/marathon/Marathon.kt +++ b/core/src/main/kotlin/com/malinskiy/marathon/Marathon.kt @@ -112,12 +112,12 @@ class Marathon( parsedAllTests = when (testParser) { is LocalTestParser -> testParser.extract() is RemoteTestParser<*> -> { - withRetry(3, 0) { - withTimeoutOrNull(configuration.deviceInitializationTimeoutMillis) { + withTimeoutOrNull(configuration.deviceInitializationTimeoutMillis) { + withRetry(3, 0) { val borrowedDevice = deviceProvider.borrow() testParser.extract(borrowedDevice) - } ?: throw NoDevicesException("Timed out waiting for a temporary device for remote test parsing") - } + } + } ?: throw NoDevicesException("Timed out waiting for a temporary device for remote test parsing") } else -> { diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/BaseAndroidDevice.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/BaseAndroidDevice.kt index 397345cc0..a403347b5 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/BaseAndroidDevice.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/BaseAndroidDevice.kt @@ -38,7 +38,6 @@ import com.malinskiy.marathon.time.Timer import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive import java.util.UUID import kotlin.system.measureTimeMillis @@ -79,11 +78,12 @@ abstract class BaseAndroidDevice( get() = initialRotation override suspend fun setup() { - booted = waitForBoot() - if(!booted) { - throw DeviceSetupException("Unable to configure device $serialNumber: not booted") - } - + booted = false + withTimeoutOrNull(androidConfiguration.timeoutConfiguration.boot) { + waitForBoot() + booted = true + } ?: throw DeviceSetupException("Unable to configure device $serialNumber: not booted") + abi = getProperty("ro.product.cpu.abi") ?: abi val sdk = getProperty("ro.build.version.sdk") @@ -215,28 +215,14 @@ abstract class BaseAndroidDevice( return safeExecuteShellCommand("ls $path")?.exitCode == 0 } - private suspend fun waitForBoot(): Boolean { - var booted = false - - for (i in 1..30) { - if (getProperty("sys.boot_completed", false) != null) { - logger.debug { "Device $serialNumber booted!" } - booted = true - break - } else { - delay(1000) - logger.debug { "Device $serialNumber is still booting..." } - } - - if (Thread.interrupted() || !isActive) { - booted = true - break - } + private suspend fun waitForBoot() { + while (getProperty("sys.boot_completed", false) == null) { + logger.debug { "Device $serialNumber is still booting..." } + delay(1000) } - - return booted + logger.debug { "Device $serialNumber booted!" } } - + fun isLocalEmulator() = adbSerial.startsWith("emulator-") protected suspend fun AndroidDevice.isEmulator(): Boolean = when { diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamDeviceProvider.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamDeviceProvider.kt index 3ec94d514..c8c8f242d 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamDeviceProvider.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamDeviceProvider.kt @@ -179,10 +179,10 @@ class AdamDeviceProvider( } override suspend fun borrow(): AdamAndroidDevice { - var availableDevices = devices.filter { it.value.setupJob.isCompleted } + var availableDevices = devices.filter { it.value.setupJob.isCompleted && !it.value.setupJob.isCancelled } while(availableDevices.isEmpty()) { delay(200) - availableDevices = devices.filter { it.value.setupJob.isCompleted } + availableDevices = devices.filter { it.value.setupJob.isCompleted && !it.value.setupJob.isCancelled } } return availableDevices.values.random().device }