feat: sync httpdns sdk/platform updates without large binaries

This commit is contained in:
robin
2026-03-04 17:59:14 +08:00
parent 853897a6f8
commit 532891fad0
700 changed files with 6096 additions and 2712 deletions

View File

@@ -0,0 +1,591 @@
//
// HttpdnsNWHTTPClient_StateMachineTests.m
// TrustHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 trustapp.com. All rights reserved.
//
// - <EFBFBD><EFBFBD>?(Q) <EFBFBD><EFBFBD>?
// <EFBFBD><EFBFBD>?7 Q:17<EFBFBD><EFBFBD>?
//
#import "HttpdnsNWHTTPClientTestBase.h"
@interface HttpdnsNWHTTPClient_StateMachineTests : HttpdnsNWHTTPClientTestBase
@end
@implementation HttpdnsNWHTTPClient_StateMachineTests
#pragma mark - Q. <EFBFBD><EFBFBD>?
// Q1.1 LRU
- (void)testStateMachine_PoolOverflowLRU_RemovesOldestByLastUsedDate {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// <EFBFBD><EFBFBD>?
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 5<EFBFBD><EFBFBD>?
for (NSInteger i = 0; i < 5; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Request %ld", (long)i]];
[expectations addObject:expectation];
dispatch_async(queue, ^{
NSError *error = nil;
// 使 /delay/2
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/2"
userAgent:[NSString stringWithFormat:@"Request%ld", (long)i]
timeout:15.0
error:&error];
[expectation fulfill];
});
[NSThread sleepForTimeInterval:0.05]; // <EFBFBD><EFBFBD>?
}
[self waitForExpectations:expectations timeout:20.0];
// <EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:1.0];
// <EFBFBD><EFBFBD>?4LRU<EFBFBD><EFBFBD>?
NSUInteger poolCount = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolCount, 4,
@"Pool should enforce max 4 connections (LRU)");
//
XCTAssertGreaterThanOrEqual(self.client.connectionCreationCount, 3,
@"Should create multiple concurrent connections");
}
// Q2.1
- (void)testAbnormal_RapidSequentialRequests_NoDuplicates {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// <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"
userAgent:@"RapidTest"
timeout:15.0
error:&error];
XCTAssertNotNil(response);
}
//
[NSThread sleepForTimeInterval:1.0];
// <EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?
NSUInteger poolCount = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolCount, 1,
@"Pool should have at most 1 connection (rapid sequential reuse)");
// 1<EFBFBD><EFBFBD>?
XCTAssertEqual(self.client.connectionCreationCount, 1,
@"Should create only 1 connection for sequential requests");
}
// Q2.2
- (void)testAbnormal_DifferentPorts_IsolatedPools {
[self.client resetPoolStatistics];
NSString *poolKey11080 = @"127.0.0.1:11080:tcp";
NSString *poolKey11443 = @"127.0.0.1:11443:tls";
// <EFBFBD><EFBFBD>?1080
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"Port11080"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1);
// <EFBFBD><EFBFBD>?1443
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"Port11443"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2);
//
[NSThread sleepForTimeInterval:0.5];
// <EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey11080], 1,
@"Port 11080 pool should have 1 connection");
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey11443], 1,
@"Port 11443 pool should have 1 connection");
// 2
XCTAssertEqual([self.client totalConnectionCount], 2,
@"Total should be 2 (one per pool)");
}
// Q3.1
- (void)testInvariant_PoolSize_NeverExceedsLimit {
[self.client resetPoolStatistics];
// <EFBFBD><EFBFBD>?0
for (NSInteger i = 0; i < 20; i++) {
NSError *error = nil;
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"InvariantTest"
timeout:15.0
error:&error];
}
// <EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:1.5];
// 4
NSArray<NSString *> *allKeys = [self.client allConnectionPoolKeys];
for (NSString *key in allKeys) {
NSUInteger poolCount = [self.client connectionPoolCountForKey:key];
XCTAssertLessThanOrEqual(poolCount, 4,
@"Pool %@ size should never exceed 4 (actual: %lu)",
key, (unsigned long)poolCount);
}
// 4<EFBFBD><EFBFBD>?
XCTAssertLessThanOrEqual([self.client totalConnectionCount], 4,
@"Total connections should not exceed 4");
}
// Q3.3
- (void)testInvariant_NoDuplicates_ConcurrentRequests {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 15<EFBFBD><EFBFBD>?
for (NSInteger i = 0; i < 15; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Request %ld", (long)i]];
[expectations addObject:expectation];
dispatch_async(queue, ^{
NSError *error = nil;
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"ConcurrentTest"
timeout:15.0
error:&error];
[expectation fulfill];
});
}
[self waitForExpectations:expectations timeout:30.0];
//
[NSThread sleepForTimeInterval:1.0];
// <EFBFBD><EFBFBD>?4<EFBFBD><EFBFBD>?
NSUInteger poolCount = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolCount, 4,
@"Pool should not have duplicates (max 4 connections)");
// <EFBFBD><EFBFBD>?5<EFBFBD><EFBFBD>?
XCTAssertLessThanOrEqual(self.client.connectionCreationCount, 15,
@"Should not create excessive connections");
}
// Q4.1 <EFBFBD><EFBFBD>?0
- (void)testBoundary_Exactly30Seconds_ConnectionExpired {
if (getenv("SKIP_SLOW_TESTS")) {
return;
}
[self.client resetPoolStatistics];
// <EFBFBD><EFBFBD>?
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"InitialRequest"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1);
// 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"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2);
// 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 <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"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1);
// 2930
[NSThread sleepForTimeInterval:29.0];
//
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"BeforeExpiry"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2);
// <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 <EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?
- (void)testBoundary_ExactlyFourConnections_AllKept {
[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);
for (NSInteger i = 0; i < 4; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:
[NSString stringWithFormat:@"Request %ld", (long)i]];
[expectations addObject:expectation];
dispatch_async(queue, ^{
NSError *error = nil;
// 使 /delay/2 <EFBFBD><EFBFBD>?
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/2"
userAgent:[NSString stringWithFormat:@"Request%ld", (long)i]
timeout:15.0
error:&error];
[expectation fulfill];
});
[NSThread sleepForTimeInterval:0.05]; // <EFBFBD><EFBFBD>?
}
[self waitForExpectations:expectations timeout:20.0];
//
[NSThread sleepForTimeInterval:1.0];
// <EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?
NSUInteger poolCount = [self.client connectionPoolCountForKey:poolKey];
XCTAssertEqual(poolCount, 4,
@"Pool should keep all 4 connections (not exceeding limit)");
// <EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?
XCTAssertEqual(self.client.connectionCreationCount, 4,
@"Should create exactly 4 connections");
}
// Q1.2 <EFBFBD><EFBFBD>?
- (void)testStateMachine_NormalSequence_StateTransitionsCorrect {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// <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
error:nil];
XCTAssertNotNil(response1, @"First request should succeed");
[NSThread sleepForTimeInterval:1.0]; //
// 1<EFBFBD><EFBFBD>?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
@"Connection should be in pool");
// <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");
}
// Q1.3 inUse
- (void)testStateMachine_InUseFlag_CorrectlyMaintained {
[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
error:nil];
[NSThread sleepForTimeInterval:1.0]; //
//
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
XCTAssertEqual(connections.count, 1, @"Should have 1 connection in pool");
// inUse NO
for (HttpdnsNWReusableConnection *conn in connections) {
XCTAssertFalse(conn.inUse, @"Connection in pool should not be marked as inUse");
}
}
// Q2.3 Nil lastUsedDate
- (void)testAbnormal_NilLastUsedDate_HandledGracefully {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
//
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"NilDateTest"
timeout:15.0
error:nil];
[NSThread sleepForTimeInterval:1.0];
// <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];
// <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");
}
// Q3.2
- (void)testInvariant_NoInvalidatedInPool {
[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"
timeout:15.0
error:nil];
}
// 1<EFBFBD><EFBFBD>?
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
userAgent:@"TimeoutTest"
timeout:0.5
error:nil];
[NSThread sleepForTimeInterval:2.0];
// <EFBFBD><EFBFBD>?
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
//
for (HttpdnsNWReusableConnection *conn in connections) {
XCTAssertFalse(conn.isInvalidated, @"Pool should not contain invalidated connections");
}
}
// Q3.4 lastUsedDate <EFBFBD><EFBFBD>?
- (void)testInvariant_LastUsedDate_Monotonic {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// <EFBFBD><EFBFBD>?使<EFBFBD><EFBFBD>?
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"MonotonicTest"
timeout:15.0
error:nil];
[NSThread sleepForTimeInterval:1.0];
NSArray<HttpdnsNWReusableConnection *> *connections1 = [self.client connectionsInPoolForKey:poolKey];
XCTAssertEqual(connections1.count, 1, @"Should have connection");
NSDate *date1 = connections1.firstObject.lastUsedDate;
XCTAssertNotNil(date1, @"lastUsedDate should be set");
// 1<EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:1.0];
// <EFBFBD><EFBFBD>?使
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"MonotonicTest"
timeout:15.0
error:nil];
[NSThread sleepForTimeInterval:1.0];
NSArray<HttpdnsNWReusableConnection *> *connections2 = [self.client connectionsInPoolForKey:poolKey];
XCTAssertEqual(connections2.count, 1, @"Should still have 1 connection");
NSDate *date2 = connections2.firstObject.lastUsedDate;
// lastUsedDate
XCTAssertTrue([date2 timeIntervalSinceDate:date1] > 0,
@"lastUsedDate should increase after reuse");
}
// Q5.1 +<EFBFBD><EFBFBD>?
- (void)testCompound_TimeoutDuringPoolOverflow_Handled {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// <EFBFBD><EFBFBD>?
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSInteger i = 0; i < 4; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:
[NSString stringWithFormat:@"Fill pool %ld", (long)i]];
[expectations addObject:expectation];
dispatch_async(queue, ^{
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/2"
userAgent:@"CompoundTest"
timeout:15.0
error:nil];
[expectation fulfill];
});
[NSThread sleepForTimeInterval:0.05];
}
[self waitForExpectations:expectations timeout:20.0];
[NSThread sleepForTimeInterval:1.0];
NSUInteger poolCountBefore = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolCountBefore, 4, @"Pool should have <20><>? connections");
// <EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
userAgent:@"TimeoutRequest"
timeout:0.5
error:&error];
XCTAssertNil(response, @"Timeout request should return nil");
XCTAssertNotNil(error, @"Should have error");
[NSThread sleepForTimeInterval:1.0];
// <EFBFBD><EFBFBD>?
NSUInteger poolCountAfter = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolCountAfter, 4, @"Timed-out connection should not be added to pool");
}
// Q2.4
- (void)testAbnormal_OpenFailure_NotAddedToPool {
[self.client resetPoolStatistics];
//
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:99999/get"
userAgent:@"FailureTest"
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 <EFBFBD><EFBFBD>?
- (void)testAbnormal_MultipleInvalidate_Idempotent {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
//
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"InvalidateTest"
timeout:15.0
error:nil];
[NSThread sleepForTimeInterval:1.0];
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
XCTAssertEqual(connections.count, 1, @"Should have connection");
HttpdnsNWReusableConnection *conn = connections.firstObject;
// invalidate
[conn debugInvalidate];
[conn debugInvalidate];
[conn debugInvalidate];
//
XCTAssertTrue(conn.isInvalidated, @"Connection should be invalidated");
}
// 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
error:nil];
[self.client performRequestWithURLString:@"http://127.0.0.1:11443/get"
userAgent:@"RaceTest"
timeout:15.0
error:nil];
[NSThread sleepForTimeInterval:1.0];
// 30
[NSThread sleepForTimeInterval:30.5];
// <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);
dispatch_group_async(group, queue, ^{
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"Race1"
timeout:15.0
error:nil];
});
dispatch_group_async(group, queue, ^{
[self.client performRequestWithURLString:@"http://127.0.0.1:11443/get"
userAgent:@"Race2"
timeout:15.0
error:nil];
});
//
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 20 * NSEC_PER_SEC));
//
NSUInteger totalCount = [self.client totalConnectionCount];
XCTAssertLessThanOrEqual(totalCount, 4, @"Pool should remain stable after concurrent prune");
}
@end