diff --git a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/apple/AppleTestBundleConfiguration.kt b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/apple/AppleTestBundleConfiguration.kt index c3121675c..7aaea60c1 100644 --- a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/apple/AppleTestBundleConfiguration.kt +++ b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/apple/AppleTestBundleConfiguration.kt @@ -22,15 +22,19 @@ data class AppleTestBundleConfiguration( File(file.parentFile, file.nameWithoutExtension).apply { deleteRecursively(); mkdirs() } } ) { - @JsonIgnore var app: File? = null - @JsonIgnore lateinit var xctest: File + @JsonIgnore + var app: File? = null + @JsonIgnore + var testApp: File? = null + @JsonIgnore + lateinit var xctest: File fun validate() { when { application != null && testApplication != null -> { app = when { application.isFile && setOf("ipa", "zip").contains(application.extension) -> { - extractAndValidateContainsDirectory(application, "app") + extractAndFindDirectory(application, "app", validate = true) } application.isDirectory && (application.extension == "app") -> { @@ -39,29 +43,45 @@ data class AppleTestBundleConfiguration( else -> throw ConfigurationException("application should be .ipa/.zip archive or a .app folder") } - xctest = when { + when { testApplication.isFile && setOf("ipa", "zip").contains(testApplication.extension) -> { - extractAndValidateContainsDirectory(testApplication, "xctest") + val extracted = extract(testApplication) + val possibleTestApp = findDirectoryInDirectory(extracted, "app", validate = false) + if (possibleTestApp != null) { + testApp = possibleTestApp + xctest = findDirectoryInDirectory(possibleTestApp, "xctest", validate = true) + ?: throw ConfigurationException("Unable to find xctest bundle") + } else { + xctest = findDirectoryInDirectory(extracted, "xctest", validate = true) + ?: throw ConfigurationException("Unable to find xctest bundle") + } + } + + testApplication.isDirectory && testApplication.extension == "app" -> { + testApp = testApplication + xctest = findDirectoryInDirectory(testApplication, "xctest", validate = true) + ?: throw ConfigurationException("Unable to find xctest bundle") } testApplication.isDirectory && testApplication.extension == "xctest" -> { - testApplication + xctest = testApplication } - else -> throw ConfigurationException("test application should be .ipa/.zip archive or a .xctest folder") + else -> throw ConfigurationException("test application should be .ipa/.zip archive or a .app/.xctest folder") } } derivedDataDir != null -> { - xctest = findDirectoryInDirectory(derivedDataDir, "xctest") - app = findDirectoryInDirectory(derivedDataDir, "app") + xctest = + findDirectoryInDirectory(derivedDataDir, "xctest", true) ?: throw ConfigurationException("Unable to find xctest bundle") + app = findDirectoryInDirectory(derivedDataDir, "app", true) } else -> throw ConfigurationException("please specify your application and test application either with files or provide derived data folder") } } - private fun findDirectoryInDirectory(directory: File, extension: String): File { + private fun findDirectoryInDirectory(directory: File, extension: String, validate: Boolean): File? { var found = mutableListOf() directory.walkTopDown().forEach { if (it.isDirectory && it.extension == extension) { @@ -69,15 +89,15 @@ data class AppleTestBundleConfiguration( } } when { - found.isEmpty() -> throw ConfigurationException("Unable to find an $extension directory in ${directory.absolutePath}") + found.isEmpty() && validate -> throw ConfigurationException("Unable to find an $extension directory in ${directory.absolutePath}") found.size > 1 -> throw ConfigurationException("Ambiguous $extension configuration in derived data folder [${found.joinToString { it.absolutePath }}]. Please specify parameters explicitly") } - return found.first() + return found.firstOrNull() } - private fun extractAndValidateContainsDirectory(file: File, extension: String): File { + private fun extractAndFindDirectory(file: File, extension: String, validate: Boolean): File? { val extracted = extract(file) - return findDirectoryInDirectory(extracted, extension) + return findDirectoryInDirectory(extracted, extension, validate) } private fun extract(file: File): File { diff --git a/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/AppleApplicationInstaller.kt b/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/AppleApplicationInstaller.kt index 315358c39..cfbf0bfd2 100644 --- a/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/AppleApplicationInstaller.kt +++ b/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/AppleApplicationInstaller.kt @@ -11,9 +11,11 @@ import com.malinskiy.marathon.config.vendor.VendorConfiguration import com.malinskiy.marathon.config.vendor.apple.TestType import com.malinskiy.marathon.exceptions.DeviceSetupException import com.malinskiy.marathon.execution.withRetry +import com.malinskiy.marathon.extension.relativePathTo import com.malinskiy.marathon.log.MarathonLogging +import java.io.File -open class AppleApplicationInstaller( +open class AppleApplicationInstaller( protected open val vendorConfiguration: VendorConfiguration, ) { private val logger = MarathonLogging.logger {} @@ -21,24 +23,41 @@ open class AppleApplicationInstaller( suspend fun prepareInstallation(device: AppleDevice, useXctestParser: Boolean = false) { val bundleConfiguration = vendorConfiguration.bundleConfiguration() val xctestrunEnv = vendorConfiguration.xctestrunEnv() ?: throw IllegalArgumentException("No xctestrunEnv provided") - val xcresultConfiguration = vendorConfiguration.xcresultConfiguration() ?: throw IllegalArgumentException("No xcresult configuration provided") + val xcresultConfiguration = + vendorConfiguration.xcresultConfiguration() ?: throw IllegalArgumentException("No xcresult configuration provided") val xctest = bundleConfiguration?.xctest ?: throw IllegalArgumentException("No test bundle provided") val app = bundleConfiguration.app - val bundle = AppleTestBundle(app, xctest, device.sdk) + val testApp = bundleConfiguration.testApp + val bundle = AppleTestBundle(app, testApp, xctest, device.sdk) val relativeTestBinaryPath = bundle.relativeBinaryPath + val testBinary = bundle.testBinary + var remoteXctest = "" - logger.debug { "Moving xctest to ${device.serialNumber}" } - val remoteXctest = device.remoteFileManager.remoteXctestFile() - withRetry(3, 1000L) { - device.remoteFileManager.createRemoteDirectory() - device.remoteFileManager.createRemoteSharedDirectory() - if (!device.pushFolder(xctest, remoteXctest)) { - throw DeviceSetupException("Error transferring $xctest to ${device.serialNumber}") + if (testApp != null) { + logger.debug { "Moving xctest runner application to ${device.serialNumber}" } + val remoteTestRunnerApplication = device.remoteFileManager.remoteTestRunnerApplication() + val relativePath = xctest.relativePathTo(testApp).split(File.separator) + remoteXctest = device.remoteFileManager.joinPath(remoteTestRunnerApplication, *relativePath.toTypedArray()) + withRetry(3, 1000L) { + device.remoteFileManager.createRemoteDirectory() + device.remoteFileManager.createRemoteSharedDirectory() + if (!device.pushFolder(testApp, remoteTestRunnerApplication)) { + throw DeviceSetupException("Error transferring $xctest to ${device.serialNumber}") + } + } + } else { + logger.debug { "Moving xctest to ${device.serialNumber}" } + remoteXctest = device.remoteFileManager.remoteXctestFile() + withRetry(3, 1000L) { + device.remoteFileManager.createRemoteDirectory() + device.remoteFileManager.createRemoteSharedDirectory() + if (!device.pushFolder(xctest, remoteXctest)) { + throw DeviceSetupException("Error transferring $xctest to ${device.serialNumber}") + } } } - logger.debug { "Generating test root for ${device.serialNumber}" } - val testBinary = bundle.testBinary + logger.debug { "Generating test root for ${device.serialNumber}" } val remoteTestBinary = device.remoteFileManager.joinPath(remoteXctest, *relativeTestBinaryPath, testBinary.name) val testType = getTestTypeFor(device, device.sdk, remoteTestBinary) TestRootFactory(device, xctestrunEnv, xcresultConfiguration).generate(testType, bundle, useXctestParser) diff --git a/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/NmTestParser.kt b/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/NmTestParser.kt index b7f06c1db..d04a8eb4a 100644 --- a/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/NmTestParser.kt +++ b/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/NmTestParser.kt @@ -31,7 +31,8 @@ class NmTestParser( val bundleConfiguration = vendorConfiguration.bundleConfiguration() val xctest = bundleConfiguration?.xctest ?: throw IllegalArgumentException("No test bundle provided") val app = bundleConfiguration.app - val bundle = AppleTestBundle(app, xctest, device.sdk) + val testApp = bundleConfiguration.testApp + val bundle = AppleTestBundle(app, testApp, xctest, device.sdk) return@withRetry parseTests(device, bundle) } catch (e: CancellationException) { throw e @@ -48,7 +49,7 @@ class NmTestParser( ): List { val testBinary = bundle.testBinary val relativeTestBinaryPath = bundle.relativeBinaryPath - val xctest = bundle.testApplication + val xctest = bundle.xctestBundle logger.debug { "Found test binary $testBinary for xctest $xctest" } diff --git a/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/RemoteFileManager.kt b/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/RemoteFileManager.kt index 6e56c14e3..6873353ac 100644 --- a/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/RemoteFileManager.kt +++ b/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/RemoteFileManager.kt @@ -43,6 +43,7 @@ class RemoteFileManager(private val device: AppleDevice) { fun remoteXctestrunFile(): String = remoteFile(xctestrunFileName()) fun remoteXctestFile(): String = remoteSharedFile(xctestFileName()) + fun remoteTestRunnerApplication(): String = remoteSharedFile(testRunnerFileName()) fun remoteXctestParserFile(): String = remoteSharedFile(`libXctestParserFileName`()) fun remoteApplication(): String = remoteSharedFile(appUnderTestFileName()) fun remoteExtraApplication(name: String) = remoteSharedFile(name) @@ -58,6 +59,7 @@ class RemoteFileManager(private val device: AppleDevice) { private fun libXctestParserFileName(): String = "libxctest-parser.dylib" fun appUnderTestFileName(): String = "appUnderTest.app" + fun testRunnerFileName(): String = "xctestRunner.app" private fun xcresultFileName(batch: TestBatch): String = "${device.udid}.${batch.id}.xcresult" diff --git a/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/XCTestParser.kt b/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/XCTestParser.kt index bb4d18cc4..119dceaef 100644 --- a/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/XCTestParser.kt +++ b/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/XCTestParser.kt @@ -36,7 +36,8 @@ class XCTestParser( val bundleConfiguration = vendorConfiguration.bundleConfiguration() val xctest = bundleConfiguration?.xctest ?: throw IllegalArgumentException("No test bundle provided") val app = bundleConfiguration.app - val bundle = AppleTestBundle(app, xctest, device.sdk) + val testApp = bundleConfiguration.testApp + val bundle = AppleTestBundle(app, testApp, xctest, device.sdk) return@withRetry parseTests(device, bundle, applicationInstaller) } catch (e: CancellationException) { throw e diff --git a/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/extensions/ConfigurationExtensions.kt b/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/extensions/ConfigurationExtensions.kt index 92f2e3d12..20b2bc5c9 100644 --- a/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/extensions/ConfigurationExtensions.kt +++ b/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/extensions/ConfigurationExtensions.kt @@ -1,6 +1,5 @@ package com.malinskiy.marathon.apple.extensions -import com.malinskiy.marathon.apple.model.AppleTestBundle import com.malinskiy.marathon.config.vendor.VendorConfiguration import com.malinskiy.marathon.config.vendor.apple.AppleTestBundleConfiguration import com.malinskiy.marathon.config.vendor.apple.ios.XcresultConfiguration diff --git a/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/model/AppleTestBundle.kt b/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/model/AppleTestBundle.kt index 102f57ad6..79d9b6993 100644 --- a/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/model/AppleTestBundle.kt +++ b/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/model/AppleTestBundle.kt @@ -11,18 +11,23 @@ import java.nio.file.Paths class AppleTestBundle( val application: File?, - val testApplication: File, + val testApplication: File?, + val xctestBundle: File, val sdk: Sdk, ) : TestBundle() { private val logger = MarathonLogging.logger {} override val id: String - get() = testApplication.absolutePath + get() = xctestBundle.absolutePath val applicationBundleInfo: BundleInfo? by lazy { application?.let { PropertyList.from( when (sdk) { - Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION , Sdk.VISION_SIMULATOR -> File(it, "Info.plist") + Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION, Sdk.VISION_SIMULATOR -> File( + it, + "Info.plist" + ) + Sdk.MACOS -> Paths.get(it.absolutePath, "Contents", "Info.plist").toFile() } ) @@ -34,28 +39,57 @@ class AppleTestBundle( val testBundleInfo: BundleInfo by lazy { val file = when (sdk) { - Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION , Sdk.VISION_SIMULATOR -> File(testApplication, "Info.plist") - Sdk.MACOS -> Paths.get(testApplication.absolutePath, "Contents", "Info.plist").toFile() + Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION, Sdk.VISION_SIMULATOR -> File( + xctestBundle, + "Info.plist" + ) + + Sdk.MACOS -> Paths.get(xctestBundle.absolutePath, "Contents", "Info.plist").toFile() } PropertyList.from(file) } - val testBundleId = (testBundleInfo.naming.bundleName ?: testApplication.nameWithoutExtension).replace("[- ]".toRegex(), "_") + val testBundleId = (testBundleInfo.naming.bundleName ?: xctestBundle.nameWithoutExtension).replace("[- ]".toRegex(), "_") val testBinary: File by lazy { val possibleTestBinaries = when (sdk) { - Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION , Sdk.VISION_SIMULATOR -> testApplication.listFiles()?.filter { it.isFile && it.extension == "" } - ?: throw ConfigurationException("missing test binaries in xctest folder at $testApplication") + Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION, Sdk.VISION_SIMULATOR -> xctestBundle.listFiles() + ?.filter { it.isFile && it.extension == "" } + ?: throw ConfigurationException("missing test binaries in xctest folder at $xctestBundle") - Sdk.MACOS -> Paths.get(testApplication.absolutePath, *relativeBinaryPath).toFile().listFiles() + Sdk.MACOS -> Paths.get(xctestBundle.absolutePath, *relativeBinaryPath).toFile().listFiles() ?.filter { it.isFile && it.extension == "" } - ?: throw ConfigurationException("missing test binaries in xctest folder at $testApplication") + ?: throw ConfigurationException("missing test binaries in xctest folder at $xctestBundle") } when (possibleTestBinaries.size) { - 0 -> throw ConfigurationException("missing test binaries in xctest folder at $testApplication") + 0 -> throw ConfigurationException("missing test binaries in xctest folder at $xctestBundle") 1 -> possibleTestBinaries[0] else -> { - logger.warn { "Multiple test binaries present in xctest folder" } - possibleTestBinaries.find { it.name == testApplication.nameWithoutExtension } ?: possibleTestBinaries.first() + logger.warn { "Multiple test binaries present [${possibleTestBinaries.joinToString(",") { it.name }}] in xctest folder" } + possibleTestBinaries.find { it.name == xctestBundle.nameWithoutExtension } ?: possibleTestBinaries.first() + } + } + } + + val testRunnerBinary: File by lazy { + if (testApplication == null) { + throw ConfigurationException("no test application provided") + } + + val possibleTestRunnerBinaries = when (sdk) { + Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION, Sdk.VISION_SIMULATOR -> testApplication.listFiles() + ?.filter { it.isFile && it.extension == "" } + ?: throw ConfigurationException("missing test binaries in test runner folder at $testApplication") + + Sdk.MACOS -> Paths.get(testApplication.absolutePath, *relativeBinaryPath).toFile().listFiles() + ?.filter { it.isFile && it.extension == "" } + ?: throw ConfigurationException("missing test binaries in test runner folder at $testApplication") + } + when (possibleTestRunnerBinaries.size) { + 0 -> throw ConfigurationException("missing test binaries in test runner folder at $testApplication") + 1 -> possibleTestRunnerBinaries[0] + else -> { + logger.warn { "Multiple test binaries present [${possibleTestRunnerBinaries.joinToString(",") { it.name }}] in test runner folder" } + possibleTestRunnerBinaries.find { it.name == testApplication.nameWithoutExtension } ?: possibleTestRunnerBinaries.first() } } } @@ -63,7 +97,9 @@ class AppleTestBundle( val applicationBinary: File? by lazy { application?.let { application -> when (sdk) { - Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION , Sdk.VISION_SIMULATOR -> application.listFiles()?.filter { it.isFile && it.extension == "" } + Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION, Sdk.VISION_SIMULATOR -> application.listFiles() + ?.filter { it.isFile && it.extension == "" } + Sdk.MACOS -> Paths.get(application.absolutePath, *relativeBinaryPath).toFile().listFiles() ?.filter { it.isFile && it.extension == "" } }?.let { possibleBinaries -> @@ -84,7 +120,7 @@ class AppleTestBundle( */ val relativeBinaryPath: Array by lazy { when (sdk) { - Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION , Sdk.VISION_SIMULATOR -> emptyArray() + Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION, Sdk.VISION_SIMULATOR -> emptyArray() Sdk.MACOS -> arrayOf("Contents", "MacOS") } } diff --git a/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/xctestrun/TestRootFactory.kt b/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/xctestrun/TestRootFactory.kt index 1af112291..8c87f2098 100644 --- a/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/xctestrun/TestRootFactory.kt +++ b/vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/xctestrun/TestRootFactory.kt @@ -17,6 +17,7 @@ import com.malinskiy.marathon.config.vendor.apple.ios.XcresultConfiguration import com.malinskiy.marathon.exceptions.DeviceLostException import com.malinskiy.marathon.exceptions.DeviceSetupException import com.malinskiy.marathon.exceptions.TransferException +import com.malinskiy.marathon.extension.relativePathTo import java.io.File import java.nio.file.Path import kotlin.io.path.createTempFile @@ -84,7 +85,22 @@ class TestRootFactory( val sdkPlatformPath = device.binaryEnvironment.xcrun.getSdkPlatformPath(device.sdk) val platformLibraryPath = remoteFileManager.joinPath(sdkPlatformPath, "Developer", "Library") - val testRunnerApp = generateTestRunnerApp(testRoot, platformLibraryPath, bundle) + val (testRunnerApp, remoteXctest) = if (bundle.testApplication != null) { + reuseTestRunnerApp(testRoot, bundle, bundle.testApplication) + } else { + val testRunnerApp = generateTestRunnerApp(testRoot, platformLibraryPath, bundle) + + val runnerPlugins = when (device.sdk) { + Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION , Sdk.VISION_SIMULATOR -> remoteFileManager.joinPath(testRunnerApp, "PlugIns") + Sdk.MACOS -> remoteFileManager.joinPath(testRunnerApp, "Contents", "PlugIns") + } + remoteFileManager.createRemoteDirectory(runnerPlugins) + val remoteXctest = remoteFileManager.remoteXctestFile() + remoteFileManager.copy(remoteXctest, runnerPlugins, override = false) + + Pair(testRunnerApp, remoteXctest) + } + val testApp = bundle.application ?: throw ConfigurationException("no application specified for XCUITest") val remoteTestApp = device.remoteFileManager.remoteApplication() @@ -92,13 +108,6 @@ class TestRootFactory( throw DeviceSetupException("failed to push app under test to remote device") } ensureApplicationBinaryIsExecutable(remoteFileManager, bundle) - val runnerPlugins = when (device.sdk) { - Sdk.IPHONEOS, Sdk.IPHONESIMULATOR, Sdk.TV, Sdk.TV_SIMULATOR, Sdk.WATCH, Sdk.WATCH_SIMULATOR, Sdk.VISION , Sdk.VISION_SIMULATOR -> remoteFileManager.joinPath(testRunnerApp, "PlugIns") - Sdk.MACOS -> remoteFileManager.joinPath(testRunnerApp, "Contents", "PlugIns") - } - remoteFileManager.createRemoteDirectory(runnerPlugins) - val remoteXctest = remoteFileManager.remoteXctestFile() - remoteFileManager.copy(remoteXctest, runnerPlugins, override = false) if (device.sdk == Sdk.IPHONEOS) { TODO("generate phone provisioning") @@ -131,7 +140,6 @@ class TestRootFactory( dyldFrameworks.add("__TESTROOT__/${remoteFileManager.appUnderTestFileName()}/Frameworks") } - val testEnv = mutableMapOf( "DYLD_FRAMEWORK_PATH" to dyldFrameworks.joinToString(":"), "DYLD_LIBRARY_PATH" to dyldLibraries.joinToString(":"), @@ -186,12 +194,12 @@ class TestRootFactory( * 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", bundle.testApplication.name) + val remoteXctest = joinPath(remoteTestApp, "PlugIns", bundle.xctestBundle.name) remoteFileManager.createRemoteDirectory(joinPath(remoteTestApp, "PlugIns")) - if (bundle.testApplication == Path.of(testApp.path, "PlugIns")) { + if (bundle.xctestBundle == Path.of(testApp.path, "PlugIns")) { //We already pushed it above } else { - if (!device.pushFolder(bundle.testApplication, remoteXctest)) { + if (!device.pushFolder(bundle.xctestBundle, remoteXctest)) { throw DeviceSetupException("failed to push xctest to remote device") } ensureApplicationBinaryIsExecutable(remoteFileManager, bundle) @@ -315,6 +323,31 @@ class TestRootFactory( return testRunnerApp } + private suspend fun reuseTestRunnerApp( + testRoot: String, + bundle: AppleTestBundle, + testApplication: File, //For null safety + ): Pair { + val sharedTestRunnerApp = device.remoteFileManager.remoteTestRunnerApplication() + val runnerBinaryName = "${bundle.testBundleId}-Runner" + val testRunnerApp = joinPath(testRoot, "$runnerBinaryName.app") + device.remoteFileManager.copy(sharedTestRunnerApp, testRunnerApp) + + val testRunnerBinary = device.remoteFileManager.joinPath(sharedTestRunnerApp, *bundle.relativeBinaryPath, bundle.testRunnerBinary.name) + val relativePath = bundle.xctestBundle.relativePathTo(testApplication).split(File.separator) + val remoteXctest = device.remoteFileManager.joinPath(testRunnerApp, *relativePath.toTypedArray()) + val remoteTestBinary = joinPath( + remoteXctest, + *bundle.relativeBinaryPath, + bundle.testBinary.nameWithoutExtension + ) + + matchArchitectures(remoteTestBinary, testRunnerBinary) + + return Pair(testRunnerApp, remoteXctest) + } + + private fun joinPath(base: String, vararg args: String) = device.remoteFileManager.joinPath(base, *args) private suspend fun matchArchitectures(testBinary: String, testRunnerBinary: String) { diff --git a/vendor/vendor-apple/ios/src/main/kotlin/com/malinskiy/marathon/apple/ios/AppleSimulatorDevice.kt b/vendor/vendor-apple/ios/src/main/kotlin/com/malinskiy/marathon/apple/ios/AppleSimulatorDevice.kt index 726803afd..da239706f 100644 --- a/vendor/vendor-apple/ios/src/main/kotlin/com/malinskiy/marathon/apple/ios/AppleSimulatorDevice.kt +++ b/vendor/vendor-apple/ios/src/main/kotlin/com/malinskiy/marathon/apple/ios/AppleSimulatorDevice.kt @@ -196,7 +196,8 @@ class AppleSimulatorDevice( testBundle = vendorConfiguration.bundleConfiguration()?.let { val xctest = it.xctest val app = it.app - AppleTestBundle(app, xctest, sdk) + val testApp = it.testApp + AppleTestBundle(app, testApp, xctest, sdk) } ?: throw IllegalArgumentException("No test bundle provided") }) add(async { diff --git a/vendor/vendor-apple/macos/src/main/kotlin/com/malinskiy/marathon/apple/macos/AppleMacosProvider.kt b/vendor/vendor-apple/macos/src/main/kotlin/com/malinskiy/marathon/apple/macos/AppleMacosProvider.kt index 6325f18ec..d8aad15ea 100644 --- a/vendor/vendor-apple/macos/src/main/kotlin/com/malinskiy/marathon/apple/macos/AppleMacosProvider.kt +++ b/vendor/vendor-apple/macos/src/main/kotlin/com/malinskiy/marathon/apple/macos/AppleMacosProvider.kt @@ -76,7 +76,7 @@ class AppleMacosProvider( override fun subscribe() = channel override suspend fun initialize() = withContext(coroutineContext) { - logger.debug("Initializing AppleSimulatorProvider") + logger.debug("Initializing AppleMacosProvider") val file = vendorConfiguration.devicesFile ?: File(System.getProperty("user.dir"), "Marathondevices") val devicesWithEnvironmentVariablesReplaced = environmentVariableSubstitutor.replace(file.readText()) val workers: List = try { diff --git a/vendor/vendor-apple/macos/src/main/kotlin/com/malinskiy/marathon/apple/macos/MacosDevice.kt b/vendor/vendor-apple/macos/src/main/kotlin/com/malinskiy/marathon/apple/macos/MacosDevice.kt index 3c6e90414..e6b19ed6c 100644 --- a/vendor/vendor-apple/macos/src/main/kotlin/com/malinskiy/marathon/apple/macos/MacosDevice.kt +++ b/vendor/vendor-apple/macos/src/main/kotlin/com/malinskiy/marathon/apple/macos/MacosDevice.kt @@ -114,7 +114,7 @@ class MacosDevice( private val dispatcher by lazy { newFixedThreadPoolContext( vendorConfiguration.threadingConfiguration.deviceThreads, - "AppleSimulatorDevice - execution - ${commandExecutor.host.id}" + "MacosDevice - execution - ${commandExecutor.host.id}" ) } override val coroutineContext: CoroutineContext = dispatcher @@ -155,40 +155,41 @@ class MacosDevice( override suspend fun executeTestRequest(request: TestRequest): ReceiveChannel> { return produce { - binaryEnvironment.xcrun.xcodebuild.testWithoutBuilding(udid, sdk, request, vendorConfiguration.xcodebuildTestArgs).use { session -> - withContext(Dispatchers.IO) { - val deferredStdout = supervisorScope { - async { - val testEventProducer = - XctestEventProducer(request.testTargetName ?: "", timer) - for (line in session.stdout) { - testEventProducer.process(line)?.let { - send(it) + binaryEnvironment.xcrun.xcodebuild.testWithoutBuilding(udid, sdk, request, vendorConfiguration.xcodebuildTestArgs) + .use { session -> + withContext(Dispatchers.IO) { + val deferredStdout = supervisorScope { + async { + val testEventProducer = + XctestEventProducer(request.testTargetName ?: "", timer) + for (line in session.stdout) { + testEventProducer.process(line)?.let { + send(it) + } + lineListeners.forEach { it.onLine(line) } } - lineListeners.forEach { it.onLine(line) } } } - } - val deferredStderr = supervisorScope { - async { - for (line in session.stderr) { - if (line.trim().isNotBlank()) { - logger.error { "simulator $udid: stderr=$line" } + val deferredStderr = supervisorScope { + async { + for (line in session.stderr) { + if (line.trim().isNotBlank()) { + logger.error { "simulator $udid: stderr=$line" } + } } } } - } - deferredStderr.await() - deferredStdout.await() - val exitCode = session.exitCode.await() - // 70 = no devices - // 65 = ** TEST EXECUTE FAILED **: crash - logger.debug { "Finished test batch execution with exit status $exitCode" } - close() + deferredStderr.await() + deferredStdout.await() + val exitCode = session.exitCode.await() + // 70 = no devices + // 65 = ** TEST EXECUTE FAILED **: crash + logger.debug { "Finished test batch execution with exit status $exitCode" } + close() + } } - } } } @@ -270,7 +271,8 @@ class MacosDevice( testBundle = vendorConfiguration.bundleConfiguration()?.let { val xctest = it.xctest val app = it.app - AppleTestBundle(app, xctest, sdk) + val testApp = it.testApp + AppleTestBundle(app, testApp, xctest, sdk) } ?: throw IllegalArgumentException("No test bundle provided") }) }.awaitAll()