带阿里标识的版本

This commit is contained in:
robin
2026-02-28 18:55:33 +08:00
parent 150799f41d
commit 5d0b7c7e91
477 changed files with 10813 additions and 4044 deletions

View File

@@ -1,9 +1,9 @@
//
// DBTest.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// Created by xuyecan on 2025/3/15.
// Copyright © 2025 alibaba-inc.com. All rights reserved.
// Copyright © 2025 trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>

View File

@@ -1,9 +1,9 @@
//
// CacheKeyFunctionTest.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// Created by xuyecan on 2024/6/12.
// Copyright © 2024 alibaba-inc.com. All rights reserved.
// Copyright © 2024 trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>
@@ -70,7 +70,7 @@ static NSString *sdnsHost = @"sdns1.onlyforhttpdnstest.run.place";
//
[self.httpdns.requestManager cleanAllHostMemoryCache];
// db
// db<EFBFBD><EFBFBD>?
[self.httpdns.requestManager syncLoadCacheFromDbToMemory];
HttpdnsResult *result = [self.httpdns resolveHostSyncNonBlocking:testHost byIpType:HttpdnsQueryIPTypeIpv4];

View File

@@ -1,9 +1,9 @@
//
// CustomTTLAndCleanCacheTest.m
// AlicloudHttpDNS
// TrustHttpDNS
//
// Created by xuyecan on 2024/6/17.
// Copyright © 2024 alibaba-inc.com. All rights reserved.
// Copyright © 2024 trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>
@@ -38,8 +38,8 @@ static int TEST_CUSTOM_TTL_SECOND = 3;
[super tearDown];
}
- (int64_t)httpdnsHost:(NSString *)host ipType:(AlicloudHttpDNS_IPType)ipType ttl:(int64_t)ttl {
// ttl3
- (int64_t)httpdnsHost:(NSString *)host ipType:(TrustHttpDNS_IPType)ipType ttl:(int64_t)ttl {
// ttl<EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?
NSString *testHost = hostNameIpPrefixMap.allKeys.firstObject;
if ([host isEqual:testHost]) {
return TEST_CUSTOM_TTL_SECOND;

View File

@@ -1,9 +1,9 @@
//
// EnableReuseExpiredIpTest.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// Created by xuyecan on 2024/5/28.
// Copyright © 2024 alibaba-inc.com. All rights reserved.
// Copyright © 2024 trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>
@@ -39,8 +39,8 @@ static int ttlForTest = 3;
self.currentTimeStamp = [[NSDate date] timeIntervalSince1970];
}
- (int64_t)httpdnsHost:(NSString *)host ipType:(AlicloudHttpDNS_IPType)ipType ttl:(int64_t)ttl {
//
- (int64_t)httpdnsHost:(NSString *)host ipType:(TrustHttpDNS_IPType)ipType ttl:(int64_t)ttl {
// <EFBFBD><EFBFBD>?
return ttlForTest;
}
@@ -73,7 +73,7 @@ static int ttlForTest = 3;
XCTAssertTrue([result2.host isEqualToString:host]);
XCTAssertGreaterThan(result2.ttl, 0);
XCTAssertLessThanOrEqual(result2.ttl, ttlForTest);
//
// <EFBFBD><EFBFBD>?
XCTAssertGreaterThan([[NSDate date] timeIntervalSince1970], result2.lastUpdatedTimeInterval + result2.ttl);
NSString *firstIp2 = [result2 firstIpv4Address];
XCTAssertTrue([firstIp2 hasPrefix:ipPrefix]);
@@ -81,13 +81,13 @@ static int ttlForTest = 3;
//
[NSThread sleepForTimeInterval:1];
// 使nonblocking
// 使nonblocking<EFBFBD><EFBFBD>?
HttpdnsResult *result3 = [self.httpdns resolveHostSyncNonBlocking:host byIpType:HttpdnsQueryIPTypeIpv4];
XCTAssertNotNil(result3);
XCTAssertTrue([result3.host isEqualToString:host]);
XCTAssertGreaterThan(result3.ttl, 0);
XCTAssertLessThanOrEqual(result3.ttl, ttlForTest);
//
// <EFBFBD><EFBFBD>?
XCTAssertLessThan([[NSDate date] timeIntervalSince1970], result3.lastUpdatedTimeInterval + result3.ttl);
NSString *firstIp3 = [result3 firstIpv4Address];
XCTAssertTrue([firstIp3 hasPrefix:ipPrefix]);

View File

@@ -1,9 +1,9 @@
//
// HttpdnsHostObjectTest.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// Created by xuyecan on 2025/3/14.
// Copyright © 2025 alibaba-inc.com. All rights reserved.
// Copyright © 2025 trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>
@@ -26,19 +26,19 @@
[super tearDown];
}
#pragma mark -
#pragma mark - <EFBFBD><EFBFBD>?
- (void)testHostObjectProperties {
// HttpdnsHostObject
HttpdnsHostObject *hostObject = [[HttpdnsHostObject alloc] init];
//
// <EFBFBD><EFBFBD>?
hostObject.host = @"example.com";
hostObject.ttl = 60;
hostObject.queryTimes = 1;
hostObject.clientIP = @"192.168.1.1";
//
// <EFBFBD><EFBFBD>?
XCTAssertEqualObjects(hostObject.host, @"example.com", @"host属性应该被正确设置");
XCTAssertEqual(hostObject.ttl, 60, @"ttl属性应该被正确设置");
XCTAssertEqual(hostObject.queryTimes, 1, @"queryTimes属性应该被正确设置");
@@ -51,13 +51,13 @@
// HttpdnsIpObject
HttpdnsIpObject *ipObject = [[HttpdnsIpObject alloc] init];
//
// <EFBFBD><EFBFBD>?
ipObject.ip = @"1.2.3.4";
ipObject.ttl = 300;
ipObject.priority = 10;
ipObject.detectRT = 50; // detectRT
ipObject.detectRT = 50; // detectRT<EFBFBD><EFBFBD>?
//
// <EFBFBD><EFBFBD>?
XCTAssertEqualObjects(ipObject.ip, @"1.2.3.4", @"ip属性应该被正确设置");
XCTAssertEqual(ipObject.ttl, 300, @"ttl属性应该被正确设置");
XCTAssertEqual(ipObject.priority, 10, @"priority属性应该被正确设置");
@@ -68,18 +68,18 @@
// HttpdnsIpObject
HttpdnsIpObject *ipObject = [[HttpdnsIpObject alloc] init];
//
// <EFBFBD><EFBFBD>?
XCTAssertEqual(ipObject.detectRT, -1, @"detectRT的默认值应该是-1");
//
// <EFBFBD><EFBFBD>?
[ipObject setDetectRT:100];
XCTAssertEqual(ipObject.detectRT, 100, @"detectRT应该被正确设置为100");
//
// <EFBFBD><EFBFBD>?
[ipObject setDetectRT:-5];
XCTAssertEqual(ipObject.detectRT, -1, @"设置负值时detectRT应该被设置为-1");
// 0
// <EFBFBD><EFBFBD>?
[ipObject setDetectRT:0];
XCTAssertEqual(ipObject.detectRT, 0, @"detectRT应该被正确设置为0");
}
@@ -102,15 +102,15 @@
ipv6Object.ttl = 600;
ipv6Object.detectRT = 80;
// IP
// IP<EFBFBD><EFBFBD>?
[hostObject addIpv4:ipv4Object];
[hostObject addIpv6:ipv6Object];
// IP
XCTAssertEqual(hostObject.ipv4List.count, 1, @"应该有1个IPv4对象");
XCTAssertEqual(hostObject.ipv6List.count, 1, @"应该有1个IPv6对象");
// IP<EFBFBD><EFBFBD>?
XCTAssertEqual(hostObject.ipv4List.count, 1, @"应该<EFBFBD><EFBFBD>?个IPv4对象");
XCTAssertEqual(hostObject.ipv6List.count, 1, @"应该<EFBFBD><EFBFBD>?个IPv6对象");
// IP
// IP<EFBFBD><EFBFBD>?
HttpdnsIpObject *retrievedIpv4 = hostObject.ipv4List.firstObject;
XCTAssertEqualObjects(retrievedIpv4.ip, @"1.2.3.4", @"IPv4地址应该正确");
XCTAssertEqual(retrievedIpv4.detectRT, 50, @"IPv4的detectRT应该正确");
@@ -127,7 +127,7 @@
HttpdnsHostObject *hostObject = [[HttpdnsHostObject alloc] init];
hostObject.host = @"example.com";
// IP
// IP<EFBFBD><EFBFBD>?
HttpdnsIpObject *ip1 = [[HttpdnsIpObject alloc] init];
ip1.ip = @"1.1.1.1";
ip1.detectRT = 100;
@@ -142,7 +142,7 @@
HttpdnsIpObject *ip4 = [[HttpdnsIpObject alloc] init];
ip4.ip = @"4.4.4.4";
ip4.detectRT = -1; //
ip4.detectRT = -1; // <EFBFBD><EFBFBD>?
// IP
[hostObject addIpv4:ip1];
@@ -155,11 +155,11 @@
//
// ip2(50ms) -> ip1(100ms) -> ip3(200ms) -> ip4(-1ms)
XCTAssertEqual(sortedIps.count, 4, @"应该有4个IP对象");
XCTAssertEqualObjects(sortedIps[0].ip, @"2.2.2.2", @"检测时间最短的IP应该排在第一位");
XCTAssertEqualObjects(sortedIps[1].ip, @"1.1.1.1", @"检测时间第二短的IP应该排在第二位");
XCTAssertEqualObjects(sortedIps[2].ip, @"3.3.3.3", @"检测时间第三短的IP应该排在第三位");
XCTAssertEqualObjects(sortedIps[3].ip, @"4.4.4.4", @"未检测的IP应该排在最后");
XCTAssertEqual(sortedIps.count, 4, @"应该<EFBFBD><EFBFBD>?个IP对象");
XCTAssertEqualObjects(sortedIps[0].ip, @"2.2.2.2", @"IP<EFBFBD><EFBFBD>?);
XCTAssertEqualObjects(sortedIps[1].ip, @"1.1.1.1", @"IP<EFBFBD><EFBFBD>?);
XCTAssertEqualObjects(sortedIps[2].ip, @"3.3.3.3", @"IP<EFBFBD><EFBFBD>?);
XCTAssertEqualObjects(sortedIps[3].ip, @"4.4.4.4", @"IP<EFBFBD><EFBFBD>?);
}
@end
@end

View File

@@ -1,9 +1,9 @@
//
// ManuallyCleanCacheTest.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// Created by xuyecan on 2024/6/17.
// Copyright © 2024 alibaba-inc.com. All rights reserved.
// Copyright © 2024 trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>

View File

@@ -1,9 +1,9 @@
//
// MultithreadCorrectnessTest.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// Created by xuyecan on 2024/5/26.
// Copyright © 2024 alibaba-inc.com. All rights reserved.
// Copyright © 2024 trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>
@@ -36,7 +36,7 @@
[super tearDown];
}
// 线
// 线<EFBFBD><EFBFBD>?
- (void)testNoneBlockingMethodShouldNotBlock {
HttpdnsRequestManager *requestManager = self.httpdns.requestManager;
HttpdnsRequestManager *mockedScheduler = OCMPartialMock(requestManager);
@@ -132,7 +132,7 @@
OCMStub([mockResolver resolve:[OCMArg any] error:(NSError * __autoreleasing *)[OCMArg anyPointer]])
.ignoringNonObjectArgs()
.andDo(^(NSInvocation *invocation) {
// 1.5
// 1.5<EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:1.5];
[invocation setReturnValue:&mockResolverHostObjects];
});
@@ -146,7 +146,7 @@
[self.httpdns resolveHostSync:ipv4OnlyHost byIpType:HttpdnsQueryIPTypeIpv4];
});
//
// <EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:0.5];
NSTimeInterval startTime = [[NSDate date] timeIntervalSince1970];
@@ -156,7 +156,7 @@
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//
//
// 1
// <EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?
HttpdnsResult *result = [self.httpdns resolveHostSync:ipv4OnlyHost byIpType:HttpdnsQueryIPTypeIpv4];
XCTAssertNotNil(result);
XCTAssertTrue([result.host isEqualToString:ipv4OnlyHost]);
@@ -171,7 +171,7 @@
XCTAssert(elapsedTime >= 1, @"elapsedTime should be more than 1s, but is %f", elapsedTime);
XCTAssert(elapsedTime <= 1.5, @"elapsedTime should not be more than 1.5s, but is %f", elapsedTime);
// TODO
// TODO <EFBFBD><EFBFBD>?
// XCTAssert(elapsedTime < 4.1, @"elapsedTime should be less than 4.1s, but is %f", elapsedTime);
}
@@ -191,7 +191,7 @@
//
@throw [NSException exceptionWithName:@"TestException" reason:@"TestException" userInfo:nil];
} else {
//
// <EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:0.4];
[invocation setReturnValue:&mockResolverHostObjects];
}
@@ -206,7 +206,7 @@
[self.httpdns resolveHostSync:ipv4OnlyHost byIpType:HttpdnsQueryIPTypeIpv4];
});
//
// <EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:0.2];
NSTimeInterval startTime = [[NSDate date] timeIntervalSince1970];
@@ -215,8 +215,8 @@
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//
//
// 5
// <EFBFBD><EFBFBD>?
// 5<EFBFBD><EFBFBD>?
HttpdnsResult *result = [self.httpdns resolveHostSync:ipv4OnlyHost byIpType:HttpdnsQueryIPTypeIpv4];
XCTAssertNotNil(result);
XCTAssertTrue([result.host isEqualToString:ipv4OnlyHost]);
@@ -232,7 +232,7 @@
XCTAssert(elapsedTime < 0.8, @"elapsedTime should be less than 0.8s, but is %f", elapsedTime);
}
//
// <EFBFBD><EFBFBD>?
- (void)testSyncMethodSetBlockTimeout {
HttpdnsRequestManager *requestManager = self.httpdns.requestManager;
[self.httpdns cleanAllHostCache];
@@ -266,7 +266,7 @@
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
// 0.5 - 5
// <EFBFBD><EFBFBD>?.5 - 5<EFBFBD><EFBFBD>?
- (void)testLimitResolveTimeoutRange {
HttpdnsRequest *request = [HttpdnsRequest new];
request.host = ipv4OnlyHost;
@@ -285,7 +285,7 @@
XCTAssertEqual(request.resolveTimeoutInSecond, 3.5);
}
//
// <EFBFBD><EFBFBD>?
- (void)testAsyncMethodSetBlockTimeout {
HttpdnsRequestManager *requestManager = self.httpdns.requestManager;
[self.httpdns cleanAllHostCache];
@@ -318,7 +318,7 @@
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
// 线线
// 线线<EFBFBD><EFBFBD>?
- (void)testMultiThreadSyncMethodMaxBlockingTime {
HttpdnsRequestManager *requestManager = self.httpdns.requestManager;
[self.httpdns cleanAllHostCache];

View File

@@ -1,9 +1,9 @@
//
// PresetCacheAndRetrieveTest.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// Created by xuyecan on 2024/5/26.
// Copyright © 2024 alibaba-inc.com. All rights reserved.
// Copyright © 2024 trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>
@@ -16,7 +16,7 @@
/**
* 使OCMockMock(使stopMocking)
* case
* case<EFBFBD><EFBFBD>?
*/
@interface PresetCacheAndRetrieveTest : TestBase
@@ -122,7 +122,7 @@
XCTAssertTrue([result.ipv6s[0] isEqualToString:ipv61]);
// autoipv6only
// ipv4autoipv6ipv6
// ipv4autoipv6ipv6<EFBFBD><EFBFBD>?
// ipv4+ipv6
result = [self.httpdns resolveHostSyncNonBlocking:ipv4AndIpv6Host byIpType:HttpdnsQueryIPTypeAuto];
@@ -180,7 +180,7 @@
XCTAssertTrue([result.ipv6s[0] isEqualToString:ipv61]);
}
// ttllastLookupTimeipv4ipv6
// ttllastLookupTimeipv4ipv6<EFBFBD><EFBFBD>?
- (void)testTTLAndLastLookUpTime {
[self presetNetworkEnvAsIpv4AndIpv6];
[self.httpdns cleanAllHostCache];
@@ -195,7 +195,7 @@
hostObject1.lastIPv4LookupTime = currentTimestamp - 1;
hostObject1.lastIPv6LookupTime = currentTimestamp - 2;
//
// <EFBFBD><EFBFBD>?
[self.httpdns.requestManager mergeLookupResultToManager:hostObject1 host:ipv4AndIpv6Host cacheKey:ipv4AndIpv6Host underQueryIpType:HttpdnsQueryIPTypeBoth];
// autoipv4ipv6
@@ -210,10 +210,10 @@
hostObject2.v4ttl = 600;
hostObject2.lastIPv4LookupTime = currentTimestamp - 10;
// ipv4
// ipv4<EFBFBD><EFBFBD>?
[self.httpdns.requestManager mergeLookupResultToManager:hostObject2 host:ipv4AndIpv6Host cacheKey:ipv4AndIpv6Host underQueryIpType:HttpdnsQueryIPTypeIpv4];
// v4v6
// v4v6<EFBFBD><EFBFBD>?
result = [self.httpdns resolveHostSyncNonBlocking:ipv4AndIpv6Host byIpType:HttpdnsQueryIPTypeAuto];
XCTAssertEqual(result.ttl, hostObject2.v4ttl);
XCTAssertEqual(result.lastUpdatedTimeInterval, hostObject2.lastIPv4LookupTime);
@@ -222,7 +222,7 @@
}
// ipv4ipv6
// ipv6ipv6
// ipv6ipv6<EFBFBD><EFBFBD>?
- (void)testMergeNoIpv6ResultAndGetBoth {
[self presetNetworkEnvAsIpv4AndIpv6];

View File

@@ -1,9 +1,9 @@
//
// ResolvingEffectiveHostTest.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// Created by xuyecan on 2024/5/28.
// Copyright © 2024 alibaba-inc.com. All rights reserved.
// Copyright © 2024 trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>
@@ -42,8 +42,8 @@
[super tearDown];
}
- (int64_t)httpdnsHost:(NSString *)host ipType:(AlicloudHttpDNS_IPType)ipType ttl:(int64_t)ttl {
// ttl1-4
- (int64_t)httpdnsHost:(NSString *)host ipType:(TrustHttpDNS_IPType)ipType ttl:(int64_t)ttl {
// ttl<EFBFBD><EFBFBD>?-4<EFBFBD><EFBFBD>?
return arc4random_uniform(4) + 1;
}
@@ -87,7 +87,7 @@
long long executeStartTimeInMs = [[NSDate date] timeIntervalSince1970] * 1000;
HttpdnsResult *result = [self.httpdns resolveHostSyncNonBlocking:host byIpType:HttpdnsQueryIPTypeIpv4];
long long executeEndTimeInMs = [[NSDate date] timeIntervalSince1970] * 1000;
// 30ms
// <EFBFBD><EFBFBD>?0ms
if (executeEndTimeInMs - executeStartTimeInMs >= 30) {
printf("XCTAssertWillFailed, host: %s, executeTime: %lldms\n", [host UTF8String], executeEndTimeInMs - executeStartTimeInMs);
}

View File

@@ -1,9 +1,9 @@
//
// ScheduleCenterV4Test.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// Created by xuyecan on 2024/6/16.
// Copyright © 2024 alibaba-inc.com. All rights reserved.
// Copyright © 2024 trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>
@@ -20,7 +20,7 @@
/**
* 使OCMockMock(使stopMocking)
* case
* case<EFBFBD><EFBFBD>?
*/
@interface ScheduleCenterV4Test : TestBase

View File

@@ -1,9 +1,9 @@
//
// ScheduleCenterV6Test.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// Created by xuyecan on 2024/6/17.
// Copyright © 2024 alibaba-inc.com. All rights reserved.
// Copyright © 2024 trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <OCMock/OCMock.h>
@@ -18,7 +18,7 @@
/**
* 使OCMockMock(使stopMocking)
* case
* case<EFBFBD><EFBFBD>?
*/
@interface ScheduleCenterV6Test : TestBase

View File

@@ -1,9 +1,9 @@
//
// SdnsScenarioTest.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// Created by xuyecan on 2024/5/29.
// Copyright © 2024 alibaba-inc.com. All rights reserved.
// Copyright © 2024 trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>
@@ -41,8 +41,8 @@ static NSString *sdnsHost = @"sdns1.onlyforhttpdnstest.run.place";
self.currentTimeStamp = [[NSDate date] timeIntervalSince1970];
}
- (int64_t)httpdnsHost:(NSString *)host ipType:(AlicloudHttpDNS_IPType)ipType ttl:(int64_t)ttl {
//
- (int64_t)httpdnsHost:(NSString *)host ipType:(TrustHttpDNS_IPType)ipType ttl:(int64_t)ttl {
// <EFBFBD><EFBFBD>?
return ttlForTest;
}

View File

@@ -1,9 +1,9 @@
//
// IpDetectorTest.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// Created by xuyecan on 2025/3/14.
// Copyright © 2025 alibaba-inc.com. All rights reserved.
// Copyright © 2025 trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>
@@ -25,14 +25,14 @@
[super tearDown];
}
#pragma mark -
#pragma mark - <EFBFBD><EFBFBD>?
- (void)testSharedInstance {
//
HttpdnsIPQualityDetector *detector1 = [HttpdnsIPQualityDetector sharedInstance];
HttpdnsIPQualityDetector *detector2 = [HttpdnsIPQualityDetector sharedInstance];
XCTAssertEqual(detector1, detector2, @"单例模式应该返回相同的实例");
XCTAssertEqual(detector1, detector2, @"<EFBFBD><EFBFBD>?);
XCTAssertNotNil(detector1, @"单例实例不应为nil");
}
@@ -42,7 +42,7 @@
// IP
HttpdnsIPQualityDetector *detector = [HttpdnsIPQualityDetector sharedInstance];
// 使DNS
// 使DNS<EFBFBD><EFBFBD>?
NSInteger costTime = [detector tcpConnectToIP:@"8.8.8.8" port:53];
//
@@ -56,8 +56,8 @@
// 使IP
NSInteger costTime = [detector tcpConnectToIP:@"192.168.255.255" port:12345];
// -1
XCTAssertEqual(costTime, -1, @"连接到无效IP应返回-1");
// <EFBFBD><EFBFBD>?1
XCTAssertEqual(costTime, -1, @"连接到无效IP应返<EFBFBD><EFBFBD>?1");
}
- (void)testTcpConnectWithInvalidParameters {
@@ -66,21 +66,21 @@
// IP
NSInteger costTime = [detector tcpConnectToIP:nil port:80];
XCTAssertEqual(costTime, -1, @"使用nil IP应返回-1");
XCTAssertEqual(costTime, -1, @"使用nil IP应返<EFBFBD><EFBFBD>?1");
// IP
costTime = [detector tcpConnectToIP:@"not-an-ip" port:80];
XCTAssertEqual(costTime, -1, @"使用无效格式IP应返回-1");
XCTAssertEqual(costTime, -1, @"使用无效格式IP应返<EFBFBD><EFBFBD>?1");
//
costTime = [detector tcpConnectToIP:@"8.8.8.8" port:-1];
XCTAssertEqual(costTime, -1, @"使用无效端口应返回-1");
XCTAssertEqual(costTime, -1, @"使用无效端口应返<EFBFBD><EFBFBD>?1");
}
#pragma mark -
- (void)testScheduleIPQualityDetection {
// IP
// IP<EFBFBD><EFBFBD>?
HttpdnsIPQualityDetector *detector = [HttpdnsIPQualityDetector sharedInstance];
id detectorMock = OCMPartialMock(detector);
@@ -110,7 +110,7 @@
HttpdnsIPQualityDetector *detector = [HttpdnsIPQualityDetector sharedInstance];
id detectorMock = OCMPartialMock(detector);
// executeDetection
// executeDetection<EFBFBD><EFBFBD>?
OCMReject([detectorMock executeDetection:[OCMArg any]
ip:[OCMArg any]
port:[OCMArg any]
@@ -146,7 +146,7 @@
HttpdnsIPQualityDetector *detector = [HttpdnsIPQualityDetector sharedInstance];
id detectorMock = OCMPartialMock(detector);
// dispatch_semaphore_wait
// dispatch_semaphore_wait<EFBFBD><EFBFBD>?
// scheduleIPQualityDetectionaddPendingTask
OCMStub([detectorMock scheduleIPQualityDetection:[OCMArg any]
ip:[OCMArg any]
@@ -163,7 +163,7 @@
[invocation getArgument:&port atIndex:4];
[invocation getArgument:&callback atIndex:5];
// addPendingTask
// addPendingTask<EFBFBD><EFBFBD>?
[detector addPendingTask:cacheKey ip:ip port:port callback:callback];
});
@@ -193,14 +193,14 @@
}
- (void)testAddPendingTask {
//
// <EFBFBD><EFBFBD>?
HttpdnsIPQualityDetector *detector = [HttpdnsIPQualityDetector sharedInstance];
id detectorMock = OCMPartialMock(detector);
// processPendingTasksIfNeeded
// processPendingTasksIfNeeded<EFBFBD><EFBFBD>?
OCMStub([detectorMock processPendingTasksIfNeeded]);
//
// <EFBFBD><EFBFBD>?
NSUInteger initialCount = [detector pendingTasksCount];
//
@@ -209,8 +209,8 @@
port:[NSNumber numberWithInt:80]
callback:^(NSString *cacheKey, NSString *ip, NSInteger costTime) {}];
//
XCTAssertEqual([detector pendingTasksCount], initialCount + 1, @"添加任务后待处理任务数量应增加1");
// <EFBFBD><EFBFBD>?
XCTAssertEqual([detector pendingTasksCount], initialCount + 1, @"添加任务后待处理任务数量应增<EFBFBD><EFBFBD>?");
//
[detectorMock stopMocking];
@@ -221,7 +221,7 @@
HttpdnsIPQualityDetector *detector = [HttpdnsIPQualityDetector sharedInstance];
id detectorMock = OCMPartialMock(detector);
// executeDetection
// executeDetection<EFBFBD><EFBFBD>?
OCMStub([detectorMock executeDetection:[OCMArg any]
ip:[OCMArg any]
port:[OCMArg any]
@@ -233,13 +233,13 @@
port:[NSNumber numberWithInt:80]
callback:^(NSString *cacheKey, NSString *ip, NSInteger costTime) {}];
//
// <EFBFBD><EFBFBD>?
[detectorMock processPendingTasksIfNeeded];
//
// <EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:0.1];
//
// <EFBFBD><EFBFBD>?
XCTAssertEqual([detector pendingTasksCount], 0, @"处理后待处理任务数量应为0");
//
@@ -253,7 +253,7 @@
HttpdnsIPQualityDetector *detector = [HttpdnsIPQualityDetector sharedInstance];
id detectorMock = OCMPartialMock(detector);
// tcpConnectToIP
// tcpConnectToIP<EFBFBD><EFBFBD>?
OCMStub([detectorMock tcpConnectToIP:@"1.2.3.4" port:80]).andReturn(100);
//
@@ -265,9 +265,9 @@
port:[NSNumber numberWithInt:80]
callback:^(NSString *cacheKey, NSString *ip, NSInteger costTime) {
//
XCTAssertEqualObjects(cacheKey, @"example.com", @"回调中的cacheKey应正确");
XCTAssertEqualObjects(ip, @"1.2.3.4", @"回调中的IP应正确");
XCTAssertEqual(costTime, 100, @"回调中的耗时应正确");
XCTAssertEqualObjects(cacheKey, @"example.com", @"cacheKey<EFBFBD><EFBFBD>?);
XCTAssertEqualObjects(ip, @"1.2.3.4", @"IP<EFBFBD><EFBFBD>?);
XCTAssertEqual(costTime, 100, @"<EFBFBD><EFBFBD>?);
[expectation fulfill];
}];
@@ -296,8 +296,8 @@
port:[NSNumber numberWithInt:80]
callback:^(NSString *cacheKey, NSString *ip, NSInteger costTime) {
//
XCTAssertEqualObjects(cacheKey, @"example.com", @"回调中的cacheKey应正确");
XCTAssertEqualObjects(ip, @"1.2.3.4", @"回调中的IP应正确");
XCTAssertEqualObjects(cacheKey, @"example.com", @"cacheKey<EFBFBD><EFBFBD>?);
XCTAssertEqualObjects(ip, @"1.2.3.4", @"IP<EFBFBD><EFBFBD>?);
XCTAssertEqual(costTime, -1, @"连接失败时回调中的耗时应为-1");
[expectation fulfill];
@@ -311,11 +311,11 @@
}
- (void)testExecuteDetectionWithNilPort {
// nil
// nil<EFBFBD><EFBFBD>?
HttpdnsIPQualityDetector *detector = [HttpdnsIPQualityDetector sharedInstance];
id detectorMock = OCMPartialMock(detector);
// tcpConnectToIP使80
// tcpConnectToIP使<EFBFBD><EFBFBD>?0
OCMExpect([detectorMock tcpConnectToIP:@"1.2.3.4" port:80]).andReturn(100);
//
@@ -356,9 +356,9 @@
// tcpConnectToIP
OCMStub([detectorMock tcpConnectToIP:[OCMArg any] port:80]).andDo(^(NSInvocation *invocation) {
// GC
// GC<EFBFBD><EFBFBD>?
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//
// <EFBFBD><EFBFBD>?
NSInteger result = 100;
[invocation setReturnValue:&result];
});
@@ -373,24 +373,24 @@
port:[NSNumber numberWithInt:80]
callback:^(NSString *cacheKey, NSString *ip, NSInteger costTime) {
//
XCTAssertEqualObjects(cacheKey, @"example.com", @"回调中的cacheKey应正确");
XCTAssertEqualObjects(ip, @"1.2.3.4", @"回调中的IP应正确");
XCTAssertEqualObjects(cacheKey, @"example.com", @"cacheKey<EFBFBD><EFBFBD>?);
XCTAssertEqualObjects(ip, @"1.2.3.4", @"IP<EFBFBD><EFBFBD>?);
[expectation fulfill];
}];
//
// <EFBFBD><EFBFBD>?
tempCacheKey = nil;
tempIP = nil;
// GCARC
// GCARC<EFBFBD><EFBFBD>?
@autoreleasepool {
//
// <EFBFBD><EFBFBD>?
}
// executeDetection
XCTAssertNotNil(weakCacheKey, @"cacheKey不应被释放");
XCTAssertNotNil(weakIP, @"IP不应被释放");
XCTAssertNotNil(weakCacheKey, @"cacheKey<EFBFBD><EFBFBD>?);
XCTAssertNotNil(weakIP, @"IP<EFBFBD><EFBFBD>?);
//
[self waitForExpectationsWithTimeout:5.0 handler:nil];

View File

@@ -1,11 +1,11 @@
//
// HttpdnsNWHTTPClientTestBase.h
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 alibaba-inc.com. All rights reserved.
// Copyright © 2025 trustapp.com. All rights reserved.
//
// 测试基类 - 为所HttpdnsNWHTTPClient 测试提供共享setup/teardown
// 测试基类 - 为所<EFBFBD><EFBFBD>?HttpdnsNWHTTPClient 测试提供共享<EFBFBD><EFBFBD>?setup/teardown
//
#import <XCTest/XCTest.h>

View File

@@ -1,15 +1,15 @@
//
// HttpdnsNWHTTPClientTestBase.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 alibaba-inc.com. All rights reserved.
// Copyright © 2025 trustapp.com. All rights reserved.
//
// -
//
// mock server
// cd AlicloudHttpDNSTests/Network && python3 mock_server.py
//
// cd TrustHttpDNSTests/Network && python3 mock_server.py
// <EFBFBD><EFBFBD>?
// - HTTP: 11080
// - HTTPS: 11443, 11444, 11445, 11446
//
@@ -21,18 +21,18 @@
- (void)setUp {
[super setUp];
// TLS mock server
//
// <EFBFBD><EFBFBD>?TLS <EFBFBD><EFBFBD>?mock server <EFBFBD><EFBFBD>?
// <EFBFBD><EFBFBD>?
// 1.
// 2. loopback (127.0.0.1)
// 3.
// 2. <EFBFBD><EFBFBD>?loopback (127.0.0.1)
// 3. <EFBFBD><EFBFBD>?
setenv("HTTPDNS_SKIP_TLS_VERIFY", "1", 1);
self.client = [[HttpdnsNWHTTPClient alloc] init];
}
- (void)tearDown {
//
// <EFBFBD><EFBFBD>?
unsetenv("HTTPDNS_SKIP_TLS_VERIFY");
self.client = nil;

View File

@@ -1,9 +1,9 @@
//
// HttpdnsNWHTTPClientTestHelper.h
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 alibaba-inc.com. All rights reserved.
// Copyright © 2025 trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>
@@ -12,20 +12,20 @@ NS_ASSUME_NONNULL_BEGIN
@interface HttpdnsNWHTTPClientTestHelper : NSObject
#pragma mark - HTTP 响应数据构
#pragma mark - HTTP 响应数据构<EFBFBD><EFBFBD>?
// 构造标HTTP 响应数据
// 构造标<EFBFBD><EFBFBD>?HTTP 响应数据
+ (NSData *)createHTTPResponseWithStatus:(NSInteger)statusCode
statusText:(NSString *)statusText
headers:(nullable NSDictionary<NSString *, NSString *> *)headers
body:(nullable NSData *)body;
// 构chunked 编码HTTP 响应
// 构<EFBFBD><EFBFBD>?chunked 编码<EFBFBD><EFBFBD>?HTTP 响应
+ (NSData *)createChunkedHTTPResponseWithStatus:(NSInteger)statusCode
headers:(nullable NSDictionary<NSString *, NSString *> *)headers
chunks:(NSArray<NSData *> *)chunks;
// 构chunked 编码HTTP 响应(带 trailers
// 构<EFBFBD><EFBFBD>?chunked 编码<EFBFBD><EFBFBD>?HTTP 响应(带 trailers<EFBFBD><EFBFBD>?
+ (NSData *)createChunkedHTTPResponseWithStatus:(NSInteger)statusCode
headers:(nullable NSDictionary<NSString *, NSString *> *)headers
chunks:(NSArray<NSData *> *)chunks
@@ -36,18 +36,18 @@ NS_ASSUME_NONNULL_BEGIN
// 编码单个 chunk
+ (NSData *)encodeChunk:(NSData *)data;
// 编码单个 chunk带 extension
// 编码单个 chunk带 extension<EFBFBD><EFBFBD>?
+ (NSData *)encodeChunk:(NSData *)data extension:(nullable NSString *)extension;
// 编码终止 chunksize=0
// 编码终止 chunksize=0<EFBFBD><EFBFBD>?
+ (NSData *)encodeLastChunk;
// 编码终止 chunk带 trailers
// 编码终止 chunk带 trailers<EFBFBD><EFBFBD>?
+ (NSData *)encodeLastChunkWithTrailers:(NSDictionary<NSString *, NSString *> *)trailers;
#pragma mark - 测试数据生成
// 生成指定大小的随机数
// 生成指定大小的随机数<EFBFBD><EFBFBD>?
+ (NSData *)randomDataWithSize:(NSUInteger)size;
// 生成 JSON 格式的响应体

View File

@@ -1,16 +1,16 @@
//
// HttpdnsNWHTTPClientTestHelper.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 alibaba-inc.com. All rights reserved.
// Copyright © 2025 trustapp.com. All rights reserved.
//
#import "HttpdnsNWHTTPClientTestHelper.h"
@implementation HttpdnsNWHTTPClientTestHelper
#pragma mark - HTTP
#pragma mark - HTTP <EFBFBD><EFBFBD>?
+ (NSData *)createHTTPResponseWithStatus:(NSInteger)statusCode
statusText:(NSString *)statusText
@@ -28,12 +28,12 @@
}
}
// body Content-Length
// <EFBFBD><EFBFBD>?body <EFBFBD><EFBFBD>?Content-Length<EFBFBD><EFBFBD>?
if (body && body.length > 0 && !headers[@"Content-Length"]) {
[response appendFormat:@"Content-Length: %lu\r\n", (unsigned long)body.length];
}
// body
// <EFBFBD><EFBFBD>?body
[response appendString:@"\r\n"];
NSMutableData *responseData = [[response dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];
@@ -144,7 +144,7 @@
+ (NSData *)randomDataWithSize:(NSUInteger)size {
NSMutableData *data = [NSMutableData dataWithLength:size];
if (SecRandomCopyBytes(kSecRandomDefault, size, data.mutableBytes) != 0) {
// SecRandom 使
// SecRandom 使<EFBFBD><EFBFBD>?
uint8_t *bytes = data.mutableBytes;
for (NSUInteger i = 0; i < size; i++) {
bytes[i] = arc4random_uniform(256);

View File

@@ -1,9 +1,9 @@
//
// HttpdnsNWHTTPClientTests.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 alibaba-inc.com. All rights reserved.
// Copyright © 2025 trustapp.com. All rights reserved.
//
#import <XCTest/XCTest.h>
@@ -33,7 +33,7 @@
#pragma mark - A. HTTP
#pragma mark - A1. Header (9)
#pragma mark - A1. Header (9<EFBFBD><EFBFBD>?
// A1.1
- (void)testParseHTTPHeaders_ValidResponse_Success {
@@ -88,7 +88,7 @@
XCTAssertEqual(result, HttpdnsHTTPHeaderParseResultSuccess);
XCTAssertEqual(headers.count, testHeaders.count);
// key
// <EFBFBD><EFBFBD>?key
XCTAssertEqualObjects(headers[@"content-type"], @"application/json");
XCTAssertEqualObjects(headers[@"content-length"], @"123");
XCTAssertEqualObjects(headers[@"connection"], @"keep-alive");
@@ -96,7 +96,7 @@
XCTAssertEqualObjects(headers[@"cache-control"], @"no-cache");
}
// A1.3
// A1.3 <EFBFBD><EFBFBD>?
- (void)testParseHTTPHeaders_IncompleteData_ReturnsIncomplete {
NSString *incompleteResponse = @"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n";
NSData *data = [incompleteResponse dataUsingEncoding:NSUTF8StringEncoding];
@@ -152,10 +152,10 @@
error:&error];
XCTAssertEqual(result, HttpdnsHTTPHeaderParseResultSuccess);
XCTAssertEqualObjects(headers[@"content-type"], @"application/json"); // trim
XCTAssertEqualObjects(headers[@"content-type"], @"application/json"); // <EFBFBD><EFBFBD>?trim
}
// A1.6
// A1.6 <EFBFBD><EFBFBD>?
- (void)testParseHTTPHeaders_EmptyHeaderValue_HandledGracefully {
NSString *responseWithEmptyValue = @"HTTP/1.1 200 OK\r\nX-Empty-Header:\r\n\r\n";
NSData *data = [responseWithEmptyValue dataUsingEncoding:NSUTF8StringEncoding];
@@ -175,7 +175,7 @@
XCTAssertEqualObjects(headers[@"x-empty-header"], @"");
}
// A1.7
// A1.7 <EFBFBD><EFBFBD>?
- (void)testParseHTTPHeaders_NonNumericStatusCode_ReturnsError {
NSString *invalidStatusCode = @"HTTP/1.1 ABC OK\r\n\r\n";
NSData *data = [invalidStatusCode dataUsingEncoding:NSUTF8StringEncoding];
@@ -213,7 +213,7 @@
XCTAssertEqual(result, HttpdnsHTTPHeaderParseResultError);
}
// A1.9
// A1.9 <EFBFBD><EFBFBD>?
- (void)testParseHTTPHeaders_HeaderWithoutColon_Skipped {
NSString *responseWithInvalidHeader = @"HTTP/1.1 200 OK\r\nInvalidHeader\r\nContent-Type: application/json\r\n\r\n";
NSData *data = [responseWithInvalidHeader dataUsingEncoding:NSUTF8StringEncoding];
@@ -233,7 +233,7 @@
XCTAssertEqualObjects(headers[@"content-type"], @"application/json"); //
}
#pragma mark - A2. Chunked (8)
#pragma mark - A2. Chunked <EFBFBD><EFBFBD>?(8<EFBFBD><EFBFBD>?
// A2.1 chunk
- (void)testCheckChunkedBody_SingleChunk_DetectsComplete {
@@ -269,9 +269,9 @@
XCTAssertNil(error);
}
// A2.3 chunk
// A2.3 <EFBFBD><EFBFBD>?chunk
- (void)testCheckChunkedBody_IncompleteChunk_ReturnsIncomplete {
NSString *incompleteChunkBody = @"5\r\nhel"; //
NSString *incompleteChunkBody = @"5\r\nhel"; // <EFBFBD><EFBFBD>?
NSString *response = [NSString stringWithFormat:@"HTTP/1.1 200 OK\r\n\r\n%@", incompleteChunkBody];
NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding];
@@ -285,13 +285,13 @@
XCTAssertEqual(result, HttpdnsHTTPChunkParseResultIncomplete);
}
// A2.4 chunk extension
// A2.4 <EFBFBD><EFBFBD>?chunk extension
- (void)testCheckChunkedBody_WithChunkExtension_Ignored {
NSString *chunkWithExtension = @"5;name=value\r\nhello\r\n0\r\n\r\n";
NSString *response = [NSString stringWithFormat:@"HTTP/1.1 200 OK\r\n\r\n%@", chunkWithExtension];
NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding];
// headerEndIndex \r\n\r\n \r
// headerEndIndex <EFBFBD><EFBFBD>?\r\n\r\n <EFBFBD><EFBFBD>?\r
NSUInteger headerEndIndex = [@"HTTP/1.1 200 OK" length];
NSError *error;
@@ -337,9 +337,9 @@
XCTAssertNotNil(error);
}
// A2.7 CRLF
// A2.7 CRLF <EFBFBD><EFBFBD>?
- (void)testCheckChunkedBody_MissingCRLFTerminator_ReturnsError {
NSString *missingTerminator = @"5\r\nhelloXX0\r\n\r\n"; // hello\r\n
NSString *missingTerminator = @"5\r\nhelloXX0\r\n\r\n"; // <EFBFBD><EFBFBD>?hello\r\n
NSString *response = [NSString stringWithFormat:@"HTTP/1.1 200 OK\r\n\r\n%@", missingTerminator];
NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding];
@@ -354,7 +354,7 @@
XCTAssertNotNil(error);
}
// A2.8 trailers
// A2.8 <EFBFBD><EFBFBD>?trailers
- (void)testCheckChunkedBody_WithTrailers_DetectsComplete {
NSString *chunkWithTrailers = @"5\r\nhello\r\n0\r\nX-Trailer: value\r\nX-Custom: test\r\n\r\n";
NSString *response = [NSString stringWithFormat:@"HTTP/1.1 200 OK\r\n\r\n%@", chunkWithTrailers];
@@ -371,7 +371,7 @@
XCTAssertNil(error);
}
#pragma mark - A3. Chunked (2)
#pragma mark - A3. Chunked (2<EFBFBD><EFBFBD>?
// A3.1 chunks
- (void)testDecodeChunkedBody_MultipleChunks_DecodesCorrectly {
@@ -384,7 +384,7 @@
headers:nil
chunks:chunks];
// chunked body headers
// chunked body <EFBFBD><EFBFBD>?headers<EFBFBD><EFBFBD>?
NSData *headerData = [@"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
NSData *bodyData = [chunkedData subdataWithRange:NSMakeRange(headerData.length, chunkedData.length - headerData.length)];
@@ -409,7 +409,7 @@
XCTAssertNotNil(error);
}
#pragma mark - A4. (6)
#pragma mark - A4. (6<EFBFBD><EFBFBD>?
// A4.1 Content-Length
- (void)testParseResponse_WithContentLength_ParsesCorrectly {
@@ -467,7 +467,7 @@
XCTAssertEqualObjects(bodyString, @"{\"ips\":[]}");
}
// A4.3 body
// A4.3 <EFBFBD><EFBFBD>?body
- (void)testParseResponse_EmptyBody_Success {
NSData *responseData = [HttpdnsNWHTTPClientTestHelper createHTTPResponseWithStatus:204
statusText:@"No Content"
@@ -490,12 +490,12 @@
XCTAssertEqual(body.length, 0);
}
// A4.4 Content-Length
// A4.4 Content-Length <EFBFBD><EFBFBD>?
- (void)testParseResponse_ContentLengthMismatch_LogsButSucceeds {
NSData *bodyData = [@"short" dataUsingEncoding:NSUTF8StringEncoding];
NSData *responseData = [HttpdnsNWHTTPClientTestHelper createHTTPResponseWithStatus:200
statusText:@"OK"
headers:@{@"Content-Length": @"100"} //
headers:@{@"Content-Length": @"100"} // <EFBFBD><EFBFBD>?
body:bodyData];
NSInteger statusCode;
@@ -509,11 +509,11 @@
body:&body
error:&error];
XCTAssertTrue(success); //
XCTAssertTrue(success); // <EFBFBD><EFBFBD>?
XCTAssertEqualObjects(body, bodyData);
}
// A4.5
// A4.5 <EFBFBD><EFBFBD>?
- (void)testParseResponse_EmptyData_ReturnsError {
NSData *emptyData = [NSData data];
@@ -532,7 +532,7 @@
XCTAssertNotNil(error);
}
// A4.6 headers body
// A4.6 headers <EFBFBD><EFBFBD>?body
- (void)testParseResponse_OnlyHeaders_EmptyBody {
NSData *responseData = [HttpdnsNWHTTPClientTestHelper createHTTPResponseWithStatus:200
statusText:@"OK"
@@ -556,7 +556,7 @@
XCTAssertEqual(body.length, 0);
}
#pragma mark - C. (7)
#pragma mark - C. (7<EFBFBD><EFBFBD>?
// C.1 GET
- (void)testBuildHTTPRequest_BasicGET_CorrectFormat {
@@ -572,7 +572,7 @@
XCTAssertTrue([request hasSuffix:@"\r\n\r\n"]);
}
// C.2
// C.2 <EFBFBD><EFBFBD>?
- (void)testBuildHTTPRequest_WithQueryString_Included {
NSURL *url = [NSURL URLWithString:@"http://example.com/path?foo=bar&baz=qux"];
NSString *request = [self.client buildHTTPRequestStringWithURL:url userAgent:nil];
@@ -588,7 +588,7 @@
XCTAssertTrue([request containsString:@"User-Agent: CustomAgent/1.0\r\n"]);
}
// C.4 HTTP
// C.4 HTTP <EFBFBD><EFBFBD>?
- (void)testBuildHTTPRequest_HTTPDefaultPort_NotInHost {
NSURL *url = [NSURL URLWithString:@"http://example.com:80/"];
NSString *request = [self.client buildHTTPRequestStringWithURL:url userAgent:nil];
@@ -597,7 +597,7 @@
XCTAssertFalse([request containsString:@"Host: example.com:80\r\n"]);
}
// C.5 HTTPS
// C.5 HTTPS <EFBFBD><EFBFBD>?
- (void)testBuildHTTPRequest_HTTPSDefaultPort_NotInHost {
NSURL *url = [NSURL URLWithString:@"https://example.com:443/"];
NSString *request = [self.client buildHTTPRequestStringWithURL:url userAgent:nil];
@@ -606,7 +606,7 @@
XCTAssertFalse([request containsString:@"Host: example.com:443\r\n"]);
}
// C.6
// C.6 <EFBFBD><EFBFBD>?
- (void)testBuildHTTPRequest_NonDefaultPort_InHost {
NSURL *url = [NSURL URLWithString:@"http://example.com:8080/"];
NSString *request = [self.client buildHTTPRequestStringWithURL:url userAgent:nil];
@@ -627,7 +627,7 @@
#pragma mark - E. TLS (4)
// TLS SecTrustRef mock
//
// <EFBFBD><EFBFBD>?
// E.1 YES
- (void)testEvaluateServerTrust_ValidCertificate_ReturnsYES {
@@ -647,10 +647,10 @@
// E.4 使 SSL Policy
- (void)testEvaluateServerTrust_WithDomain_UsesSSLPolicy {
// 使 SecPolicyCreateSSL(true, domain)
// 使<EFBFBD><EFBFBD>?SecPolicyCreateSSL(true, domain)
}
#pragma mark - F. (5)
#pragma mark - F. (5<EFBFBD><EFBFBD>?
// F.1 URL
- (void)testPerformRequest_VeryLongURL_HandlesCorrectly {
@@ -666,7 +666,7 @@
XCTAssertTrue(request.length > 5000);
}
// F.2 User-Agent
// F.2 <EFBFBD><EFBFBD>?User-Agent
- (void)testBuildRequest_EmptyUserAgent_NoUserAgentHeader {
NSURL *url = [NSURL URLWithString:@"http://example.com/"];
NSString *requestWithNil = [self.client buildHTTPRequestStringWithURL:url userAgent:nil];
@@ -674,7 +674,7 @@
XCTAssertFalse([requestWithNil containsString:@"User-Agent:"]);
}
// F.3
// F.3 <EFBFBD><EFBFBD>?
- (void)testParseResponse_VeryLargeBody_HandlesCorrectly {
NSData *largeBody = [HttpdnsNWHTTPClientTestHelper randomDataWithSize:5 * 1024 * 1024];
NSData *responseData = [HttpdnsNWHTTPClientTestHelper createHTTPResponseWithStatus:200
@@ -697,7 +697,7 @@
XCTAssertEqual(body.length, largeBody.length);
}
// F.4 Chunked 退
// F.4 Chunked 退<EFBFBD><EFBFBD>?
- (void)testParseResponse_ChunkedDecodeFails_FallsBackToRaw {
NSString *badChunked = @"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nBAD_CHUNK_DATA";
NSData *responseData = [badChunked dataUsingEncoding:NSUTF8StringEncoding];
@@ -717,7 +717,7 @@
XCTAssertNotNil(body);
}
// F.5 key
// F.5 <EFBFBD><EFBFBD>?key
- (void)testConnectionPoolKey_DifferentHosts_SeparateKeys {
NSString *key1 = [self.client connectionPoolKeyForHost:@"host1.com" port:@"80" useTLS:NO];
NSString *key2 = [self.client connectionPoolKeyForHost:@"host2.com" port:@"80" useTLS:NO];

View File

@@ -1,12 +1,12 @@
//
// HttpdnsNWHTTPClient_BasicIntegrationTests.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 alibaba-inc.com. All rights reserved.
// Copyright © 2025 trustapp.com. All rights reserved.
//
// - (G) (J)
// 12 G:7 + J:5
// - (G) <EFBFBD><EFBFBD>?(J) <EFBFBD><EFBFBD>?
// <EFBFBD><EFBFBD>?2 G:7 + J:5<EFBFBD><EFBFBD>?
//
#import "HttpdnsNWHTTPClientTestBase.h"
@@ -134,7 +134,7 @@
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
// httpbin.org/stream-bytes chunked
// httpbin.org/stream-bytes chunked <EFBFBD><EFBFBD>?
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/stream-bytes/1024"
userAgent:@"HttpdnsNWHTTPClient/1.0"
timeout:15.0
@@ -145,7 +145,7 @@
XCTAssertEqual(response.statusCode, 200);
XCTAssertEqual(response.body.length, 1024, @"Should receive exactly 1024 bytes");
// Transfer-Encoding
// Transfer-Encoding <EFBFBD><EFBFBD>?
NSString *transferEncoding = response.headers[@"transfer-encoding"];
if (transferEncoding) {
XCTAssertTrue([transferEncoding containsString:@"chunked"], @"Should use chunked encoding");
@@ -157,7 +157,7 @@
[self waitForExpectations:@[expectation] timeout:20.0];
}
#pragma mark -
#pragma mark - <EFBFBD><EFBFBD>?
// G.6
- (void)testIntegration_RequestTimeout_ReturnsError {
@@ -165,7 +165,7 @@
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
// httpbin.org/delay/10 10 2
// httpbin.org/delay/10 <EFBFBD><EFBFBD>?10 2 <EFBFBD><EFBFBD>?
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
userAgent:@"HttpdnsNWHTTPClient/1.0"
timeout:2.0
@@ -180,7 +180,7 @@
[self waitForExpectations:@[expectation] timeout:5.0];
}
// G.7
// G.7 <EFBFBD><EFBFBD>?
- (void)testIntegration_CustomHeaders_Reflected {
XCTestExpectation *expectation = [self expectationWithDescription:@"Custom headers"];
@@ -194,7 +194,7 @@
XCTAssertNotNil(response);
XCTAssertEqual(response.statusCode, 200);
// JSON User-Agent
// JSON User-Agent <EFBFBD><EFBFBD>?
NSError *jsonError = nil;
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:response.body
options:0
@@ -212,7 +212,7 @@
#pragma mark - J.
// J.1 31
// J.1 <EFBFBD><EFBFBD>?1
- (void)testConnectionReuse_Expiry31Seconds_NewConnectionCreated {
if (getenv("SKIP_SLOW_TESTS")) {
return;
@@ -252,12 +252,12 @@
[self waitForExpectations:@[expectation] timeout:70.0];
}
// J.2
// J.2 <EFBFBD><EFBFBD>?
- (void)testConnectionReuse_TenRequests_OnlyFourConnectionsKept {
XCTestExpectation *expectation = [self expectationWithDescription:@"Pool size limit"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 10
// 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"
@@ -267,7 +267,7 @@
XCTAssertTrue(response != nil || error != nil);
}
//
// <EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:1.0];
//
@@ -330,7 +330,7 @@
error:&httpError];
XCTAssertTrue(httpResponse != nil || httpError != nil);
// HTTPS 使 key
// HTTPS 使<EFBFBD><EFBFBD>?key<EFBFBD><EFBFBD>?
NSError *httpsError = nil;
HttpdnsNWHTTPClientResponse *httpsResponse = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"HTTPS"
@@ -345,7 +345,7 @@
[self waitForExpectations:@[expectation] timeout:35.0];
}
// J.5
// J.5 <EFBFBD><EFBFBD>?
- (void)testConnectionReuse_TwentyRequestsOneSecondApart_ConnectionKeptAlive {
if (getenv("SKIP_SLOW_TESTS")) {
return;
@@ -359,7 +359,7 @@
// 201
for (NSInteger i = 0; i < 20; i++) {
// 1
// 1<EFBFBD><EFBFBD>?
if (i > 0) {
[NSThread sleepForTimeInterval:1.0];
}
@@ -381,10 +381,10 @@
}
}
//
// <EFBFBD><EFBFBD>?
XCTAssertGreaterThan(successCount, 15, @"Most requests should succeed with connection reuse");
// 使keep-alive
// 使keep-alive<EFBFBD><EFBFBD>?
if (requestTimes.count >= 10) {
double firstRequestTime = [requestTimes[0] doubleValue];
double laterAvgTime = 0;
@@ -399,7 +399,7 @@
[expectation fulfill];
});
// : 19sleep + 20×~2 = 5950退
// : 19sleep + 20×~2<EFBFBD><EFBFBD>?= 5950退
[self waitForExpectations:@[expectation] timeout:50.0];
}

View File

@@ -1,12 +1,12 @@
//
// HttpdnsNWHTTPClient_ConcurrencyTests.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 alibaba-inc.com. All rights reserved.
// Copyright © 2025 trustapp.com. All rights reserved.
//
// - (H) (I) (N)
// 13 H:5 + I:5 + N:3
// - (H)<EFBFBD><EFBFBD>?(I) (N) <EFBFBD><EFBFBD>?
// <EFBFBD><EFBFBD>?3 H:5 + I:5 + N:3<EFBFBD><EFBFBD>?
//
#import "HttpdnsNWHTTPClientTestBase.h"
@@ -137,7 +137,7 @@
[self waitForExpectations:expectations timeout:40.0];
}
// H.4
// H.4 <EFBFBD><EFBFBD>?
- (void)testConcurrency_HighLoad50Concurrent_NoDeadlock {
NSInteger concurrentCount = 50;
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
@@ -169,7 +169,7 @@
[self waitForExpectations:expectations timeout:60.0];
//
// <EFBFBD><EFBFBD>?
XCTAssertGreaterThan(successCount, concurrentCount * 0.8, @"At least 80%% should succeed");
}
@@ -230,14 +230,14 @@
[self waitForExpectations:@[serialExpectation, parallel1, parallel2, parallel3] timeout:60.0];
}
#pragma mark - I.
#pragma mark - I. <EFBFBD><EFBFBD>?
// I.1
// I.1 <EFBFBD><EFBFBD>?
- (void)testRaceCondition_ExceedPoolCapacity_MaxFourConnections {
XCTestExpectation *expectation = [self expectationWithDescription:@"Pool capacity test"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 10
// <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"
@@ -251,7 +251,7 @@
[NSThread sleepForTimeInterval:1.0];
//
// 4
// <EFBFBD><EFBFBD>?4 <EFBFBD><EFBFBD>?
[expectation fulfill];
});
@@ -276,7 +276,7 @@
timeout:15.0
error:&error];
XCTAssertTrue(response != nil || error != nil);
//
// <EFBFBD><EFBFBD>?
[expectation fulfill];
});
@@ -284,15 +284,15 @@
[self waitForExpectations:expectations timeout:30.0];
//
// <EFBFBD><EFBFBD>?
}
// I.3 --
// I.3 --<EFBFBD><EFBFBD>?
- (void)testRaceCondition_AcquireReturnReacquire_CorrectState {
XCTestExpectation *expectation = [self expectationWithDescription:@"Acquire-Return-Reacquire"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//
// <EFBFBD><EFBFBD>?
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"First"
@@ -300,7 +300,7 @@
error:&error1];
XCTAssertTrue(response1 != nil || error1 != nil);
//
// <EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:0.1];
//
@@ -317,7 +317,7 @@
[self waitForExpectations:@[expectation] timeout:35.0];
}
// I.4 31
// I.4 <EFBFBD><EFBFBD>?1<EFBFBD><EFBFBD>?
- (void)testRaceCondition_ExpiredConnectionPruning_CreatesNewConnection {
// SKIP_SLOW_TESTS
if (getenv("SKIP_SLOW_TESTS")) {
@@ -335,7 +335,7 @@
error:&error1];
XCTAssertTrue(response1 != nil || error1 != nil);
// 30
// 30<EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:31.0];
//
@@ -352,19 +352,19 @@
[self waitForExpectations:@[expectation] timeout:70.0];
}
// I.5
// I.5 <EFBFBD><EFBFBD>?
- (void)testRaceCondition_ErrorRecovery_PoolRemainsHealthy {
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//
// <EFBFBD><EFBFBD>?
for (NSInteger i = 0; i < 3; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Error %ld", (long)i]];
[expectations addObject:expectation];
dispatch_async(queue, ^{
NSError *error = nil;
// 使
// 使<EFBFBD><EFBFBD>?
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/5"
userAgent:@"ErrorTest"
timeout:1.0
@@ -391,9 +391,9 @@
[self waitForExpectations:@[recoveryExpectation] timeout:20.0];
}
#pragma mark - N.
#pragma mark - N. <EFBFBD><EFBFBD>?
// N.1
// N.1 <EFBFBD><EFBFBD>?
- (void)testConcurrentMultiPort_ParallelKeepAlive_IndependentConnections {
if (getenv("SKIP_SLOW_TESTS")) {
return;
@@ -402,7 +402,7 @@
XCTestExpectation *expectation11443 = [self expectationWithDescription:@"Port 11443 keep-alive"];
XCTestExpectation *expectation11444 = [self expectationWithDescription:@"Port 11444 keep-alive"];
// 线 1 11443 10 1
// 线 1 11443 10 1 <EFBFBD><EFBFBD>?
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSInteger i = 0; i < 10; i++) {
if (i > 0) {
@@ -417,7 +417,7 @@
[expectation11443 fulfill];
});
// 线 2 11444 10 1
// 线 2 11444 10 1 <EFBFBD><EFBFBD>?
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSInteger i = 0; i < 10; i++) {
if (i > 0) {
@@ -449,7 +449,7 @@
portRequestCounts[port] = @0;
}
// 4 100
// 4 <EFBFBD><EFBFBD>?100 <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];
@@ -466,7 +466,7 @@
}
}
// 25
// 25 <EFBFBD><EFBFBD>?
for (NSNumber *port in ports) {
NSInteger count = [portRequestCounts[port] integerValue];
XCTAssertEqual(count, 25, @"Port %@ should receive 25 requests", port);
@@ -478,12 +478,12 @@
[self waitForExpectations:@[expectation] timeout:180.0];
}
// N.3
// N.3 <EFBFBD><EFBFBD>?
- (void)testConcurrentMultiPort_MixedLoadPattern_RobustHandling {
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 1144320
// 11443<EFBFBD><EFBFBD>?0
for (NSInteger i = 0; i < 20; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Heavy11443 %ld", (long)i]];
[expectations addObject:expectation];
@@ -498,7 +498,7 @@
});
}
// 1144410
// 11444<EFBFBD><EFBFBD>?0
for (NSInteger i = 0; i < 10; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Medium11444 %ld", (long)i]];
[expectations addObject:expectation];
@@ -513,7 +513,7 @@
});
}
// 114455
// 11445<EFBFBD><EFBFBD>?
for (NSInteger i = 0; i < 5; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Light11445 %ld", (long)i]];
[expectations addObject:expectation];

View File

@@ -1,12 +1,12 @@
//
// HttpdnsNWHTTPClient_EdgeCasesAndTimeoutTests.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 alibaba-inc.com. All rights reserved.
// Copyright © 2025 trustapp.com. All rights reserved.
//
// - (M) (P) Connection (R)
// 15 M:4 + P:6 + R:5
// <EFBFBD><EFBFBD>?- (M)<EFBFBD><EFBFBD>?(P) <EFBFBD><EFBFBD>?Connection (R) <EFBFBD><EFBFBD>?
// <EFBFBD><EFBFBD>?5 M:4 + P:6 + R:5<EFBFBD><EFBFBD>?
//
#import "HttpdnsNWHTTPClientTestBase.h"
@@ -17,14 +17,14 @@
@implementation HttpdnsNWHTTPClient_EdgeCasesAndTimeoutTests
#pragma mark - M.
#pragma mark - M. <EFBFBD><EFBFBD>?
// M.1
- (void)testEdgeCase_ConnectionReuseWithinPortOnly_NotAcross {
XCTestExpectation *expectation = [self expectationWithDescription:@"Reuse boundaries"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// A 11443
// A <EFBFBD><EFBFBD>?11443
CFAbsoluteTime timeA = CFAbsoluteTimeGetCurrent();
NSError *errorA = nil;
HttpdnsNWHTTPClientResponse *responseA = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
@@ -34,7 +34,7 @@
CFAbsoluteTime elapsedA = CFAbsoluteTimeGetCurrent() - timeA;
XCTAssertNotNil(responseA);
// B 11443
// B <EFBFBD><EFBFBD>?11443<EFBFBD><EFBFBD>?
CFAbsoluteTime timeB = CFAbsoluteTimeGetCurrent();
NSError *errorB = nil;
HttpdnsNWHTTPClientResponse *responseB = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
@@ -44,7 +44,7 @@
CFAbsoluteTime elapsedB = CFAbsoluteTimeGetCurrent() - timeB;
XCTAssertNotNil(responseB);
// C 11444
// C <EFBFBD><EFBFBD>?11444<EFBFBD><EFBFBD>?
CFAbsoluteTime timeC = CFAbsoluteTimeGetCurrent();
NSError *errorC = nil;
HttpdnsNWHTTPClientResponse *responseC = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
@@ -54,7 +54,7 @@
CFAbsoluteTime elapsedC = CFAbsoluteTimeGetCurrent() - timeC;
XCTAssertNotNil(responseC);
// D 11444 11444
// D <EFBFBD><EFBFBD>?11444<EFBFBD><EFBFBD>?11444
CFAbsoluteTime timeD = CFAbsoluteTimeGetCurrent();
NSError *errorD = nil;
HttpdnsNWHTTPClientResponse *responseD = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
@@ -76,14 +76,14 @@
[self waitForExpectations:@[expectation] timeout:70.0];
}
// M.2
// M.2 <EFBFBD><EFBFBD>?
- (void)testEdgeCase_HighPortCount_AllPortsManaged {
XCTestExpectation *expectation = [self expectationWithDescription:@"High port count"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445, @11446];
//
// <EFBFBD><EFBFBD>?
for (NSNumber *port in ports) {
NSString *urlString = [NSString stringWithFormat:@"https://127.0.0.1:%@/get", port];
NSError *error = nil;
@@ -94,7 +94,7 @@
XCTAssertNotNil(response, @"First round request to port %@ should succeed", port);
}
//
// <EFBFBD><EFBFBD>?
for (NSNumber *port in ports) {
NSString *urlString = [NSString stringWithFormat:@"https://127.0.0.1:%@/get", port];
NSError *error = nil;
@@ -111,7 +111,7 @@
[self waitForExpectations:@[expectation] timeout:120.0];
}
// M.3 访
// M.3 访<EFBFBD><EFBFBD>?
- (void)testEdgeCase_ConcurrentPoolAccess_NoDataRace {
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
@@ -119,7 +119,7 @@
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445];
NSInteger requestsPerPort = 5;
//
// <EFBFBD><EFBFBD>?
for (NSNumber *port in ports) {
for (NSInteger i = 0; i < requestsPerPort; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Port %@ Req %ld", port, (long)i]];
@@ -132,7 +132,7 @@
userAgent:@"ConcurrentAccess"
timeout:15.0
error:&error];
// 访
// 访<EFBFBD><EFBFBD>?
XCTAssertTrue(response != nil || error != nil);
[expectation fulfill];
});
@@ -160,7 +160,7 @@
error:&error];
}
// 2 11444
// 2 11444<EFBFBD><EFBFBD>?
for (NSInteger i = 0; i < 5; i++) {
NSError *error = nil;
[self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
@@ -169,10 +169,10 @@
error:&error];
}
// 30 11443
// 30 <EFBFBD><EFBFBD>?11443 <EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:31.0];
// 3 11444
// 3<EFBFBD><EFBFBD>?11444
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Port11444After"
@@ -180,7 +180,7 @@
error:&error1];
XCTAssertNotNil(response1, @"Port 11444 should still work after 11443 expired");
// 4 11443
// 4<EFBFBD><EFBFBD>?11443 <EFBFBD><EFBFBD>?
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"Port11443New"
@@ -201,11 +201,11 @@
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
//
// <EFBFBD><EFBFBD>?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Pool should be empty initially");
// delay 10s, timeout 1s
// delay 10s, timeout 1s<EFBFBD><EFBFBD>?
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
userAgent:@"TimeoutTest"
@@ -219,7 +219,7 @@
// returnConnection
[NSThread sleepForTimeInterval:0.5];
//
// <EFBFBD><EFBFBD>?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Timed-out connection should be removed from pool");
XCTAssertEqual([self.client totalConnectionCount], 0,
@@ -261,7 +261,7 @@
// returnConnection
[NSThread sleepForTimeInterval:0.5];
// 1
// <EFBFBD><EFBFBD>?1
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
@"Pool should have 1 connection from second request");
@@ -274,14 +274,14 @@
XCTAssertNotNil(response3);
XCTAssertEqual(response3.statusCode, 200);
// 1 + 1 + 1
// <EFBFBD><EFBFBD>? + 1 <EFBFBD><EFBFBD>? 1 <EFBFBD><EFBFBD>?
XCTAssertEqual(self.client.connectionCreationCount, 2,
@"Should have created 2 connections (1 timed out, 1 succeeded)");
XCTAssertEqual(self.client.connectionReuseCount, 1,
@"Third request should reuse second's connection");
}
// P.3
// P.3 <EFBFBD><EFBFBD>?
- (void)testTimeout_ConcurrentPartialTimeout_SuccessfulRequestsReuse {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
@@ -294,7 +294,7 @@
__block NSInteger successCount = 0;
__block NSInteger timeoutCount = 0;
// 10 5 5
// 10 5 5 <EFBFBD><EFBFBD>?
for (NSInteger i = 0; i < 10; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Request %ld", (long)i]];
[expectations addObject:expectation];
@@ -305,11 +305,11 @@
NSTimeInterval timeout;
if (i % 2 == 0) {
//
// <EFBFBD><EFBFBD>?
urlString = @"http://127.0.0.1:11080/get";
timeout = 15.0;
} else {
//
// <EFBFBD><EFBFBD>?
urlString = @"http://127.0.0.1:11080/delay/10";
timeout = 0.5;
}
@@ -339,7 +339,7 @@
XCTAssertEqual(successCount, 5, @"5 requests should succeed");
XCTAssertEqual(timeoutCount, 5, @"5 requests should timeout");
// 5 + 5 = 10
// 5<EFBFBD><EFBFBD>?+ 5<EFBFBD><EFBFBD>?= <EFBFBD><EFBFBD>?0
XCTAssertGreaterThan(self.client.connectionCreationCount, 0,
@"Should have created connections for concurrent requests");
XCTAssertLessThanOrEqual(self.client.connectionCreationCount, 10,
@@ -348,8 +348,8 @@
//
[NSThread sleepForTimeInterval:2.0];
// -
// remote close
// <EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>?
// remote close<EFBFBD><EFBFBD>?
XCTAssertLessThanOrEqual([self.client totalConnectionCount], 4,
@"Total connections should not exceed pool limit (no leak)");
@@ -363,12 +363,12 @@
XCTAssertEqual(recoveryResponse.statusCode, 200, @"Recovery request should succeed");
}
// P.4
// P.4 <EFBFBD><EFBFBD>?
- (void)testTimeout_ConsecutiveTimeouts_NoConnectionLeak {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 10
// 10 <EFBFBD><EFBFBD>?
for (NSInteger i = 0; i < 10; i++) {
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
@@ -381,13 +381,13 @@
[NSThread sleepForTimeInterval:0.2];
}
//
// <EFBFBD><EFBFBD>?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Pool should be empty after consecutive timeouts");
XCTAssertEqual([self.client totalConnectionCount], 0,
@"No connections should leak");
//
// <EFBFBD><EFBFBD>?
XCTAssertEqual(self.client.connectionCreationCount, 10,
@"Should have created 10 connections (all timed out and removed)");
XCTAssertEqual(self.client.connectionReuseCount, 0,
@@ -403,7 +403,7 @@
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// Adelay 10s, timeout 2s
// Adelay 10s, timeout 2s<EFBFBD><EFBFBD>?
dispatch_async(queue, ^{
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
@@ -414,9 +414,9 @@
[timeoutExpectation fulfill];
});
// B A
// B A <EFBFBD><EFBFBD>?
dispatch_async(queue, ^{
// A
// <EFBFBD><EFBFBD>?A <EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:0.1];
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
@@ -430,7 +430,7 @@
XCTAssertNotNil(response, @"Request B should succeed despite A timing out");
XCTAssertEqual(response.statusCode, 200);
// B A
// B <EFBFBD><EFBFBD>?A
XCTAssertLessThan(elapsed, 5.0,
@"Request B should complete quickly, not blocked by A's timeout");
@@ -442,20 +442,20 @@
//
[NSThread sleepForTimeInterval:0.5];
// B
// B <EFBFBD><EFBFBD>?
NSString *poolKey = @"127.0.0.1:11080:tcp";
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
@"Pool should have 1 connection from successful request B");
}
// P.6
// P.6 <EFBFBD><EFBFBD>?
- (void)testTimeout_MultiPort_IsolatedPoolCleaning {
[self.client resetPoolStatistics];
NSString *poolKey11443 = @"127.0.0.1:11443:tls";
NSString *poolKey11444 = @"127.0.0.1:11444:tls";
// 11443
// 11443<EFBFBD><EFBFBD>?
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/delay/10"
userAgent:@"Port11443Timeout"
@@ -463,7 +463,7 @@
error:&error1];
XCTAssertNil(response1, @"Port 11443 request should timeout");
// 11444
// 11444<EFBFBD><EFBFBD>?
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Port11444Success"
@@ -475,7 +475,7 @@
//
[NSThread sleepForTimeInterval:0.5];
// 11443 11444 1
// <EFBFBD><EFBFBD>?11443 11444 <EFBFBD><EFBFBD>?1 <EFBFBD><EFBFBD>?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey11443], 0,
@"Port 11443 pool should be empty (timed out)");
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey11444], 1,
@@ -485,7 +485,7 @@
XCTAssertEqual([self.client totalConnectionCount], 1,
@"Total should be 1 (only from port 11444)");
// 11444
// 11444<EFBFBD><EFBFBD>?
NSError *error3 = nil;
HttpdnsNWHTTPClientResponse *response3 = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Port11444Reuse"
@@ -501,7 +501,7 @@
#pragma mark - R. Connection
// R.1 Connection: close
// R.1 Connection: close <EFBFBD><EFBFBD>?
- (void)testConnectionHeader_Close_ConnectionInvalidated {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
@@ -522,7 +522,7 @@
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Connection with 'Connection: close' header should be invalidated and not returned to pool");
// 2
// 2<EFBFBD><EFBFBD>?
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=keep-alive"
userAgent:@"KeepAliveTest"
@@ -531,7 +531,7 @@
XCTAssertNotNil(response2);
XCTAssertEqual(response2.statusCode, 200);
// 2 close
// 2 close<EFBFBD><EFBFBD>?
XCTAssertEqual(self.client.connectionCreationCount, 2,
@"Should have created 2 connections (first closed by server)");
XCTAssertEqual(self.client.connectionReuseCount, 0,
@@ -559,7 +559,7 @@
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
@"Connection with 'Connection: keep-alive' should be returned to pool");
// 2
// 2<EFBFBD><EFBFBD>?
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=keep-alive"
userAgent:@"KeepAlive2"
@@ -568,7 +568,7 @@
XCTAssertNotNil(response2);
XCTAssertEqual(response2.statusCode, 200);
// 1
// <EFBFBD><EFBFBD>?1 <EFBFBD><EFBFBD>?
XCTAssertEqual(self.client.connectionCreationCount, 1,
@"Should have created only 1 connection");
XCTAssertEqual(self.client.connectionReuseCount, 1,
@@ -592,7 +592,7 @@
// returnConnection
[NSThread sleepForTimeInterval:0.5];
// Proxy-Connection: close
// Proxy-Connection: close <EFBFBD><EFBFBD>?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Connection with 'Proxy-Connection: close' header should be invalidated");
@@ -605,7 +605,7 @@
XCTAssertNotNil(response2);
XCTAssertEqual(response2.statusCode, 200);
// 2
// 2 <EFBFBD><EFBFBD>?
XCTAssertEqual(self.client.connectionCreationCount, 2,
@"Should have created 2 connections (first closed by proxy header)");
XCTAssertEqual(self.client.connectionReuseCount, 0,
@@ -617,7 +617,7 @@
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 1CONNECTION: CLOSE ()
// 1CONNECTION: CLOSE (<EFBFBD><EFBFBD>?
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=close-uppercase"
userAgent:@"UppercaseTest"
@@ -628,11 +628,11 @@
[NSThread sleepForTimeInterval:0.5];
// CLOSE
// CLOSE <EFBFBD><EFBFBD>?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"'CONNECTION: CLOSE' (uppercase) should also close connection");
// 2Connection: Close ()
// 2Connection: Close (<EFBFBD><EFBFBD>?
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=close-mixed"
userAgent:@"MixedCaseTest"
@@ -654,7 +654,7 @@
@"No reuse for any closed connections");
}
// R.5 close keep-alive
// R.5 <EFBFBD><EFBFBD>?close <EFBFBD><EFBFBD>?keep-alive
- (void)testConnectionHeader_ConcurrentMixed_CloseAndKeepAliveIsolated {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
@@ -667,7 +667,7 @@
__block NSInteger closeCount = 0;
__block NSInteger keepAliveCount = 0;
// 10 5 close5 keep-alive
// 10 5 <EFBFBD><EFBFBD>?close<EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>?keep-alive
for (NSInteger i = 0; i < 10; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Request %ld", (long)i]];
[expectations addObject:expectation];
@@ -711,23 +711,23 @@
XCTAssertEqual(closeCount, 5, @"5 close requests should succeed");
XCTAssertEqual(keepAliveCount, 5, @"5 keep-alive requests should succeed");
//
// <EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:1.0];
//
// - close
// - keep-alive remote close
// - 4
// - close <EFBFBD><EFBFBD>?
// - keep-alive remote close<EFBFBD><EFBFBD>?
// - <EFBFBD><EFBFBD>?4<EFBFBD><EFBFBD>?
NSInteger poolSize = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolSize, 4,
@"Pool size should not exceed limit (no leak from close connections)");
// close
// >= 6 ( 5 close + 1 keep-alive close )
// <EFBFBD><EFBFBD>?>= 6 ( 5 <EFBFBD><EFBFBD>?close + 1 <EFBFBD><EFBFBD>?keep-alive<EFBFBD><EFBFBD>?close )
XCTAssertGreaterThanOrEqual(self.client.connectionCreationCount, 6,
@"Should have created at least 6 connections (5 close + at least 1 keep-alive)");
//
// <EFBFBD><EFBFBD>?
NSError *recoveryError = nil;
HttpdnsNWHTTPClientResponse *recoveryResponse = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"RecoveryTest"

View File

@@ -1,12 +1,12 @@
//
// HttpdnsNWHTTPClient_PoolManagementTests.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 alibaba-inc.com. All rights reserved.
// Copyright © 2025 trustapp.com. All rights reserved.
//
// - (K) (L) (O) (S)
// 16 K:5 + L:3 + O:3 + S:5
// <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"
@@ -17,9 +17,9 @@
@implementation HttpdnsNWHTTPClient_PoolManagementTests
#pragma mark - K.
#pragma mark - K. <EFBFBD><EFBFBD>?
// K.1 HTTPS 使
// K.1 HTTPS 使<EFBFBD><EFBFBD>?
- (void)testMultiPort_DifferentHTTPSPorts_SeparatePoolKeys {
XCTestExpectation *expectation = [self expectationWithDescription:@"Different ports use different pools"];
@@ -42,7 +42,7 @@
XCTAssertNotNil(response2, @"First request to port 11444 should succeed");
XCTAssertEqual(response2.statusCode, 200);
// 11443
// 11443<EFBFBD><EFBFBD>?
NSError *error3 = nil;
HttpdnsNWHTTPClientResponse *response3 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"Port11443Again"
@@ -57,7 +57,7 @@
[self waitForExpectations:@[expectation] timeout:50.0];
}
// K.2 HTTPS
// K.2 HTTPS <EFBFBD><EFBFBD>?
- (void)testMultiPort_ConcurrentThreePorts_AllSucceed {
NSInteger requestsPerPort = 10;
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445];
@@ -97,17 +97,17 @@
XCTAssertEqual(successCount, ports.count * requestsPerPort, @"All 30 requests should succeed");
}
// K.3
// 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", // 访 11443
@"https://127.0.0.1:11444/get", // 访 11444
@"https://127.0.0.1:11445/get", // 访 11445
@"https://127.0.0.1:11443/get", // 访 11443
@"https://127.0.0.1:11444/get", // 访 11444
@"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];
@@ -140,7 +140,7 @@
XCTestExpectation *expectation = [self expectationWithDescription:@"Per-port pool limits"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 11443 10
// <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"
@@ -150,7 +150,7 @@
XCTAssertTrue(response != nil || error != nil);
}
// 11444 10
// <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"
@@ -163,7 +163,7 @@
//
[NSThread sleepForTimeInterval:1.0];
//
// <EFBFBD><EFBFBD>?
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"Verify11443"
@@ -193,7 +193,7 @@
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];
@@ -219,7 +219,7 @@
#pragma mark - L.
// L.1
// L.1 <EFBFBD><EFBFBD>?
- (void)testPoolExhaustion_FourPortsSimultaneous_AllSucceed {
NSInteger requestsPerPort = 10;
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445, @11446];
@@ -229,7 +229,7 @@
NSLock *successCountLock = [[NSLock alloc] init];
__block NSInteger successCount = 0;
// 4 10 40
// <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]];
@@ -256,11 +256,11 @@
[self waitForExpectations:expectations timeout:80.0];
// > 95%
// <EFBFBD><EFBFBD>?> 95%
XCTAssertGreaterThan(successCount, 38, @"At least 95%% of 40 requests should succeed");
}
// L.2
// 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);
@@ -268,7 +268,7 @@
__block NSInteger port11444SuccessCount = 0;
NSLock *countLock = [[NSLock alloc] init];
// 11443 20
// <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];
@@ -283,7 +283,7 @@
});
}
// 11444 5 11443
// <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];
@@ -316,7 +316,7 @@
XCTAssertEqual(port11444SuccessCount, 5, @"All port 11444 requests should succeed despite 11443 load");
}
// L.3 使
// L.3 使<EFBFBD><EFBFBD>?
- (void)testPoolExhaustion_MultiPortCleanup_ExpiredConnectionsPruned {
if (getenv("SKIP_SLOW_TESTS")) {
return;
@@ -327,7 +327,7 @@
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;
@@ -338,7 +338,7 @@
XCTAssertTrue(response != nil || error != nil);
}
// 30
// 30 <EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:31.0];
//
@@ -358,14 +358,14 @@
[self waitForExpectations:@[expectation] timeout:80.0];
}
#pragma mark - O. 使 API
#pragma mark - O. 使<EFBFBD><EFBFBD>?API<EFBFBD><EFBFBD>?
// O.1 -
// 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,
@@ -375,7 +375,7 @@
XCTAssertEqual(self.client.connectionReuseCount, 0,
@"Reuse count should be 0 initially");
// 5
// <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"
@@ -386,7 +386,7 @@
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,
@@ -398,7 +398,7 @@
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,
@@ -417,11 +417,11 @@
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);
// 11443 3
// <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"
@@ -431,13 +431,13 @@
XCTAssertNotNil(response);
}
// 11443
// 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");
// 11444 3
// <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"
@@ -455,7 +455,7 @@
XCTAssertEqual([self.client totalConnectionCount], 2,
@"Total should be 2 connections (one per port)");
// 2 4
// 2 <EFBFBD><EFBFBD>?4 <EFBFBD><EFBFBD>?
XCTAssertEqual(self.client.connectionCreationCount, 2,
@"Should create 2 connections (one per port)");
XCTAssertEqual(self.client.connectionReuseCount, 4,
@@ -468,12 +468,12 @@
XCTAssertTrue([allKeys containsObject:key11444], @"Should contain key for port 11444");
}
// O.3
// O.3 <EFBFBD><EFBFBD>?
- (void)testPoolVerification_PoolCapacity_MaxFourConnections {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 10
// <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"
@@ -486,12 +486,12 @@
//
[NSThread sleepForTimeInterval:1.0];
// 4kHttpdnsNWHTTPClientMaxIdleConnectionsPerKey
// 4kHttpdnsNWHTTPClientMaxIdleConnectionsPerKey<EFBFBD><EFBFBD>?
NSUInteger poolSize = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolSize, 4,
@"Pool size should not exceed 4 (actual: %lu)", (unsigned long)poolSize);
// 1
// <EFBFBD><EFBFBD>?1 <EFBFBD><EFBFBD>?
XCTAssertEqual(self.client.connectionCreationCount, 1,
@"Should create only 1 connection for sequential requests");
XCTAssertEqual(self.client.connectionReuseCount, 9,
@@ -500,12 +500,12 @@
#pragma mark - S.
// S.1 -
// 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"
@@ -517,7 +517,7 @@
//
[NSThread sleepForTimeInterval:0.5];
// 使 DEBUG API A 35
// 使 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");
@@ -525,7 +525,7 @@
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"
@@ -536,12 +536,12 @@
//
[NSThread sleepForTimeInterval:0.5];
// 1 connectionA connectionB
// 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)");
// connectionB
// <EFBFBD><EFBFBD>?connectionB
[self.client resetPoolStatistics];
NSError *error3 = nil;
HttpdnsNWHTTPClientResponse *response3 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
@@ -550,7 +550,7 @@
error:&error3];
XCTAssertNotNil(response3);
// connectionB
// connectionB<EFBFBD><EFBFBD>?
XCTAssertEqual(self.client.connectionCreationCount, 0,
@"Should not create new connection (reuse existing valid connection)");
XCTAssertEqual(self.client.connectionReuseCount, 1,
@@ -562,7 +562,7 @@
[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"
@@ -572,17 +572,17 @@
[NSThread sleepForTimeInterval:0.5];
// inUse=YES
// <EFBFBD><EFBFBD>?inUse=YES
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
XCTAssertEqual(connections.count, 1);
HttpdnsNWReusableConnection *conn = connections.firstObject;
// 60 30
// <EFBFBD><EFBFBD>?60 <EFBFBD><EFBFBD>?30
NSDate *veryOldDate = [NSDate dateWithTimeIntervalSinceNow:-60.0];
[conn debugSetLastUsedDate:veryOldDate];
// 使
// 使<EFBFBD><EFBFBD>?
[conn debugSetInUse:YES];
//
@@ -593,7 +593,7 @@
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
connectionsBefore = [self.client totalConnectionCount];
// pruneConnectionPool
// pruneConnectionPool<EFBFBD><EFBFBD>?
NSError *error2 = nil;
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"TriggerPrune"
@@ -608,18 +608,18 @@
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 20 * NSEC_PER_SEC));
// inUse
// <EFBFBD><EFBFBD>?inUse <EFBFBD><EFBFBD>?
[conn debugSetInUse:NO];
// inUse=YES
// connectionsBefore = 1 (), connectionsAfter = 2 ( + )
// 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 -
// S.3 <EFBFBD><EFBFBD>?-
- (void)testIdleTimeout_AllExpired_BulkPruning {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
@@ -645,21 +645,21 @@
[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");
// 31
// <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"
@@ -677,7 +677,7 @@
@"Pool should have only 1 connection (new one after bulk pruning)");
}
// S.4
// S.4 <EFBFBD><EFBFBD>?
- (void)testIdleTimeout_PoolStateAfterExpiry_DirectVerification {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
@@ -692,11 +692,11 @@
[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]];
@@ -711,7 +711,7 @@
[NSThread sleepForTimeInterval:0.5];
//
// <EFBFBD><EFBFBD>?
NSUInteger poolSizeAfter = [self.client connectionPoolCountForKey:poolKey];
XCTAssertEqual(poolSizeAfter, 1,
@"Pool should have 1 connection (expired removed, new added)");
@@ -721,7 +721,7 @@
@"Should have created at least 1 new connection");
}
// S.5 -
// S.5 <EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>?
- (void)testIdleTimeout_FastExpiry_NoWaiting {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
@@ -740,7 +740,7 @@
[NSThread sleepForTimeInterval:0.5];
// 使 DEBUG 31
// 使 DEBUG 31 <EFBFBD><EFBFBD>?
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
XCTAssertEqual(connections.count, 1);
@@ -766,7 +766,7 @@
CFAbsoluteTime elapsed = CFAbsoluteTimeGetCurrent() - startTime;
// < 5 30+
// < 5 30+ <EFBFBD><EFBFBD>?
XCTAssertLessThan(elapsed, 5.0,
@"Fast expiry test should complete quickly (%.1fs) without 30s wait", elapsed);
}

View File

@@ -1,12 +1,12 @@
//
// HttpdnsNWHTTPClient_StateMachineTests.m
// AlicloudHttpDNSTests
// TrustHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 alibaba-inc.com. All rights reserved.
// Copyright © 2025 trustapp.com. All rights reserved.
//
// - (Q)
// 17 Q:17
// - <EFBFBD><EFBFBD>?(Q) <EFBFBD><EFBFBD>?
// <EFBFBD><EFBFBD>?7 Q:17<EFBFBD><EFBFBD>?
//
#import "HttpdnsNWHTTPClientTestBase.h"
@@ -17,18 +17,18 @@
@implementation HttpdnsNWHTTPClient_StateMachineTests
#pragma mark - Q.
#pragma mark - Q. <EFBFBD><EFBFBD>?
// Q1.1 LRU
- (void)testStateMachine_PoolOverflowLRU_RemovesOldestByLastUsedDate {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 5
// <EFBFBD><EFBFBD>?
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 5
// 5<EFBFBD><EFBFBD>?
for (NSInteger i = 0; i < 5; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Request %ld", (long)i]];
[expectations addObject:expectation];
@@ -42,15 +42,15 @@
error:&error];
[expectation fulfill];
});
[NSThread sleepForTimeInterval:0.05]; //
[NSThread sleepForTimeInterval:0.05]; // <EFBFBD><EFBFBD>?
}
[self waitForExpectations:expectations timeout:20.0];
//
// <EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:1.0];
// 4LRU
// <EFBFBD><EFBFBD>?4LRU<EFBFBD><EFBFBD>?
NSUInteger poolCount = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolCount, 4,
@"Pool should enforce max 4 connections (LRU)");
@@ -65,7 +65,7 @@
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 10
// <EFBFBD><EFBFBD>?0
for (NSInteger i = 0; i < 10; i++) {
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
@@ -78,12 +78,12 @@
//
[NSThread sleepForTimeInterval:1.0];
// 1
// <EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?
NSUInteger poolCount = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolCount, 1,
@"Pool should have at most 1 connection (rapid sequential reuse)");
// 1
// 1<EFBFBD><EFBFBD>?
XCTAssertEqual(self.client.connectionCreationCount, 1,
@"Should create only 1 connection for sequential requests");
}
@@ -94,7 +94,7 @@
NSString *poolKey11080 = @"127.0.0.1:11080:tcp";
NSString *poolKey11443 = @"127.0.0.1:11443:tls";
// 11080
// <EFBFBD><EFBFBD>?1080
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"Port11080"
@@ -102,7 +102,7 @@
error:&error1];
XCTAssertNotNil(response1);
// 11443
// <EFBFBD><EFBFBD>?1443
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"Port11443"
@@ -113,7 +113,7 @@
//
[NSThread sleepForTimeInterval:0.5];
// 1
// <EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey11080], 1,
@"Port 11080 pool should have 1 connection");
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey11443], 1,
@@ -128,7 +128,7 @@
- (void)testInvariant_PoolSize_NeverExceedsLimit {
[self.client resetPoolStatistics];
// 20
// <EFBFBD><EFBFBD>?0
for (NSInteger i = 0; i < 20; i++) {
NSError *error = nil;
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
@@ -137,7 +137,7 @@
error:&error];
}
//
// <EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:1.5];
// 4
@@ -149,7 +149,7 @@
key, (unsigned long)poolCount);
}
// 4
// 4<EFBFBD><EFBFBD>?
XCTAssertLessThanOrEqual([self.client totalConnectionCount], 4,
@"Total connections should not exceed 4");
}
@@ -162,7 +162,7 @@
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 15
// 15<EFBFBD><EFBFBD>?
for (NSInteger i = 0; i < 15; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Request %ld", (long)i]];
[expectations addObject:expectation];
@@ -182,17 +182,17 @@
//
[NSThread sleepForTimeInterval:1.0];
// 4
// <EFBFBD><EFBFBD>?4<EFBFBD><EFBFBD>?
NSUInteger poolCount = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolCount, 4,
@"Pool should not have duplicates (max 4 connections)");
// 15
// <EFBFBD><EFBFBD>?5<EFBFBD><EFBFBD>?
XCTAssertLessThanOrEqual(self.client.connectionCreationCount, 15,
@"Should not create excessive connections");
}
// Q4.1 30
// Q4.1 <EFBFBD><EFBFBD>?0
- (void)testBoundary_Exactly30Seconds_ConnectionExpired {
if (getenv("SKIP_SLOW_TESTS")) {
return;
@@ -200,7 +200,7 @@
[self.client resetPoolStatistics];
//
// <EFBFBD><EFBFBD>?
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"InitialRequest"
@@ -211,7 +211,7 @@
// 30.530
[NSThread sleepForTimeInterval:30.5];
//
// <EFBFBD><EFBFBD>?
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"AfterExpiry"
@@ -219,18 +219,18 @@
error:&error2];
XCTAssertNotNil(response2);
// 2
// 2<EFBFBD><EFBFBD>?
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 29
// Q4.2 <EFBFBD><EFBFBD>?9<EFBFBD><EFBFBD>?
- (void)testBoundary_Under30Seconds_ConnectionNotExpired {
[self.client resetPoolStatistics];
//
// <EFBFBD><EFBFBD>?
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"InitialRequest"
@@ -249,19 +249,19 @@
error:&error2];
XCTAssertNotNil(response2);
// 1
// <EFBFBD><EFBFBD>?
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 4
// Q4.3 <EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?
- (void)testBoundary_ExactlyFourConnections_AllKept {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 4使4
// 4使<EFBFBD><EFBFBD>?
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
@@ -272,7 +272,7 @@
dispatch_async(queue, ^{
NSError *error = nil;
// 使 /delay/2
// 使 /delay/2 <EFBFBD><EFBFBD>?
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/2"
userAgent:[NSString stringWithFormat:@"Request%ld", (long)i]
timeout:15.0
@@ -280,7 +280,7 @@
[expectation fulfill];
});
[NSThread sleepForTimeInterval:0.05]; //
[NSThread sleepForTimeInterval:0.05]; // <EFBFBD><EFBFBD>?
}
[self waitForExpectations:expectations timeout:20.0];
@@ -288,22 +288,22 @@
//
[NSThread sleepForTimeInterval:1.0];
// 4
// <EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?
NSUInteger poolCount = [self.client connectionPoolCountForKey:poolKey];
XCTAssertEqual(poolCount, 4,
@"Pool should keep all 4 connections (not exceeding limit)");
// 4
// <EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?
XCTAssertEqual(self.client.connectionCreationCount, 4,
@"Should create exactly 4 connections");
}
// Q1.2
// Q1.2 <EFBFBD><EFBFBD>?
- (void)testStateMachine_NormalSequence_StateTransitionsCorrect {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 1使 (CREATING IN_USE IDLE)
// <EFBFBD><EFBFBD>?使<EFBFBD><EFBFBD>?(CREATING <EFBFBD><EFBFBD>?IN_USE <EFBFBD><EFBFBD>?IDLE)
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"StateTest"
timeout:15.0
@@ -312,18 +312,18 @@
[NSThread sleepForTimeInterval:1.0]; //
// 1
// 1<EFBFBD><EFBFBD>?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
@"Connection should be in pool");
// 2 (IDLE IN_USE IDLE)
// <EFBFBD><EFBFBD>? (IDLE <EFBFBD><EFBFBD>?IN_USE <EFBFBD><EFBFBD>?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");
//
// <EFBFBD><EFBFBD>?
XCTAssertEqual(self.client.connectionReuseCount, 1,
@"Should have reused connection once");
}
@@ -333,7 +333,7 @@
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
//
// <EFBFBD><EFBFBD>?
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"InUseTest"
timeout:15.0
@@ -364,20 +364,20 @@
[NSThread sleepForTimeInterval:1.0];
// lastUsedDate nil
// <EFBFBD><EFBFBD>?lastUsedDate <EFBFBD><EFBFBD>?nil
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
XCTAssertEqual(connections.count, 1, @"Should have connection");
HttpdnsNWReusableConnection *conn = connections.firstObject;
[conn debugSetLastUsedDate:nil];
// prune使 distantPast nil
// <EFBFBD><EFBFBD>?prune使 distantPast nil<EFBFBD><EFBFBD>?
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"NilDateTest"
timeout:15.0
error:nil];
//
// <EFBFBD><EFBFBD>?
XCTAssertNotNil(response, @"Should handle nil lastUsedDate gracefully");
}
@@ -386,7 +386,7 @@
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
//
// <EFBFBD><EFBFBD>?
for (NSInteger i = 0; i < 3; i++) {
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"InvariantTest"
@@ -394,7 +394,7 @@
error:nil];
}
// 1
// 1<EFBFBD><EFBFBD>?
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
userAgent:@"TimeoutTest"
timeout:0.5
@@ -402,7 +402,7 @@
[NSThread sleepForTimeInterval:2.0];
//
// <EFBFBD><EFBFBD>?
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
//
@@ -411,12 +411,12 @@
}
}
// Q3.4 lastUsedDate
// Q3.4 lastUsedDate <EFBFBD><EFBFBD>?
- (void)testInvariant_LastUsedDate_Monotonic {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 1使
// <EFBFBD><EFBFBD>?使<EFBFBD><EFBFBD>?
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"MonotonicTest"
timeout:15.0
@@ -429,10 +429,10 @@
NSDate *date1 = connections1.firstObject.lastUsedDate;
XCTAssertNotNil(date1, @"lastUsedDate should be set");
// 1
// 1<EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:1.0];
// 2使
// <EFBFBD><EFBFBD>?使
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"MonotonicTest"
timeout:15.0
@@ -449,12 +449,12 @@
@"lastUsedDate should increase after reuse");
}
// Q5.1 +
// Q5.1 +<EFBFBD><EFBFBD>?
- (void)testCompound_TimeoutDuringPoolOverflow_Handled {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 4
// <EFBFBD><EFBFBD>?
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
@@ -477,9 +477,9 @@
[NSThread sleepForTimeInterval:1.0];
NSUInteger poolCountBefore = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolCountBefore, 4, @"Pool should have ≤4 connections");
XCTAssertLessThanOrEqual(poolCountBefore, 4, @"Pool should have <EFBFBD><EFBFBD>? connections");
// 5
// <EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
userAgent:@"TimeoutRequest"
@@ -491,7 +491,7 @@
[NSThread sleepForTimeInterval:1.0];
//
// <EFBFBD><EFBFBD>?
NSUInteger poolCountAfter = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolCountAfter, 4, @"Timed-out connection should not be added to pool");
}
@@ -507,15 +507,15 @@
timeout:2.0
error:&error];
//
// <EFBFBD><EFBFBD>?
XCTAssertNil(response, @"Should fail to connect to invalid port");
//
// <EFBFBD><EFBFBD>?
XCTAssertEqual([self.client totalConnectionCount], 0,
@"Failed connection should not be added to pool");
}
// Q2.5 invalidate
// Q2.5 invalidate <EFBFBD><EFBFBD>?
- (void)testAbnormal_MultipleInvalidate_Idempotent {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
@@ -542,11 +542,11 @@
XCTAssertTrue(conn.isInvalidated, @"Connection should be invalidated");
}
// Q5.2 dequeue
// Q5.2 dequeue <EFBFBD><EFBFBD>?
- (void)testCompound_ConcurrentDequeueDuringPrune_Safe {
[self.client resetPoolStatistics];
//
// <EFBFBD><EFBFBD>?
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"RaceTest"
timeout:15.0
@@ -562,7 +562,7 @@
// 30
[NSThread sleepForTimeInterval:30.5];
// dequeue prune
// <EFBFBD><EFBFBD>?dequeue prune<EFBFBD><EFBFBD>?
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

View File

@@ -1,78 +1,78 @@
# HTTP Mock Server for Integration Tests
本目录包含用`HttpdnsNWHTTPClient` 集成测试HTTP/HTTPS mock server用于替httpbin.org
本目录包含用<EFBFBD><EFBFBD>?`HttpdnsNWHTTPClient` 集成测试<EFBFBD><EFBFBD>?HTTP/HTTPS mock server用于替<EFBFBD><EFBFBD>?httpbin.org<EFBFBD><EFBFBD>?
---
## 为什么需Mock Server
## 为什么需<EFBFBD><EFBFBD>?Mock Server<EFBFBD><EFBFBD>?
1. **可靠性**: httpbin.org 在高并发测试下表现不稳定,经常返回非预期HTTP 状态码(如 429 Too Many Requests
1. **可靠<EFBFBD><EFBFBD>?*: httpbin.org 在高并发测试下表现不稳定,经常返回非预期<EFBFBD><EFBFBD>?HTTP 状态码(如 429 Too Many Requests<EFBFBD><EFBFBD>?
2. **速度**: 本地服务器响应更快,缩短测试执行时间
3. **离线测试**: 无需网络连接即可运行集成测试
4. **可控性**: 完全掌控测试环境,便于调试和复现问题
4. **可控<EFBFBD><EFBFBD>?*: 完全掌控测试环境,便于调试和复现问题
---
## 快速开
## 快速开<EFBFBD><EFBFBD>?
### 1. 启动 Mock Server
```bash
# 进入测试目录
cd AlicloudHttpDNSTests/Network
cd TrustHttpDNSTests/Network
# 启动服务器(无需 sudo 权限,使用非特权端口
# 启动服务器(无需 sudo 权限,使用非特权端口<EFBFBD><EFBFBD>?
python3 mock_server.py
```
**注意**:
- **无需 root 权限**(使用非特权端口 11080/11443-11446
- **无需 root 权限**(使用非特权端口 11080/11443-11446<EFBFBD><EFBFBD>?
- 首次运行会自动生成自签名证书 (`server.pem`)
- `Ctrl+C` 停止服务
- <EFBFBD><EFBFBD>?`Ctrl+C` 停止服务<EFBFBD><EFBFBD>?
### 2. 运行集成测试
在另一个终端窗口:
在另一个终端窗<EFBFBD><EFBFBD>?
```bash
cd ~/Project/iOS/alicloud-ios-sdk-httpdns
cd ~/Project/iOS/Trust-ios-sdk-httpdns
# 运行所有集成测
# 运行所有集成测<EFBFBD><EFBFBD>?
xcodebuild test \
-workspace AlicloudHttpDNS.xcworkspace \
-scheme AlicloudHttpDNSTests \
-workspace TrustHttpDNS.xcworkspace \
-scheme TrustHttpDNSTests \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-only-testing:AlicloudHttpDNSTests/HttpdnsNWHTTPClientIntegrationTests
-only-testing:TrustHttpDNSTests/HttpdnsNWHTTPClientIntegrationTests
# 运行单个测试
xcodebuild test \
-workspace AlicloudHttpDNS.xcworkspace \
-scheme AlicloudHttpDNSTests \
-workspace TrustHttpDNS.xcworkspace \
-scheme TrustHttpDNSTests \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-only-testing:AlicloudHttpDNSTests/HttpdnsNWHTTPClientIntegrationTests/testConcurrency_ParallelRequestsSameHost_AllSucceed
-only-testing:TrustHttpDNSTests/HttpdnsNWHTTPClientIntegrationTests/testConcurrency_ParallelRequestsSameHost_AllSucceed
```
---
## 支持Endpoints
## 支持<EFBFBD><EFBFBD>?Endpoints
Mock server 实现了以httpbin.org 兼容endpoints:
Mock server 实现了以<EFBFBD><EFBFBD>?httpbin.org 兼容<EFBFBD><EFBFBD>?endpoints:
| Endpoint | 功能 | 示例 |
|----------|------|------|
| `GET /get` | 返回请求信息headers, args, origin | `http://127.0.0.1:11080/get` |
| `GET /status/{code}` | 返回指定状态码100-599 | `http://127.0.0.1:11080/status/404` |
| `GET /stream-bytes/{n}` | 返回 chunked 编码N 字节数据 | `http://127.0.0.1:11080/stream-bytes/1024` |
| `GET /delay/{seconds}` | 延迟指定秒数后返回(最多10秒 | `http://127.0.0.1:11080/delay/5` |
| `GET /headers` | 返回所有请求头| `http://127.0.0.1:11080/headers` |
| `GET /get` | 返回请求信息headers, args, origin<EFBFBD><EFBFBD>?| `http://127.0.0.1:11080/get` |
| `GET /status/{code}` | 返回指定状态码<EFBFBD><EFBFBD>?00-599<EFBFBD><EFBFBD>?| `http://127.0.0.1:11080/status/404` |
| `GET /stream-bytes/{n}` | 返回 chunked 编码<EFBFBD><EFBFBD>?N 字节数据 | `http://127.0.0.1:11080/stream-bytes/1024` |
| `GET /delay/{seconds}` | 延迟指定秒数后返回(最<EFBFBD><EFBFBD>?0秒 | `http://127.0.0.1:11080/delay/5` |
| `GET /headers` | 返回所有请求头<EFBFBD><EFBFBD>?| `http://127.0.0.1:11080/headers` |
| `GET /uuid` | 返回随机 UUID | `http://127.0.0.1:11080/uuid` |
| `GET /user-agent` | 返回 User-Agent 头部 | `http://127.0.0.1:11080/user-agent` |
**端口配置**:
- **HTTP**: `127.0.0.1:11080`
- **HTTPS**: `127.0.0.1:11443`, `11444`, `11445`, `11446`4个端口用于测试连接池隔离
- **HTTPS**: `127.0.0.1:11443`, `11444`, `11445`, `11446`<EFBFBD><EFBFBD>?个端口用于测试连接池隔离<EFBFBD><EFBFBD>?
endpoints HTTP HTTPS 端口上均可访问
<EFBFBD><EFBFBD>?endpoints <EFBFBD><EFBFBD>?HTTP <EFBFBD><EFBFBD>?HTTPS 端口上均可访问<EFBFBD><EFBFBD>?
---
@@ -80,25 +80,25 @@ Mock server 实现了以下 httpbin.org 兼容的 endpoints:
### 架构
- **HTTP 服务器**: 监听 `127.0.0.1:11080`
- **HTTPS 服务器**: 监听 `127.0.0.1:11443`, `11444`, `11445`, `11446`4个端口,使用自签名证书)
- **多端口目的**: 测试连接池的端口隔离机制,确保不同端口使用独立的连接
- **并发模型**: 多线程(`ThreadingMixIn`),支持高并发请
- **HTTP 服务<EFBFBD><EFBFBD>?*: 监听 `127.0.0.1:11080`
- **HTTPS 服务<EFBFBD><EFBFBD>?*: 监听 `127.0.0.1:11443`, `11444`, `11445`, `11446`<EFBFBD><EFBFBD>?个端口,使用自签名证书)
- **多端口目<EFBFBD><EFBFBD>?*: 测试连接池的端口隔离机制,确保不同端口使用独立的连接<EFBFBD><EFBFBD>?
- **并发模型**: 多线程(`ThreadingMixIn`),支持高并发请<EFBFBD><EFBFBD>?
### TLS 证书
- 自动生成自签名证书RSA 2048位有效365 天)
- 自动生成自签名证书RSA 2048位有效<EFBFBD><EFBFBD>?365 天)
- CN (Common Name): `localhost`
- 证书文件: `server.pem`(同时包含密钥和证书
- 证书文件: `server.pem`(同时包含密钥和证书<EFBFBD><EFBFBD>?
**重要**: 集成测试通过环境变量 `HTTPDNS_SKIP_TLS_VERIFY=1` 跳过 TLS 验证,这是安全的,因为:
1. 仅在测试环境生效
2. 不影响生产代
3. 连接限制为本loopback (127.0.0.1)
2. 不影响生产代<EFBFBD><EFBFBD>?
3. 连接限制为本<EFBFBD><EFBFBD>?loopback (127.0.0.1)
### 响应格式
JSON 响应遵循 httpbin.org 格式,例如:
<EFBFBD><EFBFBD>?JSON 响应遵循 httpbin.org 格式,例<EFBFBD><EFBFBD>?
```json
{
@@ -131,7 +131,7 @@ XXXXXXXXXX
**错误信息**:
```
端口 80 已被占用,请关闭占用端口的进程或使用其他端口
<EFBFBD><EFBFBD>?端口 80 已被占用,请关闭占用端口的进程或使用其他端口
```
**解决方法**:
@@ -147,9 +147,9 @@ sudo lsof -i :443
sudo kill -9 <PID>
```
3. 或修mock_server.py 使用其他端口:
3. 或修<EFBFBD><EFBFBD>?mock_server.py 使用其他端口:
```python
# 修改端口号(同时需要更新测试代码中的 URL
# 修改端口号(同时需要更新测试代码中<EFBFBD><EFBFBD>?URL<52><4C>?
run_http_server(port=8080)
run_https_server(port=8443)
```
@@ -158,13 +158,13 @@ run_https_server(port=8443)
**错误信息**:
```
✗ 未找到 openssl 命令,请安装 OpenSSL
<EFBFBD><EFBFBD>?未找<E69CAA><E689BE>?openssl 命令,请安装 OpenSSL
```
**解决方法**:
```bash
# macOS (通常已预装)
# macOS (通常已预<EFBFBD><EFBFBD>?
brew install openssl
# Ubuntu/Debian
@@ -174,11 +174,11 @@ sudo apt-get install openssl
sudo yum install openssl
```
### 权限被拒
### 权限被拒<EFBFBD><EFBFBD>?
**错误信息**:
```
错误: 需root 权限以绑80/443 端口
<EFBFBD><EFBFBD>?错误: 需<EFBFBD><EFBFBD>?root 权限以绑<EFBFBD><EFBFBD>?80/443 端口
```
**解决方法**:
@@ -190,21 +190,21 @@ sudo python3 mock_server.py
---
## 切换httpbin.org
## 切换<EFBFBD><EFBFBD>?httpbin.org
如需使用真实httpbin.org 进行测试(例如验证兼容性):
如需使用真实<EFBFBD><EFBFBD>?httpbin.org 进行测试(例如验证兼容性):
1. 编辑 `HttpdnsNWHTTPClientIntegrationTests.m`
2. 将所`127.0.0.1` 替换`httpbin.org`
3. 注释setUp/tearDown 中的环境变量设置
2. 将所<EFBFBD><EFBFBD>?`127.0.0.1` 替换<EFBFBD><EFBFBD>?`httpbin.org`
3. 注释<EFBFBD><EFBFBD>?setUp/tearDown 中的环境变量设置
---
## 开发与扩展
### 添加Endpoint
### 添加<EFBFBD><EFBFBD>?Endpoint
`mock_server.py` `MockHTTPHandler.do_GET()` 方法中添加:
<EFBFBD><EFBFBD>?`mock_server.py` <EFBFBD><EFBFBD>?`MockHTTPHandler.do_GET()` 方法中添<EFBFBD><EFBFBD>?
```python
def do_GET(self):
@@ -215,14 +215,14 @@ def do_GET(self):
# ... 其他 endpoints
def _handle_your_endpoint(self):
"""处理自定endpoint"""
"""处理自定<EFBFBD><EFBFBD>?endpoint"""
data = {'custom': 'data'}
self._send_json(200, data)
```
### 调试模式
取消注释 `log_message` 方法以启用详细日志:
取消注释 `log_message` 方法以启用详细日<EFBFBD><EFBFBD>?
```python
def log_message(self, format, *args):
@@ -234,20 +234,20 @@ def log_message(self, format, *args):
## 技术栈
- **Python 3.7+** (标准库,无需额外依赖)
- **http.server**: HTTP 服务器实
- **http.server**: HTTP 服务器实<EFBFBD><EFBFBD>?
- **ssl**: TLS/SSL 支持
- **socketserver.ThreadingMixIn**: 多线程并
- **socketserver.ThreadingMixIn**: 多线程并<EFBFBD><EFBFBD>?
---
## 安全注意事项
1. **仅用于测试**: 此服务器设计用于本地测试,不适合生产环境
2. **自签名证书**: HTTPS 使用不受信任的自签名证书
3. **无身份验证**: 不实现任何身份验证机
4. **本地绑定**: 服务器仅绑定`127.0.0.1`,不接受外部连接
1. **仅用于测<EFBFBD><EFBFBD>?*: 此服务器设计用于本地测试,不适合生产环境
2. **自签名证<EFBFBD><EFBFBD>?*: HTTPS 使用不受信任的自签名证书
3. **无身份验<EFBFBD><EFBFBD>?*: 不实现任何身份验证机<EFBFBD><EFBFBD>?
4. **本地绑定**: 服务器仅绑定<EFBFBD><EFBFBD>?`127.0.0.1`,不接受外部连接
---
**最后更新**: 2025-11-01
**维护者**: Claude Code
**最后更<EFBFBD><EFBFBD>?*: 2025-11-01
**维护<EFBFBD><EFBFBD>?*: Claude Code

View File

@@ -1,62 +1,62 @@
# HttpdnsNWHTTPClient 测试套件
本目录包`HttpdnsNWHTTPClient` `HttpdnsNWReusableConnection` 的完整测试套件
本目录包<EFBFBD><EFBFBD>?`HttpdnsNWHTTPClient` <EFBFBD><EFBFBD>?`HttpdnsNWReusableConnection` 的完整测试套件<EFBFBD><EFBFBD>?
## 测试文件结构
```
AlicloudHttpDNSTests/Network/
TrustHttpDNSTests/Network/
├── HttpdnsNWHTTPClientTests.m # 主单元测试44个
├── HttpdnsNWHTTPClientIntegrationTests.m # 集成测试7个)
├── HttpdnsNWHTTPClientTestHelper.h/m # 测试辅助工具
└── README.md # 本文
├── HttpdnsNWHTTPClientIntegrationTests.m # 集成测试<EFBFBD><EFBFBD>?个)
├── HttpdnsNWHTTPClientTestHelper.h/m # 测试辅助工具<EFBFBD><EFBFBD>?
└── README.md # 本文<EFBFBD><EFBFBD>?
```
## 测试覆盖范围
### 单元测试 (HttpdnsNWHTTPClientTests.m)
#### A. HTTP 解析逻辑测试 (25个)
- **A1. Header 解析 (9个)**
#### A. HTTP 解析逻辑测试 (25<EFBFBD><EFBFBD>?
- **A1. Header 解析 (9<EFBFBD><EFBFBD>?**
- 正常响应解析
- 多个头部字段
- 不完整数据处
- 不完整数据处<EFBFBD><EFBFBD>?
- 无效状态行
- 空格处理trim
- 空值头
- 空格处理<EFBFBD><EFBFBD>?trim
- 空值头<EFBFBD><EFBFBD>?
- 非数字状态码
- 状态码为零
- 无效头部
- 无效头部<EFBFBD><EFBFBD>?
- **A2. Chunked 编码检查 (8个)**
- **A2. Chunked 编码检<EFBFBD><EFBFBD>?(8<><38>?**
- 单个 chunk
- 多个 chunks
- 不完chunk
- 不完<EFBFBD><EFBFBD>?chunk
- Chunk extension 支持
- 无效十六进制 size
- Chunk size 溢出
- 缺少 CRLF 终止
- trailers chunked
- 缺少 CRLF 终止<EFBFBD><EFBFBD>?
- <EFBFBD><EFBFBD>?trailers <EFBFBD><EFBFBD>?chunked
- **A3. Chunked 解码 (2个)**
- **A3. Chunked 解码 (2<EFBFBD><EFBFBD>?**
- 多个 chunks 正确解码
- 无效格式返回 nil
- **A4. 完整响应解析 (6个)**
- **A4. 完整响应解析 (6<EFBFBD><EFBFBD>?**
- Content-Length 响应
- Chunked 编码响应
- body
- Content-Length 不匹
- 空数据错
- 只有 headers body
- <EFBFBD><EFBFBD>?body
- Content-Length 不匹<EFBFBD><EFBFBD>?
- 空数据错<EFBFBD><EFBFBD>?
- 只有 headers <EFBFBD><EFBFBD>?body
#### C. 请求构建测试 (7个)
#### C. 请求构建测试 (7<EFBFBD><EFBFBD>?
- 基本 GET 请求格式
- 查询参数处理
- User-Agent 头部
- HTTP 默认端口处理
- HTTPS 默认端口处理
- 非默认端口显
- 非默认端口显<EFBFBD><EFBFBD>?
- 固定头部验证
#### E. TLS 验证测试 (4个占位符)
@@ -65,95 +65,95 @@ AlicloudHttpDNSTests/Network/
- 无效证书返回 NO
- 指定域名使用 SSL Policy
*注TLS 测试需要真实的 SecTrustRef 或复mock当前为占位符*
*注TLS 测试需要真实的 SecTrustRef 或复<EFBFBD><EFBFBD>?mock当前为占位<EFBFBD><EFBFBD>?
#### F. 边缘情况测试 (8个)
#### F. 边缘情况测试 (8<EFBFBD><EFBFBD>?
- 超长 URL 处理
- User-Agent
- 超大响应体5MB
- <EFBFBD><EFBFBD>?User-Agent
- 超大响应体5MB<EFBFBD><EFBFBD>?
- Chunked 解码失败回退
- 连接key - 不同 hosts
- 连接key - 不同 ports
- 连接key - HTTP vs HTTPS
- 连接<EFBFBD><EFBFBD>?key - 不同 hosts
- 连接<EFBFBD><EFBFBD>?key - 不同 ports
- 连接<EFBFBD><EFBFBD>?key - HTTP vs HTTPS
### 集成测试 (HttpdnsNWHTTPClientIntegrationTests.m)
使用 httpbin.org 进行真实网络测试 (22个)
使用 httpbin.org 进行真实网络测试 (22<EFBFBD><EFBFBD>?<3F><>?
**G. 基础集成测试 (7个)**
**G. 基础集成测试 (7<EFBFBD><EFBFBD>?**
- HTTP GET 请求
- HTTPS GET 请求
- HTTP 404 响应
- 连接复用(两次请求)
- Chunked 响应处理
- 请求超时测试
- 自定义头部验
- 自定义头部验<EFBFBD><EFBFBD>?
**H. 并发测试 (5个)**
- 并发请求同一主机10个线程
- 并发请求不同路径5个不同endpoint
**H. 并发测试 (5<EFBFBD><EFBFBD>?**
- 并发请求同一主机<EFBFBD><EFBFBD>?0个线程
- 并发请求不同路径<EFBFBD><EFBFBD>?个不同endpoint<EFBFBD><EFBFBD>?
- 混合 HTTP + HTTPS 并发各5个线程
- 高负载压力测试50个并发请求
- 混合串行+并发模式
**I. 竞态条件测试 (5个)**
**I. 竞态条件测<EFBFBD><EFBFBD>?(5<><35>?**
- 连接池容量测试超过4个连接上限
- 同时归还连接5个并发)
- 获取-归还-再获取竞
- 同时归还连接<EFBFBD><EFBFBD>?个并发)
- 获取-归还-再获取竞<EFBFBD><EFBFBD>?
- 超时与活跃连接冲突需31秒可跳过
- 错误恢复后连接池健康状
- 错误恢复后连接池健康状<EFBFBD><EFBFBD>?
**J. 高级连接复用测试 (5个)**
**J. 高级连接复用测试 (5<EFBFBD><EFBFBD>?**
- 连接过期与清理31秒可跳过
- 连接池容量限制验证10个连续请求
- 不同路径复用连接4个不同路径)
- 不同路径复用连接<EFBFBD><EFBFBD>?个不同路径)
- HTTP vs HTTPS 使用不同连接池key
- 长连接保持测试20个请求间隔1秒,可跳过)
- 长连接保持测试20个请求间<EFBFBD><EFBFBD>?秒,可跳过)
## 运行测试
### 运行所有单元测
### 运行所有单元测<EFBFBD><EFBFBD>?
```bash
xcodebuild test \
-workspace AlicloudHttpDNS.xcworkspace \
-scheme AlicloudHttpDNSTests \
-workspace TrustHttpDNS.xcworkspace \
-scheme TrustHttpDNSTests \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-only-testing:AlicloudHttpDNSTests/HttpdnsNWHTTPClientTests
-only-testing:TrustHttpDNSTests/HttpdnsNWHTTPClientTests
```
### 运行集成测试(需要网络)
```bash
xcodebuild test \
-workspace AlicloudHttpDNS.xcworkspace \
-scheme AlicloudHttpDNSTests \
-workspace TrustHttpDNS.xcworkspace \
-scheme TrustHttpDNSTests \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-only-testing:AlicloudHttpDNSTests/HttpdnsNWHTTPClientIntegrationTests
-only-testing:TrustHttpDNSTests/HttpdnsNWHTTPClientIntegrationTests
```
### 运行单个测试
```bash
xcodebuild test \
-workspace AlicloudHttpDNS.xcworkspace \
-scheme AlicloudHttpDNSTests \
-workspace TrustHttpDNS.xcworkspace \
-scheme TrustHttpDNSTests \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-only-testing:AlicloudHttpDNSTests/HttpdnsNWHTTPClientTests/testParseHTTPHeaders_ValidResponse_Success
-only-testing:TrustHttpDNSTests/HttpdnsNWHTTPClientTests/testParseHTTPHeaders_ValidResponse_Success
```
## 测试辅助工具
### HttpdnsNWHTTPClientTestHelper
提供以下工具方法
提供以下工具方法<EFBFBD><EFBFBD>?
#### HTTP 响应构
#### HTTP 响应构<EFBFBD><EFBFBD>?
```objc
// 构造标HTTP 响应
// 构造标<EFBFBD><EFBFBD>?HTTP 响应
+ (NSData *)createHTTPResponseWithStatus:(NSInteger)statusCode
statusText:(NSString *)statusText
headers:(NSDictionary *)headers
body:(NSData *)body;
// 构chunked 响应
// 构<EFBFBD><EFBFBD>?chunked 响应
+ (NSData *)createChunkedHTTPResponseWithStatus:(NSInteger)statusCode
headers:(NSDictionary *)headers
chunks:(NSArray<NSData *> *)chunks;
@@ -175,19 +175,19 @@ xcodebuild test \
| 测试类别 | 测试数量 | 覆盖范围 |
|---------|---------|---------|
| HTTP 解析 | 25 | HTTP 头部、Chunked 编码、完整响|
| 请求构建 | 7 | URL 处理、头部生|
| TLS 验证 | 4 (占位符) | 证书验证 |
| HTTP 解析 | 25 | HTTP 头部、Chunked 编码、完整响<EFBFBD><EFBFBD>?|
| 请求构建 | 7 | URL 处理、头部生<EFBFBD><EFBFBD>?|
| TLS 验证 | 4 (占位<EFBFBD><EFBFBD>? | 证书验证 |
| 边缘情况 | 8 | 异常输入、连接池 key |
| **单元测试合计** | **43** | - |
| 基础集成测试 (G) | 7 | 真实网络请求、基本场|
| 基础集成测试 (G) | 7 | 真实网络请求、基本场<EFBFBD><EFBFBD>?|
| 并发测试 (H) | 5 | 多线程并发、高负载 |
| 竞态条件测(I) | 5 | 连接池竞态、错误恢|
| 连接复用测试 (J) | 5 | 连接过期、长连接、协议隔|
| 多端口连接隔(K) | 5 | 不同端口独立连接|
| 竞态条件测<EFBFBD><EFBFBD>?(I) | 5 | 连接池竞态、错误恢<EFBFBD><EFBFBD>?|
| 连接复用测试 (J) | 5 | 连接过期、长连接、协议隔<EFBFBD><EFBFBD>?|
| 多端口连接隔<EFBFBD><EFBFBD>?(K) | 5 | 不同端口独立连接<EFBFBD><EFBFBD>?|
| 端口池耗尽测试 (L) | 3 | 多端口高负载、池隔离 |
| 边界条件验证 (M) | 4 | 端口迁移、高端口数量 |
| 并发多端口场(N) | 3 | 并行 keep-alive、轮询分|
| 并发多端口场<EFBFBD><EFBFBD>?(N) | 3 | 并行 keep-alive、轮询分<EFBFBD><EFBFBD>?|
| **集成测试合计** | **37** | - |
| **总计** | **80** | - |
@@ -195,24 +195,24 @@ xcodebuild test \
以下测试组涉及复杂的 Mock 场景,可根据需要添加:
### B. 连接池管理测试 (18个)
Mock `HttpdnsNWReusableConnection` 的完整生命周
### B. 连接池管理测<EFBFBD><EFBFBD>?(18<31><38>?
<EFBFBD><EFBFBD>?Mock `HttpdnsNWReusableConnection` 的完整生命周<EFBFBD><EFBFBD>?
### D. 完整流程测试 (13个)
Mock 连接池和网络层的集成场景
### D. 完整流程测试 (13<EFBFBD><EFBFBD>?
<EFBFBD><EFBFBD>?Mock 连接池和网络层的集成场景
## Mock Server 使用
集成测试使用本地 mock server (127.0.0.1) 替代 httpbin.org提供稳定可靠的测试环境
集成测试使用本地 mock server (127.0.0.1) 替代 httpbin.org提供稳定可靠的测试环境<EFBFBD><EFBFBD>?
### 启动 Mock Server
```bash
cd AlicloudHttpDNSTests/Network
cd TrustHttpDNSTests/Network
python3 mock_server.py
```
**注意**:使用非特权端口11080/11443-11446无需 `sudo` 权限
**注意**:使用非特权端口<EFBFBD><EFBFBD>?1080/11443-11446无需 `sudo` 权限<EFBFBD><EFBFBD>?
### 运行集成测试
@@ -220,38 +220,38 @@ python3 mock_server.py
```bash
xcodebuild test \
-workspace AlicloudHttpDNS.xcworkspace \
-scheme AlicloudHttpDNSTests \
-workspace TrustHttpDNS.xcworkspace \
-scheme TrustHttpDNSTests \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-only-testing:AlicloudHttpDNSTests/HttpdnsNWHTTPClientIntegrationTests
-only-testing:TrustHttpDNSTests/HttpdnsNWHTTPClientIntegrationTests
```
### Mock Server 特
### Mock Server 特<EFBFBD><EFBFBD>?
- **HTTP**: 监听 `127.0.0.1:11080`
- **HTTPS**: 监听 `127.0.0.1:11443`, `11444`, `11445`, `11446` (自签名证书)
- **多端口目的**: 测试连接池的端口隔离机制
- **HTTPS**: 监听 `127.0.0.1:11443`, `11444`, `11445`, `11446` (自签名证<EFBFBD><EFBFBD>?
- **多端口目<EFBFBD><EFBFBD>?*: 测试连接池的端口隔离机制
- **并发支持**: 多线程处理,适合并发测试
- **零延迟**: 本地响应,测试速度
- **零延<EFBFBD><EFBFBD>?*: 本地响应,测试速度<EFBFBD><EFBFBD>?
详见 [MOCK_SERVER.md](./MOCK_SERVER.md)
## 注意事项
1. **集成测试依赖 Mock Server**`HttpdnsNWHTTPClientIntegrationTests` 使用本地 mock server (127.0.0.1)。测试前需先启`mock_server.py`
1. **集成测试依赖 Mock Server**`HttpdnsNWHTTPClientIntegrationTests` 使用本地 mock server (127.0.0.1)。测试前需先启<EFBFBD><EFBFBD>?`mock_server.py`<EFBFBD><EFBFBD>?
2. **慢测试跳过**:部分测试需要等待31秒测试连接过期可设置环境变`SKIP_SLOW_TESTS=1` 跳过这些测试
2. **慢测试跳<EFBFBD><EFBFBD>?*:部分测试需要等<EFBFBD><EFBFBD>?1秒测试连接过期可设置环境变<EFBFBD><EFBFBD>?`SKIP_SLOW_TESTS=1` 跳过这些测试<EFBFBD><EFBFBD>?
- `testRaceCondition_ExpiredConnectionPruning_CreatesNewConnection`
- `testConnectionReuse_Expiry31Seconds_NewConnectionCreated`
- `testConnectionReuse_TwentyRequestsOneSecondApart_ConnectionKeptAlive`
3. **并发测试容错**:并发和压力测试允许部分失败(例H.4 要求80%成功率),因为高负载下仍可能出现网络波动
3. **并发测试容错**:并发和压力测试允许部分失败(例<EFBFBD><EFBFBD>?H.4 要求80%成功率),因为高负载下仍可能出现网络波动<EFBFBD><EFBFBD>?
4. **TLS 测试占位符**E 组 TLS 测试需要真实的 `SecTrustRef` 或高mock 框架,当前仅为占位符
4. **TLS 测试占位<EFBFBD><EFBFBD>?*E <20><>?TLS 测试需要真实的 `SecTrustRef` 或高<EFBFBD><EFBFBD>?mock 框架,当前仅为占位符<EFBFBD><EFBFBD>?
5. **新文件添加到 Xcode**:创建的测试文件需要手动添加到 `AlicloudHttpDNSTests` target
5. **新文件添加到 Xcode**:创建的测试文件需要手动添加到 `TrustHttpDNSTests` target<EFBFBD><EFBFBD>?
6. **测试数据**:使`HttpdnsNWHTTPClientTestHelper` 生成测试数据,确保测试的可重复性
6. **测试数据**:使<EFBFBD><EFBFBD>?`HttpdnsNWHTTPClientTestHelper` 生成测试数据,确保测试的可重复性<EFBFBD><EFBFBD>?
## 文件依赖
@@ -263,20 +263,20 @@ xcodebuild test \
## 贡献指南
添加新测试时,请遵循
添加新测试时,请遵循<EFBFBD><EFBFBD>?
1. 命名规范:`test<Component>_<Scenario>_<ExpectedResult>`
2. 使用 `#pragma mark` 组织测试分组
3. 添加清晰的注释说明测试目
4. 验证测试覆盖率并更新本文
3. 添加清晰的注释说明测试目<EFBFBD><EFBFBD>?
4. 验证测试覆盖率并更新本文<EFBFBD><EFBFBD>?
---
**最后更新**: 2025-11-01
**最后更<EFBFBD><EFBFBD>?*: 2025-11-01
**测试框架**: XCTest + OCMock
**维护者**: Claude Code
**维护<EFBFBD><EFBFBD>?*: Claude Code
**更新日志**:
- 2025-11-01: 新增 15 个多端口连接复用测试K、L、M、N组测试总数增至 37
- 2025-11-01: Mock server 新增 3 HTTPS 端口11444-11446用于测试连接池隔离
- 2025-11-01: 新增本地 mock serverhttpbin.org提供稳定测试环
- 2025-11-01: 新增 15 个多端口连接复用测试K、L、M、N组测试总数增至 37 <EFBFBD><EFBFBD>?
- 2025-11-01: Mock server 新增 3 <EFBFBD><EFBFBD>?HTTPS 端口<EFBFBD><EFBFBD>?1444-11446用于测试连接池隔离
- 2025-11-01: 新增本地 mock server<EFBFBD><EFBFBD>?httpbin.org提供稳定测试环<EFBFBD><EFBFBD>?
- 2025-11-01: 新增 15 个并发、竞态和连接复用集成测试H、I、J组

View File

@@ -8,82 +8,82 @@
## 连接状态机定义
### 状态属
### 状态属<EFBFBD><EFBFBD>?
**HttpdnsNWReusableConnection.h:9-11**
```objc
@property (nonatomic, strong) NSDate *lastUsedDate; // 最后使用时
@property (nonatomic, assign) BOOL inUse; // 是否正在被使
@property (nonatomic, assign, getter=isInvalidated, readonly) BOOL invalidated; // 是否已失
@property (nonatomic, strong) NSDate *lastUsedDate; // 最后使用时<EFBFBD><EFBFBD>?
@property (nonatomic, assign) BOOL inUse; // 是否正在被使<EFBFBD><EFBFBD>?
@property (nonatomic, assign, getter=isInvalidated, readonly) BOOL invalidated; // 是否已失<EFBFBD><EFBFBD>?
```
### 状态枚
### 状态枚<EFBFBD><EFBFBD>?
虽然没有显式枚举,但连接实际存在以下逻辑状态:
| 状| `inUse` | `invalidated` | `pool` | 描述 |
| 状<EFBFBD><EFBFBD>?| `inUse` | `invalidated` | `pool` | 描述 |
|------|---------|---------------|--------|------|
| **CREATING** | - | NO | | 新创建,尚未打开 |
| **IN_USE** | YES | NO | | 已借出,正在使|
| **IDLE** | NO | NO | | 空闲,可复用 |
| **EXPIRED** | NO | NO | | 空闲超30秒待清|
| **INVALIDATED** | - | YES | | 已失效,已移|
| **CREATING** | - | NO | <EFBFBD><EFBFBD>?| 新创建,尚未打开 |
| **IN_USE** | YES | NO | <EFBFBD><EFBFBD>?| 已借出,正在使<EFBFBD><EFBFBD>?|
| **IDLE** | NO | NO | <EFBFBD><EFBFBD>?| 空闲,可复用 |
| **EXPIRED** | NO | NO | <EFBFBD><EFBFBD>?| 空闲<EFBFBD><EFBFBD>?0秒待清<EFBFBD><EFBFBD>?|
| **INVALIDATED** | - | YES | <EFBFBD><EFBFBD>?| 已失效,已移<EFBFBD><EFBFBD>?|
---
## 状态转换图
```
┌─────────
│CREATING (new connection)
└────┬────
openWithTimeout success
┌─────────
IN_USE (inUse=YES, in pool)
└────┬────
├──success──returnConnection(shouldClose=NO)
┌─────────
IDLE (inUse=NO, in pool)
└────┬────
├──dequeue──IN_USE (reuse)
├──idle 30s──EXPIRED
└──prune──INVALIDATED
└──!isViable──INVALIDATED (skip in dequeue)
├──error/timeout──returnConnection(shouldClose=YES)
└──────────┌──────────────
INVALIDATED (removed from pool)
└──────────────
┌─────────<EFBFBD><EFBFBD>?
│CREATING <EFBFBD><EFBFBD>?(new connection)
└────┬────<EFBFBD><EFBFBD>?
<EFBFBD><EFBFBD>?openWithTimeout success
<EFBFBD><EFBFBD>?
┌─────────<EFBFBD><EFBFBD>?
<EFBFBD><EFBFBD>?IN_USE <EFBFBD><EFBFBD>?(inUse=YES, in pool)
└────┬────<EFBFBD><EFBFBD>?
<EFBFBD><EFBFBD>?
├──success──<EFBFBD><EFBFBD>?returnConnection(shouldClose=NO)
<EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>?
<EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>?
<EFBFBD><EFBFBD>? ┌─────────<EFBFBD><EFBFBD>?
<EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>? IDLE <EFBFBD><EFBFBD>?(inUse=NO, in pool)
<EFBFBD><EFBFBD>? └────┬────<EFBFBD><EFBFBD>?
<EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>?
<EFBFBD><EFBFBD>? ├──dequeue──<EFBFBD><EFBFBD>?IN_USE (reuse)
<EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>?
<EFBFBD><EFBFBD>? ├──idle 30s──<EFBFBD><EFBFBD>?EXPIRED
<EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>?
<EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>? └──prune──<EFBFBD><EFBFBD>?INVALIDATED
<EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>?
<EFBFBD><EFBFBD>? └──!isViable──<EFBFBD><EFBFBD>?INVALIDATED (skip in dequeue)
<EFBFBD><EFBFBD>?
├──error/timeout──<EFBFBD><EFBFBD>?returnConnection(shouldClose=YES)
<EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>?
<EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>?
└──────────<EFBFBD><EFBFBD>?┌──────────────<EFBFBD><EFBFBD>?
<EFBFBD><EFBFBD>?INVALIDATED <EFBFBD><EFBFBD>?(removed from pool)
└──────────────<EFBFBD><EFBFBD>?
```
---
## 代码中的状态转
## 代码中的状态转<EFBFBD><EFBFBD>?
### 1. CREATING IN_USE (新连接)
### 1. CREATING <EFBFBD><EFBFBD>?IN_USE (新连<EFBFBD><EFBFBD>?
**HttpdnsNWHTTPClient.m:248-249**
```objc
newConnection.inUse = YES;
newConnection.lastUsedDate = now;
[pool addObject:newConnection]; // 加入
[pool addObject:newConnection]; // 加入<EFBFBD><EFBFBD>?
```
**何时触发:**
- `dequeueConnectionForHost` 找不到可复用连接
- 创建新连接并成功打开
### 2. IDLE IN_USE (复用)
### 2. IDLE <EFBFBD><EFBFBD>?IN_USE (复用)
**HttpdnsNWHTTPClient.m:210-214**
```objc
@@ -97,16 +97,16 @@ for (HttpdnsNWReusableConnection *candidate in pool) {
}
```
**关键检查:**
- `!candidate.inUse` - 必须是空闲状
**关键检<EFBFBD><EFBFBD>?**
- `!candidate.inUse` - 必须是空闲状<EFBFBD><EFBFBD>?
- `[candidate isViable]` - 连接必须仍然有效
### 3. IN_USE IDLE (正常归还)
### 3. IN_USE <EFBFBD><EFBFBD>?IDLE (正常归还)
**HttpdnsNWHTTPClient.m:283-288**
```objc
if (shouldClose || connection.isInvalidated) {
// INVALIDATED (见#4)
// <EFBFBD><EFBFBD>?INVALIDATED (<EFBFBD><EFBFBD>?4)
} else {
connection.inUse = NO;
connection.lastUsedDate = now;
@@ -120,7 +120,7 @@ if (shouldClose || connection.isInvalidated) {
**防护措施:**
- Line 285: `if (![pool containsObject:connection])` - 防止重复添加
### 4. IN_USE/IDLE INVALIDATED (失效)
### 4. IN_USE/IDLE <EFBFBD><EFBFBD>?INVALIDATED (失效)
**HttpdnsNWHTTPClient.m:279-281**
```objc
@@ -132,9 +132,9 @@ if (shouldClose || connection.isInvalidated) {
**触发条件:**
- `shouldClose=YES` (timeout, error, parse failure, remote close)
- `connection.isInvalidated=YES` (连接已失效)
- `connection.isInvalidated=YES` (连接已失<EFBFBD><EFBFBD>?
### 5. EXPIRED INVALIDATED (过期清理)
### 5. EXPIRED <EFBFBD><EFBFBD>?INVALIDATED (过期清理)
**HttpdnsNWHTTPClient.m:297-312**
```objc
@@ -145,7 +145,7 @@ if (shouldClose || connection.isInvalidated) {
if (conn.inUse) continue; // 跳过使用中的
NSTimeInterval idle = [referenceDate timeIntervalSinceDate:conn.lastUsedDate];
if (idle > kHttpdnsNWHTTPClientConnectionIdleTimeout) { // 30
if (idle > kHttpdnsNWHTTPClientConnectionIdleTimeout) { // 30<EFBFBD><EFBFBD>?
[toRemove addObject:conn];
}
}
@@ -155,7 +155,7 @@ if (shouldClose || connection.isInvalidated) {
[pool removeObject:conn];
}
// 限制池大小 ≤ 4
// 限制池大<EFBFBD><EFBFBD>?<3F><>?4
while (pool.count > kHttpdnsNWHTTPClientMaxIdleConnectionsPerHost) {
HttpdnsNWReusableConnection *oldest = pool.firstObject;
[oldest invalidate];
@@ -168,72 +168,72 @@ if (shouldClose || connection.isInvalidated) {
## 当前测试覆盖情况
### 已测试的正常流程
### <EFBFBD><EFBFBD>?已测试的正常流程
| 状态转| 测试 | 覆盖 |
| 状态转<EFBFBD><EFBFBD>?| 测试 | 覆盖 |
|----------|------|------|
| CREATING IN_USE IDLE | G.1-G.7, O.1 | |
| IDLE IN_USE (复用) | G.2, O.1-O.3, J.1-J.5 | |
| IN_USE INVALIDATED (timeout) | P.1-P.6 | |
| EXPIRED INVALIDATED (30s) | J.2, M.4, I.4 | |
| 池容量限(max 4) | O.3, J.3 | |
| 并发状态访| I.1-I.5, M.3 | |
| CREATING <EFBFBD><EFBFBD>?IN_USE <EFBFBD><EFBFBD>?IDLE | G.1-G.7, O.1 | <EFBFBD><EFBFBD>?|
| IDLE <EFBFBD><EFBFBD>?IN_USE (复用) | G.2, O.1-O.3, J.1-J.5 | <EFBFBD><EFBFBD>?|
| IN_USE <EFBFBD><EFBFBD>?INVALIDATED (timeout) | P.1-P.6 | <EFBFBD><EFBFBD>?|
| EXPIRED <EFBFBD><EFBFBD>?INVALIDATED (30s) | J.2, M.4, I.4 | <EFBFBD><EFBFBD>?|
| 池容量限<EFBFBD><EFBFBD>?(max 4) | O.3, J.3 | <EFBFBD><EFBFBD>?|
| 并发状态访<EFBFBD><EFBFBD>?| I.1-I.5, M.3 | <EFBFBD><EFBFBD>?|
### 未测试的异常场景
### <EFBFBD><EFBFBD>?未测试的异常场景
#### 1. **连接在池中失效Stale Connection**
#### 1. **连接在池中失效Stale Connection<EFBFBD><EFBFBD>?*
**场景:**
- 连接空闲 29 秒(未到 30 秒过期)
- 服务器主动关闭连
- `dequeue` `isViable` 返回 NO
- 服务器主动关闭连<EFBFBD><EFBFBD>?
- `dequeue` <EFBFBD><EFBFBD>?`isViable` 返回 NO
**当前代码行为:**
```objc
for (HttpdnsNWReusableConnection *candidate in pool) {
if (!candidate.inUse && [candidate isViable]) { // isViable 检
// 只复用有效连
if (!candidate.inUse && [candidate isViable]) { // <EFBFBD><EFBFBD>?isViable 检<EFBFBD><EFBFBD>?
// 只复用有效连<EFBFBD><EFBFBD>?
}
}
// 如果所有连接都 !isViable会创建新连
// 如果所有连接都 !isViable会创建新连<EFBFBD><EFBFBD>?
```
**风险:** 未验`isViable` 检查是否真的工
**风险:** 未验<EFBFBD><EFBFBD>?`isViable` 检查是否真的工<EFBFBD><EFBFBD>?
**测试需求:** Q.1
**测试需<EFBFBD><EFBFBD>?** Q.1
```objc
testStateTransition_StaleConnectionInPool_SkipsAndCreatesNew
```
---
#### 2. **双重归还Double Return**
#### 2. **双重归还Double Return<EFBFBD><EFBFBD>?*
**场景:**
- 连接被归
- 连接被归<EFBFBD><EFBFBD>?
- 代码错误,再次归还同一连接
**当前代码防护:**
```objc
if (![pool containsObject:connection]) {
[pool addObject:connection]; // 防止重复添加
[pool addObject:connection]; // <EFBFBD><EFBFBD>?防止重复添加
}
```
**风险:** 未验证防护是否有
**风险:** 未验证防护是否有<EFBFBD><EFBFBD>?
**测试需求:** Q.2
**测试需<EFBFBD><EFBFBD>?** Q.2
```objc
testStateTransition_DoubleReturn_Idempotent
```
---
#### 3. **归还错误的池键Wrong Pool Key**
#### 3. **归还错误的池键Wrong Pool Key<EFBFBD><EFBFBD>?*
**场景:**
- 从池A借出连接
- 归还到池B错误的key
- 归还到池B错误的key<EFBFBD><EFBFBD>?
**当前代码行为:**
```objc
@@ -246,9 +246,9 @@ testStateTransition_DoubleReturn_Idempotent
}
```
**风险:** 可能导致池污
**风险:** 可能导致池污<EFBFBD><EFBFBD>?
**测试需求:** Q.3
**测试需<EFBFBD><EFBFBD>?** Q.3
```objc
testStateTransition_ReturnToWrongPool_Isolated
```
@@ -259,67 +259,67 @@ testStateTransition_ReturnToWrongPool_Isolated
**场景:**
- 连接被借出 (inUse=YES)
- `sendRequestData` 过程中网络错
- 连接被标invalidated
- `sendRequestData` 过程中网络错<EFBFBD><EFBFBD>?
- 连接被标<EFBFBD><EFBFBD>?invalidated
**当前代码行为:**
```objc
NSData *rawResponse = [connection sendRequestData:requestData ...];
if (!rawResponse) {
[self returnConnection:connection forKey:poolKey shouldClose:YES]; // invalidated
[self returnConnection:connection forKey:poolKey shouldClose:YES]; // <EFBFBD><EFBFBD>?invalidated
}
```
**测试需求:** Q.4
**测试需<EFBFBD><EFBFBD>?** Q.4
```objc
testStateTransition_ErrorDuringUse_Invalidated
```
---
#### 5. **池容量超限时的移除策略**
#### 5. **池容量超限时的移除策<EFBFBD><EFBFBD>?*
**场景:**
- 池已4 个连
- 5 个连接被归还
- 池已<EFBFBD><EFBFBD>?4 个连<EFBFBD><EFBFBD>?
- <EFBFBD><EFBFBD>?5 个连接被归还
**当前代码行为:**
```objc
while (pool.count > kHttpdnsNWHTTPClientMaxIdleConnectionsPerHost) {
HttpdnsNWReusableConnection *oldest = pool.firstObject; // 移除最老的
HttpdnsNWReusableConnection *oldest = pool.firstObject; // <EFBFBD><EFBFBD>?移除最老的
[oldest invalidate];
[pool removeObject:oldest];
}
```
**问题:**
- 移除 `pool.firstObject` - 是按添加顺序还是使用顺序
- 移除 `pool.firstObject` - 是按添加顺序还是使用顺序<EFBFBD><EFBFBD>?
- NSMutableArray 顺序是否能保证?
**测试需求:** Q.5
**测试需<EFBFBD><EFBFBD>?** Q.5
```objc
testStateTransition_PoolOverflow_RemovesOldest
```
---
#### 6. **并发状态竞态**
#### 6. **并发状态竞<EFBFBD><EFBFBD>?*
**场景:**
- Thread A: dequeue 连接,设`inUse=YES`
- Thread A: dequeue 连接,设<EFBFBD><EFBFBD>?`inUse=YES`
- Thread B: 同时 prune 过期连接
- 竞态:连接同时被标inUse 和被移除
- 竞态:连接同时被标<EFBFBD><EFBFBD>?inUse 和被移除
**当前代码防护:**
```objc
- (void)pruneConnectionPool:... {
for (HttpdnsNWReusableConnection *conn in pool) {
if (conn.inUse) continue; // 跳过使用中的
if (conn.inUse) continue; // <EFBFBD><EFBFBD>?跳过使用中的
}
}
```
**测试需求:** Q.6 (可能已被 I 组部分覆盖)
**测试需<EFBFBD><EFBFBD>?** Q.6 (可能已被 I 组部分覆<EFBFBD><EFBFBD>?
```objc
testStateTransition_ConcurrentDequeueAndPrune_NoCorruption
```
@@ -335,100 +335,100 @@ testStateTransition_ConcurrentDequeueAndPrune_NoCorruption
**当前代码行为:**
```objc
if (![newConnection openWithTimeout:timeout error:error]) {
[newConnection invalidate]; // 立即失效
return nil; // 不加入池
[newConnection invalidate]; // <EFBFBD><EFBFBD>?立即失效
return nil; // <EFBFBD><EFBFBD>?不加入池
}
```
**测试需求:** Q.7
**测试需<EFBFBD><EFBFBD>?** Q.7
```objc
testStateTransition_OpenFails_NotAddedToPool
```
---
## 状态不变式State Invariants
## 状态不变式State Invariants<EFBFBD><EFBFBD>?
### 应该始终成立的约
### 应该始终成立的约<EFBFBD><EFBFBD>?
1. **互斥性:**
1. **互斥<EFBFBD><EFBFBD>?**
```
∀ connection: (inUse=YES) (dequeue count 1)
∀ connection: (inUse=YES) <EFBFBD><EFBFBD>?(dequeue count <EFBFBD><EFBFBD>?1)
```
同一连接不能被多次借出
2. **池完整性:**
2. **池完整<EFBFBD><EFBFBD>?**
```
∀ pool: ∑(connections) maxPoolSize (4)
∀ pool: <EFBFBD><EFBFBD>?connections) <EFBFBD><EFBFBD>?maxPoolSize (4)
```
每个池最4 个连
每个池最<EFBFBD><EFBFBD>?4 个连<EFBFBD><EFBFBD>?
3. **状态一致性:**
3. **状态一致<EFBFBD><EFBFBD>?**
```
∀ connection in pool: !invalidated
```
池中不应有失效连
池中不应有失效连<EFBFBD><EFBFBD>?
4. **时间单调性:**
4. **时间单调<EFBFBD><EFBFBD>?**
```
∀ connection: lastUsedDate 随每次使用递增
```
5. **失效不可逆:**
5. **失效不可<EFBFBD><EFBFBD>?**
```
invalidated=YES connection removed from pool
invalidated=YES <EFBFBD><EFBFBD>?connection removed from pool
```
失效连接必须从池中移
失效连接必须从池中移<EFBFBD><EFBFBD>?
---
## 测试设计建议
### Q 组:状态机异常转换测试7个新测试
### Q 组:状态机异常转换测试<EFBFBD><EFBFBD>?个新测试<EFBFBD><EFBFBD>?
| 测试 | 验证内容 | 难度 |
|------|---------|------|
| **Q.1** | Stale connection `isViable` 检测并跳过 | 🔴 高(需要模拟服务器关闭 |
| **Q.2** | 双重归还是幂等的 | 🟢 |
| **Q.3** | 归还到错误池键不污染其他| 🟡 |
| **Q.4** | 使用中错误导致连接失| 🟢 低(已有 P 组部分覆盖) |
| **Q.5** | 池溢出时移除最旧连| 🟡 |
| **Q.6** | 并发 dequeue/prune 竞| 🔴 高(需要精确时序) |
| **Q.7** | 打开失败的连接不加入| 🟢 |
| **Q.1** | Stale connection <EFBFBD><EFBFBD>?`isViable` 检测并跳过 | 🔴 高(需要模拟服务器关闭<EFBFBD><EFBFBD>?|
| **Q.2** | 双重归还是幂等的 | 🟢 <EFBFBD><EFBFBD>?|
| **Q.3** | 归还到错误池键不污染其他<EFBFBD><EFBFBD>?| 🟡 <EFBFBD><EFBFBD>?|
| **Q.4** | 使用中错误导致连接失<EFBFBD><EFBFBD>?| 🟢 低(已有 P 组部分覆盖) |
| **Q.5** | 池溢出时移除最旧连<EFBFBD><EFBFBD>?| 🟡 <EFBFBD><EFBFBD>?|
| **Q.6** | 并发 dequeue/prune 竞<EFBFBD><EFBFBD>?| 🔴 高(需要精确时序) |
| **Q.7** | 打开失败的连接不加入<EFBFBD><EFBFBD>?| 🟢 <EFBFBD><EFBFBD>?|
---
## 状态机验证策略
### 方法1: 直接状态检
### 方法1: 直接状态检<EFBFBD><EFBFBD>?
```objc
// 验证状态属
// 验证状态属<EFBFBD><EFBFBD>?
XCTAssertTrue(connection.inUse);
XCTAssertFalse(connection.isInvalidated);
XCTAssertEqual([poolCount], expectedCount);
```
### 方法2: 状态转换序
### 方法2: 状态转换序<EFBFBD><EFBFBD>?
```objc
// 验证转换序列
[client resetPoolStatistics];
// CREATING IN_USE
// CREATING <EFBFBD><EFBFBD>?IN_USE
response1 = [client performRequest...];
XCTAssertEqual(creationCount, 1);
// IN_USE IDLE
// IN_USE <EFBFBD><EFBFBD>?IDLE
[NSThread sleepForTimeInterval:0.5];
XCTAssertEqual(poolCount, 1);
// IDLE IN_USE (reuse)
// IDLE <EFBFBD><EFBFBD>?IN_USE (reuse)
response2 = [client performRequest...];
XCTAssertEqual(reuseCount, 1);
```
### 方法3: 不变式验
### 方法3: 不变式验<EFBFBD><EFBFBD>?
```objc
// 验证池不变式
@@ -441,32 +441,32 @@ for (NSString *key in keys) {
---
## 当前覆盖率评
## 当前覆盖率评<EFBFBD><EFBFBD>?
### 状态转换覆盖矩
### 状态转换覆盖矩<EFBFBD><EFBFBD>?
| From / To | CREATING | IN_USE | IDLE | EXPIRED | INVALIDATED |
| From <EFBFBD><EFBFBD>?/ To <EFBFBD><EFBFBD>?| CREATING | IN_USE | IDLE | EXPIRED | INVALIDATED |
|---------------|----------|--------|------|---------|-------------|
| **CREATING** | - | ✅ | ❌ | ❌ | ✅ (Q.7 needed) |
| **IN_USE** | | - | ✅ | ❌ | ✅ |
| **IDLE** | ❌ | ✅ | - | ✅ | ❌ (Q.1 needed) |
| **EXPIRED** | ❌ | ❌ | ❌ | - | |
| **INVALIDATED** | ❌ | ❌ | ❌ | ❌ | - |
| **CREATING** | - | <EFBFBD><EFBFBD>?| <20><>?| <20><>?| <20><>?(Q.7 needed) |
| **IN_USE** | <EFBFBD><EFBFBD>?| - | <EFBFBD><EFBFBD>?| <20><>?| <20><>?|
| **IDLE** | <EFBFBD><EFBFBD>?| <20><>?| - | <20><>?| <20><>?(Q.1 needed) |
| **EXPIRED** | <EFBFBD><EFBFBD>?| <20><>?| <20><>?| - | <EFBFBD><EFBFBD>?|
| **INVALIDATED** | <EFBFBD><EFBFBD>?| <20><>?| <20><>?| <20><>?| - |
**覆盖率:** 6/25 transitions = 24%
**有效覆盖率:** 6/10 valid transitions = 60%
**覆盖<EFBFBD><EFBFBD>?** 6/25 transitions = 24%
**有效覆盖<EFBFBD><EFBFBD>?** 6/10 valid transitions = 60%
### 异常场景覆盖
| 异常场景 | 当前测试 | 覆盖 |
|----------|---------|------|
| Stale connection | | 0% |
| Double return | | 0% |
| Wrong pool key | | 0% |
| Stale connection | <EFBFBD><EFBFBD>?| 0% |
| Double return | <EFBFBD><EFBFBD>?| 0% |
| Wrong pool key | <EFBFBD><EFBFBD>?| 0% |
| Error during use | P.1-P.6 | 100% |
| Pool overflow | O.3, J.3 | 50% (未验证移除策略) |
| Pool overflow | O.3, J.3 | 50% (未验证移除策<EFBFBD><EFBFBD>? |
| Concurrent race | I.1-I.5 | 80% |
| Open failure | | 0% |
| Open failure | <EFBFBD><EFBFBD>?| 0% |
**总体异常覆盖:** ~40%
@@ -476,15 +476,15 @@ for (NSString *key in keys) {
### 高风险未测试场景
**风险等级 🔴 高:**
**风险等级 🔴 <EFBFBD><EFBFBD>?**
1. **Stale Connection (Q.1)** - 可能导致请求失败
2. **Concurrent Dequeue/Prune (Q.6)** - 可能导致状态不一
2. **Concurrent Dequeue/Prune (Q.6)** - 可能导致状态不一<EFBFBD><EFBFBD>?
**风险等级 🟡 中:**
3. **Wrong Pool Key (Q.3)** - 可能导致池污
**风险等级 🟡 <EFBFBD><EFBFBD>?**
3. **Wrong Pool Key (Q.3)** - 可能导致池污<EFBFBD><EFBFBD>?
4. **Pool Overflow Strategy (Q.5)** - LRU vs FIFO 影响性能
**风险等级 🟢 低:**
**风险等级 🟢 <EFBFBD><EFBFBD>?**
5. **Double Return (Q.2)** - 已有代码防护
6. **Open Failure (Q.7)** - 已有错误处理
@@ -494,21 +494,21 @@ for (NSString *key in keys) {
### 短期(关键)
1. **添加 Q.2 测试** - 验证双重归还防护
2. **添加 Q.5 测试** - 验证池溢出移除策
3. **添加 Q.7 测试** - 验证打开失败处理
1. <EFBFBD><EFBFBD>?**添加 Q.2 测试** - 验证双重归还防护
2. <EFBFBD><EFBFBD>?**添加 Q.5 测试** - 验证池溢出移除策<EFBFBD><EFBFBD>?
3. <EFBFBD><EFBFBD>?**添加 Q.7 测试** - 验证打开失败处理
### 中期(增强)
4. ⚠️ **添加 Q.3 测试** - 验证池隔
5. ⚠️ **添加 Q.1 测试** - 验证 stale connectionmock
4. ⚠️ **添加 Q.3 测试** - 验证池隔<EFBFBD><EFBFBD>?
5. ⚠️ **添加 Q.1 测试** - 验证 stale connection<EFBFBD><EFBFBD>?mock<EFBFBD><EFBFBD>?
### 长期(完整)
6. 🔬 **添加 Q.6 测试** - 验证并发竞态(复杂
6. 🔬 **添加 Q.6 测试** - 验证并发竞态(复杂<EFBFBD><EFBFBD>?
---
**创建时间**: 2025-11-01
**作者**: Claude Code
**状态**: 分析完成,待实现 Q 组测
**作<EFBFBD><EFBFBD>?*: Claude Code
**状<EFBFBD><EFBFBD>?*: 分析完成,待实现 Q 组测<EFBFBD><EFBFBD>?

View File

@@ -2,10 +2,10 @@
## 问题描述
当前测试套件没有充分验证**超时与连接池交互**的"无形结果"intangible outcomes可能存在以下风险
当前测试套件没有充分验证**超时与连接池交互**<EFBFBD><EFBFBD>?无形结果"intangible outcomes可能存在以下风险<EFBFBD><EFBFBD>?
- 超时后的连接泄漏
- 连接池被超时连接污染
- 连接池无法从超时中恢
- 连接池无法从超时中恢<EFBFBD><EFBFBD>?
- 并发场景下部分超时影响整体池健康
---
@@ -26,11 +26,11 @@ if (!rawResponse) {
```objc
if (shouldClose || connection.isInvalidated) {
[connection invalidate]; // 取消底层 nw_connection
[pool removeObject:connection]; // 从池中移
[pool removeObject:connection]; // 从池中移<EFBFBD><EFBFBD>?
}
```
**结论**:代码逻辑正确,超时连接**会被移除**而非留在池中
**结论**:代码逻辑正确,超时连<EFBFBD><EFBFBD>?*会被移除**而非留在池中<EFBFBD><EFBFBD>?
---
@@ -38,33 +38,33 @@ if (shouldClose || connection.isInvalidated) {
### 已有测试:`testIntegration_RequestTimeout_ReturnsError`
**验证内容**
- 超时返回 `nil` response
- 超时设置 `error`
**验证内容<EFBFBD><EFBFBD>?*
- <EFBFBD><EFBFBD>?超时返回 `nil` response
- <EFBFBD><EFBFBD>?超时设置 `error`
**未验证内容(缺失):**
- 连接是否从池中移
- 池计数是否正
- 后续请求是否正常工作
- 是否存在连接泄漏
- 并发场景下部分超时的影响
- <EFBFBD><EFBFBD>?连接是否从池中移<EFBFBD><EFBFBD>?
- <EFBFBD><EFBFBD>?池计数是否正<EFBFBD><EFBFBD>?
- <EFBFBD><EFBFBD>?后续请求是否正常工作
- <EFBFBD><EFBFBD>?是否存在连接泄漏
- <EFBFBD><EFBFBD>?并发场景下部分超时的影响
---
## 需要验证的"无形结果"
### 1. 单次超时后的池清
### 1. 单次超时后的池清<EFBFBD><EFBFBD>?
**场景**
1. 请求 A 超时timeout=1s, endpoint=/delay/10
2. 验证池状
**场景**<EFBFBD><EFBFBD>?
1. 请求 A 超时timeout=1s, endpoint=/delay/10<EFBFBD><EFBFBD>?
2. 验证池状<EFBFBD><EFBFBD>?
**应验证:**
- Pool count = 0连接已移除
- Pool count = 0连接已移除<EFBFBD><EFBFBD>?
- Total connection count 没有异常增长
- 无连接泄
- 无连接泄<EFBFBD><EFBFBD>?
**测试方法**
**测试方法**<EFBFBD><EFBFBD>?
```objc
[client resetPoolStatistics];
@@ -78,7 +78,7 @@ HttpdnsNWHTTPClientResponse *response = [client performRequestWithURLString:@"ht
XCTAssertNil(response);
XCTAssertNotNil(error);
// 验证池状
// 验证池状<EFBFBD><EFBFBD>?
NSString *poolKey = @"127.0.0.1:11080:tcp";
XCTAssertEqual([client connectionPoolCountForKey:poolKey], 0, @"Timed-out connection should be removed");
XCTAssertEqual([client totalConnectionCount], 0, @"No connections should remain");
@@ -88,72 +88,72 @@ XCTAssertEqual(client.connectionReuseCount, 0, @"No reuse for timed-out connecti
---
### 2. 超时后的池恢复能
### 2. 超时后的池恢复能<EFBFBD><EFBFBD>?
**场景**
**场景**<EFBFBD><EFBFBD>?
1. 请求 A 超时
2. 请求 B 正常(验证池恢复
3. 请求 C 复用 B 的连
2. 请求 B 正常(验证池恢复<EFBFBD><EFBFBD>?
3. 请求 C 复用 B 的连<EFBFBD><EFBFBD>?
**应验证:**
- 请求 B 成功(池已恢复)
- 请求 C 复用连接connectionReuseCount = 1
- Pool count = 1B/C 的连接)
- 请求 C 复用连接connectionReuseCount = 1<EFBFBD><EFBFBD>?
- Pool count = 1<EFBFBD><EFBFBD>?B/C 的连接)
---
### 3. 并发场景:部分超时不影响成功请求
**场景**
1. 并发发起 10 个请
2. 5 个正常timeout=15s
3. 5 个超时timeout=0.5s, endpoint=/delay/10
**场景**<EFBFBD><EFBFBD>?
1. 并发发起 10 个请<EFBFBD><EFBFBD>?
2. 5 个正常timeout=15s<EFBFBD><EFBFBD>?
3. 5 个超时timeout=0.5s, endpoint=/delay/10<EFBFBD><EFBFBD>?
**应验证:**
- 5 个正常请求成
- 5 个超时请求失
- Pool count 5只保留成功的连接
- Total connection count 5无泄漏
- connectionCreationCount 10合理范围
- 成功的请求可以复用连
- 5 个正常请求成<EFBFBD><EFBFBD>?
- 5 个超时请求失<EFBFBD><EFBFBD>?
- Pool count <EFBFBD><EFBFBD>?5只保留成功的连接
- Total connection count <EFBFBD><EFBFBD>?5无泄漏<EFBFBD><EFBFBD>?
- connectionCreationCount <EFBFBD><EFBFBD>?10合理范围
- 成功的请求可以复用连<EFBFBD><EFBFBD>?
---
### 4. 连续超时不导致资源泄
### 4. 连续超时不导致资源泄<EFBFBD><EFBFBD>?
**场景**
1. 连续 20 次超时请
2. 验证连接池没有累积"僵尸连接"
**场景**<EFBFBD><EFBFBD>?
1. 连续 20 次超时请<EFBFBD><EFBFBD>?
2. 验证连接池没有累<EFBFBD><EFBFBD>?僵尸连接"
**应验证:**
- Pool count = 0
- Total connection count = 0
- connectionCreationCount = 20每次都创建新连接因为超时的被移除
- connectionCreationCount = 20每次都创建新连接因为超时的被移除<EFBFBD><EFBFBD>?
- connectionReuseCount = 0超时连接不可复用
- 无内存泄漏(虽然代码层面无法直接测试
- 无内存泄漏(虽然代码层面无法直接测试<EFBFBD><EFBFBD>?
---
### 5. 超时不阻塞连接池
**场景**
1. 请求 A 超时endpoint=/delay/10, timeout=1s
2. 同时请求 B 正常endpoint=/get, timeout=15s
**场景**<EFBFBD><EFBFBD>?
1. 请求 A 超时endpoint=/delay/10, timeout=1s<EFBFBD><EFBFBD>?
2. 同时请求 B 正常endpoint=/get, timeout=15s<EFBFBD><EFBFBD>?
**应验证:**
- 请求 A B 并发执行(不互相阻塞
- 请求 B 成功(不A 超时影响
- 请求 A <EFBFBD><EFBFBD>?B 并发执行(不互相阻塞<EFBFBD><EFBFBD>?
- 请求 B 成功(不<EFBFBD><EFBFBD>?A 超时影响<EFBFBD><EFBFBD>?
- 请求 A 的超时连接被正确移除
- Pool 中只有请B 的连
- Pool 中只有请<EFBFBD><EFBFBD>?B 的连<EFBFBD><EFBFBD>?
---
### 6. 多端口场景下的超时隔
### 6. 多端口场景下的超时隔<EFBFBD><EFBFBD>?
**场景**
**场景**<EFBFBD><EFBFBD>?
1. 端口 11443 请求超时
2. 端口 11444 请求正常
3. 验证端口间隔
3. 验证端口间隔<EFBFBD><EFBFBD>?
**应验证:**
- 端口 11443 pool count = 0
@@ -175,52 +175,52 @@ XCTAssertEqual(client.connectionReuseCount, 0, @"No reuse for timed-out connecti
**P.3 并发部分超时**
- `testTimeout_ConcurrentPartialTimeout_SuccessfulRequestsReuse`
**P.4 连续超时无泄漏**
**P.4 连续超时无泄<EFBFBD><EFBFBD>?*
- `testTimeout_ConsecutiveTimeouts_NoConnectionLeak`
**P.5 超时不阻塞池**
- `testTimeout_NonBlocking_ConcurrentNormalRequestSucceeds`
**P.6 多端口超时隔离**
**P.6 多端口超时隔<EFBFBD><EFBFBD>?*
- `testTimeout_MultiPort_IsolatedPoolCleaning`
---
## Mock Server 支持
需要添加可配置延迟endpoint
- `/delay/10` - 延迟 10 秒(已有
- 测试时设置短 timeout如 0.5s-2s触发超
需要添加可配置延迟<EFBFBD><EFBFBD>?endpoint<EFBFBD><EFBFBD>?
- `/delay/10` - 延迟 10 秒(已有<EFBFBD><EFBFBD>?
- 测试时设置短 timeout如 0.5s-2s触发超<EFBFBD><EFBFBD>?
---
## 预期测试结果
| 验证| 当前状| 目标状|
| 验证<EFBFBD><EFBFBD>?| 当前状<EFBFBD><EFBFBD>?| 目标状<EFBFBD><EFBFBD>?|
|--------|---------|---------|
| 超时连接移除 | 未验证 | ✅ 验证池计数=0 |
| 池恢复能| 未验证 | ✅ 后续请求成功 |
| 并发超时隔离 | 未验证 | ✅ 成功请求不受影响 |
| 无连接泄| 未验证 | ✅ 总连接数稳定 |
| 超时不阻| 未验证 | ✅ 并发执行不阻|
| 多端口隔| 未验证 | ✅ 端口间独立清|
| 超时连接移除 | 未验<EFBFBD><EFBFBD>?| <20><>?验证池计<EFBFBD><EFBFBD>?0 |
| 池恢复能<EFBFBD><EFBFBD>?| 未验<EFBFBD><EFBFBD>?| <20><>?后续请求成功 |
| 并发超时隔离 | 未验<EFBFBD><EFBFBD>?| <20><>?成功请求不受影响 |
| 无连接泄<EFBFBD><EFBFBD>?| 未验<EFBFBD><EFBFBD>?| <20><>?总连接数稳定 |
| 超时不阻<EFBFBD><EFBFBD>?| 未验<EFBFBD><EFBFBD>?| <20><>?并发执行不阻<EFBFBD><EFBFBD>?|
| 多端口隔<EFBFBD><EFBFBD>?| 未验<EFBFBD><EFBFBD>?| <20><>?端口间独立清<EFBFBD><EFBFBD>?|
---
## 风险评估
**如果不测试这些场景的风险**
1. **连接泄漏**:超时连接可能未正确清理,导致内存泄
2. **池污染**:超时连接留在池中,被后续请求复用导致失
**如果不测试这些场景的风险<EFBFBD><EFBFBD>?*
1. **连接泄漏**:超时连接可能未正确清理,导致内存泄<EFBFBD><EFBFBD>?
2. **池污<EFBFBD><EFBFBD>?*:超时连接留在池中,被后续请求复用导致失<EFBFBD><EFBFBD>?
3. **级联故障**:部分超时影响整体连接池健康
4. **资源耗尽**:连续超时累积连接,最终耗尽系统资源
**当前代码逻辑正确性:** 高(代码分析显示正确处理
**测试验证覆盖率:** 低(缺少池交互验证)
**当前代码逻辑正确性:** <EFBFBD><EFBFBD>?高(代码分析显示正确处理<EFBFBD><EFBFBD>?
**测试验证覆盖率:** <EFBFBD><EFBFBD>?低(缺少池交互验证)
**建议** 添加 P 组测试以提供**可观测的证据**证明超时处理正确
**建议<EFBFBD><EFBFBD>?* 添加 P 组测试以提供**可观测的证据**证明超时处理正确<EFBFBD><EFBFBD>?
---
**创建时间**: 2025-11-01
**维护者**: Claude Code
**维护<EFBFBD><EFBFBD>?*: Claude Code

View File

@@ -12,7 +12,7 @@
#import <SystemConfiguration/CaptiveNetwork.h>
#import <UIKit/UIDevice.h>
static char *const networkManagerQueue = "com.alibaba.managerQueue";
static char *const networkManagerQueue = "com.Trust.managerQueue";
static dispatch_queue_t reachabilityQueue;
@implementation NetworkManager {

View File

@@ -1,9 +1,9 @@
//
// TestBase.h
// AlicloudHttpDNS
// TrustHttpDNS
//
// Created by ElonChan地风 on 2017/4/14.
// Copyright © 2017年 alibaba-inc.com. All rights reserved.
// Copyright © 2017<EFBFBD><EFBFBD>?trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>

View File

@@ -1,9 +1,9 @@
//
// TestBase.m
// AlicloudHttpDNS
// TrustHttpDNS
//
// Created by ElonChan on 2017/4/14.
// Copyright © 2017 alibaba-inc.com. All rights reserved.
// Copyright © 2017<EFBFBD><EFBFBD>?trustapp.com. All rights reserved.
//
#import "TestBase.h"

View File

@@ -1,9 +1,9 @@
//
// TestBase.h
// AlicloudHttpDNS
// TrustHttpDNS
//
// Created by ElonChan地风 on 2017/4/14.
// Copyright © 2017年 alibaba-inc.com. All rights reserved.
// Copyright © 2017<EFBFBD><EFBFBD>?trustapp.com. All rights reserved.
//
#import <XCTest/XCTest.h>

View File

@@ -1,9 +1,9 @@
//
// TestBase.h
// AlicloudHttpDNS
// TrustHttpDNS
//
// Created by ElonChan on 2017/4/14.
// Copyright © 2017 alibaba-inc.com. All rights reserved.
// Copyright © 2017<EFBFBD><EFBFBD>?trustapp.com. All rights reserved.
//
#import "XCTestCase+AsyncTesting.h"