diff --git a/build.gradle.kts b/build.gradle.kts index df45c8ce5..eb3fc11a1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,8 +16,8 @@ buildscript { plugins { - id("io.gitlab.arturbosch.detekt") version "1.23.1" - id("com.github.ben-manes.versions") version "0.47.0" + id("io.gitlab.arturbosch.detekt") version "1.23.3" + id("com.github.ben-manes.versions") version "0.49.0" } configure { diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 1af8eb8de..d7066bd9e 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -7,7 +7,7 @@ repositories { } dependencies { - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10") - implementation("com.squareup:kotlinpoet:1.12.0") - implementation("com.google.code.gson:gson:2.10") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10") + implementation("com.squareup:kotlinpoet:1.14.2") + implementation("com.google.code.gson:gson:2.10.1") } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index e12f1918e..0a9a8d589 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -1,24 +1,24 @@ object Versions { val marathon = System.getenv("GIT_TAG_NAME") ?: "0.8.5" - val kotlin = "1.8.20" + val kotlin = "1.9.10" val coroutines = "1.7.3" val coroutinesTest = coroutines - val androidCommon = "31.1.0" - val adam = "0.5.1" + val androidCommon = "31.1.3" + val adam = "0.5.2" val dexTestParser = "2.3.4" val kotlinLogging = "3.0.5" - val logbackClassic = "1.4.8" + val logbackClassic = "1.4.11" val axmlParser = "1.0" - val bugsnag = "3.7.0" + val bugsnag = "3.7.1" val junitGradle = "1.2.0" - val androidGradleVersion = "7.4.1" - val gradlePluginPublish = "1.2.0" + val androidGradleVersion = "8.1.3" + val gradlePluginPublish = "1.2.1" val gradlePluginShadow = "8.1.1" - val junit5 = "5.10.0" + val junit5 = "5.10.1" val kluent = "1.73" val kakao = "3.0.2" @@ -27,37 +27,37 @@ object Versions { val espressoRunner = "1.0.1" val junit = "4.13.2" val gson = "2.10.1" - val apacheCommonsText = "1.10.0" + val apacheCommonsText = "1.11.0" val apacheCommonsIO = "2.11.0" val apacheCommonsCodec = "1.15" - val okhttp = "4.11.0" + val okhttp = "4.12.0" val influxDbClient = "2.23" val influxDb2Client = "6.10.0" - val clikt = "4.1.0" - val jacksonDatabind = "2.15.2" + val clikt = "4.2.1" + val jacksonDatabind = "2.15.3" val jacksonKotlin = jacksonDatabind val jacksonYaml = jacksonDatabind val jacksonJSR310 = jacksonDatabind val jacksonAnnotations = jacksonDatabind val ddPlist = "1.27" - val guava = "32.1.1-jre" - val rsync4j = "3.2.7-1" - val sshj = "0.35.0" + val guava = "32.1.3-jre" + val rsync4j = "3.2.7-5" + val sshj = "0.37.0" val kotlinProcess = "1.4.1" - val testContainers = "1.18.3" + val testContainers = "1.19.1" val jupiterEngine = junit5 - val jansi = "2.4.0" + val jansi = "2.4.1" val scalr = "4.2" - val allureTestFilter = "2.23.0" - val allureJava = "2.23.0" + val allureTestFilter = "2.24.0" + val allureJava = "2.24.0" val allureKotlin = "2.4.0" val allureEnvironment = "1.0.0" val mockitoKotlin = "2.2.0" - val dokka = "1.8.10" - val koin = "3.4.3" + val dokka = "1.9.10" + val koin = "3.5.0" val jsonAssert = "1.5.1" val xmlUnit = "2.9.1" - val assertk = "0.26.1" + val assertk = "0.27.0" } object BuildPlugins { diff --git a/configuration/src/main/kotlin/com/malinskiy/marathon/config/LogicalConfigurationValidator.kt b/configuration/src/main/kotlin/com/malinskiy/marathon/config/LogicalConfigurationValidator.kt index f734f7c6e..4eb016dd3 100644 --- a/configuration/src/main/kotlin/com/malinskiy/marathon/config/LogicalConfigurationValidator.kt +++ b/configuration/src/main/kotlin/com/malinskiy/marathon/config/LogicalConfigurationValidator.kt @@ -27,6 +27,9 @@ class LogicalConfigurationValidator : ConfigurationValidator { is VendorConfiguration.IOSConfiguration -> { configuration.vendorConfiguration.validate() } + is VendorConfiguration.AndroidConfiguration -> { + configuration.vendorConfiguration.validate() + } else -> Unit } diff --git a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/VendorConfiguration.kt b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/VendorConfiguration.kt index ce1c01dc0..d40e2c048 100644 --- a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/VendorConfiguration.kt +++ b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/VendorConfiguration.kt @@ -72,6 +72,26 @@ sealed class VendorConfiguration { @JsonProperty("disableWindowAnimation") val disableWindowAnimation: Boolean = DEFAULT_DISABLE_WINDOW_ANIMATION, ) : VendorConfiguration() { fun safeAndroidSdk(): File = androidSdk ?: throw ConfigurationException("No android SDK path specified") + + fun validate() { + validateFile(applicationOutput) + validateFile(testApplicationOutput) + splitApks?.forEach { validateFile(it) } + extraApplicationsOutput?.forEach { validateFile(it) } + outputs?.forEach { + validateFile(it.application) + validateFile(it.testApplication) + it.splitApks?.forEach { apk -> validateFile(apk) } + it.extraApplications?.forEach { apk -> validateFile(apk) } + } + } + + private fun validateFile(file: File?) { + if (file != null) { + if (!file.exists()) throw ConfigurationException("${file.absolutePath} does not exist") + if (!file.isFile) throw ConfigurationException("${file.absolutePath} must be a regular file") + } + } } class AndroidConfigurationBuilder { @@ -142,6 +162,7 @@ sealed class VendorConfiguration { @JsonProperty("compactOutput") val compactOutput: Boolean = false, @JsonProperty("rsync") val rsync: RsyncConfiguration = RsyncConfiguration(), @JsonProperty("xcodebuildTestArgs") val xcodebuildTestArgs: Map = emptyMap(), + @JsonProperty("dataContainerClear") val dataContainerClear: Boolean = false, @JsonProperty("testParserConfiguration") val testParserConfiguration: com.malinskiy.marathon.config.vendor.ios.TestParserConfiguration = com.malinskiy.marathon.config.vendor.ios.TestParserConfiguration.NmTestParserConfiguration(), @JsonProperty("signing") val signing: SigningConfiguration = SigningConfiguration(), diff --git a/docs/runner/ios/configure.md b/docs/runner/ios/configure.md index 179cf8977..60d9f41b4 100644 --- a/docs/runner/ios/configure.md +++ b/docs/runner/ios/configure.md @@ -357,7 +357,7 @@ timeoutConfiguration: screenshot: PT10S video: PT300S erase: PT30S - shutdown: PT30S + shutdown: PT30S delete: PT30S create: PT30S boot: PT30S @@ -454,6 +454,15 @@ rsync: remotePath: "/usr/bin/rsync-custom" ``` +### Clear state between test batch executions +By default, marathon does not clear state between test batch executions. To mitigate potential test side effects, one could add an option to +clear the container data between test runs. Keep in mind that test side effects might still be present. +If you want to isolate tests even further, then you should consider reducing the batch size. + +```yaml +dataContainerClear: true +``` + ### Test parser :::tip diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 84a0b92f9..e411586a5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sample/android-app/build.gradle.kts b/sample/android-app/build.gradle.kts index f35b81931..9cc051f14 100644 --- a/sample/android-app/build.gradle.kts +++ b/sample/android-app/build.gradle.kts @@ -10,7 +10,7 @@ buildscript { } plugins { - id("com.github.ben-manes.versions") version "0.47.0" + id("com.github.ben-manes.versions") version "0.49.0" } fun isNonStable(version: String): Boolean { diff --git a/sample/android-app/buildSrc/src/main/kotlin/Versions.kt b/sample/android-app/buildSrc/src/main/kotlin/Versions.kt index b6bf05435..2bf1081cc 100644 --- a/sample/android-app/buildSrc/src/main/kotlin/Versions.kt +++ b/sample/android-app/buildSrc/src/main/kotlin/Versions.kt @@ -1,10 +1,10 @@ object Versions { - val kotlin = "1.8.10" - val coroutines = "1.6.4" + val kotlin = "1.9.10" + val coroutines = "1.7.3" - val androidGradleVersion = "7.4.1" + val androidGradleVersion = "8.1.3" - val kakao = "3.2.3" + val kakao = "3.4.1" val espresso = "3.5.1" val espressoRules = "1.5.0" val espressoRunner = "1.5.2" @@ -13,7 +13,7 @@ object Versions { val appCompat = "1.6.1" val constraintLayout = "2.1.4" val allure = "2.4.0" - val adam = "0.5.0" + val adam = "0.5.2" } object BuildPlugins { diff --git a/sample/android-app/gradle/wrapper/gradle-wrapper.properties b/sample/android-app/gradle/wrapper/gradle-wrapper.properties index fae08049a..e411586a5 100644 --- a/sample/android-app/gradle/wrapper/gradle-wrapper.properties +++ b/sample/android-app/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sample/android-cucumber-app/app/build.gradle.kts b/sample/android-cucumber-app/app/build.gradle.kts index a1eec9818..3fd1ed4d9 100644 --- a/sample/android-cucumber-app/app/build.gradle.kts +++ b/sample/android-cucumber-app/app/build.gradle.kts @@ -5,6 +5,7 @@ plugins { } android { + namespace = "com.example.cucumber" buildToolsVersion = "30.0.3" compileSdk = 33 diff --git a/sample/android-cucumber-app/build.gradle.kts b/sample/android-cucumber-app/build.gradle.kts index f35b81931..9cc051f14 100644 --- a/sample/android-cucumber-app/build.gradle.kts +++ b/sample/android-cucumber-app/build.gradle.kts @@ -10,7 +10,7 @@ buildscript { } plugins { - id("com.github.ben-manes.versions") version "0.47.0" + id("com.github.ben-manes.versions") version "0.49.0" } fun isNonStable(version: String): Boolean { diff --git a/sample/android-cucumber-app/buildSrc/src/main/kotlin/Versions.kt b/sample/android-cucumber-app/buildSrc/src/main/kotlin/Versions.kt index f417bdf59..796775364 100644 --- a/sample/android-cucumber-app/buildSrc/src/main/kotlin/Versions.kt +++ b/sample/android-cucumber-app/buildSrc/src/main/kotlin/Versions.kt @@ -1,7 +1,7 @@ object Versions { - val kotlin = "1.8.10" + val kotlin = "1.9.10" - val androidGradleVersion = "7.4.1" + val androidGradleVersion = "8.1.3" val cucumber = "4.9.0" val cucumberPicocontainer = "4.8.1" diff --git a/sample/android-cucumber-app/gradle/wrapper/gradle-wrapper.properties b/sample/android-cucumber-app/gradle/wrapper/gradle-wrapper.properties index fae08049a..e411586a5 100644 --- a/sample/android-cucumber-app/gradle/wrapper/gradle-wrapper.properties +++ b/sample/android-cucumber-app/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sample/android-library/build.gradle.kts b/sample/android-library/build.gradle.kts index f35b81931..9cc051f14 100644 --- a/sample/android-library/build.gradle.kts +++ b/sample/android-library/build.gradle.kts @@ -10,7 +10,7 @@ buildscript { } plugins { - id("com.github.ben-manes.versions") version "0.47.0" + id("com.github.ben-manes.versions") version "0.49.0" } fun isNonStable(version: String): Boolean { diff --git a/sample/android-library/buildSrc/src/main/kotlin/Versions.kt b/sample/android-library/buildSrc/src/main/kotlin/Versions.kt index 941cd2614..5328f7cd3 100644 --- a/sample/android-library/buildSrc/src/main/kotlin/Versions.kt +++ b/sample/android-library/buildSrc/src/main/kotlin/Versions.kt @@ -1,8 +1,7 @@ object Versions { - val kotlin = "1.8.10" - val coroutines = "1.6.4" - - val androidGradleVersion = "7.4.1" + val kotlin = "1.9.10" + val coroutines = "1.7.3" + val androidGradleVersion = "8.1.3" val espressoRunner = "1.5.2" val testJunit = "1.1.5" } diff --git a/sample/android-library/gradle/wrapper/gradle-wrapper.properties b/sample/android-library/gradle/wrapper/gradle-wrapper.properties index fae08049a..e411586a5 100644 --- a/sample/android-library/gradle/wrapper/gradle-wrapper.properties +++ b/sample/android-library/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sample/android-library/library/build.gradle.kts b/sample/android-library/library/build.gradle.kts index 5feee505d..dce832af7 100644 --- a/sample/android-library/library/build.gradle.kts +++ b/sample/android-library/library/build.gradle.kts @@ -5,6 +5,7 @@ plugins { } android { + namespace = "com.example.library" buildToolsVersion = "30.0.3" compileSdk = 33 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 a403347b5..d4c502f90 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 @@ -174,16 +174,15 @@ abstract class BaseAndroidDevice( } private suspend fun detectFeatures(): List { - val hasScreenRecord = when { + val screenRecordSupport = when { !version.isGreaterOrEqualThan(19) -> false else -> hasBinary("/system/bin/screenrecord") } - val videoSupport = hasScreenRecord && manufacturer != "Genymotion" val screenshotSupport = version.isGreaterOrEqualThan(AndroidVersion.VersionCodes.JELLY_BEAN) val features = mutableListOf() - if (videoSupport) features.add(DeviceFeature.VIDEO) + if (screenRecordSupport) features.add(DeviceFeature.VIDEO) if (screenshotSupport) features.add(DeviceFeature.SCREENSHOT) return features } 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 c8c8f242d..b9268cb38 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 @@ -67,6 +67,7 @@ class AdamDeviceProvider( private val socketFactory = VertxSocketFactory(idleTimeout = vendorConfiguration.timeoutConfiguration.socketIdleTimeout.toMillis()) private val logcatManager: LogcatManager = LogcatManager() private lateinit var deviceEventsChannel: ReceiveChannel>> + private var deviceEventsChannelInitialized = false private val deviceEventsChannelMutex = Mutex() private val multiServerDeviceStateTracker = MultiServerDeviceStateTracker() @@ -132,6 +133,7 @@ class AdamDeviceProvider( } } } + deviceEventsChannelInitialized = true } for ((client, currentDeviceList) in deviceEventsChannel) { multiServerDeviceStateTracker.update(client, currentDeviceList).forEach { update -> @@ -160,6 +162,7 @@ class AdamDeviceProvider( devices[serial] = ProvidedDevice(device, job) } } + TrackingUpdate.DISCONNECTED -> { devices[serial]?.let { (device, job) -> if (job.isActive) { @@ -169,6 +172,7 @@ class AdamDeviceProvider( device.dispose() } } + TrackingUpdate.NOTHING_TO_DO -> Unit } logger.debug { "Device $serial changed state to $state" } @@ -180,7 +184,7 @@ class AdamDeviceProvider( override suspend fun borrow(): AdamAndroidDevice { var availableDevices = devices.filter { it.value.setupJob.isCompleted && !it.value.setupJob.isCancelled } - while(availableDevices.isEmpty()) { + while (availableDevices.isEmpty()) { delay(200) availableDevices = devices.filter { it.value.setupJob.isCompleted && !it.value.setupJob.isCancelled } } @@ -198,7 +202,9 @@ class AdamDeviceProvider( providerJob?.cancel() channel.close() deviceEventsChannelMutex.withLock { - deviceEventsChannel.cancel() + if (deviceEventsChannelInitialized) { + deviceEventsChannel.cancel() + } } logcatManager.close() socketFactory.close() diff --git a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleApplicationInstaller.kt b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleApplicationInstaller.kt index 359208495..68f24fd7f 100644 --- a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleApplicationInstaller.kt +++ b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleApplicationInstaller.kt @@ -6,6 +6,7 @@ import com.malinskiy.marathon.config.vendor.VendorConfiguration import com.malinskiy.marathon.config.vendor.ios.TestType import com.malinskiy.marathon.exceptions.DeviceSetupException import com.malinskiy.marathon.execution.withRetry +import com.malinskiy.marathon.ios.extensions.testBundle import com.malinskiy.marathon.ios.model.Sdk import com.malinskiy.marathon.ios.xctestrun.TestRootFactory import com.malinskiy.marathon.log.MarathonLogging @@ -18,14 +19,13 @@ class AppleApplicationInstaller( private val logger = MarathonLogging.logger {} suspend fun prepareInstallation(device: AppleSimulatorDevice, useXctestParser: Boolean = false) { - val bundle = vendorConfiguration.bundle ?: throw ConfigurationException("no xctest found for configuration") + val bundle = vendorConfiguration.testBundle() ?: throw ConfigurationException("no xctest found for configuration") - val xctest = bundle.xctest + val xctest = bundle.testApplication logger.debug { "Moving xctest to ${device.serialNumber}" } val remoteXctest = device.remoteFileManager.remoteXctestFile() withRetry(3, 1000L) { device.remoteFileManager.createRemoteDirectory() - val remoteDirectory = device.remoteFileManager.remoteDirectory() if (!device.pushFolder(xctest, remoteXctest)) { throw DeviceSetupException("Error transferring $xctest to ${device.serialNumber}") } @@ -47,7 +47,7 @@ class AppleApplicationInstaller( TestRootFactory(device, vendorConfiguration).generate(testType, bundle, useXctestParser) grantPermissions(device) - bundle.extraApplications?.forEach { + vendorConfiguration.bundle?.extraApplications?.forEach { if (it.isDirectory && it.extension == "app") { logger.debug { "Installing extra application $it to ${device.serialNumber}" } val remoteExtraApplication = device.remoteFileManager.remoteExtraApplication(it.name) diff --git a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleDeviceTestRunner.kt b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleDeviceTestRunner.kt index 2b7a67703..e2fe955f9 100644 --- a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleDeviceTestRunner.kt +++ b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleDeviceTestRunner.kt @@ -18,7 +18,7 @@ import com.malinskiy.marathon.test.toTestName import kotlinx.coroutines.CancellationException import kotlinx.coroutines.channels.ReceiveChannel -class AppleDeviceTestRunner(private val device: AppleSimulatorDevice) { +class AppleDeviceTestRunner(private val device: AppleSimulatorDevice, private val bundleIdentifier: AppleTestBundleIdentifier) { private val logger = MarathonLogging.logger {} suspend fun execute( @@ -43,7 +43,15 @@ class AppleDeviceTestRunner(private val device: AppleSimulatorDevice) { ) var channel: ReceiveChannel>? = null try { - clearData(vendorConfiguration) + if (vendorConfiguration.dataContainerClear) { + val bundleIds = rawTestBatch.tests.map { + bundleIdentifier.identify(it).appId + }.toSet() + bundleIds.forEach { + device.clearAppContainer(it) + } + } + listener.beforeTestRun() val localChannel = device.executeTestRequest(runnerRequest) @@ -77,11 +85,4 @@ class AppleDeviceTestRunner(private val device: AppleSimulatorDevice) { } } } - - private suspend fun clearData(vendorConfiguration: VendorConfiguration.IOSConfiguration) { -// if (vendorConfiguration.eraseSimulatorOnStart) { -// device.shutdown() -// device.erase() -// } - } } diff --git a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleSimulatorDevice.kt b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleSimulatorDevice.kt index 08bbcab70..4b09fb293 100644 --- a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleSimulatorDevice.kt +++ b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleSimulatorDevice.kt @@ -22,6 +22,7 @@ import com.malinskiy.marathon.execution.listener.LineListener import com.malinskiy.marathon.execution.listener.LogListener import com.malinskiy.marathon.io.FileManager import com.malinskiy.marathon.ios.bin.AppleBinaryEnvironment +import com.malinskiy.marathon.ios.bin.xcrun.simctl.service.ApplicationService import com.malinskiy.marathon.ios.cmd.CommandExecutor import com.malinskiy.marathon.ios.cmd.CommandResult import com.malinskiy.marathon.ios.cmd.FileBridge @@ -105,12 +106,13 @@ class AppleSimulatorDevice( val arch: Arch get() = when { sdk == Sdk.IPHONESIMULATOR -> { - when(abi) { + when (abi) { "x86_64" -> Arch.x86_64 "arm64" -> Arch.arm64 else -> Arch.arm64 } } + udid.contains('-') -> Arch.arm64e else -> Arch.arm64 } @@ -126,11 +128,14 @@ class AppleSimulatorDevice( private lateinit var devicePlistPath: String private var deviceDescriptor: Map<*, *>? = null private val dispatcher by lazy { - newFixedThreadPoolContext(vendorConfiguration.threadingConfiguration.deviceThreads, "AppleSimulatorDevice - execution - ${commandExecutor.host.id}") + newFixedThreadPoolContext( + vendorConfiguration.threadingConfiguration.deviceThreads, + "AppleSimulatorDevice - execution - ${commandExecutor.host.id}" + ) } override val coroutineContext: CoroutineContext = dispatcher override val remoteFileManager: RemoteFileManager = RemoteFileManager(this) - override val storagePath = "/tmp/marathon/$udid" + override val storagePath = "$SHARED_PATH/$udid" private lateinit var xcodeVersion: XcodeVersion /** @@ -188,6 +193,7 @@ class AppleSimulatorDevice( track.trackDevicePreparing(this@AppleSimulatorDevice) { remoteFileManager.removeRemoteDirectory() remoteFileManager.createRemoteDirectory() + remoteFileManager.createRemoteSharedDirectory() //Clean slate for the recorder executeWorkerCommand(listOf("pkill", "-f", "'simctl io ${udid} recordVideo'")) mutableListOf>().apply { @@ -244,7 +250,7 @@ class AppleSimulatorDevice( try { val (listener, lineListeners) = createExecutionListeners(devicePoolId, testBatch, deferred) executionLineListeners = lineListeners.onEach { addLineListener(it) } - AppleDeviceTestRunner(this@AppleSimulatorDevice).execute(configuration, vendorConfiguration, testBatch, listener) + AppleDeviceTestRunner(this@AppleSimulatorDevice, testBundleIdentifier).execute(configuration, vendorConfiguration, testBatch, listener) } finally { executionLineListeners.forEach { removeLineListener(it) } } @@ -750,4 +756,19 @@ class AppleSimulatorDevice( suspend fun grant(permission: Permission, bundleId: String): Boolean { return binaryEnvironment.xcrun.simctl.privacy.grant(udid, permission, bundleId).successful } + + suspend fun clearAppContainer(bundleId: String) { + binaryEnvironment.xcrun.simctl.application.terminateApplication(udid, bundleId) + + val containerPath = binaryEnvironment.xcrun.simctl.application.containerPath(udid, bundleId, ApplicationService.ContainerType.DATA) + if (containerPath.successful) { + remoteFileManager.removeRemotePath(containerPath.combinedStdout.trim()) + } else { + logger.warn { "Failed to clear app container:\nstdout: ${containerPath.combinedStdout}\nstderr: ${containerPath.combinedStderr}" } + } + } + + companion object { + const val SHARED_PATH = "/tmp/marathon" + } } diff --git a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/NmTestParser.kt b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/NmTestParser.kt index acf3df18b..f91412931 100644 --- a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/NmTestParser.kt +++ b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/NmTestParser.kt @@ -8,6 +8,7 @@ import com.malinskiy.marathon.device.Device import com.malinskiy.marathon.exceptions.TestParsingException import com.malinskiy.marathon.execution.RemoteTestParser import com.malinskiy.marathon.execution.withRetry +import com.malinskiy.marathon.ios.extensions.testBundle import com.malinskiy.marathon.ios.model.AppleTestBundle import com.malinskiy.marathon.log.MarathonLogging import com.malinskiy.marathon.test.Test @@ -23,23 +24,11 @@ class NmTestParser( private val logger = MarathonLogging.logger(NmTestParser::class.java.simpleName) override suspend fun extract(device: Device): List { - val app = vendorConfiguration.bundle?.app ?: throw IllegalArgumentException("No application bundle provided") - val xctest = vendorConfiguration.bundle?.xctest ?: throw IllegalArgumentException("No test bundle provided") - val possibleTestBinaries = xctest.listFiles()?.filter { it.isFile && it.extension == "" } - ?: throw ConfigurationException("missing test binaries in xctest folder at $xctest") - val testBinary = when (possibleTestBinaries.size) { - 0 -> throw ConfigurationException("missing test binaries in xctest folder at $xctest") - 1 -> possibleTestBinaries[0] - else -> { - logger.warn { "Multiple test binaries present in xctest folder" } - possibleTestBinaries.find { it.name == xctest.nameWithoutExtension } ?: possibleTestBinaries.first() - } - } - + val bundle = vendorConfiguration.testBundle() return withRetry(3, 0) { try { val device = device as? AppleSimulatorDevice ?: throw ConfigurationException("Unexpected device type for remote test parsing") - return@withRetry parseTests(device, xctest, testBinary) + return@withRetry parseTests(device, bundle) } catch (e: CancellationException) { throw e } catch (e: Exception) { @@ -51,13 +40,15 @@ class NmTestParser( private suspend fun parseTests( device: AppleSimulatorDevice, - xctest: File, - testBinary: File + bundle: AppleTestBundle, ): List { + val testBinary = bundle.testBinary + val xctest = bundle.testApplication logger.debug { "Found test binary $testBinary for xctest $xctest" } device.remoteFileManager.createRemoteDirectory() + device.remoteFileManager.createRemoteSharedDirectory() val remoteXctest = device.remoteFileManager.remoteXctestFile() if (!device.pushFile(xctest, remoteXctest)) { throw TestParsingException("failed to push xctest for test parsing") @@ -87,7 +78,7 @@ class NmTestParser( } - val testBundle = AppleTestBundle(vendorConfiguration.bundle?.application, xctest, testBinary) + val testBundle = AppleTestBundle(vendorConfiguration.bundle?.application, xctest) swiftTests.forEach { testBundleIdentifier.put(it, testBundle) } objectiveCTests.forEach { testBundleIdentifier.put(it, testBundle) } diff --git a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/RemoteFileManager.kt b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/RemoteFileManager.kt index 47f1fc686..8b9e488aa 100644 --- a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/RemoteFileManager.kt +++ b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/RemoteFileManager.kt @@ -16,6 +16,7 @@ class RemoteFileManager(private val device: AppleDevice) { private val outputDir by lazy { device.storagePath } fun remoteDirectory(): String = outputDir + fun remoteSharedDirectory(): String = AppleSimulatorDevice.SHARED_PATH + "/shared/" suspend fun createRemoteDirectory(remoteDir: String = remoteDirectory()) { executeCommand( @@ -24,6 +25,8 @@ class RemoteFileManager(private val device: AppleDevice) { ) } + suspend fun createRemoteSharedDirectory() = createRemoteDirectory(remoteSharedDirectory()) + suspend fun removeRemoteDirectory() { executeCommand( listOf("rm", "-rf", remoteDirectory()), @@ -40,10 +43,10 @@ class RemoteFileManager(private val device: AppleDevice) { fun remoteXctestrunFile(): String = remoteFile(xctestrunFileName()) - fun remoteXctestFile(): String = remoteFile(xctestFileName()) - fun remoteXctestParserFile(): String = remoteFile(`libXctestParserFileName`()) - fun remoteApplication(): String = remoteFile(appUnderTestFileName()) - fun remoteExtraApplication(name: String) = remoteFile(name) + fun remoteXctestFile(): String = remoteSharedFile(xctestFileName()) + fun remoteXctestParserFile(): String = remoteSharedFile(`libXctestParserFileName`()) + fun remoteApplication(): String = remoteSharedFile(appUnderTestFileName()) + fun remoteExtraApplication(name: String) = remoteSharedFile(name) /** * Omitting xcresult extension results in a symlink @@ -61,6 +64,7 @@ class RemoteFileManager(private val device: AppleDevice) { "${device.udid}.${batch.id}.xcresult" private fun remoteFile(file: String): String = remoteDirectory().resolve(file) + private fun remoteSharedFile(file: String): String = remoteSharedDirectory().resolve(file) private suspend fun safeExecuteCommand(command: List) { try { diff --git a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/XCTestParser.kt b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/XCTestParser.kt index b39c77147..4675d1210 100644 --- a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/XCTestParser.kt +++ b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/XCTestParser.kt @@ -105,7 +105,7 @@ class XCTestParser( } } - val testBundle = AppleTestBundle(vendorConfiguration.bundle?.application, xctest, testBinary) + val testBundle = AppleTestBundle(vendorConfiguration.bundle?.application, xctest) val result = tests.toList() result.forEach { testBundleIdentifier.put(it, testBundle) } diff --git a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/bin/xcrun/simctl/service/ApplicationService.kt b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/bin/xcrun/simctl/service/ApplicationService.kt index 8ac917a1b..3acf727b8 100644 --- a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/bin/xcrun/simctl/service/ApplicationService.kt +++ b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/bin/xcrun/simctl/service/ApplicationService.kt @@ -16,11 +16,24 @@ class ApplicationService (commandExecutor: CommandExecutor, ) } + suspend fun containerPath(udid: String, bundleId: String, containerType: ContainerType): CommandResult { + return criticalExec( + timeout = timeoutConfiguration.shell, + "get_app_container", udid, bundleId, containerType.value + ) + } + + enum class ContainerType(val value: String) { + APPLICATION("app"), + DATA("data"), + GROUPS("groups") + } + /** * Terminates a running application with the given bundle ID on this device */ - suspend fun terminateApplication(udid: String, bundleId: String): CommandResult { - return criticalExec( + suspend fun terminateApplication(udid: String, bundleId: String): CommandResult? { + return safeExecute( timeout = timeoutConfiguration.shell, "terminate", udid, bundleId ) diff --git a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/cmd/remote/ssh/sshj/config/PerformanceDefaultConfig.kt b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/cmd/remote/ssh/sshj/config/PerformanceDefaultConfig.kt index 24dc88e49..75b205054 100644 --- a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/cmd/remote/ssh/sshj/config/PerformanceDefaultConfig.kt +++ b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/cmd/remote/ssh/sshj/config/PerformanceDefaultConfig.kt @@ -10,6 +10,7 @@ import net.schmizz.sshj.transport.random.Random import net.schmizz.sshj.transport.random.SingletonRandomFactory import net.schmizz.sshj.common.Factory import net.schmizz.sshj.common.LoggerFactory +import net.schmizz.sshj.common.SecurityUtils import org.slf4j.Logger internal class PerformanceDefaultConfig( @@ -19,7 +20,7 @@ internal class PerformanceDefaultConfig( val bcFactory = MemoizingFactory(BouncyCastleRandom.Factory()) val jceFactory = MemoizingFactory(JCERandom.Factory()) } - + init { val loggerFactory = object : LoggerFactory { override fun getLogger(clazz: Class<*>?): Logger { @@ -44,19 +45,15 @@ internal class PerformanceDefaultConfig( ) } setLoggerFactory(loggerFactory) + setRandomFactory { + SingletonRandomFactory(if (SecurityUtils.isBouncyCastleRegistered()) bcFactory else jceFactory) + } } - - - override fun initRandomFactory(bouncyCastleRegistered: Boolean) { - randomFactory = SingletonRandomFactory(if (bouncyCastleRegistered) bcFactory else jceFactory) - } - - class MemoizingFactory(private val factory: Factory) : Factory { - val random : Random by lazy { factory.create() } + val random: Random by lazy { factory.create() } override fun create(): Random { return random } - } + } } diff --git a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/extensions/ConfigurationExtensions.kt b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/extensions/ConfigurationExtensions.kt new file mode 100644 index 000000000..fd15e2ad4 --- /dev/null +++ b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/extensions/ConfigurationExtensions.kt @@ -0,0 +1,11 @@ +package com.malinskiy.marathon.ios.extensions + +import com.malinskiy.marathon.config.vendor.VendorConfiguration +import com.malinskiy.marathon.ios.model.AppleTestBundle + +fun VendorConfiguration.IOSConfiguration.testBundle(): AppleTestBundle { + val xctest = bundle?.xctest ?: throw IllegalArgumentException("No test bundle provided") + val app = bundle?.app ?: throw IllegalArgumentException("No application bundle provided") + + return AppleTestBundle(app, xctest) +} diff --git a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/model/AppleTestBundle.kt b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/model/AppleTestBundle.kt index 8016321e5..713fda3e8 100644 --- a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/model/AppleTestBundle.kt +++ b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/model/AppleTestBundle.kt @@ -1,13 +1,47 @@ package com.malinskiy.marathon.ios.model +import com.dd.plist.NSDictionary +import com.malinskiy.marathon.config.exceptions.ConfigurationException import com.malinskiy.marathon.execution.bundle.TestBundle +import com.malinskiy.marathon.ios.plist.PropertyList +import com.malinskiy.marathon.ios.plist.bundle.BundleInfo +import com.malinskiy.marathon.log.MarathonLogging import java.io.File class AppleTestBundle( val application: File?, val testApplication: File, - val testBinary: File, ) : TestBundle() { + private val logger = MarathonLogging.logger {} override val id: String get() = testApplication.absolutePath + + val applicationBundleInfo: BundleInfo? by lazy { + application?.let { + PropertyList.from( + File( + it, + "Info.plist" + ) + ) + } + } + val appId = + applicationBundleInfo?.identification?.bundleIdentifier ?: throw ConfigurationException("No bundle identifier specified in $application") + + val testBundleInfo: BundleInfo by lazy { PropertyList.from(File(testApplication, "Info.plist")) } + val testBundleId = (testBundleInfo.naming.bundleName ?: testApplication.nameWithoutExtension).replace('-', '_') + + val testBinary: File by lazy { + val possibleTestBinaries = testApplication.listFiles()?.filter { it.isFile && it.extension == "" } + ?: throw ConfigurationException("missing test binaries in xctest folder at $testApplication") + when (possibleTestBinaries.size) { + 0 -> throw ConfigurationException("missing test binaries in xctest folder at $testApplication") + 1 -> possibleTestBinaries[0] + else -> { + logger.warn { "Multiple test binaries present in xctest folder" } + possibleTestBinaries.find { it.name == testApplication.nameWithoutExtension } ?: possibleTestBinaries.first() + } + } + } } diff --git a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/xctestrun/TestRootFactory.kt b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/xctestrun/TestRootFactory.kt index ff5be3905..b32175d6e 100644 --- a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/xctestrun/TestRootFactory.kt +++ b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/xctestrun/TestRootFactory.kt @@ -2,16 +2,14 @@ package com.malinskiy.marathon.ios.xctestrun import com.malinskiy.marathon.config.exceptions.ConfigurationException import com.malinskiy.marathon.config.vendor.VendorConfiguration -import com.malinskiy.marathon.config.vendor.ios.AppleTestBundleConfiguration import com.malinskiy.marathon.config.vendor.ios.TestType import com.malinskiy.marathon.exceptions.DeviceSetupException import com.malinskiy.marathon.exceptions.TransferException import com.malinskiy.marathon.ios.AppleSimulatorDevice import com.malinskiy.marathon.ios.RemoteFileManager +import com.malinskiy.marathon.ios.model.AppleTestBundle import com.malinskiy.marathon.ios.model.Arch import com.malinskiy.marathon.ios.model.Sdk -import com.malinskiy.marathon.ios.plist.PropertyList -import com.malinskiy.marathon.ios.plist.bundle.BundleInfo import com.malinskiy.marathon.ios.xctestrun.v2.Metadata import com.malinskiy.marathon.ios.xctestrun.v2.TestConfiguration import com.malinskiy.marathon.ios.xctestrun.v2.TestTarget @@ -20,15 +18,18 @@ import java.io.File import java.nio.file.Path import kotlin.io.path.createTempFile -class TestRootFactory(private val device: AppleSimulatorDevice, private val vendorConfiguration: VendorConfiguration.IOSConfiguration) { - suspend fun generate(testType: TestType, bundleConfiguration: AppleTestBundleConfiguration, useXctestParser: Boolean) { +class TestRootFactory( + private val device: AppleSimulatorDevice, + private val vendorConfiguration: VendorConfiguration.IOSConfiguration +) { + suspend fun generate(testType: TestType, bundle: AppleTestBundle, useXctestParser: Boolean) { val remoteFileManager = device.remoteFileManager val testRoot = remoteFileManager.remoteTestRoot() remoteFileManager.createRemoteDirectory(testRoot) val xctestrun = when (testType) { - TestType.XCUITEST -> generateXCUITest(testRoot, remoteFileManager, bundleConfiguration, useXctestParser) - TestType.XCTEST -> generateXCTest(testRoot, remoteFileManager, bundleConfiguration, useXctestParser) + TestType.XCUITEST -> generateXCUITest(testRoot, remoteFileManager, bundle, useXctestParser) + TestType.XCTEST -> generateXCTest(testRoot, remoteFileManager, bundle, useXctestParser) TestType.LOGIC_TEST -> TODO() } @@ -47,19 +48,15 @@ class TestRootFactory(private val device: AppleSimulatorDevice, private val vend private suspend fun generateXCUITest( testRoot: String, remoteFileManager: RemoteFileManager, - bundleConfiguration: AppleTestBundleConfiguration, + bundle: AppleTestBundle, useLibParseTests: Boolean ): Xctestrun { val sdkPlatformPath = device.binaryEnvironment.xcrun.getSdkPlatformPath(device.sdk) val platformLibraryPath = remoteFileManager.joinPath(sdkPlatformPath, "Developer", "Library") - val xctestBundleInfo: BundleInfo = PropertyList.from(File(bundleConfiguration.xctest, "Info.plist")) - val testBundleId = (xctestBundleInfo.naming.bundleName ?: bundleConfiguration.xctest.nameWithoutExtension).replace('-', '_') - val testRunnerApp = generateTestRunnerApp(testRoot, testBundleId, platformLibraryPath, bundleConfiguration) - val testApp = bundleConfiguration.app ?: throw ConfigurationException("no application specified for XCUITest") - val appBundleInfo: BundleInfo = PropertyList.from(File(testApp, "Info.plist")) - val appId = - appBundleInfo.identification.bundleIdentifier ?: throw ConfigurationException("No bundle identifier specified in $testApp") + val testRunnerApp = generateTestRunnerApp(testRoot, platformLibraryPath, bundle) + val testApp = bundle.application ?: throw ConfigurationException("no application specified for XCUITest") + val remoteTestApp = device.remoteFileManager.remoteApplication() if (!device.pushFolder(testApp, remoteTestApp)) { throw DeviceSetupException("failed to push app under test to remote device") @@ -91,7 +88,7 @@ class TestRootFactory(private val device: AppleSimulatorDevice, private val vend } else { emptyList() } - + /** * If the app contains internal frameworks we need to add them to xctestrun */ @@ -119,9 +116,9 @@ class TestRootFactory(private val device: AppleSimulatorDevice, private val vend "marathon", arrayOf( TestTarget.withArtifactReinstall( - name = testBundleId, + name = bundle.testBundleId, testingEnvironmentVariables = testEnv, - productModuleName = testBundleId, + productModuleName = bundle.testBundleId, systemAttachmentLifetime = vendorConfiguration.xcresult.attachments.systemAttachmentLifetime.value, userAttachmentLifetime = vendorConfiguration.xcresult.attachments.userAttachmentLifetime.value, dependentProductPaths = arrayOf(testRunnerApp, remoteXctest, remoteTestApp), @@ -139,16 +136,10 @@ class TestRootFactory(private val device: AppleSimulatorDevice, private val vend private suspend fun generateXCTest( testRoot: String, remoteFileManager: RemoteFileManager, - bundleConfiguration: AppleTestBundleConfiguration, + bundle: AppleTestBundle, useLibParseTests: Boolean ): Xctestrun { - val testApp = bundleConfiguration.app ?: throw ConfigurationException("no application specified for XCTest") - val xctestBundleInfo: BundleInfo = PropertyList.from(File(bundleConfiguration.xctest, "Info.plist")) - val testBundleId = (xctestBundleInfo.naming.bundleName ?: bundleConfiguration.xctest.nameWithoutExtension).replace('-', '_') - - val appBundleInfo: BundleInfo = PropertyList.from(File(testApp, "Info.plist")) - val appId = - appBundleInfo.identification.bundleIdentifier ?: throw ConfigurationException("No bundle identifier specified in $testApp") + val testApp = bundle.application ?: throw ConfigurationException("no application specified for XCTest") val remoteTestApp = device.remoteFileManager.remoteApplication() if (!device.pushFolder(testApp, remoteTestApp)) { @@ -159,12 +150,12 @@ class TestRootFactory(private val device: AppleSimulatorDevice, private val vend * A common scenario is to place xctest for unit tests inside the app's PlugIns. * This is what Xcode does out of the box */ - val remoteXctest = joinPath(remoteTestApp, "PlugIns", bundleConfiguration.xctest.name) + val remoteXctest = joinPath(remoteTestApp, "PlugIns", bundle.testApplication.name) remoteFileManager.createRemoteDirectory(joinPath(remoteTestApp, "PlugIns")) - if (bundleConfiguration.xctest == Path.of(testApp.path, "PlugIns")) { + if (bundle.testApplication == Path.of(testApp.path, "PlugIns")) { //We already pushed it above } else { - if (!device.pushFolder(bundleConfiguration.xctest, remoteXctest)) { + if (!device.pushFolder(bundle.testApplication, remoteXctest)) { throw DeviceSetupException("failed to push xctest to remote device") } } @@ -194,7 +185,7 @@ class TestRootFactory(private val device: AppleSimulatorDevice, private val vend if (File(testApp, "Frameworks").exists()) { dyldFrameworks.add("__TESTROOT__/${remoteFileManager.appUnderTestFileName()}/Frameworks") } - + val dyldLibraries = listOf("__TESTROOT__", usrLib, *userLibraryPath.toTypedArray()) val bundleInject = if (device.sdk == Sdk.IPHONEOS) { "__TESTHOST__/Frameworks/libXCTestBundleInject.dylib" @@ -223,9 +214,9 @@ class TestRootFactory(private val device: AppleSimulatorDevice, private val vend "marathon", arrayOf( TestTarget.withArtifactReinstall( - name = testBundleId, + name = bundle.testBundleId, testingEnvironmentVariables = testEnv, - productModuleName = testBundleId, + productModuleName = bundle.testBundleId, systemAttachmentLifetime = vendorConfiguration.xcresult.attachments.systemAttachmentLifetime.value, userAttachmentLifetime = vendorConfiguration.xcresult.attachments.userAttachmentLifetime.value, testBundlePath = remoteXctest, @@ -240,13 +231,12 @@ class TestRootFactory(private val device: AppleSimulatorDevice, private val vend private suspend fun generateTestRunnerApp( testRoot: String, - testBundleId: String, platformLibraryPath: String, - bundleConfiguration: AppleTestBundleConfiguration + bundle: AppleTestBundle, ): String { - val testBinary = joinPath(device.remoteFileManager.remoteXctestFile(), bundleConfiguration.xctest.nameWithoutExtension) + val testBinary = joinPath(device.remoteFileManager.remoteXctestFile(), bundle.testApplication.nameWithoutExtension) val baseApp = joinPath(platformLibraryPath, "Xcode", "Agents", "XCTRunner.app") - val runnerBinaryName = "$testBundleId-Runner" + val runnerBinaryName = "${bundle.testBundleId}-Runner" val testRunnerApp = joinPath(testRoot, "$runnerBinaryName.app") device.remoteFileManager.copy(baseApp, testRunnerApp) diff --git a/vendor/vendor-ios/src/test/kotlin/com/malinskiy/marathon/ios/logparser/parser/TestRunProgressParserTest.kt b/vendor/vendor-ios/src/test/kotlin/com/malinskiy/marathon/ios/logparser/parser/TestRunProgressParserTest.kt index 715176c38..94c9662bd 100644 --- a/vendor/vendor-ios/src/test/kotlin/com/malinskiy/marathon/ios/logparser/parser/TestRunProgressParserTest.kt +++ b/vendor/vendor-ios/src/test/kotlin/com/malinskiy/marathon/ios/logparser/parser/TestRunProgressParserTest.kt @@ -5,10 +5,10 @@ import assertk.assertions.isEqualTo import com.malinskiy.marathon.ios.logparser.formatter.NoopPackageNameFormatter import com.malinskiy.marathon.ios.test.TestEvent import com.malinskiy.marathon.time.Timer +import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.mockito.Mockito.mock import org.mockito.Mockito.reset class TestRunProgressParserTest {