diff --git a/configuration/src/main/kotlin/com/malinskiy/marathon/config/serialization/ConfigurationFactory.kt b/configuration/src/main/kotlin/com/malinskiy/marathon/config/serialization/ConfigurationFactory.kt index 3583b4cd2..9f314cfb1 100644 --- a/configuration/src/main/kotlin/com/malinskiy/marathon/config/serialization/ConfigurationFactory.kt +++ b/configuration/src/main/kotlin/com/malinskiy/marathon/config/serialization/ConfigurationFactory.kt @@ -68,9 +68,10 @@ class ConfigurationFactory( val resolvedDerivedDataDir = it.derivedDataDir?.let { ddd -> marathonfileDir.resolve(ddd) } val resolvedApplication = it.application?.let { ddd -> marathonfileDir.resolve(ddd) } val resolvedTestApplication = it.testApplication?.let { ddd -> marathonfileDir.resolve(ddd) } + val resolvedExtraArtifacts = it.extraArtifacts?.map { ddd -> marathonfileDir.resolve(ddd) } val resolvedExtraApplications = it.extraApplications?.map { ddd -> marathonfileDir.resolve(ddd) } - - AppleTestBundleConfiguration(resolvedApplication, resolvedTestApplication, resolvedExtraApplications, resolvedDerivedDataDir).apply { validate() } + + AppleTestBundleConfiguration(resolvedApplication, resolvedTestApplication, resolvedExtraApplications, resolvedExtraArtifacts, resolvedDerivedDataDir).apply { validate() } } val optionalDevices = configuration.vendorConfiguration.devicesFile?.resolveAgainst(marathonfileDir) ?: marathonfileDir.resolve("Marathondevices") diff --git a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/ios/AppleTestBundleConfiguration.kt b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/ios/AppleTestBundleConfiguration.kt index a5f3f28b7..b65d0e4ea 100644 --- a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/ios/AppleTestBundleConfiguration.kt +++ b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/ios/AppleTestBundleConfiguration.kt @@ -17,6 +17,7 @@ data class AppleTestBundleConfiguration( @JsonProperty("application") val application: File? = null, @JsonProperty("testApplication") val testApplication: File? = null, @JsonProperty("extraApplications") val extraApplications: List? = null, + @JsonProperty("extraArtifacts") val extraArtifacts: List? = null, @JsonProperty("derivedDataDir") val derivedDataDir: File? = null, @JsonProperty("testType") val testType: TestType? = null, private val tempDirFor: (File) -> File = { file -> diff --git a/docs/docs/ios/configure.md b/docs/docs/ios/configure.md index 2daf859eb..55ba6fca7 100644 --- a/docs/docs/ios/configure.md +++ b/docs/docs/ios/configure.md @@ -82,6 +82,24 @@ extraApplications: - "/path/to/additional.app" ``` +#### Extra artifacts +Marathon can push additional artifacts such as files and folders to the remote environment for testing: + +```yaml +extraArtifacts: + - "/path/to/my/folder" +``` + +To retrieve these artifacts from the test marathon passes an environment variable `TEST_EXTRA_ARTIFACTS` with the absolute path of the +folder containing all of the `extraArtifacts`, e.g. for the example above `$TEST_EXTRA_ARTIFACTS/folder` will be a valid path to read. + +:::tip + +There is no unwrapping during the push process so a local folder `a` will be a remote folder `TEST_EXTRA_ARTIFACTS/a` and +a local file `b` will be a remote file `TEST_EXTRA_ARTIFACTS/b`. + +::: + ### Devices By default, marathon will look for a file `Marathondevices` in the same folder as `Marathonfile` for the configuration of workers. You can override this location with the following property: 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 3673aa8bb..3603a4b1b 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 @@ -24,7 +24,7 @@ class AppleApplicationInstaller( logger.debug { "Moving xctest to ${device.serialNumber}" } val remoteXctest = device.remoteFileManager.remoteXctestFile() withRetry(3, 1000L) { - device.remoteFileManager.createRemoteDirectory() + device.remoteFileManager.createRemoteDirectories() val remoteDirectory = device.remoteFileManager.remoteDirectory() if (!device.pushFolder(xctest, remoteXctest)) { throw DeviceSetupException("Error transferring $xctest to ${device.serialNumber}") @@ -57,6 +57,16 @@ class AppleApplicationInstaller( logger.warn { "Extra application $it should be a directory with extension app" } } } + + bundle.extraArtifacts?.forEach { + logger.debug { "Pushing extra artifact $it to ${device.serialNumber}" } + val remoteArtifactFile = device.remoteFileManager.remoteArtifactFile(it.name) + if (it.isDirectory) { + device.pushFolder(it, remoteArtifactFile) + } else { + device.pushFile(it, remoteArtifactFile) + } + } } private suspend fun grantPermissions(device: AppleSimulatorDevice) { 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 888d91196..3b4d3f33a 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 @@ -132,7 +132,7 @@ class AppleSimulatorDevice( } override val coroutineContext: CoroutineContext = dispatcher override val remoteFileManager: RemoteFileManager = RemoteFileManager(this) - override val storagePath = "/tmp/marathon/$udid" + override val storagePath = "${RemoteFileManager.MARATHON_ROOT_PATH}/$udid" private lateinit var xcodeVersion: XcodeVersion /** @@ -188,8 +188,8 @@ class AppleSimulatorDevice( async(CoroutineName("prepare $serialNumber")) { supervisorScope { track.trackDevicePreparing(this@AppleSimulatorDevice) { - remoteFileManager.removeRemoteDirectory() - remoteFileManager.createRemoteDirectory() + remoteFileManager.removeRemoteDirectories() + remoteFileManager.createRemoteDirectories() //Clean slate for the recorder executeWorkerCommand(listOf("pkill", "-f", "'simctl io ${udid} recordVideo'")) mutableListOf>().apply { diff --git a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleTestParser.kt b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleTestParser.kt index eadd1512c..c89b5f7b4 100644 --- a/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleTestParser.kt +++ b/vendor/vendor-ios/src/main/kotlin/com/malinskiy/marathon/ios/AppleTestParser.kt @@ -4,7 +4,6 @@ import com.malinskiy.marathon.config.Configuration import com.malinskiy.marathon.config.exceptions.ConfigurationException import com.malinskiy.marathon.config.vendor.VendorConfiguration import com.malinskiy.marathon.device.Device -import com.malinskiy.marathon.device.DeviceProvider import com.malinskiy.marathon.exceptions.TestParsingException import com.malinskiy.marathon.execution.RemoteTestParser import com.malinskiy.marathon.execution.withRetry @@ -56,7 +55,7 @@ class AppleTestParser( logger.debug { "Found test binary $testBinary for xctest $xctest" } - device.remoteFileManager.createRemoteDirectory() + device.remoteFileManager.createRemoteDirectories() val remoteXctest = device.remoteFileManager.remoteXctestFile() if (!device.pushFile(xctest, remoteXctest)) { throw TestParsingException("failed to push xctest for test parsing") 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 c58e3a878..03cbfcba3 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,17 +16,23 @@ class RemoteFileManager(private val device: AppleDevice) { private val outputDir by lazy { device.storagePath } fun remoteDirectory(): String = outputDir + fun remoteArtifactDirectory(): String = MARATHON_ROOT_PATH.resolve("extraArtifacts") - suspend fun createRemoteDirectory(remoteDir: String = outputDir) { + suspend fun createRemoteDirectories() { + createRemoteDirectory(remoteDirectory()) + createRemoteDirectory(remoteArtifactDirectory()) + } + + suspend fun createRemoteDirectory(path: String) { executeCommand( - listOf("mkdir", "-p", remoteDirectory()), - "Could not create remote directory ${remoteDirectory()}" + listOf("mkdir", "-p", path), + "Could not create remote directory $path" ) } - suspend fun removeRemoteDirectory() { + suspend fun removeRemoteDirectories() { executeCommand( - listOf("rm", "-rf", remoteDirectory()), + listOf("rm", "-rf", MARATHON_ROOT_PATH), "Unable to remove directory ${remoteDirectory()}" ) } @@ -43,6 +49,7 @@ class RemoteFileManager(private val device: AppleDevice) { fun remoteXctestFile(): String = remoteFile(xctestFileName()) fun remoteApplication(): String = remoteFile(appUnderTestFileName()) fun remoteExtraApplication(name: String) = remoteFile(name) + fun remoteExtraArtifact(name: String) = remoteFile(name) /** * Omitting xcresult extension results in a symlink @@ -58,6 +65,7 @@ class RemoteFileManager(private val device: AppleDevice) { "${device.udid}.${batch.id}.xcresult" private fun remoteFile(file: String): String = remoteDirectory().resolve(file) + fun remoteArtifactFile(file: String): String = remoteArtifactDirectory().resolve(file) private suspend fun safeExecuteCommand(command: List) { try { @@ -132,6 +140,7 @@ class RemoteFileManager(private val device: AppleDevice) { companion object { const val FILE_SEPARATOR = "/" + const val MARATHON_ROOT_PATH = "/tmp/marathon" } } 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 ad348928c..e938cabd1 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 @@ -25,7 +25,6 @@ class TestRootFactory(private val device: AppleSimulatorDevice, private val vend val remoteFileManager = device.remoteFileManager val testRoot = remoteFileManager.remoteTestRoot() - remoteFileManager.createRemoteDirectory(testRoot) val xctestrun = when (testType) { TestType.XCUITEST -> generateXCUITest(testRoot, remoteFileManager, bundleConfiguration) TestType.XCTEST -> generateXCTest(testRoot, remoteFileManager, bundleConfiguration) @@ -102,6 +101,7 @@ class TestRootFactory(private val device: AppleSimulatorDevice, private val vend .forEach { put(it.key, it.value) } + put(ENV_TEST_EXTRA_ARTIFACTS, remoteFileManager.remoteArtifactDirectory()) }.toMap() return Xctestrun( @@ -204,6 +204,7 @@ class TestRootFactory(private val device: AppleSimulatorDevice, private val vend .forEach { put(it.key, it.value) } + put(ENV_TEST_EXTRA_ARTIFACTS, remoteFileManager.remoteArtifactDirectory()) }.toMap() return Xctestrun( @@ -273,4 +274,8 @@ class TestRootFactory(private val device: AppleSimulatorDevice, private val vend } } } + + companion object { + const val ENV_TEST_EXTRA_ARTIFACTS = "TEST_EXTRA_ARTIFACTS" + } }