Skip to content

Commit cfab1f4

Browse files
committed
Merge branch 'fix/ios-prepare-reconnect' into feature/apple
2 parents 3d48f99 + 35a0d61 commit cfab1f4

File tree

12 files changed

+162
-108
lines changed

12 files changed

+162
-108
lines changed

core/src/main/kotlin/com/malinskiy/marathon/device/DeviceProvider.kt

-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ interface DeviceProvider {
88
class DeviceDisconnected(val device: Device) : DeviceEvent()
99
}
1010

11-
/**
12-
* Informational for scheduler, device providers should not impose timeouts internally
13-
*/
14-
val deviceInitializationTimeoutMillis: Long
1511
suspend fun initialize()
1612

1713
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.malinskiy.marathon.exceptions
2+
3+
/**
4+
* Indicates that the device cannot be utilized for test run
5+
*/
6+
class IncompatibleDeviceException : RuntimeException {
7+
constructor(message: String) : super(message)
8+
constructor(message: String, cause: Throwable) : super(message, cause)
9+
10+
constructor(cause: Throwable) : super(cause)
11+
}

core/src/main/kotlin/com/malinskiy/marathon/execution/Scheduler.kt

+1-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import com.malinskiy.marathon.device.DevicePoolId
88
import com.malinskiy.marathon.device.DeviceProvider
99
import com.malinskiy.marathon.device.toDeviceInfo
1010
import com.malinskiy.marathon.exceptions.NoDevicesException
11-
import com.malinskiy.marathon.execution.DevicePoolMessage.FromScheduler
1211
import com.malinskiy.marathon.execution.DevicePoolMessage.FromScheduler.AddDevice
1312
import com.malinskiy.marathon.execution.DevicePoolMessage.FromScheduler.RemoveDevice
1413
import com.malinskiy.marathon.execution.bundle.TestBundleIdentifier
@@ -20,7 +19,6 @@ import kotlinx.coroutines.CoroutineScope
2019
import kotlinx.coroutines.Job
2120
import kotlinx.coroutines.TimeoutCancellationException
2221
import kotlinx.coroutines.cancelAndJoin
23-
import kotlinx.coroutines.channels.SendChannel
2422
import kotlinx.coroutines.delay
2523
import kotlinx.coroutines.launch
2624
import kotlinx.coroutines.withTimeout
@@ -56,7 +54,7 @@ class Scheduler(
5654
suspend fun execute() : Boolean {
5755
subscribeOnDevices(job)
5856
try {
59-
withTimeout(deviceProvider.deviceInitializationTimeoutMillis) {
57+
withTimeout(configuration.deviceInitializationTimeoutMillis) {
6058
while (pools.isEmpty()) {
6159
delay(100)
6260
}

vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamDeviceProvider.kt

-8
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,9 @@ class AdamDeviceProvider(
5252

5353
private val channel: Channel<DeviceProvider.DeviceEvent> = unboundedChannel()
5454
override val coroutineContext: CoroutineContext by lazy { newFixedThreadPoolContext(1, "DeviceMonitor") }
55-
private val adbCommunicationContext: CoroutineContext by lazy {
56-
newFixedThreadPoolContext(
57-
vendorConfiguration.threadingConfiguration.adbIoThreads,
58-
"AdbIOThreadPool"
59-
)
60-
}
6155
private val setupSupervisor = SupervisorJob()
6256
private var providerJob: Job? = null
6357

64-
override val deviceInitializationTimeoutMillis: Long = configuration.deviceInitializationTimeoutMillis
65-
6658
private lateinit var clients: List<AndroidDebugBridgeClient>
6759
private val socketFactory = VertxSocketFactory(idleTimeout = vendorConfiguration.timeoutConfiguration.socketIdleTimeout.toMillis())
6860
private val logcatManager: LogcatManager = LogcatManager()

vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/AppleDevice.kt

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.malinskiy.marathon.apple
22

33
import com.malinskiy.marathon.apple.bin.AppleBinaryEnvironment
44
import com.malinskiy.marathon.apple.cmd.CommandResult
5+
import com.malinskiy.marathon.apple.configuration.Transport
56
import com.malinskiy.marathon.apple.model.Arch
67
import com.malinskiy.marathon.apple.model.Sdk
78
import com.malinskiy.marathon.apple.test.TestEvent
@@ -18,6 +19,7 @@ interface AppleDevice : Device, Screenshottable, LogProducer {
1819
val udid: String
1920
val remoteFileManager: RemoteFileManager
2021
val storagePath: String
22+
val transport: Transport
2123

2224
val arch: Arch
2325
get() = when {

vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/xctestrun/TestRootFactory.kt

+20-13
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,14 @@ class TestRootFactory(
6262
val bundleSupportedPlatforms = bundle.undocumented.bundleSupportedPlatforms
6363
val platform = device.sdk.platformName
6464
if (!bundleSupportedPlatforms.contains(platform)) {
65-
throw DeviceLostException(
66-
DeviceFailureException(
67-
reason = DeviceFailureReason.IncompatibleDevice,
68-
message = "Device ${device.serialNumber} with platform $platform " +
69-
"is incompatible with bundle's supported platforms [${
70-
bundleSupportedPlatforms.joinToString(
71-
","
72-
)
73-
}]"
74-
)
65+
throw DeviceFailureException(
66+
reason = DeviceFailureReason.IncompatibleDevice,
67+
message = "Device ${device.serialNumber} with platform $platform " +
68+
"is incompatible with bundle's supported platforms [${
69+
bundleSupportedPlatforms.joinToString(
70+
","
71+
)
72+
}]"
7573
)
7674
}
7775
}
@@ -91,7 +89,11 @@ class TestRootFactory(
9189
val testRunnerApp = generateTestRunnerApp(testRoot, platformLibraryPath, bundle)
9290

9391
val runnerPlugins = when (device.sdk) {
94-
Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION , Sdk.VISION_SIMULATOR -> remoteFileManager.joinPath(testRunnerApp, "PlugIns")
92+
Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION, Sdk.VISION_SIMULATOR -> remoteFileManager.joinPath(
93+
testRunnerApp,
94+
"PlugIns"
95+
)
96+
9597
Sdk.MACOS -> remoteFileManager.joinPath(testRunnerApp, "Contents", "PlugIns")
9698
}
9799
remoteFileManager.createRemoteDirectory(runnerPlugins)
@@ -310,7 +312,11 @@ class TestRootFactory(
310312
matchArchitectures(remoteTestBinary, testRunnerBinary)
311313

312314
val plist = when (device.sdk) {
313-
Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION , Sdk.VISION_SIMULATOR -> joinPath(testRunnerApp, "Info.plist")
315+
Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION, Sdk.VISION_SIMULATOR -> joinPath(
316+
testRunnerApp,
317+
"Info.plist"
318+
)
319+
314320
Sdk.MACOS -> joinPath(testRunnerApp, "Contents", "Info.plist")
315321
}
316322

@@ -333,7 +339,8 @@ class TestRootFactory(
333339
val testRunnerApp = joinPath(testRoot, "$runnerBinaryName.app")
334340
device.remoteFileManager.copy(sharedTestRunnerApp, testRunnerApp)
335341

336-
val testRunnerBinary = device.remoteFileManager.joinPath(sharedTestRunnerApp, *bundle.relativeBinaryPath, bundle.testRunnerBinary.name)
342+
val testRunnerBinary =
343+
device.remoteFileManager.joinPath(sharedTestRunnerApp, *bundle.relativeBinaryPath, bundle.testRunnerBinary.name)
337344
val relativePath = bundle.xctestBundle.relativePathTo(testApplication).split(File.separator)
338345
val remoteXctest = device.remoteFileManager.joinPath(testRunnerApp, *relativePath.toTypedArray())
339346
val remoteTestBinary = joinPath(

vendor/vendor-apple/ios/src/main/kotlin/com/malinskiy/marathon/apple/ios/AppleSimulatorDevice.kt

+82-68
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.malinskiy.marathon.apple.bin.xcrun.simctl.service.ApplicationService
1111
import com.malinskiy.marathon.apple.cmd.CommandExecutor
1212
import com.malinskiy.marathon.apple.cmd.CommandResult
1313
import com.malinskiy.marathon.apple.cmd.FileBridge
14+
import com.malinskiy.marathon.apple.configuration.Transport
1415
import com.malinskiy.marathon.apple.extensions.bundleConfiguration
1516
import com.malinskiy.marathon.apple.ios.listener.DataContainerClearListener
1617
import com.malinskiy.marathon.apple.listener.AppleTestRunListener
@@ -24,6 +25,7 @@ import com.malinskiy.marathon.apple.ios.listener.video.ScreenRecordingListener
2425
import com.malinskiy.marathon.apple.model.Sdk
2526
import com.malinskiy.marathon.apple.ios.model.Simulator
2627
import com.malinskiy.marathon.apple.logparser.parser.DeviceFailureException
28+
import com.malinskiy.marathon.apple.logparser.parser.DeviceFailureReason
2729
import com.malinskiy.marathon.apple.logparser.parser.DiagnosticLogsPathFinder
2830
import com.malinskiy.marathon.apple.logparser.parser.SessionResultsPathFinder
2931
import com.malinskiy.marathon.apple.model.AppleTestBundle
@@ -45,6 +47,7 @@ import com.malinskiy.marathon.device.screenshot.Rotation
4547
import com.malinskiy.marathon.device.toDeviceInfo
4648
import com.malinskiy.marathon.exceptions.DeviceLostException
4749
import com.malinskiy.marathon.exceptions.DeviceSetupException
50+
import com.malinskiy.marathon.exceptions.IncompatibleDeviceException
4851
import com.malinskiy.marathon.execution.TestBatchResults
4952
import com.malinskiy.marathon.execution.listener.LineListener
5053
import com.malinskiy.marathon.execution.listener.LogListener
@@ -76,11 +79,13 @@ import java.awt.image.BufferedImage
7679
import java.io.File
7780
import java.time.Duration
7881
import java.util.concurrent.CopyOnWriteArrayList
82+
import java.util.concurrent.RejectedExecutionException
7983
import javax.imageio.ImageIO
8084
import kotlin.coroutines.CoroutineContext
8185

8286
class AppleSimulatorDevice(
8387
override val udid: String,
88+
override val transport: Transport,
8489
override var sdk: Sdk,
8590
override val binaryEnvironment: AppleBinaryEnvironment,
8691
private val testBundleIdentifier: AppleTestBundleIdentifier,
@@ -134,53 +139,55 @@ class AppleSimulatorDevice(
134139
* Called only once per device's lifetime
135140
*/
136141
override suspend fun setup() {
137-
val simctlDevices = binaryEnvironment.xcrun.simctl.device.listDevices()
138-
val simctlDevice = simctlDevices.find { it.udid == udid } ?: throw DeviceSetupException("simulator $udid not found")
139-
env = fetchEnvvars()
142+
withContext(coroutineContext) {
143+
val simctlDevices = binaryEnvironment.xcrun.simctl.device.listDevices()
144+
val simctlDevice = simctlDevices.find { it.udid == udid } ?: throw DeviceSetupException("simulator $udid not found")
145+
env = fetchEnvvars()
146+
147+
xcodeVersion = binaryEnvironment.xcrun.xcodebuild.getVersion()
148+
149+
home = env["HOME"]
150+
?: binaryEnvironment.xcrun.simctl.simulator.getenv(udid, "SIMULATOR_HOST_HOME")
151+
?: ""
152+
if (home.isBlank()) {
153+
throw DeviceSetupException("simulator $udid: invalid value $home for environment variable HOME")
154+
}
155+
val logDirectory = simctlDevice.logPath ?: "$home/Library/Developer/CoreSimulator/Devices/$udid"
156+
logFile = "$logDirectory/system.log"
157+
rootPath = binaryEnvironment.xcrun.simctl.simulator.getenv(udid, "HOME")?.let {
158+
if (it.endsWith("/data")) {
159+
it.substringBefore("/data")
160+
} else {
161+
it
162+
}
163+
} ?: "$home/Library/Developer/CoreSimulator/Devices/$udid"
140164

141-
xcodeVersion = binaryEnvironment.xcrun.xcodebuild.getVersion()
165+
devicePlistPath = "$rootPath/device.plist"
166+
fetchDeviceDescriptor()
142167

143-
home = env["HOME"]
144-
?: binaryEnvironment.xcrun.simctl.simulator.getenv(udid, "SIMULATOR_HOST_HOME")
145-
?: ""
146-
if (home.isBlank()) {
147-
throw DeviceSetupException("simulator $udid: invalid value $home for environment variable HOME")
148-
}
149-
val logDirectory = simctlDevice.logPath ?: "$home/Library/Developer/CoreSimulator/Devices/$udid"
150-
logFile = "$logDirectory/system.log"
151-
rootPath = binaryEnvironment.xcrun.simctl.simulator.getenv(udid, "HOME")?.let {
152-
if (it.endsWith("/data")) {
153-
it.substringBefore("/data")
154-
} else {
155-
it
156-
}
157-
} ?: "$home/Library/Developer/CoreSimulator/Devices/$udid"
158-
159-
devicePlistPath = "$rootPath/device.plist"
160-
fetchDeviceDescriptor()
161-
162-
deviceType = simctlDevice.deviceTypeIdentifier
163-
?: getDeviceProperty<String>("deviceType")?.trim()
164-
?: throw DeviceSetupException("simulator $udid: unable to detect deviceType")
165-
166-
model = getSimpleEnvProperty("SIMULATOR_MODEL_IDENTIFIER", deviceType)
167-
manufacturer = "Apple"
168-
runtimeBuildVersion = getSimpleEnvProperty("SIMULATOR_RUNTIME_BUILD_VERSION")
169-
runtimeVersion = getSimpleEnvProperty("SIMULATOR_RUNTIME_VERSION")
170-
version = getSimpleEnvProperty("SIMULATOR_VERSION_INFO")
171-
abi = executeWorkerCommand(listOf("uname", "-m"))?.let {
172-
if (it.successful) {
173-
it.combinedStdout.trim()
174-
} else {
175-
null
176-
}
177-
} ?: "Unknown"
168+
deviceType = simctlDevice.deviceTypeIdentifier
169+
?: getDeviceProperty<String>("deviceType")?.trim()
170+
?: throw DeviceSetupException("simulator $udid: unable to detect deviceType")
171+
172+
model = getSimpleEnvProperty("SIMULATOR_MODEL_IDENTIFIER", deviceType)
173+
manufacturer = "Apple"
174+
runtimeBuildVersion = getSimpleEnvProperty("SIMULATOR_RUNTIME_BUILD_VERSION")
175+
runtimeVersion = getSimpleEnvProperty("SIMULATOR_RUNTIME_VERSION")
176+
version = getSimpleEnvProperty("SIMULATOR_VERSION_INFO")
177+
abi = executeWorkerCommand(listOf("uname", "-m"))?.let {
178+
if (it.successful) {
179+
it.combinedStdout.trim()
180+
} else {
181+
null
182+
}
183+
} ?: "Unknown"
178184

179-
deviceFeatures = detectFeatures()
185+
deviceFeatures = detectFeatures()
186+
}
180187
}
181188

182189
override suspend fun prepare(configuration: Configuration) {
183-
async(CoroutineName("prepare $serialNumber")) {
190+
async(coroutineContext + CoroutineName("prepare $serialNumber")) {
184191
supervisorScope {
185192
track.trackDevicePreparing(this@AppleSimulatorDevice) {
186193
remoteFileManager.removeRemoteDirectory()
@@ -240,7 +247,7 @@ class AppleSimulatorDevice(
240247
deferred: CompletableDeferred<TestBatchResults>
241248
) {
242249
try {
243-
async(CoroutineName("execute $serialNumber")) {
250+
async(coroutineContext + CoroutineName("execute $serialNumber")) {
244251
supervisorScope {
245252
var executionLineListeners = setOf<LineListener>()
246253
try {
@@ -266,7 +273,13 @@ class AppleSimulatorDevice(
266273
} catch (e: IllegalStateException) {
267274
throw DeviceLostException(e)
268275
} catch (e: DeviceFailureException) {
269-
throw DeviceLostException(e)
276+
when (e.reason) {
277+
DeviceFailureReason.InvalidSimulatorIdentifier, DeviceFailureReason.IncompatibleDevice -> throw IncompatibleDeviceException(
278+
e
279+
)
280+
281+
else -> throw DeviceLostException(e)
282+
}
270283
}
271284
}
272285

@@ -348,40 +361,41 @@ class AppleSimulatorDevice(
348361

349362
override suspend fun executeTestRequest(request: TestRequest): ReceiveChannel<List<TestEvent>> {
350363
return produce {
351-
binaryEnvironment.xcrun.xcodebuild.testWithoutBuilding(udid, sdk, request, vendorConfiguration.xcodebuildTestArgs).use { session ->
352-
withContext(Dispatchers.IO) {
353-
val deferredStdout = supervisorScope {
354-
async {
355-
val testEventProducer =
356-
com.malinskiy.marathon.apple.logparser.XctestEventProducer(request.testTargetName ?: "", timer)
357-
for (line in session.stdout) {
358-
testEventProducer.process(line)?.let {
359-
send(it)
364+
binaryEnvironment.xcrun.xcodebuild.testWithoutBuilding(udid, sdk, request, vendorConfiguration.xcodebuildTestArgs)
365+
.use { session ->
366+
withContext(Dispatchers.IO) {
367+
val deferredStdout = supervisorScope {
368+
async {
369+
val testEventProducer =
370+
com.malinskiy.marathon.apple.logparser.XctestEventProducer(request.testTargetName ?: "", timer)
371+
for (line in session.stdout) {
372+
testEventProducer.process(line)?.let {
373+
send(it)
374+
}
375+
lineListeners.forEach { it.onLine(line) }
360376
}
361-
lineListeners.forEach { it.onLine(line) }
362377
}
363378
}
364-
}
365379

366-
val deferredStderr = supervisorScope {
367-
async {
368-
for (line in session.stderr) {
369-
if (line.trim().isNotBlank()) {
370-
logger.error { "simulator $udid: stderr=$line" }
380+
val deferredStderr = supervisorScope {
381+
async {
382+
for (line in session.stderr) {
383+
if (line.trim().isNotBlank()) {
384+
logger.error { "simulator $udid: stderr=$line" }
385+
}
371386
}
372387
}
373388
}
374-
}
375389

376-
deferredStderr.await()
377-
deferredStdout.await()
378-
val exitCode = session.exitCode.await()
379-
// 70 = no devices
380-
// 65 = ** TEST EXECUTE FAILED **: crash
381-
logger.debug { "Finished test batch execution with exit status $exitCode" }
382-
close()
390+
deferredStderr.await()
391+
deferredStdout.await()
392+
val exitCode = session.exitCode.await()
393+
// 70 = no devices
394+
// 65 = ** TEST EXECUTE FAILED **: crash
395+
logger.debug { "Finished test batch execution with exit status $exitCode" }
396+
close()
397+
}
383398
}
384-
}
385399
}
386400
}
387401

0 commit comments

Comments
 (0)