FIRCLSOnDemandModelTests.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. // Copyright 2022 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/FIRCLSExecutionIdentifierModel.h"
  18. #import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
  19. #import "Crashlytics/Crashlytics/Private/FIRCLSOnDemandModel_Private.h"
  20. #import "Crashlytics/UnitTests/Mocks/FIRAppFake.h"
  21. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockExistingReportManager.h"
  22. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockOnDemandModel.h"
  23. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockReportUploader.h"
  24. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockSettings.h"
  25. #import "Crashlytics/UnitTests/Mocks/FIRCLSTempMockFileManager.h"
  26. #import "Crashlytics/UnitTests/Mocks/FIRMockGDTCoreTransport.h"
  27. #import "Crashlytics/UnitTests/Mocks/FIRMockInstallations.h"
  28. #import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.h"
  29. #import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.h"
  30. #import "Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.h"
  31. #define TEST_GOOGLE_APP_ID (@"1:632950151350:ios:d5b0d08d4f00f4b1")
  32. @interface FIRCLSOnDemandModelTests : XCTestCase
  33. @property(nonatomic, retain) FIRCLSMockOnDemandModel *onDemandModel;
  34. @property(nonatomic, strong) FIRCLSExistingReportManager *existingReportManager;
  35. @property(nonatomic, strong) FIRCLSManagerData *managerData;
  36. @property(nonatomic, strong) FIRCLSDataCollectionArbiter *dataArbiter;
  37. @property(nonatomic, strong) FIRCLSTempMockFileManager *fileManager;
  38. @property(nonatomic, strong) FIRCLSMockReportUploader *mockReportUploader;
  39. @property(nonatomic, strong) FIRCLSMockSettings *mockSettings;
  40. @end
  41. @implementation FIRCLSOnDemandModelTests
  42. - (void)setUp {
  43. [super setUp];
  44. FIRSetLoggerLevel(FIRLoggerLevelMax);
  45. FIRCLSContextBaseInit();
  46. id fakeApp = [[FIRAppFake alloc] init];
  47. self.dataArbiter = [[FIRCLSDataCollectionArbiter alloc] initWithApp:fakeApp withAppInfo:@{}];
  48. self.fileManager = [[FIRCLSTempMockFileManager alloc] init];
  49. FIRCLSApplicationIdentifierModel *appIDModel = [[FIRCLSApplicationIdentifierModel alloc] init];
  50. _mockSettings = [[FIRCLSMockSettings alloc] initWithFileManager:self.fileManager
  51. appIDModel:appIDModel];
  52. _onDemandModel = [[FIRCLSMockOnDemandModel alloc] initWithFIRCLSSettings:_mockSettings
  53. sleepBlock:^(int delay){
  54. }];
  55. FIRMockInstallations *iid = [[FIRMockInstallations alloc] initWithFID:@"test_token"];
  56. FIRMockGDTCORTransport *mockGoogleTransport =
  57. [[FIRMockGDTCORTransport alloc] initWithMappingID:@"id" transformers:nil target:0];
  58. _managerData = [[FIRCLSManagerData alloc] initWithGoogleAppID:TEST_GOOGLE_APP_ID
  59. googleTransport:mockGoogleTransport
  60. installations:iid
  61. analytics:nil
  62. fileManager:self.fileManager
  63. dataArbiter:self.dataArbiter
  64. settings:self.mockSettings
  65. onDemandModel:_onDemandModel];
  66. _mockReportUploader = [[FIRCLSMockReportUploader alloc] initWithManagerData:self.managerData];
  67. _existingReportManager =
  68. [[FIRCLSExistingReportManager alloc] initWithManagerData:self.managerData
  69. reportUploader:self.mockReportUploader];
  70. [self.fileManager createReportDirectories];
  71. [self.fileManager
  72. setupNewPathForExecutionIdentifier:self.managerData.executionIDModel.executionID];
  73. NSString *name = @"exception_model_report";
  74. NSString *reportPath = [self.fileManager.rootPath stringByAppendingPathComponent:name];
  75. [self.fileManager createDirectoryAtPath:reportPath];
  76. FIRCLSInternalReport *report =
  77. [[FIRCLSInternalReport alloc] initWithPath:reportPath
  78. executionIdentifier:@"TEST_EXECUTION_IDENTIFIER"];
  79. FIRCLSContextInitialize(report, self.mockSettings, self.fileManager);
  80. }
  81. - (void)tearDown {
  82. self.onDemandModel = nil;
  83. [[NSFileManager defaultManager] removeItemAtPath:self.fileManager.rootPath error:nil];
  84. [super tearDown];
  85. }
  86. - (void)setSleepBlock:(void (^)(int))sleepBlock {
  87. ((FIRCLSMockOnDemandModel *)self.managerData.onDemandModel).sleepBlock = sleepBlock;
  88. }
  89. - (void)testIncrementsQueueWhenEventRecorded {
  90. FIRExceptionModel *exceptionModel = [self getTestExceptionModel];
  91. XCTestExpectation *testComplete =
  92. [[XCTestExpectation alloc] initWithDescription:@"complete test"];
  93. // Put an expectation in the sleep block so we can test the state of the queue.
  94. __weak FIRCLSOnDemandModelTests *weakSelf = self;
  95. [self setSleepBlock:^(int delay) {
  96. XCTAssertEqual(delay, 60 / self.mockSettings.onDemandUploadRate);
  97. [weakSelf waitForExpectations:@[ testComplete ] timeout:1.0];
  98. }];
  99. BOOL success = [self.onDemandModel recordOnDemandExceptionIfQuota:exceptionModel
  100. withDataCollectionEnabled:YES
  101. usingExistingReportManager:self.existingReportManager];
  102. // Should record but not submit a report.
  103. XCTAssertTrue(success);
  104. XCTAssertEqual([self.onDemandModel recordedOnDemandExceptionCount], 1);
  105. XCTAssertEqual(self.onDemandModel.getQueuedOperationsCount, 1);
  106. // Fulfill the expectation so the sleep block completes.
  107. [testComplete fulfill];
  108. }
  109. - (void)testCompliesWithDataCollectionOff {
  110. FIRExceptionModel *exceptionModel = [self getTestExceptionModel];
  111. XCTestExpectation *testComplete =
  112. [[XCTestExpectation alloc] initWithDescription:@"complete test"];
  113. // Put an expectation in the sleep block so we can test the state of the queue.
  114. __weak FIRCLSOnDemandModelTests *weakSelf = self;
  115. [self setSleepBlock:^(int delay) {
  116. XCTAssertEqual(delay, 60 / self.mockSettings.onDemandUploadRate);
  117. [weakSelf waitForExpectations:@[ testComplete ] timeout:1.0];
  118. }];
  119. BOOL success = [self.onDemandModel recordOnDemandExceptionIfQuota:exceptionModel
  120. withDataCollectionEnabled:NO
  121. usingExistingReportManager:self.existingReportManager];
  122. // Should record but not submit a report.
  123. XCTAssertTrue(success);
  124. // We still count this as a recorded event if it was recorded but not submitted.
  125. XCTAssertEqual([self.onDemandModel recordedOnDemandExceptionCount], 1);
  126. XCTAssertEqual([self contentsOfActivePath].count, 2);
  127. XCTAssertEqual(self.onDemandModel.getQueuedOperationsCount, 1);
  128. XCTAssertEqual([self.onDemandModel.storedActiveReportPaths count], 1);
  129. // Fulfill the expectation so the sleep block completes.
  130. [testComplete fulfill];
  131. }
  132. - (void)testQuotaWithDataCollectionOff {
  133. FIRExceptionModel *exceptionModel = [self getTestExceptionModel];
  134. for (int i = 0; i < 10; i++) {
  135. BOOL success =
  136. [self.managerData.onDemandModel recordOnDemandExceptionIfQuota:exceptionModel
  137. withDataCollectionEnabled:NO
  138. usingExistingReportManager:self.existingReportManager];
  139. XCTAssertTrue(success);
  140. }
  141. // Once we've finished processing, there should be only FIRCLSMaxUnsentReports recorded with the
  142. // rest considered dropped. The recorded events should be stored in storedActiveReportPaths which
  143. // is kept in sync with the contents of the active path.
  144. [self.managerData.onDemandModel.operationQueue waitUntilAllOperationsAreFinished];
  145. XCTAssertEqual([self.managerData.onDemandModel.operationQueue operationCount], 0);
  146. XCTAssertEqual([self.managerData.onDemandModel recordedOnDemandExceptionCount],
  147. FIRCLSMaxUnsentReports);
  148. XCTAssertEqual([self contentsOfActivePath].count, FIRCLSMaxUnsentReports + 1);
  149. XCTAssertEqual([self.managerData.onDemandModel.storedActiveReportPaths count],
  150. FIRCLSMaxUnsentReports);
  151. // Once we call sendUnsentReports, stored reports should be sent immediately.
  152. [self.existingReportManager sendUnsentReportsWithToken:[FIRCLSDataCollectionToken validToken]
  153. asUrgent:YES];
  154. XCTAssertEqual([self.managerData.onDemandModel recordedOnDemandExceptionCount],
  155. FIRCLSMaxUnsentReports);
  156. XCTAssertEqual([self contentsOfActivePath].count, 1);
  157. XCTAssertEqual([self.managerData.onDemandModel.storedActiveReportPaths count], 0);
  158. }
  159. - (void)testDropsEventIfNoQuota {
  160. [self.onDemandModel setQueueToFull];
  161. FIRExceptionModel *exceptionModel = [self getTestExceptionModel];
  162. BOOL success = [self.onDemandModel recordOnDemandExceptionIfQuota:exceptionModel
  163. withDataCollectionEnabled:NO
  164. usingExistingReportManager:self.existingReportManager];
  165. // Should return false when attempting to record an event and increment the count of dropped
  166. // events.
  167. XCTAssertFalse(success);
  168. XCTAssertEqual(self.onDemandModel.getQueuedOperationsCount, [self.onDemandModel getQueueMax]);
  169. XCTAssertEqual([self.onDemandModel droppedOnDemandExceptionCount], 1);
  170. }
  171. - (void)testDroppedEventCountResets {
  172. [self.onDemandModel setQueueToFull];
  173. FIRExceptionModel *exceptionModel = [self getTestExceptionModel];
  174. BOOL success = [self.onDemandModel recordOnDemandExceptionIfQuota:exceptionModel
  175. withDataCollectionEnabled:NO
  176. usingExistingReportManager:self.existingReportManager];
  177. // Should return false when attempting to record an event and increment the count of dropped
  178. // events.
  179. XCTAssertFalse(success);
  180. XCTAssertEqual(self.onDemandModel.getQueuedOperationsCount, [self.onDemandModel getQueueMax]);
  181. XCTAssertEqual([self.onDemandModel droppedOnDemandExceptionCount], 1);
  182. // Reset the queue to empty
  183. [self.onDemandModel setQueuedOperationsCount:0];
  184. success = [self.onDemandModel recordOnDemandExceptionIfQuota:exceptionModel
  185. withDataCollectionEnabled:NO
  186. usingExistingReportManager:self.existingReportManager];
  187. // Now have room in the queue to record the event
  188. XCTAssertTrue(success);
  189. // droppedOnDemandExceptionCount should be reset once we record the event
  190. XCTAssertEqual([self.onDemandModel droppedOnDemandExceptionCount], 0);
  191. }
  192. #pragma mark - Helpers
  193. - (NSArray *)contentsOfActivePath {
  194. return [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.fileManager.activePath
  195. error:nil];
  196. }
  197. - (FIRExceptionModel *)getTestExceptionModel {
  198. NSArray *stackTrace = @[
  199. [FIRStackFrame stackFrameWithSymbol:@"CrashyFunc" file:@"AppLib.m" line:504],
  200. [FIRStackFrame stackFrameWithSymbol:@"ApplicationMain" file:@"AppleLib" line:1],
  201. [FIRStackFrame stackFrameWithSymbol:@"main()" file:@"main.m" line:201],
  202. ];
  203. NSString *name = @"FIRCLSOnDemandModelTestCrash";
  204. NSString *reason = @"Programmer made an error";
  205. FIRExceptionModel *exceptionModel = [FIRExceptionModel exceptionModelWithName:name reason:reason];
  206. exceptionModel.stackTrace = stackTrace;
  207. exceptionModel.isFatal = YES;
  208. exceptionModel.onDemand = YES;
  209. return exceptionModel;
  210. }
  211. @end