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 "rtc_base/gunit.h" 15 16// Width and height don't play any role so lets use predefined values throughout 17// the tests. 18static const int kFormatWidth = 789; 19static const int kFormatHeight = 987; 20 21// Hardcoded framrate to be used throughout the tests. 22static const int kFramerate = 30; 23 24// Same width and height is used so it's ok to expect same cricket::VideoFormat 25static cricket::VideoFormat expectedFormat = 26 cricket::VideoFormat(kFormatWidth, 27 kFormatHeight, 28 cricket::VideoFormat::FpsToInterval(kFramerate), 29 cricket::FOURCC_NV12); 30 31// Mock class for AVCaptureDeviceFormat. 32// Custom implementation needed because OCMock cannot handle the 33// CMVideoDescriptionRef mocking. 34@interface AVCaptureDeviceFormatMock : NSObject 35 36@property (nonatomic, assign) CMVideoFormatDescriptionRef format; 37@property (nonatomic, strong) OCMockObject *rangeMock; 38 39- (instancetype)initWithMediaSubtype:(FourCharCode)subtype 40 minFps:(float)minFps 41 maxFps:(float)maxFps; 42+ (instancetype)validFormat; 43+ (instancetype)invalidFpsFormat; 44+ (instancetype)invalidMediaSubtypeFormat; 45 46@end 47 48@implementation AVCaptureDeviceFormatMock 49 50@synthesize format = _format; 51@synthesize rangeMock = _rangeMock; 52 53- (instancetype)initWithMediaSubtype:(FourCharCode)subtype 54 minFps:(float)minFps 55 maxFps:(float)maxFps { 56 if (self = [super init]) { 57 CMVideoFormatDescriptionCreate(nil, subtype, kFormatWidth, kFormatHeight, 58 nil, &_format); 59 // We can use OCMock for the range. 60 _rangeMock = [OCMockObject mockForClass:[AVFrameRateRange class]]; 61 [[[_rangeMock stub] andReturnValue:@(minFps)] minFrameRate]; 62 [[[_rangeMock stub] andReturnValue:@(maxFps)] maxFrameRate]; 63 } 64 65 return self; 66} 67 68+ (instancetype)validFormat { 69 AVCaptureDeviceFormatMock *instance = [[AVCaptureDeviceFormatMock alloc] 70 initWithMediaSubtype:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 71 minFps:0.0 72 maxFps:30.0]; 73 return instance; 74} 75 76+ (instancetype)invalidFpsFormat { 77 AVCaptureDeviceFormatMock *instance = [[AVCaptureDeviceFormatMock alloc] 78 initWithMediaSubtype:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 79 minFps:0.0 80 maxFps:22.0]; 81 return instance; 82} 83 84+ (instancetype)invalidMediaSubtypeFormat { 85 AVCaptureDeviceFormatMock *instance = [[AVCaptureDeviceFormatMock alloc] 86 initWithMediaSubtype:kCVPixelFormatType_420YpCbCr8Planar 87 minFps:0.0 88 maxFps:60.0]; 89 return instance; 90} 91 92- (void)dealloc { 93 if (_format != nil) { 94 CFRelease(_format); 95 _format = nil; 96 } 97} 98 99// Redefinition of AVCaptureDevice methods we want to mock. 100- (CMVideoFormatDescriptionRef)formatDescription { 101 return self.format; 102} 103 104- (NSArray *)videoSupportedFrameRateRanges { 105 return @[ self.rangeMock ]; 106} 107 108@end 109 110TEST(AVFormatMapperTest, SuportedCricketFormatsWithInvalidFramerateFormats) { 111 // given 112 id mockDevice = OCMClassMock([AVCaptureDevice class]); 113 114 // Valid media subtype, invalid framerate 115 AVCaptureDeviceFormatMock* mock = 116 [AVCaptureDeviceFormatMock invalidFpsFormat]; 117 OCMStub([mockDevice formats]).andReturn(@[ mock ]); 118 119 // when 120 std::set<cricket::VideoFormat> result = 121 webrtc::GetSupportedVideoFormatsForDevice(mockDevice); 122 123 // then 124 EXPECT_TRUE(result.empty()); 125} 126 127TEST(AVFormatMapperTest, SuportedCricketFormatsWithInvalidFormats) { 128 // given 129 id mockDevice = OCMClassMock([AVCaptureDevice class]); 130 131 // Invalid media subtype, valid framerate 132 AVCaptureDeviceFormatMock* mock = 133 [AVCaptureDeviceFormatMock invalidMediaSubtypeFormat]; 134 OCMStub([mockDevice formats]).andReturn(@[ mock ]); 135 136 // when 137 std::set<cricket::VideoFormat> result = 138 webrtc::GetSupportedVideoFormatsForDevice(mockDevice); 139 140 // then 141 EXPECT_TRUE(result.empty()); 142} 143 144TEST(AVFormatMapperTest, SuportedCricketFormats) { 145 // given 146 id mockDevice = OCMClassMock([AVCaptureDevice class]); 147 148 // valid media subtype, valid framerate 149 AVCaptureDeviceFormatMock* mock = [AVCaptureDeviceFormatMock validFormat]; 150 OCMStub([mockDevice formats]).andReturn(@[ mock ]); 151 152 // when 153 std::set<cricket::VideoFormat> result = 154 webrtc::GetSupportedVideoFormatsForDevice(mockDevice); 155 156 // then 157 EXPECT_EQ(1u, result.size()); 158 // make sure the set has the expected format 159 EXPECT_EQ(expectedFormat, *result.begin()); 160} 161 162TEST(AVFormatMapperTest, MediaSubtypePreference) { 163 // given 164 id mockDevice = OCMClassMock([AVCaptureDevice class]); 165 166 // valid media subtype, valid framerate 167 AVCaptureDeviceFormatMock* mockOne = [[AVCaptureDeviceFormatMock alloc] 168 initWithMediaSubtype:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange 169 minFps:0.0 170 maxFps:30.0]; 171 // valid media subtype, valid framerate. 172 // This media subtype should be the preffered one. 173 AVCaptureDeviceFormatMock* mockTwo = [[AVCaptureDeviceFormatMock alloc] 174 initWithMediaSubtype:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 175 minFps:0.0 176 maxFps:30.0]; 177 OCMStub([mockDevice lockForConfiguration:[OCMArg setTo:nil]]).andReturn(YES); 178 OCMStub([mockDevice unlockForConfiguration]); 179 NSArray* array = @[ mockOne, mockTwo ]; 180 OCMStub([mockDevice formats]).andReturn(array); 181 182 // to verify 183 OCMExpect([mockDevice setActiveFormat:(AVCaptureDeviceFormat*)mockTwo]); 184 OCMExpect( 185 [mockDevice setActiveVideoMinFrameDuration:CMTimeMake(1, kFramerate)]); 186 187 // when 188 bool resultFormat = 189 webrtc::SetFormatForCaptureDevice(mockDevice, nil, expectedFormat); 190 191 // then 192 EXPECT_TRUE(resultFormat); 193 [mockDevice verify]; 194} 195 196TEST(AVFormatMapperTest, SetFormatWhenDeviceCannotLock) { 197 // given 198 id mockDevice = OCMClassMock([AVCaptureDevice class]); 199 [[[mockDevice stub] andReturnValue:@(NO)] 200 lockForConfiguration:[OCMArg setTo:nil]]; 201 [[[mockDevice stub] andReturn:@[]] formats]; 202 203 // when 204 bool resultFormat = webrtc::SetFormatForCaptureDevice(mockDevice, nil, 205 cricket::VideoFormat()); 206 207 // then 208 EXPECT_FALSE(resultFormat); 209} 210 211TEST(AVFormatMapperTest, SetFormatWhenFormatIsIncompatible) { 212 // given 213 id mockDevice = OCMClassMock([AVCaptureDevice class]); 214 OCMStub([mockDevice formats]).andReturn(@[]); 215 OCMStub([mockDevice lockForConfiguration:[OCMArg setTo:nil]]).andReturn(YES); 216 NSException* testException = 217 [NSException exceptionWithName:@"Test exception" 218 reason:@"Raised from unit tests" 219 userInfo:nil]; 220 OCMStub([mockDevice setActiveFormat:[OCMArg any]]).andThrow(testException); 221 OCMExpect([mockDevice unlockForConfiguration]); 222 223 // when 224 bool resultFormat = webrtc::SetFormatForCaptureDevice(mockDevice, nil, 225 cricket::VideoFormat()); 226 227 // then 228 EXPECT_FALSE(resultFormat); 229 230 // TODO(denicija): Remove try-catch when Chromium rolls this change: 231 // https://github.com/erikdoe/ocmock/commit/de1419415581dc307045e54bfe9c98c86efea96b 232 // Without it, stubbed exceptions are being re-raised on [mock verify]. 233 // More information here: 234 //https://github.com/erikdoe/ocmock/issues/241 235 @try { 236 [mockDevice verify]; 237 } @catch (NSException* exception) { 238 if ([exception.reason isEqual:testException.reason]) { 239 // Nothing dangerous here 240 EXPECT_TRUE([exception.reason isEqualToString:exception.reason]); 241 } 242 } 243} 244