Permalink
Browse files

Using `drawViewHierarchyInRect` instead of `renderInContext` for snap…

…shot tests

Summary:
We found that `-[CALayer renderInContext:]` produces bad results in some cases (which is actually documented thing!),
so we decided to replace it with `-[UIView drawViewHierarchyInRect:]` which is more reliable (I hope).
As part of this change I completly removed support of `CALayer` from local fork of `RNTesterIntegrationTests`.

See #14011 (comment) for more details.

janicduplessis

Reviewed By: javache

Differential Revision: D5129492

fbshipit-source-id: 6a9227037c85bb8f862d55267f5301e177985ad9
  • Loading branch information...
shergin authored and facebook-github-bot committed May 26, 2017
1 parent cc1a4b0 commit 3df537a25c9490da85ba5620c95ddaef98d53d18
Showing with 36 additions and 185 deletions.
  1. +0 −51 Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestCase.h
  2. +4 −29 Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestCase.m
  3. +1 −28 Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.h
  4. +31 −77 Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.m
  5. BIN ...NTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testARTExample_1-iOS10@2x.png
  6. BIN ...esterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testARTExample_1-iOS10_tvOS.png
  7. BIN ...ster/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testARTExample_1@2x.png
  8. BIN ...er/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testARTExample_1_tvOS.png
  9. BIN ...sterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testLayoutExample_1-iOS10@2x.png
  10. BIN ...erIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testLayoutExample_1-iOS10_tvOS.png
  11. BIN ...r/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testLayoutExample_1@2x.png
  12. BIN ...RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testLayoutExample_1_tvOS.png
  13. BIN ...IntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testScrollViewExample_1-iOS10@2x.png
  14. BIN ...tegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testScrollViewExample_1-iOS10_tvOS.png
  15. BIN ...TesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testScrollViewExample_1@2x.png
  16. BIN ...sterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testScrollViewExample_1_tvOS.png
  17. BIN ...sterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testSliderExample_1-iOS10@2x.png
  18. BIN ...r/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testSliderExample_1@2x.png
  19. BIN ...sterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testSwitchExample_1-iOS10@2x.png
  20. BIN ...r/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testSwitchExample_1@2x.png
  21. BIN ...sterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testTabBarExample_1-iOS10@2x.png
  22. BIN ...erIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testTabBarExample_1-iOS10_tvOS.png
  23. BIN ...r/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testTabBarExample_1@2x.png
  24. BIN ...RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testTabBarExample_1_tvOS.png
  25. BIN ...TesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testTextExample_1-iOS10@2x.png
  26. BIN ...sterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testTextExample_1-iOS10_tvOS.png
  27. BIN ...ter/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testTextExample_1@2x.png
  28. BIN ...r/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testTextExample_1_tvOS.png
  29. BIN ...TesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testViewExample_1-iOS10@2x.png
  30. BIN ...sterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testViewExample_1-iOS10_tvOS.png
  31. BIN ...ter/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testViewExample_1@2x.png
  32. BIN ...r/RNTesterIntegrationTests/ReferenceImages/RNTester-js-RNTesterApp.ios/testViewExample_1_tvOS.png
