FIRCLSExistingReportManagerTests.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import <XCTest/XCTest.h>
  15. #include "Crashlytics/Crashlytics/Components/FIRCLSContext.h"
  16. #import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
  17. #import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
  18. #import "Crashlytics/Crashlytics/Private/FIRCLSExistingReportManager_Private.h"
  19. #import "Crashlytics/Crashlytics/Public/FirebaseCrashlytics/FIRCrashlyticsReport.h"
  20. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockReportUploader.h"
  21. #import "Crashlytics/UnitTests/Mocks/FIRCLSTempMockFileManager.h"
  22. #define METADATA_FORMAT \
  23. (@"{\"identity\":{\"generator\":\"Crashlytics iOS " \
  24. @"SDK/" \
  25. @"7.7.0\",\"display_version\":\"7.7.0\",\"build_version\":\"7.7.0\",\"started_at\":%ld," \
  26. @"\"session_id\":\"%@\",\"install_id\":\"CA3EE845-594A-4AAD-8343-B0379559E5C5\",\"beta_" \
  27. @"token\":\"\",\"absolute_log_timestamps\":true}}\n{\"host\":{\"model\":\"iOS Simulator " \
  28. @"(iPhone)\",\"machine\":\"x86_64\",\"cpu\":\"Intel(R) Core(TM) i9-9880H CPU @ " \
  29. @"2.30GHz\",\"os_build_version\":\"20D74\",\"os_display_version\":\"14.3.0\",\"platform\":" \
  30. @"\"ios\",\"locale\":\"en_US\"}}\n{\"application\":{\"bundle_id\":\"com.google.firebase." \
  31. @"quickstart.TestingNoAWS\",\"custom_bundle_id\":null,\"build_version\":\"131\",\"display_" \
  32. @"version\":\"10.10.33\",\"extension_id\":null}}\n{\"executable\":{\"architecture\":\"x86_" \
  33. @"64\",\"uuid\":\"6a082b52b92a36bdb766fda9049deb21\",\"base\":4471803904,\"size\":49152," \
  34. @"\"encrypted\":false,\"minimum_sdk_version\":\"8.0.0\",\"built_sdk_version\":\"14.3.0\"}}")
  35. @interface FIRCLSExistingReportManagerTests : XCTestCase
  36. @property(nonatomic, strong) FIRCLSMockReportUploader *mockReportUploader;
  37. @property(nonatomic, strong) FIRCLSTempMockFileManager *fileManager;
  38. @property(nonatomic, strong) FIRCLSExistingReportManager *existingReportManager;
  39. @property(nonatomic, strong) FIRCLSManagerData *managerData;
  40. @end
  41. @implementation FIRCLSExistingReportManagerTests
  42. - (void)setUp {
  43. [super setUp];
  44. FIRCLSContextBaseInit();
  45. self.fileManager = [[FIRCLSTempMockFileManager alloc] init];
  46. // Allow nil values only in tests
  47. #pragma clang diagnostic push
  48. #pragma clang diagnostic ignored "-Wnonnull"
  49. self.managerData = [[FIRCLSManagerData alloc] initWithGoogleAppID:@"TEST_GOOGLE_APP_ID"
  50. googleTransport:nil
  51. installations:nil
  52. analytics:nil
  53. fileManager:self.fileManager
  54. dataArbiter:nil
  55. settings:nil
  56. onDemandModel:nil];
  57. #pragma clang diagnostic pop
  58. self.mockReportUploader = [[FIRCLSMockReportUploader alloc] initWithManagerData:self.managerData];
  59. self.existingReportManager =
  60. [[FIRCLSExistingReportManager alloc] initWithManagerData:self.managerData
  61. reportUploader:self.mockReportUploader];
  62. }
  63. - (void)tearDown {
  64. if ([[NSFileManager defaultManager] fileExistsAtPath:[self.fileManager rootPath]]) {
  65. assert([self.fileManager removeItemAtPath:[self.fileManager rootPath]]);
  66. }
  67. FIRCLSContextBaseDeinit();
  68. [super tearDown];
  69. }
  70. - (NSArray *)contentsOfActivePath {
  71. return [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.fileManager.activePath
  72. error:nil];
  73. }
  74. #pragma mark - Helpers
  75. - (FIRCLSInternalReport *)createActiveReportWithID:(NSString *)reportID
  76. time:(time_t)time
  77. withEvents:(BOOL)withEvents {
  78. NSString *reportPath = [self.fileManager.activePath stringByAppendingPathComponent:reportID];
  79. FIRCLSInternalReport *report = [[FIRCLSInternalReport alloc] initWithPath:reportPath
  80. executionIdentifier:reportID];
  81. if (![self.fileManager createDirectoryAtPath:report.path]) {
  82. return nil;
  83. }
  84. NSString *metadata = [NSString stringWithFormat:METADATA_FORMAT, time, reportID];
  85. if (![self createMetadata:metadata forReport:report]) {
  86. return nil;
  87. }
  88. if (withEvents) {
  89. [self createCrashExceptionFile:@"Content doesn't matter" forReport:report];
  90. }
  91. return report;
  92. }
  93. - (BOOL)createFileWithContents:(NSString *)contents atPath:(NSString *)path {
  94. return [self.fileManager.underlyingFileManager
  95. createFileAtPath:path
  96. contents:[contents dataUsingEncoding:NSUTF8StringEncoding]
  97. attributes:nil];
  98. }
  99. - (BOOL)createMetadata:(NSString *)value forReport:(FIRCLSInternalReport *)report {
  100. return [self createFileWithContents:value atPath:[report metadataPath]];
  101. }
  102. - (BOOL)createCrashExceptionFile:(NSString *)value forReport:(FIRCLSInternalReport *)report {
  103. return [self createFileWithContents:value
  104. atPath:[report pathForContentFile:FIRCLSReportExceptionFile]];
  105. }
  106. - (BOOL)reportPathAtIndex:(NSUInteger)index isReportID:(NSString *)reportID {
  107. return [[self.existingReportManager.existingUnemptyActiveReportPaths objectAtIndex:index]
  108. containsString:reportID];
  109. }
  110. #pragma mark - Tests
  111. - (void)testNoReports {
  112. [self.existingReportManager collectExistingReports];
  113. [self.existingReportManager.operationQueue waitUntilAllOperationsAreFinished];
  114. // Reports without events should be deleted
  115. XCTAssertEqual([[self contentsOfActivePath] count], 0);
  116. XCTAssertEqual(self.existingReportManager.unsentReportsCount, 0);
  117. XCTAssertEqual(self.existingReportManager.newestUnsentReport, nil);
  118. XCTAssertEqual(self.existingReportManager.existingUnemptyActiveReportPaths.count, 0);
  119. }
  120. - (void)testReportNoEvents {
  121. [self createActiveReportWithID:@"report_A" time:12312 withEvents:NO];
  122. [self createActiveReportWithID:@"report_B" time:12315 withEvents:NO];
  123. [self.existingReportManager collectExistingReports];
  124. [self.existingReportManager.operationQueue waitUntilAllOperationsAreFinished];
  125. // Reports without events should be deleted
  126. XCTAssertEqual([[self contentsOfActivePath] count], 0);
  127. XCTAssertEqual(self.existingReportManager.unsentReportsCount, 0);
  128. XCTAssertEqual(self.existingReportManager.newestUnsentReport, nil);
  129. XCTAssertEqual(self.existingReportManager.existingUnemptyActiveReportPaths.count, 0);
  130. }
  131. - (void)testUnsentReportsUnderLimit {
  132. [self createActiveReportWithID:@"report_A" time:12312 withEvents:YES];
  133. [self createActiveReportWithID:@"report_B" time:12315 withEvents:YES];
  134. [self createActiveReportWithID:@"report_C" time:31533 withEvents:YES];
  135. [self createActiveReportWithID:@"report_D" time:63263 withEvents:YES];
  136. [self.existingReportManager collectExistingReports];
  137. [self.existingReportManager.operationQueue waitUntilAllOperationsAreFinished];
  138. // Reports with events should be kept if there's less than MAX_UNSENT_REPORTS reports
  139. XCTAssertEqual([[self contentsOfActivePath] count], FIRCLSMaxUnsentReports);
  140. XCTAssertEqual(self.existingReportManager.unsentReportsCount, FIRCLSMaxUnsentReports);
  141. XCTAssertEqual(self.existingReportManager.existingUnemptyActiveReportPaths.count,
  142. FIRCLSMaxUnsentReports);
  143. // Newest report based on started_at timestamp
  144. XCTAssertEqualObjects(self.existingReportManager.newestUnsentReport.reportID, @"report_D");
  145. }
  146. /**
  147. * When we go over the limit of FIRCLSMaxUnsentReports, we delete any reports over the limit to
  148. * ensure performant startup and prevent disk space from filling up. Delete starting with the oldest
  149. * first.
  150. */
  151. - (void)testUnsentReportsOverLimit {
  152. // Create a bunch of reports starting at different times
  153. [self createActiveReportWithID:@"report_A" time:12312 withEvents:YES];
  154. [self createActiveReportWithID:@"report_B" time:12315 withEvents:YES];
  155. [self createActiveReportWithID:@"report_C" time:31533 withEvents:YES];
  156. [self createActiveReportWithID:@"report_D" time:63263 withEvents:YES];
  157. [self createActiveReportWithID:@"report_E" time:33263 withEvents:YES];
  158. [self createActiveReportWithID:@"report_F" time:43263 withEvents:YES];
  159. [self createActiveReportWithID:@"report_G" time:77777 withEvents:YES];
  160. [self createActiveReportWithID:@"report_H" time:13263 withEvents:YES];
  161. [self createActiveReportWithID:@"report_I" time:61263 withEvents:YES];
  162. [self.existingReportManager collectExistingReports];
  163. [self.existingReportManager.operationQueue waitUntilAllOperationsAreFinished];
  164. // Remove any reports over the limit, starting with the oldest
  165. XCTAssertEqual([[self contentsOfActivePath] count], FIRCLSMaxUnsentReports);
  166. XCTAssertEqual(self.existingReportManager.unsentReportsCount, FIRCLSMaxUnsentReports);
  167. XCTAssertEqual(self.existingReportManager.existingUnemptyActiveReportPaths.count,
  168. FIRCLSMaxUnsentReports);
  169. // Newest report based on started_at timestamp
  170. XCTAssertEqualObjects(self.existingReportManager.newestUnsentReport.reportID, @"report_G");
  171. // Make sure we're sorting correctly and keeping the newest reports.
  172. XCTAssertEqual([self reportPathAtIndex:0 isReportID:@"report_G"], true);
  173. XCTAssertEqual([self reportPathAtIndex:1 isReportID:@"report_D"], true);
  174. XCTAssertEqual([self reportPathAtIndex:2 isReportID:@"report_I"], true);
  175. XCTAssertEqual([self reportPathAtIndex:3 isReportID:@"report_F"], true);
  176. }
  177. @end