2
import AutomaticTermination
9
import LocalHostDeterminer
10
import MetricsExtensions
13
import QueueServerConfiguration
15
import ResourceLocationResolver
16
import SimulatorPoolModels
20
import UniqueIdentifierGenerator
22
public final class RunTestsCommand: Command {
23
public let name = "runTests"
24
public let description = "Runs tests (easy to use command)"
25
public let arguments: Arguments = [
26
ArgumentDescriptions.queue.asRequired.asMultiple,
27
ArgumentDescriptions.worker.asRequired.asMultiple,
28
ArgumentDescriptions.device.asRequired,
29
ArgumentDescriptions.runtime.asRequired,
30
ArgumentDescriptions.testBundle.asRequired,
31
ArgumentDescriptions.kind.asOptional,
32
ArgumentDescriptions.app.asOptional,
33
ArgumentDescriptions.runner.asOptional,
34
ArgumentDescriptions.test.asOptional,
35
ArgumentDescriptions.retries.asOptional,
36
ArgumentDescriptions.testTimeout.asOptional,
37
ArgumentDescriptions.junit.asOptional,
38
ArgumentDescriptions.trace.asOptional,
39
ArgumentDescriptions.hostname.asOptional,
40
ArgumentDescriptions.locale.asOptional,
41
ArgumentDescriptions.language.asMultiple.asOptional,
42
ArgumentDescriptions.keyboard.asMultiple.asOptional,
46
private let uniqueIdentifierGenerator: UniqueIdentifierGenerator
48
public init(di: DI) throws {
50
self.uniqueIdentifierGenerator = try di.get()
53
public func run(payload: CommandPayload) throws {
54
let hostname: String = try payload.optionalSingleTypedValue(argumentName: ArgumentDescriptions.hostname.name) ?? LocalHostDeterminer.currentHostAddress
55
try HostnameSetup.update(hostname: hostname, di: di)
57
let httpRestServer = HTTPRESTServer(
58
automaticTerminationController: StayAliveTerminationController(),
60
portProvider: AnyAvailablePortProvider(),
64
let queueUrls: [URL] = try payload.nonEmptyCollectionOfValues(argumentName: ArgumentDescriptions.queue.name)
65
let queueDeploymentDestinations = try queueUrls.map { try $0.deploymentDestination() }
67
let workerUrls: [URL] = try payload.nonEmptyCollectionOfValues(argumentName: ArgumentDescriptions.worker.name)
68
let workerDeploymentDestinations = try workerUrls.map { try $0.deploymentDestination() }
70
let resourceLocationResolver: ResourceLocationResolver = try di.get()
71
let testBundlePath: AbsolutePath = try resourceLocationResolver.resolvePath(
72
resourceLocation: .localFilePath(
73
try payload.expectedSingleTypedValue(argumentName: ArgumentDescriptions.testBundle.name)
75
).directlyAccessibleResourcePath()
76
let xcTestBundle = XcTestBundle(location: TestBundleLocation(.localFilePath(testBundlePath.pathString)), testDiscoveryMode: .parseFunctionSymbols)
78
let appBundleLocation: String? = try payload.optionalSingleTypedValue(argumentName: ArgumentDescriptions.app.name)
79
let appBundlePath: AbsolutePath?
80
if let appBundleLocation = appBundleLocation {
81
appBundlePath = try resourceLocationResolver.resolvePath(
82
resourceLocation: .localFilePath(appBundleLocation)
83
).directlyAccessibleResourcePath()
88
let runnerBundleLocation: String? = try payload.optionalSingleTypedValue(argumentName: ArgumentDescriptions.runner.name)
89
let runnerBundlePath: AbsolutePath?
90
if let runnerBundleLocation = runnerBundleLocation {
91
runnerBundlePath = try resourceLocationResolver.resolvePath(
92
resourceLocation: .localFilePath(runnerBundleLocation)
93
).directlyAccessibleResourcePath()
95
runnerBundlePath = nil
98
let buildArtifacts: AppleBuildArtifacts
99
if let runnerBundlePath = runnerBundlePath, let appBundlePath = appBundlePath {
100
buildArtifacts = .iosUiTests(
101
xcTestBundle: xcTestBundle,
102
appBundle: AppBundleLocation(.localFilePath(appBundlePath.pathString)),
103
runner: RunnerAppLocation(.localFilePath(runnerBundlePath.pathString)),
104
additionalApplicationBundles: []
106
} else if let appBundlePath = appBundlePath {
107
buildArtifacts = .iosApplicationTests(
108
xcTestBundle: xcTestBundle,
109
appBundle: AppBundleLocation(.localFilePath(appBundlePath.pathString))
112
buildArtifacts = .iosLogicTests(xcTestBundle: xcTestBundle)
115
let testNamesToRun: [TestName] = try payload.possiblyEmptyCollectionOfValues(argumentName: ArgumentDescriptions.test.name)
116
let numberOfRetries: UInt = try payload.optionalSingleTypedValue(argumentName: ArgumentDescriptions.retries.name) ?? TestArgFileDefaultValues.numberOfRetries
117
let testTimeout: TimeInterval = try payload.optionalSingleTypedValue(argumentName: ArgumentDescriptions.testTimeout.name) ?? TestArgFileDefaultValues.testTimeoutConfiguration.singleTestMaximumDuration
118
let testDestination = TestDestination.appleSimulator(
119
deviceType: try payload.expectedSingleTypedValue(argumentName: ArgumentDescriptions.device.name),
120
kind: try payload.optionalSingleTypedValue(argumentName: ArgumentDescriptions.kind.name) ?? .iOS,
121
version: try payload.expectedSingleTypedValue(argumentName: ArgumentDescriptions.runtime.name)
124
let simulatorLocale: String = try payload.optionalSingleTypedValue(argumentName: ArgumentDescriptions.locale.name) ?? TestArgFileDefaultValues.simulatorLocalizationSettings.localeIdentifier
125
let simulatorLanguages: [String] = try payload.possiblyEmptyCollectionOfValues(argumentName: ArgumentDescriptions.language.name).orIfEmpty(TestArgFileDefaultValues.simulatorLocalizationSettings.languages)
126
let simulatorKeyboards: [String] = try payload.possiblyEmptyCollectionOfValues(argumentName: ArgumentDescriptions.keyboard.name).orIfEmpty(TestArgFileDefaultValues.simulatorLocalizationSettings.keyboards)
127
let passcodeKeyboards: [String] = try payload.possiblyEmptyCollectionOfValues(argumentName: ArgumentDescriptions.passcodeKeyboard.name).orIfEmpty(TestArgFileDefaultValues.simulatorLocalizationSettings.passcodeKeyboards)
129
var testsToRun: [TestToRun] = []
130
if testNamesToRun.isEmpty {
131
testsToRun = [.allDiscoveredTests]
133
testsToRun = testNamesToRun.map { .testName($0) }
136
var environment = [String: String]()
137
ProcessInfo.processInfo.environment.forEach { (key: String, value: String) in
138
if key.starts(with: "EMCEE_") {
139
environment[String(key.dropFirst("EMCEE_".count))] = value
143
let testArgFile = TestArgFile(
146
buildArtifacts: buildArtifacts,
147
developerDir: TestArgFileDefaultValues.developerDir,
148
environment: environment,
149
userInsertedLibraries: [],
150
numberOfRetries: numberOfRetries,
151
testRetryMode: TestArgFileDefaultValues.testRetryMode,
152
logCapturingMode: TestArgFileDefaultValues.logCapturingMode,
153
runnerWasteCleanupPolicy: TestArgFileDefaultValues.runnerWasteCleanupPolicy,
155
scheduleStrategy: TestArgFileDefaultValues.scheduleStrategy,
156
simulatorOperationTimeouts: TestArgFileDefaultValues.simulatorOperationTimeouts,
157
simulatorSettings: SimulatorSettings(
158
simulatorLocalizationSettings: TestArgFileDefaultValues.simulatorLocalizationSettings
159
.with(localeIdentifier: simulatorLocale)
160
.with(languages: simulatorLanguages)
161
.with(keyboards: simulatorKeyboards)
162
.with(passcodeKeyboards: passcodeKeyboards),
163
simulatorKeychainSettings: TestArgFileDefaultValues.simulatorSettings.simulatorKeychainSettings,
164
watchdogSettings: TestArgFileDefaultValues.simulatorSettings.watchdogSettings
166
testDestination: testDestination,
167
testTimeoutConfiguration: TestTimeoutConfiguration(singleTestMaximumDuration: testTimeout, testRunnerMaximumSilenceDuration: testTimeout),
168
testAttachmentLifetime: TestArgFileDefaultValues.testAttachmentLifetime,
169
testsToRun: testsToRun,
170
workerCapabilityRequirements: TestArgFileDefaultValues.workerCapabilityRequirements
173
prioritizedJob: PrioritizedJob(
174
analyticsConfiguration: AnalyticsConfiguration(),
175
jobGroupId: JobGroupId(uniqueIdentifierGenerator.generate()),
176
jobGroupPriority: .medium,
177
jobId: JobId("autoJobId_" + uniqueIdentifierGenerator.generate()),
180
testDestinationConfigurations: []
183
let queueServerConfiguration = QueueServerConfiguration(
184
globalAnalyticsConfiguration: AnalyticsConfiguration(),
185
checkAgainTimeInterval: QueueServerConfigurationDefaultValues.checkAgainTimeInterval,
186
queueServerDeploymentDestinations: queueDeploymentDestinations,
187
queueServerTerminationPolicy: QueueServerConfigurationDefaultValues.queueServerTerminationPolicy,
188
workerDeploymentDestinations: workerDeploymentDestinations,
189
defaultWorkerSpecificConfiguration: WorkerSpecificConfigurationDefaultValues.defaultWorkerConfiguration,
190
workerStartMode: QueueServerConfigurationDefaultValues.workerStartMode,
191
useOnlyIPv4: QueueServerConfigurationDefaultValues.useOnlyIPv4,
192
portRange: QueueServerConfigurationDefaultValues.defaultQueuePortRange
195
try RunTestsOnRemoteQueueLogic(di: di).run(
196
commonReportOutput: ReportOutput(
197
junit: try payload.optionalSingleTypedValue(argumentName: ArgumentDescriptions.junit.name),
198
tracingReport: try payload.optionalSingleTypedValue(argumentName: ArgumentDescriptions.trace.name)
200
emceeVersion: EmceeVersion.version,
202
logger: try di.get(),
203
queueServerConfiguration: queueServerConfiguration,
204
remoteCacheConfig: nil,
205
tempFolder: try TemporaryFolder(),
206
testArgFile: testArgFile,
207
httpRestServer: httpRestServer
212
extension TestName: ParsableArgument {
213
public init(argumentValue: String) throws {
214
self = try Self.createFromTestNameString(stringValue: argumentValue)
218
extension Double: ParsableArgument {
219
public init(argumentValue: String) throws {
220
guard let value = Self(argumentValue) else {
221
throw GenericParseError<Self>(argumentValue: argumentValue)