FIRCLSExistingReportManagerTests.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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. // Cleanup potential artifacts from other test files.
  47. if ([[NSFileManager defaultManager] fileExistsAtPath:[self.fileManager rootPath]]) {
  48. assert([self.fileManager removeItemAtPath:[self.fileManager rootPath]]);
  49. }
  50. // Allow nil values only in tests
  51. #pragma clang diagnostic push
  52. #pragma clang diagnostic ignored "-Wnonnull"
  53. self.managerData = [[FIRCLSManagerData alloc] initWithGoogleAppID:@"TEST_GOOGLE_APP_ID"
  54. googleTransport:nil
  55. installations:nil
  56. analytics:nil
  57. fileManager:self.fileManager
  58. dataArbiter:nil
  59. settings:nil
  60. onDemandModel:nil];
  61. #pragma clang diagnostic pop
  62. self.mockReportUploader = [[FIRCLSMockReportUploader alloc] initWithManagerData:self.managerData];
  63. self.existingReportManager =
  64. [[FIRCLSExistingReportManager alloc] initWithManagerData:self.managerData
  65. reportUploader:self.mockReportUploader];
  66. }
  67. - (void)tearDown {
  68. if ([[NSFileManager defaultManager] fileExistsAtPath:[self.fileManager rootPath]]) {
  69. assert([self.fileManager removeItemAtPath:[self.fileManager rootPath]]);
  70. }
  71. FIRCLSContextBaseDeinit();
  72. [super tearDown];
  73. }
  74. - (NSArray *)contentsOfActivePath {
  75. return [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.fileManager.activePath
  76. error:nil];
  77. }
  78. #pragma mark - Helpers
  79. - (FIRCLSInternalReport *)createActiveReportWithID:(NSString *)reportID
  80. time:(time_t)time
  81. withEvents:(BOOL)withEvents {
  82. NSString *reportPath = [self.fileManager.activePath stringByAppendingPathComponent:reportID];
  83. FIRCLSInternalReport *report = [[FIRCLSInternalReport alloc] initWithPath:reportPath
  84. executionIdentifier:reportID];
  85. if (![self.fileManager createDirectoryAtPath:report.path]) {
  86. return nil;
  87. }
  88. NSString *metadata = [NSString stringWithFormat:METADATA_FORMAT, time, reportID];
  89. if (![self createMetadata:metadata forReport:report]) {
  90. return nil;
  91. }
  92. if (withEvents) {
  93. [self createCrashExceptionFile:@"Content doesn't matter" forReport:report];
  94. }
  95. return report;
  96. }
  97. - (BOOL)createFileWithContents:(NSString *)contents atPath:(NSString *)path {
  98. return [self.fileManager.underlyingFileManager
  99. createFileAtPath:path
  100. contents:[contents dataUsingEncoding:NSUTF8StringEncoding]
  101. attributes:nil];
  102. }
  103. - (BOOL)createMetadata:(NSString *)value forReport:(FIRCLSInternalReport *)report {
  104. return [self createFileWithContents:value atPath:[report metadataPath]];
  105. }
  106. - (BOOL)createCrashExceptionFile:(NSString *)value forReport:(FIRCLSInternalReport *)report {
  107. return [self createFileWithContents:value
  108. atPath:[report pathForContentFile:FIRCLSReportExceptionFile]];
  109. }
  110. - (BOOL)reportPathAtIndex:(NSUInteger)index isReportID:(NSString *)reportID {
  111. return [[self.existingReportManager.existingUnemptyActiveReportPaths objectAtIndex:index]
  112. containsString:reportID];
  113. }
  114. #pragma mark - Tests
  115. - (void)testNoReports {
  116. [self.existingReportManager collectExistingReports];
  117. [self.existingReportManager.operationQueue waitUntilAllOperationsAreFinished];
  118. // Reports without events should be deleted
  119. XCTAssertEqual([[self contentsOfActivePath] count], 0, @"Contents of active path: %@",
  120. [self contentsOfActivePath]);
  121. XCTAssertEqual(self.existingReportManager.unsentReportsCount, 0);
  122. XCTAssertEqual(self.existingReportManager.newestUnsentReport, nil);
  123. XCTAssertEqual(self.existingReportManager.existingUnemptyActiveReportPaths.count, 0);
  124. }
  125. - (void)testReportNoEvents {
  126. [self createActiveReportWithID:@"report_A" time:12312 withEvents:NO];
  127. [self createActiveReportWithID:@"report_B" time:12315 withEvents:NO];
  128. [self.existingReportManager collectExistingReports];
  129. [self.existingReportManager.operationQueue waitUntilAllOperationsAreFinished];
  130. // Reports without events should be deleted
  131. XCTAssertEqual([[self contentsOfActivePath] count], 0, @"Contents of active path: %@",
  132. [self contentsOfActivePath]);
  133. XCTAssertEqual(self.existingReportManager.unsentReportsCount, 0);
  134. XCTAssertEqual(self.existingReportManager.newestUnsentReport, nil);
  135. XCTAssertEqual(self.existingReportManager.existingUnemptyActiveReportPaths.count, 0);
  136. }
  137. - (void)testUnsentReportsUnderLimit {
  138. [self createActiveReportWithID:@"report_A" time:12312 withEvents:YES];
  139. [self createActiveReportWithID:@"report_B" time:12315 withEvents:YES];
  140. [self createActiveReportWithID:@"report_C" time:31533 withEvents:YES];
  141. [self createActiveReportWithID:@"report_D" time:63263 withEvents:YES];
  142. [self.existingReportManager collectExistingReports];
  143. [self.existingReportManager.operationQueue waitUntilAllOperationsAreFinished];
  144. // Reports with events should be kept if there's less than MAX_UNSENT_REPORTS reports
  145. XCTAssertEqual([[self contentsOfActivePath] count], FIRCLSMaxUnsentReports,
  146. @"Contents of active path: %@", [self contentsOfActivePath]);
  147. XCTAssertEqual(self.existingReportManager.unsentReportsCount, FIRCLSMaxUnsentReports);
  148. XCTAssertEqual(self.existingReportManager.existingUnemptyActiveReportPaths.count,
  149. FIRCLSMaxUnsentReports);
  150. // Newest report based on started_at timestamp
  151. XCTAssertEqualObjects(self.existingReportManager.newestUnsentReport.reportID, @"report_D");
  152. }
  153. /**
  154. * When we go over the limit of FIRCLSMaxUnsentReports, we delete any reports over the limit to
  155. * ensure performant startup and prevent disk space from filling up. Delete starting with the oldest
  156. * first.
  157. */
  158. - (void)testUnsentReportsOverLimit {
  159. // Create a bunch of reports starting at different times
  160. [self createActiveReportWithID:@"report_A" time:12312 withEvents:YES];
  161. [self createActiveReportWithID:@"report_B" time:12315 withEvents:YES];
  162. [self createActiveReportWithID:@"report_C" time:31533 withEvents:YES];
  163. [self createActiveReportWithID:@"report_D" time:63263 withEvents:YES];
  164. [self createActiveReportWithID:@"report_E" time:33263 withEvents:YES];
  165. [self createActiveReportWithID:@"report_F" time:43263 withEvents:YES];
  166. [self createActiveReportWithID:@"report_G" time:77777 withEvents:YES];
  167. [self createActiveReportWithID:@"report_H" time:13263 withEvents:YES];
  168. [self createActiveReportWithID:@"report_I" time:61263 withEvents:YES];
  169. [self.existingReportManager collectExistingReports];
  170. [self.existingReportManager.operationQueue waitUntilAllOperationsAreFinished];
  171. // Remove any reports over the limit, starting with the oldest
  172. XCTAssertEqual([[self contentsOfActivePath] count], FIRCLSMaxUnsentReports,
  173. @"Contents of active path: %@", [self contentsOfActivePath]);
  174. XCTAssertEqual(self.existingReportManager.unsentReportsCount, FIRCLSMaxUnsentReports);
  175. XCTAssertEqual(self.existingReportManager.existingUnemptyActiveReportPaths.count,
  176. FIRCLSMaxUnsentReports);
  177. // Newest report based on started_at timestamp
  178. XCTAssertEqualObjects(self.existingReportManager.newestUnsentReport.reportID, @"report_G");
  179. // Make sure we're sorting correctly and keeping the newest reports.
  180. XCTAssertEqual([self reportPathAtIndex:0 isReportID:@"report_G"], true);
  181. XCTAssertEqual([self reportPathAtIndex:1 isReportID:@"report_D"], true);
  182. XCTAssertEqual([self reportPathAtIndex:2 isReportID:@"report_I"], true);
  183. XCTAssertEqual([self reportPathAtIndex:3 isReportID:@"report_F"], true);
  184. }
  185. @end