diff options
author | NicoleYarroch <nicole@livio.io> | 2019-01-31 13:52:01 -0500 |
---|---|---|
committer | NicoleYarroch <nicole@livio.io> | 2019-01-31 13:52:01 -0500 |
commit | ca292eadc2df8ef7cf7159c956f121782173f1c2 (patch) | |
tree | d09096292e1f60958ee07d831453df06aff5ab14 | |
parent | 38ff9ec0442c7ce6269a55544d37c8cfa4236f6b (diff) | |
download | sdl_ios-bugfix/issue_1079_switching_transports_fails.tar.gz |
* Added test cases for iAPTransport classesbugfix/issue_1079_switching_transports_fails
* Added test cases for iAPTransport and iAPSession classes
* Fixed a lot of documentation spelling and grammar.
-rw-r--r-- | SmartDeviceLink-iOS.xcodeproj/project.pbxproj | 30 | ||||
-rw-r--r-- | SmartDeviceLink/SDLIAPSession.m | 46 | ||||
-rw-r--r-- | SmartDeviceLink/SDLIAPTransport.h | 4 | ||||
-rw-r--r-- | SmartDeviceLink/SDLIAPTransport.m | 35 | ||||
-rw-r--r-- | SmartDeviceLinkTests/TransportSpecs/TCP/SDLTCPTransportSpec.m (renamed from SmartDeviceLinkTests/TransportSpecs/SDLTCPTransportSpec.m) | 0 | ||||
-rw-r--r-- | SmartDeviceLinkTests/TransportSpecs/iAP/EAAccessory+OCMock.m | 62 | ||||
-rw-r--r-- | SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPSessionSpec.m | 165 | ||||
-rw-r--r-- | SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPTransportSpec.m | 118 |
8 files changed, 430 insertions, 30 deletions
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj index 7937c15bf..e87b031e9 100644 --- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj +++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj @@ -1256,6 +1256,9 @@ 88B848C31F45E1A600DED768 /* TestResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 88B848C21F45E1A600DED768 /* TestResponse.m */; }; 88B848C91F462E3600DED768 /* TestFileProgressResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 88B848C81F462E3600DED768 /* TestFileProgressResponse.m */; }; 88D2AAE41F682BB20078D5B2 /* SDLLogConstantsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88D2AAE31F682BB20078D5B2 /* SDLLogConstantsSpec.m */; }; + 88DF998D22035CC600477AC1 /* EAAccessory+OCMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 88DF998C22035CC600477AC1 /* EAAccessory+OCMock.m */; }; + 88DF998F22035D1700477AC1 /* SDLIAPSessionSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88DF998E22035D1700477AC1 /* SDLIAPSessionSpec.m */; }; + 88DF999122035D5A00477AC1 /* SDLIAPTransportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88DF999022035D5A00477AC1 /* SDLIAPTransportSpec.m */; }; 88EED8381F33AE1700E6C42E /* SDLHapticRect.h in Headers */ = {isa = PBXBuildFile; fileRef = 88EED8361F33AE1700E6C42E /* SDLHapticRect.h */; settings = {ATTRIBUTES = (Public, ); }; }; 88EED8391F33AE1700E6C42E /* SDLHapticRect.m in Sources */ = {isa = PBXBuildFile; fileRef = 88EED8371F33AE1700E6C42E /* SDLHapticRect.m */; }; 88EED83B1F33BECB00E6C42E /* SDLHapticRectSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88EED83A1F33BECB00E6C42E /* SDLHapticRectSpec.m */; }; @@ -2757,6 +2760,9 @@ 88B848C71F462E3600DED768 /* TestFileProgressResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestFileProgressResponse.h; sourceTree = "<group>"; }; 88B848C81F462E3600DED768 /* TestFileProgressResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestFileProgressResponse.m; sourceTree = "<group>"; }; 88D2AAE31F682BB20078D5B2 /* SDLLogConstantsSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLLogConstantsSpec.m; sourceTree = "<group>"; }; + 88DF998C22035CC600477AC1 /* EAAccessory+OCMock.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "EAAccessory+OCMock.m"; sourceTree = "<group>"; }; + 88DF998E22035D1700477AC1 /* SDLIAPSessionSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLIAPSessionSpec.m; sourceTree = "<group>"; }; + 88DF999022035D5A00477AC1 /* SDLIAPTransportSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLIAPTransportSpec.m; sourceTree = "<group>"; }; 88EED8361F33AE1700E6C42E /* SDLHapticRect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLHapticRect.h; sourceTree = "<group>"; }; 88EED8371F33AE1700E6C42E /* SDLHapticRect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLHapticRect.m; sourceTree = "<group>"; }; 88EED83A1F33BECB00E6C42E /* SDLHapticRectSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLHapticRectSpec.m; sourceTree = "<group>"; }; @@ -5506,6 +5512,24 @@ name = Helpers; sourceTree = "<group>"; }; + 88DF998A22035CA400477AC1 /* TCP */ = { + isa = PBXGroup; + children = ( + EE5D1B32208EBCA900D17216 /* SDLTCPTransportSpec.m */, + ); + path = TCP; + sourceTree = "<group>"; + }; + 88DF998B22035CB100477AC1 /* iAP */ = { + isa = PBXGroup; + children = ( + 88DF998C22035CC600477AC1 /* EAAccessory+OCMock.m */, + 88DF998E22035D1700477AC1 /* SDLIAPSessionSpec.m */, + 88DF999022035D5A00477AC1 /* SDLIAPTransportSpec.m */, + ); + path = iAP; + sourceTree = "<group>"; + }; DA1166D71D14601C00438CEA /* Touches */ = { isa = PBXGroup; children = ( @@ -5618,7 +5642,8 @@ EE5D1B31208EBC7100D17216 /* TransportSpecs */ = { isa = PBXGroup; children = ( - EE5D1B32208EBCA900D17216 /* SDLTCPTransportSpec.m */, + 88DF998B22035CB100477AC1 /* iAP */, + 88DF998A22035CA400477AC1 /* TCP */, ); path = TransportSpecs; sourceTree = "<group>"; @@ -6817,6 +6842,7 @@ 5DC09EDA1F2F7FEC00F4AB1D /* SDLControlFramePayloadNakSpec.m in Sources */, 1EB59CCE202DC97900343A61 /* SDLMassageCushionSpec.m in Sources */, 162E83041A9BDE8B00906325 /* SDLUpdateModeSpec.m in Sources */, + 88DF998D22035CC600477AC1 /* EAAccessory+OCMock.m in Sources */, 162E83801A9BDE8B00906325 /* SDLHMIPermissionsSpec.m in Sources */, 1EAA476C2036A52F000FE74B /* SDLLightCapabilitiesSpec.m in Sources */, 5D1654561D3E754F00554D93 /* SDLLifecycleManagerSpec.m in Sources */, @@ -6824,6 +6850,7 @@ 162E83021A9BDE8B00906325 /* SDLTouchTypeSpec.m in Sources */, 1EAA47722036AEF5000FE74B /* SDLLightNameSpec.m in Sources */, 5DB92D2F1AC59F0000C15BB0 /* SDLObjectWithPrioritySpec.m in Sources */, + 88DF999122035D5A00477AC1 /* SDLIAPTransportSpec.m in Sources */, 162E838A1A9BDE8B00906325 /* SDLSingleTireStatusSpec.m in Sources */, 5D6EB4CC1BF28DC600693731 /* NSMapTable+SubscriptingSpec.m in Sources */, 162E83051A9BDE8B00906325 /* SDLVehicleDataActiveStatusSpec.m in Sources */, @@ -7005,6 +7032,7 @@ 162E83141A9BDE8B00906325 /* SDLOnDriverDistractionSpec.m in Sources */, 162E83371A9BDE8B00906325 /* SDLResetGlobalPropertiesSpec.m in Sources */, 162E82DF1A9BDE8B00906325 /* SDLGlobalProperySpec.m in Sources */, + 88DF998F22035D1700477AC1 /* SDLIAPSessionSpec.m in Sources */, 5DD8406520FCE21A0082CE04 /* SDLElectronicParkBrakeStatusSpec.m in Sources */, 162E82F61A9BDE8B00906325 /* SDLRequestTypeSpec.m in Sources */, 5DE35E4520CAFC5D0034BE5A /* SDLChoiceCellSpec.m in Sources */, diff --git a/SmartDeviceLink/SDLIAPSession.m b/SmartDeviceLink/SDLIAPSession.m index 599358557..6fc39be9a 100644 --- a/SmartDeviceLink/SDLIAPSession.m +++ b/SmartDeviceLink/SDLIAPSession.m @@ -49,15 +49,19 @@ NSTimeInterval const StreamThreadWaitSecs = 10.0; #pragma mark - Public Stream Lifecycle - (BOOL)start { - __weak typeof(self) weakSelf = self; SDLLogD(@"Opening EASession withAccessory:%@ forProtocol:%@", _accessory.name, _protocol); + self.easession = [[EASession alloc] initWithAccessory:self.accessory forProtocol:self.protocol]; + return [self sdl_startWithSession:self.easession]; +} - // TODO: This assignment should be broken out of the if and the if / else should be flipped. - if ((self.easession = [[EASession alloc] initWithAccessory:self.accessory forProtocol:self.protocol])) { +- (BOOL)sdl_startWithSession:(EASession *)session { + __weak typeof(self) weakSelf = self; + if (session == nil) { + SDLLogE(@"Error creating the session object"); + return NO; + } else { + SDLLogD(@"Created the session object successfully"); __strong typeof(self) strongSelf = weakSelf; - - SDLLogD(@"Created Session Object"); - strongSelf.streamDelegate.streamErrorHandler = [self streamErroredHandler]; strongSelf.streamDelegate.streamOpenHandler = [self streamOpenedHandler]; if (self.isDataSession) { @@ -72,10 +76,6 @@ NSTimeInterval const StreamThreadWaitSecs = 10.0; [self startStream:self.easession.inputStream]; } return YES; - - } else { - SDLLogE(@"Error: Could Not Create Session Object"); - return NO; } } @@ -94,14 +94,13 @@ NSTimeInterval const StreamThreadWaitSecs = 10.0; if (self.isDataSession) { [self.ioStreamThread cancel]; - long lWait = dispatch_semaphore_wait(self.canceledSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(StreamThreadWaitSecs * NSEC_PER_SEC))); - if (lWait == 0) { - SDLLogD(@"Stream thread cancelled successfully"); - } else { - SDLLogE(@"Failed to cancel stream thread"); - } - self.ioStreamThread = nil; - self.isDataSession = NO; + [self sdl_isIOThreadCanceled:self.canceledSemaphore completionHandler:^(BOOL success) { + if (success == NO) { + SDLLogE(@"About to destroy a thread that has not yet closed."); + } + self.ioStreamThread = nil; + self.isDataSession = NO; + }]; } else { // Stop control session [self stopStream:self.easession.outputStream]; @@ -110,6 +109,17 @@ NSTimeInterval const StreamThreadWaitSecs = 10.0; self.easession = nil; } +- (void)sdl_isIOThreadCanceled:(dispatch_semaphore_t)canceledSemaphore completionHandler:(void (^)(BOOL success))completionHandler { + long lWait = dispatch_semaphore_wait(canceledSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(StreamThreadWaitSecs * NSEC_PER_SEC))); + if (lWait == 0) { + SDLLogD(@"Stream thread canceled successfully"); + return completionHandler(YES); + } else { + SDLLogE(@"Failed to cancel stream thread"); + return completionHandler(NO); + } +} + - (BOOL)isStopped { return !self.isOutputStreamOpen && !self.isInputStreamOpen; } diff --git a/SmartDeviceLink/SDLIAPTransport.h b/SmartDeviceLink/SDLIAPTransport.h index ec32ae1f1..6934ffc27 100644 --- a/SmartDeviceLink/SDLIAPTransport.h +++ b/SmartDeviceLink/SDLIAPTransport.h @@ -11,12 +11,12 @@ NS_ASSUME_NONNULL_BEGIN @interface SDLIAPTransport : NSObject <SDLTransportType, SDLIAPSessionDelegate> /** - * Session for transporting data between the app and Core. + * Session for establishing a connection with Core. Once the connection has been established, the session is closed and a session is established. A `controlSession` is not needed if the head unit supports the multisession protocol string. */ @property (nullable, strong, nonatomic) SDLIAPSession *controlSession; /** - * Session for establishing a connection with Core. Once the connection has been established, the session is closed and a control session is established. + * Session for transporting data between the app and Core. */ @property (nullable, strong, nonatomic) SDLIAPSession *session; diff --git a/SmartDeviceLink/SDLIAPTransport.m b/SmartDeviceLink/SDLIAPTransport.m index 9e36754cd..b0fa07177 100644 --- a/SmartDeviceLink/SDLIAPTransport.m +++ b/SmartDeviceLink/SDLIAPTransport.m @@ -68,14 +68,16 @@ int const ProtocolIndexTimeoutSeconds = 10; */ - (void)sdl_backgroundTaskStart { if (self.backgroundTaskId != UIBackgroundTaskInvalid) { + SDLLogV(@"A background task is already running. No need to start a background task. Returning..."); return; } - SDLLogD(@"Starting background task"); self.backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithName:BackgroundTaskName expirationHandler:^{ SDLLogD(@"Background task expired"); [self sdl_backgroundTaskEnd]; }]; + + SDLLogD(@"Started a background task with id: %lu", (unsigned long)self.backgroundTaskId); } /** @@ -83,10 +85,11 @@ int const ProtocolIndexTimeoutSeconds = 10; */ - (void)sdl_backgroundTaskEnd { if (self.backgroundTaskId == UIBackgroundTaskInvalid) { + SDLLogV(@"No background task running. No need to stop the background task. Returning..."); return; } - SDLLogD(@"Ending background task"); + SDLLogD(@"Ending background task with id: %lu", (unsigned long)self.backgroundTaskId); [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId]; self.backgroundTaskId = UIBackgroundTaskInvalid; } @@ -142,13 +145,12 @@ int const ProtocolIndexTimeoutSeconds = 10; - (void)sdl_accessoryConnected:(NSNotification *)notification { EAAccessory *newAccessory = [notification.userInfo objectForKey:EAAccessoryKey]; - if ((self.session != nil) && (self.session.accessory.connectionID != newAccessory.connectionID)) { - SDLLogD(@"Switching transports from Bluetooth to USB. Waiting for disconnect notification."); + if ([self sdl_isSessionActive:self.session newAccessory:newAccessory]) { self.accessoryConnectDuringActiveSession = YES; return; } - double retryDelay = self.retryDelay; + double retryDelay = self.sdl_retryDelay; SDLLogD(@"Accessory Connected (%@), Opening in %0.03fs", notification.userInfo[EAAccessoryKey], retryDelay); if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive) { @@ -161,6 +163,22 @@ int const ProtocolIndexTimeoutSeconds = 10; } /** + * Checks if the newly connected accessory connected while a data session is already opened. This can happen when a session is established over bluetooth and then the user connects to the same head unit with a USB cord. + * + * @param session The current data session, which may be nil + * @param newAccessory The newly connected accessory + * @return True if the accessory connected while a data session is already in progress; false if not + */ +- (BOOL)sdl_isSessionActive:(SDLIAPSession *)session newAccessory:(EAAccessory *)newAccessory { + if ((session != nil) && (session.accessory.connectionID != newAccessory.connectionID)) { + SDLLogD(@"Switching transports from Bluetooth to USB. Waiting for disconnect notification."); + return YES; + } + + return NO; +} + +/** * Handles a notification sent by the system when an accessory has been disconnected by cleaning up after the disconnected device. Only check for the data session, the control session is handled separately * * @param notification Contains information about the connected accessory @@ -328,14 +346,13 @@ int const ProtocolIndexTimeoutSeconds = 10; } /** - * Attept to establish a session with an accessory, or if nil is passed, to scan for one. + * Attempt to establish a session with an accessory, or if nil is passed, to scan for one. * * @param accessory The accessory to try to establish a session with, or nil to scan all connected accessories. */ - (void)sdl_establishSessionWithAccessory:(nullable EAAccessory *)accessory { SDLLogD(@"Attempting to connect accessory: %@", accessory.name); if (self.retryCounter < CreateSessionRetries) { - // We should be attempting to connect self.retryCounter++; EAAccessory *sdlAccessory = accessory; @@ -366,7 +383,7 @@ int const ProtocolIndexTimeoutSeconds = 10; self.sessionSetupInProgress = NO; } } else { - // We are beyond the number of retries allowed + // We have surpassed the number of retries allowed SDLLogW(@"Surpassed allowed retry attempts (%d), dismissing setup", CreateSessionRetries); self.sessionSetupInProgress = NO; } @@ -643,7 +660,7 @@ int const ProtocolIndexTimeoutSeconds = 10; }; } -- (double)retryDelay { +- (double)sdl_retryDelay { const double MinRetrySeconds = 1.5; const double MaxRetrySeconds = 9.5; double RetryRangeSeconds = MaxRetrySeconds - MinRetrySeconds; diff --git a/SmartDeviceLinkTests/TransportSpecs/SDLTCPTransportSpec.m b/SmartDeviceLinkTests/TransportSpecs/TCP/SDLTCPTransportSpec.m index e9a25f834..e9a25f834 100644 --- a/SmartDeviceLinkTests/TransportSpecs/SDLTCPTransportSpec.m +++ b/SmartDeviceLinkTests/TransportSpecs/TCP/SDLTCPTransportSpec.m diff --git a/SmartDeviceLinkTests/TransportSpecs/iAP/EAAccessory+OCMock.m b/SmartDeviceLinkTests/TransportSpecs/iAP/EAAccessory+OCMock.m new file mode 100644 index 000000000..1c32c4b73 --- /dev/null +++ b/SmartDeviceLinkTests/TransportSpecs/iAP/EAAccessory+OCMock.m @@ -0,0 +1,62 @@ +// +// EAAccessory+OCMock.m +// SmartDeviceLinkTests +// +// Created by Nicole on 1/24/19. +// Copyright © 2019 smartdevicelink. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import <UIKit/UIKit.h> +#import <OCMock/OCMock.h> +#import <ExternalAccessory/ExternalAccessory.h> + +// Based off of the Pebble Accessory OCKMock by Heiko Behrens https://github.com/HBehrens/PebbleKit-ios-sdk-test/blob/master/PebbleVendor/EAAccessoryFramework%2BOCMock.m + +@implementation EAAccessory (OCMock) +static id coreMockDelegate = nil; ++ (EAAccessory *)sdlCoreMock { + id mockEAAccessory = OCMClassMock([EAAccessory class]); + OCMStub([mockEAAccessory protocolStrings]).andReturn(@[@"com.smartdevicelink.multisession"]); + [[[mockEAAccessory stub] andReturnValue:OCMOCK_VALUE((NSString *)@"SDLTestHeadUnit")] name]; + OCMStub([mockEAAccessory modelNumber]).andReturn(@"0.0.0"); + OCMStub([mockEAAccessory serialNumber]).andReturn(@"123456"); + OCMStub([mockEAAccessory firmwareRevision]).andReturn(@"1.2.3"); + OCMStub([mockEAAccessory hardwareRevision]).andReturn(@"3.2.1"); + OCMStub([mockEAAccessory isConnected]).andReturn(OCMOCK_VALUE(YES)); + OCMStub([mockEAAccessory setDelegate:[OCMArg checkWithBlock:^BOOL(id obj) { + coreMockDelegate = obj; + return YES; + }]]); + OCMStub([mockEAAccessory delegate]).andCall(self, @selector(coreDelegate)); + [[[mockEAAccessory stub] andReturnValue:OCMOCK_VALUE((NSUInteger){5})] connectionID]; + + return mockEAAccessory; +} +- (id)coreDelegate { + return coreMockDelegate; +} +@end + +@implementation EAAccessoryManager (OCMock) ++ (EAAccessoryManager *)mockManager { + id mockEAAccessoryManager = OCMClassMock([EAAccessoryManager class]); + id mockEAAccessory = [EAAccessory sdlCoreMock]; + OCMStub([mockEAAccessoryManager connectedAccessories]).andReturn(@[mockEAAccessory]); + OCMStub([mockEAAccessory registerForLocalNotifications]); + OCMStub([mockEAAccessory unregisterForLocalNotifications]); + return mockEAAccessoryManager; +} +@end + +@implementation EASession (OCMock) ++ (EASession *)mockSessionWithAccessory:(EAAccessory*)mockAccessory protocolString:(NSString*)mockProtocolString inputStream:(NSInputStream*)mockInputStream outputStream:(NSOutputStream*)mockOutputStream { + id session = OCMClassMock([EASession class]); + OCMStub([session accessory]).andReturn(mockAccessory); + OCMStub([session protocolString]).andReturn(mockProtocolString); + OCMStub([session inputStream]).andReturn(mockInputStream); + OCMStub([session outputStream]).andReturn(mockOutputStream); + return session; +} +@end + diff --git a/SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPSessionSpec.m b/SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPSessionSpec.m new file mode 100644 index 000000000..672aa76b1 --- /dev/null +++ b/SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPSessionSpec.m @@ -0,0 +1,165 @@ +// +// SDLIAPSessionSpec.m +// SmartDeviceLinkTests +// +// Created by Nicole on 1/23/19. +// Copyright © 2019 smartdevicelink. All rights reserved. +// + +#import <Quick/Quick.h> +#import <Nimble/Nimble.h> +#import <OCMock/OCMock.h> + +#import "EAAccessory+OCMock.m" +#import "SDLIAPSession.h" +#import "SDLMutableDataQueue.h" +#import "SDLStreamDelegate.h" + +@interface SDLIAPSession () +@property (nonatomic, assign) BOOL isInputStreamOpen; +@property (nonatomic, assign) BOOL isOutputStreamOpen; +@property (nonatomic, assign) BOOL isDataSession; +@property (nullable, nonatomic, strong) NSThread *ioStreamThread; +@property (nonatomic, strong) SDLMutableDataQueue *sendDataQueue; +@property (nonatomic, strong) dispatch_semaphore_t canceledSemaphore; +- (BOOL)sdl_startWithSession:(EASession *)session; +@end + +QuickSpecBegin(SDLIAPSessionSpec) + +describe(@"SDLIAPSession", ^{ + __block SDLIAPSession *iapSession = nil; + __block EAAccessory *mockAccessory = nil; + __block NSString *protocol = nil; + + describe(@"Initialization", ^{ + beforeEach(^{ + mockAccessory = [EAAccessory.class sdlCoreMock]; + }); + + it(@"Should init correctly with a control protocol string", ^{ + protocol = @"com.smartdevicelink.prot0"; + iapSession = [[SDLIAPSession alloc] initWithAccessory:mockAccessory forProtocol:protocol]; + + expect(iapSession.isDataSession).to(beFalse()); + }); + + it(@"Should init correctly with a multisession protocol string", ^{ + protocol = @"com.smartdevicelink.multisession"; + iapSession = [[SDLIAPSession alloc] initWithAccessory:mockAccessory forProtocol:protocol]; + + expect(iapSession.isDataSession).to(beTrue()); + }); + + it(@"Should init correctly with a legacy protocol string", ^{ + protocol = @"com.ford.sync.prot0"; + iapSession = [[SDLIAPSession alloc] initWithAccessory:mockAccessory forProtocol:protocol]; + + expect(iapSession.isDataSession).to(beTrue()); + }); + + it(@"Should init correctly with a indexed protocol string", ^{ + protocol = @"com.smartdevicelink.prot1"; + iapSession = [[SDLIAPSession alloc] initWithAccessory:mockAccessory forProtocol:protocol]; + + expect(iapSession.isDataSession).to(beTrue()); + }); + + afterEach(^{ + expect(iapSession).toNot(beNil()); + expect(iapSession.protocol).to(match(protocol)); + expect(iapSession.accessory).to(equal(mockAccessory)); + expect(iapSession.canceledSemaphore).toNot(beNil()); + expect(iapSession.sendDataQueue).toNot(beNil()); + expect(iapSession.isInputStreamOpen).to(beFalse()); + expect(iapSession.isOutputStreamOpen).to(beFalse()); + }); + }); + + describe(@"When starting a session", ^{ + __block SDLStreamDelegate *streamDelegate = nil; + __block NSInputStream *inputStream = nil; + __block NSOutputStream *outputStream = nil; + + describe(@"unsuccessfully", ^{ + beforeEach(^{ + protocol = @"com.smartdevicelink.multisession"; + mockAccessory = [EAAccessory.class sdlCoreMock]; + iapSession = [[SDLIAPSession alloc] initWithAccessory:mockAccessory forProtocol:protocol]; + streamDelegate = [[SDLStreamDelegate alloc] init]; + iapSession.streamDelegate = streamDelegate; + }); + + it(@"the start method should return false if a session cannot be created", ^{ + BOOL success = [iapSession sdl_startWithSession:nil]; + expect(success).to(beFalse()); + expect(iapSession.ioStreamThread).to(beNil()); + expect(iapSession.isInputStreamOpen).to(beFalse()); + expect(iapSession.isOutputStreamOpen).to(beFalse()); + }); + }); + + describe(@"successfully", ^{ + beforeEach(^{ + inputStream = OCMClassMock([NSInputStream class]); + outputStream = OCMClassMock([NSOutputStream class]); + }); + + context(@"if creating a control session", ^{ + beforeEach(^{ + protocol = @"com.smartdevicelink.prot0"; + mockAccessory = [EAAccessory.class sdlCoreMock]; + iapSession = [[SDLIAPSession alloc] initWithAccessory:mockAccessory forProtocol:protocol]; + streamDelegate = [[SDLStreamDelegate alloc] init]; + iapSession.streamDelegate = streamDelegate; + + expect(iapSession.isDataSession).to(beFalse()); + }); + + it(@"should establish a control session ", ^{ + EASession *mockSession = [EASession.class mockSessionWithAccessory:mockAccessory protocolString:protocol inputStream:inputStream outputStream:outputStream]; + iapSession.easession = mockSession; + + BOOL success = [iapSession sdl_startWithSession:mockSession]; + expect(success).to(beTrue()); + expect(iapSession.ioStreamThread).to(beNil()); + expect(iapSession.easession.inputStream).toNot(beNil()); + expect(iapSession.easession.outputStream).toNot(beNil()); + }); + }); + + context(@"if creating a data session", ^{ + beforeEach(^{ + protocol = @"com.smartdevicelink.multisession"; + mockAccessory = [EAAccessory.class sdlCoreMock]; + iapSession = [[SDLIAPSession alloc] initWithAccessory:mockAccessory forProtocol:protocol]; + streamDelegate = [[SDLStreamDelegate alloc] init]; + iapSession.streamDelegate = streamDelegate; + + expect(iapSession.isDataSession).to(beTrue()); + }); + + it(@"should establish a data session ", ^{ + EASession *mockSession = [EASession.class mockSessionWithAccessory:mockAccessory protocolString:protocol inputStream:inputStream outputStream:outputStream]; + iapSession.easession = mockSession; + BOOL success = [iapSession sdl_startWithSession:mockSession]; + expect(success).to(beTrue()); + expect(iapSession.ioStreamThread).toNot(beNil()); + // The streams are opened in the `sdl_streamHasSpaceHandler` method + expect(iapSession.easession.inputStream).toNot(beNil()); + expect(iapSession.easession.outputStream).toNot(beNil()); + }); + }); + }); + + afterEach(^{ + [iapSession stop]; + + expect(iapSession.easession).to(beNil()); + expect(iapSession.ioStreamThread).to(beNil()); + }); + }); +}); + +QuickSpecEnd + diff --git a/SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPTransportSpec.m b/SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPTransportSpec.m new file mode 100644 index 000000000..7223c8d0b --- /dev/null +++ b/SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPTransportSpec.m @@ -0,0 +1,118 @@ +// +// SDLIAPTransportSpec.m +// SmartDeviceLinkTests +// +// Created by Nicole on 1/23/19. +// Copyright © 2019 smartdevicelink. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import <Quick/Quick.h> +#import <Nimble/Nimble.h> +#import <OCMock/OCMock.h> + +#import "EAAccessory+OCMock.m" +#import "SDLIAPTransport.h" +#import "SDLIAPSession.h" +#import "SDLTimer.h" + +@interface SDLIAPTransport () +@property (assign, nonatomic) int retryCounter; +@property (assign, nonatomic) BOOL sessionSetupInProgress; +@property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundTaskId; +@property (nullable, strong, nonatomic) SDLTimer *protocolIndexTimer; +@property (assign, nonatomic) BOOL accessoryConnectDuringActiveSession; +- (BOOL)sdl_isSessionActive:(SDLIAPSession *)session newAccessory:(EAAccessory *)newAccessory; +@end + +QuickSpecBegin(SDLIAPTransportSpec) + +describe(@"SDLIAPTransport", ^{ + __block SDLIAPTransport *transport = nil; + __block id mockTransportDelegate = nil; + __block EAAccessory *mockAccessory = nil; + + beforeEach(^{ + transport = [SDLIAPTransport new]; + mockTransportDelegate = OCMProtocolMock(@protocol(SDLTransportDelegate)); + transport.delegate = mockTransportDelegate; + mockAccessory = [EAAccessory.class sdlCoreMock]; + }); + + describe(@"Initialization", ^{ + it(@"Should init correctly", ^{ + expect(transport.delegate).toNot(beNil()); + expect(transport.controlSession).to(beNil()); + expect(transport.session).to(beNil()); + expect(transport.sessionSetupInProgress).to(beFalse()); + expect(transport.session).to(beNil()); + expect(transport.controlSession).to(beNil()); + expect(transport.retryCounter).to(equal(0)); + expect(transport.protocolIndexTimer).to(beNil()); + expect(transport.accessoryConnectDuringActiveSession).to(beFalse()); + }); + }); + + describe(@"When an accessory connects while a session is not open", ^{ + beforeEach(^{ + transport.session = nil; + }); + + it(@"If no session is open, it should create a session when an accessory connects", ^{ + BOOL sessionInProgress = [transport sdl_isSessionActive:transport.session newAccessory:mockAccessory]; + expect(sessionInProgress).to(beFalse()); + }); + }); + + describe(@"When an accessory connects when a session is already open", ^{ + beforeEach(^{ + transport.session = OCMClassMock([SDLIAPSession class]); + }); + + it(@"If a session is in progress", ^{ + BOOL sessionInProgress = [transport sdl_isSessionActive:transport.session newAccessory:mockAccessory]; + expect(sessionInProgress).to(beTrue()); + }); + }); + + describe(@"When an accessory disconnects while a data session is open", ^{ + beforeEach(^{ + transport.controlSession = nil; + transport.session = [[SDLIAPSession alloc] initWithAccessory:mockAccessory forProtocol:@"com.smartdevicelink.multisession"]; + transport.accessoryConnectDuringActiveSession = YES; + NSNotification *accessoryDisconnectedNotification = [[NSNotification alloc] initWithName:EAAccessoryDidDisconnectNotification object:nil userInfo:@{EAAccessoryKey: mockAccessory}]; + [[NSNotificationCenter defaultCenter] postNotification:accessoryDisconnectedNotification]; + }); + + it(@"It should close the open data session", ^{ + expect(transport.session).to(beNil()); + expect(transport.controlSession).to(beNil()); + expect(transport.retryCounter).to(equal(0)); + expect(transport.accessoryConnectDuringActiveSession).to(beFalse()); + expect(transport.sessionSetupInProgress).to(beFalse()); + }); + }); + + describe(@"When an accessory disconnects while a control session is open", ^{ + beforeEach(^{ + transport.controlSession = [[SDLIAPSession alloc] initWithAccessory:mockAccessory forProtocol:@"com.smartdevicelink.prot0"];; + transport.session = nil; + transport.accessoryConnectDuringActiveSession = NO; + transport.sessionSetupInProgress = YES; + transport.retryCounter = 1; + NSNotification *accessoryDisconnectedNotification = [[NSNotification alloc] initWithName:EAAccessoryDidDisconnectNotification object:nil userInfo:@{EAAccessoryKey: mockAccessory}]; + [[NSNotificationCenter defaultCenter] postNotification:accessoryDisconnectedNotification]; + }); + + it(@"It should close the open control session", ^{ + expect(transport.session).to(beNil()); + expect(transport.controlSession).to(beNil()); + expect(transport.retryCounter).to(equal(0)); + expect(transport.accessoryConnectDuringActiveSession).to(beFalse()); + expect(transport.sessionSetupInProgress).to(beFalse()); + }); + }); +}); + +QuickSpecEnd + |