Replace RxSwift for Async queue (#4026)

* Remove RxSwift from CacheBundleBuilder, CacheFrameworkBuilder, CacheXCFrameworkBuilder and RxBlocking from project

* Remove unneeded import

* Apply AsyncParsableCommand to LintCodeCommand

* Replace RxSwift for AsyncQueue

* refactor: remove function

* fix: compilation error

Co-authored-by: Daniele Formichelli <df@bendingspoons.com>
This commit is contained in:
Alfredo Delli Bovi 2022-01-22 14:04:04 +01:00 committed by GitHub
parent 0540a660cc
commit d06735adcb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 155 additions and 273 deletions

View File

@ -35,16 +35,16 @@ public struct TuistAnalyticsDispatcher: AsyncQueueDispatching {
public var identifier = TuistAnalyticsDispatcher.dispatcherId
public func dispatch(event: AsyncQueueEvent, completion: @escaping () -> Void) throws {
public func dispatch(event: AsyncQueueEvent, completion: @escaping () throws -> Void) throws {
guard let commandEvent = event as? CommandEvent else { return }
Task.detached {
_ = try await backends.concurrentMap { try? await $0.send(commandEvent: commandEvent) }
completion()
try completion()
}
}
public func dispatchPersisted(data: Data, completion: @escaping () -> Void) throws {
public func dispatchPersisted(data: Data, completion: @escaping () throws -> Void) throws {
let decoder = JSONDecoder()
let commandEvent = try decoder.decode(CommandEvent.self, from: data)
return try dispatch(event: commandEvent, completion: completion)

View File

@ -1,6 +1,5 @@
import Foundation
import Queuer
import RxSwift
import TuistCore
import TuistSupport
@ -8,18 +7,16 @@ public protocol AsyncQueuing {
/// It dispatches the given event.
/// - Parameter event: Event to be dispatched.
/// - Parameter didPersistEvent: It's called when the event has been persisted, to make sure it can't get lost
func dispatch<T: AsyncQueueEvent>(event: T, didPersistEvent: @escaping () -> Void)
func dispatch<T: AsyncQueueEvent>(event: T) throws
}
public class AsyncQueue: AsyncQueuing {
// MARK: - Attributes
private let disposeBag = DisposeBag()
private let queue: Queuing
private let ciChecker: CIChecking
private let persistor: AsyncQueuePersisting
private var dispatchers: [String: AsyncQueueDispatching] = [:]
private let persistedEventsSchedulerType: SchedulerType
public static let sharedInstance = AsyncQueue()
@ -27,13 +24,11 @@ public class AsyncQueue: AsyncQueuing {
init(queue: Queuing = Queuer.shared,
ciChecker: CIChecking = CIChecker(),
persistor: AsyncQueuePersisting = AsyncQueuePersistor(),
persistedEventsSchedulerType: SchedulerType = AsyncQueue.schedulerType())
persistor: AsyncQueuePersisting = AsyncQueuePersistor())
{
self.queue = queue
self.ciChecker = ciChecker
self.persistor = persistor
self.persistedEventsSchedulerType = persistedEventsSchedulerType
}
public func register(dispatcher: AsyncQueueDispatching) {
@ -47,7 +42,7 @@ public class AsyncQueue: AsyncQueuing {
queue.resume()
}
public func dispatch<T: AsyncQueueEvent>(event: T, didPersistEvent: @escaping () -> Void) {
public func dispatch<T: AsyncQueueEvent>(event: T) throws {
guard let dispatcher = dispatchers[event.dispatcherId] else {
logger.error("Couldn't find dispatcher with id: \(event.dispatcherId)")
return
@ -56,17 +51,9 @@ public class AsyncQueue: AsyncQueuing {
// We persist the event in case the dispatching is halted because Tuist's
// process exits. In that case we want to retry again the next time there's
// opportunity for that.
let writeCompletable = persistor.write(event: event)
_ = writeCompletable.subscribe { _ in
// Queue event to send
let operation = self.liveDispatchOperation(event: event, dispatcher: dispatcher)
self.queue.addOperation(operation)
didPersistEvent()
}
}
public static func schedulerType() -> SchedulerType {
SerialDispatchQueueScheduler(queue: dispatchQueue(), internalSerialQueueName: "tuist-async-queue")
try persistor.write(event: event)
let operation = liveDispatchOperation(event: event, dispatcher: dispatcher)
queue.addOperation(operation)
}
// MARK: - Private
@ -76,7 +63,7 @@ public class AsyncQueue: AsyncQueuing {
logger.debug("Dispatching event with ID '\(event.id.uuidString)' to '\(dispatcher.identifier)'")
do {
try dispatcher.dispatch(event: event) {
_ = self.persistor.delete(event: event)
try self.persistor.delete(event: event)
operation.success = true
}
} catch {
@ -85,9 +72,9 @@ public class AsyncQueue: AsyncQueuing {
}
}
private func dispatchPersisted(eventTuple: AsyncQueueEventTuple) {
private func dispatchPersisted(eventTuple: AsyncQueueEventTuple) throws {
guard let dispatcher = dispatchers.first(where: { $0.key == eventTuple.dispatcherId })?.value else {
deletePersistedEvent(filename: eventTuple.filename)
try deletePersistedEvent(filename: eventTuple.filename)
logger.error("Couldn't find dispatcher for persisted event with id: \(eventTuple.dispatcherId)")
return
}
@ -103,7 +90,7 @@ public class AsyncQueue: AsyncQueuing {
do {
logger.debug("Dispatching persisted event with ID '\(event.id.uuidString)' to '\(dispatcher.identifier)'")
try dispatcher.dispatchPersisted(data: event.data) {
self.deletePersistedEvent(filename: event.filename)
try self.deletePersistedEvent(filename: event.filename)
}
} catch {
logger.debug("Failed to dispatch persisted event with ID '\(event.id.uuidString)' to '\(dispatcher.identifier)'")
@ -117,24 +104,17 @@ public class AsyncQueue: AsyncQueuing {
}
private func loadEvents() {
persistor
.readAll()
.subscribe(on: persistedEventsSchedulerType)
.subscribe(onSuccess: { events in
events.forEach(self.dispatchPersisted)
}, onFailure: { error in
logger.debug("Error loading persisted events: \(error)")
})
.disposed(by: disposeBag)
do {
let events = try persistor.readAll()
for event in events {
try dispatchPersisted(eventTuple: event)
}
} catch {
logger.debug("Error loading persisted events: \(error)")
}
}
private func deletePersistedEvent(filename: String) {
persistor.delete(filename: filename).subscribe().disposed(by: disposeBag)
}
// MARK: Private & Static
private static func dispatchQueue() -> DispatchQueue {
DispatchQueue(label: "io.tuist.async-queue", qos: .background)
private func deletePersistedEvent(filename: String) throws {
try persistor.delete(filename: filename)
}
}

View File

@ -1,5 +1,4 @@
import Foundation
import RxSwift
import TSCBasic
import TuistCore
import TuistSupport
@ -8,19 +7,19 @@ public typealias AsyncQueueEventTuple = (dispatcherId: String, id: UUID, date: D
public protocol AsyncQueuePersisting {
/// Reads all the persisted events and returns them.
func readAll() -> Single<[AsyncQueueEventTuple]>
func readAll() throws -> [AsyncQueueEventTuple]
/// Persiss a given event.
/// - Parameter event: Event to be persisted.
func write<T: AsyncQueueEvent>(event: T) -> Completable
func write<T: AsyncQueueEvent>(event: T) throws
/// Deletes the given event from disk.
/// - Parameter event: Event to be deleted.
func delete<T: AsyncQueueEvent>(event: T) -> Completable
func delete<T: AsyncQueueEvent>(event: T) throws
/// Deletes the given file name from disk.
/// - Parameter filename: Name of the file to be deleted.
func delete(filename: String) -> Completable
func delete(filename: String) throws
}
final class AsyncQueuePersistor: AsyncQueuePersisting {
@ -35,72 +34,53 @@ final class AsyncQueuePersistor: AsyncQueuePersisting {
self.directory = directory
}
func write<T: AsyncQueueEvent>(event: T) -> Completable {
Completable.create { observer -> Disposable in
let path = self.directory.appending(component: self.filename(event: event))
func write<T: AsyncQueueEvent>(event: T) throws {
let path = directory.appending(component: filename(event: event))
try createDirectoryIfNeeded()
let data = try jsonEncoder.encode(event)
try data.write(to: path.url)
}
func delete<T: AsyncQueueEvent>(event: T) throws {
try delete(filename: filename(event: event))
}
func delete(filename: String) throws {
let path = directory.appending(component: filename)
guard FileHandler.shared.exists(path) else { return }
try FileHandler.shared.delete(path)
}
func readAll() throws -> [AsyncQueueEventTuple] {
let paths = FileHandler.shared.glob(directory, glob: "*.json")
var events: [AsyncQueueEventTuple] = []
paths.forEach { eventPath in
let fileName = eventPath.basenameWithoutExt
let components = fileName.split(separator: ".")
guard components.count == 3,
let timestamp = Double(components[0]),
let id = UUID(uuidString: String(components[2]))
else {
/// Changing the naming convention is a breaking change. When detected
/// we delete the event.
try? FileHandler.shared.delete(eventPath)
return
}
do {
try self.createDirectoryIfNeeded()
let data = try self.jsonEncoder.encode(event)
try data.write(to: path.url)
observer(.completed)
let data = try Data(contentsOf: eventPath.url)
let event = (
dispatcherId: String(components[1]),
id: id,
date: Date(timeIntervalSince1970: timestamp),
data: data,
filename: eventPath.basename
)
events.append(event)
} catch {
observer(.error(error))
try? FileHandler.shared.delete(eventPath)
}
return Disposables.create()
}
}
func delete<T: AsyncQueueEvent>(event: T) -> Completable {
delete(filename: filename(event: event))
}
func delete(filename: String) -> Completable {
Completable.create { observer -> Disposable in
let path = self.directory.appending(component: filename)
guard FileHandler.shared.exists(path) else { return Disposables.create() }
do {
try FileHandler.shared.delete(path)
observer(.completed)
} catch {
observer(.error(error))
}
return Disposables.create()
}
}
func readAll() -> Single<[AsyncQueueEventTuple]> {
Single.create { observer -> Disposable in
let paths = FileHandler.shared.glob(self.directory, glob: "*.json")
var events: [AsyncQueueEventTuple] = []
paths.forEach { eventPath in
let fileName = eventPath.basenameWithoutExt
let components = fileName.split(separator: ".")
guard components.count == 3,
let timestamp = Double(components[0]),
let id = UUID(uuidString: String(components[2]))
else {
/// Changing the naming convention is a breaking change. When detected
/// we delete the event.
try? FileHandler.shared.delete(eventPath)
return
}
do {
let data = try Data(contentsOf: eventPath.url)
let event = (
dispatcherId: String(components[1]),
id: id,
date: Date(timeIntervalSince1970: timestamp),
data: data,
filename: eventPath.basename
)
events.append(event)
} catch {
try? FileHandler.shared.delete(eventPath)
}
}
observer(.success(events))
return Disposables.create()
}
return events
}
// MARK: - Private

View File

@ -26,7 +26,7 @@ public class MockAsyncQueueDispatcher: AsyncQueueDispatching {
public var invokedDispatchParametersEventsList = [AsyncQueueEvent]()
public var stubbedDispatchError: Error?
public func dispatch(event: AsyncQueueEvent, completion: @escaping () -> Void) throws {
public func dispatch(event: AsyncQueueEvent, completion: @escaping () throws -> Void) throws {
invokedDispatch = true
invokedDispatchCount += 1
invokedDispatchParameterEvent = event
@ -36,7 +36,7 @@ public class MockAsyncQueueDispatcher: AsyncQueueDispatching {
throw error
}
invokedDispatchCallBack()
completion()
try completion()
}
public var invokedDispatchPersisted = false
@ -46,7 +46,7 @@ public class MockAsyncQueueDispatcher: AsyncQueueDispatching {
public var invokedDispatchPersistedParametersDataList = [Data]()
public var stubbedDispatchPersistedError: Error?
public func dispatchPersisted(data: Data, completion: @escaping () -> Void) throws {
public func dispatchPersisted(data: Data, completion: @escaping () throws -> Void) throws {
invokedDispatchPersisted = true
invokedDispatchPersistedCount += 1
invokedDispatchPersistedDataParameter = data
@ -56,6 +56,6 @@ public class MockAsyncQueueDispatcher: AsyncQueueDispatching {
throw error
}
invokedDispatchPersistedCallBack()
completion()
try completion()
}
}

View File

@ -1,5 +1,4 @@
import Foundation
import RxSwift
import TuistAsyncQueue
import TuistCore
@ -8,9 +7,9 @@ public final class MockAsyncQueuePersistor<U: AsyncQueueEvent>: AsyncQueuePersis
public var invokedReadAll = false
public var invokedReadAllCount = 0
public var stubbedReadAllResult: Single<[AsyncQueueEventTuple]> = Single.just([])
public var stubbedReadAllResult: [AsyncQueueEventTuple] = []
public func readAll() -> Single<[AsyncQueueEventTuple]> {
public func readAll() -> [AsyncQueueEventTuple] {
invokedReadAll = true
invokedReadAllCount += 1
return stubbedReadAllResult
@ -20,45 +19,39 @@ public final class MockAsyncQueuePersistor<U: AsyncQueueEvent>: AsyncQueuePersis
public var invokedWriteCount = 0
public var invokedWriteEvent: U?
public var invokedWriteEvents = [U]()
public var stubbedWriteResult: Completable = .empty()
public func write<T: AsyncQueueEvent>(event: T) -> Completable {
public func write<T: AsyncQueueEvent>(event: T) {
invokedWrite = true
invokedWriteCount += 1
if let event = event as? U {
invokedWriteEvent = event
invokedWriteEvents.append(event)
}
return stubbedWriteResult
}
public var invokedDeleteEventCount = 0
public var invokedDeleteCallBack: () -> Void = {}
public var invokedDeleteEvent: U?
public var invokedDeleteEvents = [U]()
public var stubbedDeleteEventResult: Completable = .empty()
public func delete<T: AsyncQueueEvent>(event: T) -> Completable {
public func delete<T: AsyncQueueEvent>(event: T) {
invokedDeleteEventCount += 1
if let event = event as? U {
invokedDeleteEvent = event
invokedDeleteEvents.append(event)
}
invokedDeleteCallBack()
return stubbedDeleteEventResult
}
public var invokedDeleteFilename = false
public var invokedDeleteFilenameCount = 0
public var invokedDeleteFilenameParameter: String?
public var invokedDeleteFilenameParametersList = [String]()
public var stubbedDeleteFilenameResult: Completable = .empty()
public func delete(filename: String) -> Completable {
public func delete(filename: String) {
invokedDeleteFilename = true
invokedDeleteFilenameCount += 1
invokedDeleteFilenameParameter = filename
invokedDeleteFilenameParametersList.append(filename)
return stubbedDeleteFilenameResult
}
}

View File

@ -10,11 +10,10 @@ public class MockAsyncQueuer: AsyncQueuing {
public var invokedDispatchParameters: (event: Any, Void)?
public var invokedDispatchParametersList = [(event: Any, Void)]()
public func dispatch<T: AsyncQueueEvent>(event: T, didPersistEvent: @escaping () -> Void) {
public func dispatch<T: AsyncQueueEvent>(event: T) throws {
invokedDispatch = true
invokedDispatchCount += 1
invokedDispatchParameters = (event, ())
invokedDispatchParametersList.append((event, ()))
didPersistEvent()
}
}

View File

@ -73,7 +73,8 @@ public final class CacheXCFrameworkBuilder: CacheArtifactBuilding {
}
frameworkpaths.append(self.frameworkPath(fromArchivePath: deviceArchivePath, productName: productName))
let xcframeworkPath = outputDirectory.appending(component: "\(productName).xcframework")
try await self.buildXCFramework(frameworks: frameworkpaths, output: xcframeworkPath)
try await self.xcodeBuildController.createXCFramework(frameworks: frameworkpaths, output: xcframeworkPath)
.printFormattedOutput()
try FileHandler.shared.move(
from: xcframeworkPath,
@ -85,10 +86,6 @@ public final class CacheXCFrameworkBuilder: CacheArtifactBuilding {
// MARK: - Fileprivate
fileprivate func buildXCFramework(frameworks: [AbsolutePath], output: AbsolutePath) async throws {
try await xcodeBuildController.createXCFramework(frameworks: frameworks, output: output).printFormattedOutput()
}
fileprivate func deviceBuild(projectTarget: XcodeBuildTarget,
scheme: String,
platform: Platform,

View File

@ -7,9 +7,9 @@ public protocol AsyncQueueDispatching {
/// Dispatches a given event.
/// - Parameter event: Event to be dispatched.
func dispatch(event: AsyncQueueEvent, completion: @escaping () -> Void) throws
func dispatch(event: AsyncQueueEvent, completion: @escaping () throws -> Void) throws
/// Dispatch a persisted event.
/// - Parameter data: Serialized data of the event.
func dispatchPersisted(data: Data, completion: @escaping () -> Void) throws
func dispatchPersisted(data: Data, completion: @escaping () throws -> Void) throws
}

View File

@ -1,37 +0,0 @@
import Combine
import Foundation
/// `TuistProcess` is a wrapper on top of the cli command to provide
/// an asynchronous way to exit the process, which waits for all `futureTask`
/// to be completed, with a maximum threshold of time of `maximumWaitingTime` seconds
final class TuistProcess {
static let shared = TuistProcess()
private var futureTasks: [Future<Void, Never>] = []
private let maximumWaitingTime = DispatchTimeInterval.seconds(2)
private init() {}
/// `add` a task that needs to complete before tuist process ends.
/// Note that tasks will only have `maximumWaitingTime` seconds to complete
/// after tuist is ready to exit, otherwise they will be canceled
func add(futureTask: Future<Void, Never>) {
futureTasks.append(futureTask)
}
/// `asyncExit` will make sure that all important async tasks
/// complete before it `exit`s the process
func asyncExit(_ code: Int32 = 0) -> Never {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
let cancellable = Publishers.MergeMany(futureTasks).collect().sink { _ in
dispatchGroup.leave()
}
// Set `maximumWaitingTime` seconds as a parachute timeout in case something
// goes wrong and events don't cmoplete: we don't want tuist's
// process to hang forever
_ = dispatchGroup.wait(timeout: DispatchTime.now() + maximumWaitingTime)
cancellable.cancel()
exit(code)
}
}

View File

@ -1,5 +1,4 @@
import ArgumentParser
import Combine
import Foundation
import TuistAsyncQueue
import TuistSupport
@ -32,7 +31,7 @@ public class TrackableCommand: TrackableParametersDelegate {
self.asyncQueue = asyncQueue
}
func run() async throws -> Future<Void, Never> {
func run() async throws {
let timer = clock.startTimer()
if let command = command as? HasTrackableParameters {
type(of: command).analyticsDelegate = self
@ -53,11 +52,7 @@ public class TrackableCommand: TrackableParametersDelegate {
durationInMs: durationInMs
)
let commandEvent = commandEventFactory.make(from: info)
return Future { promise in
self.asyncQueue.dispatch(event: commandEvent) {
promise(.success(()))
}
}
try asyncQueue.dispatch(event: commandEvent)
}
func willRun(withParameters parameters: [String: String]) {

View File

@ -66,7 +66,6 @@ public struct TuistCommand: ParsableCommand {
}
do {
try await execute(command)
TuistProcess.shared.asyncExit()
} catch let error as FatalError {
errorHandler.fatal(error: error)
_exit(exitCode(for: error).rawValue)
@ -85,8 +84,7 @@ public struct TuistCommand: ParsableCommand {
var command = command
if Environment.shared.isStatsEnabled {
let trackableCommand = TrackableCommand(command: command)
let future = try await trackableCommand.run()
TuistProcess.shared.add(futureTask: future)
try await trackableCommand.run()
} else {
if var asyncCommand = command as? AsyncParsableCommand {
try await asyncCommand.runAsync()

View File

@ -24,7 +24,10 @@ enum TuistApp {
}
try TuistSupport.Environment.shared.bootstrap()
try TuistAnalytics.bootstrap(config: ConfigLoader().loadConfig(path: path))
Task.detached(priority: .background) {
try TuistAnalytics.bootstrap(config: ConfigLoader().loadConfig(path: path))
}
await TuistCommand.main()
}

View File

@ -1,5 +1,4 @@
import Foundation
import RxSwift
import TSCBasic
import TuistCore
import TuistSupport
@ -22,15 +21,15 @@ final class AsyncQueuePersistorTests: TuistUnitTestCase {
super.tearDown()
}
func test_write() async throws {
func test_write() throws {
// Given
let event = AnyAsyncQueueEvent(dispatcherId: "dispatcher")
// When
_ = try await subject.write(event: event).value
try subject.write(event: event)
// Then
let got = try await subject.readAll().value
let got = try subject.readAll()
let gotEvent = try XCTUnwrap(got.first)
XCTAssertEqual(gotEvent.dispatcherId, "dispatcher")
XCTAssertEqual(gotEvent.id, event.id)
@ -38,7 +37,7 @@ final class AsyncQueuePersistorTests: TuistUnitTestCase {
XCTAssertEqual(gotEvent.date, normalizedDate)
}
func test_write_whenDirectoryDoesntExist_itCreatesDirectory() async throws {
func test_write_whenDirectoryDoesntExist_itCreatesDirectory() throws {
let temporaryDirectory = try! temporaryPath()
subject = AsyncQueuePersistor(directory: temporaryDirectory.appending(RelativePath("test/")))
@ -46,10 +45,10 @@ final class AsyncQueuePersistorTests: TuistUnitTestCase {
let event = AnyAsyncQueueEvent(dispatcherId: "dispatcher")
// When
_ = try await subject.write(event: event).value
try subject.write(event: event)
// Then
let got = try await subject.readAll().value
let got = try subject.readAll()
let gotEvent = try XCTUnwrap(got.first)
XCTAssertEqual(gotEvent.dispatcherId, "dispatcher")
XCTAssertEqual(gotEvent.id, event.id)
@ -57,18 +56,18 @@ final class AsyncQueuePersistorTests: TuistUnitTestCase {
XCTAssertEqual(gotEvent.date, normalizedDate)
}
func test_delete() async throws {
func test_delete() throws {
// Given
let event = AnyAsyncQueueEvent(dispatcherId: "dispatcher")
_ = try await subject.write(event: event).value
var persistedEvents = try await subject.readAll().value
try subject.write(event: event)
var persistedEvents = try subject.readAll()
XCTAssertEqual(persistedEvents.count, 1)
// When
_ = try await subject.delete(event: event).value
try subject.delete(event: event)
// Then
persistedEvents = try await subject.readAll().value
persistedEvents = try subject.readAll()
XCTAssertEqual(persistedEvents.count, 0)
}
}

View File

@ -1,6 +1,5 @@
import Foundation
import Queuer
import RxSwift
import TuistCore
import TuistSupport
import XCTest
@ -57,8 +56,7 @@ final class AsyncQueueTests: TuistUnitTestCase {
let asyncQueue = AsyncQueue(
queue: queue ?? mockQueuer,
ciChecker: ciChecker ?? mockCIChecker,
persistor: persistor ?? mockPersistor,
persistedEventsSchedulerType: MainScheduler()
persistor: persistor ?? mockPersistor
)
asyncQueue.register(dispatcher: mockAsyncQueueDispatcher1)
asyncQueue.register(dispatcher: mockAsyncQueueDispatcher2)
@ -66,42 +64,34 @@ final class AsyncQueueTests: TuistUnitTestCase {
}
func test_dispatch_eventIsPersisted() throws {
var didComplete = false
// Given
let event = AnyAsyncQueueEvent(dispatcherId: dispatcher1ID)
subject = makeSubject()
// When
subject.dispatch(event: event) {
// Then
guard let persistedEvent = self.mockPersistor.invokedWriteEvent else {
XCTFail("Event not passed to the persistor")
return
}
XCTAssertEqual(event.id, persistedEvent.id)
didComplete = true
try subject.dispatch(event: event)
// Then
guard let persistedEvent = mockPersistor.invokedWriteEvent else {
XCTFail("Event not passed to the persistor")
return
}
XCTAssertTrue(didComplete)
XCTAssertEqual(event.id, persistedEvent.id)
}
func test_dispatch_eventIsQueued() throws {
var didComplete = false
// Given
let event = AnyAsyncQueueEvent(dispatcherId: dispatcher1ID)
subject = makeSubject()
// When
subject.dispatch(event: event) {
// Then
guard let queuedOperation = self.mockQueuer.invokedAddOperationParameterOperation as? ConcurrentOperation else {
XCTFail("Operation not added to the queuer")
return
}
XCTAssertEqual(queuedOperation.name, event.id.uuidString)
didComplete = true
try subject.dispatch(event: event)
// Then
guard let queuedOperation = mockQueuer.invokedAddOperationParameterOperation as? ConcurrentOperation else {
XCTFail("Operation not added to the queuer")
return
}
XCTAssertTrue(didComplete)
XCTAssertEqual(queuedOperation.name, event.id.uuidString)
}
func test_dispatch_eventIsPersistedOnDispatcherSuccess() throws {
@ -113,30 +103,26 @@ final class AsyncQueueTests: TuistUnitTestCase {
expectation.fulfill()
}
// When
subject.dispatch(event: event) {
self.wait(for: [expectation], timeout: self.timeout)
guard let deletedEvent = self.mockPersistor.invokedDeleteEvent else {
XCTFail("Event was not deleted by the persistor")
return
}
// Then
XCTAssertEqual(event.id, deletedEvent.id)
try subject.dispatch(event: event)
wait(for: [expectation], timeout: timeout)
guard let deletedEvent = mockPersistor.invokedDeleteEvent else {
XCTFail("Event was not deleted by the persistor")
return
}
// Then
XCTAssertEqual(event.id, deletedEvent.id)
}
func test_dispatch_eventIsPersistedOnCompletion() throws {
// Given
let event = AnyAsyncQueueEvent(dispatcherId: dispatcher1ID)
subject = makeSubject(queue: Queuer.shared)
let expectation = XCTestExpectation(description: #function)
// When
subject.dispatch(event: event) {
// Then
XCTAssertEqual(self.mockPersistor.invokedWriteEvent?.id, event.id)
expectation.fulfill()
}
wait(for: [expectation], timeout: timeout)
try subject.dispatch(event: event)
// Then
XCTAssertEqual(mockPersistor.invokedWriteEvent?.id, event.id)
}
func test_dispatch_eventIsDispatchedByTheRightDispatcher() throws {
@ -148,19 +134,20 @@ final class AsyncQueueTests: TuistUnitTestCase {
expectation.fulfill()
}
// When
subject.dispatch(event: event) {
self.wait(for: [expectation], timeout: self.timeout)
try subject.dispatch(event: event)
guard let dispatchedEvent = self.mockAsyncQueueDispatcher1.invokedDispatchParameterEvent else {
XCTFail("Event was not dispatched")
return
}
// Then
XCTAssertEqual(event.id, dispatchedEvent.id)
XCTAssertEqual(self.mockAsyncQueueDispatcher1.invokedDispatchCount, 1)
XCTAssertEqual(self.mockAsyncQueueDispatcher2.invokedDispatchCount, 0)
XCTAssertNil(self.mockAsyncQueueDispatcher2.invokedDispatchParameterEvent)
// Then
wait(for: [expectation], timeout: timeout)
guard let dispatchedEvent = mockAsyncQueueDispatcher1.invokedDispatchParameterEvent else {
XCTFail("Event was not dispatched")
return
}
// Then
XCTAssertEqual(event.id, dispatchedEvent.id)
XCTAssertEqual(mockAsyncQueueDispatcher1.invokedDispatchCount, 1)
XCTAssertEqual(mockAsyncQueueDispatcher2.invokedDispatchCount, 0)
XCTAssertNil(mockAsyncQueueDispatcher2.invokedDispatchParameterEvent)
}
func test_dispatch_queuerTriesThreeTimesToDispatch() throws {
@ -179,11 +166,11 @@ final class AsyncQueueTests: TuistUnitTestCase {
}
// When
subject.dispatch(event: event) {
self.wait(for: [expectation], timeout: self.timeout)
// Then
XCTAssertEqual(count, 3)
}
try subject.dispatch(event: event)
// Then
wait(for: [expectation], timeout: timeout)
XCTAssertEqual(count, 3)
}
func test_dispatch_doesNotDeleteEventOnError() throws {
@ -202,12 +189,12 @@ final class AsyncQueueTests: TuistUnitTestCase {
}
// When
subject.dispatch(event: event) {
self.wait(for: [expectation], timeout: self.timeout)
// Then
XCTAssertEqual(count, 3)
XCTAssertEqual(self.mockPersistor.invokedDeleteEventCount, 0)
}
try subject.dispatch(event: event)
// Then
wait(for: [expectation], timeout: timeout)
XCTAssertEqual(count, 3)
XCTAssertEqual(mockPersistor.invokedDeleteEventCount, 0)
}
func test_start_readsPersistedEventsInitialization() throws {
@ -215,7 +202,7 @@ final class AsyncQueueTests: TuistUnitTestCase {
let eventTuple1: AsyncQueueEventTuple = makeEventTuple(id: 1)
let eventTuple2: AsyncQueueEventTuple = makeEventTuple(id: 2)
let eventTuple3: AsyncQueueEventTuple = makeEventTuple(id: 3)
mockPersistor.stubbedReadAllResult = .just([eventTuple1, eventTuple2, eventTuple3])
mockPersistor.stubbedReadAllResult = [eventTuple1, eventTuple2, eventTuple3]
// When
subject = makeSubject()
@ -247,7 +234,7 @@ final class AsyncQueueTests: TuistUnitTestCase {
func test_start_persistedEventIsDispatchedByTheRightDispatcher() throws {
// Given
let eventTuple1: AsyncQueueEventTuple = makeEventTuple(id: 1)
mockPersistor.stubbedReadAllResult = .just([eventTuple1])
mockPersistor.stubbedReadAllResult = [eventTuple1]
let expectation = XCTestExpectation(description: #function)
mockAsyncQueueDispatcher1.invokedDispatchPersistedCallBack = {
@ -273,7 +260,7 @@ final class AsyncQueueTests: TuistUnitTestCase {
// Given
let id: UInt = 1
let eventTuple1: AsyncQueueEventTuple = makeEventTuple(id: id)
mockPersistor.stubbedReadAllResult = .just([eventTuple1])
mockPersistor.stubbedReadAllResult = [eventTuple1]
let expectation = XCTestExpectation(description: #function)
mockAsyncQueueDispatcher1.invokedDispatchPersistedCallBack = {

View File

@ -1,5 +1,4 @@
import Foundation
import RxSwift
import TSCBasic
import TuistCore
import TuistSupport

View File

@ -1,4 +1,3 @@
import RxSwift
import TSCBasic
import TuistCore
import TuistGraph

View File

@ -38,39 +38,29 @@ final class TrackableCommandTests: TuistTestCase {
// Given
makeSubject(flag: true)
let expectedParams = ["flag": "true"]
var didPersisteEvent = false
// When
let future = try await subject.run()
_ = future.sink {
didPersisteEvent = true
}
try await subject.run()
// Then
XCTAssertEqual(mockAsyncQueue.invokedDispatchCount, 1)
let event = try XCTUnwrap(mockAsyncQueue.invokedDispatchParameters?.event as? CommandEvent)
XCTAssertEqual(event.name, "test")
XCTAssertEqual(event.params, expectedParams)
XCTAssertTrue(didPersisteEvent)
}
func test_whenParamsHaveFlagFalse_dispatchesEventWithExpectedParameters() async throws {
// Given
makeSubject(flag: false)
let expectedParams = ["flag": "false"]
var didPersisteEvent = false
// When
let future = try await subject.run()
_ = future.sink {
didPersisteEvent = true
}
try await subject.run()
// Then
XCTAssertEqual(mockAsyncQueue.invokedDispatchCount, 1)
let event = try XCTUnwrap(mockAsyncQueue.invokedDispatchParameters?.event as? CommandEvent)
XCTAssertEqual(event.name, "test")
XCTAssertEqual(event.params, expectedParams)
XCTAssertTrue(didPersisteEvent)
}
}