Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 15 additions & 21 deletions Crashlytics/UnitTests/FIRCLSSettingsTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -127,27 +127,33 @@
- (void)cacheSettingsWithGoogleAppID:(NSString *)googleAppID
currentTimestamp:(NSTimeInterval)currentTimestamp
expectedRemoveCount:(NSInteger)expectedRemoveCount {
self.fileManager.removeExpectation = [[XCTestExpectation alloc]
initWithDescription:@"FIRCLSMockFileManager.removeExpectation.cache"];
self.fileManager.removeCount = 0;
self.fileManager.expectedRemoveCount = expectedRemoveCount;

XCTestExpectation *expectation =
[self expectationForNotification:FIRCLSMockFileManagerDidRemoveItemNotification
object:nil
handler:nil];
expectation.expectedFulfillmentCount = expectedRemoveCount;

[self.settings cacheSettingsWithGoogleAppID:googleAppID currentTimestamp:currentTimestamp];

[self waitForExpectations:@[ self.fileManager.removeExpectation ] timeout:1];
[self waitForExpectations:@[ expectation ] timeout:5.0];

Check failure on line 140 in Crashlytics/UnitTests/FIRCLSSettingsTests.m

View workflow job for this annotation

GitHub Actions / spm / spm (macos-26, Xcode_26.1, iOS)

testCorruptCache, Asynchronous wait failed: Exceeded timeout of 5 seconds, with unfulfilled expectations: "Expect notification 'FIRCLSMockFileManagerDidRemoveItemNotification' from any object".
}

- (void)reloadFromCacheWithGoogleAppID:(NSString *)googleAppID
currentTimestamp:(NSTimeInterval)currentTimestamp
expectedRemoveCount:(NSInteger)expectedRemoveCount {
self.fileManager.removeExpectation = [[XCTestExpectation alloc]
initWithDescription:@"FIRCLSMockFileManager.removeExpectation.reload"];
self.fileManager.removeCount = 0;
self.fileManager.expectedRemoveCount = expectedRemoveCount;

XCTestExpectation *expectation =
[self expectationForNotification:FIRCLSMockFileManagerDidRemoveItemNotification
object:nil
handler:nil];
expectation.expectedFulfillmentCount = expectedRemoveCount;

[self.settings reloadFromCacheWithGoogleAppID:googleAppID currentTimestamp:currentTimestamp];

[self waitForExpectations:@[ self.fileManager.removeExpectation ] timeout:5.0];
[self waitForExpectations:@[ expectation ] timeout:10.0];

Check failure on line 156 in Crashlytics/UnitTests/FIRCLSSettingsTests.m

View workflow job for this annotation

GitHub Actions / spm / spm (macos-26, Xcode_26.1, iOS)

testCorruptCacheKey, Asynchronous wait failed: Exceeded timeout of 10 seconds, with unfulfilled expectations: "Expect notification 'FIRCLSMockFileManagerDidRemoveItemNotification' from any object".

Check failure on line 156 in Crashlytics/UnitTests/FIRCLSSettingsTests.m

View workflow job for this annotation

GitHub Actions / spm / spm (macos-26, Xcode_26.1, iOS)

testActivatedSettingsMissingCacheKey, Asynchronous wait failed: Exceeded timeout of 10 seconds, with unfulfilled expectations: "Expect notification 'FIRCLSMockFileManagerDidRemoveItemNotification' from any object".

Check failure on line 156 in Crashlytics/UnitTests/FIRCLSSettingsTests.m

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, tvOS)

testActivatedSettingsMissingCacheKey, Asynchronous wait failed: Exceeded timeout of 10 seconds, with unfulfilled expectations: "Expect notification 'FIRCLSMockFileManagerDidRemoveItemNotification' from any object".

Check failure on line 156 in Crashlytics/UnitTests/FIRCLSSettingsTests.m

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

