appium-webdriveragent 16.12.11 → 16.13.1

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.h +53 -0
  3. package/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m +124 -0
  4. package/WebDriverAgentLib/Commands/FBElementCommands.m +20 -5
  5. package/WebDriverAgentLib/Info.plist +2 -2
  6. package/WebDriverAgentLib/Routing/FBHTTPServer.m +14 -1
  7. package/WebDriverAgentLib/Routing/FBTCPSocket.h +5 -1
  8. package/WebDriverAgentLib/Routing/FBTCPSocket.m +10 -1
  9. package/WebDriverAgentLib/Utilities/FBConfiguration.h +6 -0
  10. package/WebDriverAgentLib/Utilities/FBConfiguration.m +1 -0
  11. package/WebDriverAgentLib/Utilities/FBImageProcessor.m +3 -1
  12. package/WebDriverAgentLib/Utilities/FBMjpegServer.m +2 -0
  13. package/WebDriverAgentLib/Utilities/FBScreen.h +17 -0
  14. package/WebDriverAgentLib/Utilities/FBScreen.m +32 -0
  15. package/WebDriverAgentLib/Utilities/FBScreenshot.h +2 -2
  16. package/WebDriverAgentLib/Utilities/FBScreenshot.m +6 -2
  17. package/WebDriverAgentLib/Utilities/FBSettings.h +1 -0
  18. package/WebDriverAgentLib/Utilities/FBSettings.m +1 -0
  19. package/WebDriverAgentLib/Utilities/FBSettingsHandler.m +24 -0
  20. package/WebDriverAgentLib/Utilities/FBXPath.m +36 -4
  21. package/WebDriverAgentLib/Utilities/LRUCache/LRUCache.m +8 -5
  22. package/WebDriverAgentLib/Utilities/LRUCache/LRUCacheNode.h +1 -1
  23. package/WebDriverAgentTests/IntegrationTests/FBElementSwipingTests.m +95 -0
  24. package/WebDriverAgentTests/IntegrationTests/FBImageProcessorTests.m +34 -0
  25. package/WebDriverAgentTests/IntegrationTests/FBScreenTests.m +67 -0
  26. package/WebDriverAgentTests/UnitTests/FBHTTPServerTests.m +149 -5
  27. package/WebDriverAgentTests/UnitTests/FBLRUCacheTests.m +34 -0
  28. package/WebDriverAgentTests/UnitTests/FBXPathTests.m +36 -0
  29. package/build/lib/xcodebuild.d.ts +0 -1
  30. package/build/lib/xcodebuild.d.ts.map +1 -1
  31. package/build/lib/xcodebuild.js +7 -7
  32. package/build/lib/xcodebuild.js.map +1 -1
  33. package/lib/xcodebuild.ts +7 -7
  34. package/package.json +1 -1
@@ -504,6 +504,18 @@ static NSString *const topNodeIndexPath = @"top";
504
504
  indexPath:(nullable NSString *)indexPath
505
505
  includedAttributes:(nullable NSSet<Class> *)includedAttributes
