From a96818b40933e7ac6850f58a3efd987400d0f4d0 Mon Sep 17 00:00:00 2001 From: Edwin Wu Date: Tue, 11 Nov 2025 18:06:06 -0800 Subject: [PATCH] [NC Apple coverage] Refactor `GNCNWFrameworkServerSocket` and add more tests. PiperOrigin-RevId: 831142947 --- .../GNCNWFrameworkServerSocket+Internal.h | 15 ++ .../WiFiCommon/GNCNWFrameworkServerSocket.m | 15 +- .../Tests/GNCNWFrameworkServerSocketTest.m | 139 ++++++++++++++++++ 3 files changed, 164 insertions(+), 5 deletions(-) diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkServerSocket+Internal.h b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkServerSocket+Internal.h index d1b57c9f..9c20d743 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkServerSocket+Internal.h +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkServerSocket+Internal.h @@ -96,6 +96,21 @@ NS_ASSUME_NONNULL_BEGIN state:(nw_connection_state_t)state error:(nullable nw_error_t)error; +/** + * Handles a new incoming connection from the listener. + * + * @param connection The new connection object. + */ +- (void)handleNewConnection:(nw_connection_t)connection; + +/** + * Configures a connection's queue and state handler, then starts it. + * NOTE: This method should not be called directly except for testing purposes. + * + * @param connection The connection to configure and start. + */ +- (void)configureAndStartConnection:(nw_connection_t)connection; + @end NS_ASSUME_NONNULL_END diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkServerSocket.m b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkServerSocket.m index ca5ae11f..0aa89f5b 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkServerSocket.m +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkServerSocket.m @@ -66,6 +66,7 @@ NS_ASSUME_NONNULL_BEGIN @implementation GNCNWFrameworkServerSocket { NSInteger _port; + // TODO: b/434870979 - Use a dispatch_semaphore instead of a condition to simplify the logic. NSCondition *_condition; dispatch_queue_t _dispatchQueue; id _listener; @@ -288,17 +289,18 @@ NS_ASSUME_NONNULL_BEGIN // This doesn't start flaking until 0.0005 seconds, so 0.5 should be plenty of time. BOOL didSignal = [_condition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]]; + // We timed out waiting for the listener to transition into a state. + if (!didSignal) { + _listenerError = [NSError errorWithDomain:GNCNWFrameworkErrorDomain + code:GNCNWFrameworkErrorTimedOut + userInfo:nil]; + } if (error != nil) { *error = [_listenerError copy]; } [_condition unlock]; - - // We timed out waiting for the listener to transition into a state. if (!didSignal) { [self close]; - _listenerError = [NSError errorWithDomain:GNCNWFrameworkErrorDomain - code:GNCNWFrameworkErrorTimedOut - userInfo:nil]; return NO; } @@ -322,7 +324,10 @@ NS_ASSUME_NONNULL_BEGIN // Keep track of any incoming connections. We must keep a reference otherwise the connection will // be dropped. [self.pendingConnections addObject:connection]; + [self configureAndStartConnection:connection]; +} +- (void)configureAndStartConnection:(nw_connection_t)connection { // Register for state changes so we can clean up any failed/canceled connections and inform Nearby // of any ready connections when asked. nw_connection_set_queue(connection, _dispatchQueue); diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCNWFrameworkServerSocketTest.m b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCNWFrameworkServerSocketTest.m index 1344646c..626783e7 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCNWFrameworkServerSocketTest.m +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCNWFrameworkServerSocketTest.m @@ -25,6 +25,7 @@ #import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkSocket.h" #import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWConnection.h" #import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWListener.h" +#import "third_party/objective_c/ocmock/v3/Source/OCMock/OCMock.h" @interface GNCNWFrameworkServerSocketTest : XCTestCase @end @@ -90,6 +91,59 @@ XCTAssertNil(error, @"error should be nil when accept is successful"); } +- (void)testAcceptWithError_FailureWhenListenerFails API_AVAILABLE(ios(13.0)) { + // Simulate listener entering a failed state. + _serverSocket.listenerState = nw_listener_state_failed; + _serverSocket.listenerError = [NSError errorWithDomain:@"Test" code:123 userInfo:nil]; + + NSError *error = nil; + GNCNWFrameworkSocket *socket = [_serverSocket acceptWithError:&error]; + + XCTAssertNil(socket, @"acceptWithError should return nil when listener has failed"); + XCTAssertNotNil(error, @"error should be non-nil when listener has failed"); + XCTAssertEqualObjects(error.domain, @"Test"); + XCTAssertEqual(error.code, 123); +} + +- (void)testStartListening_Timeout API_AVAILABLE(ios(13.0)) { + _fakeListener.simulateTimeout = YES; + NSError *error = nil; + XCTAssertFalse([_serverSocket startListeningWithError:&error includePeerToPeer:NO]); + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, GNCNWFrameworkErrorDomain); + XCTAssertEqual(error.code, GNCNWFrameworkErrorTimedOut); +} + +- (void)testStartListening_StateInvalid API_AVAILABLE(ios(13.0)) { + _fakeListener.stateForStart = nw_listener_state_invalid; + _serverSocket.listenerError = [NSError errorWithDomain:@"Test" code:123 userInfo:nil]; + NSError *error = nil; + XCTAssertFalse([_serverSocket startListeningWithError:&error includePeerToPeer:NO]); + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, @"Test"); + XCTAssertEqual(error.code, 123); +} + +- (void)testStartListening_StateWaiting API_AVAILABLE(ios(13.0)) { + _fakeListener.stateForStart = nw_listener_state_waiting; + _serverSocket.listenerError = [NSError errorWithDomain:@"Test" code:123 userInfo:nil]; + NSError *error = nil; + XCTAssertFalse([_serverSocket startListeningWithError:&error includePeerToPeer:NO]); + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, @"Test"); + XCTAssertEqual(error.code, 123); +} + +- (void)testStartListening_StateCancelled API_AVAILABLE(ios(13.0)) { + _fakeListener.stateForStart = nw_listener_state_cancelled; + _serverSocket.listenerError = [NSError errorWithDomain:@"Test" code:123 userInfo:nil]; + NSError *error = nil; + XCTAssertFalse([_serverSocket startListeningWithError:&error includePeerToPeer:NO]); + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, @"Test"); + XCTAssertEqual(error.code, 123); +} + - (void)testStartListeningWithError_Success API_AVAILABLE(ios(13.0)) { // Configure the fake listener to simulate a successful start _fakeListener.port = 12345; @@ -220,4 +274,89 @@ @"Connection should not be added to ready connections on failure."); } +- (void)testHandleNewConnectionAddsConnectionToPendingList API_AVAILABLE(ios(13.0)) { + id partialServerSocket = OCMPartialMock(_serverSocket); + OCMStub([partialServerSocket configureAndStartConnection:[OCMArg any]]); + + GNCFakeNWConnection *fakeConnection = [[GNCFakeNWConnection alloc] init]; + nw_connection_t connection = (nw_connection_t)fakeConnection; + + [partialServerSocket handleNewConnection:connection]; + + XCTAssertTrue([_serverSocket.pendingConnections containsObject:connection], + @"handleNewConnection should add the connection to pendingConnections."); +} + +- (void)testHandleConnectionStateChange_Cancelled API_AVAILABLE(ios(13.0)) { + GNCFakeNWConnection *fakeConnection = [[GNCFakeNWConnection alloc] init]; + nw_connection_t connection = (nw_connection_t)fakeConnection; + + // Manually add to pending connections. + [_serverSocket.pendingConnections addObject:connection]; + + // Simulate the connection failing. + [_serverSocket handleConnectionStateChange:connection + state:nw_connection_state_cancelled + error:nil]; + + XCTAssertFalse([_serverSocket.pendingConnections containsObject:connection], + @"Connection should be removed from pending connections on failure."); + XCTAssertFalse([_serverSocket.readyConnections containsObject:connection], + @"Connection should not be added to ready connections on failure."); +} + +- (void)testHandleConnectionStateChange_Invalid API_AVAILABLE(ios(13.0)) { + GNCFakeNWConnection *fakeConnection = [[GNCFakeNWConnection alloc] init]; + nw_connection_t connection = (nw_connection_t)fakeConnection; + + // Manually add to pending connections. + [_serverSocket.pendingConnections addObject:connection]; + + // Simulate the connection failing. + [_serverSocket handleConnectionStateChange:connection + state:nw_connection_state_invalid + error:nil]; + + XCTAssertFalse([_serverSocket.pendingConnections containsObject:connection], + @"Connection should be removed from pending connections on failure."); + XCTAssertFalse([_serverSocket.readyConnections containsObject:connection], + @"Connection should not be added to ready connections on failure."); +} + +- (void)testHandleConnectionStateChange_Waiting API_AVAILABLE(ios(13.0)) { + GNCFakeNWConnection *fakeConnection = [[GNCFakeNWConnection alloc] init]; + nw_connection_t connection = (nw_connection_t)fakeConnection; + + // Manually add to pending connections. + [_serverSocket.pendingConnections addObject:connection]; + + // Simulate the connection failing. + [_serverSocket handleConnectionStateChange:connection + state:nw_connection_state_waiting + error:nil]; + + XCTAssertFalse([_serverSocket.pendingConnections containsObject:connection], + @"Connection should be removed from pending connections on failure."); + XCTAssertFalse([_serverSocket.readyConnections containsObject:connection], + @"Connection should not be added to ready connections on failure."); +} + +- (void)testHandleConnectionStateChange_Preparing API_AVAILABLE(ios(13.0)) { + GNCFakeNWConnection *fakeConnection = [[GNCFakeNWConnection alloc] init]; + nw_connection_t connection = (nw_connection_t)fakeConnection; + + // Manually add to pending connections. + [_serverSocket.pendingConnections addObject:connection]; + + // Simulate the connection failing. + [_serverSocket handleConnectionStateChange:connection + state:nw_connection_state_preparing + error:nil]; + + XCTAssertTrue([_serverSocket.pendingConnections containsObject:connection], + @"Connection should remain in pending connections when preparing."); + XCTAssertFalse([_serverSocket.readyConnections containsObject:connection], + @"Connection should not be added to ready connections when preparing."); +} + @end