Emcee
94 строки · 3.9 Кб
1import BalancingBucketQueue
2import CommonTestModels
3import Dispatch
4import Foundation
5import EmceeLogging
6import QueueModels
7import RESTInterfaces
8import RESTMethods
9import RESTServer
10import SynchronousWaiter
11import WorkerAlivenessModels
12import WorkerAlivenessProvider
13import WorkerCapabilities
14import UniqueIdentifierGenerator
15
16public final class ScheduleTestsEndpoint: RESTEndpoint {
17private let testsEnqueuer: TestsEnqueuer
18private let uniqueIdentifierGenerator: UniqueIdentifierGenerator
19private let waitForCapableWorkerTimeout: TimeInterval
20private let workerAlivenessProvider: WorkerAlivenessProvider
21private let workerCapabilityConstraintResolver = WorkerCapabilityConstraintResolver()
22private let workerCapabilitiesStorage: WorkerCapabilitiesStorage
23public let path: RESTPath = RESTMethod.scheduleTests
24public let requestIndicatesActivity = true
25
26public init(
27testsEnqueuer: TestsEnqueuer,
28uniqueIdentifierGenerator: UniqueIdentifierGenerator,
29waitForCapableWorkerTimeout: TimeInterval,
30workerAlivenessProvider: WorkerAlivenessProvider,
31workerCapabilitiesStorage: WorkerCapabilitiesStorage
32) {
33self.testsEnqueuer = testsEnqueuer
34self.uniqueIdentifierGenerator = uniqueIdentifierGenerator
35self.waitForCapableWorkerTimeout = waitForCapableWorkerTimeout
36self.workerAlivenessProvider = workerAlivenessProvider
37self.workerCapabilitiesStorage = workerCapabilitiesStorage
38}
39
40public func handle(payload: ScheduleTestsPayload) throws -> ScheduleTestsResponse {
41try waitForAnySuitableWorker(payload: payload)
42
43try testsEnqueuer.enqueue(
44configuredTestEntries: payload.similarlyConfiguredTestEntries.configuredTestEntries,
45testSplitter: payload.scheduleStrategy.testSplitter,
46prioritizedJob: payload.prioritizedJob
47)
48return .scheduledTests
49}
50
51private func waitForAnySuitableWorker(payload: ScheduleTestsPayload) throws {
52guard !payload.similarlyConfiguredTestEntries.testEntries.isEmpty else { return }
53
54let configurationHasAllRequirementsMet: () -> Bool = {
55self.workerAlivenessProvider.workerAliveness.contains(
56where: { (item: (workerId: WorkerId, aliveness: WorkerAliveness)) -> Bool in
57guard item.aliveness.isInWorkingCondition else { return false }
58return self.workerCapabilityConstraintResolver.requirementsSatisfied(
59requirements: payload.similarlyConfiguredTestEntries.testEntryConfiguration.workerCapabilityRequirements,
60workerCapabilities: self.workerCapabilitiesStorage.workerCapabilities(
61forWorkerId: item.workerId
62)
63)
64}
65)
66}
67
68try SynchronousWaiter().mapErrorIfTimeout(
69work: { waiter in
70try waiter.waitWhile(
71timeout: waitForCapableWorkerTimeout,
72description: "Test entry configuration requirements can be satisfied"
73) {
74configurationHasAllRequirementsMet() == false
75}
76},
77timeoutToErrorTransformation: { timeout -> Error in
78NoSuitableWorkerAppearedError(
79testEntryConfiguration: payload.similarlyConfiguredTestEntries.testEntryConfiguration,
80timeout: timeout.value
81)
82}
83)
84}
85}
86
87private struct NoSuitableWorkerAppearedError: Error, CustomStringConvertible {
88let testEntryConfiguration: TestEntryConfiguration
89let timeout: TimeInterval
90
91var description: String {
92"Some worker requirements cannot be met after waiting for \(timeout.loggableInSeconds()) for any worker with suitable capabilities to appear: \(testEntryConfiguration)"
93}
94}
95