506
506
  {
507
+ return [self recordElementAttributes:writer forElement:element indexPath:indexPath
508
+ includedAttributes:includedAttributes siblingIndex:nil];
509
+ }
510
+
511
+ + (int)recordElementAttributes:(xmlTextWriterPtr)writer
512
+ forElement:(id<FBXCElementSnapshot>)element
513
+ indexPath:(nullable NSString *)indexPath
514
+ includedAttributes:(nullable NSSet<Class> *)includedAttributes
515
+ siblingIndex:(nullable NSNumber *)siblingIndex
516
+ {
517
+ FBXCElementSnapshotWrapper *wrapped = [FBXCElementSnapshotWrapper ensureWrapped:element];
518
+ NSDictionary *rect = nil;
507
519
  for (Class attributeCls in FBElementAttribute.supportedAttributes) {
508
520
  // include all supported attributes by default unless enumerated explicitly
509
521
  if (includedAttributes && ![includedAttributes containsObject:attributeCls]) {
@@ -519,8 +531,15 @@ static NSString *const topNodeIndexPath = @"top";
519
531
  !FBDoesElementSupportMinMaxValue(element.elementType)) {
520
532
  continue;
521
533
  }
522
- int rc = [attributeCls recordWithWriter:writer
523
- forElement:[FBXCElementSnapshotWrapper ensureWrapped:element]];
534
+ int rc;
535
+ if (attributeCls == FBIndexAttribute.class && nil != siblingIndex) {
536
+ rc = [attributeCls recordWithWriter:writer forValue:siblingIndex.stringValue];
537
+ } else if ([attributeCls isSubclassOfClass:FBDimensionAttribute.class]) {
538
+ rect = rect ?: wrapped.wdRect;
539
+ rc = [attributeCls recordWithWriter:writer forValue:[rect[[attributeCls name]] description]];
540
+ } else {
541
+ rc = [attributeCls recordWithWriter:writer forElement:wrapped];
542
+ }
524
543
  if (rc < 0) {
525
544
  return rc;
526
545
  }
@@ -559,6 +578,17 @@ static NSString *const topNodeIndexPath = @"top";
559
578
  elementStore:(nullable NSMutableDictionary *)elementStore
560
579
  includedAttributes:(nullable NSSet<Class> *)includedAttributes
561
580
  writer:(xmlTextWriterPtr)writer
581
+ {
582
+ return [self writeXmlWithRootElement:root indexPath:indexPath elementStore:elementStore
583
+ includedAttributes:includedAttributes writer:writer siblingIndex:nil];
584
+ }
585
+
586
+ + (int)writeXmlWithRootElement:(id<FBXCElementSnapshot>)root
587
+ indexPath:(nullable NSString *)indexPath
588
+ elementStore:(nullable NSMutableDictionary *)elementStore
589
+ includedAttributes:(nullable NSSet<Class> *)includedAttributes
590
+ writer:(xmlTextWriterPtr)writer
591
+ siblingIndex:(nullable NSNumber *)siblingIndex
562
592
  {
563
593
  NSAssert((indexPath == nil && elementStore == nil) || (indexPath != nil && elementStore != nil), @"Either both or none of indexPath and elementStore arguments should be equal to nil", nil);
564
594
 
@@ -578,7 +608,8 @@ static NSString *const topNodeIndexPath = @"top";
578
608
  rc = [self recordElementAttributes:writer
579
609
  forElement:root
580
610
  indexPath:indexPath
581
- includedAttributes:includedAttributes];
611
+ includedAttributes:includedAttributes
612
+ siblingIndex:siblingIndex];
582
613
  if (rc < 0) {
583
614
  return rc;
584
615
  }
@@ -594,7 +625,8 @@ static NSString *const topNodeIndexPath = @"top";
594
625
  indexPath:newIndexPath
595
626
  elementStore:elementStore
596
627
  includedAttributes:includedAttributes
597
- writer:writer];
628
+ writer:writer
629
+ siblingIndex:@(i)];
598
630
  if (rc < 0) {
599
631
  return rc;
600
632
  }
@@ -80,7 +80,7 @@
80
80
  }
81
81
  LRUCacheNode *nextNode = node.next;
82
82
  if (nil != nextNode) {
83
- nextNode.prev = node.prev;
83
+ nextNode.prev = previousNode;
84
84
  }
85
85
  if (node == self.tailNode) {
86
86
  self.tailNode = previousNode;
@@ -94,19 +94,22 @@
94
94
  return;
95
95
  }
96
96
 
97
+ LRUCacheNode *previousNode = node.prev;
97
98
  if (nil != node.next) {
98
- node.next.prev = node.prev;
99
+ node.next.prev = previousNode;
99
100
  }
100
101
  if (node == self.headNode) {
101
102
  self.headNode = node.next;
102
103
  }
103
- if (nil != node.prev) {
104
- node.prev.next = node.next;
104
+ if (nil != previousNode) {
105
+ previousNode.next = node.next;
105
106
  }
106
107
  if (node == self.tailNode) {
107
- self.tailNode = node.prev;
108
+ self.tailNode = previousNode;
108
109
  }
109
110
  [self.store removeObjectForKey:(id)node.key];
111
+ node.next = nil;
112
+ node.prev = nil;
110
113
  }
111
114
 
112
115
  - (nullable LRUCacheNode *)addNodeToHead:(nullable LRUCacheNode *)node
