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- (void)testLockForConfiguration; 117 118@end 119 120@implementation RTCAudioSessionTest 121 122- (void)testLockForConfiguration { 123 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 124 125 for (size_t i = 0; i < 2; i++) { 126 [session lockForConfiguration]; 127 EXPECT_TRUE(session.isLocked); 128 } 129 for (size_t i = 0; i < 2; i++) { 130 EXPECT_TRUE(session.isLocked); 131 [session unlockForConfiguration]; 132 } 133 EXPECT_FALSE(session.isLocked); 134} 135 136- (void)testAddAndRemoveDelegates { 137 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 138 NSMutableArray *delegates = [NSMutableArray array]; 139 const size_t count = 5; 140 for (size_t i = 0; i < count; ++i) { 141 RTCAudioSessionTestDelegate *delegate = 142 [[RTCAudioSessionTestDelegate alloc] init]; 143 [session addDelegate:delegate]; 144 [delegates addObject:delegate]; 145 EXPECT_EQ(i + 1, session.delegates.size()); 146 } 147 [delegates enumerateObjectsUsingBlock:^(RTCAudioSessionTestDelegate *obj, 148 NSUInteger idx, 149 BOOL *stop) { 150 [session removeDelegate:obj]; 151 }]; 152 EXPECT_EQ(0u, session.delegates.size()); 153} 154 155- (void)testPushDelegate { 156 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 157 NSMutableArray *delegates = [NSMutableArray array]; 158 const size_t count = 2; 159 for (size_t i = 0; i < count; ++i) { 160 RTCAudioSessionTestDelegate *delegate = 161 [[RTCAudioSessionTestDelegate alloc] init]; 162 [session addDelegate:delegate]; 163 [delegates addObject:delegate]; 164 } 165 // Test that it gets added to the front of the list. 166 RTCAudioSessionTestDelegate *pushedDelegate = 167 [[RTCAudioSessionTestDelegate alloc] init]; 168 [session pushDelegate:pushedDelegate]; 169 EXPECT_TRUE(pushedDelegate == session.delegates[0]); 170 171 // Test that it stays at the front of the list. 172 for (size_t i = 0; i < count; ++i) { 173 RTCAudioSessionTestDelegate *delegate = 174 [[RTCAudioSessionTestDelegate alloc] init]; 175 [session addDelegate:delegate]; 176 [delegates addObject:delegate]; 177 } 178 EXPECT_TRUE(pushedDelegate == session.delegates[0]); 179 180 // Test that the next one goes to the front too. 181 pushedDelegate = [[RTCAudioSessionTestDelegate alloc] init]; 182 [session pushDelegate:pushedDelegate]; 183 EXPECT_TRUE(pushedDelegate == session.delegates[0]); 184} 185 186// Tests that delegates added to the audio session properly zero out. This is 187// checking an implementation detail (that vectors of __weak work as expected). 188- (void)testZeroingWeakDelegate { 189 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 190 @autoreleasepool { 191 // Add a delegate to the session. There should be one delegate at this 192 // point. 193 RTCAudioSessionTestDelegate *delegate = 194 [[RTCAudioSessionTestDelegate alloc] init]; 195 [session addDelegate:delegate]; 196 EXPECT_EQ(1u, session.delegates.size()); 197 EXPECT_TRUE(session.delegates[0]); 198 } 199 // The previously created delegate should've de-alloced, leaving a nil ptr. 200 EXPECT_FALSE(session.delegates[0]); 201 RTCAudioSessionTestDelegate *delegate = 202 [[RTCAudioSessionTestDelegate alloc] init]; 203 [session addDelegate:delegate]; 204 // On adding a new delegate, nil ptrs should've been cleared. 205 EXPECT_EQ(1u, session.delegates.size()); 206 EXPECT_TRUE(session.delegates[0]); 207} 208 209// Tests that we don't crash when removing delegates in dealloc. 210// Added as a regression test. 211- (void)testRemoveDelegateOnDealloc { 212 @autoreleasepool { 213 RTCTestRemoveOnDeallocDelegate *delegate = 214 [[RTCTestRemoveOnDeallocDelegate alloc] init]; 215 EXPECT_TRUE(delegate); 216 } 217 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 218 EXPECT_EQ(0u, session.delegates.size()); 219} 220 221- (void)testAudioSessionActivation { 222 RTC_OBJC_TYPE(RTCAudioSession) *audioSession = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 223 EXPECT_EQ(0, audioSession.activationCount); 224 [audioSession audioSessionDidActivate:[AVAudioSession sharedInstance]]; 225 EXPECT_EQ(1, audioSession.activationCount); 226 [audioSession audioSessionDidDeactivate:[AVAudioSession sharedInstance]]; 227 EXPECT_EQ(0, audioSession.activationCount); 228} 229 230// Hack - fixes OCMVerify link error 231// Link error is: Undefined symbols for architecture i386: 232// "OCMMakeLocation(objc_object*, char const*, int)", referenced from: 233// -[RTCAudioSessionTest testConfigureWebRTCSession] in RTCAudioSessionTest.o 234// ld: symbol(s) not found for architecture i386 235// REASON: https://github.com/erikdoe/ocmock/issues/238 236OCMLocation *OCMMakeLocation(id testCase, const char *fileCString, int line){ 237 return [OCMLocation locationWithTestCase:testCase 238 file:[NSString stringWithUTF8String:fileCString] 239 line:line]; 240} 241 242- (void)testConfigureWebRTCSession { 243 NSError *error = nil; 244 245 void (^setActiveBlock)(NSInvocation *invocation) = ^(NSInvocation *invocation) { 246 __autoreleasing NSError **retError; 247 [invocation getArgument:&retError atIndex:4]; 248 *retError = [NSError errorWithDomain:@"AVAudioSession" 249 code:AVAudioSessionErrorInsufficientPriority 250 userInfo:nil]; 251 BOOL failure = NO; 252 [invocation setReturnValue:&failure]; 253 }; 254 255 id mockAVAudioSession = OCMPartialMock([AVAudioSession sharedInstance]); 256 OCMStub([[mockAVAudioSession ignoringNonObjectArgs] 257 setActive:YES withOptions:0 error:((NSError __autoreleasing **)[OCMArg anyPointer])]). 258 andDo(setActiveBlock); 259 260 id mockAudioSession = OCMPartialMock([RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]); 261 OCMStub([mockAudioSession session]).andReturn(mockAVAudioSession); 262 263 RTC_OBJC_TYPE(RTCAudioSession) *audioSession = mockAudioSession; 264 EXPECT_EQ(0, audioSession.activationCount); 265 [audioSession lockForConfiguration]; 266 EXPECT_TRUE([audioSession checkLock:nil]); 267 // configureWebRTCSession is forced to fail in the above mock interface, 268 // so activationCount should remain 0 269 OCMExpect([[mockAVAudioSession ignoringNonObjectArgs] 270 setActive:YES withOptions:0 error:((NSError __autoreleasing **)[OCMArg anyPointer])]). 271 andDo(setActiveBlock); 272 OCMExpect([mockAudioSession session]).andReturn(mockAVAudioSession); 273 EXPECT_FALSE([audioSession configureWebRTCSession:&error]); 274 EXPECT_EQ(0, audioSession.activationCount); 275 276 id session = audioSession.session; 277 EXPECT_EQ(session, mockAVAudioSession); 278 EXPECT_EQ(NO, [mockAVAudioSession setActive:YES withOptions:0 error:&error]); 279 [audioSession unlockForConfiguration]; 280 281 OCMVerify([mockAudioSession session]); 282 OCMVerify([[mockAVAudioSession ignoringNonObjectArgs] setActive:YES withOptions:0 error:&error]); 283 OCMVerify([[mockAVAudioSession ignoringNonObjectArgs] setActive:NO withOptions:0 error:&error]); 284 285 [mockAVAudioSession stopMocking]; 286 [mockAudioSession stopMocking]; 287} 288 289- (void)testAudioVolumeDidNotify { 290 MockAVAudioSession *mockAVAudioSession = [[MockAVAudioSession alloc] init]; 291 RTC_OBJC_TYPE(RTCAudioSession) *session = 292 [[RTC_OBJC_TYPE(RTCAudioSession) alloc] initWithAudioSession:mockAVAudioSession]; 293 RTCAudioSessionTestDelegate *delegate = 294 [[RTCAudioSessionTestDelegate alloc] init]; 295 [session addDelegate:delegate]; 296 297 float expectedVolume = 0.75; 298 mockAVAudioSession.outputVolume = expectedVolume; 299 300 EXPECT_EQ(expectedVolume, delegate.outputVolume); 301} 302 303@end 304 305namespace webrtc { 306 307class AudioSessionTest : public ::testing::Test { 308 protected: 309 void TearDown() override { 310 RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 311 for (id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> delegate : session.delegates) { 312 [session removeDelegate:delegate]; 313 } 314 } 315}; 316 317TEST_F(AudioSessionTest, LockForConfiguration) { 318 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init]; 319 [test testLockForConfiguration]; 320} 321 322TEST_F(AudioSessionTest, AddAndRemoveDelegates) { 323 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init]; 324 [test testAddAndRemoveDelegates]; 325} 326 327TEST_F(AudioSessionTest, PushDelegate) { 328 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init]; 329 [test testPushDelegate]; 330} 331 332TEST_F(AudioSessionTest, ZeroingWeakDelegate) { 333 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init]; 334 [test testZeroingWeakDelegate]; 335} 336 337TEST_F(AudioSessionTest, RemoveDelegateOnDealloc) { 338 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init]; 339 [test testRemoveDelegateOnDealloc]; 340} 341 342TEST_F(AudioSessionTest, AudioSessionActivation) { 343 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init]; 344 [test testAudioSessionActivation]; 345} 346 347TEST_F(AudioSessionTest, ConfigureWebRTCSession) { 348 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init]; 349 [test testConfigureWebRTCSession]; 350} 351 352TEST_F(AudioSessionTest, AudioVolumeDidNotify) { 353 RTCAudioSessionTest *test = [[RTCAudioSessionTest alloc] init]; 354 [test testAudioVolumeDidNotify]; 355} 356 357} // namespace webrtc 358