blob: 98e8a558678339d974a6542266ad19db65cec312 [file] [log] [blame] [edit]
/*
* Copyright (c) 2020-2023 Apple Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "unittest_common.h"
#import "DNSHeuristicsInternal.h"
#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
@interface DNSHeuristicsTest : XCTestCase
@end
@implementation DNSHeuristicsTest
#if (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST && !TARGET_OS_XR)
- (void)testEmptyStateFailure {
id mockHeuristics = OCMClassMock([DNSHeuristics class]);
OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(@{});
OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
NSURL *url = [NSURL URLWithString:@"https://example.com"];
XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
OCMVerify(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]]));
}
- (void)testStateFailureUnderThreshold {
id mockHeuristics = OCMClassMock([DNSHeuristics class]);
NSUInteger now = [DNSHeuristics currentTimeMs];
NSURL *url = [NSURL URLWithString:@"https://example.com"];
NSDictionary *existingState = @{
DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:now],
DNSHeuristicsLongCounterKey: [NSNumber numberWithInt:1],
DNSHeuristicsBurstCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicsDefaultBurstTokenBucketCapacity],
DNSHeuristicsFilterFlagKey: @(NO),
};
OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
OCMVerify(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]]));
}
- (void)testStateFailureOverThreshold {
id mockHeuristics = OCMClassMock([DNSHeuristics class]);
NSUInteger now = [DNSHeuristics currentTimeMs];
NSURL *url = [NSURL URLWithString:@"https://example.com"];
NSDictionary *existingState = @{
DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:now],
DNSHeuristicsLongCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicDefaultLongCounterThreshold], // reporting an error will cause this count to exceed the threshold
DNSHeuristicsBurstCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicsDefaultBurstTokenBucketCapacity],
DNSHeuristicsFilterFlagKey: @(NO),
};
OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
OCMVerify(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]]));
}
- (void)testStateFailureUnderThreshold_StickAfterFailure {
id mockHeuristics = OCMClassMock([DNSHeuristics class]);
NSUInteger now = [DNSHeuristics currentTimeMs];
NSURL *url = [NSURL URLWithString:@"https://example.com"];
NSDictionary *existingState = @{
DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:now],
DNSHeuristicsLongCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicDefaultLongCounterThreshold],
DNSHeuristicsBurstCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicsDefaultBurstTokenBucketCapacity],
DNSHeuristicsFilterFlagKey: @(YES),
};
OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + DNSHeuristicDefaultLongCounterTimeWindow * 2); // two days pass, we should reset
XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
OCMReject(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]]));
}
- (void)testStateFailureUnderThreshold_ResetAfterSuccess {
id mockHeuristics = OCMClassMock([DNSHeuristics class]);
NSUInteger now = [DNSHeuristics currentTimeMs];
NSDictionary *existingState = @{
DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:now],
DNSHeuristicsLongCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicDefaultLongCounterThreshold],
DNSHeuristicsBurstCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicsDefaultBurstTokenBucketCapacity],
DNSHeuristicsFilterFlagKey: @(YES),
};
OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + DNSHeuristicDefaultLongCounterTimeWindow * 2); // two days pass, we should reset
XCTAssertTrue([DNSHeuristics updateHeuristicState:YES isTimeout:NO]);
OCMVerify(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]]));
}
- (void)testStateFailureDrainTokenBucket_NoReset {
id mockHeuristics = OCMClassMock([DNSHeuristics class]);
NSUInteger now = [DNSHeuristics currentTimeMs];
NSURL *url = [NSURL URLWithString:@"https://example.com"];
NSDictionary *existingState = @{
DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:now],
DNSHeuristicsLongCounterKey: [NSNumber numberWithInt:0],
DNSHeuristicsBurstCounterKey: [NSNumber numberWithInt:1],
DNSHeuristicsFilterFlagKey: @(NO),
};
OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + 1); // within the same epoch -- overflow
XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
OCMReject(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]]));
}
- (void)testStateFailureDrainTokenBucket_Reset {
id mockHeuristics = OCMClassMock([DNSHeuristics class]);
NSUInteger now = [DNSHeuristics currentTimeMs];
NSURL *url = [NSURL URLWithString:@"https://example.com"];
NSDictionary *existingState = @{
DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:now],
DNSHeuristicsLongCounterKey: [NSNumber numberWithInt:0],
DNSHeuristicsBurstCounterKey: [NSNumber numberWithInt:1],
DNSHeuristicsFilterFlagKey: @(NO),
};
OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + DNSHeuristicsDefaultBurstTokenBucketRefillTime + 1); // allow the bucket to replenish
XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
OCMReject(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]]));
}
- (void)testStateFailureFilteredThenSuccessBeforeWindow {
id mockHeuristics = OCMClassMock([DNSHeuristics class]);
NSUInteger now = [DNSHeuristics currentTimeMs];
NSDictionary *existingState = @{
DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:now],
DNSHeuristicsLongCounterKey: [NSNumber numberWithInt:0],
DNSHeuristicsBurstCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicsDefaultBurstTokenBucketCapacity],
DNSHeuristicsFilterFlagKey: @(YES),
};
OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + DNSHeuristicsDefaultBurstTokenBucketRefillTime + 1); // allow the bucket to replenish
XCTAssertTrue([DNSHeuristics updateHeuristicState:YES isTimeout:NO]);
OCMReject(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]]));
}
- (void)testStateFailureFilteredThenSuccessAfterWindow {
id mockHeuristics = OCMClassMock([DNSHeuristics class]);
NSUInteger now = [DNSHeuristics currentTimeMs];
NSDictionary *existingState = @{
DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:(now - DNSHeuristicDefaultLongCounterTimeWindow)],
DNSHeuristicsLongCounterKey: [NSNumber numberWithInt:0],
DNSHeuristicsBurstCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicsDefaultBurstTokenBucketCapacity],
DNSHeuristicsFilterFlagKey: @(YES),
};
OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + DNSHeuristicsDefaultBurstTokenBucketRefillTime + 1); // allow the bucket to replenish
XCTAssertTrue([DNSHeuristics updateHeuristicState:YES isTimeout:NO]);
OCMReject(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]]));
}
#endif // (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST && !TARGET_OS_XR)
@end