@@ -18,44 +18,6 @@
#define FB_REFERENCE_IMAGE_DIR "\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\""
#endif
/**
Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though.
@param view The view to snapshot
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
@param referenceImageDirectorySuffix An optional suffix, appended to the reference image directory path, such as "_iOS8"
*/
#define FBSnapshotVerifyViewWithReferenceDirectorySuffix(view__, identifier__, referenceImagesDirectorySuffix__) \
{ \
NSError *error__ = nil; \
NSString *referenceImagesDirectory__ = [NSString stringWithFormat:@"%s%@", FB_REFERENCE_IMAGE_DIR, referenceImagesDirectorySuffix__]; \
BOOL comparisonSuccess__ = [self compareSnapshotOfView:(view__) referenceImagesDirectory:referenceImagesDirectory__ identifier:(identifier__) error:&error__]; \
XCTAssertTrue(comparisonSuccess__, @"Snapshot comparison failed: %@", error__); \
}
#define FBSnapshotVerifyView(view__, identifier__) \
{ \
FBSnapshotVerifyViewWithReferenceDirectorySuffix(view__, identifier__, @""); \
}
/**
Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though.
@param layer The layer to snapshot
@param identifier An optional identifier, used is there are multiple snapshot tests in a given -test method.
@param referenceImageDirectorySuffix An optional suffix, appended to the reference image directory path, such as "_iOS8"
*/
#define FBSnapshotVerifyLayerWithReferenceDirectorySuffix(layer__, identifier__, referenceImagesDirectorySuffix__) \
{ \
NSError *error__ = nil; \
NSString *referenceImagesDirectory__ = [NSString stringWithFormat:@"%s%@", FB_REFERENCE_IMAGE_DIR, referenceImagesDirectorySuffix__]; \
BOOL comparisonSuccess__ = [self compareSnapshotOfLayer:(layer__) referenceImagesDirectory:referenceImagesDirectory__ identifier:(identifier__) error:&error__]; \
XCTAssertTrue(comparisonSuccess__, @"Snapshot comparison failed: %@", error__); \
}
#define FBSnapshotVerifyLayer(layer__, identifier__) \
{ \
FBSnapshotVerifyLayerWithReferenceDirectorySuffix(layer__, identifier__, @""); \
}
/**
The base class of view snapshotting tests. If you have small UI component, it's often easier to configure it in a test
and compare an image of the view to a reference image that write lots of complex layout-code tests.
@@ -70,19 +32,6 @@ FBSnapshotVerifyLayerWithReferenceDirectorySuffix(layer__, identifier__, @""); \
*/
@property (readwrite, nonatomic, assign) BOOL recordMode;
/**
Performs the comparisons or records a snapshot of the layer if recordMode is YES.
@param layer The Layer to snapshot
@param referenceImagesDirectory The directory in which reference images are stored.
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
@param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
@returns YES if the comparison (or saving of the reference image) succeeded.
*/
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
error:(NSError **)errorPtr;
/**
Performs the comparisons or records a snapshot of the view if recordMode is YES.
@param view The view to snapshot
@@ -42,41 +42,16 @@ - (void)setRecordMode:(BOOL)recordMode
self.snapshotController.recordMode = recordMode;
}
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
return [self _compareSnapshotOfViewOrLayer:layer
referenceImagesDirectory:referenceImagesDirectory
identifier:identifier
error:errorPtr];
}
- (BOOL)compareSnapshotOfView:(UIView *)view
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
return [self _compareSnapshotOfViewOrLayer:view
referenceImagesDirectory:referenceImagesDirectory
identifier:identifier
error:errorPtr];
}
#pragma mark -
#pragma mark Private API
- (BOOL)_compareSnapshotOfViewOrLayer:(id)viewOrLayer
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
_snapshotController.referenceImagesDirectory = referenceImagesDirectory;
return [_snapshotController compareSnapshotOfViewOrLayer:viewOrLayer
selector:self.invocation.selector
identifier:identifier
error:errorPtr];
return [_snapshotController compareSnapshotOfView:view
selector:self.invocation.selector
identifier:identifier
error:errorPtr];
}
@end
@@ -41,7 +41,7 @@ extern NSString *const FBReferenceImageFilePathKey;
@property(readwrite, nonatomic, assign) BOOL recordMode;
/**
@param testClass The subclass of FBSnapshotTestCase that is using this controller.d.
@param testClass The subclass of FBSnapshotTestCase that is using this controller.
@returns An instance of FBSnapshotTestController.
*/
- (id)initWithTestClass:(Class)testClass;
@@ -53,19 +53,6 @@ extern NSString *const FBReferenceImageFilePathKey;
*/
- (id)initWithTestName:(NSString *)testName;
/**
Performs the comparison of the layer.
@param layer The Layer to snapshot.
@param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method.
@param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
@returns YES if the comparison (or saving of the reference image) succeeded.
*/
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr;
/**
Performs the comparison of the view.
@param view The view to snapshot.
@@ -79,20 +66,6 @@ extern NSString *const FBReferenceImageFilePathKey;
identifier:(NSString *)identifier
error:(NSError **)errorPtr;
/**
Performs the comparison of a view or layer.
@param viewOrLayer The view or layer to snapshot.
@param selector selector
@param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method.
@param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
@returns YES if the comparison (or saving of the reference image) succeeded.
*/
- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr;
/**
The directory in which reference images are stored.
*/
@@ -10,13 +10,13 @@
#import "FBSnapshotTestController.h"
#import "UIImage+Compare.h"
#import "UIImage+Diff.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
#import "UIImage+Compare.h"
#import "UIImage+Diff.h"
NSString *const FBSnapshotTestControllerErrorDomain = @"FBSnapshotTestControllerErrorDomain";
NSString *const FBReferenceImageFilePathKey = @"FBReferenceImageFilePathKey";
@@ -39,15 +39,14 @@ @implementation FBSnapshotTestController
NSFileManager *_fileManager;
}
#pragma mark -
#pragma mark Lifecycle
#pragma mark - Lifecycle
- (id)initWithTestClass:(Class)testClass;
- (instancetype)initWithTestClass:(Class)testClass;
{
return [self initWithTestName:NSStringFromClass(testClass)];
}
- (id)initWithTestName:(NSString *)testName
- (instancetype)initWithTestName:(NSString *)testName
{
if ((self = [super init])) {
_testName = [testName copy];
@@ -56,16 +55,14 @@ - (id)initWithTestName:(NSString *)testName
return self;
}
#pragma mark -
#pragma mark Properties
#pragma mark - Properties
- (NSString *)description
{
return [NSString stringWithFormat:@"%@ %@", [super description], _referenceImagesDirectory];
}
#pragma mark -
#pragma mark Public API
#pragma mark - Public API
- (UIImage *)referenceImageForSelector:(SEL)selector
identifier:(NSString *)identifier
@@ -211,8 +208,7 @@ - (BOOL)compareReferenceImage:(UIImage *)referenceImage toImage:(UIImage *)image
return NO;
}
#pragma mark -
#pragma mark Private API
#pragma mark - Private API
typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) {
FBTestSnapshotFileNameTypeReference,
@@ -280,51 +276,28 @@ - (NSString *)_failedFilePathForSelector:(SEL)selector
return filePath;
}
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
return [self compareSnapshotOfViewOrLayer:layer
selector:selector
identifier:identifier
error:errorPtr];
}
- (BOOL)compareSnapshotOfView:(UIView *)view
- (BOOL)compareSnapshotOfView:(id)view
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
return [self compareSnapshotOfViewOrLayer:view
selector:selector
identifier:identifier
error:errorPtr];
}
- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
if (self.recordMode) {
return [self _recordSnapshotOfViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr];
return [self _recordSnapshotOfView:view selector:selector identifier:identifier error:errorPtr];
} else {
return [self _performPixelComparisonWithViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr];
return [self _performPixelComparisonWithView:view selector:selector identifier:identifier error:errorPtr];
}
}
#pragma mark -
#pragma mark Private API
#pragma mark - Private API
- (BOOL)_performPixelComparisonWithViewOrLayer:(UIView *)viewOrLayer
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
- (BOOL)_performPixelComparisonWithView:(UIView *)view
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
UIImage *referenceImage = [self referenceImageForSelector:selector identifier:identifier error:errorPtr];
if (nil != referenceImage) {
UIImage *snapshot = [self _snapshotViewOrLayer:viewOrLayer];
UIImage *snapshot = [self _snapshotView:view];
BOOL imagesSame = [self compareReferenceImage:referenceImage toImage:snapshot error:errorPtr];
if (!imagesSame) {
[self saveFailedReferenceImage:referenceImage
@@ -338,46 +311,33 @@ - (BOOL)_performPixelComparisonWithViewOrLayer:(UIView *)viewOrLayer
return NO;
}
- (BOOL)_recordSnapshotOfViewOrLayer:(id)viewOrLayer
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
- (BOOL)_recordSnapshotOfView:(UIView *)view
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
UIImage *snapshot = [self _snapshotViewOrLayer:viewOrLayer];
UIImage *snapshot = [self _snapshotView:view];
return [self saveReferenceImage:snapshot selector:selector identifier:identifier error:errorPtr];
}
- (UIImage *)_snapshotViewOrLayer:(id)viewOrLayer
- (UIImage *)_snapshotView:(UIView *)view
{
CALayer *layer = nil;
if ([viewOrLayer isKindOfClass:[UIView class]]) {
return [self _renderView:viewOrLayer];
} else if ([viewOrLayer isKindOfClass:[CALayer class]]) {
layer = (CALayer *)viewOrLayer;
[layer layoutIfNeeded];
return [self _renderLayer:layer];
} else {
[NSException raise:@"Only UIView and CALayer classes can be snapshotted" format:@"%@", viewOrLayer];
}
return nil;
}
[view layoutIfNeeded];
- (UIImage *)_renderLayer:(CALayer *)layer
{
CGRect bounds = layer.bounds;
CGRect bounds = view.bounds;
NSAssert1(CGRectGetWidth(bounds), @"Zero width for layer %@", layer);
NSAssert1(CGRectGetHeight(bounds), @"Zero height for layer %@", layer);
NSAssert1(CGRectGetWidth(bounds), @"Zero width for view %@", view);
NSAssert1(CGRectGetHeight(bounds), @"Zero height for view %@", view);
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
NSAssert1(context, @"Could not generate context for layer %@", layer);
NSAssert1(context, @"Could not generate context for view %@", view);
UIGraphicsPushContext(context);
CGContextSaveGState(context);
{
[layer renderInContext:context];
BOOL success = [view drawViewHierarchyInRect:bounds afterScreenUpdates:YES];
NSAssert1(success, @"Could not create snapshot for view %@", view);
}
CGContextRestoreGState(context);
UIGraphicsPopContext();
@@ -388,10 +348,4 @@ - (UIImage *)_renderLayer:(CALayer *)layer
return snapshot;
}
- (UIImage *)_renderView:(UIView *)view
{
[view layoutIfNeeded];
return [self _renderLayer:view.layer];
}
@end

0 comments on commit 3df537a

Please sign in to comment.