Index: webrtc/examples/objc/AppRTCDemo/tests/ARDAppClientTest.mm |
diff --git a/webrtc/examples/objc/AppRTCDemo/tests/ARDAppClientTest.mm b/webrtc/examples/objc/AppRTCDemo/tests/ARDAppClientTest.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c1fc08ca8e94e5b5ae7a6500711e45658165cdae |
--- /dev/null |
+++ b/webrtc/examples/objc/AppRTCDemo/tests/ARDAppClientTest.mm |
@@ -0,0 +1,350 @@ |
+/* |
+ * Copyright 2014 The WebRTC Project Authors. All rights reserved. |
+ * |
+ * Use of this source code is governed by a BSD-style license |
+ * that can be found in the LICENSE file in the root of the source |
+ * tree. An additional intellectual property rights grant can be found |
+ * in the file PATENTS. All contributing project authors may |
+ * be found in the AUTHORS file in the root of the source tree. |
+ */ |
+ |
+#import <Foundation/Foundation.h> |
+#import <OCMock/OCMock.h> |
+ |
+#include "webrtc/base/gunit.h" |
+#include "webrtc/base/ssladapter.h" |
+ |
+#import "WebRTC/RTCMediaConstraints.h" |
+#import "WebRTC/RTCPeerConnectionFactory.h" |
+#import "WebRTC/RTCSessionDescription.h" |
+ |
+#import "ARDAppClient+Internal.h" |
+#import "ARDJoinResponse+Internal.h" |
+#import "ARDMessageResponse+Internal.h" |
+#import "ARDSDPUtils.h" |
+ |
+// These classes mimic XCTest APIs, to make eventual conversion to XCTest |
+// easier. Conversion will happen once XCTest is supported well on build bots. |
+@interface ARDTestExpectation : NSObject |
+ |
+@property(nonatomic, readonly) NSString *description; |
+@property(nonatomic, readonly) BOOL isFulfilled; |
+ |
+- (instancetype)initWithDescription:(NSString *)description; |
+- (void)fulfill; |
+ |
+@end |
+ |
+@implementation ARDTestExpectation |
+ |
+@synthesize description = _description; |
+@synthesize isFulfilled = _isFulfilled; |
+ |
+- (instancetype)initWithDescription:(NSString *)description { |
+ if (self = [super init]) { |
+ _description = description; |
+ } |
+ return self; |
+} |
+ |
+- (void)fulfill { |
+ _isFulfilled = YES; |
+} |
+ |
+@end |
+ |
+@interface ARDTestCase : NSObject |
+ |
+- (ARDTestExpectation *)expectationWithDescription:(NSString *)description; |
+- (void)waitForExpectationsWithTimeout:(NSTimeInterval)timeout |
+ handler:(void (^)(NSError *error))handler; |
+ |
+@end |
+ |
+@implementation ARDTestCase { |
+ NSMutableArray *_expectations; |
+} |
+ |
+- (instancetype)init { |
+ if (self = [super init]) { |
+ _expectations = [NSMutableArray array]; |
+ } |
+ return self; |
+} |
+ |
+- (ARDTestExpectation *)expectationWithDescription:(NSString *)description { |
+ ARDTestExpectation *expectation = |
+ [[ARDTestExpectation alloc] initWithDescription:description]; |
+ [_expectations addObject:expectation]; |
+ return expectation; |
+} |
+ |
+- (void)waitForExpectationsWithTimeout:(NSTimeInterval)timeout |
+ handler:(void (^)(NSError *error))handler { |
+ NSDate *startDate = [NSDate date]; |
+ while (![self areExpectationsFulfilled]) { |
+ NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:startDate]; |
+ if (duration > timeout) { |
+ NSAssert(NO, @"Expectation timed out."); |
+ break; |
+ } |
+ [[NSRunLoop currentRunLoop] |
+ runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; |
+ } |
+ handler(nil); |
+} |
+ |
+- (BOOL)areExpectationsFulfilled { |
+ for (ARDTestExpectation *expectation in _expectations) { |
+ if (!expectation.isFulfilled) { |
+ return NO; |
+ } |
+ } |
+ return YES; |
+} |
+ |
+@end |
+ |
+@interface ARDAppClientTest : ARDTestCase |
+@end |
+ |
+@implementation ARDAppClientTest |
+ |
+#pragma mark - Mock helpers |
+ |
+- (id)mockRoomServerClientForRoomId:(NSString *)roomId |
+ clientId:(NSString *)clientId |
+ isInitiator:(BOOL)isInitiator |
+ messages:(NSArray *)messages |
+ messageHandler: |
+ (void (^)(ARDSignalingMessage *))messageHandler { |
+ id mockRoomServerClient = |
+ [OCMockObject mockForProtocol:@protocol(ARDRoomServerClient)]; |
+ |
+ // Successful join response. |
+ ARDJoinResponse *joinResponse = [[ARDJoinResponse alloc] init]; |
+ joinResponse.result = kARDJoinResultTypeSuccess; |
+ joinResponse.roomId = roomId; |
+ joinResponse.clientId = clientId; |
+ joinResponse.isInitiator = isInitiator; |
+ joinResponse.messages = messages; |
+ |
+ // Successful message response. |
+ ARDMessageResponse *messageResponse = [[ARDMessageResponse alloc] init]; |
+ messageResponse.result = kARDMessageResultTypeSuccess; |
+ |
+ // Return join response from above on join. |
+ [[[mockRoomServerClient stub] andDo:^(NSInvocation *invocation) { |
+ __unsafe_unretained void (^completionHandler)(ARDJoinResponse *response, |
+ NSError *error); |
+ [invocation getArgument:&completionHandler atIndex:3]; |
+ completionHandler(joinResponse, nil); |
+ }] joinRoomWithRoomId:roomId isLoopback:NO completionHandler:[OCMArg any]]; |
+ |
+ // Return message response from above on join. |
+ [[[mockRoomServerClient stub] andDo:^(NSInvocation *invocation) { |
+ __unsafe_unretained ARDSignalingMessage *message; |
+ __unsafe_unretained void (^completionHandler)(ARDMessageResponse *response, |
+ NSError *error); |
+ [invocation getArgument:&message atIndex:2]; |
+ [invocation getArgument:&completionHandler atIndex:5]; |
+ messageHandler(message); |
+ completionHandler(messageResponse, nil); |
+ }] sendMessage:[OCMArg any] |
+ forRoomId:roomId |
+ clientId:clientId |
+ completionHandler:[OCMArg any]]; |
+ |
+ // Do nothing on leave. |
+ [[[mockRoomServerClient stub] andDo:^(NSInvocation *invocation) { |
+ __unsafe_unretained void (^completionHandler)(NSError *error); |
+ [invocation getArgument:&completionHandler atIndex:4]; |
+ if (completionHandler) { |
+ completionHandler(nil); |
+ } |
+ }] leaveRoomWithRoomId:roomId |
+ clientId:clientId |
+ completionHandler:[OCMArg any]]; |
+ |
+ return mockRoomServerClient; |
+} |
+ |
+- (id)mockSignalingChannelForRoomId:(NSString *)roomId |
+ clientId:(NSString *)clientId |
+ messageHandler: |
+ (void (^)(ARDSignalingMessage *message))messageHandler { |
+ id mockSignalingChannel = |
+ [OCMockObject niceMockForProtocol:@protocol(ARDSignalingChannel)]; |
+ [[mockSignalingChannel stub] registerForRoomId:roomId clientId:clientId]; |
+ [[[mockSignalingChannel stub] andDo:^(NSInvocation *invocation) { |
+ __unsafe_unretained ARDSignalingMessage *message; |
+ [invocation getArgument:&message atIndex:2]; |
+ messageHandler(message); |
+ }] sendMessage:[OCMArg any]]; |
+ return mockSignalingChannel; |
+} |
+ |
+- (id)mockTURNClient { |
+ id mockTURNClient = |
+ [OCMockObject mockForProtocol:@protocol(ARDTURNClient)]; |
+ [[[mockTURNClient stub] andDo:^(NSInvocation *invocation) { |
+ // Don't return anything in TURN response. |
+ __unsafe_unretained void (^completionHandler)(NSArray *turnServers, |
+ NSError *error); |
+ [invocation getArgument:&completionHandler atIndex:2]; |
+ completionHandler([NSArray array], nil); |
+ }] requestServersWithCompletionHandler:[OCMArg any]]; |
+ return mockTURNClient; |
+} |
+ |
+- (ARDAppClient *)createAppClientForRoomId:(NSString *)roomId |
+ clientId:(NSString *)clientId |
+ isInitiator:(BOOL)isInitiator |
+ messages:(NSArray *)messages |
+ messageHandler: |
+ (void (^)(ARDSignalingMessage *message))messageHandler |
+ connectedHandler:(void (^)(void))connectedHandler { |
+ id turnClient = [self mockTURNClient]; |
+ id signalingChannel = [self mockSignalingChannelForRoomId:roomId |
+ clientId:clientId |
+ messageHandler:messageHandler]; |
+ id roomServerClient = |
+ [self mockRoomServerClientForRoomId:roomId |
+ clientId:clientId |
+ isInitiator:isInitiator |
+ messages:messages |
+ messageHandler:messageHandler]; |
+ id delegate = |
+ [OCMockObject niceMockForProtocol:@protocol(ARDAppClientDelegate)]; |
+ [[[delegate stub] andDo:^(NSInvocation *invocation) { |
+ connectedHandler(); |
+ }] appClient:[OCMArg any] |
+ didChangeConnectionState:RTCIceConnectionStateConnected]; |
+ |
+ return [[ARDAppClient alloc] initWithRoomServerClient:roomServerClient |
+ signalingChannel:signalingChannel |
+ turnClient:turnClient |
+ delegate:delegate]; |
+} |
+ |
+// Tests that an ICE connection is established between two ARDAppClient objects |
+// where one is set up as a caller and the other the answerer. Network |
+// components are mocked out and messages are relayed directly from object to |
+// object. It's expected that both clients reach the |
+// RTCIceConnectionStateConnected state within a reasonable amount of time. |
+- (void)testSession { |
+ // Need block arguments here because we're setting up a callbacks before we |
+ // create the clients. |
+ ARDAppClient *caller = nil; |
+ ARDAppClient *answerer = nil; |
+ __block __weak ARDAppClient *weakCaller = nil; |
+ __block __weak ARDAppClient *weakAnswerer = nil; |
+ NSString *roomId = @"testRoom"; |
+ NSString *callerId = @"testCallerId"; |
+ NSString *answererId = @"testAnswererId"; |
+ |
+ ARDTestExpectation *callerConnectionExpectation = |
+ [self expectationWithDescription:@"Caller PC connected."]; |
+ ARDTestExpectation *answererConnectionExpectation = |
+ [self expectationWithDescription:@"Answerer PC connected."]; |
+ |
+ caller = [self createAppClientForRoomId:roomId |
+ clientId:callerId |
+ isInitiator:YES |
+ messages:[NSArray array] |
+ messageHandler:^(ARDSignalingMessage *message) { |
+ ARDAppClient *strongAnswerer = weakAnswerer; |
+ [strongAnswerer channel:strongAnswerer.channel didReceiveMessage:message]; |
+ } connectedHandler:^{ |
+ [callerConnectionExpectation fulfill]; |
+ }]; |
+ // TODO(tkchin): Figure out why DTLS-SRTP constraint causes thread assertion |
+ // crash in Debug. |
+ caller.defaultPeerConnectionConstraints = |
+ [[RTCMediaConstraints alloc] initWithMandatoryConstraints:nil |
+ optionalConstraints:nil]; |
+ weakCaller = caller; |
+ |
+ answerer = [self createAppClientForRoomId:roomId |
+ clientId:answererId |
+ isInitiator:NO |
+ messages:[NSArray array] |
+ messageHandler:^(ARDSignalingMessage *message) { |
+ ARDAppClient *strongCaller = weakCaller; |
+ [strongCaller channel:strongCaller.channel didReceiveMessage:message]; |
+ } connectedHandler:^{ |
+ [answererConnectionExpectation fulfill]; |
+ }]; |
+ // TODO(tkchin): Figure out why DTLS-SRTP constraint causes thread assertion |
+ // crash in Debug. |
+ answerer.defaultPeerConnectionConstraints = |
+ [[RTCMediaConstraints alloc] initWithMandatoryConstraints:nil |
+ optionalConstraints:nil]; |
+ weakAnswerer = answerer; |
+ |
+ // Kick off connection. |
+ [caller connectToRoomWithId:roomId |
+ isLoopback:NO |
+ isAudioOnly:NO |
+ shouldMakeAecDump:NO |
+ shouldUseLevelControl:NO]; |
+ [answerer connectToRoomWithId:roomId |
+ isLoopback:NO |
+ isAudioOnly:NO |
+ shouldMakeAecDump:NO |
+ shouldUseLevelControl:NO]; |
+ [self waitForExpectationsWithTimeout:20 handler:^(NSError *error) { |
+ if (error) { |
+ NSLog(@"Expectations error: %@", error); |
+ } |
+ }]; |
+} |
+ |
+@end |
+ |
+@interface ARDSDPUtilsTest : ARDTestCase |
+- (void)testPreferVideoCodec; |
+@end |
+ |
+@implementation ARDSDPUtilsTest |
+ |
+- (void)testPreferVideoCodec { |
+ NSString *sdp = @("m=video 9 RTP/SAVPF 100 116 117 96 120\n" |
+ "a=rtpmap:120 H264/90000\n"); |
+ NSString *expectedSdp = @("m=video 9 RTP/SAVPF 120 100 116 117 96\n" |
+ "a=rtpmap:120 H264/90000\n"); |
+ RTCSessionDescription* desc = |
+ [[RTCSessionDescription alloc] initWithType:RTCSdpTypeOffer sdp:sdp]; |
+ RTCSessionDescription *h264Desc = |
+ [ARDSDPUtils descriptionForDescription:desc |
+ preferredVideoCodec:@"H264"]; |
+ EXPECT_TRUE([h264Desc.description isEqualToString:expectedSdp]); |
+} |
+ |
+@end |
+ |
+class SignalingTest : public ::testing::Test { |
+ protected: |
+ static void SetUpTestCase() { |
+ rtc::InitializeSSL(); |
+ } |
+ static void TearDownTestCase() { |
+ rtc::CleanupSSL(); |
+ } |
+}; |
+ |
+TEST_F(SignalingTest, SessionTest) { |
+ @autoreleasepool { |
+ ARDAppClientTest *test = [[ARDAppClientTest alloc] init]; |
+ [test testSession]; |
+ } |
+} |
+ |
+TEST_F(SignalingTest, SDPTest) { |
+ @autoreleasepool { |
+ ARDSDPUtilsTest *test = [[ARDSDPUtilsTest alloc] init]; |
+ [test testPreferVideoCodec]; |
+ } |
+} |
+ |
+ |