mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
* Fix FIAM test flakes and resulting library bugs * style * Fix naming of initWithExpireAdfterInSeconds:withTimeFetcher:withCacheFile. Move mock setup of mockTimeFetcher prior to initializing FIRIAMClearcutLogStorage * FIRIAMClearcutUploader: Only schedule next log send on init if there are logs that haven't been sent at that time. Early return from scheduling if there's already a pending send * Fix whitespace * Make cachePath in `initWithExpireAfterInSeconds:withTimeFetcher:cachePath:` nullable
400 lines
18 KiB
Objective-C
400 lines
18 KiB
Objective-C
/*
|
|
* Copyright 2018 Google
|
|
*
|
|
* 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 <OCMock/OCMock.h>
|
|
#import <XCTest/XCTest.h>
|
|
|
|
#import "FIRIAMClearcutHttpRequestSender.h"
|
|
#import "FIRIAMClearcutLogStorage.h"
|
|
#import "FIRIAMClearcutUploader.h"
|
|
#import "FIRIAMTimeFetcher.h"
|
|
|
|
@interface FIRIAMClearcutUploaderTests : XCTestCase
|
|
@property(nonatomic) id<FIRIAMTimeFetcher> mockTimeFetcher;
|
|
@property(nonatomic) FIRIAMClearcutHttpRequestSender *mockRequestSender;
|
|
@property(nonatomic) FIRIAMClearcutLogStorage *mockLogStorage;
|
|
@property(nonatomic) FIRIAMClearcutStrategy *defaultStrategy;
|
|
@property(nonatomic) NSUserDefaults *mockUserDefaults;
|
|
@property(nonatomic) NSString *cachePath;
|
|
@end
|
|
|
|
// Expose certain internal things to help with unit testing.
|
|
@interface FIRIAMClearcutUploader (UnitTest)
|
|
@property(nonatomic, assign) int64_t nextValidSendTimeInMills;
|
|
@end
|
|
|
|
@implementation FIRIAMClearcutUploaderTests
|
|
|
|
// Helper function to avoid conflicts between tests with the singleton cache path.
|
|
- (NSString *)generatedCachePath {
|
|
// Filter out any invalid filesystem characters.
|
|
NSCharacterSet *invalidCharacters = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
|
|
|
|
// This will result in a string with the class name, a space, and the test name. We only care
|
|
// about the test name so split it into components and return the last item.
|
|
NSString *friendlyTestName = [self.name stringByTrimmingCharactersInSet:invalidCharacters];
|
|
NSArray<NSString *> *components = [friendlyTestName componentsSeparatedByString:@" "];
|
|
NSString *testName = [components lastObject];
|
|
|
|
NSString *cacheDirPath =
|
|
NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
|
|
return [NSString stringWithFormat:@"%@/%@", cacheDirPath, testName];
|
|
}
|
|
|
|
- (void)setUp {
|
|
[super setUp];
|
|
self.mockTimeFetcher = OCMProtocolMock(@protocol(FIRIAMTimeFetcher));
|
|
self.mockRequestSender = OCMClassMock(FIRIAMClearcutHttpRequestSender.class);
|
|
self.mockLogStorage = OCMClassMock(FIRIAMClearcutLogStorage.class);
|
|
|
|
self.defaultStrategy = [[FIRIAMClearcutStrategy alloc] initWithMinWaitTimeInMills:1000
|
|
maxWaitTimeInMills:2000
|
|
failureBackoffTimeInMills:1000
|
|
batchSendSize:10];
|
|
|
|
self.mockUserDefaults = OCMClassMock(NSUserDefaults.class);
|
|
self.cachePath = [self generatedCachePath];
|
|
OCMStub([self.mockUserDefaults integerForKey:[OCMArg any]]).andReturn(0);
|
|
}
|
|
|
|
- (void)tearDown {
|
|
[[NSFileManager defaultManager] removeItemAtPath:self.cachePath error:NULL];
|
|
[super tearDown];
|
|
}
|
|
|
|
- (void)testUploadTriggeredWhenWaitTimeConditionSatisfied {
|
|
NSTimeInterval currentMoment = 10000;
|
|
OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(currentMoment);
|
|
|
|
// using a real storage in this case
|
|
FIRIAMClearcutLogStorage *logStorage =
|
|
[[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:1000
|
|
withTimeFetcher:self.mockTimeFetcher
|
|
cachePath:self.cachePath];
|
|
|
|
FIRIAMClearcutUploader *uploader =
|
|
[[FIRIAMClearcutUploader alloc] initWithRequestSender:self.mockRequestSender
|
|
timeFetcher:self.mockTimeFetcher
|
|
logStorage:logStorage
|
|
usingStrategy:self.defaultStrategy
|
|
usingUserDefaults:self.mockUserDefaults];
|
|
|
|
// Upload right away: nextValidSendTimeInMills < current time
|
|
uploader.nextValidSendTimeInMills = (int64_t)(currentMoment - 1) * 1000;
|
|
|
|
XCTestExpectation *expectation = [self expectationWithDescription:@"Triggers send on sender"];
|
|
|
|
OCMStub([self.mockRequestSender sendClearcutHttpRequestForLogs:[OCMArg any]
|
|
withCompletion:[OCMArg any]])
|
|
.andDo(^(NSInvocation *invocation) {
|
|
[expectation fulfill];
|
|
});
|
|
|
|
FIRIAMClearcutLogRecord *newRecord =
|
|
[[FIRIAMClearcutLogRecord alloc] initWithExtensionJsonString:@"string"
|
|
eventTimestampInSeconds:currentMoment];
|
|
|
|
[uploader addNewLogRecord:newRecord];
|
|
|
|
// We expect expectation to be fulfilled right away since the upload can be carried out without
|
|
// delay.
|
|
[self waitForExpectationsWithTimeout:1.0 handler:nil];
|
|
}
|
|
|
|
- (void)testUploadNotTriggeredWhenWaitTimeConditionNotSatisfied {
|
|
// using a real storage in this case
|
|
NSTimeInterval currentMoment = 10000;
|
|
OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(currentMoment);
|
|
|
|
FIRIAMClearcutLogStorage *logStorage =
|
|
[[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:1000
|
|
withTimeFetcher:self.mockTimeFetcher
|
|
cachePath:self.cachePath];
|
|
|
|
FIRIAMClearcutUploader *uploader =
|
|
[[FIRIAMClearcutUploader alloc] initWithRequestSender:self.mockRequestSender
|
|
timeFetcher:self.mockTimeFetcher
|
|
logStorage:logStorage
|
|
usingStrategy:self.defaultStrategy
|
|
usingUserDefaults:self.mockUserDefaults];
|
|
|
|
// Forces uploading to be at least 5 seconds later.
|
|
uploader.nextValidSendTimeInMills = (int64_t)(currentMoment + 5) * 1000;
|
|
|
|
XCTestExpectation *expectation = [self expectationWithDescription:@"Triggers send on sender"];
|
|
|
|
FIRIAMClearcutLogRecord *newRecord =
|
|
[[FIRIAMClearcutLogRecord alloc] initWithExtensionJsonString:@"string"
|
|
eventTimestampInSeconds:currentMoment];
|
|
|
|
__block BOOL sendingAttempted = NO;
|
|
// We don't expect sendClearcutHttpRequestForLogs:withCompletion: to be triggered
|
|
// after wait for 2.0 seconds below. We have a BOOL flag to be used for that kind verification
|
|
// checking.
|
|
OCMStub([self.mockRequestSender sendClearcutHttpRequestForLogs:[OCMArg any]
|
|
withCompletion:[OCMArg any]])
|
|
.andDo(^(NSInvocation *invocation) {
|
|
sendingAttempted = YES;
|
|
});
|
|
[uploader addNewLogRecord:newRecord];
|
|
|
|
// We wait for 2 seconds and we expect nothing should happen to self.mockRequestSender right after
|
|
// 2 seconds: the upload will eventually be attempted in after 10 seconds based on the setup
|
|
// in this unit test.
|
|
double delayInSeconds = 2.0;
|
|
dispatch_time_t popTime =
|
|
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
|
|
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
|
[expectation fulfill];
|
|
});
|
|
|
|
// We expect expectation to be fulfilled right away since the upload can be carried out without
|
|
// delay.
|
|
[self waitForExpectationsWithTimeout:10.0 handler:nil];
|
|
XCTAssertFalse(sendingAttempted);
|
|
}
|
|
|
|
- (void)testUploadBatchSizeIsBasedOnStrategySetting {
|
|
int batchSendSize = 5;
|
|
|
|
// using a strategy with batch send size as 5
|
|
FIRIAMClearcutStrategy *strategy =
|
|
[[FIRIAMClearcutStrategy alloc] initWithMinWaitTimeInMills:1000
|
|
maxWaitTimeInMills:2000
|
|
failureBackoffTimeInMills:1000
|
|
batchSendSize:batchSendSize];
|
|
|
|
// Next upload is now.
|
|
NSTimeInterval currentMoment = 10000;
|
|
OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(currentMoment);
|
|
|
|
FIRIAMClearcutUploader *uploader =
|
|
[[FIRIAMClearcutUploader alloc] initWithRequestSender:self.mockRequestSender
|
|
timeFetcher:self.mockTimeFetcher
|
|
logStorage:self.mockLogStorage
|
|
usingStrategy:strategy
|
|
usingUserDefaults:self.mockUserDefaults];
|
|
|
|
uploader.nextValidSendTimeInMills = (int64_t)currentMoment * 1000;
|
|
|
|
XCTestExpectation *expectation = [self expectationWithDescription:@"Triggers send on sender"];
|
|
OCMExpect([self.mockLogStorage popStillValidRecordsForUpTo:batchSendSize]);
|
|
|
|
FIRIAMClearcutLogRecord *newRecord =
|
|
[[FIRIAMClearcutLogRecord alloc] initWithExtensionJsonString:@"string"
|
|
eventTimestampInSeconds:currentMoment];
|
|
[uploader addNewLogRecord:newRecord];
|
|
|
|
// we wait for 2 seconds to ensure that the next send is attempted and then verify its
|
|
// interacton with the underlying storage
|
|
double delayInSeconds = 2.0;
|
|
dispatch_time_t popTime =
|
|
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
|
|
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
|
[expectation fulfill];
|
|
});
|
|
|
|
// we expect expectation to be fulfilled right away since the upload can be carried out without
|
|
// delay
|
|
[self waitForExpectationsWithTimeout:10.0 handler:nil];
|
|
OCMVerifyAll((id)self.mockLogStorage);
|
|
}
|
|
|
|
- (void)testRespectingWaitTimeFromRequestSender {
|
|
// The next upload is now.
|
|
NSTimeInterval currentMoment = 10000;
|
|
OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(currentMoment);
|
|
|
|
// using a real storage in this case
|
|
FIRIAMClearcutLogStorage *logStorage =
|
|
[[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:1000
|
|
withTimeFetcher:self.mockTimeFetcher
|
|
cachePath:self.cachePath];
|
|
|
|
FIRIAMClearcutUploader *uploader =
|
|
[[FIRIAMClearcutUploader alloc] initWithRequestSender:self.mockRequestSender
|
|
timeFetcher:self.mockTimeFetcher
|
|
logStorage:logStorage
|
|
usingStrategy:self.defaultStrategy
|
|
usingUserDefaults:self.mockUserDefaults];
|
|
|
|
uploader.nextValidSendTimeInMills = (int64_t)currentMoment * 1000;
|
|
|
|
XCTestExpectation *expectation = [self expectationWithDescription:@"Triggers send on sender"];
|
|
|
|
// notice that waitTime is between minWaitTimeInMills and maxWaitTimeInMills in the default
|
|
// strategy
|
|
NSNumber *waitTime = [NSNumber numberWithLongLong:1500];
|
|
// set up request sender which triggers the callback with a wait time interval to be 1000
|
|
// milliseconds
|
|
OCMStub(
|
|
[self.mockRequestSender
|
|
sendClearcutHttpRequestForLogs:[OCMArg any]
|
|
withCompletion:([OCMArg invokeBlockWithArgs:@YES, @NO, waitTime, nil])])
|
|
.andDo(^(NSInvocation *invocation) {
|
|
[expectation fulfill];
|
|
});
|
|
|
|
FIRIAMClearcutLogRecord *newRecord =
|
|
[[FIRIAMClearcutLogRecord alloc] initWithExtensionJsonString:@"string"
|
|
eventTimestampInSeconds:currentMoment];
|
|
[uploader addNewLogRecord:newRecord];
|
|
|
|
[self waitForExpectationsWithTimeout:10.0 handler:nil];
|
|
// verify the update to nextValidSendTimeInMills is expected
|
|
XCTAssertEqual(currentMoment * 1000 + 1500, uploader.nextValidSendTimeInMills);
|
|
}
|
|
|
|
- (void)disable_testWaitTimeFromRequestSenderAdjustedByMinWaitTimeInStrategy {
|
|
// The next upload is now.
|
|
NSTimeInterval currentMoment = 10000;
|
|
OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(currentMoment);
|
|
|
|
// using a real storage in this case
|
|
FIRIAMClearcutLogStorage *logStorage =
|
|
[[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:1000
|
|
withTimeFetcher:self.mockTimeFetcher
|
|
cachePath:self.cachePath];
|
|
|
|
FIRIAMClearcutUploader *uploader =
|
|
[[FIRIAMClearcutUploader alloc] initWithRequestSender:self.mockRequestSender
|
|
timeFetcher:self.mockTimeFetcher
|
|
logStorage:logStorage
|
|
usingStrategy:self.defaultStrategy
|
|
usingUserDefaults:self.mockUserDefaults];
|
|
|
|
uploader.nextValidSendTimeInMills = (int64_t)currentMoment * 1000;
|
|
|
|
XCTestExpectation *expectation = [self expectationWithDescription:@"Triggers send on sender"];
|
|
|
|
// notice that waitTime is below minWaitTimeInMills in the default strategy
|
|
NSNumber *waitTime =
|
|
[NSNumber numberWithLongLong:self.defaultStrategy.minimalWaitTimeInMills - 200];
|
|
// set up request sender which triggers the callback with a wait time interval to be 1000
|
|
// milliseconds
|
|
OCMStub(
|
|
[self.mockRequestSender
|
|
sendClearcutHttpRequestForLogs:[OCMArg any]
|
|
withCompletion:([OCMArg invokeBlockWithArgs:@YES, @NO, waitTime, nil])])
|
|
.andDo(^(NSInvocation *invocation) {
|
|
[expectation fulfill];
|
|
});
|
|
|
|
FIRIAMClearcutLogRecord *newRecord =
|
|
[[FIRIAMClearcutLogRecord alloc] initWithExtensionJsonString:@"string"
|
|
eventTimestampInSeconds:currentMoment];
|
|
[uploader addNewLogRecord:newRecord];
|
|
|
|
[self waitForExpectationsWithTimeout:10.0 handler:nil];
|
|
// verify the update to nextValidSendTimeInMills is expected
|
|
XCTAssertEqual(currentMoment * 1000 + self.defaultStrategy.minimalWaitTimeInMills,
|
|
uploader.nextValidSendTimeInMills);
|
|
}
|
|
|
|
- (void)testWaitTimeFromRequestSenderAdjustedByMaxWaitTimeInStrategy {
|
|
// The next upload is now.
|
|
NSTimeInterval currentMoment = 10000;
|
|
OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(currentMoment);
|
|
|
|
// using a real storage in this case
|
|
FIRIAMClearcutLogStorage *logStorage =
|
|
[[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:1000
|
|
withTimeFetcher:self.mockTimeFetcher
|
|
cachePath:self.cachePath];
|
|
|
|
FIRIAMClearcutUploader *uploader =
|
|
[[FIRIAMClearcutUploader alloc] initWithRequestSender:self.mockRequestSender
|
|
timeFetcher:self.mockTimeFetcher
|
|
logStorage:logStorage
|
|
usingStrategy:self.defaultStrategy
|
|
usingUserDefaults:self.mockUserDefaults];
|
|
|
|
uploader.nextValidSendTimeInMills = (int64_t)currentMoment * 1000;
|
|
|
|
XCTestExpectation *expectation = [self expectationWithDescription:@"Triggers send on sender"];
|
|
|
|
// notice that waitTime is larger than maximumWaitTimeInMills in the default strategy
|
|
NSNumber *waitTime =
|
|
[NSNumber numberWithLongLong:self.defaultStrategy.maximumWaitTimeInMills + 200];
|
|
// set up request sender which triggers the callback with a wait time interval to be 1000
|
|
// milliseconds
|
|
OCMStub(
|
|
[self.mockRequestSender
|
|
sendClearcutHttpRequestForLogs:[OCMArg any]
|
|
withCompletion:([OCMArg invokeBlockWithArgs:@YES, @NO, waitTime, nil])])
|
|
.andDo(^(NSInvocation *invocation) {
|
|
[expectation fulfill];
|
|
});
|
|
|
|
FIRIAMClearcutLogRecord *newRecord =
|
|
[[FIRIAMClearcutLogRecord alloc] initWithExtensionJsonString:@"string"
|
|
eventTimestampInSeconds:currentMoment];
|
|
[uploader addNewLogRecord:newRecord];
|
|
|
|
[self waitForExpectationsWithTimeout:10.0 handler:nil];
|
|
// verify the update to nextValidSendTimeInMills is expected
|
|
XCTAssertEqual(currentMoment * 1000 + self.defaultStrategy.maximumWaitTimeInMills,
|
|
uploader.nextValidSendTimeInMills);
|
|
}
|
|
|
|
- (void)testRepushLogsIfRequestSenderSaysSo {
|
|
// The next upload is now.
|
|
NSTimeInterval currentMoment = 10000;
|
|
OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(currentMoment);
|
|
|
|
// using a real storage in this case
|
|
FIRIAMClearcutLogStorage *logStorage =
|
|
[[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:1000
|
|
withTimeFetcher:self.mockTimeFetcher
|
|
cachePath:self.cachePath];
|
|
|
|
FIRIAMClearcutUploader *uploader =
|
|
[[FIRIAMClearcutUploader alloc] initWithRequestSender:self.mockRequestSender
|
|
timeFetcher:self.mockTimeFetcher
|
|
logStorage:logStorage
|
|
usingStrategy:self.defaultStrategy
|
|
usingUserDefaults:self.mockUserDefaults];
|
|
|
|
uploader.nextValidSendTimeInMills = (int64_t)currentMoment * 1000;
|
|
|
|
XCTestExpectation *expectation = [self expectationWithDescription:@"Triggers send on sender"];
|
|
|
|
// notice that waitTime is larger than maximumWaitTimeInMills in the default strategy
|
|
NSNumber *waitTime =
|
|
[NSNumber numberWithLongLong:self.defaultStrategy.maximumWaitTimeInMills + 200];
|
|
|
|
// Notice that it's invoking completion with falure flag and a flag to re-push those logs
|
|
OCMStub(
|
|
[self.mockRequestSender
|
|
sendClearcutHttpRequestForLogs:[OCMArg any]
|
|
withCompletion:([OCMArg invokeBlockWithArgs:@NO, @YES, waitTime, nil])])
|
|
.andDo(^(NSInvocation *invocation) {
|
|
[expectation fulfill];
|
|
});
|
|
|
|
FIRIAMClearcutLogRecord *newRecord =
|
|
[[FIRIAMClearcutLogRecord alloc] initWithExtensionJsonString:@"string"
|
|
eventTimestampInSeconds:currentMoment];
|
|
[uploader addNewLogRecord:newRecord];
|
|
|
|
[self waitForExpectationsWithTimeout:10.0 handler:nil];
|
|
|
|
// we should still be able to fetch one log record from storage since it's re-pushed due
|
|
// to send failure
|
|
XCTAssertEqual([logStorage popStillValidRecordsForUpTo:10].count, 1);
|
|
}
|
|
@end
|