diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2023-10-11 16:19:25 +0200 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2023-10-13 14:49:41 +0200 |
| commit | 40f7b0d6fc509e3ecde4bc61ae80eec69d77eeeb (patch) | |
| tree | 57914c93f42f1dba0f8dd05eccde74c77e8cbed5 /ios/PacketTunnelCoreTests | |
| parent | 127bd501fbcc7d1eb27f8f39e603bb7bf9e9b472 (diff) | |
| download | mullvadvpn-40f7b0d6fc509e3ecde4bc61ae80eec69d77eeeb.tar.xz mullvadvpn-40f7b0d6fc509e3ecde4bc61ae80eec69d77eeeb.zip | |
Ensure atomicity between (re)connection attempts
Diffstat (limited to 'ios/PacketTunnelCoreTests')
| -rw-r--r-- | ios/PacketTunnelCoreTests/ActorTests.swift | 94 | ||||
| -rw-r--r-- | ios/PacketTunnelCoreTests/Mocks/TunnelMonitorStub.swift | 2 |
2 files changed, 95 insertions, 1 deletions
diff --git a/ios/PacketTunnelCoreTests/ActorTests.swift b/ios/PacketTunnelCoreTests/ActorTests.swift index 7b8cd69538..c82119759b 100644 --- a/ios/PacketTunnelCoreTests/ActorTests.swift +++ b/ios/PacketTunnelCoreTests/ActorTests.swift @@ -99,4 +99,98 @@ final class ActorTests: XCTestCase { await fulfillment(of: allExpectations, timeout: 1, enforceOrder: true) } + + /** + Each subsequent connection attempt should produce a single change to `state` containing the incremented attempt counter and new relay. + + .connecting (attempt: 0) → .connecting (attempt: 1) → .connecting (attempt: 2) → ... + */ + func testConnectionAttemptTransition() async throws { + let tunnelMonitor = TunnelMonitorStub { _, _ in } + let actor = PacketTunnelActor.mock(tunnelMonitor: tunnelMonitor) + let connectingStateExpectation = expectation(description: "Expect connecting state") + connectingStateExpectation.expectedFulfillmentCount = 5 + + var nextAttemptCount: UInt = 0 + stateSink = await actor.$state + .receive(on: DispatchQueue.main) + .sink { newState in + switch newState { + case .initial: + break + + case let .connecting(connState): + XCTAssertEqual(connState.connectionAttemptCount, nextAttemptCount) + nextAttemptCount += 1 + connectingStateExpectation.fulfill() + + if nextAttemptCount < connectingStateExpectation.expectedFulfillmentCount { + tunnelMonitor.dispatch(.connectionLost, after: .milliseconds(10)) + } + + default: + XCTFail("Received invalid state: \(newState.name).") + } + } + + self.actor = actor + + actor.start(options: StartOptions(launchSource: .app)) + + await fulfillment(of: [connectingStateExpectation], timeout: 1) + } + + /** + Each subsequent re-connection attempt should produce a single change to `state` containing the incremented attempt counter and new relay. + + .reconnecting (attempt: 0) → .reconnecting (attempt: 1) → .reconnecting (attempt: 2) → ... + */ + func testReconnectionAttemptTransition() async throws { + let tunnelMonitor = TunnelMonitorStub { _, _ in } + let actor = PacketTunnelActor.mock(tunnelMonitor: tunnelMonitor) + let connectingStateExpectation = expectation(description: "Expect connecting state") + let connectedStateExpectation = expectation(description: "Expect connected state") + let reconnectingStateExpectation = expectation(description: "Expect reconnecting state") + reconnectingStateExpectation.expectedFulfillmentCount = 5 + + var nextAttemptCount: UInt = 0 + stateSink = await actor.$state + .receive(on: DispatchQueue.main) + .sink { newState in + switch newState { + case .initial: + break + + case .connecting: + connectingStateExpectation.fulfill() + tunnelMonitor.dispatch(.connectionEstablished, after: .milliseconds(10)) + + case .connected: + connectedStateExpectation.fulfill() + tunnelMonitor.dispatch(.connectionLost, after: .milliseconds(10)) + + case let .reconnecting(connState): + XCTAssertEqual(connState.connectionAttemptCount, nextAttemptCount) + nextAttemptCount += 1 + reconnectingStateExpectation.fulfill() + + if nextAttemptCount < reconnectingStateExpectation.expectedFulfillmentCount { + tunnelMonitor.dispatch(.connectionLost, after: .milliseconds(10)) + } + + default: + XCTFail("Received invalid state: \(newState.name).") + } + } + + self.actor = actor + + actor.start(options: StartOptions(launchSource: .app)) + + await fulfillment( + of: [connectingStateExpectation, connectedStateExpectation, reconnectingStateExpectation], + timeout: 1, + enforceOrder: true + ) + } } diff --git a/ios/PacketTunnelCoreTests/Mocks/TunnelMonitorStub.swift b/ios/PacketTunnelCoreTests/Mocks/TunnelMonitorStub.swift index 3b045540b2..4857b03ae2 100644 --- a/ios/PacketTunnelCoreTests/Mocks/TunnelMonitorStub.swift +++ b/ios/PacketTunnelCoreTests/Mocks/TunnelMonitorStub.swift @@ -64,7 +64,7 @@ class TunnelMonitorStub: TunnelMonitorProtocol { func onSleep() {} - private func dispatch(_ event: TunnelMonitorEvent, after delay: DispatchTimeInterval = .never) { + func dispatch(_ event: TunnelMonitorEvent, after delay: DispatchTimeInterval = .never) { if case .never = delay { onEvent?(event) } else { |