@@ -27,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN
27
27
  /*! Pointer to the next node */
28
28
  @property (nonatomic, nullable) LRUCacheNode *next;
29
29
  /*! Pointer to the previous node */
30
- @property (nonatomic, nullable) LRUCacheNode *prev;
30
+ @property (nonatomic, weak, nullable) LRUCacheNode *prev;
31
31
 
32
32
  /**
33
33
  Factory method to create a new cache node with the given value and key
@@ -13,6 +13,10 @@
13
13
  #import "XCUIElement+FBWebDriverAttributes.h"
14
14
  #import "FBXCodeCompatibility.h"
15
15
  #import "XCUIElement+FBSwiping.h"
16
+ #import "FBExceptions.h"
17
+ #import "XCUIApplication+FBTouchAction.h"
18
+ #import "XCSynthesizedEventRecord.h"
19
+ #import "XCUICoordinate.h"
16
20
 
17
21
  @interface FBElementSwipingTests : FBIntegrationTestCase
18
22
  @property (nonatomic, strong) XCUIElement *scrollView;
@@ -125,3 +129,94 @@
125
129
  }
126
130
 
127
131
  @end
132
+
133
+ /**
134
+ Stands in for a coordinate whose application did not deliver a usable snapshot (as happens for a
135
+ busy application right after an app switch), which XCTest resolves to an infinite screen point.
136
+ */
137
+ @interface FBUnresolvableCoordinate : XCUICoordinate
138
+ @end
139
+
140
+ @implementation FBUnresolvableCoordinate
141
+
142
+ - (CGPoint)screenPoint
143
+ {
144
+ return CGPointMake(INFINITY, INFINITY);
145
+ }
146
+
147
+ @end
148
+
149
+ @interface FBPressAndDragTests : FBIntegrationTestCase
150
+ @end
151
+
152
+ @implementation FBPressAndDragTests
153
+
154
+ - (void)setUp
155
+ {
156
+ [super setUp];
157
+ [self resetOrientation];
158
+ [self launchApplication];
159
+ [self goToScrollPageWithCells:YES];
160
+ }
161
+
162
+ - (void)testPressAndDragBetweenApplicationCoordinates
163
+ {
164
+ XCUICoordinate *start = [self.testedApplication coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.7)];
165
+ XCUICoordinate *end = [self.testedApplication coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.3)];
166
+ NSError *error;
167
+ XCTAssertTrue([self.testedApplication fb_pressAtCoordinate:start
168
+ forDuration:0.3
169
+ thenDragToCoordinate:end
170
+ withVelocity:XCUIGestureVelocityDefault
171
+ thenHoldForDuration:0
172
+ error:&error]);
173
+ XCTAssertNil(error);
174
+ FBAssertInvisibleCell(@"0");
175
+ }
176
+
177
+ // https://github.com/appium/WebDriverAgent/issues/1056
178
+ - (void)testPressAndDragFailsCleanlyForUnresolvableCoordinates
179
+ {
180
+ XCUICoordinate *start = [[FBUnresolvableCoordinate alloc] initWithElement:self.testedApplication
181
+ normalizedOffset:CGVectorMake(0.5, 0.7)];
182
+ XCUICoordinate *end = [self.testedApplication coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.3)];
183
+ NSError *error;
184
+ XCTAssertFalse([self.testedApplication fb_pressAtCoordinate:start
185
+ forDuration:0.3
186
+ thenDragToCoordinate:end
187
+ withVelocity:XCUIGestureVelocityDefault
188
+ thenHoldForDuration:0
189
+ error:&error]);
190
+ XCTAssertTrue([error.localizedDescription containsString:@"Cannot resolve the on-screen position"]);
191
+ FBAssertVisibleCell(@"0");
192
+ }
193
+
194
+ - (void)testPressDragEventKeepsDisplayIDAndTiming
195
+ {
196
+ XCSynthesizedEventRecord *event = [XCUIApplication fb_pressDragEventFromPoint:CGPointMake(100, 500)
197
+ forDuration:0.3
198
+ toPoint:CGPointMake(100, 100)
199
+ pointsPerSecond:500
200
+ thenHoldForDuration:0.1
201
+ displayID:42
202
+ interfaceOrientation:UIInterfaceOrientationPortrait];
203
+ XCTAssertEqual(event.displayID, 42ULL);
204
+ XCTAssertEqual(event.eventPaths.count, 1U);
205
+ // 0.3 s press + 400 pt / 500 pt per s drag + 0.1 s hold
206
+ XCTAssertEqualWithAccuracy(event.maximumOffset, 1.2, 0.0001);
207
+ }
208
+
209
+ - (void)testPressAndDragRejectsInvalidVelocity
210
+ {
211
+ XCUICoordinate *start = [self.testedApplication coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.7)];
212
+ XCUICoordinate *end = [self.testedApplication coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.3)];
213
+ XCTAssertThrowsSpecificNamed([self.testedApplication fb_pressAtCoordinate:start
214
+ forDuration:0
215
+ thenDragToCoordinate:end
216
+ withVelocity:0
217
+ thenHoldForDuration:0
218
+ error:nil],
219
+ NSException, FBInvalidArgumentException);
220
+ }
221
+
222
+ @end
@@ -9,6 +9,7 @@
9
9
  #import <XCTest/XCTest.h>
