1/* 2 * Copyright 2017 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 <XCTest/XCTest.h> 12 13#import <Foundation/Foundation.h> 14#import <MetalKit/MetalKit.h> 15#import <OCMock/OCMock.h> 16 17#import "components/renderer/metal/RTCMTLVideoView.h" 18 19#import "api/video_frame_buffer/RTCNativeI420Buffer.h" 20#import "base/RTCVideoFrameBuffer.h" 21#import "components/renderer/metal/RTCMTLNV12Renderer.h" 22#import "components/video_frame_buffer/RTCCVPixelBuffer.h" 23 24// Extension of RTC_OBJC_TYPE(RTCMTLVideoView) for testing purposes. 25@interface RTC_OBJC_TYPE (RTCMTLVideoView) 26(Testing) 27 28 @property(nonatomic, readonly) MTKView *metalView; 29 30+ (BOOL)isMetalAvailable; 31+ (UIView *)createMetalView:(CGRect)frame; 32+ (id<RTCMTLRenderer>)createNV12Renderer; 33+ (id<RTCMTLRenderer>)createI420Renderer; 34- (void)drawInMTKView:(id)view; 35@end 36 37@interface RTCMTLVideoViewTests : XCTestCase 38@property(nonatomic, strong) id classMock; 39@property(nonatomic, strong) id rendererNV12Mock; 40@property(nonatomic, strong) id rendererI420Mock; 41@property(nonatomic, strong) id frameMock; 42@end 43 44@implementation RTCMTLVideoViewTests 45 46@synthesize classMock = _classMock; 47@synthesize rendererNV12Mock = _rendererNV12Mock; 48@synthesize rendererI420Mock = _rendererI420Mock; 49@synthesize frameMock = _frameMock; 50 51- (void)setUp { 52 self.classMock = OCMClassMock([RTC_OBJC_TYPE(RTCMTLVideoView) class]); 53 [self startMockingNilView]; 54} 55 56- (void)tearDown { 57 [self.classMock stopMocking]; 58 [self.rendererI420Mock stopMocking]; 59 [self.rendererNV12Mock stopMocking]; 60 [self.frameMock stopMocking]; 61 self.classMock = nil; 62 self.rendererI420Mock = nil; 63 self.rendererNV12Mock = nil; 64 self.frameMock = nil; 65} 66 67- (id)frameMockWithCVPixelBuffer:(BOOL)hasCVPixelBuffer { 68 id frameMock = OCMClassMock([RTC_OBJC_TYPE(RTCVideoFrame) class]); 69 if (hasCVPixelBuffer) { 70 CVPixelBufferRef pixelBufferRef; 71 CVPixelBufferCreate( 72 kCFAllocatorDefault, 200, 200, kCVPixelFormatType_420YpCbCr8Planar, nil, &pixelBufferRef); 73 OCMStub([frameMock buffer]) 74 .andReturn([[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:pixelBufferRef]); 75 } else { 76 OCMStub([frameMock buffer]) 77 .andReturn([[RTC_OBJC_TYPE(RTCI420Buffer) alloc] initWithWidth:200 height:200]); 78 } 79 OCMStub([frameMock timeStampNs]).andReturn(arc4random_uniform(INT_MAX)); 80 return frameMock; 81} 82 83- (id)rendererMockWithSuccessfulSetup:(BOOL)success { 84 id rendererMock = OCMClassMock([RTCMTLRenderer class]); 85 OCMStub([rendererMock addRenderingDestination:[OCMArg any]]).andReturn(success); 86 return rendererMock; 87} 88 89- (void)startMockingNilView { 90 // Use OCMock 2 syntax here until OCMock is upgraded to 3.4 91 [[[self.classMock stub] andReturn:nil] createMetalView:CGRectZero]; 92} 93 94#pragma mark - Test cases 95 96- (void)testInitAssertsIfMetalUnavailabe { 97 // given 98 OCMStub([self.classMock isMetalAvailable]).andReturn(NO); 99 100 // when 101 BOOL asserts = NO; 102 @try { 103 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 104 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectZero]; 105 (void)realView; 106 } @catch (NSException *ex) { 107 asserts = YES; 108 } 109 110 XCTAssertTrue(asserts); 111} 112 113- (void)testRTCVideoRenderNilFrameCallback { 114 // given 115 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 116 117 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 118 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 119 self.frameMock = OCMClassMock([RTC_OBJC_TYPE(RTCVideoFrame) class]); 120 121 [[self.frameMock reject] buffer]; 122 [[self.classMock reject] createNV12Renderer]; 123 [[self.classMock reject] createI420Renderer]; 124 125 // when 126 [realView renderFrame:nil]; 127 [realView drawInMTKView:realView.metalView]; 128 129 // then 130 [self.frameMock verify]; 131 [self.classMock verify]; 132} 133 134- (void)testRTCVideoRenderFrameCallbackI420 { 135 // given 136 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 137 self.rendererI420Mock = [self rendererMockWithSuccessfulSetup:YES]; 138 self.frameMock = [self frameMockWithCVPixelBuffer:NO]; 139 140 OCMExpect([self.rendererI420Mock drawFrame:self.frameMock]); 141 OCMExpect([self.classMock createI420Renderer]).andReturn(self.rendererI420Mock); 142 [[self.classMock reject] createNV12Renderer]; 143 144 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 145 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 146 147 // when 148 [realView renderFrame:self.frameMock]; 149 [realView drawInMTKView:realView.metalView]; 150 151 // then 152 [self.rendererI420Mock verify]; 153 [self.classMock verify]; 154} 155 156- (void)testRTCVideoRenderFrameCallbackNV12 { 157 // given 158 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 159 self.rendererNV12Mock = [self rendererMockWithSuccessfulSetup:YES]; 160 self.frameMock = [self frameMockWithCVPixelBuffer:YES]; 161 162 OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); 163 OCMExpect([self.classMock createNV12Renderer]).andReturn(self.rendererNV12Mock); 164 [[self.classMock reject] createI420Renderer]; 165 166 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 167 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 168 169 // when 170 [realView renderFrame:self.frameMock]; 171 [realView drawInMTKView:realView.metalView]; 172 173 // then 174 [self.rendererNV12Mock verify]; 175 [self.classMock verify]; 176} 177 178- (void)testRTCVideoRenderWorksAfterReconstruction { 179 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 180 self.rendererNV12Mock = [self rendererMockWithSuccessfulSetup:YES]; 181 self.frameMock = [self frameMockWithCVPixelBuffer:YES]; 182 183 OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); 184 OCMExpect([self.classMock createNV12Renderer]).andReturn(self.rendererNV12Mock); 185 [[self.classMock reject] createI420Renderer]; 186 187 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 188 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 189 190 [realView renderFrame:self.frameMock]; 191 [realView drawInMTKView:realView.metalView]; 192 [self.rendererNV12Mock verify]; 193 [self.classMock verify]; 194 195 // Recreate view. 196 realView = [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 197 OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); 198 // View hould reinit renderer. 199 OCMExpect([self.classMock createNV12Renderer]).andReturn(self.rendererNV12Mock); 200 201 [realView renderFrame:self.frameMock]; 202 [realView drawInMTKView:realView.metalView]; 203 [self.rendererNV12Mock verify]; 204 [self.classMock verify]; 205} 206 207- (void)testDontRedrawOldFrame { 208 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 209 self.rendererNV12Mock = [self rendererMockWithSuccessfulSetup:YES]; 210 self.frameMock = [self frameMockWithCVPixelBuffer:YES]; 211 212 OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); 213 OCMExpect([self.classMock createNV12Renderer]).andReturn(self.rendererNV12Mock); 214 [[self.classMock reject] createI420Renderer]; 215 216 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 217 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 218 [realView renderFrame:self.frameMock]; 219 [realView drawInMTKView:realView.metalView]; 220 221 [self.rendererNV12Mock verify]; 222 [self.classMock verify]; 223 224 [[self.rendererNV12Mock reject] drawFrame:[OCMArg any]]; 225 226 [realView renderFrame:self.frameMock]; 227 [realView drawInMTKView:realView.metalView]; 228 229 [self.rendererNV12Mock verify]; 230} 231 232- (void)testDoDrawNewFrame { 233 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 234 self.rendererNV12Mock = [self rendererMockWithSuccessfulSetup:YES]; 235 self.frameMock = [self frameMockWithCVPixelBuffer:YES]; 236 237 OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); 238 OCMExpect([self.classMock createNV12Renderer]).andReturn(self.rendererNV12Mock); 239 [[self.classMock reject] createI420Renderer]; 240 241 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 242 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 243 [realView renderFrame:self.frameMock]; 244 [realView drawInMTKView:realView.metalView]; 245 246 [self.rendererNV12Mock verify]; 247 [self.classMock verify]; 248 249 // Get new frame. 250 self.frameMock = [self frameMockWithCVPixelBuffer:YES]; 251 OCMExpect([self.rendererNV12Mock drawFrame:self.frameMock]); 252 253 [realView renderFrame:self.frameMock]; 254 [realView drawInMTKView:realView.metalView]; 255 256 [self.rendererNV12Mock verify]; 257} 258 259- (void)testReportsSizeChangesToDelegate { 260 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 261 262 id delegateMock = OCMProtocolMock(@protocol(RTC_OBJC_TYPE(RTCVideoViewDelegate))); 263 CGSize size = CGSizeMake(640, 480); 264 OCMExpect([delegateMock videoView:[OCMArg any] didChangeVideoSize:size]); 265 266 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = 267 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectMake(0, 0, 640, 480)]; 268 realView.delegate = delegateMock; 269 [realView setSize:size]; 270 271 // Delegate method is invoked with a dispatch_async. 272 OCMVerifyAllWithDelay(delegateMock, 1); 273} 274 275- (void)testSetContentMode { 276 OCMStub([self.classMock isMetalAvailable]).andReturn(YES); 277 id metalKitView = OCMClassMock([MTKView class]); 278 [[[[self.classMock stub] ignoringNonObjectArgs] andReturn:metalKitView] 279 createMetalView:CGRectZero]; 280 OCMExpect([metalKitView setContentMode:UIViewContentModeScaleAspectFill]); 281 282 RTC_OBJC_TYPE(RTCMTLVideoView) *realView = [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] init]; 283 [realView setVideoContentMode:UIViewContentModeScaleAspectFill]; 284 285 OCMVerify(metalKitView); 286} 287 288@end 289