Add tests for starting and stopping RTCCameraVideoCapturer.

Bug: webrtc:8755
Change-Id: I07d9a203276359069af7ba384c58612df7f2b467
Reviewed-on: https://webrtc-review.googlesource.com/40240
Commit-Queue: Anders Carlsson <andersc@webrtc.org>
Reviewed-by: Kári Helgason <kthelgason@webrtc.org>
Reviewed-by: Patrik Höglund <phoglund@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#21692}
diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn
index b587a55..e370a05 100644
--- a/sdk/BUILD.gn
+++ b/sdk/BUILD.gn
@@ -625,10 +625,7 @@
           "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 89060ef..a7d1ec1 100644
--- a/sdk/objc/Framework/Classes/PeerConnection/RTCCameraVideoCapturer.m
+++ b/sdk/objc/Framework/Classes/PeerConnection/RTCCameraVideoCapturer.m
@@ -47,12 +47,18 @@
 @synthesize captureSession = _captureSession;
 
 - (instancetype)initWithDelegate:(__weak id<RTCVideoCapturerDelegate>)delegate {
+  return [self initWithDelegate:delegate captureSession:[[AVCaptureSession alloc] init]];
+}
+
+// This initializer is used for testing.
+- (instancetype)initWithDelegate:(__weak id<RTCVideoCapturerDelegate>)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 @@
                         if (completionHandler) {
                           completionHandler(error);
                         }
+                        _willBeRunning = NO;
                         return;
                       }
                       [self reconfigureCaptureSessionInput];
@@ -379,9 +386,9 @@
   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 f543069..905b974 100644
--- a/sdk/objc/Framework/UnitTests/RTCCameraVideoCapturerTests.mm
+++ b/sdk/objc/Framework/UnitTests/RTCCameraVideoCapturerTests.mm
@@ -60,20 +60,24 @@
 }
 #endif
 @interface RTCCameraVideoCapturer (Tests)<AVCaptureVideoDataOutputSampleBufferDelegate>
+- (instancetype)initWithDelegate:(__weak id<RTCVideoCapturerDelegate>)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 @@
   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 @@
   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 @@
 #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 @@
   [test tearDown];
 }
 
-MAYBE_TEST(RTCCameraVideoCapturerTests, RotationCameraFrontLandscapeLeft) {
+TEST(RTCCameraVideoCapturerTests, RotationCameraFrontLandscapeLeft) {
   RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init];
   [test setup];
   [test testRotationCamera:AVCaptureDevicePositionFront
@@ -417,7 +541,7 @@
   [test tearDown];
 }
 
-MAYBE_TEST(RTCCameraVideoCapturerTests, RotationCameraBackLandscapeRight) {
+TEST(RTCCameraVideoCapturerTests, RotationCameraBackLandscapeRight) {
   RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init];
   [test setup];
   [test testRotationCamera:AVCaptureDevicePositionBack
@@ -425,7 +549,7 @@
   [test tearDown];
 }
 
-MAYBE_TEST(RTCCameraVideoCapturerTests, RotationCameraFrontLandscapeRight) {
+TEST(RTCCameraVideoCapturerTests, RotationCameraFrontLandscapeRight) {
   RTCCameraVideoCapturerTests *test = [[RTCCameraVideoCapturerTests alloc] init];
   [test setup];
   [test testRotationCamera:AVCaptureDevicePositionFront
@@ -433,16 +557,44 @@
   [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];
+}