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