testCorruptCacheKey, Asynchronous wait failed: Exceeded timeout of 10 seconds, with unfulfilled expectations: "Expect notification 'FIRCLSMockFileManagerDidRemoveItemNotification' from any object".

Check failure on line 156 in Crashlytics/UnitTests/FIRCLSSettingsTests.m

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

testActivatedSettingsMissingCacheKey, Asynchronous wait failed: Exceeded timeout of 10 seconds, with unfulfilled expectations: "Expect notification 'FIRCLSMockFileManagerDidRemoveItemNotification' from any object".
}

- (void)testActivatedSettingsCached {
Expand Down Expand Up @@ -205,10 +211,6 @@
[self writeSettings:FIRCLSTestSettingsActivated error:&error];
XCTAssertNil(error, "%@", error);

// 1 delete for clearing the cache key, plus 2 for the deletes from reloading and clearing the
// cache and cache key
self.fileManager.expectedRemoveCount = 3;

NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
[self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];

Expand Down Expand Up @@ -238,10 +240,6 @@
[self writeSettings:FIRCLSTestSettingsActivated error:&error];
XCTAssertNil(error, "%@", error);

// 1 delete for clearing the cache key, plus 2 for the deletes from reloading and clearing the
// cache and cache key
self.fileManager.expectedRemoveCount = 3;

NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
[self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];

Expand Down Expand Up @@ -272,10 +270,6 @@
[self writeSettings:FIRCLSTestSettingsActivated error:&error];
XCTAssertNil(error, "%@", error);

// 1 delete for clearing the cache key, plus 2 for the deletes from reloading and clearing the
// cache and cache key
self.fileManager.expectedRemoveCount = 3;

NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
[self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];

Expand Down Expand Up @@ -387,7 +381,7 @@
// Cache them, and reload. Since it's corrupted we should delete it all
[self cacheSettingsWithGoogleAppID:TestGoogleAppID
currentTimestamp:currentTimestamp
expectedRemoveCount:2];
expectedRemoveCount:3];

// Should have default values because we deleted the cache and settingsDictionary
XCTAssertEqual(self.settings.isCacheExpired, YES);
Expand Down
1 change: 1 addition & 0 deletions Crashlytics/UnitTests/FIRCLSUserDefaultsTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ - (void)setUp {

- (void)tearDown {
[_userDefaults removeAllObjects];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:_testKey1];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

Adding cleanup for NSUserDefaults in tearDown is a great practice for improving test isolation. However, this change appears to be incomplete. Other tests, such as testMigrateFromNSUserDefaults, also set _testKey2 and _testKey3 on NSUserDefaults. To ensure comprehensive cleanup and prevent any state from leaking between tests (especially in cases where a test might fail before completing), it would be best to remove all keys that are set during testing.

  [[NSUserDefaults standardUserDefaults] removeObjectForKey:_testKey1];
  [[NSUserDefaults standardUserDefaults] removeObjectForKey:_testKey2];
  [[NSUserDefaults standardUserDefaults] removeObjectForKey:_testKey3];

[super tearDown];
}

Expand Down
12 changes: 3 additions & 9 deletions Crashlytics/UnitTests/Mocks/FIRCLSMockFileManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,12 @@

#import <XCTest/XCTest.h>

@interface FIRCLSMockFileManager : FIRCLSFileManager
// Notification posted when an item is removed via `removeItemAtPath`.
extern NSNotificationName const FIRCLSMockFileManagerDidRemoveItemNotification;

// Number of calls to removeItemAtPath are expected for the unit test
@property(nonatomic) NSInteger expectedRemoveCount;
@interface FIRCLSMockFileManager : FIRCLSFileManager

// Incremented when a remove happens with removeItemAtPath
@property(nonatomic) NSInteger removeCount;

// Will be fulfilled when the expected number of removes have happened
// using removeItemAtPath
//
// Users should initialize this in their test.
@property(nonatomic, strong) XCTestExpectation *removeExpectation;

