Skip to content

Commit 48adc9a

Browse files
committed
feat(apple): macOS parsing
1 parent be1c3f2 commit 48adc9a

File tree

46 files changed

+851
-203
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+851
-203
lines changed

cli/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ distributions {
3535
dependencies {
3636
implementation(project(":core"))
3737
implementation(project(":vendor:vendor-apple:ios"))
38+
implementation(project(":vendor:vendor-apple:macos"))
3839
implementation(project(":vendor:vendor-android"))
3940
implementation(project(":analytics:usage"))
4041
implementation(Libraries.kotlinStdLib)

cli/src/main/kotlin/com/malinskiy/marathon/cli/ApplicationView.kt

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.malinskiy.marathon.config.vendor.VendorConfiguration
2121
import com.malinskiy.marathon.di.marathonStartKoin
2222
import com.malinskiy.marathon.exceptions.ExceptionsReporterFactory
2323
import com.malinskiy.marathon.apple.ios.IosVendor
24+
import com.malinskiy.marathon.apple.macos.MacosVendor
2425
import com.malinskiy.marathon.log.MarathonLogging
2526
import org.koin.core.context.stopKoin
2627
import org.koin.dsl.module
@@ -71,6 +72,9 @@ private fun execute(cliConfiguration: CliConfiguration) {
7172
is VendorConfiguration.AndroidConfiguration -> {
7273
AndroidVendor + module { single { vendorConfiguration } } + listOf(adamModule)
7374
}
75+
is VendorConfiguration.MacosConfiguration -> {
76+
MacosVendor + module { single { vendorConfiguration } }
77+
}
7478
else -> throw ConfigurationException("No vendor config present in ${marathonStartConfiguration.marathonfile.absolutePath}")
7579
}
7680

configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/VendorConfiguration.kt

+6-7
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,19 @@ import com.malinskiy.marathon.config.vendor.android.SerialStrategy
1313
import com.malinskiy.marathon.config.vendor.android.TestAccessConfiguration
1414
import com.malinskiy.marathon.config.vendor.android.TestParserConfiguration
1515
import com.malinskiy.marathon.config.vendor.android.ThreadingConfiguration
16-
import com.malinskiy.marathon.config.vendor.android.TimeoutConfiguration
1716
import com.malinskiy.marathon.config.vendor.apple.AppleTestBundleConfiguration
1817
import com.malinskiy.marathon.config.vendor.apple.ios.LifecycleConfiguration
1918
import com.malinskiy.marathon.config.vendor.apple.ios.PermissionsConfiguration
2019
import com.malinskiy.marathon.config.vendor.apple.RsyncConfiguration
2120
import com.malinskiy.marathon.config.vendor.apple.ios.SigningConfiguration
2221
import com.malinskiy.marathon.config.vendor.apple.SshConfiguration
22+
import com.malinskiy.marathon.config.vendor.apple.TimeoutConfiguration as AppleTimeoutConfiguration
2323
import com.malinskiy.marathon.config.vendor.apple.ios.XcresultConfiguration
2424
import java.io.File
25+
import com.malinskiy.marathon.config.vendor.android.TimeoutConfiguration as AndroidTimeoutConfiguration
2526
import com.malinskiy.marathon.config.vendor.apple.TestParserConfiguration as AppleTestParserConfiguration
2627
import com.malinskiy.marathon.config.vendor.apple.ios.ScreenRecordConfiguration as IosScreenRecordConfiguration
2728
import com.malinskiy.marathon.config.vendor.apple.ThreadingConfiguration as AppleThreadingConfiguration
28-
import com.malinskiy.marathon.config.vendor.apple.ios.TimeoutConfiguration as IosTimeoutConfiguration
29-
import com.malinskiy.marathon.config.vendor.apple.macos.TimeoutConfiguration as MacosTimeoutConfiguration1
3029

3130
const val DEFAULT_INIT_TIMEOUT_MILLIS = 30_000
3231
const val DEFAULT_AUTO_GRANT_PERMISSION = false
@@ -66,7 +65,7 @@ sealed class VendorConfiguration {
6665
@JsonProperty("screenRecordConfiguration") val screenRecordConfiguration: ScreenRecordConfiguration = ScreenRecordConfiguration(),
6766
@JsonProperty("waitForDevicesTimeoutMillis") val waitForDevicesTimeoutMillis: Long = DEFAULT_WAIT_FOR_DEVICES_TIMEOUT,
6867
@JsonProperty("allureConfiguration") val allureConfiguration: AllureConfiguration = AllureConfiguration(),
69-
@JsonProperty("timeoutConfiguration") val timeoutConfiguration: TimeoutConfiguration = TimeoutConfiguration(),
68+
@JsonProperty("timeoutConfiguration") val timeoutConfiguration: AndroidTimeoutConfiguration = AndroidTimeoutConfiguration(),
7069
@JsonProperty("fileSyncConfiguration") val fileSyncConfiguration: FileSyncConfiguration = FileSyncConfiguration(),
7170
@JsonProperty("threadingConfiguration") val threadingConfiguration: ThreadingConfiguration = ThreadingConfiguration(),
7271
@JsonProperty("testParserConfiguration") val testParserConfiguration: TestParserConfiguration = TestParserConfiguration.LocalTestParserConfiguration,
@@ -114,7 +113,7 @@ sealed class VendorConfiguration {
114113
var screenRecordConfiguration: ScreenRecordConfiguration = ScreenRecordConfiguration()
115114
var waitForDevicesTimeoutMillis: Long = DEFAULT_WAIT_FOR_DEVICES_TIMEOUT
116115
var allureConfiguration: AllureConfiguration = AllureConfiguration()
117-
var timeoutConfiguration: TimeoutConfiguration = TimeoutConfiguration()
116+
var timeoutConfiguration: AndroidTimeoutConfiguration = AndroidTimeoutConfiguration()
118117
var fileSyncConfiguration: FileSyncConfiguration = FileSyncConfiguration()
119118
var threadingConfiguration: ThreadingConfiguration = ThreadingConfiguration()
120119
var testParserConfiguration: TestParserConfiguration = TestParserConfiguration.LocalTestParserConfiguration
@@ -159,7 +158,7 @@ sealed class VendorConfiguration {
159158
@JsonProperty("xctestrunEnv") val xctestrunEnv: Map<String, String> = emptyMap(),
160159
@JsonProperty("lifecycle") val lifecycleConfiguration: LifecycleConfiguration = LifecycleConfiguration(),
161160
@JsonProperty("permissions") val permissions: PermissionsConfiguration = PermissionsConfiguration(),
162-
@JsonProperty("timeoutConfiguration") val timeoutConfiguration: IosTimeoutConfiguration = IosTimeoutConfiguration(),
161+
@JsonProperty("timeoutConfiguration") val timeoutConfiguration: AppleTimeoutConfiguration = AppleTimeoutConfiguration(),
163162
@JsonProperty("threadingConfiguration") val threadingConfiguration: AppleThreadingConfiguration = AppleThreadingConfiguration(),
164163
@JsonProperty("hideRunnerOutput") val hideRunnerOutput: Boolean = false,
165164
@JsonProperty("compactOutput") val compactOutput: Boolean = false,
@@ -185,7 +184,7 @@ sealed class VendorConfiguration {
185184

186185
@JsonProperty("xcresult") val xcresult: XcresultConfiguration = XcresultConfiguration(),
187186
@JsonProperty("xctestrunEnv") val xctestrunEnv: Map<String, String> = emptyMap(),
188-
@JsonProperty("timeoutConfiguration") val timeoutConfiguration: MacosTimeoutConfiguration1 = MacosTimeoutConfiguration1(),
187+
@JsonProperty("timeoutConfiguration") val timeoutConfiguration: AppleTimeoutConfiguration = AppleTimeoutConfiguration(),
189188
@JsonProperty("threadingConfiguration") val threadingConfiguration: AppleThreadingConfiguration = AppleThreadingConfiguration(),
190189
@JsonProperty("hideRunnerOutput") val hideRunnerOutput: Boolean = false,
191190
@JsonProperty("compactOutput") val compactOutput: Boolean = false,

configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/apple/ios/TimeoutConfiguration.kt configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/apple/TimeoutConfiguration.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.malinskiy.marathon.config.vendor.apple.ios
1+
package com.malinskiy.marathon.config.vendor.apple
22

33
import com.fasterxml.jackson.annotation.JsonProperty
44
import java.time.Duration

configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/apple/macos/TimeoutConfiguration.kt

-16
This file was deleted.

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

+4-13
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ open class AppleApplicationInstaller<in T: AppleDevice>(
2424
val xcresultConfiguration = vendorConfiguration.xcresultConfiguration() ?: throw IllegalArgumentException("No xcresult configuration provided")
2525
val xctest = bundleConfiguration?.xctest ?: throw IllegalArgumentException("No test bundle provided")
2626
val app = bundleConfiguration.app
27-
val bundle = AppleTestBundle(app, xctest)
27+
val bundle = AppleTestBundle(app, xctest, device.sdk)
28+
val relativeTestBinaryPath = bundle.relativeTestBinaryPath
2829

2930
logger.debug { "Moving xctest to ${device.serialNumber}" }
3031
val remoteXctest = device.remoteFileManager.remoteXctestFile()
@@ -37,22 +38,12 @@ open class AppleApplicationInstaller<in T: AppleDevice>(
3738
}
3839
logger.debug { "Generating test root for ${device.serialNumber}" }
3940

40-
val possibleTestBinaries = xctest.listFiles()?.filter { it.isFile && it.extension == "" }
41-
?: throw ConfigurationException("missing test binaries in xctest folder at $xctest")
42-
val testBinary = when (possibleTestBinaries.size) {
43-
0 -> throw ConfigurationException("missing test binaries in xctest folder at $xctest")
44-
1 -> possibleTestBinaries[0]
45-
else -> {
46-
logger.warn { "Multiple test binaries present in xctest folder" }
47-
possibleTestBinaries.find { it.name == xctest.nameWithoutExtension } ?: possibleTestBinaries.first()
48-
}
49-
}
50-
val remoteTestBinary = device.remoteFileManager.joinPath(remoteXctest, testBinary.name)
41+
val testBinary = bundle.testBinary
42+
val remoteTestBinary = device.remoteFileManager.joinPath(remoteXctest, *relativeTestBinaryPath, testBinary.name)
5143
val testType = getTestTypeFor(device, device.sdk, remoteTestBinary)
5244
TestRootFactory(device, xctestrunEnv, xcresultConfiguration).generate(testType, bundle, useXctestParser)
5345
afterInstall(device as T)
5446

55-
5647
bundleConfiguration.extraApplications?.forEach {
5748
if (it.isDirectory && it.extension == "app") {
5849
logger.debug { "Installing extra application $it to ${device.serialNumber}" }

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

+1-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import ch.qos.logback.classic.PatternLayout
77
import ch.qos.logback.classic.spi.ILoggingEvent
88
import ch.qos.logback.core.ConsoleAppender
99
import ch.qos.logback.core.encoder.LayoutWrappingEncoder
10-
import com.malinskiy.marathon.config.vendor.VendorConfiguration
1110
import com.malinskiy.marathon.log.MarathonLogConfigurator
1211
import com.malinskiy.marathon.report.timeline.TimelineSummaryProvider
1312
import net.schmizz.sshj.DefaultConfig
@@ -16,11 +15,10 @@ import net.schmizz.sshj.transport.kex.Curve25519SHA256
1615
import net.schmizz.sshj.transport.random.BouncyCastleRandom
1716
import org.slf4j.LoggerFactory
1817

19-
class AppleLogConfigurator(private val vendorConfiguration: VendorConfiguration.IOSConfiguration) : MarathonLogConfigurator {
18+
class AppleLogConfigurator(private val compactOutput: Boolean) : MarathonLogConfigurator {
2019
override fun configure() {
2120
val loggerContext = LoggerFactory.getILoggerFactory() as LoggerContext
2221

23-
val compactOutput = vendorConfiguration.compactOutput
2422
val layout = PatternLayout()
2523
layout.pattern = if (compactOutput) {
2624
"%highlight(%.-1level [%thread] <%logger{48}> %msg%n)"

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

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

3-
import com.malinskiy.marathon.apple.extensions.testBundle
3+
import com.malinskiy.marathon.apple.extensions.bundleConfiguration
44
import com.malinskiy.marathon.apple.model.AppleTestBundle
55
import com.malinskiy.marathon.config.Configuration
66
import com.malinskiy.marathon.config.exceptions.ConfigurationException
@@ -25,10 +25,13 @@ class NmTestParser(
2525
private val logger = MarathonLogging.logger(NmTestParser::class.java.simpleName)
2626

2727
override suspend fun extract(device: Device): List<Test> {
28-
val bundle = vendorConfiguration.testBundle()
2928
return withRetry(3, 0) {
3029
try {
3130
val device = device as? AppleDevice ?: throw ConfigurationException("Unexpected device type for remote test parsing")
31+
val bundleConfiguration = vendorConfiguration.bundleConfiguration()
32+
val xctest = bundleConfiguration?.xctest ?: throw IllegalArgumentException("No test bundle provided")
33+
val app = bundleConfiguration.app
34+
val bundle = AppleTestBundle(app, xctest, device.sdk)
3235
return@withRetry parseTests(device, bundle)
3336
} catch (e: CancellationException) {
3437
throw e
@@ -44,6 +47,7 @@ class NmTestParser(
4447
bundle: AppleTestBundle,
4548
): List<Test> {
4649
val testBinary = bundle.testBinary
50+
val relativeTestBinaryPath = bundle.relativeTestBinaryPath
4751
val xctest = bundle.testApplication
4852

4953
logger.debug { "Found test binary $testBinary for xctest $xctest" }
@@ -54,7 +58,7 @@ class NmTestParser(
5458
if (!device.pushFile(xctest, remoteXctest)) {
5559
throw TestParsingException("failed to push xctest for test parsing")
5660
}
57-
val remoteTestBinary = device.remoteFileManager.joinPath(remoteXctest, testBinary.name)
61+
val remoteTestBinary = device.remoteFileManager.joinPath(remoteXctest, *relativeTestBinaryPath, testBinary.name)
5862

5963
val rawSwiftTests = device.binaryEnvironment.nm.swiftTests(remoteTestBinary)
6064
val swiftTests = rawSwiftTests.map { it.trim().split('.') }

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

+2
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ class RemoteFileManager(private val device: AppleDevice) {
136136
)
137137
}
138138

139+
private fun String.bashEscape() = "'" + replace("'", "'\\''") + "'"
140+
139141
companion object {
140142
const val FILE_SEPARATOR = "/"
141143
}

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

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

33
import com.malinskiy.marathon.apple.extensions.bundleConfiguration
4-
import com.malinskiy.marathon.apple.extensions.testBundle
54
import com.malinskiy.marathon.apple.model.AppleTestBundle
65
import com.malinskiy.marathon.apple.test.TestEvent
76
import com.malinskiy.marathon.apple.test.TestRequest
@@ -23,7 +22,7 @@ import kotlin.io.path.outputStream
2322

2423
class XCTestParser<T: AppleDevice>(
2524
private val configuration: Configuration,
26-
private val vendorConfiguration: VendorConfiguration.IOSConfiguration,
25+
private val vendorConfiguration: VendorConfiguration.MacosConfiguration,
2726
private val testBundleIdentifier: AppleTestBundleIdentifier,
2827
private val applicationInstaller: AppleApplicationInstaller<T>,
2928
) : RemoteTestParser<DeviceProvider>, LineListener {
@@ -34,7 +33,11 @@ class XCTestParser<T: AppleDevice>(
3433
try {
3534
val device =
3635
device as? T ?: throw ConfigurationException("Unexpected device type for remote test parsing")
37-
return@withRetry parseTests(device, configuration, vendorConfiguration, applicationInstaller)
36+
val bundleConfiguration = vendorConfiguration.bundleConfiguration()
37+
val xctest = bundleConfiguration?.xctest ?: throw IllegalArgumentException("No test bundle provided")
38+
val app = bundleConfiguration.app
39+
val bundle = AppleTestBundle(app, xctest, device.sdk)
40+
return@withRetry parseTests(device, bundle, applicationInstaller)
3841
} catch (e: CancellationException) {
3942
throw e
4043
} catch (e: Exception) {
@@ -46,13 +49,12 @@ class XCTestParser<T: AppleDevice>(
4649

4750
private suspend fun parseTests(
4851
device: AppleDevice,
49-
configuration: Configuration,
50-
vendorConfiguration: VendorConfiguration,
52+
bundle: AppleTestBundle,
5153
applicationInstaller: AppleApplicationInstaller<T>,
5254
): List<Test> {
5355
applicationInstaller.prepareInstallation(device, useXctestParser = true)
5456

55-
val platform = "iPhoneSimulator"
57+
val platform = device.sdk.platformName
5658
val dylib = javaClass.getResourceAsStream("/libxctest-parser/$platform/libxctest-parser.dylib")
5759
val tempFile = kotlin.io.path.createTempFile().apply {
5860
outputStream().use {
@@ -86,11 +88,12 @@ class XCTestParser<T: AppleDevice>(
8688
when (event) {
8789
is TestStarted -> {
8890
//Target name is never printed via xcodebuild. We create it using the bundle id in com.malinskiy.marathon.ios.xctestrun.TestRootFactory
89-
val testWithTargetName = event.id.copy(pkg = vendorConfiguration.testBundle().testBundleId)
91+
val testWithTargetName = event.id.copy(pkg = bundle.testBundleId)
9092
tests.add(testWithTargetName)
9193
}
9294
else -> Unit
9395
}
96+
9497
}
9598
}
9699

@@ -104,25 +107,12 @@ class XCTestParser<T: AppleDevice>(
104107
device.removeLineListener(this)
105108
}
106109

107-
val xctest = vendorConfiguration.bundleConfiguration()?.xctest ?: throw IllegalArgumentException("No test bundle provided")
108-
val possibleTestBinaries = xctest.listFiles()?.filter { it.isFile && it.extension == "" }
109-
?: throw ConfigurationException("missing test binaries in xctest folder at $xctest")
110-
val testBinary = when (possibleTestBinaries.size) {
111-
0 -> throw ConfigurationException("missing test binaries in xctest folder at $xctest")
112-
1 -> possibleTestBinaries[0]
113-
else -> {
114-
logger.warn { "Multiple test binaries present in xctest folder" }
115-
possibleTestBinaries.find { it.name == xctest.nameWithoutExtension } ?: possibleTestBinaries.first()
116-
}
117-
}
118-
119110
if (tests.size == 0) {
120111
logger.warn { "XCTestParser failed to parse tests. xcodebuild output:" + System.lineSeparator() + "$lineBuffer" }
121112
}
122113

123-
val testBundle = AppleTestBundle(vendorConfiguration.bundleConfiguration()?.app, xctest)
124114
val result = tests.toList()
125-
result.forEach { testBundleIdentifier.put(it, testBundle) }
115+
result.forEach { testBundleIdentifier.put(it, bundle) }
126116

127117
return result
128118
}

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

+9-4
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,34 @@ package com.malinskiy.marathon.apple.bin
33
import com.google.gson.Gson
44
import com.malinskiy.marathon.apple.bin.codesign.Codesign
55
import com.malinskiy.marathon.apple.bin.getconf.Getconf
6+
import com.malinskiy.marathon.apple.bin.ioreg.Ioreg
67
import com.malinskiy.marathon.apple.bin.lipo.Lipo
78
import com.malinskiy.marathon.apple.bin.nm.Nm
89
import com.malinskiy.marathon.apple.bin.plistbuddy.PlistBuddy
10+
import com.malinskiy.marathon.apple.bin.swvers.SwVers
11+
import com.malinskiy.marathon.apple.bin.systemprofiler.SystemProfiler
912
import com.malinskiy.marathon.apple.bin.xcodeselect.Xcodeselect
1013
import com.malinskiy.marathon.apple.bin.xcrun.Xcrun
1114
import com.malinskiy.marathon.apple.cmd.CommandExecutor
1215
import com.malinskiy.marathon.config.Configuration
1316
import com.malinskiy.marathon.config.vendor.VendorConfiguration
17+
import com.malinskiy.marathon.config.vendor.apple.TimeoutConfiguration
1418

1519
class AppleBinaryEnvironment(
1620
commandExecutor: CommandExecutor,
1721
configuration: Configuration,
18-
vendorConfiguration: VendorConfiguration.IOSConfiguration,
22+
timeoutConfiguration: TimeoutConfiguration,
1923
gson: Gson
2024
) {
21-
private val timeoutConfiguration = vendorConfiguration.timeoutConfiguration
22-
2325
val codesign: Codesign = Codesign(commandExecutor, timeoutConfiguration)
2426
val getconf: Getconf =
2527
Getconf(commandExecutor, timeoutConfiguration)
2628
val lipo: Lipo = Lipo(commandExecutor, timeoutConfiguration)
2729
val nm: Nm = Nm(commandExecutor, timeoutConfiguration)
2830
val plistBuddy = PlistBuddy(commandExecutor, timeoutConfiguration)
2931
val xcodeselect: Xcodeselect = Xcodeselect(commandExecutor, timeoutConfiguration)
30-
val xcrun: Xcrun = Xcrun(commandExecutor, configuration, vendorConfiguration, gson)
32+
val ioreg: Ioreg = Ioreg(commandExecutor, timeoutConfiguration)
33+
val systemProfiler: SystemProfiler = SystemProfiler(commandExecutor, timeoutConfiguration)
34+
val swvers: SwVers = SwVers(commandExecutor, timeoutConfiguration)
35+
val xcrun: Xcrun = Xcrun(commandExecutor, configuration, timeoutConfiguration, gson)
3136
}

vendor/vendor-apple/base/src/main/kotlin/com/malinskiy/marathon/apple/bin/codesign/Codesign.kt

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

33
import com.malinskiy.marathon.apple.cmd.CommandExecutor
44
import com.malinskiy.marathon.apple.cmd.CommandResult
5-
import com.malinskiy.marathon.config.vendor.apple.ios.TimeoutConfiguration
5+
import com.malinskiy.marathon.config.vendor.apple.TimeoutConfiguration
66
import com.malinskiy.marathon.exceptions.DeviceSetupException
77
import java.time.Duration
88

0 commit comments

Comments
 (0)