10
10
 
11
11
  #import "FBImageProcessor.h"
12
+ @import UniformTypeIdentifiers;
12
13
  #import "FBIntegrationTestCase.h"
13
14
 
14
15
 
@@ -80,3 +81,36 @@
80
81
 
81
82
  @end
82
83
 
84
+
85
+
86
+ // Synthetic images keep these renderer regressions independent of the app's screen.
87
+ @interface FBImageRendererScalingTests : XCTestCase
88
+ @end
89
+
90
+ @implementation FBImageRendererScalingTests
91
+
92
+ - (void)testOrientationFixDoesNotApplyScalingTwice
93
+ {
94
+ for (NSNumber *sourceScale in @[@1, @2, @3]) {
95
+ UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat new];
96
+ format.scale = sourceScale.doubleValue;
97
+ UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc]
98
+ initWithSize:CGSizeMake(120, 80) format:format];
99
+ NSData *data = [renderer PNGDataWithActions:^(UIGraphicsImageRendererContext *context) {
100
+ [UIColor.redColor setFill];
101
+ [context fillRect:CGRectMake(0, 0, 120, 80)];
102
+ }];
103
+ FBImageProcessor *processor = [FBImageProcessor new];
104
+ NSData *scaled = [processor scaledImageWithData:data
105
+ uti:UTTypePNG
106
+ scalingFactor:0.5
107
+ compressionQuality:1.0
108
+ error:nil];
109
+ UIImage *image = [UIImage imageWithData:scaled];
110
+ XCTAssertNotNil(image);
111
+ XCTAssertEqual(CGImageGetWidth(image.CGImage), (size_t)(60 * sourceScale.integerValue));
112
+ XCTAssertEqual(CGImageGetHeight(image.CGImage), (size_t)(40 * sourceScale.integerValue));
113
+ }
114
+ }
115
+
116
+ @end
@@ -9,7 +9,10 @@
9
9
  #import <XCTest/XCTest.h>
10
10
 
11
11
  #import "FBIntegrationTestCase.h"
12
+ #import "FBConfiguration.h"
12
13
  #import "FBScreen.h"
14
+ #import "FBScreenshot.h"
15
+ #import "XCUIScreen.h"
13
16
 
14
17
  @interface FBScreenTests : FBIntegrationTestCase
15
18
  @end
@@ -22,6 +25,12 @@
22
25
  [self launchApplication];
23
26
  }
24
27
 
28
+ - (void)tearDown
29
+ {
30
+ FBConfiguration.sharedInstance.currentDisplayId = nil;
31
+ [super tearDown];
32
+ }
33
+
25
34
  - (void)testDisplayID
26
35
  {
27
36
  XCTAssertGreaterThanOrEqual([FBScreen displayID], 0LL);
@@ -50,6 +59,64 @@
50
59
  XCTAssertNotNil(mainScreen[@"traits"]);
51
60
  }
52
61
 
