mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-11 08:50:24 +00:00
Add unit tests for the layout engine (#424)
* Build testing platform & tests for the layout engine * Add our license header to debugbreak. * Remove thing * Address review comments * Beef up the logging * Update -[ASLayout isEqual:] * testLayoutTransitionWithAsyncMeasurement passes now * Disable testASetNeedsLayoutInterferingWithTheCurrentTransition * Fix build errors
This commit is contained in:
parent
bccde6cf0f
commit
0dc7002f0b
@ -405,6 +405,9 @@
|
||||
CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.m */; };
|
||||
CCDD148B1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */; };
|
||||
CCE4F9B31F0D60AC00062E4E /* ASIntegerMapTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.m */; };
|
||||
CCE4F9B51F0DA4F300062E4E /* ASLayoutEngineTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */; };
|
||||
CCE4F9BA1F0DBB5000062E4E /* ASLayoutTestNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B71F0DBA5000062E4E /* ASLayoutTestNode.mm */; };
|
||||
CCE4F9BE1F0ECE5200062E4E /* ASTLayoutFixture.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */; };
|
||||
CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; };
|
||||
@ -899,6 +902,12 @@
|
||||
CCE04B211E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "IGListAdapter+AsyncDisplayKit.m"; sourceTree = "<group>"; };
|
||||
CCE04B2B1E314A32006AEBBB /* ASSupplementaryNodeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSupplementaryNodeSource.h; sourceTree = "<group>"; };
|
||||
CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIntegerMapTests.m; sourceTree = "<group>"; };
|
||||
CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutEngineTests.mm; sourceTree = "<group>"; };
|
||||
CCE4F9B61F0DBA5000062E4E /* ASLayoutTestNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutTestNode.h; sourceTree = "<group>"; };
|
||||
CCE4F9B71F0DBA5000062E4E /* ASLayoutTestNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutTestNode.mm; sourceTree = "<group>"; };
|
||||
CCE4F9BB1F0EA67F00062E4E /* debugbreak.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = debugbreak.h; sourceTree = "<group>"; };
|
||||
CCE4F9BC1F0ECE5200062E4E /* ASTLayoutFixture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTLayoutFixture.h; sourceTree = "<group>"; };
|
||||
CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTLayoutFixture.mm; sourceTree = "<group>"; };
|
||||
D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = "<group>"; };
|
||||
D785F6611A74327E00291744 /* ASScrollNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASScrollNode.mm; sourceTree = "<group>"; };
|
||||
@ -1175,6 +1184,11 @@
|
||||
CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.m */,
|
||||
CC051F1E1D7A286A006434CB /* ASCALayerTests.m */,
|
||||
CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.m */,
|
||||
CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */,
|
||||
CCE4F9B61F0DBA5000062E4E /* ASLayoutTestNode.h */,
|
||||
CCE4F9B71F0DBA5000062E4E /* ASLayoutTestNode.mm */,
|
||||
CCE4F9BC1F0ECE5200062E4E /* ASTLayoutFixture.h */,
|
||||
CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */,
|
||||
CC8B05D71D73979700F54286 /* ASTextNodePerformanceTests.m */,
|
||||
CC8B05D41D73836400F54286 /* ASPerformanceTestContext.h */,
|
||||
CC8B05D51D73836400F54286 /* ASPerformanceTestContext.m */,
|
||||
@ -1573,6 +1587,7 @@
|
||||
CC583ABF1EF9BAB400134156 /* Common */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CCE4F9BB1F0EA67F00062E4E /* debugbreak.h */,
|
||||
CC583AC01EF9BAB400134156 /* ASDisplayNode+OCMock.m */,
|
||||
CC583AC11EF9BAB400134156 /* ASTestCase.h */,
|
||||
CC583AC21EF9BAB400134156 /* ASTestCase.m */,
|
||||
@ -2187,6 +2202,7 @@
|
||||
ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */,
|
||||
CC8B05D61D73836400F54286 /* ASPerformanceTestContext.m in Sources */,
|
||||
CC0AEEA41D66316E005D1C78 /* ASUICollectionViewTests.m in Sources */,
|
||||
CCE4F9B51F0DA4F300062E4E /* ASLayoutEngineTests.mm in Sources */,
|
||||
69B225671D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm in Sources */,
|
||||
ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */,
|
||||
7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */,
|
||||
@ -2195,12 +2211,14 @@
|
||||
254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */,
|
||||
05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */,
|
||||
ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */,
|
||||
CCE4F9BA1F0DBB5000062E4E /* ASLayoutTestNode.mm in Sources */,
|
||||
81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */,
|
||||
3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */,
|
||||
AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */,
|
||||
254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */,
|
||||
058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */,
|
||||
CC3B20901C3F892D00798563 /* ASBridgedPropertiesTests.mm in Sources */,
|
||||
CCE4F9BE1F0ECE5200062E4E /* ASTLayoutFixture.mm in Sources */,
|
||||
058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */,
|
||||
DBC453221C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m in Sources */,
|
||||
058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */,
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
- [Layout] Fix an issue that causes a pending layout to be applied multiple times. [Huy Nguyen](https://github.com/nguyenhuy) [#695](https://github.com/TextureGroup/Texture/pull/695)
|
||||
- [ASScrollNode] Ensure the node respects the given size range while calculating its layout. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy)
|
||||
- [ASScrollNode] Invalidate the node's calculated layout if its scrollable directions changed. Also add unit tests for the class. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy)
|
||||
- Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424)
|
||||
|
||||
## 2.6
|
||||
- [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon)
|
||||
|
||||
@ -85,6 +85,7 @@
|
||||
layout = [self calculateLayoutThatFits:constrainedSize
|
||||
restrictedToSize:self.style.size
|
||||
relativeToParentSize:parentSize];
|
||||
as_log_verbose(ASLayoutLog(), "Established pending layout for %@ in %s", self, sel_getName(_cmd));
|
||||
_pendingDisplayNodeLayout = std::make_shared<ASDisplayNodeLayout>(layout, constrainedSize, parentSize, version);
|
||||
ASDisplayNodeAssertNotNil(layout, @"-[ASDisplayNode layoutThatFits:parentSize:] newly calculated layout should not be nil! %@", self);
|
||||
}
|
||||
|
||||
@ -2762,7 +2762,8 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
|
||||
}
|
||||
}
|
||||
|
||||
ASDisplayNodeLogEvent(self, @"setHierarchyState: oldState = %@, newState = %@", NSStringFromASHierarchyState(oldState), NSStringFromASHierarchyState(newState));
|
||||
ASDisplayNodeLogEvent(self, @"setHierarchyState: %@", NSStringFromASHierarchyStateChange(oldState, newState));
|
||||
as_log_verbose(ASNodeLog(), "%s%@ %@", sel_getName(_cmd), NSStringFromASHierarchyStateChange(oldState, newState), self);
|
||||
}
|
||||
|
||||
- (void)willEnterHierarchy
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
|
||||
@implementation _ASDisplayLayer
|
||||
{
|
||||
@ -93,6 +94,7 @@
|
||||
- (void)setNeedsLayout
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
as_log_verbose(ASNodeLog(), "%s on %@", sel_getName(_cmd), self);
|
||||
[super setNeedsLayout];
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#import <AsyncDisplayKit/ASLayoutSpecUtilities.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpec+Subclasses.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASEqualityHelpers.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
#import <AsyncDisplayKit/ASRectTable.h>
|
||||
@ -281,11 +282,12 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT(
|
||||
}
|
||||
|
||||
if (!CGSizeEqualToSize(_size, layout.size)) return NO;
|
||||
if (!CGPointEqualToPoint(_position, layout.position)) return NO;
|
||||
|
||||
if (!((ASPointIsNull(self.position) && ASPointIsNull(layout.position))
|
||||
|| CGPointEqualToPoint(self.position, layout.position))) return NO;
|
||||
if (_layoutElement != layout.layoutElement) return NO;
|
||||
|
||||
NSArray *sublayouts = layout.sublayouts;
|
||||
if (sublayouts != _sublayouts && (sublayouts == nil || _sublayouts == nil || ![_sublayouts isEqual:sublayouts])) {
|
||||
if (!ASObjectIsEqual(_sublayouts, layout.sublayouts)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
@ -99,6 +99,29 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat
|
||||
return [NSString stringWithFormat:@"{ %@ }", [states componentsJoinedByString:@" | "]];
|
||||
}
|
||||
|
||||
#define HIERARCHY_STATE_DELTA(Name) ({ \
|
||||
if ((oldState & ASHierarchyState##Name) != (newState & ASHierarchyState##Name)) { \
|
||||
[changes appendFormat:@"%c%s ", (newState & ASHierarchyState##Name ? '+' : '-'), #Name]; \
|
||||
} \
|
||||
})
|
||||
|
||||
__unused static NSString * _Nonnull NSStringFromASHierarchyStateChange(ASHierarchyState oldState, ASHierarchyState newState)
|
||||
{
|
||||
if (oldState == newState) {
|
||||
return @"{ }";
|
||||
}
|
||||
|
||||
NSMutableString *changes = [NSMutableString stringWithString:@"{ "];
|
||||
HIERARCHY_STATE_DELTA(Rasterized);
|
||||
HIERARCHY_STATE_DELTA(RangeManaged);
|
||||
HIERARCHY_STATE_DELTA(TransitioningSupernodes);
|
||||
HIERARCHY_STATE_DELTA(LayoutPending);
|
||||
[changes appendString:@"}"];
|
||||
return changes;
|
||||
}
|
||||
|
||||
#undef HIERARCHY_STATE_DELTA
|
||||
|
||||
@interface ASDisplayNode () <ASDescriptionProvider, ASDebugDescriptionProvider>
|
||||
{
|
||||
@protected
|
||||
|
||||
517
Tests/ASLayoutEngineTests.mm
Normal file
517
Tests/ASLayoutEngineTests.mm
Normal file
@ -0,0 +1,517 @@
|
||||
//
|
||||
// ASLayoutEngineTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASTestCase.h"
|
||||
#import "ASLayoutTestNode.h"
|
||||
#import "ASXCTExtensions.h"
|
||||
#import "ASTLayoutFixture.h"
|
||||
|
||||
@interface ASLayoutEngineTests : ASTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASLayoutEngineTests {
|
||||
ASLayoutTestNode *nodeA;
|
||||
ASLayoutTestNode *nodeB;
|
||||
ASLayoutTestNode *nodeC;
|
||||
ASLayoutTestNode *nodeD;
|
||||
ASLayoutTestNode *nodeE;
|
||||
ASTLayoutFixture *fixture1;
|
||||
ASTLayoutFixture *fixture2;
|
||||
ASTLayoutFixture *fixture3;
|
||||
ASTLayoutFixture *fixture4;
|
||||
|
||||
// fixtures 1 and 3 share the same exact node A layout spec block.
|
||||
// we don't want the infra to call -setNeedsLayout when we switch fixtures
|
||||
// so we need to use the same exact block.
|
||||
ASLayoutSpecBlock fixture1and3NodeALayoutSpecBlock;
|
||||
|
||||
UIWindow *window;
|
||||
UIViewController *vc;
|
||||
NSArray<ASLayoutTestNode *> *allNodes;
|
||||
NSTimeInterval verifyDelay;
|
||||
// See -stubCalculatedLayoutDidChange.
|
||||
BOOL stubbedCalculatedLayoutDidChange;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
verifyDelay = 3;
|
||||
window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 10, 1)];
|
||||
vc = [[UIViewController alloc] init];
|
||||
nodeA = [ASLayoutTestNode new];
|
||||
nodeA.backgroundColor = [UIColor redColor];
|
||||
|
||||
// NOTE: nodeB has flexShrink, the others don't
|
||||
nodeB = [ASLayoutTestNode new];
|
||||
nodeB.style.flexShrink = 1;
|
||||
nodeB.backgroundColor = [UIColor orangeColor];
|
||||
|
||||
nodeC = [ASLayoutTestNode new];
|
||||
nodeC.backgroundColor = [UIColor yellowColor];
|
||||
nodeD = [ASLayoutTestNode new];
|
||||
nodeD.backgroundColor = [UIColor greenColor];
|
||||
nodeE = [ASLayoutTestNode new];
|
||||
nodeE.backgroundColor = [UIColor blueColor];
|
||||
allNodes = @[ nodeA, nodeB, nodeC, nodeD, nodeE ];
|
||||
ASSetDebugNames(nodeA, nodeB, nodeC, nodeD, nodeE);
|
||||
ASLayoutSpecBlock b = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
return [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:0 justifyContent:ASStackLayoutJustifyContentSpaceBetween alignItems:ASStackLayoutAlignItemsStart children:@[ nodeB, nodeC, nodeD ]];
|
||||
};
|
||||
fixture1and3NodeALayoutSpecBlock = b;
|
||||
fixture1 = [self createFixture1];
|
||||
fixture2 = [self createFixture2];
|
||||
fixture3 = [self createFixture3];
|
||||
fixture4 = [self createFixture4];
|
||||
|
||||
nodeA.frame = vc.view.bounds;
|
||||
nodeA.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
[vc.view addSubnode:nodeA];
|
||||
|
||||
window.rootViewController = vc;
|
||||
[window makeKeyAndVisible];
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
nodeA.layoutSpecBlock = nil;
|
||||
for (ASLayoutTestNode *node in allNodes) {
|
||||
OCMVerifyAllWithDelay(node.mock, verifyDelay);
|
||||
}
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)testFirstLayoutPassWhenInWindow
|
||||
{
|
||||
[self runFirstLayoutPassWithFixture:fixture1];
|
||||
}
|
||||
|
||||
- (void)testSetNeedsLayoutAndNormalLayoutPass
|
||||
{
|
||||
[self runFirstLayoutPassWithFixture:fixture1];
|
||||
|
||||
[fixture2 apply];
|
||||
|
||||
// skip nodeB because its layout doesn't change.
|
||||
for (ASLayoutTestNode *node in @[ nodeA, nodeC, nodeE ]) {
|
||||
[fixture2 withSizeRangesForNode:node block:^(ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange]).onMainThread();
|
||||
}];
|
||||
OCMExpect([node.mock calculatedLayoutDidChange]).onMainThread();
|
||||
}
|
||||
|
||||
[window layoutIfNeeded];
|
||||
[self verifyFixture:fixture2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition from fixture1 to Fixture2 on node A.
|
||||
*
|
||||
* Expect A and D to calculate once off main, and
|
||||
* to receive calculatedLayoutDidChange on main,
|
||||
* then to get the measurement completion call on main,
|
||||
* then to get animateLayoutTransition: and didCompleteLayoutTransition: on main.
|
||||
*/
|
||||
- (void)testLayoutTransitionWithAsyncMeasurement
|
||||
{
|
||||
[self stubCalculatedLayoutDidChange];
|
||||
[self runFirstLayoutPassWithFixture:fixture1];
|
||||
|
||||
[fixture2 apply];
|
||||
|
||||
// Expect A, C, E to calculate new layouts off-main
|
||||
// dispatch_once onto main to run our injectedMainThread work while the transition calculates.
|
||||
__block dispatch_block_t injectedMainThreadWork = nil;
|
||||
for (ASLayoutTestNode *node in @[ nodeA, nodeC, nodeE ]) {
|
||||
[fixture2 withSizeRangesForNode:node block:^(ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange])
|
||||
.offMainThread()
|
||||
.andDo(^(NSInvocation *inv) {
|
||||
// On first calculateLayoutThatFits, schedule our injected main thread work.
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
injectedMainThreadWork();
|
||||
});
|
||||
});
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
// The code in this section is designed to move in time order, all on the main thread:
|
||||
|
||||
OCMExpect([nodeA.mock animateLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
OCMExpect([nodeA.mock didCompleteLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
|
||||
// Trigger the layout transition.
|
||||
__block dispatch_block_t measurementCompletionBlock = nil;
|
||||
[nodeA transitionLayoutWithAnimation:NO shouldMeasureAsync:YES measurementCompletion:^{
|
||||
measurementCompletionBlock();
|
||||
}];
|
||||
|
||||
// This block will get run after bg layout calculate starts, but before measurementCompletion
|
||||
__block BOOL injectedMainThreadWorkDone = NO;
|
||||
injectedMainThreadWork = ^{
|
||||
injectedMainThreadWorkDone = YES;
|
||||
|
||||
[window layoutIfNeeded];
|
||||
|
||||
// Ensure we're still on the old layout. We should stay on this until the transition completes.
|
||||
[self verifyFixture:fixture1];
|
||||
};
|
||||
|
||||
measurementCompletionBlock = ^{
|
||||
XCTAssert(injectedMainThreadWorkDone, @"We hoped to get onto the main thread before the measurementCompletion callback ran.");
|
||||
};
|
||||
|
||||
for (ASLayoutTestNode *node in allNodes) {
|
||||
OCMVerifyAllWithDelay(node.mock, verifyDelay);
|
||||
}
|
||||
|
||||
[self verifyFixture:fixture2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Start at fixture 1.
|
||||
* Trigger an async transition to fixture 2.
|
||||
* While it's measuring, on main switch to fixture 4 (setNeedsLayout A, D) and run a CA layout pass.
|
||||
*
|
||||
* Correct behavior, we end up at fixture 4 since it's newer.
|
||||
* Current incorrect behavior, we end up at fixture 2 and we remeasure surviving node C.
|
||||
* Note: incorrect behavior likely introduced by the early check in __layout added in
|
||||
* https://github.com/facebookarchive/AsyncDisplayKit/pull/2657
|
||||
*/
|
||||
- (void)DISABLE_testASetNeedsLayoutInterferingWithTheCurrentTransition
|
||||
{
|
||||
static BOOL enforceCorrectBehavior = NO;
|
||||
|
||||
[self stubCalculatedLayoutDidChange];
|
||||
[self runFirstLayoutPassWithFixture:fixture1];
|
||||
|
||||
[fixture2 apply];
|
||||
|
||||
// Expect A, C, E to calculate new layouts off-main
|
||||
// dispatch_once onto main to run our injectedMainThread work while the transition calculates.
|
||||
__block dispatch_block_t injectedMainThreadWork = nil;
|
||||
for (ASLayoutTestNode *node in @[ nodeA, nodeC, nodeE ]) {
|
||||
[fixture2 withSizeRangesForNode:node block:^(ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange])
|
||||
.offMainThread()
|
||||
.andDo(^(NSInvocation *inv) {
|
||||
// On first calculateLayoutThatFits, schedule our injected main thread work.
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
injectedMainThreadWork();
|
||||
});
|
||||
});
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
// The code in this section is designed to move in time order, all on the main thread:
|
||||
|
||||
// With the current behavior, the transition will continue and complete.
|
||||
if (!enforceCorrectBehavior) {
|
||||
OCMExpect([nodeA.mock animateLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
OCMExpect([nodeA.mock didCompleteLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
}
|
||||
|
||||
// Trigger the layout transition.
|
||||
__block dispatch_block_t measurementCompletionBlock = nil;
|
||||
[nodeA transitionLayoutWithAnimation:NO shouldMeasureAsync:YES measurementCompletion:^{
|
||||
measurementCompletionBlock();
|
||||
}];
|
||||
|
||||
// Injected block will get run on main after bg layout calculate starts, but before measurementCompletion
|
||||
__block BOOL injectedMainThreadWorkDone = NO;
|
||||
injectedMainThreadWork = ^{
|
||||
as_log_verbose(OS_LOG_DEFAULT, "Begin injectedMainThreadWork");
|
||||
injectedMainThreadWorkDone = YES;
|
||||
|
||||
[fixture4 apply];
|
||||
as_log_verbose(OS_LOG_DEFAULT, "Did apply new fixture");
|
||||
|
||||
if (enforceCorrectBehavior) {
|
||||
// Correct measurement behavior here is unclear, may depend on whether the layouts which
|
||||
// are common to both fixture2 and fixture4 are available from the cache.
|
||||
} else {
|
||||
// Incorrect behavior: nodeC will get measured against its new bounds on main.
|
||||
auto cPendingSize = [fixture2 layoutForNode:nodeC].size;
|
||||
OCMExpect([nodeC.mock calculateLayoutThatFits:ASSizeRangeMake(cPendingSize)]).onMainThread();
|
||||
}
|
||||
[window layoutIfNeeded];
|
||||
as_log_verbose(OS_LOG_DEFAULT, "End injectedMainThreadWork");
|
||||
};
|
||||
|
||||
measurementCompletionBlock = ^{
|
||||
XCTAssert(injectedMainThreadWorkDone, @"We hoped to get onto the main thread before the measurementCompletion callback ran.");
|
||||
};
|
||||
|
||||
for (ASLayoutTestNode *node in allNodes) {
|
||||
OCMVerifyAllWithDelay(node.mock, verifyDelay);
|
||||
}
|
||||
|
||||
// Incorrect behavior: The transition will "win" even though its transitioning to stale data.
|
||||
if (enforceCorrectBehavior) {
|
||||
[self verifyFixture:fixture4];
|
||||
} else {
|
||||
[self verifyFixture:fixture2];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start on fixture 3 where nodeB is force-shrunk via multipass layout.
|
||||
* Apply fixture 1, which just changes nodeB's size and calls -setNeedsLayout on it.
|
||||
*
|
||||
* This behavior is currently broken. See implementation for correct behavior and incorrect behavior.
|
||||
*/
|
||||
- (void)testCallingSetNeedsLayoutOnANodeThatWasSubjectToMultipassLayout
|
||||
{
|
||||
static BOOL const enforceCorrectBehavior = NO;
|
||||
[self stubCalculatedLayoutDidChange];
|
||||
[self runFirstLayoutPassWithFixture:fixture3];
|
||||
|
||||
// Switch to fixture 1, updating nodeB's desired size and calling -setNeedsLayout
|
||||
// Now nodeB will fit happily into the stack.
|
||||
[fixture1 apply];
|
||||
|
||||
if (enforceCorrectBehavior) {
|
||||
/*
|
||||
* Correct behavior: nodeB is remeasured against the first (unconstrained) size
|
||||
* and when it's discovered that now nodeB fits, nodeA will re-layout and we'll
|
||||
* end up correctly at fixture1.
|
||||
*/
|
||||
OCMExpect([nodeB.mock calculateLayoutThatFits:[fixture3 firstSizeRangeForNode:nodeB]]);
|
||||
|
||||
[fixture1 withSizeRangesForNode:nodeA block:^(ASSizeRange sizeRange) {
|
||||
OCMExpect([nodeA.mock calculateLayoutThatFits:sizeRange]);
|
||||
}];
|
||||
|
||||
[window layoutIfNeeded];
|
||||
[self verifyFixture:fixture1];
|
||||
} else {
|
||||
/*
|
||||
* Incorrect behavior: nodeB is remeasured against the second (fixed-width) constraint.
|
||||
* The returned value (8) is clamped to the fixed with (7), and then compared to the previous
|
||||
* width (7) and we decide not to propagate up the invalidation, and we stay stuck on the old
|
||||
* layout (fixture3).
|
||||
*/
|
||||
OCMExpect([nodeB.mock calculateLayoutThatFits:nodeB.constrainedSizeForCalculatedLayout]);
|
||||
[window layoutIfNeeded];
|
||||
[self verifyFixture:fixture3];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (void)verifyFixture:(ASTLayoutFixture *)fixture
|
||||
{
|
||||
auto expected = fixture.layout;
|
||||
|
||||
// Ensure expected == frames
|
||||
auto frames = [fixture.rootNode currentLayoutBasedOnFrames];
|
||||
if (![expected isEqual:frames]) {
|
||||
XCTFail(@"\n*** Layout verification failed – frames don't match expected. ***\nGot:\n%@\nExpected:\n%@", [frames recursiveDescription], [expected recursiveDescription]);
|
||||
}
|
||||
|
||||
// Ensure expected == calculatedLayout
|
||||
auto calculated = fixture.rootNode.calculatedLayout;
|
||||
if (![expected isEqual:calculated]) {
|
||||
XCTFail(@"\n*** Layout verification failed – calculated layout doesn't match expected. ***\nGot:\n%@\nExpected:\n%@", [calculated recursiveDescription], [expected recursiveDescription]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stubs calculatedLayoutDidChange for all nodes.
|
||||
*
|
||||
* It's not really a core layout engine method, and it's also
|
||||
* currently bugged and gets called a lot so for most
|
||||
* tests its better not to have expectations about it littered around.
|
||||
* https://github.com/TextureGroup/Texture/issues/422
|
||||
*/
|
||||
- (void)stubCalculatedLayoutDidChange
|
||||
{
|
||||
stubbedCalculatedLayoutDidChange = YES;
|
||||
for (ASLayoutTestNode *node in allNodes) {
|
||||
OCMStub([node.mock calculatedLayoutDidChange]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixture 1: A basic horizontal stack, all single-pass.
|
||||
*
|
||||
* [A: HorizStack([B, C, D])]. B is (1x1), C is (2x1), D is (1x1)
|
||||
*/
|
||||
- (ASTLayoutFixture *)createFixture1
|
||||
{
|
||||
auto fixture = [[ASTLayoutFixture alloc] init];
|
||||
|
||||
// nodeB
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB];
|
||||
auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{1,1} position:{0,0} sublayouts:nil];
|
||||
|
||||
// nodeC
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeC];
|
||||
auto layoutC = [ASLayout layoutWithLayoutElement:nodeC size:{2,1} position:{4,0} sublayouts:nil];
|
||||
|
||||
// nodeD
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeD];
|
||||
auto layoutD = [ASLayout layoutWithLayoutElement:nodeD size:{1,1} position:{9,0} sublayouts:nil];
|
||||
|
||||
[fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA];
|
||||
auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutC, layoutD ]];
|
||||
fixture.layout = layoutA;
|
||||
|
||||
[fixture.layoutSpecBlocks setObject:fixture1and3NodeALayoutSpecBlock forKey:nodeA];
|
||||
return fixture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixture 2: A simple transition away from fixture 1.
|
||||
*
|
||||
* [A: HorizStack([B, C, E])]. B is (1x1), C is (4x1), E is (1x1)
|
||||
*
|
||||
* From fixture 1:
|
||||
* B survives with same layout
|
||||
* C survives with new layout
|
||||
* D is removed
|
||||
* E joins with first layout
|
||||
*/
|
||||
- (ASTLayoutFixture *)createFixture2
|
||||
{
|
||||
auto fixture = [[ASTLayoutFixture alloc] init];
|
||||
|
||||
// nodeB
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB];
|
||||
auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{1,1} position:{0,0} sublayouts:nil];
|
||||
|
||||
// nodeC
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeC];
|
||||
auto layoutC = [ASLayout layoutWithLayoutElement:nodeC size:{4,1} position:{3,0} sublayouts:nil];
|
||||
|
||||
// nodeE
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeE];
|
||||
auto layoutE = [ASLayout layoutWithLayoutElement:nodeE size:{1,1} position:{9,0} sublayouts:nil];
|
||||
|
||||
[fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA];
|
||||
auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutC, layoutE ]];
|
||||
fixture.layout = layoutA;
|
||||
|
||||
ASLayoutSpecBlock specBlockA = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
return [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:0 justifyContent:ASStackLayoutJustifyContentSpaceBetween alignItems:ASStackLayoutAlignItemsStart children:@[ nodeB, nodeC, nodeE ]];
|
||||
};
|
||||
[fixture.layoutSpecBlocks setObject:specBlockA forKey:nodeA];
|
||||
return fixture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixture 3: Multipass stack layout
|
||||
*
|
||||
* [A: HorizStack([B, C, D])]. B is (7x1), C is (2x1), D is (1x1)
|
||||
*
|
||||
* nodeB (which has flexShrink=1) will return 8x1 for its size during the first
|
||||
* stack pass, and it'll be subject to a second pass where it returns 7x1.
|
||||
*
|
||||
*/
|
||||
- (ASTLayoutFixture *)createFixture3
|
||||
{
|
||||
auto fixture = [[ASTLayoutFixture alloc] init];
|
||||
|
||||
// nodeB wants 8,1 but it will settle for 7,1
|
||||
[fixture setReturnedSize:{8,1} forNode:nodeB];
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB];
|
||||
[fixture addSizeRange:{{7, 0}, {7, 1}} forNode:nodeB];
|
||||
auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{7,1} position:{0,0} sublayouts:nil];
|
||||
|
||||
// nodeC
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeC];
|
||||
auto layoutC = [ASLayout layoutWithLayoutElement:nodeC size:{2,1} position:{7,0} sublayouts:nil];
|
||||
|
||||
// nodeD
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeD];
|
||||
auto layoutD = [ASLayout layoutWithLayoutElement:nodeD size:{1,1} position:{9,0} sublayouts:nil];
|
||||
|
||||
[fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA];
|
||||
auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutC, layoutD ]];
|
||||
fixture.layout = layoutA;
|
||||
|
||||
[fixture.layoutSpecBlocks setObject:fixture1and3NodeALayoutSpecBlock forKey:nodeA];
|
||||
return fixture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixture 4: A different simple transition away from fixture 1.
|
||||
*
|
||||
* [A: HorizStack([B, D, E])]. B is (1x1), D is (2x1), E is (1x1)
|
||||
*
|
||||
* From fixture 1:
|
||||
* B survives with same layout
|
||||
* C is removed
|
||||
* D survives with new layout
|
||||
* E joins with first layout
|
||||
*
|
||||
* From fixture 2:
|
||||
* B survives with same layout
|
||||
* C is removed
|
||||
* D joins with first layout
|
||||
* E survives with same layout
|
||||
*/
|
||||
- (ASTLayoutFixture *)createFixture4
|
||||
{
|
||||
auto fixture = [[ASTLayoutFixture alloc] init];
|
||||
|
||||
// nodeB
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB];
|
||||
auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{1,1} position:{0,0} sublayouts:nil];
|
||||
|
||||
// nodeD
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeD];
|
||||
auto layoutD = [ASLayout layoutWithLayoutElement:nodeD size:{2,1} position:{4,0} sublayouts:nil];
|
||||
|
||||
// nodeE
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeE];
|
||||
auto layoutE = [ASLayout layoutWithLayoutElement:nodeE size:{1,1} position:{9,0} sublayouts:nil];
|
||||
|
||||
[fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA];
|
||||
auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutD, layoutE ]];
|
||||
fixture.layout = layoutA;
|
||||
|
||||
ASLayoutSpecBlock specBlockA = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
return [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:0 justifyContent:ASStackLayoutJustifyContentSpaceBetween alignItems:ASStackLayoutAlignItemsStart children:@[ nodeB, nodeD, nodeE ]];
|
||||
};
|
||||
[fixture.layoutSpecBlocks setObject:specBlockA forKey:nodeA];
|
||||
return fixture;
|
||||
}
|
||||
|
||||
- (void)runFirstLayoutPassWithFixture:(ASTLayoutFixture *)fixture
|
||||
{
|
||||
[fixture apply];
|
||||
for (ASLayoutTestNode *node in fixture.allNodes) {
|
||||
[fixture withSizeRangesForNode:node block:^(ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange]).onMainThread();
|
||||
}];
|
||||
|
||||
if (!stubbedCalculatedLayoutDidChange) {
|
||||
OCMExpect([node.mock calculatedLayoutDidChange]).onMainThread();
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger CA layout pass.
|
||||
[window layoutIfNeeded];
|
||||
|
||||
// Make sure it went through.
|
||||
[self verifyFixture:fixture];
|
||||
}
|
||||
|
||||
@end
|
||||
42
Tests/ASLayoutTestNode.h
Normal file
42
Tests/ASLayoutTestNode.h
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// ASLayoutTestNode.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
@interface ASLayoutTestNode : ASDisplayNode
|
||||
|
||||
/**
|
||||
* Mocking ASDisplayNodes directly isn't very safe because when you pump mock objects
|
||||
* into the guts of the framework, bad things happen e.g. direct-ivar-access on mock
|
||||
* objects will return garbage data.
|
||||
*
|
||||
* Instead we create a strict mock for each node, and forward a selected set of calls to it.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) id mock;
|
||||
|
||||
/**
|
||||
* The size that this node will return in calculateLayoutThatFits (if it doesn't have a layoutSpecBlock).
|
||||
*
|
||||
* Changing this value will call -setNeedsLayout on the node.
|
||||
*/
|
||||
@property (nonatomic) CGSize testSize;
|
||||
|
||||
/**
|
||||
* Generate a layout based on the frame of this node and its subtree.
|
||||
*
|
||||
* The root layout will be unpositioned. This is so that the returned layout can be directly
|
||||
* compared to `calculatedLayout`
|
||||
*/
|
||||
- (ASLayout *)currentLayoutBasedOnFrames;
|
||||
|
||||
@end
|
||||
92
Tests/ASLayoutTestNode.mm
Normal file
92
Tests/ASLayoutTestNode.mm
Normal file
@ -0,0 +1,92 @@
|
||||
//
|
||||
// ASLayoutTestNode.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutTestNode.h"
|
||||
#import <OCMock/OCMock.h>
|
||||
#import "OCMockObject+ASAdditions.h"
|
||||
|
||||
@implementation ASLayoutTestNode
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_mock = OCMStrictClassMock([ASDisplayNode class]);
|
||||
|
||||
// If errors occur (e.g. unexpected method) we need to quickly figure out
|
||||
// which node is at fault, so we inject the node name into the mock instance
|
||||
// description.
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
[_mock setModifyDescriptionBlock:^(id mock, NSString *baseDescription){
|
||||
return [NSString stringWithFormat:@"Mock(%@)", weakSelf.description];
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASLayout *)currentLayoutBasedOnFrames
|
||||
{
|
||||
return [self _currentLayoutBasedOnFramesForRootNode:YES];
|
||||
}
|
||||
|
||||
- (ASLayout *)_currentLayoutBasedOnFramesForRootNode:(BOOL)isRootNode
|
||||
{
|
||||
auto sublayouts = [NSMutableArray<ASLayout *> array];
|
||||
for (ASLayoutTestNode *subnode in self.subnodes) {
|
||||
[sublayouts addObject:[subnode _currentLayoutBasedOnFramesForRootNode:NO]];
|
||||
}
|
||||
CGPoint rootPosition = isRootNode ? ASPointNull : self.frame.origin;
|
||||
return [ASLayout layoutWithLayoutElement:self size:self.frame.size position:rootPosition sublayouts:sublayouts];
|
||||
}
|
||||
|
||||
- (void)setTestSize:(CGSize)testSize
|
||||
{
|
||||
if (!CGSizeEqualToSize(testSize, _testSize)) {
|
||||
_testSize = testSize;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
[_mock calculateLayoutThatFits:constrainedSize];
|
||||
|
||||
// If we have a layout spec block, or no test size, return super.
|
||||
if (self.layoutSpecBlock || CGSizeEqualToSize(self.testSize, CGSizeZero)) {
|
||||
return [super calculateLayoutThatFits:constrainedSize];
|
||||
} else {
|
||||
// Interestingly, the infra will auto-clamp sizes from calculateSizeThatFits, but not from calculateLayoutThatFits.
|
||||
auto size = ASSizeRangeClamp(constrainedSize, self.testSize);
|
||||
return [ASLayout layoutWithLayoutElement:self size:size];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Forwarding to mock
|
||||
|
||||
- (void)calculatedLayoutDidChange
|
||||
{
|
||||
[_mock calculatedLayoutDidChange];
|
||||
[super calculatedLayoutDidChange];
|
||||
}
|
||||
|
||||
- (void)didCompleteLayoutTransition:(id<ASContextTransitioning>)context
|
||||
{
|
||||
[_mock didCompleteLayoutTransition:context];
|
||||
[super didCompleteLayoutTransition:context];
|
||||
}
|
||||
|
||||
- (void)animateLayoutTransition:(id<ASContextTransitioning>)context
|
||||
{
|
||||
[_mock animateLayoutTransition:context];
|
||||
[super animateLayoutTransition:context];
|
||||
}
|
||||
|
||||
@end
|
||||
61
Tests/ASTLayoutFixture.h
Normal file
61
Tests/ASTLayoutFixture.h
Normal file
@ -0,0 +1,61 @@
|
||||
//
|
||||
// ASTLayoutFixture.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import "ASTestCase.h"
|
||||
#import "ASLayoutTestNode.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASTLayoutFixture : NSObject
|
||||
|
||||
/// The correct layout. The root should be unpositioned (same as -calculatedLayout).
|
||||
@property (nonatomic, strong, nullable) ASLayout *layout;
|
||||
|
||||
/// The layoutSpecBlocks for non-leaf nodes.
|
||||
@property (nonatomic, strong, readonly) NSMapTable<ASDisplayNode *, ASLayoutSpecBlock> *layoutSpecBlocks;
|
||||
|
||||
@property (nonatomic, strong, readonly) ASLayoutTestNode *rootNode;
|
||||
|
||||
@property (nonatomic, strong, readonly) NSSet<ASLayoutTestNode *> *allNodes;
|
||||
|
||||
/// Get the (correct) layout for the specified node.
|
||||
- (ASLayout *)layoutForNode:(ASLayoutTestNode *)node;
|
||||
|
||||
/// Add this to the list of expected size ranges for the given node.
|
||||
- (void)addSizeRange:(ASSizeRange)sizeRange forNode:(ASLayoutTestNode *)node;
|
||||
|
||||
/// If you have a node that wants a size different than it gets, set it here.
|
||||
/// For any leaf nodes that you don't call this on, the node will return the correct size
|
||||
/// based on the fixture's layout. This is useful for triggering multipass stack layout.
|
||||
- (void)setReturnedSize:(CGSize)size forNode:(ASLayoutTestNode *)node;
|
||||
|
||||
/// Get the first expected size range for the node.
|
||||
- (ASSizeRange)firstSizeRangeForNode:(ASLayoutTestNode *)node;
|
||||
|
||||
/// Enumerate all the size ranges for the node.
|
||||
- (void)withSizeRangesForNode:(ASLayoutTestNode *)node block:(void (^)(ASSizeRange sizeRange))block;
|
||||
|
||||
/// Configure the nodes for this fixture. Set testSize on leaf nodes, layoutSpecBlock on container nodes.
|
||||
- (void)apply;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASLayout (TestHelpers)
|
||||
|
||||
@property (nonatomic, readonly) NSArray<ASDisplayNode *> *allNodes;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
134
Tests/ASTLayoutFixture.mm
Normal file
134
Tests/ASTLayoutFixture.mm
Normal file
@ -0,0 +1,134 @@
|
||||
//
|
||||
// ASTLayoutFixture.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASTLayoutFixture.h"
|
||||
|
||||
@interface ASTLayoutFixture ()
|
||||
|
||||
/// The size ranges against which nodes are expected to be measured.
|
||||
@property (nonatomic, strong, readonly) NSMapTable<ASDisplayNode *, NSMutableArray<NSValue *> *> *sizeRanges;
|
||||
|
||||
/// The overridden returned sizes for nodes where you want to trigger multipass layout.
|
||||
@property (nonatomic, strong, readonly) NSMapTable<ASDisplayNode *, NSValue *> *returnedSizes;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTLayoutFixture
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_sizeRanges = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality valueOptions:NSMapTableStrongMemory];
|
||||
_layoutSpecBlocks = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality valueOptions:NSMapTableStrongMemory];
|
||||
_returnedSizes = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality valueOptions:NSMapTableStrongMemory];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addSizeRange:(ASSizeRange)sizeRange forNode:(ASLayoutTestNode *)node
|
||||
{
|
||||
auto ranges = [_sizeRanges objectForKey:node];
|
||||
if (ranges == nil) {
|
||||
ranges = [NSMutableArray array];
|
||||
[_sizeRanges setObject:ranges forKey:node];
|
||||
}
|
||||
[ranges addObject:[NSValue valueWithBytes:&sizeRange objCType:@encode(ASSizeRange)]];
|
||||
}
|
||||
|
||||
- (void)setReturnedSize:(CGSize)size forNode:(ASLayoutTestNode *)node
|
||||
{
|
||||
[_returnedSizes setObject:[NSValue valueWithCGSize:size] forKey:node];
|
||||
}
|
||||
|
||||
- (ASSizeRange)firstSizeRangeForNode:(ASLayoutTestNode *)node
|
||||
{
|
||||
auto val = [_sizeRanges objectForKey:node].firstObject;
|
||||
ASSizeRange r;
|
||||
[val getValue:&r];
|
||||
return r;
|
||||
}
|
||||
|
||||
- (void)withSizeRangesForNode:(ASLayoutTestNode *)node block:(void (^)(ASSizeRange))block
|
||||
{
|
||||
for (NSValue *value in [_sizeRanges objectForKey:node]) {
|
||||
ASSizeRange r;
|
||||
[value getValue:&r];
|
||||
block(r);
|
||||
}
|
||||
}
|
||||
|
||||
- (ASLayout *)layoutForNode:(ASLayoutTestNode *)node
|
||||
{
|
||||
NSMutableArray *allLayouts = [NSMutableArray array];
|
||||
[ASTLayoutFixture collectAllLayoutsFromLayout:self.layout array:allLayouts];
|
||||
for (ASLayout *layout in allLayouts) {
|
||||
if (layout.layoutElement == node) {
|
||||
return layout;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
/// A very dumb tree iteration approach. NSEnumerator or something would be way better.
|
||||
+ (void)collectAllLayoutsFromLayout:(ASLayout *)layout array:(NSMutableArray<ASLayout *> *)array
|
||||
{
|
||||
[array addObject:layout];
|
||||
for (ASLayout *sublayout in layout.sublayouts) {
|
||||
[self collectAllLayoutsFromLayout:sublayout array:array];
|
||||
}
|
||||
}
|
||||
|
||||
- (ASLayoutTestNode *)rootNode
|
||||
{
|
||||
return (ASLayoutTestNode *)self.layout.layoutElement;
|
||||
}
|
||||
|
||||
- (NSSet<ASLayoutTestNode *> *)allNodes
|
||||
{
|
||||
auto allLayouts = [NSMutableArray array];
|
||||
[ASTLayoutFixture collectAllLayoutsFromLayout:self.layout array:allLayouts];
|
||||
return [NSSet setWithArray:[allLayouts valueForKey:@"layoutElement"]];
|
||||
}
|
||||
|
||||
- (void)apply
|
||||
{
|
||||
// Update layoutSpecBlock for parent nodes, set automatic subnode management
|
||||
for (ASDisplayNode *node in _layoutSpecBlocks) {
|
||||
auto block = [_layoutSpecBlocks objectForKey:node];
|
||||
if (node.layoutSpecBlock != block) {
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
node.layoutSpecBlock = block;
|
||||
[node setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
[self setTestSizesOfLeafNodesInLayout:self.layout];
|
||||
}
|
||||
|
||||
/// Go through the given layout, and for all the leaf nodes, set their preferredSize
|
||||
/// to the layout size if needed, then call -setNeedsLayout
|
||||
- (void)setTestSizesOfLeafNodesInLayout:(ASLayout *)layout
|
||||
{
|
||||
auto node = (ASLayoutTestNode *)layout.layoutElement;
|
||||
if (layout.sublayouts.count == 0) {
|
||||
auto override = [self.returnedSizes objectForKey:node];
|
||||
node.testSize = override ? override.CGSizeValue : layout.size;
|
||||
} else {
|
||||
node.testSize = CGSizeZero;
|
||||
for (ASLayout *sublayout in layout.sublayouts) {
|
||||
[self setTestSizesOfLeafNodesInLayout:sublayout];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@ -12,6 +12,11 @@
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
// Not strictly necessary, but convenient
|
||||
#import <OCMock/OCMock.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import "OCMockObject+ASAdditions.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ASTestCase : XCTestCase
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <OCMock/OCMockObject.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
@interface OCMockObject (ASAdditions)
|
||||
|
||||
@ -30,4 +30,31 @@
|
||||
*/
|
||||
- (void)addImplementedOptionalProtocolMethods:(SEL)aSelector, ... NS_REQUIRES_NIL_TERMINATION;
|
||||
|
||||
/// An optional block to modify description text. Only used in OCClassMockObject currently.
|
||||
@property (atomic) NSString *(^modifyDescriptionBlock)(OCMockObject *object, NSString *baseDescription);
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Additional stub recorders useful in ASDK.
|
||||
*/
|
||||
@interface OCMStubRecorder (ASProperties)
|
||||
|
||||
/**
|
||||
* Add a debug-break side effect to this stub/expectation.
|
||||
*
|
||||
* You will usually need to jump to frame 12 "fr s 12"
|
||||
*/
|
||||
#define andDebugBreak() _andDebugBreak()
|
||||
@property (nonatomic, readonly) OCMStubRecorder *(^ _andDebugBreak)(void);
|
||||
|
||||
#define ignoringNonObjectArgs() _ignoringNonObjectArgs()
|
||||
@property (nonatomic, readonly) OCMStubRecorder *(^ _ignoringNonObjectArgs)(void);
|
||||
|
||||
#define onMainThread() _onMainThread()
|
||||
@property (nonatomic, readonly) OCMStubRecorder *(^ _onMainThread)(void);
|
||||
|
||||
#define offMainThread() _offMainThread()
|
||||
@property (nonatomic, readonly) OCMStubRecorder *(^ _offMainThread)(void);
|
||||
|
||||
@end
|
||||
|
||||
@ -15,6 +15,8 @@
|
||||
#import <OCMock/OCMock.h>
|
||||
#import <objc/runtime.h>
|
||||
#import "ASTestCase.h"
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import "debugbreak.h"
|
||||
|
||||
@interface ASTestCase (OCMockObjectRegistering)
|
||||
|
||||
@ -32,9 +34,18 @@
|
||||
method_exchangeImplementations(orig, new);
|
||||
|
||||
// init <-> swizzled_init
|
||||
Method origInit = class_getInstanceMethod([OCMockObject class], @selector(init));
|
||||
Method newInit = class_getInstanceMethod(self, @selector(swizzled_init));
|
||||
method_exchangeImplementations(origInit, newInit);
|
||||
{
|
||||
Method origInit = class_getInstanceMethod([OCMockObject class], @selector(init));
|
||||
Method newInit = class_getInstanceMethod(self, @selector(swizzled_init));
|
||||
method_exchangeImplementations(origInit, newInit);
|
||||
}
|
||||
|
||||
// (class mock) description <-> swizzled_classMockDescription
|
||||
{
|
||||
Method orig = class_getInstanceMethod(OCMockObject.classMockObjectClass, @selector(description));
|
||||
Method new = class_getInstanceMethod(self, @selector(swizzled_classMockDescription));
|
||||
method_exchangeImplementations(orig, new);
|
||||
}
|
||||
}
|
||||
|
||||
/// Since OCProtocolMockObject is private, use this method to get the class.
|
||||
@ -49,6 +60,18 @@
|
||||
return c;
|
||||
}
|
||||
|
||||
/// Since OCClassMockObject is private, use this method to get the class.
|
||||
+ (Class)classMockObjectClass
|
||||
{
|
||||
static Class c;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
c = NSClassFromString(@"OCClassMockObject");
|
||||
NSAssert(c != Nil, nil);
|
||||
});
|
||||
return c;
|
||||
}
|
||||
|
||||
/// Whether the user has opted-in to specify which optional methods are implemented for this object.
|
||||
- (BOOL)hasSpecifiedOptionalProtocolMethods
|
||||
{
|
||||
@ -142,4 +165,77 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)swizzled_classMockDescription
|
||||
{
|
||||
NSString *orig = [self swizzled_classMockDescription];
|
||||
__auto_type block = self.modifyDescriptionBlock;
|
||||
if (block) {
|
||||
return block(self, orig);
|
||||
}
|
||||
return orig;
|
||||
}
|
||||
|
||||
- (void)setModifyDescriptionBlock:(NSString *(^)(OCMockObject *, NSString *))modifyDescriptionBlock
|
||||
{
|
||||
objc_setAssociatedObject(self, @selector(modifyDescriptionBlock), modifyDescriptionBlock, OBJC_ASSOCIATION_COPY);
|
||||
}
|
||||
|
||||
- (NSString *(^)(OCMockObject *, NSString *))modifyDescriptionBlock
|
||||
{
|
||||
return objc_getAssociatedObject(self, _cmd);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation OCMStubRecorder (ASProperties)
|
||||
|
||||
@dynamic _ignoringNonObjectArgs;
|
||||
|
||||
- (OCMStubRecorder *(^)(void))_ignoringNonObjectArgs
|
||||
{
|
||||
id (^theBlock)(void) = ^ ()
|
||||
{
|
||||
return [self ignoringNonObjectArgs];
|
||||
};
|
||||
return theBlock;
|
||||
}
|
||||
|
||||
@dynamic _onMainThread;
|
||||
|
||||
- (OCMStubRecorder *(^)(void))_onMainThread
|
||||
{
|
||||
id (^theBlock)(void) = ^ ()
|
||||
{
|
||||
return [self andDo:^(NSInvocation *invocation) {
|
||||
ASDisplayNodeAssertMainThread();
|
||||
}];
|
||||
};
|
||||
return theBlock;
|
||||
}
|
||||
|
||||
@dynamic _offMainThread;
|
||||
|
||||
- (OCMStubRecorder *(^)(void))_offMainThread
|
||||
{
|
||||
id (^theBlock)(void) = ^ ()
|
||||
{
|
||||
return [self andDo:^(NSInvocation *invocation) {
|
||||
ASDisplayNodeAssertNotMainThread();
|
||||
}];
|
||||
};
|
||||
return theBlock;
|
||||
}
|
||||
|
||||
@dynamic _andDebugBreak;
|
||||
|
||||
- (OCMStubRecorder *(^)(void))_andDebugBreak
|
||||
{
|
||||
id (^theBlock)(void) = ^ ()
|
||||
{
|
||||
return [self andDo:^(NSInvocation *invocation) {
|
||||
debug_break();
|
||||
}];
|
||||
};
|
||||
return theBlock;
|
||||
}
|
||||
@end
|
||||
|
||||
146
Tests/Common/debugbreak.h
Normal file
146
Tests/Common/debugbreak.h
Normal file
@ -0,0 +1,146 @@
|
||||
//
|
||||
// debugbreak.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
/* Copyright (c) 2011-2015, Scott Tsai
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef DEBUG_BREAK_H
|
||||
#define DEBUG_BREAK_H
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#define debug_break __debugbreak
|
||||
|
||||
#else
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum {
|
||||
/* gcc optimizers consider code after __builtin_trap() dead.
|
||||
* Making __builtin_trap() unsuitable for breaking into the debugger */
|
||||
DEBUG_BREAK_PREFER_BUILTIN_TRAP_TO_SIGTRAP = 0,
|
||||
};
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
enum { HAVE_TRAP_INSTRUCTION = 1, };
|
||||
__attribute__((gnu_inline, always_inline))
|
||||
__inline__ static void trap_instruction(void)
|
||||
{
|
||||
__asm__ volatile("int $0x03");
|
||||
}
|
||||
#elif defined(__thumb__)
|
||||
enum { HAVE_TRAP_INSTRUCTION = 1, };
|
||||
/* FIXME: handle __THUMB_INTERWORK__ */
|
||||
__attribute__((gnu_inline, always_inline))
|
||||
__inline__ static void trap_instruction(void)
|
||||
{
|
||||
/* See 'arm-linux-tdep.c' in GDB source.
|
||||
* Both instruction sequences below work. */
|
||||
#if 1
|
||||
/* 'eabi_linux_thumb_le_breakpoint' */
|
||||
__asm__ volatile(".inst 0xde01");
|
||||
#else
|
||||
/* 'eabi_linux_thumb2_le_breakpoint' */
|
||||
__asm__ volatile(".inst.w 0xf7f0a000");
|
||||
#endif
|
||||
|
||||
/* Known problem:
|
||||
* After a breakpoint hit, can't stepi, step, or continue in GDB.
|
||||
* 'step' stuck on the same instruction.
|
||||
*
|
||||
* Workaround: a new GDB command,
|
||||
* 'debugbreak-step' is defined in debugbreak-gdb.py
|
||||
* that does:
|
||||
* (gdb) set $instruction_len = 2
|
||||
* (gdb) tbreak *($pc + $instruction_len)
|
||||
* (gdb) jump *($pc + $instruction_len)
|
||||
*/
|
||||
}
|
||||
#elif defined(__arm__) && !defined(__thumb__)
|
||||
enum { HAVE_TRAP_INSTRUCTION = 1, };
|
||||
__attribute__((gnu_inline, always_inline))
|
||||
__inline__ static void trap_instruction(void)
|
||||
{
|
||||
/* See 'arm-linux-tdep.c' in GDB source,
|
||||
* 'eabi_linux_arm_le_breakpoint' */
|
||||
__asm__ volatile(".inst 0xe7f001f0");
|
||||
/* Has same known problem and workaround
|
||||
* as Thumb mode */
|
||||
}
|
||||
#elif defined(__aarch64__)
|
||||
enum { HAVE_TRAP_INSTRUCTION = 1, };
|
||||
__attribute__((gnu_inline, always_inline))
|
||||
__inline__ static void trap_instruction(void)
|
||||
{
|
||||
/* See 'aarch64-tdep.c' in GDB source,
|
||||
* 'aarch64_default_breakpoint' */
|
||||
__asm__ volatile(".inst 0xd4200000");
|
||||
}
|
||||
#else
|
||||
enum { HAVE_TRAP_INSTRUCTION = 0, };
|
||||
#endif
|
||||
|
||||
__attribute__((gnu_inline, always_inline))
|
||||
__inline__ static void debug_break(void)
|
||||
{
|
||||
if (HAVE_TRAP_INSTRUCTION) {
|
||||
trap_instruction();
|
||||
} else if (DEBUG_BREAK_PREFER_BUILTIN_TRAP_TO_SIGTRAP) {
|
||||
/* raises SIGILL on Linux x86{,-64}, to continue in gdb:
|
||||
* (gdb) handle SIGILL stop nopass
|
||||
* */
|
||||
__builtin_trap();
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
/* SIGTRAP available only on POSIX-compliant operating systems
|
||||
* use builtin trap instead */
|
||||
__builtin_trap();
|
||||
#else
|
||||
raise(SIGTRAP);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Loading…
x
Reference in New Issue
Block a user