592 lines
26 KiB
Objective-C
592 lines
26 KiB
Objective-C
//
|
||
// HttpdnsNWHTTPClient_StateMachineTests.m
|
||
// TrustHttpDNSTests
|
||
//
|
||
// @author Created by Claude Code on 2025-11-01
|
||
// Copyright © 2025 trustapp.com. All rights reserved.
|
||
//
|
||
// 状æ€<C3A6>机测试 - 包å<E280A6>«çжæ€<C3A6>机与异常场æ™?(Q) 测试ç»?
|
||
// 测试总数�7 个(Q:17�
|
||
//
|
||
|
||
#import "HttpdnsNWHTTPClientTestBase.h"
|
||
|
||
@interface HttpdnsNWHTTPClient_StateMachineTests : HttpdnsNWHTTPClientTestBase
|
||
|
||
@end
|
||
|
||
@implementation HttpdnsNWHTTPClient_StateMachineTests
|
||
|
||
#pragma mark - Q. 状æ€<C3A6>机与异常场景测è¯?
|
||
|
||
// Q1.1 æ± æº¢å‡ºæ—¶LRU移除ç–略验è¯<C3A8>
|
||
- (void)testStateMachine_PoolOverflowLRU_RemovesOldestByLastUsedDate {
|
||
[self.client resetPoolStatistics];
|
||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||
|
||
// 需è¦<C3A8>å¹¶å<C2B6>‘创å»?个连接(串行请求会å¤<C3A5>用)
|
||
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
|
||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||
|
||
// å¹¶å<C2B6>‘å<E28098>‘èµ·5个请æ±?
|
||
for (NSInteger i = 0; i < 5; i++) {
|
||
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Request %ld", (long)i]];
|
||
[expectations addObject:expectation];
|
||
|
||
dispatch_async(queue, ^{
|
||
NSError *error = nil;
|
||
// 使用 /delay/2 ç¡®ä¿<C3A4>所有请求å<E2809A>Œæ—¶åœ¨é£žè¡Œä¸ï¼Œå¼ºåˆ¶åˆ›å»ºå¤šä¸ªè¿žæŽ¥
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/2"
|
||
userAgent:[NSString stringWithFormat:@"Request%ld", (long)i]
|
||
timeout:15.0
|
||
error:&error];
|
||
[expectation fulfill];
|
||
});
|
||
[NSThread sleepForTimeInterval:0.05]; // å°<C3A5>é—´éš”é<E2809D>¿å…<C3A5>完全å<C2A8>Œæ—¶å<C2B6>¯åŠ?
|
||
}
|
||
|
||
[self waitForExpectations:expectations timeout:20.0];
|
||
|
||
// ç‰å¾…所有连接归è¿?
|
||
[NSThread sleepForTimeInterval:1.0];
|
||
|
||
// 验è¯<C3A8>ï¼šæ± å¤§å°<C3A5> â‰?4(LRU移除溢出部分ï¼?
|
||
NSUInteger poolCount = [self.client connectionPoolCountForKey:poolKey];
|
||
XCTAssertLessThanOrEqual(poolCount, 4,
|
||
@"Pool should enforce max 4 connections (LRU)");
|
||
|
||
// 验è¯<C3A8>:创建了多个连接
|
||
XCTAssertGreaterThanOrEqual(self.client.connectionCreationCount, 3,
|
||
@"Should create multiple concurrent connections");
|
||
}
|
||
|
||
// Q2.1 快速连ç»è¯·æ±‚ä¸<C3A4>产生é‡<C3A9>å¤<C3A5>连接(间接验è¯<C3A8>å<EFBFBD>Œé‡<C3A9>归还防护)
|
||
- (void)testAbnormal_RapidSequentialRequests_NoDuplicates {
|
||
[self.client resetPoolStatistics];
|
||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||
|
||
// 快速连ç»å<C2AD>‘èµ?0ä¸ªè¯·æ±‚ï¼ˆæµ‹è¯•è¿žæŽ¥å½’è¿˜çš„å¹‚ç‰æ€§ï¼‰
|
||
for (NSInteger i = 0; i < 10; i++) {
|
||
NSError *error = nil;
|
||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"RapidTest"
|
||
timeout:15.0
|
||
error:&error];
|
||
XCTAssertNotNil(response);
|
||
}
|
||
|
||
// ç‰å¾…连接归还
|
||
[NSThread sleepForTimeInterval:1.0];
|
||
|
||
// 验è¯<C3A8>ï¼šæ± ä¸æœ€å¤?ä¸ªè¿žæŽ¥ï¼ˆå› ä¸ºä¸²è¡Œè¯·æ±‚å¤<C3A5>用å<C2A8>Œä¸€è¿žæŽ¥ï¼?
|
||
NSUInteger poolCount = [self.client connectionPoolCountForKey:poolKey];
|
||
XCTAssertLessThanOrEqual(poolCount, 1,
|
||
@"Pool should have at most 1 connection (rapid sequential reuse)");
|
||
|
||
// 验è¯<C3A8>:创建次数应该是1(所有请求å¤<C3A5>用å<C2A8>Œä¸€è¿žæŽ¥ï¼?
|
||
XCTAssertEqual(self.client.connectionCreationCount, 1,
|
||
@"Should create only 1 connection for sequential requests");
|
||
}
|
||
|
||
// Q2.2 ä¸<C3A4>å<EFBFBD>Œç«¯å<C2AF>£è¯·æ±‚ä¸<C3A4>互相污染æ±
|
||
- (void)testAbnormal_DifferentPorts_IsolatedPools {
|
||
[self.client resetPoolStatistics];
|
||
NSString *poolKey11080 = @"127.0.0.1:11080:tcp";
|
||
NSString *poolKey11443 = @"127.0.0.1:11443:tls";
|
||
|
||
// å<>‘端å<C2AF>?1080å<30>‘起请求
|
||
NSError *error1 = nil;
|
||
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"Port11080"
|
||
timeout:15.0
|
||
error:&error1];
|
||
XCTAssertNotNil(response1);
|
||
|
||
// å<>‘端å<C2AF>?1443å<33>‘起请求
|
||
NSError *error2 = nil;
|
||
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
|
||
userAgent:@"Port11443"
|
||
timeout:15.0
|
||
error:&error2];
|
||
XCTAssertNotNil(response2);
|
||
|
||
// ç‰å¾…连接归还
|
||
[NSThread sleepForTimeInterval:0.5];
|
||
|
||
// 验è¯<C3A8>ï¼šä¸¤ä¸ªæ± å<C2A0>„自æœ?个连æŽ?
|
||
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey11080], 1,
|
||
@"Port 11080 pool should have 1 connection");
|
||
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey11443], 1,
|
||
@"Port 11443 pool should have 1 connection");
|
||
|
||
// 验è¯<C3A8>:总共2ä¸ªè¿žæŽ¥ï¼ˆæ± å®Œå…¨éš”ç¦»ï¼‰
|
||
XCTAssertEqual([self.client totalConnectionCount], 2,
|
||
@"Total should be 2 (one per pool)");
|
||
}
|
||
|
||
// Q3.1 æ± å¤§å°<C3A5>ä¸<C3A4>å<EFBFBD>˜å¼<C3A5>ï¼šä»»ä½•æ—¶å€™æ± å¤§å°<C3A5>都ä¸<C3A4>超过é™<C3A9>制
|
||
- (void)testInvariant_PoolSize_NeverExceedsLimit {
|
||
[self.client resetPoolStatistics];
|
||
|
||
// 快速连ç»å<C2AD>‘èµ?0个请求到å<C2B0>Œä¸€ç«¯ç‚¹
|
||
for (NSInteger i = 0; i < 20; i++) {
|
||
NSError *error = nil;
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"InvariantTest"
|
||
timeout:15.0
|
||
error:&error];
|
||
}
|
||
|
||
// ç‰å¾…所有连接归è¿?
|
||
[NSThread sleepForTimeInterval:1.5];
|
||
|
||
// 验è¯<C3A8>:æ¯<C3A6>ä¸ªæ± çš„å¤§å°<C3A5>ä¸<C3A4>超过4
|
||
NSArray<NSString *> *allKeys = [self.client allConnectionPoolKeys];
|
||
for (NSString *key in allKeys) {
|
||
NSUInteger poolCount = [self.client connectionPoolCountForKey:key];
|
||
XCTAssertLessThanOrEqual(poolCount, 4,
|
||
@"Pool %@ size should never exceed 4 (actual: %lu)",
|
||
key, (unsigned long)poolCount);
|
||
}
|
||
|
||
// 验è¯<C3A8>:总连接数也ä¸<C3A4>超过4ï¼ˆå› ä¸ºå<C2BA>ªæœ‰ä¸€ä¸ªæ± ï¼?
|
||
XCTAssertLessThanOrEqual([self.client totalConnectionCount], 4,
|
||
@"Total connections should not exceed 4");
|
||
}
|
||
|
||
// Q3.3 æ— é‡<C3A9>å¤<C3A5>连接ä¸<C3A4>å<EFBFBD>˜å¼<C3A5>:并å<C2B6>‘请求ä¸<C3A4>产生é‡<C3A9>å¤<C3A5>
|
||
- (void)testInvariant_NoDuplicates_ConcurrentRequests {
|
||
[self.client resetPoolStatistics];
|
||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||
|
||
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
|
||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||
|
||
// å¹¶å<C2B6>‘å<E28098>‘èµ·15个请求(å<CB86>¯èƒ½å¤<C3A5>用连接ï¼?
|
||
for (NSInteger i = 0; i < 15; i++) {
|
||
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Request %ld", (long)i]];
|
||
[expectations addObject:expectation];
|
||
|
||
dispatch_async(queue, ^{
|
||
NSError *error = nil;
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"ConcurrentTest"
|
||
timeout:15.0
|
||
error:&error];
|
||
[expectation fulfill];
|
||
});
|
||
}
|
||
|
||
[self waitForExpectations:expectations timeout:30.0];
|
||
|
||
// ç‰å¾…连接归还
|
||
[NSThread sleepForTimeInterval:1.0];
|
||
|
||
// 验è¯<C3A8>ï¼šæ± å¤§å°<C3A5> â‰?4(ä¸<C3A4>å<EFBFBD>˜å¼<C3A5>ï¼?
|
||
NSUInteger poolCount = [self.client connectionPoolCountForKey:poolKey];
|
||
XCTAssertLessThanOrEqual(poolCount, 4,
|
||
@"Pool should not have duplicates (max 4 connections)");
|
||
|
||
// 验è¯<C3A8>:创建的连接数å<C2B0>ˆç<CB86>†ï¼ˆâ‰?5ï¼Œå› ä¸ºå<C2BA>¯èƒ½æœ‰å¤<C3A5>用ï¼?
|
||
XCTAssertLessThanOrEqual(self.client.connectionCreationCount, 15,
|
||
@"Should not create excessive connections");
|
||
}
|
||
|
||
// Q4.1 边界æ<C592>¡ä»¶ï¼šæ<C5A1>°å¥?0ç§’å<E28099>Žè¿žæŽ¥è¿‡æœŸ
|
||
- (void)testBoundary_Exactly30Seconds_ConnectionExpired {
|
||
if (getenv("SKIP_SLOW_TESTS")) {
|
||
return;
|
||
}
|
||
|
||
[self.client resetPoolStatistics];
|
||
|
||
// 第一个请�
|
||
NSError *error1 = nil;
|
||
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"InitialRequest"
|
||
timeout:15.0
|
||
error:&error1];
|
||
XCTAssertNotNil(response1);
|
||
|
||
// ç‰å¾…æ<E280A6>°å¥½30.5秒(超过30秒过期时间)
|
||
[NSThread sleepForTimeInterval:30.5];
|
||
|
||
// 第二个请求:应该创建新连接(旧连接已过期�
|
||
NSError *error2 = nil;
|
||
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"AfterExpiry"
|
||
timeout:15.0
|
||
error:&error2];
|
||
XCTAssertNotNil(response2);
|
||
|
||
// 验è¯<C3A8>:创建了2ä¸ªè¿žæŽ¥ï¼ˆæ—§è¿žæŽ¥è¿‡æœŸï¼Œæ— æ³•å¤<C3A5>用ï¼?
|
||
XCTAssertEqual(self.client.connectionCreationCount, 2,
|
||
@"Should create 2 connections (first expired after 30s)");
|
||
XCTAssertEqual(self.client.connectionReuseCount, 0,
|
||
@"Should not reuse expired connection");
|
||
}
|
||
|
||
// Q4.2 边界æ<C592>¡ä»¶ï¼?9秒内连接未过æœ?
|
||
- (void)testBoundary_Under30Seconds_ConnectionNotExpired {
|
||
[self.client resetPoolStatistics];
|
||
|
||
// 第一个请�
|
||
NSError *error1 = nil;
|
||
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"InitialRequest"
|
||
timeout:15.0
|
||
error:&error1];
|
||
XCTAssertNotNil(response1);
|
||
|
||
// ç‰å¾…29秒(未到30秒过期时间)
|
||
[NSThread sleepForTimeInterval:29.0];
|
||
|
||
// 第二个请求:应该å¤<C3A5>用连接
|
||
NSError *error2 = nil;
|
||
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"BeforeExpiry"
|
||
timeout:15.0
|
||
error:&error2];
|
||
XCTAssertNotNil(response2);
|
||
|
||
// 验è¯<C3A8>:å<C5A1>ªåˆ›å»ºäº?个连接(å¤<C3A5>用了)
|
||
XCTAssertEqual(self.client.connectionCreationCount, 1,
|
||
@"Should create only 1 connection (reused within 30s)");
|
||
XCTAssertEqual(self.client.connectionReuseCount, 1,
|
||
@"Should reuse connection within 30s");
|
||
}
|
||
|
||
// Q4.3 边界æ<C592>¡ä»¶ï¼šæ<C5A1>°å¥?个连接全部ä¿<C3A4>ç•?
|
||
- (void)testBoundary_ExactlyFourConnections_AllKept {
|
||
[self.client resetPoolStatistics];
|
||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||
|
||
// å¹¶å<C2B6>‘å<E28098>‘èµ·4个请求(使用延迟确ä¿<C3A4>å<EFBFBD>Œæ—¶åœ¨é£žè¡Œä¸ï¼Œåˆ›å»?个独立连接)
|
||
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
|
||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||
|
||
for (NSInteger i = 0; i < 4; i++) {
|
||
XCTestExpectation *expectation = [self expectationWithDescription:
|
||
[NSString stringWithFormat:@"Request %ld", (long)i]];
|
||
[expectations addObject:expectation];
|
||
|
||
dispatch_async(queue, ^{
|
||
NSError *error = nil;
|
||
// 使用 /delay/2 ç¡®ä¿<C3A4>所有请求å<E2809A>Œæ—¶åœ¨é£žè¡Œä¸?
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/2"
|
||
userAgent:[NSString stringWithFormat:@"Request%ld", (long)i]
|
||
timeout:15.0
|
||
error:&error];
|
||
[expectation fulfill];
|
||
});
|
||
|
||
[NSThread sleepForTimeInterval:0.05]; // å°<C3A5>é—´éš”é<E2809D>¿å…<C3A5>完全å<C2A8>Œæ—¶å<C2B6>¯åŠ?
|
||
}
|
||
|
||
[self waitForExpectations:expectations timeout:20.0];
|
||
|
||
// ç‰å¾…连接归还
|
||
[NSThread sleepForTimeInterval:1.0];
|
||
|
||
// 验è¯<C3A8>ï¼šæ± æ<C2A0>°å¥½æœ?个连接(全部ä¿<C3A4>ç•™ï¼?
|
||
NSUInteger poolCount = [self.client connectionPoolCountForKey:poolKey];
|
||
XCTAssertEqual(poolCount, 4,
|
||
@"Pool should keep all 4 connections (not exceeding limit)");
|
||
|
||
// 验è¯<C3A8>:æ<C5A1>°å¥½åˆ›å»?个连æŽ?
|
||
XCTAssertEqual(self.client.connectionCreationCount, 4,
|
||
@"Should create exactly 4 connections");
|
||
}
|
||
|
||
// Q1.2 æ£å¸¸çжæ€<C3A6>åº<C3A5>列验è¯?
|
||
- (void)testStateMachine_NormalSequence_StateTransitionsCorrect {
|
||
[self.client resetPoolStatistics];
|
||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||
|
||
// ç¬?æ¥ï¼šåˆ›å»ºå¹¶ä½¿ç”¨è¿žæŽ?(CREATING â†?IN_USE â†?IDLE)
|
||
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"StateTest"
|
||
timeout:15.0
|
||
error:nil];
|
||
XCTAssertNotNil(response1, @"First request should succeed");
|
||
|
||
[NSThread sleepForTimeInterval:1.0]; // ç‰å¾…归还
|
||
|
||
// 验è¯<C3A8>ï¼šæ± ä¸æœ‰1个连æŽ?
|
||
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
|
||
@"Connection should be in pool");
|
||
|
||
// ç¬?æ¥ï¼šå¤<C3A5>用连接 (IDLE â†?IN_USE â†?IDLE)
|
||
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"StateTest"
|
||
timeout:15.0
|
||
error:nil];
|
||
XCTAssertNotNil(response2, @"Second request should reuse connection");
|
||
|
||
// 验è¯<C3A8>:å¤<C3A5>用计数增åŠ?
|
||
XCTAssertEqual(self.client.connectionReuseCount, 1,
|
||
@"Should have reused connection once");
|
||
}
|
||
|
||
// Q1.3 inUse æ ‡å¿—ç»´æŠ¤éªŒè¯<C3A8>
|
||
- (void)testStateMachine_InUseFlag_CorrectlyMaintained {
|
||
[self.client resetPoolStatistics];
|
||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||
|
||
// å<>‘起请求并归è¿?
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"InUseTest"
|
||
timeout:15.0
|
||
error:nil];
|
||
|
||
[NSThread sleepForTimeInterval:1.0]; // ç‰å¾…归还
|
||
|
||
// 获å<C2B7>–æ± ä¸è¿žæŽ¥
|
||
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
|
||
XCTAssertEqual(connections.count, 1, @"Should have 1 connection in pool");
|
||
|
||
// 验è¯<C3A8>ï¼šæ± ä¸è¿žæŽ¥çš„ inUse 应为 NO
|
||
for (HttpdnsNWReusableConnection *conn in connections) {
|
||
XCTAssertFalse(conn.inUse, @"Connection in pool should not be marked as inUse");
|
||
}
|
||
}
|
||
|
||
// Q2.3 Nil lastUsedDate 处ç<E2809E>†éªŒè¯<C3A8>
|
||
- (void)testAbnormal_NilLastUsedDate_HandledGracefully {
|
||
[self.client resetPoolStatistics];
|
||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||
|
||
// å<>‘起请求创建连接
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"NilDateTest"
|
||
timeout:15.0
|
||
error:nil];
|
||
|
||
[NSThread sleepForTimeInterval:1.0];
|
||
|
||
// 获å<C2B7>–连接并设ç½?lastUsedDate ä¸?nil
|
||
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
|
||
XCTAssertEqual(connections.count, 1, @"Should have connection");
|
||
|
||
HttpdnsNWReusableConnection *conn = connections.firstObject;
|
||
[conn debugSetLastUsedDate:nil];
|
||
|
||
// å<>‘起新请求触å<C2A6>?prune(内部应使用 distantPast 处ç<E2809E>† nilï¼?
|
||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"NilDateTest"
|
||
timeout:15.0
|
||
error:nil];
|
||
|
||
// 验è¯<C3A8>:ä¸<C3A4>崩溃,æ£å¸¸å·¥ä½?
|
||
XCTAssertNotNil(response, @"Should handle nil lastUsedDate gracefully");
|
||
}
|
||
|
||
// Q3.2 æ± ä¸æ— 失效连接ä¸<C3A4>å<EFBFBD>˜å¼<C3A5>
|
||
- (void)testInvariant_NoInvalidatedInPool {
|
||
[self.client resetPoolStatistics];
|
||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||
|
||
// å<>‘起多个请求(包括æˆ<C3A6>功和超时ï¼?
|
||
for (NSInteger i = 0; i < 3; i++) {
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"InvariantTest"
|
||
timeout:15.0
|
||
error:nil];
|
||
}
|
||
|
||
// å<>‘èµ·1个超时请æ±?
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
|
||
userAgent:@"TimeoutTest"
|
||
timeout:0.5
|
||
error:nil];
|
||
|
||
[NSThread sleepForTimeInterval:2.0];
|
||
|
||
// 获å<C2B7>–æ± ä¸æ‰€æœ‰è¿žæŽ?
|
||
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
|
||
|
||
// 验è¯<C3A8>ï¼šæ± ä¸æ— 失效连接
|
||
for (HttpdnsNWReusableConnection *conn in connections) {
|
||
XCTAssertFalse(conn.isInvalidated, @"Pool should not contain invalidated connections");
|
||
}
|
||
}
|
||
|
||
// Q3.4 lastUsedDate å<>•调性验è¯?
|
||
- (void)testInvariant_LastUsedDate_Monotonic {
|
||
[self.client resetPoolStatistics];
|
||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||
|
||
// �次使�
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"MonotonicTest"
|
||
timeout:15.0
|
||
error:nil];
|
||
|
||
[NSThread sleepForTimeInterval:1.0];
|
||
|
||
NSArray<HttpdnsNWReusableConnection *> *connections1 = [self.client connectionsInPoolForKey:poolKey];
|
||
XCTAssertEqual(connections1.count, 1, @"Should have connection");
|
||
NSDate *date1 = connections1.firstObject.lastUsedDate;
|
||
XCTAssertNotNil(date1, @"lastUsedDate should be set");
|
||
|
||
// ç‰å¾…1ç§’ç¡®ä¿<C3A4>时间推è¿?
|
||
[NSThread sleepForTimeInterval:1.0];
|
||
|
||
// ç¬?次使用å<C2A8>Œä¸€è¿žæŽ¥
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"MonotonicTest"
|
||
timeout:15.0
|
||
error:nil];
|
||
|
||
[NSThread sleepForTimeInterval:1.0];
|
||
|
||
NSArray<HttpdnsNWReusableConnection *> *connections2 = [self.client connectionsInPoolForKey:poolKey];
|
||
XCTAssertEqual(connections2.count, 1, @"Should still have 1 connection");
|
||
NSDate *date2 = connections2.firstObject.lastUsedDate;
|
||
|
||
// 验è¯<C3A8>:lastUsedDate 递增
|
||
XCTAssertTrue([date2 timeIntervalSinceDate:date1] > 0,
|
||
@"lastUsedDate should increase after reuse");
|
||
}
|
||
|
||
// Q5.1 è¶…æ—¶+æ± æº¢å‡ºå¤<C3A5>å<EFBFBD>ˆåœºæ™?
|
||
- (void)testCompound_TimeoutDuringPoolOverflow_Handled {
|
||
[self.client resetPoolStatistics];
|
||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||
|
||
// å…ˆå¡«æ»¡æ± ï¼?个æˆ<C3A6>功连接)
|
||
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
|
||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||
|
||
for (NSInteger i = 0; i < 4; i++) {
|
||
XCTestExpectation *expectation = [self expectationWithDescription:
|
||
[NSString stringWithFormat:@"Fill pool %ld", (long)i]];
|
||
[expectations addObject:expectation];
|
||
|
||
dispatch_async(queue, ^{
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/2"
|
||
userAgent:@"CompoundTest"
|
||
timeout:15.0
|
||
error:nil];
|
||
[expectation fulfill];
|
||
});
|
||
[NSThread sleepForTimeInterval:0.05];
|
||
}
|
||
|
||
[self waitForExpectations:expectations timeout:20.0];
|
||
[NSThread sleepForTimeInterval:1.0];
|
||
|
||
NSUInteger poolCountBefore = [self.client connectionPoolCountForKey:poolKey];
|
||
XCTAssertLessThanOrEqual(poolCountBefore, 4, @"Pool should have � connections");
|
||
|
||
// �个请求超�
|
||
NSError *error = nil;
|
||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
|
||
userAgent:@"TimeoutRequest"
|
||
timeout:0.5
|
||
error:&error];
|
||
|
||
XCTAssertNil(response, @"Timeout request should return nil");
|
||
XCTAssertNotNil(error, @"Should have error");
|
||
|
||
[NSThread sleepForTimeInterval:1.0];
|
||
|
||
// 验è¯<C3A8>ï¼šè¶…æ—¶è¿žæŽ¥æœªåŠ å…¥æ±?
|
||
NSUInteger poolCountAfter = [self.client connectionPoolCountForKey:poolKey];
|
||
XCTAssertLessThanOrEqual(poolCountAfter, 4, @"Timed-out connection should not be added to pool");
|
||
}
|
||
|
||
// Q2.4 打开失败ä¸<C3A4>åŠ å…¥æ±
|
||
- (void)testAbnormal_OpenFailure_NotAddedToPool {
|
||
[self.client resetPoolStatistics];
|
||
|
||
// å°<C3A5>è¯•è¿žæŽ¥æ— æ•ˆç«¯å<C2AF>£ï¼ˆè¿žæŽ¥æ‹’ç»<C3A7>)
|
||
NSError *error = nil;
|
||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:99999/get"
|
||
userAgent:@"FailureTest"
|
||
timeout:2.0
|
||
error:&error];
|
||
|
||
// 验è¯<C3A8>:请求失è´?
|
||
XCTAssertNil(response, @"Should fail to connect to invalid port");
|
||
|
||
// 验è¯<C3A8>ï¼šæ— è¿žæŽ¥åŠ å…¥æ±?
|
||
XCTAssertEqual([self.client totalConnectionCount], 0,
|
||
@"Failed connection should not be added to pool");
|
||
}
|
||
|
||
// Q2.5 多次 invalidate å¹‚ç‰æ€?
|
||
- (void)testAbnormal_MultipleInvalidate_Idempotent {
|
||
[self.client resetPoolStatistics];
|
||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||
|
||
// 创建连接
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"InvalidateTest"
|
||
timeout:15.0
|
||
error:nil];
|
||
|
||
[NSThread sleepForTimeInterval:1.0];
|
||
|
||
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
|
||
XCTAssertEqual(connections.count, 1, @"Should have connection");
|
||
|
||
HttpdnsNWReusableConnection *conn = connections.firstObject;
|
||
|
||
// 多次 invalidate
|
||
[conn debugInvalidate];
|
||
[conn debugInvalidate];
|
||
[conn debugInvalidate];
|
||
|
||
// 验è¯<C3A8>:ä¸<C3A4>崩溃
|
||
XCTAssertTrue(conn.isInvalidated, @"Connection should be invalidated");
|
||
}
|
||
|
||
// Q5.2 å¹¶å<C2B6>‘ dequeue 竞æ€<C3A6>测è¯?
|
||
- (void)testCompound_ConcurrentDequeueDuringPrune_Safe {
|
||
[self.client resetPoolStatistics];
|
||
|
||
// 在两个端å<C2AF>£åˆ›å»ºè¿žæŽ?
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"RaceTest"
|
||
timeout:15.0
|
||
error:nil];
|
||
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11443/get"
|
||
userAgent:@"RaceTest"
|
||
timeout:15.0
|
||
error:nil];
|
||
|
||
[NSThread sleepForTimeInterval:1.0];
|
||
|
||
// ç‰å¾…30秒让连接过期
|
||
[NSThread sleepForTimeInterval:30.5];
|
||
|
||
// å¹¶å<C2B6>‘触å<C2A6>‘两个端å<C2AF>£çš?dequeue(会触å<C2A6>‘ pruneï¼?
|
||
dispatch_group_t group = dispatch_group_create();
|
||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||
|
||
dispatch_group_async(group, queue, ^{
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||
userAgent:@"Race1"
|
||
timeout:15.0
|
||
error:nil];
|
||
});
|
||
|
||
dispatch_group_async(group, queue, ^{
|
||
[self.client performRequestWithURLString:@"http://127.0.0.1:11443/get"
|
||
userAgent:@"Race2"
|
||
timeout:15.0
|
||
error:nil];
|
||
});
|
||
|
||
// ç‰å¾…完æˆ<C3A6>
|
||
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 20 * NSEC_PER_SEC));
|
||
|
||
// 验è¯<C3A8>ï¼šæ— å´©æºƒï¼Œè¿žæŽ¥æ± æ£å¸¸å·¥ä½œ
|
||
NSUInteger totalCount = [self.client totalConnectionCount];
|
||
XCTAssertLessThanOrEqual(totalCount, 4, @"Pool should remain stable after concurrent prune");
|
||
}
|
||
|
||
@end
|