62
+ - (void)testScreenWithDisplayID
63
+ {
64
+ NSError *error = nil;
65
+ XCUIScreen *screen = [FBScreen screenWithDisplayID:[FBScreen displayID] error:&error];
66
+ XCTAssertNil(error);
67
+ XCTAssertEqual(screen.displayID, [FBScreen displayID]);
68
+
69
+ XCTAssertNil([FBScreen screenWithDisplayID:[self unknownDisplayID] error:&error]);
70
+ XCTAssertNotNil(error);
71
+ NSMutableArray<NSNumber *> *availableIDs = [NSMutableArray array];
72
+ for (NSDictionary<NSString *, id> *availableScreen in [FBScreen screensWithError:nil]) {
73
+ [availableIDs addObject:availableScreen[@"displayId"]];
74
+ }
75
+ NSString *availableDisplays = [NSString stringWithFormat:@"Available display ids: [%@]",
76
+ [availableIDs componentsJoinedByString:@", "]];
77
+ XCTAssertTrue([error.localizedDescription containsString:availableDisplays]);
78
+ }
79
+
80
+ - (void)testCurrentScreenDefaultsToMainScreen
81
+ {
82
+ NSError *error = nil;
83
+ XCTAssertTrue([FBScreen currentScreenWithError:&error].isMainScreen);
84
+ XCTAssertNil(error);
85
+ }
86
+
87
+ - (void)testCurrentScreenFollowsSetting
88
+ {
89
+ FBConfiguration.sharedInstance.currentDisplayId = @([FBScreen displayID]);
90
+ NSError *error = nil;
91
+ XCTAssertEqual([FBScreen currentScreenWithError:&error].displayID, [FBScreen displayID]);
92
+ XCTAssertNil(error);
93
+ XCTAssertNotNil([FBScreenshot takeInOriginalResolutionWithQuality:0 error:&error]);
94
+ XCTAssertNil(error);
95
+
96
+ FBConfiguration.sharedInstance.currentDisplayId = @([self unknownDisplayID]);
97
+ XCTAssertNil([FBScreen currentScreenWithError:&error]);
98
+ XCTAssertNotNil(error);
99
+ error = nil;
100
+ XCTAssertNil([FBScreenshot takeInOriginalResolutionWithQuality:0 error:&error]);
101
+ XCTAssertNotNil(error);
102
+ }
103
+
104
+ - (void)testSessionResetClearsCurrentDisplay
105
+ {
106
+ FBConfiguration.sharedInstance.currentDisplayId = @([FBScreen displayID]);
107
+ [FBConfiguration.sharedInstance resetSessionSettings];
108
+ XCTAssertNil(FBConfiguration.sharedInstance.currentDisplayId);
109
+ }
110
+
111
+ - (long long)unknownDisplayID
112
+ {
113
+ long long maxID = 0;
114
+ for (NSDictionary<NSString *, id> *screen in [FBScreen screensWithError:nil]) {
115
+ maxID = MAX(maxID, [screen[@"displayId"] longLongValue]);
116
+ }
117
+ return maxID + 1;
118
+ }
119
+
53
120
  - (void)testScreenScale
54
121
  {
55
122
  XCTAssertTrue([FBScreen scale] >= 2);
@@ -17,11 +17,35 @@
17
17
 
18
18
  static atomic_int gFramingProbeHits;
19
19
 
20
+ // The response in `data` once all of it has arrived - its header block and Content-Length body -
21
+ // or nil while more is due.
22
+ static NSString *FBCompleteResponse(NSData *data)
23
+ {
24
+ NSData *separator = (NSData * _Nonnull)[@"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
25
+ NSRange headerEnd = [data rangeOfData:separator options:(NSDataSearchOptions)0 range:NSMakeRange(0, data.length)];
26
+ if (NSNotFound == headerEnd.location) {
27
+ return nil;
28
+ }
29
+ NSString *headers = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, headerEnd.location)] encoding:NSUTF8StringEncoding];
30
+ NSUInteger contentLength = 0;
31
+ for (NSString *line in [headers componentsSeparatedByString:@"\r\n"]) {
32
+ NSArray<NSString *> *field = [line componentsSeparatedByString:@":"];
33
+ if (2 == field.count && NSOrderedSame == [field[0] caseInsensitiveCompare:@"Content-Length"]) {
34
+ contentLength = (NSUInteger)[field[1] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet].integerValue;
35
+ }
36
+ }
37
+ if (data.length < NSMaxRange(headerEnd) + contentLength) {
38
+ return nil;
39
+ }
40
+ return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
41
+ }
42
+
20
43
  // Exercises FBHTTPServer's HTTP framing defenses with raw socket data that URL-loading APIs
