@devicelab/maestro-runner-darwin-arm64 1.1.28 → 1.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -3
- package/bin/maestro-runner +0 -0
- package/drivers/android/devicelab-android-driver-test.apk +0 -0
- package/drivers/android/devicelab-android-driver.apk +0 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/ATTRIBUTION.md +16 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+Lifecycle.swift +33 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+Snapshot.swift +195 -88
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests.swift +3 -0
- package/drivers/ios/WebDriverAgent/CHANGELOG.md +202 -0
- package/drivers/ios/WebDriverAgent/README.md +18 -9
- package/drivers/ios/WebDriverAgent/Scripts/ci/build-real.sh +6 -10
- package/drivers/ios/WebDriverAgent/Scripts/fetch-prebuilt-wda.mjs +3 -2
- package/drivers/ios/WebDriverAgent/WebDriverAgent.xcodeproj/project.pbxproj +119 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCAXClient_iOS+FBSnapshotReqParams.m +8 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.h +3 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +30 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.h +53 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m +124 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h +6 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +100 -23
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHinge.h +41 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHinge.m +215 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBRotation.m +8 -3
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBFind.m +20 -7
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBScrolling.m +110 -50
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBTyping.h +7 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBTyping.m +20 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m +14 -11
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBCustomCommands.m +86 -10
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBElementCommands.m +57 -7
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBSessionCommands.m +3 -2
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Info.plist +2 -2
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBHTTPServer.m +39 -16
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBScreenRecordingRequest.m +7 -2
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBSession.h +22 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBSession.m +78 -12
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBTCPSocket.h +5 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBTCPSocket.m +10 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBWebServer.m +4 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBActiveAppDetectionPoint.h +2 -2
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBActiveAppDetectionPoint.m +20 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.h +8 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.m +2 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBHIDOperations.h +41 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBHIDOperations.m +244 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBImageProcessor.m +3 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBMathUtils.h +11 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBMathUtils.m +38 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBMjpegServer.m +5 -3
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreen.h +17 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreen.m +76 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreenshot.h +2 -2
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreenshot.m +6 -2
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.h +2 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.m +2 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettingsHandler.m +48 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m +121 -13
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBXPath.m +181 -128
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/LRUCache/LRUCache.m +8 -5
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/LRUCache/LRUCacheNode.h +1 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/WebDriverAgentLib.h +1 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/COORDINATE_PROBE.md +3 -1
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/FBCoordinateProbeViewController.m +18 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/SceneDelegate.m +33 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/ViewController.m +26 -7
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBConfigurationTests.m +52 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBDisplayGestureTests.m +541 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBElementSwipingTests.m +95 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBImageProcessorTests.m +34 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBScreenTests.m +392 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBSessionIntegrationTests.m +97 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBTypingTest.m +33 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBW3CTouchActionsIntegrationTests.m +126 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIApplicationHelperTests.m +111 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m +97 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIDeviceRotationTests.m +53 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIElementFBFindTests.m +113 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests_tvOS/FBTVFocusIntegrationTests.m +21 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests_watchOS/WDAAppLifecycleIntegrationTests.swift +12 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBCustomCommandsTests.m +61 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBElementUtilitiesTests.m +40 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBHTTPServerSessionTests.m +22 -4
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBHTTPServerTests.m +199 -5
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBLRUCacheTests.m +34 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBMathUtilsTests.m +45 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBSessionTests.m +201 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBXPathTests.m +101 -0
- package/drivers/ios/WebDriverAgent/docs/hinge-angle.md +54 -0
- package/drivers/ios/WebDriverAgent/docs/multiple-displays.md +168 -0
- package/drivers/ios/WebDriverAgent/lib/webdriveragent.ts +1 -2
- package/drivers/ios/WebDriverAgent/lib/xcodebuild.ts +58 -10
- package/drivers/ios/WebDriverAgent/package.json +8 -15
- package/drivers/ios/WebDriverAgent/test/functional/helpers/display-settings.ts +49 -0
- package/drivers/ios/WebDriverAgent/test/functional/helpers/simulator.ts +7 -7
- package/drivers/ios/WebDriverAgent/test/functional/webdriveragent-e2e.spec.ts +4 -0
- package/drivers/ios/WebDriverAgent/test/unit/xcodebuild.spec.ts +230 -0
- package/drivers/ios/WebDriverAgent/tsconfig.json +2 -1
- package/drivers/ios/devicelab-ios-agent/README.md +12 -0
- package/drivers/ios/devicelab-ios-agent/device/DevicelabIOSAgent.app/DevicelabIOSAgent +0 -0
- package/drivers/ios/devicelab-ios-agent/device/DevicelabIOSAgentUITests-Runner.app/PlugIns/DevicelabIOSAgentUITests.xctest/DevicelabIOSAgentUITests +0 -0
- package/drivers/ios/devicelab-ios-agent/device/manifest.json +3 -3
- package/drivers/ios/devicelab-ios-agent/simulator/DevicelabIOSAgent.app/DevicelabIOSAgent +0 -0
- package/drivers/ios/devicelab-ios-agent/simulator/DevicelabIOSAgentUITests-Runner.app/PlugIns/DevicelabIOSAgentUITests.xctest/DevicelabIOSAgentUITests +0 -0
- package/drivers/ios/devicelab-ios-agent/simulator/manifest.json +3 -3
- package/package.json +1 -1
- package/drivers/ios/WebDriverAgent/.editorconfig +0 -11
- package/drivers/ios/WebDriverAgent/.github/ISSUE_TEMPLATE.md +0 -4
- package/drivers/ios/WebDriverAgent/.github/actions/xcode-setup/action.yml +0 -32
- package/drivers/ios/WebDriverAgent/.github/dependabot.yml +0 -22
- package/drivers/ios/WebDriverAgent/.github/workflows/functional-test.yml +0 -103
- package/drivers/ios/WebDriverAgent/.github/workflows/pr-title.yml +0 -10
- package/drivers/ios/WebDriverAgent/.github/workflows/publish.js.yml +0 -112
- package/drivers/ios/WebDriverAgent/.github/workflows/unit-test.yml +0 -32
- package/drivers/ios/WebDriverAgent/.github/workflows/wda-package.yml +0 -114
- package/drivers/ios/WebDriverAgent/.github/workflows/wda-tests.yml +0 -246
- package/drivers/ios/WebDriverAgent/oxfmt.config.mjs +0 -6
- package/drivers/ios/WebDriverAgent/oxlint.config.mjs +0 -6
- package/drivers/ios/WebDriverAgent/release.config.mjs +0 -14
|
@@ -12,12 +12,17 @@
|
|
|
12
12
|
#import "FBElementCache.h"
|
|
13
13
|
#import "FBRouteRequest-Private.h"
|
|
14
14
|
#import "FBSession.h"
|
|
15
|
+
#import "FBResponsePayload.h"
|
|
16
|
+
#import "RouteResponse.h"
|
|
17
|
+
#import "XCUIDevice+FBHinge.h"
|
|
15
18
|
#import "Doubles/XCUIElementDouble.h"
|
|
16
19
|
|
|
17
20
|
#if !TARGET_OS_TV && __clang_major__ >= 15
|
|
18
21
|
|
|
19
22
|
@interface FBCustomCommands (FBWDATestable)
|
|
20
23
|
+ (id<FBResponsePayload>)handleKeyboardInput:(FBRouteRequest *)request;
|
|
24
|
+
+ (id<FBResponsePayload>)handleGetSimulatedHingeAngle:(FBRouteRequest *)request;
|
|
25
|
+
+ (id<FBResponsePayload>)handleSetSimulatedHingeAngle:(FBRouteRequest *)request;
|
|
21
26
|
@end
|
|
22
27
|
|
|
23
28
|
@interface FBCustomCommandsTests : XCTestCase
|
|
@@ -78,6 +83,62 @@
|
|
|
78
83
|
XCTAssertEqual(element.lastTypedModifierFlags, 2);
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
- (NSDictionary *)hingeResponseWithArguments:(NSDictionary *)arguments
|
|
87
|
+
{
|
|
88
|
+
FBRouteRequest *request = [FBRouteRequest routeRequestWithURL:[NSURL URLWithString:@"http://localhost:8100/"]
|
|
89
|
+
parameters:@{}
|
|
90
|
+
arguments:arguments];
|
|
91
|
+
RouteResponse *response = [RouteResponse new];
|
|
92
|
+
[[FBCustomCommands handleSetSimulatedHingeAngle:request] dispatchWithResponse:response];
|
|
93
|
+
return [NSJSONSerialization JSONObjectWithData:response.responseData options:0 error:nil];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
- (void)testHingeAngleReading
|
|
97
|
+
{
|
|
98
|
+
NSError *error = nil;
|
|
99
|
+
NSNumber *angle = [XCUIDevice.sharedDevice fb_getSimulatedHingeAngle:&error];
|
|
100
|
+
BOOL available = XCUIDevice.sharedDevice.fb_supportsHingeAngleReading;
|
|
101
|
+
if (available) {
|
|
102
|
+
XCTAssertNotNil(angle);
|
|
103
|
+
XCTAssertNil(error);
|
|
104
|
+
XCTAssertTrue(isfinite(angle.doubleValue));
|
|
105
|
+
} else {
|
|
106
|
+
XCTAssertNil(angle);
|
|
107
|
+
XCTAssertNotNil(error);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
FBRouteRequest *request = [FBRouteRequest routeRequestWithURL:[NSURL URLWithString:@"http://localhost:8100/"]
|
|
111
|
+
parameters:@{}
|
|
112
|
+
arguments:@{}];
|
|
113
|
+
RouteResponse *response = [RouteResponse new];
|
|
114
|
+
[[FBCustomCommands handleGetSimulatedHingeAngle:request] dispatchWithResponse:response];
|
|
115
|
+
NSDictionary *body = [NSJSONSerialization JSONObjectWithData:response.responseData options:0 error:nil];
|
|
116
|
+
if (available) {
|
|
117
|
+
XCTAssertTrue([body[@"value"] isKindOfClass:NSNumber.class]);
|
|
118
|
+
XCTAssertTrue(isfinite([body[@"value"] doubleValue]));
|
|
119
|
+
} else {
|
|
120
|
+
XCTAssertEqualObjects(body[@"value"][@"error"], @"unsupported operation");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
- (void)testHingeAngleRejectsInvalidArguments
|
|
125
|
+
{
|
|
126
|
+
for (id value in @[@(-1), @181, @YES, @"90", NSNull.null, @(NAN), @(INFINITY)]) {
|
|
127
|
+
NSDictionary *response = [self hingeResponseWithArguments:@{@"angle": value}];
|
|
128
|
+
XCTAssertEqualObjects(response[@"value"][@"error"], @"invalid argument");
|
|
129
|
+
}
|
|
130
|
+
XCTAssertEqualObjects([self hingeResponseWithArguments:@{}][@"value"][@"error"], @"invalid argument");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
- (void)testHingeAngleRejectsUnsupportedDevices
|
|
134
|
+
{
|
|
135
|
+
XCTSkipIf(XCUIDevice.sharedDevice.fb_canAttemptSimulatedHingeAngleInjection, @"Requires a device without an available hinge");
|
|
136
|
+
for (NSNumber *angle in @[@0, @90, @180]) {
|
|
137
|
+
XCTAssertEqualObjects([self hingeResponseWithArguments:@{@"angle": angle}][@"value"][@"error"],
|
|
138
|
+
@"unsupported operation");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
81
142
|
@end
|
|
82
143
|
|
|
83
144
|
#endif
|
|
@@ -12,12 +12,52 @@
|
|
|
12
12
|
#import "FBElement.h"
|
|
13
13
|
#import "XCUIElementDouble.h"
|
|
14
14
|
#import "FBElementUtils.h"
|
|
15
|
+
#import "FBConfiguration.h"
|
|
16
|
+
#import "XCUIElement+FBUtilities.h"
|
|
17
|
+
#import <objc/runtime.h>
|
|
18
|
+
|
|
19
|
+
@interface FBQuiescenceApplicationDouble : NSObject
|
|
20
|
+
@property (nonatomic) BOOL fb_shouldWaitForQuiescence;
|
|
21
|
+
@property (nonatomic) NSTimeInterval observedTimeout;
|
|
22
|
+
@end
|
|
23
|
+
@implementation FBQuiescenceApplicationDouble
|
|
24
|
+
- (id)applicationImpl { return self; }
|
|
25
|
+
- (id)currentProcess { return self; }
|
|
26
|
+
- (void)fb_waitForQuiescenceIncludingAnimationsIdle:(BOOL)includingAnimations
|
|
27
|
+
{
|
|
28
|
+
self.observedTimeout = FBConfiguration.sharedInstance.waitForIdleTimeout;
|
|
29
|
+
@throw [NSException exceptionWithName:@"QuiescenceFailure" reason:@"test" userInfo:nil];
|
|
30
|
+
}
|
|
31
|
+
@end
|
|
15
32
|
|
|
16
33
|
@interface FBElementUtilitiesTests : XCTestCase
|
|
17
34
|
@end
|
|
18
35
|
|
|
19
36
|
@implementation FBElementUtilitiesTests
|
|
20
37
|
|
|
38
|
+
- (void)testStabilityWaitRestoresSettingsAfterAnException
|
|
39
|
+
{
|
|
40
|
+
NSTimeInterval originalTimeout = FBConfiguration.sharedInstance.waitForIdleTimeout;
|
|
41
|
+
FBQuiescenceApplicationDouble *application = [FBQuiescenceApplicationDouble new];
|
|
42
|
+
XCUIElementDouble *element = [XCUIElementDouble new];
|
|
43
|
+
element.application = (id)application;
|
|
44
|
+
SEL selector = @selector(fb_waitUntilStableWithTimeout:);
|
|
45
|
+
#pragma clang diagnostic push
|
|
46
|
+
#pragma clang diagnostic ignored "-Wcast-function-type-strict"
|
|
47
|
+
void (*wait)(id, SEL, NSTimeInterval) = (void (*)(id, SEL, NSTimeInterval))
|
|
48
|
+
class_getMethodImplementation(XCUIElement.class, selector);
|
|
49
|
+
#pragma clang diagnostic pop
|
|
50
|
+
@try {
|
|
51
|
+
FBConfiguration.sharedInstance.waitForIdleTimeout = 17;
|
|
52
|
+
XCTAssertThrowsSpecificNamed(wait(element, selector, 2), NSException, @"QuiescenceFailure");
|
|
53
|
+
XCTAssertEqual(application.observedTimeout, 2);
|
|
54
|
+
XCTAssertFalse(application.fb_shouldWaitForQuiescence);
|
|
55
|
+
XCTAssertEqual(FBConfiguration.sharedInstance.waitForIdleTimeout, 17);
|
|
56
|
+
} @finally {
|
|
57
|
+
FBConfiguration.sharedInstance.waitForIdleTimeout = originalTimeout;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
21
61
|
- (void)testTypesFiltering {
|
|
22
62
|
NSMutableArray *elements = [NSMutableArray new];
|
|
23
63
|
XCUIElementDouble *el1 = [XCUIElementDouble new];
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
#import <sys/socket.h>
|
|
14
14
|
|
|
15
15
|
#import "FBHTTPServer.h"
|
|
16
|
+
#import "FBSession-Private.h"
|
|
16
17
|
|
|
17
18
|
static atomic_int gSessionProbeHits;
|
|
18
19
|
|
|
@@ -23,10 +24,26 @@ static atomic_int gSessionProbeHits;
|
|
|
23
24
|
|
|
24
25
|
@implementation FBHTTPServerSessionTests
|
|
25
26
|
|
|
27
|
+
- (void)testAbandonmentDoesNotRetainResponsesAfterPendingRequestsDrain
|
|
28
|
+
{
|
|
29
|
+
__weak RouteResponse *weakResponse;
|
|
30
|
+
@autoreleasepool {
|
|
31
|
+
RouteResponse *response = [RouteResponse new];
|
|
32
|
+
[response respondWithString:@"deleted"];
|
|
33
|
+
weakResponse = response;
|
|
34
|
+
for (NSUInteger index = 0; index < 1000; index++) {
|
|
35
|
+
[self.server abandonPendingRequestsForSessionID:[NSString stringWithFormat:@"old-%lu", (unsigned long)index]
|
|
36
|
+
withResponse:response];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
XCTAssertNil(weakResponse);
|
|
40
|
+
}
|
|
41
|
+
|
|
26
42
|
- (void)setUp
|
|
27
43
|
{
|
|
28
44
|
[super setUp];
|
|
29
45
|
atomic_store(&gSessionProbeHits, 0);
|
|
46
|
+
[FBSession initWithApplication:nil].identifier = @"live-session";
|
|
30
47
|
self.server = [FBHTTPServer new];
|
|
31
48
|
[self.server get:@"/session/:sessionID/probe" withBlock:^(RouteRequest *request, RouteResponse *response) {
|
|
32
49
|
atomic_fetch_add(&gSessionProbeHits, 1);
|
|
@@ -42,6 +59,7 @@ static atomic_int gSessionProbeHits;
|
|
|
42
59
|
{
|
|
43
60
|
[self.server stop:NO];
|
|
44
61
|
self.server = nil;
|
|
62
|
+
[FBSession.activeSession kill];
|
|
45
63
|
[super tearDown];
|
|
46
64
|
}
|
|
47
65
|
|
|
@@ -93,14 +111,14 @@ static atomic_int gSessionProbeHits;
|
|
|
93
111
|
|
|
94
112
|
NSString *response = [self responseForRawPayload:(NSData * _Nonnull)[@"GET /session/dead-session/probe HTTP/1.1\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]
|
|
95
113
|
timeout:5.0];
|
|
96
|
-
XCTAssertTrue([response containsString:@"session
|
|
114
|
+
XCTAssertTrue([response containsString:@"invalid session id"], @"%@", response);
|
|
97
115
|
XCTAssertEqual(atomic_load(&gSessionProbeHits), 0, @"the route must not run for a deleted session");
|
|
98
116
|
}
|
|
99
117
|
|
|
100
118
|
- (void)testAbandonedSessionIsRememberedAfterManyLaterAbandonments
|
|
101
119
|
{
|
|
102
|
-
//
|
|
103
|
-
//
|
|
120
|
+
// A stale UUID must still be rejected after arbitrarily many other sessions,
|
|
121
|
+
// without needing to remember every deleted UUID.
|
|
104
122
|
RouteResponse *abandonedResponse = [RouteResponse new];
|
|
105
123
|
[abandonedResponse respondWithString:@"session-was-deleted"];
|
|
106
124
|
[self.server abandonPendingRequestsForSessionID:@"dead-session" withResponse:abandonedResponse];
|
|
@@ -113,7 +131,7 @@ static atomic_int gSessionProbeHits;
|
|
|
113
131
|
|
|
114
132
|
NSString *response = [self responseForRawPayload:(NSData * _Nonnull)[@"GET /session/dead-session/probe HTTP/1.1\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]
|
|
115
133
|
timeout:5.0];
|
|
116
|
-
XCTAssertTrue([response containsString:@"session
|
|
134
|
+
XCTAssertTrue([response containsString:@"invalid session id"], @"%@", response);
|
|
117
135
|
XCTAssertEqual(atomic_load(&gSessionProbeHits), 0, @"the route must not run for a deleted session");
|
|
118
136
|
}
|
|
119
137
|
|
|
@@ -14,14 +14,44 @@
|
|
|
14
14
|
#import <unistd.h>
|
|
15
15
|
|
|
16
16
|
#import "FBHTTPServer.h"
|
|
17
|
+
#import "FBTCPSocket.h"
|
|
18
|
+
#import "FBWebServer.h"
|
|
19
|
+
|
|
20
|
+
@interface FBWebServer (FBWDATestable)
|
|
21
|
+
- (void)registerServerKeyRouteHandlers;
|
|
22
|
+
@end
|
|
17
23
|
|
|
18
24
|
static atomic_int gFramingProbeHits;
|
|
19
25
|
|
|
26
|
+
// The response in `data` once all of it has arrived - its header block and Content-Length body -
|
|
27
|
+
// or nil while more is due.
|
|
28
|
+
static NSString *FBCompleteResponse(NSData *data)
|
|
29
|
+
{
|
|
30
|
+
NSData *separator = (NSData * _Nonnull)[@"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
|
|
31
|
+
NSRange headerEnd = [data rangeOfData:separator options:(NSDataSearchOptions)0 range:NSMakeRange(0, data.length)];
|
|
32
|
+
if (NSNotFound == headerEnd.location) {
|
|
33
|
+
return nil;
|
|
34
|
+
}
|
|
35
|
+
NSString *headers = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, headerEnd.location)] encoding:NSUTF8StringEncoding];
|
|
36
|
+
NSUInteger contentLength = 0;
|
|
37
|
+
for (NSString *line in [headers componentsSeparatedByString:@"\r\n"]) {
|
|
38
|
+
NSArray<NSString *> *field = [line componentsSeparatedByString:@":"];
|
|
39
|
+
if (2 == field.count && NSOrderedSame == [field[0] caseInsensitiveCompare:@"Content-Length"]) {
|
|
40
|
+
contentLength = (NSUInteger)[field[1] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet].integerValue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (data.length < NSMaxRange(headerEnd) + contentLength) {
|
|
44
|
+
return nil;
|
|
45
|
+
}
|
|
46
|
+
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
47
|
+
}
|
|
48
|
+
|
|
20
49
|
// Exercises FBHTTPServer's HTTP framing defenses with raw socket data that URL-loading APIs
|
|
21
50
|
// cannot produce: malformed Content-Length values and header blocks that never terminate.
|
|
22
51
|
@interface FBHTTPServerTests : XCTestCase
|
|
23
52
|
@property (nonatomic, strong) FBHTTPServer *server;
|
|
24
53
|
@property (nonatomic, assign) uint16_t port;
|
|
54
|
+
@property (nonatomic, strong) dispatch_semaphore_t slowRouteStarted;
|
|
25
55
|
@end
|
|
26
56
|
|
|
27
57
|
@implementation FBHTTPServerTests
|
|
@@ -38,6 +68,13 @@ static atomic_int gFramingProbeHits;
|
|
|
38
68
|
[self.server get:@"/framing/ping" withBlock:^(RouteRequest *request, RouteResponse *response) {
|
|
39
69
|
[response respondWithString:@"pong"];
|
|
40
70
|
}];
|
|
71
|
+
dispatch_semaphore_t slowRouteStarted = dispatch_semaphore_create(0);
|
|
72
|
+
self.slowRouteStarted = slowRouteStarted;
|
|
73
|
+
[self.server get:@"/framing/slow" withBlock:^(RouteRequest *request, RouteResponse *response) {
|
|
74
|
+
dispatch_semaphore_signal(slowRouteStarted);
|
|
75
|
+
[NSThread sleepForTimeInterval:0.5];
|
|
76
|
+
[response respondWithString:@"slow-ok"];
|
|
77
|
+
}];
|
|
41
78
|
self.server.port = 0;
|
|
42
79
|
NSError *error;
|
|
43
80
|
XCTAssertTrue([self.server start:&error], @"%@", error);
|
|
@@ -51,14 +88,12 @@ static atomic_int gFramingProbeHits;
|
|
|
51
88
|
[super tearDown];
|
|
52
89
|
}
|
|
53
90
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
- (NSString *)responseForRawPayload:(NSData *)payload timeout:(NSTimeInterval)timeout didClose:(BOOL *)didClose
|
|
91
|
+
// A socket connected to the server that gives up reading after `timeout`, or -1.
|
|
92
|
+
- (int)connectedSocketWithTimeout:(NSTimeInterval)timeout
|
|
57
93
|
{
|
|
58
|
-
*didClose = NO;
|
|
59
94
|
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
60
95
|
if (fd < 0) {
|
|
61
|
-
return
|
|
96
|
+
return -1;
|
|
62
97
|
}
|
|
63
98
|
int noSigpipe = 1;
|
|
64
99
|
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &noSigpipe, sizeof(noSigpipe));
|
|
@@ -68,6 +103,84 @@ static atomic_int gFramingProbeHits;
|
|
|
68
103
|
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
69
104
|
if (0 != connect(fd, (struct sockaddr *)&addr, sizeof(addr))) {
|
|
70
105
|
close(fd);
|
|
106
|
+
return -1;
|
|
107
|
+
}
|
|
108
|
+
return fd;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Descriptors in this process bound to the server's port: its listener plus one per server-side
|
|
112
|
+
// connection still open. Client sockets are bound to ephemeral ports, so they never count.
|
|
113
|
+
- (NSInteger)serverSocketCount
|
|
114
|
+
{
|
|
115
|
+
NSInteger count = 0;
|
|
116
|
+
int limit = getdtablesize();
|
|
117
|
+
for (int fd = 0; fd < limit; fd++) {
|
|
118
|
+
struct sockaddr_storage addr;
|
|
119
|
+
socklen_t length = sizeof(addr);
|
|
120
|
+
if (0 != getsockname(fd, (struct sockaddr *)&addr, &length)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
in_port_t port = 0;
|
|
124
|
+
if (AF_INET == addr.ss_family) {
|
|
125
|
+
port = ((struct sockaddr_in *)&addr)->sin_port;
|
|
126
|
+
} else if (AF_INET6 == addr.ss_family) {
|
|
127
|
+
port = ((struct sockaddr_in6 *)&addr)->sin6_port;
|
|
128
|
+
}
|
|
129
|
+
if (ntohs(port) == self.port) {
|
|
130
|
+
count++;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return count;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// The server learns about hang-ups asynchronously: polls until it holds `expected` sockets or
|
|
137
|
+
// 5 seconds pass, and returns the last count.
|
|
138
|
+
- (NSInteger)serverSocketCountSettlingAt:(NSInteger)expected
|
|
139
|
+
{
|
|
140
|
+
NSInteger count = [self serverSocketCount];
|
|
141
|
+
NSDate *deadline = [NSDate dateWithTimeIntervalSinceNow:5.0];
|
|
142
|
+
while (count != expected && deadline.timeIntervalSinceNow > 0) {
|
|
143
|
+
[NSThread sleepForTimeInterval:0.05];
|
|
144
|
+
count = [self serverSocketCount];
|
|
145
|
+
}
|
|
146
|
+
return count;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Sends `request` on a new connection, reads its complete response and then hangs up first, the
|
|
150
|
+
// way curl or iproxy end a keep-alive exchange. Returns nil unless the server answered and still
|
|
151
|
+
// held its side open when the client hung up.
|
|
152
|
+
- (NSString *)responseClosingFirstForRequest:(NSString *)request
|
|
153
|
+
{
|
|
154
|
+
int fd = [self connectedSocketWithTimeout:5.0];
|
|
155
|
+
if (fd < 0) {
|
|
156
|
+
return nil;
|
|
157
|
+
}
|
|
158
|
+
NSData *payload = (NSData * _Nonnull)[request dataUsingEncoding:NSUTF8StringEncoding];
|
|
159
|
+
send(fd, payload.bytes, payload.length, 0);
|
|
160
|
+
NSMutableData *received = [NSMutableData data];
|
|
161
|
+
NSString *response = nil;
|
|
162
|
+
char chunk[4096];
|
|
163
|
+
while (nil == response) {
|
|
164
|
+
ssize_t n = recv(fd, chunk, sizeof(chunk), 0);
|
|
165
|
+
if (n <= 0) {
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
[received appendBytes:chunk length:(NSUInteger)n];
|
|
169
|
+
response = FBCompleteResponse(received);
|
|
170
|
+
}
|
|
171
|
+
char probe;
|
|
172
|
+
BOOL isStillOpen = -1 == recv(fd, &probe, 1, MSG_PEEK | MSG_DONTWAIT) && EAGAIN == errno;
|
|
173
|
+
close(fd);
|
|
174
|
+
return isStillOpen ? response : nil;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Sends `payload` as-is and reads until the server closes the connection or `timeout` elapses.
|
|
178
|
+
// Returns everything received (nil on connect failure); *didClose reports whether EOF was seen.
|
|
179
|
+
- (NSString *)responseForRawPayload:(NSData *)payload timeout:(NSTimeInterval)timeout didClose:(BOOL *)didClose
|
|
180
|
+
{
|
|
181
|
+
*didClose = NO;
|
|
182
|
+
int fd = [self connectedSocketWithTimeout:timeout];
|
|
183
|
+
if (fd < 0) {
|
|
71
184
|
return nil;
|
|
72
185
|
}
|
|
73
186
|
// send(2) may write only part of the payload, which would truncate the multi-KiB flood
|
|
@@ -122,6 +235,87 @@ static atomic_int gFramingProbeHits;
|
|
|
122
235
|
XCTAssertTrue([response containsString:@"pong"], @"%@", response);
|
|
123
236
|
}
|
|
124
237
|
|
|
238
|
+
- (void)testHealthRespondsWhileRouteQueueIsBlocked
|
|
239
|
+
{
|
|
240
|
+
FBWebServer *webServer = [FBWebServer new];
|
|
241
|
+
[webServer setValue:self.server forKey:@"server"];
|
|
242
|
+
[webServer registerServerKeyRouteHandlers];
|
|
243
|
+
dispatch_queue_t routeQueue = dispatch_queue_create("com.facebook.wda.test.health", DISPATCH_QUEUE_SERIAL);
|
|
244
|
+
[self.server setRouteQueue:routeQueue];
|
|
245
|
+
dispatch_suspend(routeQueue);
|
|
246
|
+
@try {
|
|
247
|
+
NSString *response = [self responseClosingFirstForRequest:@"GET /health HTTP/1.1\r\nHost: localhost\r\n\r\n"];
|
|
248
|
+
XCTAssertTrue([response hasPrefix:@"HTTP/1.1 200"], @"%@", response);
|
|
249
|
+
XCTAssertTrue([response hasSuffix:@"<!DOCTYPE html><html><title>Health Check</title><body><p>I-AM-ALIVE</p></body></html>"], @"%@", response);
|
|
250
|
+
} @finally {
|
|
251
|
+
dispatch_resume(routeQueue);
|
|
252
|
+
dispatch_sync(routeQueue, ^{});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
- (void)testConnectionsTheClientClosesReleaseTheirSockets
|
|
257
|
+
{
|
|
258
|
+
// The server keeps a connection open after answering, so the client is the one that hangs up.
|
|
259
|
+
// Each such connection must give its server-side socket back, or a long-running server
|
|
260
|
+
// eventually fails every request with EMFILE.
|
|
261
|
+
NSInteger baseline = [self serverSocketCount];
|
|
262
|
+
for (int i = 0; i < 50; i++) {
|
|
263
|
+
NSString *response = [self responseClosingFirstForRequest:@"GET /framing/ping HTTP/1.1\r\n\r\n"];
|
|
264
|
+
XCTAssertTrue([response containsString:@"pong"], @"%@", response);
|
|
265
|
+
}
|
|
266
|
+
XCTAssertEqual([self serverSocketCountSettlingAt:baseline], baseline);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
- (void)testEofWaitsForPreviouslyReceivedBytesToBeParsed
|
|
270
|
+
{
|
|
271
|
+
FBHTTPServer *server = [FBHTTPServer new];
|
|
272
|
+
__block NSUInteger calls = 0;
|
|
273
|
+
[server get:@"/probe" withBlock:^(RouteRequest *request, RouteResponse *response) {
|
|
274
|
+
calls++;
|
|
275
|
+
}];
|
|
276
|
+
dispatch_queue_t queue = [server valueForKey:@"bufferProcessingQueue"];
|
|
277
|
+
nw_connection_t client = nw_connection_create(nw_endpoint_create_host("127.0.0.1", "1"),
|
|
278
|
+
nw_parameters_create_secure_tcp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION));
|
|
279
|
+
id<FBTCPSocketDelegate> delegate = (id)server;
|
|
280
|
+
[delegate didClientConnect:client];
|
|
281
|
+
dispatch_suspend(queue);
|
|
282
|
+
@try {
|
|
283
|
+
NSData *data = [@"GET /probe HTTP/1.1\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
|
|
284
|
+
[delegate client:client didReceiveData:data];
|
|
285
|
+
[delegate didClientDisconnect:client];
|
|
286
|
+
} @finally {
|
|
287
|
+
dispatch_resume(queue);
|
|
288
|
+
}
|
|
289
|
+
dispatch_sync(queue, ^{});
|
|
290
|
+
XCTAssertEqual(calls, 1u);
|
|
291
|
+
[server stop:YES];
|
|
292
|
+
nw_connection_cancel(client);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
- (void)testHalfClosedClientGetsItsResponseAndIsThenClosed
|
|
296
|
+
{
|
|
297
|
+
// A client may shut down its sending side right after the request and wait for the response.
|
|
298
|
+
// Its end-of-stream must not cost it that response, and the server must close afterwards.
|
|
299
|
+
NSInteger baseline = [self serverSocketCount];
|
|
300
|
+
int fd = [self connectedSocketWithTimeout:5.0];
|
|
301
|
+
XCTAssertGreaterThanOrEqual(fd, 0);
|
|
302
|
+
NSData *payload = (NSData * _Nonnull)[@"GET /framing/slow HTTP/1.1\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
|
|
303
|
+
XCTAssertEqual(send(fd, payload.bytes, payload.length, 0), (ssize_t)payload.length);
|
|
304
|
+
XCTAssertEqual(dispatch_semaphore_wait(self.slowRouteStarted, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC))), 0L);
|
|
305
|
+
XCTAssertEqual(shutdown(fd, SHUT_WR), 0);
|
|
306
|
+
NSMutableData *received = [NSMutableData data];
|
|
307
|
+
char chunk[4096];
|
|
308
|
+
ssize_t n;
|
|
309
|
+
while ((n = recv(fd, chunk, sizeof(chunk), 0)) > 0) {
|
|
310
|
+
[received appendBytes:chunk length:(NSUInteger)n];
|
|
311
|
+
}
|
|
312
|
+
close(fd);
|
|
313
|
+
NSString *response = [[NSString alloc] initWithData:received encoding:NSUTF8StringEncoding];
|
|
314
|
+
XCTAssertTrue([response containsString:@"slow-ok"], @"%@", response);
|
|
315
|
+
XCTAssertEqual(n, (ssize_t)0, @"the server must close the connection once it has answered");
|
|
316
|
+
XCTAssertEqual([self serverSocketCountSettlingAt:baseline], baseline);
|
|
317
|
+
}
|
|
318
|
+
|
|
125
319
|
- (void)testNonNumericContentLengthIsRejected
|
|
126
320
|
{
|
|
127
321
|
// Under -integerValue's lenient parsing "bogus" became 0: the probe route would run with an
|
|
@@ -15,6 +15,40 @@
|
|
|
15
15
|
|
|
16
16
|
@implementation FBLRUCacheTests
|
|
17
17
|
|
|
18
|
+
- (void)testDestroyingCacheReleasesAllValues
|
|
19
|
+
{
|
|
20
|
+
__weak NSObject *firstValue;
|
|
21
|
+
__weak NSObject *secondValue;
|
|
22
|
+
@autoreleasepool {
|
|
23
|
+
LRUCache *cache = [[LRUCache alloc] initWithCapacity:2];
|
|
24
|
+
NSObject *first = [NSObject new];
|
|
25
|
+
NSObject *second = [NSObject new];
|
|
26
|
+
firstValue = first;
|
|
27
|
+
secondValue = second;
|
|
28
|
+
[cache setObject:first forKey:@"first"];
|
|
29
|
+
[cache setObject:second forKey:@"second"];
|
|
30
|
+
XCTAssertNotNil([cache objectForKey:@"first"]);
|
|
31
|
+
}
|
|
32
|
+
XCTAssertNil(firstValue);
|
|
33
|
+
XCTAssertNil(secondValue);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
- (void)testRemovingValueReleasesItWhileCacheRemainsAlive
|
|
37
|
+
{
|
|
38
|
+
LRUCache *cache = [[LRUCache alloc] initWithCapacity:3];
|
|
39
|
+
__weak NSObject *removedValue;
|
|
40
|
+
@autoreleasepool {
|
|
41
|
+
NSObject *value = [NSObject new];
|
|
42
|
+
removedValue = value;
|
|
43
|
+
[cache setObject:@"oldest" forKey:@"oldest"];
|
|
44
|
+
[cache setObject:value forKey:@"removed"];
|
|
45
|
+
[cache setObject:@"newest" forKey:@"newest"];
|
|
46
|
+
[cache removeObjectForKey:@"removed"];
|
|
47
|
+
}
|
|
48
|
+
XCTAssertNil(removedValue);
|
|
49
|
+
XCTAssertEqualObjects(cache.allObjects, (@[@"newest", @"oldest"]));
|
|
50
|
+
}
|
|
51
|
+
|
|
18
52
|
- (void)assertArray:(NSArray *)array1 equalsTo:(NSArray *)array2
|
|
19
53
|
{
|
|
20
54
|
XCTAssertEqualObjects(array1, array2);
|
|
@@ -148,4 +148,49 @@
|
|
|
148
148
|
XCTAssertTrue(endOffset.dx == -1 && endOffset.dy == -1);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
- (void)testDisplayCoordinateOffsets
|
|
152
|
+
{
|
|
153
|
+
CGSize mainSize = CGSizeMake(402, 874);
|
|
154
|
+
CGSize innerSize = CGSizeMake(669, 951);
|
|
155
|
+
NSArray<NSNumber *> *orientations = @[
|
|
156
|
+
@(UIInterfaceOrientationPortrait),
|
|
157
|
+
@(UIInterfaceOrientationLandscapeLeft),
|
|
158
|
+
@(UIInterfaceOrientationLandscapeRight),
|
|
159
|
+
@(UIInterfaceOrientationPortraitUpsideDown),
|
|
160
|
+
];
|
|
161
|
+
NSArray<NSValue *> *expected = @[
|
|
162
|
+
[NSValue valueWithCGPoint:CGPointZero],
|
|
163
|
+
[NSValue valueWithCGPoint:CGPointMake(0, 77)],
|
|
164
|
+
[NSValue valueWithCGPoint:CGPointMake(267, 0)],
|
|
165
|
+
[NSValue valueWithCGPoint:CGPointMake(267, 77)],
|
|
166
|
+
];
|
|
167
|
+
for (NSUInteger i = 0; i < orientations.count; i++) {
|
|
168
|
+
UIInterfaceOrientation orientation = orientations[i].integerValue;
|
|
169
|
+
CGVector offset = FBDisplayCoordinateOffset(mainSize, innerSize, orientation);
|
|
170
|
+
XCTAssertEqual(offset.dx, expected[i].CGPointValue.x);
|
|
171
|
+
XCTAssertEqual(offset.dy, expected[i].CGPointValue.y);
|
|
172
|
+
CGVector sameDisplay = FBDisplayCoordinateOffset(mainSize, mainSize, orientation);
|
|
173
|
+
XCTAssertEqual(sameDisplay.dx, 0);
|
|
174
|
+
XCTAssertEqual(sameDisplay.dy, 0);
|
|
175
|
+
CGVector reverse = FBDisplayCoordinateOffset(innerSize, mainSize, orientation);
|
|
176
|
+
XCTAssertEqual(reverse.dx, -expected[i].CGPointValue.x);
|
|
177
|
+
XCTAssertEqual(reverse.dy, -expected[i].CGPointValue.y);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
- (void)testPortraitVisibleFrameRotation
|
|
182
|
+
{
|
|
183
|
+
CGSize size = CGSizeMake(669, 951);
|
|
184
|
+
CGRect clipped = CGRectMake(200, 100, 300, 400);
|
|
185
|
+
XCTAssertTrue(CGRectEqualToRect(FBRectFromPortraitCoordinates(clipped, size, UIInterfaceOrientationPortrait), clipped));
|
|
186
|
+
XCTAssertTrue(CGRectEqualToRect(FBRectFromPortraitCoordinates(clipped, size, UIInterfaceOrientationLandscapeLeft), CGRectMake(451, 200, 400, 300)));
|
|
187
|
+
XCTAssertTrue(CGRectEqualToRect(FBRectFromPortraitCoordinates(clipped, size, UIInterfaceOrientationLandscapeRight), CGRectMake(100, 169, 400, 300)));
|
|
188
|
+
XCTAssertTrue(CGRectEqualToRect(FBRectFromPortraitCoordinates(clipped, size, UIInterfaceOrientationPortraitUpsideDown), CGRectMake(169, 451, 300, 400)));
|
|
189
|
+
// Observed on Duo's inner landscape display: keep the clipping in the same
|
|
190
|
+
// coordinate space as the element's reported frame.
|
|
191
|
+
XCTAssertTrue(CGRectEqualToRect(FBRectFromPortraitCoordinates(CGRectMake(236, 104, 379, 827), size, UIInterfaceOrientationLandscapeLeft), CGRectMake(20, 236, 827, 379)));
|
|
192
|
+
XCTAssertTrue(CGRectIsEmpty(FBRectFromPortraitCoordinates(CGRectZero, size, UIInterfaceOrientationLandscapeLeft)));
|
|
193
|
+
XCTAssertTrue(CGRectIsNull(FBRectFromPortraitCoordinates(CGRectNull, size, UIInterfaceOrientationLandscapeLeft)));
|
|
194
|
+
}
|
|
195
|
+
|
|
151
196
|
@end
|