Add unit tests for RTCMTLVideoView.
To properly test the functionality, following changes were needed - Make RTCMTLVideoView compiliable for all cpu architectures not just arm64. This is needed so that the test can run on any device and on simulator as well. - Refactor RTCMTLVideoView to have mockable class methods. The unittest class, RTCMTLVideoViewTests was designed to provide easy transition to XCTest when the time comes for that. To transition to XCTest it would suffice to inherit from XCTestCase and remove the gtest methods. BUG=webrtc:7079 Review-Url: https://codereview.webrtc.org/2723903003 Cr-Commit-Position: refs/heads/master@{#17014}
This commit is contained in:
parent
446fb136dc
commit
0ebe0199ac
@ -94,6 +94,7 @@ if (is_ios || is_mac) {
|
||||
|
||||
rtc_static_library("rtc_sdk_peerconnection_objc") {
|
||||
sources = [
|
||||
"objc/Framework/Classes/Metal/RTCMTLNV12Renderer.h",
|
||||
"objc/Framework/Classes/RTCAVFoundationVideoCapturerInternal.h",
|
||||
"objc/Framework/Classes/RTCAVFoundationVideoCapturerInternal.mm",
|
||||
"objc/Framework/Classes/RTCAVFoundationVideoSource+Private.h",
|
||||
@ -257,6 +258,12 @@ if (is_ios || is_mac) {
|
||||
"objc/Framework/UnitTests/RTCSessionDescriptionTest.mm",
|
||||
"objc/Framework/UnitTests/avformatmappertests.mm",
|
||||
]
|
||||
if (is_ios) {
|
||||
sources += [ "objc/Framework/UnitTests/RTCMTLVideoViewTests.mm" ]
|
||||
if (current_cpu != "arm64") {
|
||||
sources += [ "objc/Framework/Classes/Metal/RTCMTLVideoView.m" ]
|
||||
}
|
||||
}
|
||||
deps = [
|
||||
":rtc_sdk_peerconnection_objc",
|
||||
"//third_party/ocmock",
|
||||
|
||||
@ -24,12 +24,6 @@
|
||||
* @param frame The frame to be rendered.
|
||||
*/
|
||||
- (void)drawFrame:(RTCVideoFrame *)frame;
|
||||
@end
|
||||
|
||||
/**
|
||||
* Implementation of RTCMTLRenderer protocol for rendering native nv12 video frames.
|
||||
*/
|
||||
@interface RTCMTLNV12Renderer : NSObject <RTCMTLRenderer>
|
||||
|
||||
/**
|
||||
* Sets the provided view as rendering destination if possible.
|
||||
@ -37,5 +31,13 @@
|
||||
* If not possible method returns NO and callers of the method are responisble for performing
|
||||
* cleanups.
|
||||
*/
|
||||
- (BOOL)addRenderingDestination:(__kindof MTKView *)view;
|
||||
- (BOOL)addRenderingDestination:(__kindof UIView *)view;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Implementation of RTCMTLRenderer protocol for rendering native nv12 video frames.
|
||||
*/
|
||||
@interface RTCMTLNV12Renderer : NSObject <RTCMTLRenderer>
|
||||
|
||||
@end
|
||||
|
||||
@ -18,15 +18,18 @@
|
||||
|
||||
#import "RTCMTLNV12Renderer.h"
|
||||
|
||||
// To avoid unreconized symbol linker errors, we're taking advantage of the objc runtime.
|
||||
// Linking errors occur when compiling for architectures that don't support Metal.
|
||||
#define MTKViewClass NSClassFromString(@"MTKView")
|
||||
#define RTCMTLNV12RendererClass NSClassFromString(@"RTCMTLNV12Renderer")
|
||||
|
||||
@interface RTCMTLVideoView () <MTKViewDelegate>
|
||||
@property(nonatomic, strong) id<RTCMTLRenderer> renderer;
|
||||
@property(nonatomic, strong) RTCMTLNV12Renderer *renderer;
|
||||
@property(nonatomic, strong) MTKView *metalView;
|
||||
@property(atomic, strong) RTCVideoFrame *videoFrame;
|
||||
@end
|
||||
|
||||
@implementation RTCMTLVideoView {
|
||||
id<RTCMTLRenderer> _renderer;
|
||||
}
|
||||
@implementation RTCMTLVideoView
|
||||
|
||||
@synthesize renderer = _renderer;
|
||||
@synthesize metalView = _metalView;
|
||||
@ -58,11 +61,41 @@
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (MTKView *)createMetalView:(CGRect)frame {
|
||||
MTKView *view = [[MTKViewClass alloc] initWithFrame:frame];
|
||||
return view;
|
||||
}
|
||||
|
||||
+ (RTCMTLNV12Renderer *)createMetalRenderer {
|
||||
RTCMTLNV12Renderer *renderer = [[RTCMTLNV12RendererClass alloc] init];
|
||||
return renderer;
|
||||
}
|
||||
|
||||
- (void)configure {
|
||||
if ([RTCMTLVideoView isMetalAvailable]) {
|
||||
_metalView = [[MTKView alloc] initWithFrame:self.bounds];
|
||||
[self addSubview:_metalView];
|
||||
if (![RTCMTLVideoView isMetalAvailable]) {
|
||||
RTCLog("Metal unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
_metalView = [RTCMTLVideoView createMetalView:self.bounds];
|
||||
_renderer = [RTCMTLVideoView createMetalRenderer];
|
||||
|
||||
if ([self configureMetalRenderer]) {
|
||||
[self configureMetalView];
|
||||
} else {
|
||||
_renderer = nil;
|
||||
RTCLogError("Metal configuration falied.");
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)configureMetalRenderer {
|
||||
return [_renderer addRenderingDestination:_metalView];
|
||||
}
|
||||
|
||||
- (void)configureMetalView {
|
||||
if (_metalView) {
|
||||
_metalView.delegate = self;
|
||||
[self addSubview:_metalView];
|
||||
_metalView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_metalView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
UILayoutGuide *margins = self.layoutMarginsGuide;
|
||||
@ -70,20 +103,14 @@
|
||||
[_metalView.bottomAnchor constraintEqualToAnchor:margins.bottomAnchor].active = YES;
|
||||
[_metalView.leftAnchor constraintEqualToAnchor:margins.leftAnchor].active = YES;
|
||||
[_metalView.rightAnchor constraintEqualToAnchor:margins.rightAnchor].active = YES;
|
||||
|
||||
_renderer = [[RTCMTLNV12Renderer alloc] init];
|
||||
if (![(RTCMTLNV12Renderer *)_renderer addRenderingDestination:_metalView]) {
|
||||
_renderer = nil;
|
||||
};
|
||||
} else {
|
||||
RTCLogError("Metal configuration falied.");
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - MTKViewDelegate methods
|
||||
|
||||
- (void)drawInMTKView:(nonnull MTKView *)view {
|
||||
NSAssert(view == self.metalView, @"Receiving draw callbacks from foreign instance.");
|
||||
[_renderer drawFrame:self.videoFrame];
|
||||
[self.renderer drawFrame:self.videoFrame];
|
||||
}
|
||||
|
||||
- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
|
||||
@ -92,8 +119,8 @@
|
||||
#pragma mark - RTCVideoRenderer
|
||||
|
||||
- (void)setSize:(CGSize)size {
|
||||
_metalView.drawableSize = size;
|
||||
[_metalView draw];
|
||||
self.metalView.drawableSize = size;
|
||||
[self.metalView draw];
|
||||
}
|
||||
|
||||
- (void)renderFrame:(nullable RTCVideoFrame *)frame {
|
||||
|
||||
168
webrtc/sdk/objc/Framework/UnitTests/RTCMTLVideoViewTests.mm
Normal file
168
webrtc/sdk/objc/Framework/UnitTests/RTCMTLVideoViewTests.mm
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
#include "webrtc/base/gunit.h"
|
||||
|
||||
#include <Metal/RTCMTLNV12Renderer.h>
|
||||
#include <WebRTC/RTCMTLVideoView.h>
|
||||
|
||||
// Extension of RTCMTLVideoView for testing purposes.
|
||||
@interface RTCMTLVideoView (Testing)
|
||||
@property(nonatomic, strong) id<RTCMTLRenderer> renderer;
|
||||
@property(nonatomic, strong) UIView* metalView;
|
||||
@property(atomic, strong) RTCVideoFrame* videoFrame;
|
||||
|
||||
+ (BOOL)isMetalAvailable;
|
||||
+ (UIView*)createMetalView:(CGRect)frame;
|
||||
+ (id<RTCMTLRenderer>)createMetalRenderer;
|
||||
- (void)drawInMTKView:(id)view;
|
||||
@end
|
||||
|
||||
@interface RTCMTLVideoViewTests : NSObject
|
||||
@property(nonatomic, strong) id classMock;
|
||||
@property(nonatomic, strong) id metalViewMock;
|
||||
@property(nonatomic, strong) id rendererMock;
|
||||
@end
|
||||
|
||||
@implementation RTCMTLVideoViewTests
|
||||
|
||||
@synthesize classMock = _classMock;
|
||||
@synthesize metalViewMock = _metalViewMock;
|
||||
@synthesize rendererMock = _rendererMock;
|
||||
|
||||
- (void)setup {
|
||||
self.classMock = OCMClassMock([RTCMTLVideoView class]);
|
||||
|
||||
self.metalViewMock = OCMClassMock([RTCMTLVideoViewTests class]);
|
||||
// NOTE: OCMock doesen't provide modern syntax for -ignoringNonObjectArgs.
|
||||
[[[[self.classMock stub] ignoringNonObjectArgs] andReturn:self.metalViewMock]
|
||||
createMetalView:CGRectZero];
|
||||
|
||||
self.rendererMock = OCMProtocolMock(@protocol(RTCMTLRenderer));
|
||||
OCMStub([self.classMock createMetalRenderer]).andReturn(self.rendererMock);
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
[self.classMock stopMocking];
|
||||
[self.rendererMock stopMocking];
|
||||
[self.metalViewMock stopMocking];
|
||||
self.classMock = nil;
|
||||
self.rendererMock = nil;
|
||||
self.metalViewMock = nil;
|
||||
}
|
||||
|
||||
- (void)testMetalConfigureNotExecuted {
|
||||
// when
|
||||
OCMStub([self.classMock isMetalAvailable]).andReturn(NO);
|
||||
RTCMTLVideoView *realView = [[RTCMTLVideoView alloc] init];
|
||||
|
||||
// then
|
||||
EXPECT_TRUE(realView.renderer == nil);
|
||||
EXPECT_TRUE(realView.metalView == nil);
|
||||
}
|
||||
|
||||
- (void)testMetalConfigureExecuted {
|
||||
// given
|
||||
OCMStub([self.classMock isMetalAvailable]).andReturn(YES);
|
||||
OCMStub([self.rendererMock addRenderingDestination:self.metalViewMock])
|
||||
.andReturn(NO);
|
||||
|
||||
// when
|
||||
RTCMTLVideoView *realView = [[RTCMTLVideoView alloc] init];
|
||||
|
||||
// then
|
||||
EXPECT_TRUE(realView.renderer == nil);
|
||||
EXPECT_TRUE(realView.metalView != nil);
|
||||
}
|
||||
|
||||
- (void)testMetalDrawCallback {
|
||||
// given
|
||||
OCMStub([self.classMock isMetalAvailable]).andReturn(NO);
|
||||
OCMExpect([self.rendererMock drawFrame:[OCMArg any]]);
|
||||
RTCMTLVideoView *realView = [[RTCMTLVideoView alloc] init];
|
||||
realView.metalView = self.metalViewMock;
|
||||
realView.renderer = self.rendererMock;
|
||||
|
||||
// when
|
||||
[realView drawInMTKView:self.metalViewMock];
|
||||
|
||||
// then
|
||||
[self.rendererMock verify];
|
||||
}
|
||||
|
||||
- (void)testRTCVideoRenderNilFrameCallback {
|
||||
// given
|
||||
OCMStub([self.classMock isMetalAvailable]).andReturn(NO);
|
||||
RTCMTLVideoView *realView = [[RTCMTLVideoView alloc] init];
|
||||
|
||||
// when
|
||||
[realView renderFrame:nil];
|
||||
|
||||
// then
|
||||
EXPECT_TRUE(realView.videoFrame == nil);
|
||||
}
|
||||
|
||||
- (void)testRTCVideoRenderFrameCallback {
|
||||
// given
|
||||
OCMStub([self.classMock isMetalAvailable]).andReturn(NO);
|
||||
RTCMTLVideoView *realView = [[RTCMTLVideoView alloc] init];
|
||||
id frame = OCMClassMock([RTCVideoFrame class]);
|
||||
realView.metalView = self.metalViewMock;
|
||||
realView.renderer = self.rendererMock;
|
||||
OCMExpect([self.rendererMock drawFrame:frame]);
|
||||
|
||||
// when
|
||||
[realView renderFrame:frame];
|
||||
[realView drawInMTKView:self.metalViewMock];
|
||||
|
||||
// then
|
||||
EXPECT_EQ(realView.videoFrame, frame);
|
||||
[self.rendererMock verify];
|
||||
}
|
||||
@end
|
||||
|
||||
TEST(RTCMTLVideoViewTests, MetalConfigureNotExecuted) {
|
||||
RTCMTLVideoViewTests *test = [[RTCMTLVideoViewTests alloc] init];
|
||||
[test setup];
|
||||
[test testMetalConfigureNotExecuted];
|
||||
[test tearDown];
|
||||
}
|
||||
|
||||
|
||||
TEST(RTCMTLVideoViewTests, MetalConfigureExecuted) {
|
||||
RTCMTLVideoViewTests *test = [[RTCMTLVideoViewTests alloc] init];
|
||||
[test setup];
|
||||
[test testMetalConfigureExecuted];
|
||||
[test tearDown];
|
||||
}
|
||||
|
||||
TEST(RTCMTLVideoViewTests, MetalDrawCallback) {
|
||||
RTCMTLVideoViewTests *test = [[RTCMTLVideoViewTests alloc] init];
|
||||
[test setup];
|
||||
[test testMetalDrawCallback];
|
||||
[test tearDown];
|
||||
}
|
||||
|
||||
TEST(RTCMTLVideoViewTests, RTCVideoRenderNilFrameCallback) {
|
||||
RTCMTLVideoViewTests *test = [[RTCMTLVideoViewTests alloc] init];
|
||||
[test setup];
|
||||
[test testRTCVideoRenderNilFrameCallback];
|
||||
[test tearDown];
|
||||
}
|
||||
|
||||
TEST(RTCMTLVideoViewTests, RTCVideoRenderFrameCallback) {
|
||||
RTCMTLVideoViewTests *test = [[RTCMTLVideoViewTests alloc] init];
|
||||
[test setup];
|
||||
[test testRTCVideoRenderFrameCallback];
|
||||
[test tearDown];
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user