21
44
  // cannot produce: malformed Content-Length values and header blocks that never terminate.
22
45
  @interface FBHTTPServerTests : XCTestCase
23
46
  @property (nonatomic, strong) FBHTTPServer *server;
24
47
  @property (nonatomic, assign) uint16_t port;
48
+ @property (nonatomic, strong) dispatch_semaphore_t slowRouteStarted;
25
49
  @end
26
50
 
27
51
  @implementation FBHTTPServerTests
@@ -38,6 +62,13 @@ static atomic_int gFramingProbeHits;
38
62
  [self.server get:@"/framing/ping" withBlock:^(RouteRequest *request, RouteResponse *response) {
39
63
  [response respondWithString:@"pong"];
40
64
  }];
65
+ dispatch_semaphore_t slowRouteStarted = dispatch_semaphore_create(0);
66
+ self.slowRouteStarted = slowRouteStarted;
67
+ [self.server get:@"/framing/slow" withBlock:^(RouteRequest *request, RouteResponse *response) {
68
+ dispatch_semaphore_signal(slowRouteStarted);
69
+ [NSThread sleepForTimeInterval:0.5];
70
+ [response respondWithString:@"slow-ok"];
71
+ }];
41
72
  self.server.port = 0;
42
73
  NSError *error;
43
74
  XCTAssertTrue([self.server start:&error], @"%@", error);
@@ -51,14 +82,12 @@ static atomic_int gFramingProbeHits;
51
82
  [super tearDown];
52
83
  }
53
84
 
54
- // Sends `payload` as-is and reads until the server closes the connection or `timeout` elapses.
55
- // Returns everything received (nil on connect failure); *didClose reports whether EOF was seen.
56
- - (NSString *)responseForRawPayload:(NSData *)payload timeout:(NSTimeInterval)timeout didClose:(BOOL *)didClose
85
+ // A socket connected to the server that gives up reading after `timeout`, or -1.
86
+ - (int)connectedSocketWithTimeout:(NSTimeInterval)timeout
57
87
  {
58
- *didClose = NO;
59
88
  int fd = socket(AF_INET, SOCK_STREAM, 0);
60
89
  if (fd < 0) {
61
- return nil;
90
+ return -1;
62
91
  }
63
92
  int noSigpipe = 1;
64
93
  setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &noSigpipe, sizeof(noSigpipe));
@@ -68,6 +97,84 @@ static atomic_int gFramingProbeHits;
68
97
  addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
