1/* 2 * Copyright 2014 The WebRTC Project Authors. All rights reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11#import <Foundation/Foundation.h> 12#import <OCMock/OCMock.h> 13 14#import "ARDAppClient+Internal.h" 15#import "ARDJoinResponse+Internal.h" 16#import "ARDMessageResponse+Internal.h" 17#import "ARDSDPUtils.h" 18#import "RTCMediaConstraints.h" 19#import "RTCPeerConnectionFactory.h" 20#import "RTCSessionDescription.h" 21 22#include "webrtc/base/gunit.h" 23#include "webrtc/base/ssladapter.h" 24 25// These classes mimic XCTest APIs, to make eventual conversion to XCTest 26// easier. Conversion will happen once XCTest is supported well on build bots. 27@interface ARDTestExpectation : NSObject 28 29@property(nonatomic, readonly) NSString *description; 30@property(nonatomic, readonly) BOOL isFulfilled; 31 32- (instancetype)initWithDescription:(NSString *)description; 33- (void)fulfill; 34 35@end 36 37@implementation ARDTestExpectation 38 39@synthesize description = _description; 40@synthesize isFulfilled = _isFulfilled; 41 42- (instancetype)initWithDescription:(NSString *)description { 43 if (self = [super init]) { 44 _description = description; 45 } 46 return self; 47} 48 49- (void)fulfill { 50 _isFulfilled = YES; 51} 52 53@end 54 55@interface ARDTestCase : NSObject 56 57- (ARDTestExpectation *)expectationWithDescription:(NSString *)description; 58- (void)waitForExpectationsWithTimeout:(NSTimeInterval)timeout 59 handler:(void (^)(NSError *error))handler; 60 61@end 62 63@implementation ARDTestCase { 64 NSMutableArray *_expectations; 65} 66 67- (instancetype)init { 68 if (self = [super init]) { 69 _expectations = [NSMutableArray array]; 70 } 71 return self; 72} 73 74- (ARDTestExpectation *)expectationWithDescription:(NSString *)description { 75 ARDTestExpectation *expectation = 76 [[ARDTestExpectation alloc] initWithDescription:description]; 77 [_expectations addObject:expectation]; 78 return expectation; 79} 80 81- (void)waitForExpectationsWithTimeout:(NSTimeInterval)timeout 82 handler:(void (^)(NSError *error))handler { 83 NSDate *startDate = [NSDate date]; 84 while (![self areExpectationsFulfilled]) { 85 NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:startDate]; 86 if (duration > timeout) { 87 NSAssert(NO, @"Expectation timed out."); 88 break; 89 } 90 [[NSRunLoop currentRunLoop] 91 runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; 92 } 93 handler(nil); 94} 95 96- (BOOL)areExpectationsFulfilled { 97 for (ARDTestExpectation *expectation in _expectations) { 98 if (!expectation.isFulfilled) { 99 return NO; 100 } 101 } 102 return YES; 103} 104 105@end 106 107@interface ARDAppClientTest : ARDTestCase 108@end 109 110@implementation ARDAppClientTest 111 112#pragma mark - Mock helpers 113 114- (id)mockRoomServerClientForRoomId:(NSString *)roomId 115 clientId:(NSString *)clientId 116 isInitiator:(BOOL)isInitiator 117 messages:(NSArray *)messages 118 messageHandler: 119 (void (^)(ARDSignalingMessage *))messageHandler { 120 id mockRoomServerClient = 121 [OCMockObject mockForProtocol:@protocol(ARDRoomServerClient)]; 122 123 // Successful join response. 124 ARDJoinResponse *joinResponse = [[ARDJoinResponse alloc] init]; 125 joinResponse.result = kARDJoinResultTypeSuccess; 126 joinResponse.roomId = roomId; 127 joinResponse.clientId = clientId; 128 joinResponse.isInitiator = isInitiator; 129 joinResponse.messages = messages; 130 131 // Successful message response. 132 ARDMessageResponse *messageResponse = [[ARDMessageResponse alloc] init]; 133 messageResponse.result = kARDMessageResultTypeSuccess; 134 135 // Return join response from above on join. 136 [[[mockRoomServerClient stub] andDo:^(NSInvocation *invocation) { 137 __unsafe_unretained void (^completionHandler)(ARDJoinResponse *response, 138 NSError *error); 139 [invocation getArgument:&completionHandler atIndex:3]; 140 completionHandler(joinResponse, nil); 141 }] joinRoomWithRoomId:roomId isLoopback:NO completionHandler:[OCMArg any]]; 142 143 // Return message response from above on join. 144 [[[mockRoomServerClient stub] andDo:^(NSInvocation *invocation) { 145 __unsafe_unretained ARDSignalingMessage *message; 146 __unsafe_unretained void (^completionHandler)(ARDMessageResponse *response, 147 NSError *error); 148 [invocation getArgument:&message atIndex:2]; 149 [invocation getArgument:&completionHandler atIndex:5]; 150 messageHandler(message); 151 completionHandler(messageResponse, nil); 152 }] sendMessage:[OCMArg any] 153 forRoomId:roomId 154 clientId:clientId 155 completionHandler:[OCMArg any]]; 156 157 // Do nothing on leave. 158 [[[mockRoomServerClient stub] andDo:^(NSInvocation *invocation) { 159 __unsafe_unretained void (^completionHandler)(NSError *error); 160 [invocation getArgument:&completionHandler atIndex:4]; 161 if (completionHandler) { 162 completionHandler(nil); 163 } 164 }] leaveRoomWithRoomId:roomId 165 clientId:clientId 166 completionHandler:[OCMArg any]]; 167 168 return mockRoomServerClient; 169} 170 171- (id)mockSignalingChannelForRoomId:(NSString *)roomId 172 clientId:(NSString *)clientId 173 messageHandler: 174 (void (^)(ARDSignalingMessage *message))messageHandler { 175 id mockSignalingChannel = 176 [OCMockObject niceMockForProtocol:@protocol(ARDSignalingChannel)]; 177 [[mockSignalingChannel stub] registerForRoomId:roomId clientId:clientId]; 178 [[[mockSignalingChannel stub] andDo:^(NSInvocation *invocation) { 179 __unsafe_unretained ARDSignalingMessage *message; 180 [invocation getArgument:&message atIndex:2]; 181 messageHandler(message); 182 }] sendMessage:[OCMArg any]]; 183 return mockSignalingChannel; 184} 185 186- (id)mockTURNClient { 187 id mockTURNClient = 188 [OCMockObject mockForProtocol:@protocol(ARDTURNClient)]; 189 [[[mockTURNClient stub] andDo:^(NSInvocation *invocation) { 190 // Don't return anything in TURN response. 191 __unsafe_unretained void (^completionHandler)(NSArray *turnServers, 192 NSError *error); 193 [invocation getArgument:&completionHandler atIndex:2]; 194 completionHandler([NSArray array], nil); 195 }] requestServersWithCompletionHandler:[OCMArg any]]; 196 return mockTURNClient; 197} 198 199- (ARDAppClient *)createAppClientForRoomId:(NSString *)roomId 200 clientId:(NSString *)clientId 201 isInitiator:(BOOL)isInitiator 202 messages:(NSArray *)messages 203 messageHandler: 204 (void (^)(ARDSignalingMessage *message))messageHandler 205 connectedHandler:(void (^)(void))connectedHandler { 206 id turnClient = [self mockTURNClient]; 207 id signalingChannel = [self mockSignalingChannelForRoomId:roomId 208 clientId:clientId 209 messageHandler:messageHandler]; 210 id roomServerClient = 211 [self mockRoomServerClientForRoomId:roomId 212 clientId:clientId 213 isInitiator:isInitiator 214 messages:messages 215 messageHandler:messageHandler]; 216 id delegate = 217 [OCMockObject niceMockForProtocol:@protocol(ARDAppClientDelegate)]; 218 [[[delegate stub] andDo:^(NSInvocation *invocation) { 219 connectedHandler(); 220 }] appClient:[OCMArg any] didChangeConnectionState:RTCICEConnectionConnected]; 221 222 return [[ARDAppClient alloc] initWithRoomServerClient:roomServerClient 223 signalingChannel:signalingChannel 224 turnClient:turnClient 225 delegate:delegate]; 226} 227 228// Tests that an ICE connection is established between two ARDAppClient objects 229// where one is set up as a caller and the other the answerer. Network 230// components are mocked out and messages are relayed directly from object to 231// object. It's expected that both clients reach the RTCICEConnectionConnected 232// state within a reasonable amount of time. 233- (void)testSession { 234 // Need block arguments here because we're setting up a callbacks before we 235 // create the clients. 236 ARDAppClient *caller = nil; 237 ARDAppClient *answerer = nil; 238 __block __weak ARDAppClient *weakCaller = nil; 239 __block __weak ARDAppClient *weakAnswerer = nil; 240 NSString *roomId = @"testRoom"; 241 NSString *callerId = @"testCallerId"; 242 NSString *answererId = @"testAnswererId"; 243 244 ARDTestExpectation *callerConnectionExpectation = 245 [self expectationWithDescription:@"Caller PC connected."]; 246 ARDTestExpectation *answererConnectionExpectation = 247 [self expectationWithDescription:@"Answerer PC connected."]; 248 249 caller = [self createAppClientForRoomId:roomId 250 clientId:callerId 251 isInitiator:YES 252 messages:[NSArray array] 253 messageHandler:^(ARDSignalingMessage *message) { 254 ARDAppClient *strongAnswerer = weakAnswerer; 255 [strongAnswerer channel:strongAnswerer.channel didReceiveMessage:message]; 256 } connectedHandler:^{ 257 [callerConnectionExpectation fulfill]; 258 }]; 259 // TODO(tkchin): Figure out why DTLS-SRTP constraint causes thread assertion 260 // crash in Debug. 261 caller.defaultPeerConnectionConstraints = [[RTCMediaConstraints alloc] init]; 262 weakCaller = caller; 263 264 answerer = [self createAppClientForRoomId:roomId 265 clientId:answererId 266 isInitiator:NO 267 messages:[NSArray array] 268 messageHandler:^(ARDSignalingMessage *message) { 269 ARDAppClient *strongCaller = weakCaller; 270 [strongCaller channel:strongCaller.channel didReceiveMessage:message]; 271 } connectedHandler:^{ 272 [answererConnectionExpectation fulfill]; 273 }]; 274 // TODO(tkchin): Figure out why DTLS-SRTP constraint causes thread assertion 275 // crash in Debug. 276 answerer.defaultPeerConnectionConstraints = 277 [[RTCMediaConstraints alloc] init]; 278 weakAnswerer = answerer; 279 280 // Kick off connection. 281 [caller connectToRoomWithId:roomId isLoopback:NO isAudioOnly:NO]; 282 [answerer connectToRoomWithId:roomId isLoopback:NO isAudioOnly:NO]; 283 [self waitForExpectationsWithTimeout:20 handler:^(NSError *error) { 284 if (error) { 285 NSLog(@"Expectations error: %@", error); 286 } 287 }]; 288} 289 290@end 291 292@interface ARDSDPUtilsTest : ARDTestCase 293- (void)testPreferVideoCodec; 294@end 295 296@implementation ARDSDPUtilsTest 297 298- (void)testPreferVideoCodec { 299 NSString *sdp = @("m=video 9 RTP/SAVPF 100 116 117 96 120\n" 300 "a=rtpmap:120 H264/90000\n"); 301 NSString *expectedSdp = @("m=video 9 RTP/SAVPF 120 100 116 117 96\n" 302 "a=rtpmap:120 H264/90000\n"); 303 RTCSessionDescription* desc = 304 [[RTCSessionDescription alloc] initWithType:@"offer" sdp:sdp]; 305 RTCSessionDescription *h264Desc = 306 [ARDSDPUtils descriptionForDescription:desc 307 preferredVideoCodec:@"H264"]; 308 EXPECT_TRUE([h264Desc.description isEqualToString:expectedSdp]); 309} 310 311@end 312 313class SignalingTest : public ::testing::Test { 314 protected: 315 static void SetUpTestCase() { 316 rtc::InitializeSSL(); 317 } 318 static void TearDownTestCase() { 319 rtc::CleanupSSL(); 320 } 321}; 322 323TEST_F(SignalingTest, SessionTest) { 324 @autoreleasepool { 325 ARDAppClientTest *test = [[ARDAppClientTest alloc] init]; 326 [test testSession]; 327 } 328} 329 330TEST_F(SignalingTest, SDPTest) { 331 @autoreleasepool { 332 ARDSDPUtilsTest *test = [[ARDSDPUtilsTest alloc] init]; 333 [test testPreferVideoCodec]; 334 } 335} 336 337 338