Skip to content

Commit

Permalink
Merge pull request #87 from ably-labs/48-transient-disconnect-timeouts
Browse files Browse the repository at this point in the history
[ECO-4983] Implement the "transient disconnect timeouts" parts of room lifecycle spec
  • Loading branch information
lawrence-forooghian authored Nov 6, 2024
2 parents 20f21c7 + 19c1a27 commit b539224
Show file tree
Hide file tree
Showing 8 changed files with 482 additions and 40 deletions.
2 changes: 1 addition & 1 deletion Example/AblyChatExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ struct ContentView: View {
func showRoomStatus() async throws {
for await status in try await room().status.onChange(bufferingPolicy: .unbounded) {
withAnimation {
if status.current == .attaching {
if status.current.isAttaching {
statusInfo = "\(status.current)...".capitalized
} else {
statusInfo = "\(status.current)".capitalized
Expand Down
2 changes: 1 addition & 1 deletion Example/AblyChatExample/Mocks/MockClients.swift
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ actor MockRoomStatus: RoomStatus {

private func createSubscription() -> MockSubscription<RoomStatusChange> {
let subscription = MockSubscription<RoomStatusChange>(randomElement: {
RoomStatusChange(current: [.attached, .attached, .attached, .attached, .attaching, .attaching, .suspended(error: .createUnknownError())].randomElement()!, previous: .attaching)
RoomStatusChange(current: [.attached, .attached, .attached, .attached, .attaching(error: nil), .attaching(error: nil), .suspended(error: .createUnknownError())].randomElement()!, previous: .attaching(error: nil))
}, interval: 8)
mockSubscriptions.append(subscription)
return subscription
Expand Down
164 changes: 145 additions & 19 deletions Sources/AblyChat/RoomLifecycleManager.swift

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion Sources/AblyChat/RoomStatus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public protocol RoomStatus: AnyObject, Sendable {

public enum RoomLifecycle: Sendable, Equatable {
case initialized
case attaching
case attaching(error: ARTErrorInfo?)
case attached
case detaching
case detached
Expand All @@ -21,6 +21,14 @@ public enum RoomLifecycle: Sendable, Equatable {
// 1. testing (e.g. `#expect(status.isFailed)`)
// 2. testing that a status does _not_ have a particular case (e.g. if !status.isFailed), which a `case` statement cannot succinctly express

public var isAttaching: Bool {
if case .attaching = self {
true
} else {
false
}
}

public var isSuspended: Bool {
if case .suspended = self {
true
Expand Down
17 changes: 17 additions & 0 deletions Tests/AblyChatTests/Helpers/Subscription+RoomStatusChange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ extension Subscription where Element == RoomStatusChange {
var error: ARTErrorInfo
}

struct StatusChangeWithOptionalError {
/// A status change whose `current` has an optional associated error; ``error`` provides access to this error.
var statusChange: RoomStatusChange
/// The error associated with `statusChange.current`.
var error: ARTErrorInfo?
}

func suspendedElements() async -> AsyncCompactMapSequence<Subscription<RoomStatusChange>, Subscription<RoomStatusChange>.StatusChangeWithError> {
compactMap { statusChange in
if case let .suspended(error) = statusChange.current {
Expand All @@ -31,4 +38,14 @@ extension Subscription where Element == RoomStatusChange {
}
}
}

func attachingElements() async -> AsyncCompactMapSequence<Subscription<RoomStatusChange>, Subscription<RoomStatusChange>.StatusChangeWithOptionalError> {
compactMap { statusChange in
if case let .attaching(error) = statusChange.current {
StatusChangeWithOptionalError(statusChange: statusChange, error: error)
} else {
nil
}
}
}
}
13 changes: 11 additions & 2 deletions Tests/AblyChatTests/Helpers/TestLogger.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
@testable import AblyChat

struct TestLogger: InternalLogger {
func log(message _: String, level _: LogLevel, codeLocation _: CodeLocation) {
// No-op; currently we don’t log in tests to keep the test logs easy to read. Can reconsider if necessary.
// By default, we don’t log in tests to keep the test logs easy to read. You can set this property to `true` to temporarily turn logging on if you want to debug a test.
static let loggingEnabled = false

private let underlyingLogger = DefaultInternalLogger(logHandler: nil, logLevel: .trace)

func log(message: String, level: LogLevel, codeLocation: CodeLocation) {
guard Self.loggingEnabled else {
return
}

underlyingLogger.log(message: message, level: level, codeLocation: codeLocation)
}
}
27 changes: 27 additions & 0 deletions Tests/AblyChatTests/Mocks/MockSimpleClock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,36 @@ import Foundation

/// A mock implementation of ``SimpleClock`` which records its arguments but does not actually sleep.
actor MockSimpleClock: SimpleClock {
private let sleepBehavior: SleepBehavior

enum SleepBehavior {
case success
case fromFunction(@Sendable () async throws -> Void)
}

init(sleepBehavior: SleepBehavior? = nil) {
self.sleepBehavior = sleepBehavior ?? .success
_sleepCallArgumentsAsyncSequence = AsyncStream<TimeInterval>.makeStream()
}

private(set) var sleepCallArguments: [TimeInterval] = []

/// Emits an element each time ``sleep(timeInterval:)`` is called.
var sleepCallArgumentsAsyncSequence: AsyncStream<TimeInterval> {
_sleepCallArgumentsAsyncSequence.stream
}

private let _sleepCallArgumentsAsyncSequence: (stream: AsyncStream<TimeInterval>, continuation: AsyncStream<TimeInterval>.Continuation)

func sleep(timeInterval: TimeInterval) async throws {
sleepCallArguments.append(timeInterval)
_sleepCallArgumentsAsyncSequence.continuation.yield(timeInterval)

switch sleepBehavior {
case .success:
break
case let .fromFunction(function):
try await function()
}
}
}
Loading

0 comments on commit b539224

Please sign in to comment.