69
98
  if (0 != connect(fd, (struct sockaddr *)&addr, sizeof(addr))) {
70
99
  close(fd);
100
+ return -1;
101
+ }
102
+ return fd;
103
+ }
104
+
105
+ // Descriptors in this process bound to the server's port: its listener plus one per server-side
106
+ // connection still open. Client sockets are bound to ephemeral ports, so they never count.
107
+ - (NSInteger)serverSocketCount
108
+ {
109
+ NSInteger count = 0;
110
+ int limit = getdtablesize();
111
+ for (int fd = 0; fd < limit; fd++) {
112
+ struct sockaddr_storage addr;
113
+ socklen_t length = sizeof(addr);
114
+ if (0 != getsockname(fd, (struct sockaddr *)&addr, &length)) {
115
+ continue;
116
+ }
117
+ in_port_t port = 0;
118
+ if (AF_INET == addr.ss_family) {
119
+ port = ((struct sockaddr_in *)&addr)->sin_port;
120
+ } else if (AF_INET6 == addr.ss_family) {
121
+ port = ((struct sockaddr_in6 *)&addr)->sin6_port;
122
+ }
123
+ if (ntohs(port) == self.port) {
124
+ count++;
125
+ }
126
+ }
127
+ return count;
128
+ }
129
+
130
+ // The server learns about hang-ups asynchronously: polls until it holds `expected` sockets or
131
+ // 5 seconds pass, and returns the last count.
132
+ - (NSInteger)serverSocketCountSettlingAt:(NSInteger)expected
133
+ {
134
+ NSInteger count = [self serverSocketCount];
135
+ NSDate *deadline = [NSDate dateWithTimeIntervalSinceNow:5.0];
136
+ while (count != expected && deadline.timeIntervalSinceNow > 0) {
137
+ [NSThread sleepForTimeInterval:0.05];
138
+ count = [self serverSocketCount];
139
+ }
140
+ return count;
141
+ }
142
+
143
+ // Sends `request` on a new connection, reads its complete response and then hangs up first, the
144
+ // way curl or iproxy end a keep-alive exchange. Returns nil unless the server answered and still
145
+ // held its side open when the client hung up.
146
+ - (NSString *)responseClosingFirstForRequest:(NSString *)request
147
+ {
148
+ int fd = [self connectedSocketWithTimeout:5.0];
149
+ if (fd < 0) {
150
+ return nil;
151
+ }
152
+ NSData *payload = (NSData * _Nonnull)[request dataUsingEncoding:NSUTF8StringEncoding];
153
+ send(fd, payload.bytes, payload.length, 0);
154
+ NSMutableData *received = [NSMutableData data];
155
+ NSString *response = nil;
156
+ char chunk[4096];
157
+ while (nil == response) {
158
+ ssize_t n = recv(fd, chunk, sizeof(chunk), 0);
159
+ if (n <= 0) {
160
+ break;
161
+ }
162
+ [received appendBytes:chunk length:(NSUInteger)n];
163
+ response = FBCompleteResponse(received);
164
+ }
165
+ char probe;
166
+ BOOL isStillOpen = -1 == recv(fd, &probe, 1, MSG_PEEK | MSG_DONTWAIT) && EAGAIN == errno;
167
+ close(fd);
168
+ return isStillOpen ? response : nil;
169
+ }
170
+
171
+ // Sends `payload` as-is and reads until the server closes the connection or `timeout` elapses.
172
+ // Returns everything received (nil on connect failure); *didClose reports whether EOF was seen.
173
+ - (NSString *)responseForRawPayload:(NSData *)payload timeout:(NSTimeInterval)timeout didClose:(BOOL *)didClose
174
+ {
175
+ *didClose = NO;
176
+ int fd = [self connectedSocketWithTimeout:timeout];
177
+ if (fd < 0) {
71
178
  return nil;
72
179
  }
73
180
  // send(2) may write only part of the payload, which would truncate the multi-KiB flood
@@ -122,6 +229,43 @@ static atomic_int gFramingProbeHits;
122
229
  XCTAssertTrue([response containsString:@"pong"], @"%@", response);
123
230
  }
124
231
 
232
+ - (void)testConnectionsTheClientClosesReleaseTheirSockets
233
+ {
234
+ // The server keeps a connection open after answering, so the client is the one that hangs up.
235
+ // Each such connection must give its server-side socket back, or a long-running server
236
+ // eventually fails every request with EMFILE.
237
+ NSInteger baseline = [self serverSocketCount];
238
+ for (int i = 0; i < 50; i++) {
239
+ NSString *response = [self responseClosingFirstForRequest:@"GET /framing/ping HTTP/1.1\r\n\r\n"];
240
+ XCTAssertTrue([response containsString:@"pong"], @"%@", response);
241
+ }
242
+ XCTAssertEqual([self serverSocketCountSettlingAt:baseline], baseline);
243
+ }
244
+
245
+ - (void)testHalfClosedClientGetsItsResponseAndIsThenClosed
246
+ {
247
+ // A client may shut down its sending side right after the request and wait for the response.
248
+ // Its end-of-stream must not cost it that response, and the server must close afterwards.
249
+ NSInteger baseline = [self serverSocketCount];
250
+ int fd = [self connectedSocketWithTimeout:5.0];
251
+ XCTAssertGreaterThanOrEqual(fd, 0);
252
+ NSData *payload = (NSData * _Nonnull)[@"GET /framing/slow HTTP/1.1\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
253
+ XCTAssertEqual(send(fd, payload.bytes, payload.length, 0), (ssize_t)payload.length);
254
+ XCTAssertEqual(dispatch_semaphore_wait(self.slowRouteStarted, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC))), 0L);
255
+ XCTAssertEqual(shutdown(fd, SHUT_WR), 0);
256
+ NSMutableData *received = [NSMutableData data];
257
+ char chunk[4096];
258
+ ssize_t n;
259
+ while ((n = recv(fd, chunk, sizeof(chunk), 0)) > 0) {
260
+ [received appendBytes:chunk length:(NSUInteger)n];
261
+ }
262
+ close(fd);
263
+ NSString *response = [[NSString alloc] initWithData:received encoding:NSUTF8StringEncoding];
264
+ XCTAssertTrue([response containsString:@"slow-ok"], @"%@", response);
265
+ XCTAssertEqual(n, (ssize_t)0, @"the server must close the connection once it has answered");
266
+ XCTAssertEqual([self serverSocketCountSettlingAt:baseline], baseline);
267
+ }
268
+
125
269
  - (void)testNonNumericContentLengthIsRejected
