From e7dd83f2a79a211a303efabfe32a215c8e26f733 Mon Sep 17 00:00:00 2001 From: Anders Carlsson Date: Fri, 19 Jan 2018 11:28:44 +0100 Subject: [PATCH] Add tests for starting and stopping RTCCameraVideoCapturer. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: webrtc:8755 Change-Id: I07d9a203276359069af7ba384c58612df7f2b467 Reviewed-on: https://webrtc-review.googlesource.com/40240 Commit-Queue: Anders Carlsson Reviewed-by: Kári Helgason Reviewed-by: Patrik Höglund Cr-Commit-Position: refs/heads/master@{#21692} --- sdk/BUILD.gn | 5 +- .../PeerConnection/RTCCameraVideoCapturer.m | 13 +- .../UnitTests/RTCCameraVideoCapturerTests.mm | 198 ++++++++++++++++-- 3 files changed, 186 insertions(+), 30 deletions(-) diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index b587a55ca9..e370a0532c 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -625,10 +625,7 @@ if (is_ios || is_mac) { "objc/Framework/UnitTests/objc_video_encoder_factory_tests.mm", "objc/Framework/UnitTests/scoped_cftyperef_tests.mm", ] - if (is_ios && - !(use_ios_simulator && - # The tests crash on these simulator versions: - (ios_sdk_version == "10.0" || ios_sdk_version == "10.1"))) { + if (is_ios) { sources += [ "objc/Framework/UnitTests/RTCCameraVideoCapturerTests.mm" ] } diff --git a/sdk/objc/Framework/Classes/PeerConnection/RTCCameraVideoCapturer.m b/sdk/objc/Framework/Classes/PeerConnection/RTCCameraVideoCapturer.m index 89060efa58..a7d1ec1bc0 100644 --- a/sdk/objc/Framework/Classes/PeerConnection/RTCCameraVideoCapturer.m +++ b/sdk/objc/Framework/Classes/PeerConnection/RTCCameraVideoCapturer.m @@ -47,12 +47,18 @@ const int64_t kNanosecondsPerSecond = 1000000000; @synthesize captureSession = _captureSession; - (instancetype)initWithDelegate:(__weak id)delegate { + return [self initWithDelegate:delegate captureSession:[[AVCaptureSession alloc] init]]; +} + +// This initializer is used for testing. +- (instancetype)initWithDelegate:(__weak id)delegate + captureSession:(AVCaptureSession *)captureSession { if (self = [super initWithDelegate:delegate]) { // Create the capture session and all relevant inputs and outputs. We need // to do this in init because the application may want the capture session // before we start the capturer for e.g. AVCapturePreviewLayer. All objects // created here are retained until dealloc and never recreated. - if (![self setupCaptureSession]) { + if (![self setupCaptureSession:captureSession]) { return nil; } NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; @@ -146,6 +152,7 @@ const int64_t kNanosecondsPerSecond = 1000000000; if (completionHandler) { completionHandler(error); } + _willBeRunning = NO; return; } [self reconfigureCaptureSessionInput]; @@ -379,9 +386,9 @@ const int64_t kNanosecondsPerSecond = 1000000000; return _frameQueue; } -- (BOOL)setupCaptureSession { +- (BOOL)setupCaptureSession:(AVCaptureSession *)captureSession { NSAssert(_captureSession == nil, @"Setup capture session called twice."); - _captureSession = [[AVCaptureSession alloc] init]; + _captureSession = captureSession; #if defined(WEBRTC_IOS) _captureSession.sessionPreset = AVCaptureSessionPresetInputPriority; _captureSession.usesApplicationAudioSession = NO; diff --git a/sdk/objc/Framework/UnitTests/RTCCameraVideoCapturerTests.mm b/sdk/objc/Framework/UnitTests/RTCCameraVideoCapturerTests.mm index f543069c5f..905b97490a 100644 --- a/sdk/objc/Framework/UnitTests/RTCCameraVideoCapturerTests.mm +++ b/sdk/objc/Framework/UnitTests/RTCCameraVideoCapturerTests.mm @@ -60,20 +60,24 @@ CMSampleBufferRef createTestSampleBufferRef() { } #endif @interface RTCCameraVideoCapturer (Tests) +- (instancetype)initWithDelegate:(__weak id)delegate + captureSession:(AVCaptureSession *)captureSession; @end @interface RTCCameraVideoCapturerTests : NSObject @property(nonatomic, strong) id delegateMock; @property(nonatomic, strong) id deviceMock; @property(nonatomic, strong) id captureConnectionMock; +@property(nonatomic, strong) id captureSessionMock; @property(nonatomic, strong) RTCCameraVideoCapturer *capturer; @end @implementation RTCCameraVideoCapturerTests @synthesize delegateMock = _delegateMock; -@synthesize captureConnectionMock = _captureConnectionMock; -@synthesize capturer = _capturer; @synthesize deviceMock = _deviceMock; +@synthesize captureConnectionMock = _captureConnectionMock; +@synthesize captureSessionMock = _captureSessionMock; +@synthesize capturer = _capturer; - (void)setup { self.delegateMock = OCMProtocolMock(@protocol(RTCVideoCapturerDelegate)); @@ -82,6 +86,21 @@ CMSampleBufferRef createTestSampleBufferRef() { self.deviceMock = [self createDeviceMock]; } +- (void)setupWithMockedCaptureSession { + self.captureSessionMock = OCMStrictClassMock([AVCaptureSession class]); + OCMStub([self.captureSessionMock setSessionPreset:[OCMArg any]]); + OCMStub([self.captureSessionMock setUsesApplicationAudioSession:NO]); + OCMStub([self.captureSessionMock canAddOutput:[OCMArg any]]).andReturn(YES); + OCMStub([self.captureSessionMock addOutput:[OCMArg any]]); + OCMStub([self.captureSessionMock beginConfiguration]); + OCMStub([self.captureSessionMock commitConfiguration]); + self.delegateMock = OCMProtocolMock(@protocol(RTCVideoCapturerDelegate)); + self.captureConnectionMock = OCMClassMock([AVCaptureConnection class]); + self.capturer = [[RTCCameraVideoCapturer alloc] initWithDelegate:self.delegateMock + captureSession:self.captureSessionMock]; + self.deviceMock = [self createDeviceMock]; +} + - (void)tearDown { [self.delegateMock stopMocking]; [self.deviceMock stopMocking]; @@ -144,9 +163,11 @@ CMSampleBufferRef createTestSampleBufferRef() { NSArray *supportedFormats = [RTCCameraVideoCapturer supportedFormatsForDevice:self.deviceMock]; // then - EXPECT_EQ(supportedFormats.count, 2u); + EXPECT_EQ(supportedFormats.count, 3u); EXPECT_TRUE([supportedFormats containsObject:validFormat1]); EXPECT_TRUE([supportedFormats containsObject:validFormat2]); + EXPECT_TRUE([supportedFormats containsObject:invalidFormat]); + // cleanup [validFormat1 stopMocking]; [validFormat2 stopMocking]; @@ -349,59 +370,162 @@ CMSampleBufferRef createTestSampleBufferRef() { #endif } +- (void)testStartingAndStoppingCapture { + id expectedDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); + id captureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); + OCMStub([captureDeviceInputMock deviceInputWithDevice:self.deviceMock error:[OCMArg setTo:nil]]) + .andReturn(expectedDeviceInputMock); + + OCMStub([self.deviceMock lockForConfiguration:[OCMArg setTo:nil]]).andReturn(YES); + OCMStub([self.deviceMock unlockForConfiguration]); + OCMStub([_captureSessionMock canAddInput:expectedDeviceInputMock]).andReturn(YES); + OCMStub([_captureSessionMock inputs]).andReturn(@[ expectedDeviceInputMock ]); + OCMStub([_captureSessionMock removeInput:expectedDeviceInputMock]); + + // Set expectation that the capture session should be started with correct device. + OCMExpect([_captureSessionMock addInput:expectedDeviceInputMock]); + OCMExpect([_captureSessionMock startRunning]); + OCMExpect([_captureSessionMock stopRunning]); + + id format = OCMClassMock([AVCaptureDeviceFormat class]); + [self.capturer startCaptureWithDevice:self.deviceMock format:format fps:30]; + [self.capturer stopCapture]; + + // Start capture code is dispatched async. + OCMVerifyAllWithDelay(_captureSessionMock, 15); +} + +- (void)testStartCaptureFailingToLockForConfiguration { + // The captureSessionMock is a strict mock, so this test will crash if the startCapture + // method does not return when failing to lock for configuration. + OCMExpect([self.deviceMock lockForConfiguration:[OCMArg setTo:nil]]).andReturn(NO); + + id format = OCMClassMock([AVCaptureDeviceFormat class]); + [self.capturer startCaptureWithDevice:self.deviceMock format:format fps:30]; + + // Start capture code is dispatched async. + OCMVerifyAllWithDelay(self.deviceMock, 15); +} + +- (void)testStartingAndStoppingCaptureWithCallbacks { + id expectedDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); + id captureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); + OCMStub([captureDeviceInputMock deviceInputWithDevice:self.deviceMock error:[OCMArg setTo:nil]]) + .andReturn(expectedDeviceInputMock); + + OCMStub([self.deviceMock lockForConfiguration:[OCMArg setTo:nil]]).andReturn(YES); + OCMStub([self.deviceMock unlockForConfiguration]); + OCMStub([_captureSessionMock canAddInput:expectedDeviceInputMock]).andReturn(YES); + OCMStub([_captureSessionMock inputs]).andReturn(@[ expectedDeviceInputMock ]); + OCMStub([_captureSessionMock removeInput:expectedDeviceInputMock]); + + // Set expectation that the capture session should be started with correct device. + OCMExpect([_captureSessionMock addInput:expectedDeviceInputMock]); + OCMExpect([_captureSessionMock startRunning]); + OCMExpect([_captureSessionMock stopRunning]); + + dispatch_semaphore_t completedStopSemaphore = dispatch_semaphore_create(0); + + __block BOOL completedStart = NO; + id format = OCMClassMock([AVCaptureDeviceFormat class]); + [self.capturer startCaptureWithDevice:self.deviceMock + format:format + fps:30 + completionHandler:^(NSError *error) { + EXPECT_EQ(error, nil); + completedStart = YES; + }]; + + __block BOOL completedStop = NO; + [self.capturer stopCaptureWithCompletionHandler:^{ + completedStop = YES; + dispatch_semaphore_signal(completedStopSemaphore); + }]; + + dispatch_semaphore_wait(completedStopSemaphore, + dispatch_time(DISPATCH_TIME_NOW, 15.0 * NSEC_PER_SEC)); + OCMVerifyAllWithDelay(_captureSessionMock, 15); + EXPECT_TRUE(completedStart); + EXPECT_TRUE(completedStop); +} + +- (void)testStartCaptureFailingToLockForConfigurationWithCallback { + id expectedDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); + id captureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); + OCMStub([captureDeviceInputMock deviceInputWithDevice:self.deviceMock error:[OCMArg setTo:nil]]) + .andReturn(expectedDeviceInputMock); + + id errorMock = OCMClassMock([NSError class]); + + OCMStub([self.deviceMock lockForConfiguration:[OCMArg setTo:errorMock]]).andReturn(NO); + OCMStub([_captureSessionMock canAddInput:expectedDeviceInputMock]).andReturn(YES); + OCMStub([self.deviceMock unlockForConfiguration]); + + OCMExpect([_captureSessionMock addInput:expectedDeviceInputMock]); + + dispatch_semaphore_t completedStartSemaphore = dispatch_semaphore_create(0); + __block NSError *callbackError = nil; + + id format = OCMClassMock([AVCaptureDeviceFormat class]); + [self.capturer startCaptureWithDevice:self.deviceMock + format:format + fps:30 + completionHandler:^(NSError *error) { + callbackError = error; + dispatch_semaphore_signal(completedStartSemaphore); + }]; + + long ret = dispatch_semaphore_wait(completedStartSemaphore, + dispatch_time(DISPATCH_TIME_NOW, 15.0 * NSEC_PER_SEC)); + EXPECT_EQ(ret, 0); + EXPECT_EQ(callbackError, errorMock); +} + @end -// TODO(kthelgason): Reenable these tests on simulator. -// See bugs.webrtc.org/7813 -#if TARGET_IPHONE_SIMULATOR -#define MAYBE_TEST(f, name) TEST(f, DISABLED_##name) -#else -#define MAYBE_TEST TEST -#endif - -MAYBE_TEST(RTCCameraVideoCapturerTests, SetupSession) { +TEST(RTCCameraVideoCapturerTests, SetupSession) { RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; [test setup]; [test testSetupSession]; [test tearDown]; } -MAYBE_TEST(RTCCameraVideoCapturerTests, SetupSessionOutput) { +TEST(RTCCameraVideoCapturerTests, SetupSessionOutput) { RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; [test setup]; [test testSetupSessionOutput]; [test tearDown]; } -MAYBE_TEST(RTCCameraVideoCapturerTests, SupportedFormatsForDevice) { +TEST(RTCCameraVideoCapturerTests, SupportedFormatsForDevice) { RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; [test setup]; [test testSupportedFormatsForDevice]; [test tearDown]; } -MAYBE_TEST(RTCCameraVideoCapturerTests, CaptureDevices) { +TEST(RTCCameraVideoCapturerTests, CaptureDevices) { RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; [test setup]; [test testCaptureDevices]; [test tearDown]; } -MAYBE_TEST(RTCCameraVideoCapturerTests, DelegateCallbackNotCalledWhenInvalidBuffer) { +TEST(RTCCameraVideoCapturerTests, DelegateCallbackNotCalledWhenInvalidBuffer) { RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; [test setup]; [test testDelegateCallbackNotCalledWhenInvalidBuffer]; [test tearDown]; } -MAYBE_TEST(RTCCameraVideoCapturerTests, DelegateCallbackWithValidBufferAndOrientationUpdate) { +TEST(RTCCameraVideoCapturerTests, DelegateCallbackWithValidBufferAndOrientationUpdate) { RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; [test setup]; [test testDelegateCallbackWithValidBufferAndOrientationUpdate]; [test tearDown]; } -MAYBE_TEST(RTCCameraVideoCapturerTests, RotationCameraBackLandscapeLeft) { +TEST(RTCCameraVideoCapturerTests, RotationCameraBackLandscapeLeft) { RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; [test setup]; [test testRotationCamera:AVCaptureDevicePositionBack @@ -409,7 +533,7 @@ MAYBE_TEST(RTCCameraVideoCapturerTests, RotationCameraBackLandscapeLeft) { [test tearDown]; } -MAYBE_TEST(RTCCameraVideoCapturerTests, RotationCameraFrontLandscapeLeft) { +TEST(RTCCameraVideoCapturerTests, RotationCameraFrontLandscapeLeft) { RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; [test setup]; [test testRotationCamera:AVCaptureDevicePositionFront @@ -417,7 +541,7 @@ MAYBE_TEST(RTCCameraVideoCapturerTests, RotationCameraFrontLandscapeLeft) { [test tearDown]; } -MAYBE_TEST(RTCCameraVideoCapturerTests, RotationCameraBackLandscapeRight) { +TEST(RTCCameraVideoCapturerTests, RotationCameraBackLandscapeRight) { RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; [test setup]; [test testRotationCamera:AVCaptureDevicePositionBack @@ -425,7 +549,7 @@ MAYBE_TEST(RTCCameraVideoCapturerTests, RotationCameraBackLandscapeRight) { [test tearDown]; } -MAYBE_TEST(RTCCameraVideoCapturerTests, RotationCameraFrontLandscapeRight) { +TEST(RTCCameraVideoCapturerTests, RotationCameraFrontLandscapeRight) { RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; [test setup]; [test testRotationCamera:AVCaptureDevicePositionFront @@ -433,16 +557,44 @@ MAYBE_TEST(RTCCameraVideoCapturerTests, RotationCameraFrontLandscapeRight) { [test tearDown]; } -MAYBE_TEST(RTCCameraVideoCapturerTests, RotationCameraFrame) { +TEST(RTCCameraVideoCapturerTests, RotationCameraFrame) { RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; [test setup]; [test testRotationFrame]; [test tearDown]; } -MAYBE_TEST(RTCCameraVideoCapturerTests, ImageExif) { +TEST(RTCCameraVideoCapturerTests, ImageExif) { RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; [test setup]; [test testImageExif]; [test tearDown]; } + +TEST(RTCCameraVideoCapturerTests, StartAndStopCapture) { + RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; + [test setupWithMockedCaptureSession]; + [test testStartingAndStoppingCapture]; + [test tearDown]; +} + +TEST(RTCCameraVideoCapturerTests, StartCaptureFailingToLockForConfiguration) { + RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; + [test setupWithMockedCaptureSession]; + [test testStartCaptureFailingToLockForConfiguration]; + [test tearDown]; +} + +TEST(RTCCameraVideoCapturerTests, StartAndStopCaptureWithCallbacks) { + RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; + [test setupWithMockedCaptureSession]; + [test testStartingAndStoppingCaptureWithCallbacks]; + [test tearDown]; +} + +TEST(RTCCameraVideoCapturerTests, StartCaptureFailingToLockForConfigurationWithCallback) { + RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init]; + [test setupWithMockedCaptureSession]; + [test testStartCaptureFailingToLockForConfigurationWithCallback]; + [test tearDown]; +}