1/* 2 * Copyright 2016 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#include <vector> 15 16#include "rtc_base/gunit.h" 17 18#import "components/audio/RTCAudioSession+Private.h" 19 20#import "components/audio/RTCAudioSession.h" 21#import "components/audio/RTCAudioSessionConfiguration.h" 22 23@interface RTC_OBJC_TYPE (RTCAudioSession) 24(UnitTesting) 25 26 @property(nonatomic, 27 readonly) std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> > delegates; 28 29- (instancetype)initWithAudioSession:(id)audioSession; 30 31@end 32 33@interface MockAVAudioSession : NSObject 34 35@property (nonatomic, readwrite, assign) float outputVolume; 36 37@end 38 39@implementation MockAVAudioSession 40@synthesize outputVolume = _outputVolume; 41@end 42 43@interface RTCAudioSessionTestDelegate : NSObject <RTC_OBJC_TYPE (RTCAudioSessionDelegate)> 44 45@property (nonatomic, readonly) float outputVolume; 46 47@end 48 49@implementation RTCAudioSessionTestDelegate 50 51@synthesize outputVolume = _outputVolume; 52 53- (instancetype)init { 54 if (self = [super init]) { 55 _outputVolume = -1; 56 } 57 return self; 58} 59 60- (void)audioSessionDidBeginInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session { 61} 62 63- (void)audioSessionDidEndInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session 64 shouldResumeSession:(BOOL)shouldResumeSession { 65} 66 67- (void)audioSessionDidChangeRoute:(RTC_OBJC_TYPE(RTCAudioSession) *)session 68 reason:(AVAudioSessionRouteChangeReason)reason 69 previousRoute:(AVAudioSessionRouteDescription *)previousRoute { 70} 71 72- (void)audioSessionMediaServerTerminated:(RTC_OBJC_TYPE(RTCAudioSession) *)session { 73} 74 75- (void)audioSessionMediaServerReset:(RTC_OBJC_TYPE(RTCAudioSession) *)session { 76} 77 78- (void)audioSessionShouldConfigure:(RTC_OBJC_TYPE(RTCAudioSession) *)session { 79} 80 81- (void)audioSessionShouldUnconfigure:(RTC_OBJC_TYPE(RTCAudioSession) *)session { 82} 83 84- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession 85 didChangeOutputVolume:(float)outputVolume { 86 _outputVolume = outputVolume; 87} 88 89@end 90 91// A delegate that adds itself to the audio session on init and removes itself 92// in its dealloc. 93@interface RTCTestRemoveOnDeallocDelegate : RTCAudioSessionTestDelegate 94@end 95 96@implementation RTCTestRemoveOnDeallocDelegate 97 98- (instancetype)init { 99 if (self = [super init]) { 100 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 101 [session addDelegate:self]; 102 } 103 return self; 104} 105 106- (void)dealloc { 107 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 108 [session removeDelegate:self]; 109} 110 111@end 112 113 114@interface RTCAudioSessionTest : NSObject 115 116@end 117 118@implementation RTCAudioSessionTest 119 120- (void)testAddAndRemoveDelegates { 121 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 122 NSMutableArray *delegates = [NSMutableArray array]; 123 const size_t count = 5; 124 for (size_t i = 0; i < count; ++i) { 125 RTCAudioSessionTestDelegate *delegate = 126 [[RTCAudioSessionTestDelegate alloc] init]; 127 [session addDelegate:delegate]; 128 [delegates addObject:delegate]; 129 EXPECT_EQ(i + 1, session.delegates.size()); 130 } 131 [delegates enumerateObjectsUsingBlock:^(RTCAudioSessionTestDelegate *obj, 132 NSUInteger idx, 133 BOOL *stop) { 134 [session removeDelegate:obj]; 135 }]; 136 EXPECT_EQ(0u, session.delegates.size()); 137} 138 139- (void)testPushDelegate { 140 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 141 NSMutableArray *delegates = [NSMutableArray array]; 142 const size_t count = 2; 143 for (size_t i = 0; i < count; ++i) { 144 RTCAudioSessionTestDelegate *delegate = 145 [[RTCAudioSessionTestDelegate alloc] init]; 146 [session addDelegate:delegate]; 147 [delegates addObject:delegate]; 148 } 149 // Test that it gets added to the front of the list. 150 RTCAudioSessionTestDelegate *pushedDelegate = 151 [[RTCAudioSessionTestDelegate alloc] init]; 152 [session pushDelegate:pushedDelegate]; 153 EXPECT_TRUE(pushedDelegate == session.delegates[0]); 154 155 // Test that it stays at the front of the list. 156 for (size_t i = 0; i < count; ++i) { 157 RTCAudioSessionTestDelegate *delegate = 158 [[RTCAudioSessionTestDelegate alloc] init]; 159 [session addDelegate:delegate]; 160 [delegates addObject:delegate]; 161 } 162 EXPECT_TRUE(pushedDelegate == session.delegates[0]); 163 164 // Test that the next one goes to the front too. 165 pushedDelegate = [[RTCAudioSessionTestDelegate alloc] init]; 166 [session pushDelegate:pushedDelegate]; 167 EXPECT_TRUE(pushedDelegate == session.delegates[0]); 168} 169 170// Tests that delegates added to the audio session properly zero out. This is 171// checking an implementation detail (that vectors of __weak work as expected). 172- (void)testZeroingWeakDelegate { 173 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 174 @autoreleasepool { 175 // Add a delegate to the session. There should be one delegate at this 176 // point. 177 RTCAudioSessionTestDelegate *delegate = 178 [[RTCAudioSessionTestDelegate alloc] init]; 179 [session addDelegate:delegate]; 180 EXPECT_EQ(1u, session.delegates.size()); 181 EXPECT_TRUE(session.delegates[0]); 182 } 183 // The previously created delegate should've de-alloced, leaving a nil ptr. 184 EXPECT_FALSE(session.delegates[0]); 185 RTCAudioSessionTestDelegate *delegate = 186 [[RTCAudioSessionTestDelegate alloc] init]; 187 [session addDelegate:delegate]; 188 // On adding a new delegate, nil ptrs should've been cleared. 189 EXPECT_EQ(1u, session.delegates.size()); 190 EXPECT_TRUE(session.delegates[0]); 191} 192 193// Tests that we don't crash when removing delegates in dealloc. 194// Added as a regression test. 195- (void)testRemoveDelegateOnDealloc { 196 @autoreleasepool { 197 RTCTestRemoveOnDeallocDelegate *delegate = 198 [[RTCTestRemoveOnDeallocDelegate alloc] init]; 199 EXPECT_TRUE(delegate); 200 } 201 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 202 EXPECT_EQ(0u, session.delegates.size()); 203} 204 205- (void)testAudioSessionActivation { 206 RTC_OBJC_TYPE(RTCAudioSession) *audioSession = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 207 EXPECT_EQ(0, audioSession.activationCount); 208 [audioSession audioSessionDidActivate:[AVAudioSession sharedInstance]]; 209 EXPECT_EQ(1, audioSession.activationCount); 210 [audioSession audioSessionDidDeactivate:[AVAudioSession sharedInstance]]; 211 EXPECT_EQ(0, audioSession.activationCount); 212} 213 214// Hack - fixes OCMVerify link error 215// Link error is: Undefined symbols for architecture i386: 216// "OCMMakeLocation(objc_object*, char const*, int)", referenced from: 217// -[RTCAudioSessionTest testConfigureWebRTCSession] in RTCAudioSessionTest.o 218// ld: symbol(s) not found for architecture i386 219// REASON: https://github.com/erikdoe/ocmock/issues/238 220OCMLocation *OCMMakeLocation(id testCase, const char *fileCString, int line){ 221 return [OCMLocation locationWithTestCase:testCase 222 file:[NSString stringWithUTF8String:fileCString] 223 line:line]; 224} 225 226- (void)testConfigureWebRTCSession { 227 NSError *error = nil; 228 229 void (^setActiveBlock)(NSInvocation *invocation) = ^(NSInvocation *invocation) { 230 __autoreleasing NSError **retError; 231 [invocation getArgument:&retError atIndex:4]; 232 *retError = [NSError errorWithDomain:@"AVAudioSession" 233 code:AVAudioSessionErrorInsufficientPriority 234 userInfo:nil]; 235 BOOL failure = NO; 236 [invocation setReturnValue:&failure]; 237 }; 238 239 id mockAVAudioSession = OCMPartialMock([AVAudioSession sharedInstance]); 240 OCMStub([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES 241 withOptions:0 242 error:([OCMArg anyObjectRef])]) 243 .andDo(setActiveBlock); 244 245 id mockAudioSession = OCMPartialMock([RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]); 246 OCMStub([mockAudioSession session]).andReturn(mockAVAudioSession); 247 248 RTC_OBJC_TYPE(RTCAudioSession) *audioSession = mockAudioSession; 249 EXPECT_EQ(0, audioSession.activationCount); 250 [audioSession lockForConfiguration]; 251 // configureWebRTCSession is forced to fail in the above mock interface, 252 // so activationCount should remain 0 253 OCMExpect([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES 254 withOptions:0 255 error:([OCMArg anyObjectRef])]) 256 .andDo(setActiveBlock); 257 OCMExpect([mockAudioSession session]).andReturn(mockAVAudioSession); 258 EXPECT_FALSE([audioSession configureWebRTCSession:&error]); 259 EXPECT_EQ(0, audioSession.activationCount); 260 261 id session = audioSession.session; 262 EXPECT_EQ(session, mockAVAudioSession); 263 EXPECT_EQ(NO, [mockAVAudioSession setActive:YES withOptions:0 error:&error]); 264 [audioSession unlockForConfiguration]; 265 266 OCMVerify([mockAudioSession session]); 267 OCMVerify([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES withOptions:0 error:&error]); 268 OCMVerify([[mockAVAudioSession ignoringNonObjectArgs] setActive:NO withOptions:0 error:&error]); 269 270 [mockAVAudioSession stopMocking]; 271 [mockAudioSession stopMocking]; 272} 273 274- (void)testAudioVolumeDidNotify { 275 MockAVAudioSession *mockAVAudioSession = [[MockAVAudioSession alloc] init]; 276 RTC_OBJC_TYPE(RTCAudioSession) *session = 277 [[RTC_OBJC_TYPE(RTCAudioSession) alloc] initWithAudioSession:mockAVAudioSession]; 278 RTCAudioSessionTestDelegate *delegate = 279 [[RTCAudioSessionTestDelegate alloc] init]; 280 [session addDelegate:delegate]; 281 282 float expectedVolume = 0.75; 283 mockAVAudioSession.outputVolume = expectedVolume; 284 285 EXPECT_EQ(expectedVolume, delegate.outputVolume); 286} 287 288@end 289 290namespace webrtc { 291 292class AudioSessionTest : public ::testing::Test { 293 protected: 294 void TearDown() override { 295 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 296 for (id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> delegate : session.delegates) { 297 [session removeDelegate:delegate]; 298 } 299 } 300}; 301 302TEST_F(AudioSessionTest, AddAndRemoveDelegates) { 303 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init]; 304 [test testAddAndRemoveDelegates]; 305} 306 307TEST_F(AudioSessionTest, PushDelegate) { 308 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init]; 309 [test testPushDelegate]; 310} 311 312TEST_F(AudioSessionTest, ZeroingWeakDelegate) { 313 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init]; 314 [test testZeroingWeakDelegate]; 315} 316 317TEST_F(AudioSessionTest, RemoveDelegateOnDealloc) { 318 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init]; 319 [test testRemoveDelegateOnDealloc]; 320} 321 322TEST_F(AudioSessionTest, AudioSessionActivation) { 323 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init]; 324 [test testAudioSessionActivation]; 325} 326 327TEST_F(AudioSessionTest, ConfigureWebRTCSession) { 328 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init]; 329 [test testConfigureWebRTCSession]; 330} 331 332TEST_F(AudioSessionTest, AudioVolumeDidNotify) { 333 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init]; 334 [test testAudioVolumeDidNotify]; 335} 336 337} // namespace webrtc 338