blob: 2470e50c9ebd1bcaf4d30d3c60a5a49c6f11168c [file] [log] [blame]
/*
* 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>
#import "ARDAppClient+Internal.h"
#import "ARDJoinResponse+Internal.h"
#import "ARDMessageResponse+Internal.h"
#import "ARDSDPUtils.h"
#import "RTCMediaConstraints.h"
#import "RTCPeerConnectionFactory.h"
#import "RTCSessionDescription.h"
#include "webrtc/base/gunit.h"
#include "webrtc/base/ssladapter.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:RTCICEConnectionConnected];
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 RTCICEConnectionConnected
// 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] init];
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] init];
weakAnswerer = answerer;
// Kick off connection.
[caller connectToRoomWithId:roomId isLoopback:NO isAudioOnly:NO];
[answerer connectToRoomWithId:roomId isLoopback:NO isAudioOnly: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:@"offer" 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];
}
}