feat: sync httpdns sdk/platform updates without large binaries
This commit is contained in:
@@ -0,0 +1,774 @@
|
||||
//
|
||||
// HttpdnsNWHTTPClient_PoolManagementTests.m
|
||||
// TrustHttpDNSTests
|
||||
//
|
||||
// @author Created by Claude Code on 2025-11-01
|
||||
// Copyright © 2025 trustapp.com. All rights reserved.
|
||||
//
|
||||
// 连接池管理测<EFBFBD><EFBFBD>?- 包含多端口隔<EFBFBD><EFBFBD>?(K)、端口池耗尽 (L)、池验证 (O)、空闲超<EFBFBD><EFBFBD>?(S) 测试<EFBFBD><EFBFBD>?
|
||||
// 测试总数<EFBFBD><EFBFBD>?6 个(K:5 + L:3 + O:3 + S:5<EFBFBD><EFBFBD>?
|
||||
//
|
||||
|
||||
#import "HttpdnsNWHTTPClientTestBase.h"
|
||||
|
||||
@interface HttpdnsNWHTTPClient_PoolManagementTests : HttpdnsNWHTTPClientTestBase
|
||||
|
||||
@end
|
||||
|
||||
@implementation HttpdnsNWHTTPClient_PoolManagementTests
|
||||
|
||||
#pragma mark - K. 多端口连接隔离测<EFBFBD><EFBFBD>?
|
||||
|
||||
// K.1 不同 HTTPS 端口使用不同连接<EFBFBD><EFBFBD>?
|
||||
- (void)testMultiPort_DifferentHTTPSPorts_SeparatePoolKeys {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Different ports use different pools"];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
// 请求端口 11443
|
||||
NSError *error1 = nil;
|
||||
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
|
||||
userAgent:@"Port11443"
|
||||
timeout:15.0
|
||||
error:&error1];
|
||||
XCTAssertNotNil(response1, @"First request to port 11443 should succeed");
|
||||
XCTAssertEqual(response1.statusCode, 200);
|
||||
|
||||
// 请求端口 11444(应该创建新连接,不复用 11443 的)
|
||||
NSError *error2 = nil;
|
||||
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
|
||||
userAgent:@"Port11444"
|
||||
timeout:15.0
|
||||
error:&error2];
|
||||
XCTAssertNotNil(response2, @"First request to port 11444 should succeed");
|
||||
XCTAssertEqual(response2.statusCode, 200);
|
||||
|
||||
// 再次请求端口 11443(应该复用之前的连接<EFBFBD><EFBFBD>?
|
||||
NSError *error3 = nil;
|
||||
HttpdnsNWHTTPClientResponse *response3 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
|
||||
userAgent:@"Port11443Again"
|
||||
timeout:15.0
|
||||
error:&error3];
|
||||
XCTAssertNotNil(response3, @"Second request to port 11443 should reuse connection");
|
||||
XCTAssertEqual(response3.statusCode, 200);
|
||||
|
||||
[expectation fulfill];
|
||||
});
|
||||
|
||||
[self waitForExpectations:@[expectation] timeout:50.0];
|
||||
}
|
||||
|
||||
// K.2 三个不同 HTTPS 端口的并发请<EFBFBD><EFBFBD>?
|
||||
- (void)testMultiPort_ConcurrentThreePorts_AllSucceed {
|
||||
NSInteger requestsPerPort = 10;
|
||||
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445];
|
||||
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
|
||||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
|
||||
NSLock *successCountLock = [[NSLock alloc] init];
|
||||
__block NSInteger successCount = 0;
|
||||
|
||||
for (NSNumber *port in ports) {
|
||||
for (NSInteger i = 0; i < requestsPerPort; i++) {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Port %@ Request %ld", port, (long)i]];
|
||||
[expectations addObject:expectation];
|
||||
|
||||
dispatch_async(queue, ^{
|
||||
NSString *urlString = [NSString stringWithFormat:@"https://127.0.0.1:%@/get", port];
|
||||
NSError *error = nil;
|
||||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
|
||||
userAgent:@"ConcurrentMultiPort"
|
||||
timeout:15.0
|
||||
error:&error];
|
||||
|
||||
if (response && response.statusCode == 200) {
|
||||
[successCountLock lock];
|
||||
successCount++;
|
||||
[successCountLock unlock];
|
||||
}
|
||||
|
||||
[expectation fulfill];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[self waitForExpectations:expectations timeout:60.0];
|
||||
|
||||
// 验证所有请求都成功
|
||||
XCTAssertEqual(successCount, ports.count * requestsPerPort, @"All 30 requests should succeed");
|
||||
}
|
||||
|
||||
// K.3 快速切换端口模<EFBFBD><EFBFBD>?
|
||||
- (void)testMultiPort_SequentialPortSwitching_ConnectionReusePerPort {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Sequential port switching"];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSArray<NSString *> *urls = @[
|
||||
@"https://127.0.0.1:11443/get", // 第一次访<EFBFBD><EFBFBD>?11443
|
||||
@"https://127.0.0.1:11444/get", // 第一次访<EFBFBD><EFBFBD>?11444
|
||||
@"https://127.0.0.1:11445/get", // 第一次访<EFBFBD><EFBFBD>?11445
|
||||
@"https://127.0.0.1:11443/get", // 第二次访<EFBFBD><EFBFBD>?11443(应复用<EFBFBD><EFBFBD>?
|
||||
@"https://127.0.0.1:11444/get", // 第二次访<EFBFBD><EFBFBD>?11444(应复用<EFBFBD><EFBFBD>?
|
||||
];
|
||||
|
||||
NSMutableArray<NSNumber *> *responseTimes = [NSMutableArray array];
|
||||
|
||||
for (NSString *url in urls) {
|
||||
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
|
||||
NSError *error = nil;
|
||||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:url
|
||||
userAgent:@"SwitchingPorts"
|
||||
timeout:15.0
|
||||
error:&error];
|
||||
CFAbsoluteTime elapsed = CFAbsoluteTimeGetCurrent() - startTime;
|
||||
|
||||
XCTAssertNotNil(response, @"Request to %@ should succeed", url);
|
||||
XCTAssertEqual(response.statusCode, 200);
|
||||
[responseTimes addObject:@(elapsed)];
|
||||
}
|
||||
|
||||
// 验证所有请求都完成
|
||||
XCTAssertEqual(responseTimes.count, urls.count);
|
||||
|
||||
[expectation fulfill];
|
||||
});
|
||||
|
||||
[self waitForExpectations:@[expectation] timeout:80.0];
|
||||
}
|
||||
|
||||
// K.4 每个端口独立的连接池限制
|
||||
- (void)testMultiPort_PerPortPoolLimit_IndependentPools {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Per-port pool limits"];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
// 向端<EFBFBD><EFBFBD>?11443 发<EFBFBD><EFBFBD>?10 个请<EFBFBD><EFBFBD>?
|
||||
for (NSInteger i = 0; i < 10; i++) {
|
||||
NSError *error = nil;
|
||||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
|
||||
userAgent:@"Pool11443"
|
||||
timeout:15.0
|
||||
error:&error];
|
||||
XCTAssertTrue(response != nil || error != nil);
|
||||
}
|
||||
|
||||
// 向端<EFBFBD><EFBFBD>?11444 发<EFBFBD><EFBFBD>?10 个请求(应该有独立的池)
|
||||
for (NSInteger i = 0; i < 10; i++) {
|
||||
NSError *error = nil;
|
||||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
|
||||
userAgent:@"Pool11444"
|
||||
timeout:15.0
|
||||
error:&error];
|
||||
XCTAssertTrue(response != nil || error != nil);
|
||||
}
|
||||
|
||||
// 等待连接归还
|
||||
[NSThread sleepForTimeInterval:1.0];
|
||||
|
||||
// 验证两个端口都仍然可<EFBFBD><EFBFBD>?
|
||||
NSError *error1 = nil;
|
||||
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
|
||||
userAgent:@"Verify11443"
|
||||
timeout:15.0
|
||||
error:&error1];
|
||||
XCTAssertNotNil(response1, @"Port 11443 should still work after heavy usage");
|
||||
|
||||
NSError *error2 = nil;
|
||||
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
|
||||
userAgent:@"Verify11444"
|
||||
timeout:15.0
|
||||
error:&error2];
|
||||
XCTAssertNotNil(response2, @"Port 11444 should still work after heavy usage");
|
||||
|
||||
[expectation fulfill];
|
||||
});
|
||||
|
||||
[self waitForExpectations:@[expectation] timeout:150.0];
|
||||
}
|
||||
|
||||
// K.5 交错访问多个端口
|
||||
- (void)testMultiPort_InterleavedRequests_AllPortsAccessible {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Interleaved multi-port requests"];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445, @11446];
|
||||
NSInteger totalRequests = 20;
|
||||
NSInteger successCount = 0;
|
||||
|
||||
// 交错请求:依次循环访问所有端<EFBFBD><EFBFBD>?
|
||||
for (NSInteger i = 0; i < totalRequests; i++) {
|
||||
NSNumber *port = ports[i % ports.count];
|
||||
NSString *urlString = [NSString stringWithFormat:@"https://127.0.0.1:%@/get", port];
|
||||
|
||||
NSError *error = nil;
|
||||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
|
||||
userAgent:@"Interleaved"
|
||||
timeout:15.0
|
||||
error:&error];
|
||||
|
||||
if (response && response.statusCode == 200) {
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertEqual(successCount, totalRequests, @"All interleaved requests should succeed");
|
||||
|
||||
[expectation fulfill];
|
||||
});
|
||||
|
||||
[self waitForExpectations:@[expectation] timeout:120.0];
|
||||
}
|
||||
|
||||
#pragma mark - L. 基于端口的池耗尽测试
|
||||
|
||||
// L.1 四个端口同时承载高负<EFBFBD><EFBFBD>?
|
||||
- (void)testPoolExhaustion_FourPortsSimultaneous_AllSucceed {
|
||||
NSInteger requestsPerPort = 10;
|
||||
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445, @11446];
|
||||
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
|
||||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
|
||||
NSLock *successCountLock = [[NSLock alloc] init];
|
||||
__block NSInteger successCount = 0;
|
||||
|
||||
// <EFBFBD><EFBFBD>?4 个端口各发起 10 个并发请求(<EFBFBD><EFBFBD>?40 个)
|
||||
for (NSNumber *port in ports) {
|
||||
for (NSInteger i = 0; i < requestsPerPort; i++) {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Port %@ Request %ld", port, (long)i]];
|
||||
[expectations addObject:expectation];
|
||||
|
||||
dispatch_async(queue, ^{
|
||||
NSString *urlString = [NSString stringWithFormat:@"https://127.0.0.1:%@/get", port];
|
||||
NSError *error = nil;
|
||||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
|
||||
userAgent:@"FourPortLoad"
|
||||
timeout:15.0
|
||||
error:&error];
|
||||
|
||||
if (response && response.statusCode == 200) {
|
||||
[successCountLock lock];
|
||||
successCount++;
|
||||
[successCountLock unlock];
|
||||
}
|
||||
|
||||
[expectation fulfill];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[self waitForExpectations:expectations timeout:80.0];
|
||||
|
||||
// 验证成功<EFBFBD><EFBFBD>?> 95%(允许少量因并发导致的失败)
|
||||
XCTAssertGreaterThan(successCount, 38, @"At least 95%% of 40 requests should succeed");
|
||||
}
|
||||
|
||||
// L.2 单个端口耗尽时其他端口不受影<EFBFBD><EFBFBD>?
|
||||
- (void)testPoolExhaustion_SinglePortExhausted_OthersUnaffected {
|
||||
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
|
||||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
|
||||
__block NSInteger port11444SuccessCount = 0;
|
||||
NSLock *countLock = [[NSLock alloc] init];
|
||||
|
||||
// 向端<EFBFBD><EFBFBD>?11443 发起 20 个并发请求(可能导致池耗尽<EFBFBD><EFBFBD>?
|
||||
for (NSInteger i = 0; i < 20; i++) {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Exhaust11443 %ld", (long)i]];
|
||||
[expectations addObject:expectation];
|
||||
|
||||
dispatch_async(queue, ^{
|
||||
NSError *error = nil;
|
||||
[self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
|
||||
userAgent:@"Exhaust11443"
|
||||
timeout:15.0
|
||||
error:&error];
|
||||
[expectation fulfill];
|
||||
});
|
||||
}
|
||||
|
||||
// 同时向端<EFBFBD><EFBFBD>?11444 发起 5 个请求(应该不受 11443 影响<EFBFBD><EFBFBD>?
|
||||
for (NSInteger i = 0; i < 5; i++) {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Port11444 %ld", (long)i]];
|
||||
[expectations addObject:expectation];
|
||||
|
||||
dispatch_async(queue, ^{
|
||||
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
|
||||
NSError *error = nil;
|
||||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
|
||||
userAgent:@"Independent11444"
|
||||
timeout:15.0
|
||||
error:&error];
|
||||
CFAbsoluteTime elapsed = CFAbsoluteTimeGetCurrent() - startTime;
|
||||
|
||||
if (response && response.statusCode == 200) {
|
||||
[countLock lock];
|
||||
port11444SuccessCount++;
|
||||
[countLock unlock];
|
||||
}
|
||||
|
||||
// 验证响应时间合理(不应因 11443 负载而显著延迟)
|
||||
XCTAssertLessThan(elapsed, 10.0, @"Port 11444 should not be delayed by port 11443 load");
|
||||
|
||||
[expectation fulfill];
|
||||
});
|
||||
}
|
||||
|
||||
[self waitForExpectations:expectations timeout:60.0];
|
||||
|
||||
// 验证端口 11444 的请求都成功
|
||||
XCTAssertEqual(port11444SuccessCount, 5, @"All port 11444 requests should succeed despite 11443 load");
|
||||
}
|
||||
|
||||
// L.3 多端口使用后的连接清<EFBFBD><EFBFBD>?
|
||||
- (void)testPoolExhaustion_MultiPortCleanup_ExpiredConnectionsPruned {
|
||||
if (getenv("SKIP_SLOW_TESTS")) {
|
||||
return;
|
||||
}
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Multi-port connection cleanup"];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445];
|
||||
|
||||
// 向三个端口各发起一个请<EFBFBD><EFBFBD>?
|
||||
for (NSNumber *port in ports) {
|
||||
NSString *urlString = [NSString stringWithFormat:@"https://127.0.0.1:%@/get", port];
|
||||
NSError *error = nil;
|
||||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
|
||||
userAgent:@"Initial"
|
||||
timeout:15.0
|
||||
error:&error];
|
||||
XCTAssertTrue(response != nil || error != nil);
|
||||
}
|
||||
|
||||
// 等待超过 30 秒,让所有连接过<EFBFBD><EFBFBD>?
|
||||
[NSThread sleepForTimeInterval:31.0];
|
||||
|
||||
// 再次向三个端口发起请求(应该创建新连接)
|
||||
for (NSNumber *port in ports) {
|
||||
NSString *urlString = [NSString stringWithFormat:@"https://127.0.0.1:%@/get", port];
|
||||
NSError *error = nil;
|
||||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
|
||||
userAgent:@"AfterExpiry"
|
||||
timeout:15.0
|
||||
error:&error];
|
||||
XCTAssertTrue(response != nil || error != nil, @"Requests should succeed after expiry");
|
||||
}
|
||||
|
||||
[expectation fulfill];
|
||||
});
|
||||
|
||||
[self waitForExpectations:@[expectation] timeout:80.0];
|
||||
}
|
||||
|
||||
#pragma mark - O. 连接池验证测试(使用新增的检<EFBFBD><EFBFBD>?API<EFBFBD><EFBFBD>?
|
||||
|
||||
// O.1 综合连接池验<EFBFBD><EFBFBD>?- 演示所有检查能<EFBFBD><EFBFBD>?
|
||||
- (void)testPoolVerification_ComprehensiveCheck_AllAspectsVerified {
|
||||
[self.client resetPoolStatistics];
|
||||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||||
|
||||
// 初始状态:无连<EFBFBD><EFBFBD>?
|
||||
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
|
||||
@"Pool should be empty initially");
|
||||
XCTAssertEqual([self.client totalConnectionCount], 0,
|
||||
@"Total connections should be 0 initially");
|
||||
XCTAssertEqual(self.client.connectionCreationCount, 0,
|
||||
@"Creation count should be 0 initially");
|
||||
XCTAssertEqual(self.client.connectionReuseCount, 0,
|
||||
@"Reuse count should be 0 initially");
|
||||
|
||||
// 发<EFBFBD><EFBFBD>?5 个请求到同一端点
|
||||
for (NSInteger i = 0; i < 5; i++) {
|
||||
NSError *error = nil;
|
||||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||||
userAgent:@"PoolVerificationTest"
|
||||
timeout:15.0
|
||||
error:&error];
|
||||
XCTAssertNotNil(response, @"Request %ld should succeed", (long)i);
|
||||
XCTAssertEqual(response.statusCode, 200, @"Request %ld should return 200", (long)i);
|
||||
}
|
||||
|
||||
// 验证连接池状<EFBFBD><EFBFBD>?
|
||||
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
|
||||
@"Should have exactly 1 connection in pool for key: %@", poolKey);
|
||||
XCTAssertEqual([self.client totalConnectionCount], 1,
|
||||
@"Total connection count should be 1");
|
||||
|
||||
// 验证统计计数
|
||||
XCTAssertEqual(self.client.connectionCreationCount, 1,
|
||||
@"Should create only 1 connection");
|
||||
XCTAssertEqual(self.client.connectionReuseCount, 4,
|
||||
@"Should reuse connection 4 times");
|
||||
|
||||
// 验证连接复用<EFBFBD><EFBFBD>?
|
||||
CGFloat reuseRate = (CGFloat)self.client.connectionReuseCount /
|
||||
(self.client.connectionCreationCount + self.client.connectionReuseCount);
|
||||
XCTAssertGreaterThanOrEqual(reuseRate, 0.8,
|
||||
@"Reuse rate should be at least 80%% (actual: %.1f%%)", reuseRate * 100);
|
||||
|
||||
// 验证 pool keys
|
||||
NSArray<NSString *> *allKeys = [self.client allConnectionPoolKeys];
|
||||
XCTAssertEqual(allKeys.count, 1, @"Should have exactly 1 pool key");
|
||||
XCTAssertTrue([allKeys containsObject:poolKey], @"Should contain the expected pool key");
|
||||
}
|
||||
|
||||
// O.2 多端口连接池隔离验证
|
||||
- (void)testPoolVerification_MultiPort_IndependentPools {
|
||||
[self.client resetPoolStatistics];
|
||||
|
||||
NSString *key11443 = @"127.0.0.1:11443:tls";
|
||||
NSString *key11444 = @"127.0.0.1:11444:tls";
|
||||
|
||||
// 初始:两个池都为<EFBFBD><EFBFBD>?
|
||||
XCTAssertEqual([self.client connectionPoolCountForKey:key11443], 0);
|
||||
XCTAssertEqual([self.client connectionPoolCountForKey:key11444], 0);
|
||||
|
||||
// 向端<EFBFBD><EFBFBD>?11443 发<EFBFBD><EFBFBD>?3 个请<EFBFBD><EFBFBD>?
|
||||
for (NSInteger i = 0; i < 3; i++) {
|
||||
NSError *error = nil;
|
||||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
|
||||
userAgent:@"Port11443"
|
||||
timeout:15.0
|
||||
error:&error];
|
||||
XCTAssertNotNil(response);
|
||||
}
|
||||
|
||||
// 验证端口 11443 的池状<EFBFBD><EFBFBD>?
|
||||
XCTAssertEqual([self.client connectionPoolCountForKey:key11443], 1,
|
||||
@"Port 11443 should have 1 connection");
|
||||
XCTAssertEqual([self.client connectionPoolCountForKey:key11444], 0,
|
||||
@"Port 11444 should still be empty");
|
||||
|
||||
// 向端<EFBFBD><EFBFBD>?11444 发<EFBFBD><EFBFBD>?3 个请<EFBFBD><EFBFBD>?
|
||||
for (NSInteger i = 0; i < 3; i++) {
|
||||
NSError *error = nil;
|
||||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
|
||||
userAgent:@"Port11444"
|
||||
timeout:15.0
|
||||
error:&error];
|
||||
XCTAssertNotNil(response);
|
||||
}
|
||||
|
||||
// 验证两个端口的池都存在且独立
|
||||
XCTAssertEqual([self.client connectionPoolCountForKey:key11443], 1,
|
||||
@"Port 11443 should still have 1 connection");
|
||||
XCTAssertEqual([self.client connectionPoolCountForKey:key11444], 1,
|
||||
@"Port 11444 should now have 1 connection");
|
||||
XCTAssertEqual([self.client totalConnectionCount], 2,
|
||||
@"Total should be 2 connections (one per port)");
|
||||
|
||||
// 验证统计:应该创建了 2 个连接,复用<EFBFBD><EFBFBD>?4 <EFBFBD><EFBFBD>?
|
||||
XCTAssertEqual(self.client.connectionCreationCount, 2,
|
||||
@"Should create 2 connections (one per port)");
|
||||
XCTAssertEqual(self.client.connectionReuseCount, 4,
|
||||
@"Should reuse connections 4 times total");
|
||||
|
||||
// 验证 pool keys
|
||||
NSArray<NSString *> *allKeys = [self.client allConnectionPoolKeys];
|
||||
XCTAssertEqual(allKeys.count, 2, @"Should have 2 pool keys");
|
||||
XCTAssertTrue([allKeys containsObject:key11443], @"Should contain key for port 11443");
|
||||
XCTAssertTrue([allKeys containsObject:key11444], @"Should contain key for port 11444");
|
||||
}
|
||||
|
||||
// O.3 连接池容量限制验<EFBFBD><EFBFBD>?
|
||||
- (void)testPoolVerification_PoolCapacity_MaxFourConnections {
|
||||
[self.client resetPoolStatistics];
|
||||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||||
|
||||
// 发<EFBFBD><EFBFBD>?10 个连续请求(每个请求都会归还连接到池<EFBFBD><EFBFBD>?
|
||||
for (NSInteger i = 0; i < 10; i++) {
|
||||
NSError *error = nil;
|
||||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||||
userAgent:@"CapacityTest"
|
||||
timeout:15.0
|
||||
error:&error];
|
||||
XCTAssertNotNil(response);
|
||||
}
|
||||
|
||||
// 等待连接归还
|
||||
[NSThread sleepForTimeInterval:1.0];
|
||||
|
||||
// 验证池大小不超过 4(kHttpdnsNWHTTPClientMaxIdleConnectionsPerKey<EFBFBD><EFBFBD>?
|
||||
NSUInteger poolSize = [self.client connectionPoolCountForKey:poolKey];
|
||||
XCTAssertLessThanOrEqual(poolSize, 4,
|
||||
@"Pool size should not exceed 4 (actual: %lu)", (unsigned long)poolSize);
|
||||
|
||||
// 验证统计:应该只创建<EFBFBD><EFBFBD>?1 个连接(因为串行请求,每次都复用<EFBFBD><EFBFBD>?
|
||||
XCTAssertEqual(self.client.connectionCreationCount, 1,
|
||||
@"Should create only 1 connection for sequential requests");
|
||||
XCTAssertEqual(self.client.connectionReuseCount, 9,
|
||||
@"Should reuse connection 9 times");
|
||||
}
|
||||
|
||||
#pragma mark - S. 空闲超时详细测试
|
||||
|
||||
// S.1 混合过期和有效连<EFBFBD><EFBFBD>?- 选择性清<EFBFBD><EFBFBD>?
|
||||
- (void)testIdleTimeout_MixedExpiredValid_SelectivePruning {
|
||||
[self.client resetPoolStatistics];
|
||||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||||
|
||||
// 创建第一个连<EFBFBD><EFBFBD>?
|
||||
NSError *error1 = nil;
|
||||
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||||
userAgent:@"ConnectionA"
|
||||
timeout:15.0
|
||||
error:&error1];
|
||||
XCTAssertNotNil(response1);
|
||||
XCTAssertEqual(response1.statusCode, 200);
|
||||
|
||||
// 等待连接归还
|
||||
[NSThread sleepForTimeInterval:0.5];
|
||||
|
||||
// 使用 DEBUG API 获取连接 A 并设置为过期<EFBFBD><EFBFBD>?5 秒前<EFBFBD><EFBFBD>?
|
||||
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
|
||||
XCTAssertEqual(connections.count, 1, @"Should have 1 connection in pool");
|
||||
|
||||
HttpdnsNWReusableConnection *connectionA = connections.firstObject;
|
||||
NSDate *expiredDate = [NSDate dateWithTimeIntervalSinceNow:-35.0];
|
||||
[connectionA debugSetLastUsedDate:expiredDate];
|
||||
|
||||
// 创建第二个连接(通过并发请求<EFBFBD><EFBFBD>?
|
||||
NSError *error2 = nil;
|
||||
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||||
userAgent:@"ConnectionB"
|
||||
timeout:15.0
|
||||
error:&error2];
|
||||
XCTAssertNotNil(response2);
|
||||
|
||||
// 等待归还
|
||||
[NSThread sleepForTimeInterval:0.5];
|
||||
|
||||
// 验证:应该有 1 个连接(connectionA 过期被移除,connectionB 留下<EFBFBD><EFBFBD>?
|
||||
connections = [self.client connectionsInPoolForKey:poolKey];
|
||||
XCTAssertEqual(connections.count, 1,
|
||||
@"Should have only 1 connection (expired A removed, valid B kept)");
|
||||
|
||||
// 第三个请求应该复<EFBFBD><EFBFBD>?connectionB
|
||||
[self.client resetPoolStatistics];
|
||||
NSError *error3 = nil;
|
||||
HttpdnsNWHTTPClientResponse *response3 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||||
userAgent:@"ReuseB"
|
||||
timeout:15.0
|
||||
error:&error3];
|
||||
XCTAssertNotNil(response3);
|
||||
|
||||
// 验证:复用了 connectionB(没有创建新连接<EFBFBD><EFBFBD>?
|
||||
XCTAssertEqual(self.client.connectionCreationCount, 0,
|
||||
@"Should not create new connection (reuse existing valid connection)");
|
||||
XCTAssertEqual(self.client.connectionReuseCount, 1,
|
||||
@"Should reuse the valid connection B");
|
||||
}
|
||||
|
||||
// S.2 In-Use 保护 - 使用中的连接不会过期
|
||||
- (void)testIdleTimeout_InUseProtection_ActiveConnectionNotPruned {
|
||||
[self.client resetPoolStatistics];
|
||||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||||
|
||||
// 创建第一个连<EFBFBD><EFBFBD>?
|
||||
NSError *error1 = nil;
|
||||
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||||
userAgent:@"Initial"
|
||||
timeout:15.0
|
||||
error:&error1];
|
||||
XCTAssertNotNil(response1);
|
||||
|
||||
[NSThread sleepForTimeInterval:0.5];
|
||||
|
||||
// 借出连接并保<EFBFBD><EFBFBD>?inUse=YES
|
||||
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
|
||||
XCTAssertEqual(connections.count, 1);
|
||||
|
||||
HttpdnsNWReusableConnection *conn = connections.firstObject;
|
||||
|
||||
// 手动设置<EFBFBD><EFBFBD>?60 秒前(远<EFBFBD><EFBFBD>?30 秒超时)
|
||||
NSDate *veryOldDate = [NSDate dateWithTimeIntervalSinceNow:-60.0];
|
||||
[conn debugSetLastUsedDate:veryOldDate];
|
||||
|
||||
// 将连接标记为使用<EFBFBD><EFBFBD>?
|
||||
[conn debugSetInUse:YES];
|
||||
|
||||
// 触发清理(通过发起另一个并发请求)
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
__block NSInteger connectionsBefore = 0;
|
||||
__block NSInteger connectionsAfter = 0;
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
connectionsBefore = [self.client totalConnectionCount];
|
||||
|
||||
// 发起请求(会触发 pruneConnectionPool<EFBFBD><EFBFBD>?
|
||||
NSError *error2 = nil;
|
||||
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||||
userAgent:@"TriggerPrune"
|
||||
timeout:15.0
|
||||
error:&error2];
|
||||
|
||||
[NSThread sleepForTimeInterval:0.5];
|
||||
connectionsAfter = [self.client totalConnectionCount];
|
||||
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
});
|
||||
|
||||
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 20 * NSEC_PER_SEC));
|
||||
|
||||
// 清理:重<EFBFBD><EFBFBD>?inUse 状<EFBFBD><EFBFBD>?
|
||||
[conn debugSetInUse:NO];
|
||||
|
||||
// 验证:inUse=YES 的连接不应该被清<EFBFBD><EFBFBD>?
|
||||
// connectionsBefore = 1 (旧连<EFBFBD><EFBFBD>?, connectionsAfter = 2 (旧连<EFBFBD><EFBFBD>?+ 新连<EFBFBD><EFBFBD>?
|
||||
XCTAssertEqual(connectionsBefore, 1,
|
||||
@"Should have 1 connection before (in-use protected)");
|
||||
XCTAssertEqual(connectionsAfter, 2,
|
||||
@"Should have 2 connections after (in-use connection NOT pruned, new connection added)");
|
||||
}
|
||||
|
||||
// S.3 所有连接过<EFBFBD><EFBFBD>?- 批量清理
|
||||
- (void)testIdleTimeout_AllExpired_BulkPruning {
|
||||
[self.client resetPoolStatistics];
|
||||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||||
|
||||
// 创建 4 个连接(填满池)
|
||||
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;
|
||||
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||||
userAgent:@"FillPool"
|
||||
timeout:15.0
|
||||
error:&error];
|
||||
XCTAssertNotNil(response);
|
||||
[expectation fulfill];
|
||||
});
|
||||
}
|
||||
|
||||
[self waitForExpectations:expectations timeout:30.0];
|
||||
|
||||
// 等待所有连接归<EFBFBD><EFBFBD>?
|
||||
[NSThread sleepForTimeInterval:1.0];
|
||||
|
||||
// 验证池已<EFBFBD><EFBFBD>?
|
||||
NSUInteger poolSizeBefore = [self.client connectionPoolCountForKey:poolKey];
|
||||
XCTAssertGreaterThan(poolSizeBefore, 0, @"Pool should have connections");
|
||||
|
||||
// 将所有连接设置为过期<EFBFBD><EFBFBD>?1 秒前<EFBFBD><EFBFBD>?
|
||||
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
|
||||
NSDate *expiredDate = [NSDate dateWithTimeIntervalSinceNow:-31.0];
|
||||
for (HttpdnsNWReusableConnection *conn in connections) {
|
||||
[conn debugSetLastUsedDate:expiredDate];
|
||||
}
|
||||
|
||||
// 发起新请求(触发批量清理<EFBFBD><EFBFBD>?
|
||||
NSError *errorNew = nil;
|
||||
HttpdnsNWHTTPClientResponse *responseNew = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||||
userAgent:@"AfterBulkExpiry"
|
||||
timeout:15.0
|
||||
error:&errorNew];
|
||||
XCTAssertNotNil(responseNew, @"Request should succeed after bulk pruning");
|
||||
XCTAssertEqual(responseNew.statusCode, 200);
|
||||
|
||||
// 等待归还
|
||||
[NSThread sleepForTimeInterval:0.5];
|
||||
|
||||
// 验证:池中只有新连接(所有旧连接被清理)
|
||||
NSUInteger poolSizeAfter = [self.client connectionPoolCountForKey:poolKey];
|
||||
XCTAssertEqual(poolSizeAfter, 1,
|
||||
@"Pool should have only 1 connection (new one after bulk pruning)");
|
||||
}
|
||||
|
||||
// S.4 过期后池状态验<EFBFBD><EFBFBD>?
|
||||
- (void)testIdleTimeout_PoolStateAfterExpiry_DirectVerification {
|
||||
[self.client resetPoolStatistics];
|
||||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||||
|
||||
// 创建连接
|
||||
NSError *error1 = nil;
|
||||
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||||
userAgent:@"CreateConnection"
|
||||
timeout:15.0
|
||||
error:&error1];
|
||||
XCTAssertNotNil(response1);
|
||||
|
||||
[NSThread sleepForTimeInterval:0.5];
|
||||
|
||||
// 验证连接在池<EFBFBD><EFBFBD>?
|
||||
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
|
||||
@"Pool should have 1 connection");
|
||||
|
||||
// 设置连接为过<EFBFBD><EFBFBD>?
|
||||
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
|
||||
HttpdnsNWReusableConnection *conn = connections.firstObject;
|
||||
[conn debugSetLastUsedDate:[NSDate dateWithTimeIntervalSinceNow:-31.0]];
|
||||
|
||||
// 发起请求(触发清理)
|
||||
NSError *error2 = nil;
|
||||
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||||
userAgent:@"TriggerPrune"
|
||||
timeout:15.0
|
||||
error:&error2];
|
||||
XCTAssertNotNil(response2);
|
||||
|
||||
[NSThread sleepForTimeInterval:0.5];
|
||||
|
||||
// 直接验证池状态:过期连接已被移除,新连接已加<EFBFBD><EFBFBD>?
|
||||
NSUInteger poolSizeAfter = [self.client connectionPoolCountForKey:poolKey];
|
||||
XCTAssertEqual(poolSizeAfter, 1,
|
||||
@"Pool should have 1 connection (expired removed, new added)");
|
||||
|
||||
// 验证统计:创建了新连接(旧连接过期不可复用)
|
||||
XCTAssertGreaterThanOrEqual(self.client.connectionCreationCount, 1,
|
||||
@"Should have created at least 1 new connection");
|
||||
}
|
||||
|
||||
// S.5 快速过期测试(无需等待<EFBFBD><EFBFBD>? 演示最佳实<EFBFBD><EFBFBD>?
|
||||
- (void)testIdleTimeout_FastExpiry_NoWaiting {
|
||||
[self.client resetPoolStatistics];
|
||||
NSString *poolKey = @"127.0.0.1:11080:tcp";
|
||||
|
||||
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
|
||||
|
||||
// 第一个请求:创建连接
|
||||
NSError *error1 = nil;
|
||||
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||||
userAgent:@"FastTest1"
|
||||
timeout:15.0
|
||||
error:&error1];
|
||||
XCTAssertNotNil(response1);
|
||||
XCTAssertEqual(response1.statusCode, 200);
|
||||
XCTAssertEqual(self.client.connectionCreationCount, 1, @"Should create 1 connection");
|
||||
|
||||
[NSThread sleepForTimeInterval:0.5];
|
||||
|
||||
// 使用 DEBUG 辅助函数模拟 31 秒过期(无需实际等待<EFBFBD><EFBFBD>?
|
||||
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
|
||||
XCTAssertEqual(connections.count, 1);
|
||||
|
||||
HttpdnsNWReusableConnection *conn = connections.firstObject;
|
||||
NSDate *expiredDate = [NSDate dateWithTimeIntervalSinceNow:-31.0];
|
||||
[conn debugSetLastUsedDate:expiredDate];
|
||||
|
||||
// 第二个请求:应该检测到过期并创建新连接
|
||||
[self.client resetPoolStatistics];
|
||||
NSError *error2 = nil;
|
||||
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
|
||||
userAgent:@"FastTest2"
|
||||
timeout:15.0
|
||||
error:&error2];
|
||||
XCTAssertNotNil(response2);
|
||||
XCTAssertEqual(response2.statusCode, 200);
|
||||
|
||||
// 验证:创建了新连接(而非复用过期的)
|
||||
XCTAssertEqual(self.client.connectionCreationCount, 1,
|
||||
@"Should create new connection (expired connection not reused)");
|
||||
XCTAssertEqual(self.client.connectionReuseCount, 0,
|
||||
@"Should not reuse expired connection");
|
||||
|
||||
CFAbsoluteTime elapsed = CFAbsoluteTimeGetCurrent() - startTime;
|
||||
|
||||
// 关键验证:测试应该很快完成(< 5 秒),而非等待 30+ <EFBFBD><EFBFBD>?
|
||||
XCTAssertLessThan(elapsed, 5.0,
|
||||
@"Fast expiry test should complete quickly (%.1fs) without 30s wait", elapsed);
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user