@end
84 changes: 48 additions & 36 deletions Crashlytics/UnitTests/Mocks/FIRCLSMockFileManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

#import "Crashlytics/UnitTests/Mocks/FIRCLSMockFileManager.h"

NSNotificationName const FIRCLSMockFileManagerDidRemoveItemNotification =
@"FIRCLSMockFileManagerDidRemoveItemNotification";

@interface FIRCLSMockFileManager ()

@property(nonatomic) NSMutableDictionary<NSString *, NSData *> *fileSystemDict;
Expand All @@ -34,68 +37,77 @@ - (instancetype)init {
}

- (BOOL)removeItemAtPath:(NSString *)path {
[self.fileSystemDict removeObjectForKey:path];
@synchronized(self) {
[self.fileSystemDict removeObjectForKey:path];
}

self.removeCount += 1;

// If we set up the expectation, and we went over the expected count or removes, fulfill the
// expectation
if (self.removeExpectation && self.removeCount >= self.expectedRemoveCount) {
[self.removeExpectation fulfill];
}
[[NSNotificationCenter defaultCenter]
postNotificationName:FIRCLSMockFileManagerDidRemoveItemNotification
object:self];

return YES;
}

- (BOOL)fileExistsAtPath:(NSString *)path {
return self.fileSystemDict[path] != nil;
@synchronized(self) {
return self.fileSystemDict[path] != nil;
}
}

- (BOOL)createFileAtPath:(NSString *)path
contents:(NSData *)data
attributes:(NSDictionary<NSFileAttributeKey, id> *)attr {
self.fileSystemDict[path] = data;
@synchronized(self) {
self.fileSystemDict[path] = data;
}
return YES;
}

- (NSArray *)activePathContents {
NSMutableArray *pathsWithActive = [[NSMutableArray alloc] init];
for (NSString *path in [_fileSystemDict allKeys]) {
if ([path containsString:@"v5/reports/active"]) {
[pathsWithActive addObject:path];
@synchronized(self) {
NSMutableArray *pathsWithActive = [[NSMutableArray alloc] init];
for (NSString *path in [_fileSystemDict allKeys]) {
if ([path containsString:@"v5/reports/active"]) {
[pathsWithActive addObject:path];
}
}
return pathsWithActive;
}

return pathsWithActive;
}

- (NSData *)dataWithContentsOfFile:(NSString *)path {
return self.fileSystemDict[path];
@synchronized(self) {
return self.fileSystemDict[path];
}
}

- (void)enumerateFilesInDirectory:(NSString *)directory
usingBlock:(void (^)(NSString *filePath, NSString *extension))block {
NSArray<NSString *> *filteredPaths = [self.fileSystemDict.allKeys
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *path,
NSDictionary *bindings) {
return [path hasPrefix:directory];
}]];

for (NSString *path in filteredPaths) {
NSString *extension;
NSString *fullPath;

// Skip files that start with a dot. This is important, because if you try to move a .DS_Store
// file, it will fail if the target directory also has a .DS_Store file in it. Plus, its
// wasteful, because we don't care about dot files.
if ([path hasPrefix:@"."]) {
continue;
}

extension = [path pathExtension];
fullPath = [directory stringByAppendingPathComponent:path];
if (block) {
block(fullPath, extension);
@synchronized(self) {
NSArray<NSString *> *filteredPaths = [self.fileSystemDict.allKeys
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *path,
NSDictionary *bindings) {
return [path hasPrefix:directory];
}]];

for (NSString *path in filteredPaths) {
NSString *extension;
NSString *fullPath;

// Skip files that start with a dot. This is important, because if you try to move a
// .DS_Store file, it will fail if the target directory also has a .DS_Store file in
// it. Plus, its wasteful, because we don't care about dot files.
if ([path hasPrefix:@"."]) {
continue;
}

extension = [path pathExtension];
fullPath = [directory stringByAppendingPathComponent:path];
if (block) {
block(fullPath, extension);
}
}
}
}
Expand Down
Loading