126
270
  {
127
271
  // 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);
@@ -16,11 +16,47 @@
16
16
  #import "FBXCElementSnapshotWrapper+Helpers.h"
17
17
  #import "XCTestPrivateSymbols.h"
18
18
 
19
+ @interface FBTraversalSnapshot : XCElementSnapshotDouble
20
+ @property (nonatomic, copy) NSArray *childSnapshots;
21
+ @property (nonatomic, weak) FBTraversalSnapshot *parentSnapshot;
22
+ @property (nonatomic) NSUInteger childrenReads;
23
+ @property (nonatomic) NSUInteger frameReads;
24
+ @end
25
+ @implementation FBTraversalSnapshot
26
+ - (NSArray *)children { self.childrenReads++; return self.childSnapshots ?: @[]; }
27
+ - (id)parent { return self.parentSnapshot; }
28
+ - (CGRect)frame { self.frameReads++; return CGRectMake(1, 2, 3, 4); }
29
+ @end
30
+
19
31
  @interface FBXPathTests : XCTestCase
20
32
  @end
21
33
 
22
34
  @implementation FBXPathTests
23
35
 
36
+ - (void)testSerializationVisitsSiblingListAndFrameOnce
37
+ {
38
+ FBTraversalSnapshot *root = [FBTraversalSnapshot new];
39
+ NSMutableArray *children = [NSMutableArray array];
40
+ for (NSUInteger index = 0; index < 64; index++) {
41
+ FBTraversalSnapshot *child = [FBTraversalSnapshot new];
42
+ child.parentSnapshot = root;
43
+ [children addObject:child];
44
+ }
45
+ root.childSnapshots = children;
46
+ NSString *query = @"//*[@index >= 0 and @x=1 and @y=2 and @width=3 and @height=4]";
47
+ xmlDocPtr doc = [self documentForSnapshot:root query:query];
48
+ @try {
49
+ XCTAssertEqual(root.childrenReads, 1u);
50
+ XCTAssertEqual(root.frameReads, 1u);
51
+ for (FBTraversalSnapshot *child in children) {
52
+ XCTAssertEqual(child.frameReads, 1u);
53
+ }
54
+ XCTAssertEqualObjects([self xpathStringResultForQuery:@"count(//*[@index=63])" document:doc], @"1");
55
+ } @finally {
56
+ xmlFreeDoc(doc);
57
+ }
58
+ }
59
+
24
60
  - (NSString *)xmlStringWithElement:(id<FBXCElementSnapshot>)snapshot
25
61
  xpathQuery:(nullable NSString *)query
26
62
  excludingAttributes:(nullable NSArray<NSString *> *)excludedAttributes
@@ -33,7 +33,6 @@ export declare class XcodeBuild {
33
33
  private readonly resultBundlePath?;
34
34
  private readonly resultBundleVersion?;
35
35
  private _didBuildFail;
36
- private _didProcessExit;
37
36
  private readonly _buildSettingsPromises;
38
37
  private noSessionProxy?;
39
38
  private xctestrunFilePath?;
@@ -1 +1 @@
1
- {"version":3,"file":"xcodebuild.d.ts","sourceRoot":"","sources":["../../lib/xcodebuild.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"xcodebuild.d.ts","sourceRoot":"","sources":["../../lib/xcodebuild.ts"],"names":[],"mappings":"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"}