FIRCLSMetricKitManagerTests.m 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  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 <Foundation/Foundation.h>
  15. #import <XCTest/XCTest.h>
  16. #import "Crashlytics/Crashlytics/Controllers/FIRCLSMetricKitManager.h"
  17. #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
  18. #if CLS_METRICKIT_SUPPORTED
  19. #if __has_include(<FBLPromises/FBLPromises.h>)
  20. #import <FBLPromises/FBLPromises.h>
  21. #else
  22. #import "FBLPromises.h"
  23. #endif
  24. #include "Crashlytics/Crashlytics/Components/FIRCLSContext.h"
  25. #import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
  26. #import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.h"
  27. #import "Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h"
  28. #import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
  29. #import "Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.h"
  30. #import "Crashlytics/UnitTests/Mocks/FIRAppFake.h"
  31. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockReportManager.h"
  32. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockReportUploader.h"
  33. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockSettings.h"
  34. #import "Crashlytics/UnitTests/Mocks/FIRCLSTempMockFileManager.h"
  35. #import "Crashlytics/UnitTests/Mocks/FIRMockGDTCoreTransport.h"
  36. #import "Crashlytics/UnitTests/Mocks/FIRMockInstallations.h"
  37. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockExistingReportManager.h"
  38. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockMXCPUExceptionDiagnostic.h"
  39. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockMXCallStackTree.h"
  40. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockMXCrashDiagnostic.h"
  41. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockMXDiagnosticPayload.h"
  42. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockMXDiskWriteExceptionDiagnostic.h"
  43. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockMXHangDiagnostic.h"
  44. #define TEST_GOOGLE_APP_ID (@"1:632950151350:ios:d5b0d08d4f00f4b1")
  45. API_AVAILABLE(ios(14))
  46. @interface FIRCLSMetricKitManagerTests : XCTestCase
  47. @property(nonatomic, strong) FIRCLSMockReportManager *reportManager;
  48. @property(nonatomic, strong) FIRCLSMetricKitManager *metricKitManager;
  49. @property(nonatomic, strong) FIRCLSMockSettings *mockSettings;
  50. @property(nonatomic, strong) FIRCLSManagerData *managerData;
  51. @property(nonatomic, strong) FIRCLSMockReportUploader *mockReportUploader;
  52. @property(nonatomic, strong) FIRCLSTempMockFileManager *fileManager;
  53. @property(nonatomic, strong) FIRCLSMockExistingReportManager *existingReportManager;
  54. @property(nonatomic, strong) FIRCLSDataCollectionArbiter *dataArbiter;
  55. @property(nonatomic, strong) FIRCLSApplicationIdentifierModel *appIDModel;
  56. @property(nonatomic, strong) NSDate *beginTime;
  57. @property(nonatomic, strong) NSDate *endTime;
  58. @end
  59. @implementation FIRCLSMetricKitManagerTests
  60. - (void)setUp {
  61. [super setUp];
  62. FIRSetLoggerLevel(FIRLoggerLevelMax);
  63. FIRCLSContextBaseInit();
  64. id fakeApp = [[FIRAppFake alloc] init];
  65. self.dataArbiter = [[FIRCLSDataCollectionArbiter alloc] initWithApp:fakeApp withAppInfo:@{}];
  66. self.fileManager = [[FIRCLSTempMockFileManager alloc] init];
  67. // Delete cached settings
  68. [self.fileManager removeItemAtPath:_fileManager.settingsFilePath];
  69. FIRMockInstallations *iid = [[FIRMockInstallations alloc] initWithFID:@"test_token"];
  70. FIRMockGDTCORTransport *mockGoogleTransport =
  71. [[FIRMockGDTCORTransport alloc] initWithMappingID:@"id" transformers:nil target:0];
  72. FIRCLSApplicationIdentifierModel *appIDModel = [[FIRCLSApplicationIdentifierModel alloc] init];
  73. FIRCLSMockSettings *mockSettings =
  74. [[FIRCLSMockSettings alloc] initWithFileManager:self.fileManager appIDModel:appIDModel];
  75. _managerData = [[FIRCLSManagerData alloc] initWithGoogleAppID:TEST_GOOGLE_APP_ID
  76. googleTransport:mockGoogleTransport
  77. installations:iid
  78. analytics:nil
  79. fileManager:self.fileManager
  80. dataArbiter:self.dataArbiter
  81. settings:mockSettings];
  82. self.mockReportUploader = [[FIRCLSMockReportUploader alloc] initWithManagerData:self.managerData];
  83. self.existingReportManager =
  84. [[FIRCLSMockExistingReportManager alloc] initWithManagerData:self.managerData
  85. reportUploader:self.mockReportUploader];
  86. [self.fileManager createReportDirectories];
  87. [self.fileManager
  88. setupNewPathForExecutionIdentifier:self.managerData.executionIDModel.executionID];
  89. self.metricKitManager =
  90. [[FIRCLSMetricKitManager alloc] initWithManagerData:self.managerData
  91. existingReportManager:self.existingReportManager
  92. fileManager:self.fileManager];
  93. self.beginTime = [NSDate date];
  94. self.endTime = [NSDate dateWithTimeIntervalSinceNow:1];
  95. }
  96. - (void)tearDown {
  97. self.existingReportManager = nil;
  98. if ([[NSFileManager defaultManager] fileExistsAtPath:[self.fileManager rootPath]]) {
  99. assert([self.fileManager removeItemAtPath:[self.fileManager rootPath]]);
  100. }
  101. FIRCLSContextBaseDeinit();
  102. [super tearDown];
  103. }
  104. #pragma mark - Diagnostic Creation Helpers
  105. - (FIRCLSMockMXCallStackTree *)createMockCallStackTree {
  106. NSString *callStackTreeString =
  107. @"{\n \"callStacks\" : [\n {\n \"threadAttributed\" : true,\n "
  108. @"\"callStackRootFrames\" : [\n {\n \"binaryUUID\" : "
  109. @"\"6387F46B-BE42-4575-8BFA-782CAAE676AA\",\n \"offsetIntoBinaryTextSegment\" : "
  110. @"123,\n \"sampleCount\" : 20,\n \"binaryName\" : \"testBinaryName\",\n "
  111. @" \"address\" : 74565\n }\n ]\n }\n ],\n \"callStackPerThread\" : "
  112. @"true\n}";
  113. return [[FIRCLSMockMXCallStackTree alloc] initWithStringData:callStackTreeString];
  114. }
  115. - (FIRCLSMockMXMetadata *)createMockMetadata {
  116. return [[FIRCLSMockMXMetadata alloc] initWithRegionFormat:@"US"
  117. osVersion:@"iPhone OS 15.0 (19A5281j)"
  118. deviceType:@"iPhone9,1"
  119. applicationBuildVersion:@"1"
  120. platformArchitecture:@"arm64"];
  121. }
  122. - (FIRCLSMockMXCrashDiagnostic *)createCrashDiagnostic {
  123. return [[FIRCLSMockMXCrashDiagnostic alloc]
  124. initWithCallStackTree:[self createMockCallStackTree]
  125. terminationReason:@"Namespace SIGNAL, Code 0xb"
  126. virtualMemoryRegionInfo:
  127. @"0 is not in any region. Bytes before following region: 4000000000 REGION TYPE "
  128. @" START - END [ VSIZE] PRT\\/MAX SHRMOD REGION DETAIL UNUSED "
  129. @"SPACE AT START ---> __TEXT 0000000000000000-0000000000000000 [ 32K] "
  130. @"r-x\\/r-x SM=COW ...pp\\/Test"
  131. exceptionType:@6
  132. exceptionCode:@0
  133. signal:@11
  134. metaData:[self createMockMetadata]
  135. applicationVersion:@"1"];
  136. }
  137. - (FIRCLSMockMXHangDiagnostic *)createHangDiagnostic {
  138. return [[FIRCLSMockMXHangDiagnostic alloc]
  139. initWithCallStackTree:[self createMockCallStackTree]
  140. hangDuration:[[NSMeasurement alloc] initWithDoubleValue:4.0
  141. unit:NSUnitDuration.seconds]
  142. metaData:[self createMockMetadata]
  143. applicationVersion:@"1"];
  144. }
  145. - (FIRCLSMockMXCPUExceptionDiagnostic *)createCPUExceptionDiagnostic {
  146. return [[FIRCLSMockMXCPUExceptionDiagnostic alloc]
  147. initWithCallStackTree:[self createMockCallStackTree]
  148. totalCPUTime:[[NSMeasurement alloc] initWithDoubleValue:1.0
  149. unit:NSUnitDuration.seconds]
  150. totalSampledTime:[[NSMeasurement alloc] initWithDoubleValue:2.0
  151. unit:NSUnitDuration.seconds]
  152. metaData:[self createMockMetadata]
  153. applicationVersion:@"1"];
  154. }
  155. - (FIRCLSMockMXDiskWriteExceptionDiagnostic *)createDiskWriteExcptionDiagnostic {
  156. return [[FIRCLSMockMXDiskWriteExceptionDiagnostic alloc]
  157. initWithCallStackTree:[self createMockCallStackTree]
  158. totalWritesCaused:[[NSMeasurement alloc] initWithDoubleValue:24.0
  159. unit:NSUnitDuration.seconds]
  160. metaData:[self createMockMetadata]
  161. applicationVersion:@"1"];
  162. }
  163. - (FIRCLSMockMXDiagnosticPayload *)createCrashDiagnosticPayload {
  164. NSDictionary *diagnostics = @{@"crashes" : @[ [self createCrashDiagnostic] ]};
  165. return [[FIRCLSMockMXDiagnosticPayload alloc] initWithDiagnostics:diagnostics
  166. timeStampBegin:self.beginTime
  167. timeStampEnd:self.endTime
  168. applicationVersion:@"1"];
  169. }
  170. - (FIRCLSMockMXDiagnosticPayload *)createHangDiagnosticPayload {
  171. NSDictionary *diagnostics = @{@"hangs" : @[ [self createHangDiagnostic] ]};
  172. return [[FIRCLSMockMXDiagnosticPayload alloc] initWithDiagnostics:diagnostics
  173. timeStampBegin:self.beginTime
  174. timeStampEnd:self.endTime
  175. applicationVersion:@"1"];
  176. }
  177. - (FIRCLSMockMXDiagnosticPayload *)createCPUExceptionDiagnosticPayload {
  178. NSDictionary *diagnostics =
  179. @{@"cpuExceptionDiagnostics" : @[ [self createCPUExceptionDiagnostic] ]};
  180. return [[FIRCLSMockMXDiagnosticPayload alloc] initWithDiagnostics:diagnostics
  181. timeStampBegin:self.beginTime
  182. timeStampEnd:self.endTime
  183. applicationVersion:@"1"];
  184. }
  185. - (FIRCLSMockMXDiagnosticPayload *)createDiskWriteExceptionDiagnosticPayload {
  186. NSDictionary *diagnostics =
  187. @{@"diskWriteExceptionDiagnostics" : @[ [self createDiskWriteExcptionDiagnostic] ]};
  188. return [[FIRCLSMockMXDiagnosticPayload alloc] initWithDiagnostics:diagnostics
  189. timeStampBegin:self.beginTime
  190. timeStampEnd:self.endTime
  191. applicationVersion:@"1"];
  192. }
  193. - (FIRCLSMockMXDiagnosticPayload *)createFullDiagnosticPayload {
  194. NSDictionary *diagnostics = @{
  195. @"crashes" : @[ [self createCrashDiagnostic] ],
  196. @"hangs" : @[ [self createHangDiagnostic] ],
  197. @"cpuExceptionDiagnostics" : @[ [self createCPUExceptionDiagnostic] ],
  198. @"diskWriteExceptionDiagnostics" : @[ [self createDiskWriteExcptionDiagnostic] ]
  199. };
  200. return [[FIRCLSMockMXDiagnosticPayload alloc] initWithDiagnostics:diagnostics
  201. timeStampBegin:self.beginTime
  202. timeStampEnd:self.endTime
  203. applicationVersion:@"1"];
  204. }
  205. - (FIRCLSMockMXDiagnosticPayload *)createEmptyDiagnosticPayload {
  206. NSDictionary *diagnostics = @{@"should" : @"be empty"};
  207. return [[FIRCLSMockMXDiagnosticPayload alloc] initWithDiagnostics:diagnostics
  208. timeStampBegin:self.beginTime
  209. timeStampEnd:self.endTime
  210. applicationVersion:@"1"];
  211. }
  212. - (FIRCLSMockMXDiagnosticPayload *)createDiagnosticPayloadWithMultipleCrashes {
  213. NSDictionary *diagnostics = @{
  214. @"crashes" : @[
  215. [self createCrashDiagnostic], [self createCrashDiagnostic], [self createCrashDiagnostic]
  216. ]
  217. };
  218. return [[FIRCLSMockMXDiagnosticPayload alloc] initWithDiagnostics:diagnostics
  219. timeStampBegin:self.beginTime
  220. timeStampEnd:self.endTime
  221. applicationVersion:@"1"];
  222. }
  223. - (void)checkMetadata:(NSDictionary *)metadata andThreads:(NSDictionary *)threads {
  224. XCTAssertNotNil(metadata, "MetricKit event should write metadata to file.");
  225. XCTAssertNotNil(threads, "MetricKit event should write threads to file.");
  226. XCTAssertTrue([[metadata objectForKey:@"appBuildVersion"] isEqualToString:@"1"]);
  227. XCTAssertTrue(
  228. [[metadata objectForKey:@"osVersion"] isEqualToString:@"iPhone OS 15.0 (19A5281j)"]);
  229. XCTAssertTrue([[metadata objectForKey:@"regionFormat"] isEqualToString:@"US"]);
  230. XCTAssertTrue([[metadata objectForKey:@"platformArchitecture"] isEqualToString:@"arm64"]);
  231. XCTAssertTrue([[metadata objectForKey:@"deviceType"] isEqualToString:@"iPhone9,1"]);
  232. XCTAssertTrue([threads objectForKey:@"crashed"]); // YES
  233. XCTAssertEqual([[threads objectForKey:@"registers"] count], 0); //{}
  234. XCTAssertEqual([[[threads objectForKey:@"stacktrace"] objectAtIndex:0] intValue], 74565);
  235. }
  236. #pragma mark - Path Helpers
  237. - (NSArray *)contentsOfActivePath {
  238. return [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.fileManager.activePath
  239. error:nil];
  240. }
  241. - (BOOL)metricKitFileExistsInCurrentReport:(BOOL)currentReport fatalReport:(BOOL)fatalReport {
  242. NSString *newestUnsentReportID =
  243. [self.existingReportManager.newestUnsentReport.reportID stringByAppendingString:@"/"];
  244. NSString *currentReportID =
  245. [_managerData.executionIDModel.executionID stringByAppendingString:@"/"];
  246. NSString *reportID = (currentReport ? currentReportID : newestUnsentReportID);
  247. NSString *metricKitName =
  248. fatalReport ? @"metric_kit_fatal.clsrecord" : @"metric_kit_nonfatal.clsrecord";
  249. NSString *temp =
  250. [[self.fileManager.activePath stringByAppendingString:@"/"] stringByAppendingString:reportID];
  251. // Need to determine which report the file should be in
  252. return [[NSFileManager defaultManager]
  253. fileExistsAtPath:[temp stringByAppendingString:metricKitName]];
  254. }
  255. - (NSString *)contentsOfMetricKitFile:(BOOL)currentReport fatalReport:(BOOL)fatalReport {
  256. if (![self metricKitFileExistsInCurrentReport:currentReport fatalReport:fatalReport]) return nil;
  257. NSString *newestUnsentReportID =
  258. [self.existingReportManager.newestUnsentReport.reportID stringByAppendingString:@"/"];
  259. NSString *currentReportID =
  260. [_managerData.executionIDModel.executionID stringByAppendingString:@"/"];
  261. NSString *reportID = (currentReport ? currentReportID : newestUnsentReportID);
  262. NSString *metricKitName =
  263. fatalReport ? @"metric_kit_fatal.clsrecord" : @"metric_kit_nonfatal.clsrecord";
  264. NSString *filePath = [[[self.fileManager.activePath stringByAppendingString:@"/"]
  265. stringByAppendingString:reportID] stringByAppendingString:metricKitName];
  266. NSString *fileContents = [[NSString alloc] initWithContentsOfFile:filePath
  267. encoding:NSUTF8StringEncoding
  268. error:nil];
  269. return fileContents;
  270. }
  271. - (NSDictionary *)contentsOfMetricKitFileAsDictionary:(BOOL)currentReport
  272. fatalReport:(BOOL)fatalReport {
  273. NSString *metricKitFileContents = [self contentsOfMetricKitFile:currentReport
  274. fatalReport:fatalReport];
  275. NSArray *metricKitFileArray = [metricKitFileContents componentsSeparatedByString:@"\n"];
  276. NSMutableDictionary *fileDictionary = [[NSMutableDictionary alloc] init];
  277. BOOL hasCrash = NO;
  278. for (NSString *json in metricKitFileArray) {
  279. NSString *itemKey = nil;
  280. if ([json containsString:@"metric_kit_fatal"])
  281. itemKey = @"crash_event";
  282. else if ([json containsString:@"exception"] && [json containsString:@"hang_event"])
  283. itemKey = @"hang_event";
  284. else if ([json containsString:@"exception"] && [json containsString:@"cpu_exception_event"])
  285. itemKey = @"cpu_exception_event";
  286. else if ([json containsString:@"exception"] &&
  287. [json containsString:@"disk_write_exception_event"])
  288. itemKey = @"disk_write_exception_event";
  289. else if ([json containsString:@"end_time"])
  290. itemKey = @"time";
  291. else if ([json containsString:@"threads"])
  292. itemKey = @"threads";
  293. NSData *itemData = [json dataUsingEncoding:NSUTF8StringEncoding];
  294. if (itemData == nil || itemKey == nil) continue;
  295. NSError *error = nil;
  296. NSDictionary *itemDictionary = [NSJSONSerialization JSONObjectWithData:itemData
  297. options:0
  298. error:&error];
  299. [fileDictionary setObject:itemDictionary forKey:itemKey];
  300. if ([itemKey isEqualToString:@"crash_event"]) {
  301. XCTAssertTrue(hasCrash == NO, "MetricKit reports should only have one crash event");
  302. hasCrash = YES;
  303. }
  304. }
  305. return fileDictionary;
  306. }
  307. - (void)createUnsentFatalReport {
  308. // create a report and put it in place
  309. NSString *reportPath =
  310. [self.fileManager.activePath stringByAppendingPathComponent:@"my_session_id"];
  311. FIRCLSInternalReport *report = [[FIRCLSInternalReport alloc] initWithPath:reportPath
  312. executionIdentifier:@"my_session_id"];
  313. [self.fileManager createDirectoryAtPath:report.path];
  314. [self.existingReportManager setShouldHaveExistingReport];
  315. }
  316. #pragma mark - Diagnostic Handling
  317. - (void)testEmptyDiagnosticHandling {
  318. FIRCLSMockMXDiagnosticPayload *emptyPayload = [self createEmptyDiagnosticPayload];
  319. [self.metricKitManager didReceiveDiagnosticPayloads:@[ emptyPayload ]];
  320. XCTAssertFalse([self metricKitFileExistsInCurrentReport:YES fatalReport:NO],
  321. "MetricKit report should not exist");
  322. }
  323. - (void)testCrashDiagnosticHandling {
  324. [self createUnsentFatalReport];
  325. FIRCLSMockMXDiagnosticPayload *crashPayload = [self createCrashDiagnosticPayload];
  326. [self.metricKitManager didReceiveDiagnosticPayloads:@[ crashPayload ]];
  327. XCTAssertTrue([self metricKitFileExistsInCurrentReport:NO fatalReport:YES],
  328. "MetricKit report should exist");
  329. NSDictionary *fileDictionary = [self contentsOfMetricKitFileAsDictionary:NO fatalReport:YES];
  330. XCTAssertNotNil(fileDictionary, "MetricKit file should not be empty");
  331. NSDictionary *crashDictionary =
  332. [[fileDictionary objectForKey:@"crash_event"] objectForKey:@"metric_kit_fatal"];
  333. XCTAssertNotNil(crashDictionary, "MetricKit event should include a crash diagnostic");
  334. XCTAssertEqual([[crashDictionary objectForKey:@"time"] longValue],
  335. [[NSNumber numberWithDouble:[self.beginTime timeIntervalSince1970]] longValue]);
  336. XCTAssertEqual([[crashDictionary objectForKey:@"end_time"] longValue],
  337. [[NSNumber numberWithDouble:[self.endTime timeIntervalSince1970]] longValue]);
  338. XCTAssertEqual([[crashDictionary objectForKey:@"signal"] integerValue], 11);
  339. XCTAssertTrue([[crashDictionary objectForKey:@"app_version"] isEqualToString:@"1"]);
  340. XCTAssertTrue([[crashDictionary objectForKey:@"termination_reason"]
  341. isEqualToString:@"Namespace SIGNAL, Code 0xb"]);
  342. XCTAssertTrue([[crashDictionary objectForKey:@"virtual_memory_region_info"]
  343. isEqualToString:
  344. @"0 is not in any region. Bytes before following region: 4000000000 REGION TYPE "
  345. @" START - END [ VSIZE] PRT\\/MAX SHRMOD REGION DETAIL UNUSED "
  346. @"SPACE AT START ---> __TEXT 0000000000000000-0000000000000000 [ 32K] "
  347. @"r-x\\/r-x SM=COW ...pp\\/Test"]);
  348. XCTAssertEqual([[crashDictionary objectForKey:@"exception_code"] integerValue], 0);
  349. XCTAssertEqual([[crashDictionary objectForKey:@"exception_type"] integerValue], 6);
  350. XCTAssertTrue([[crashDictionary objectForKey:@"name"] isEqualToString:@"SIGABRT"]);
  351. XCTAssertTrue([[crashDictionary objectForKey:@"code_name"] isEqualToString:@"ABORT"]);
  352. NSDictionary *metadata = [crashDictionary objectForKey:@"metadata"];
  353. NSDictionary *threads =
  354. [[[fileDictionary objectForKey:@"threads"] objectForKey:@"threads"] objectAtIndex:0];
  355. [self checkMetadata:metadata andThreads:threads];
  356. }
  357. - (void)testHangDiagnosticHandling {
  358. FIRCLSMockMXDiagnosticPayload *hangPayload = [self createHangDiagnosticPayload];
  359. [self.metricKitManager didReceiveDiagnosticPayloads:@[ hangPayload ]];
  360. XCTAssertTrue([self metricKitFileExistsInCurrentReport:YES fatalReport:NO],
  361. "MetricKit report should exist");
  362. NSDictionary *fileDictionary = [self contentsOfMetricKitFileAsDictionary:YES fatalReport:NO];
  363. XCTAssertNotNil(fileDictionary, "MetricKit file should not be empty");
  364. NSDictionary *hangDictionary =
  365. [[fileDictionary objectForKey:@"hang_event"] objectForKey:@"exception"];
  366. XCTAssertNotNil(hangDictionary, "MetricKit event should include a hang diagnostic");
  367. XCTAssertEqual([[hangDictionary objectForKey:@"hang_duration"] integerValue], 4);
  368. XCTAssertEqual([[hangDictionary objectForKey:@"time"] longValue],
  369. [[NSNumber numberWithDouble:[self.beginTime timeIntervalSince1970]] longValue]);
  370. XCTAssertEqual([[hangDictionary objectForKey:@"end_time"] longValue],
  371. [[NSNumber numberWithDouble:[self.endTime timeIntervalSince1970]] longValue]);
  372. XCTAssertTrue([[hangDictionary objectForKey:@"app_version"] isEqualToString:@"1"]);
  373. NSDictionary *metadata = [hangDictionary objectForKey:@"metadata"];
  374. NSDictionary *threads = [[hangDictionary objectForKey:@"threads"] objectAtIndex:0];
  375. [self checkMetadata:metadata andThreads:threads];
  376. }
  377. - (void)testCPUExceptionDiagnosticHandling {
  378. FIRCLSMockMXDiagnosticPayload *cpuPayload = [self createCPUExceptionDiagnosticPayload];
  379. [self.metricKitManager didReceiveDiagnosticPayloads:@[ cpuPayload ]];
  380. XCTAssertTrue([self metricKitFileExistsInCurrentReport:YES fatalReport:NO],
  381. "MetricKit report should exist");
  382. NSDictionary *fileDictionary = [self contentsOfMetricKitFileAsDictionary:YES fatalReport:NO];
  383. XCTAssertNotNil(fileDictionary, "MetricKit file should not be empty");
  384. NSDictionary *cpuDictionary =
  385. [[fileDictionary objectForKey:@"cpu_exception_event"] objectForKey:@"exception"];
  386. XCTAssertNotNil(cpuDictionary, "MetricKit event should include a CPU exception diagnostic");
  387. XCTAssertEqual([[cpuDictionary objectForKey:@"total_cpu_time"] integerValue], 1);
  388. XCTAssertEqual([[cpuDictionary objectForKey:@"total_sampled_time"] integerValue], 2);
  389. XCTAssertTrue([[cpuDictionary objectForKey:@"app_version"] isEqualToString:@"1"]);
  390. XCTAssertEqual([[cpuDictionary objectForKey:@"time"] longValue],
  391. [[NSNumber numberWithDouble:[self.beginTime timeIntervalSince1970]] longValue]);
  392. XCTAssertEqual([[cpuDictionary objectForKey:@"end_time"] longValue],
  393. [[NSNumber numberWithDouble:[self.endTime timeIntervalSince1970]] longValue]);
  394. NSDictionary *metadata = [cpuDictionary objectForKey:@"metadata"];
  395. NSDictionary *threads = [[cpuDictionary objectForKey:@"threads"] objectAtIndex:0];
  396. [self checkMetadata:metadata andThreads:threads];
  397. }
  398. - (void)testDiskWriteExceptionDiagnosticHandling {
  399. FIRCLSMockMXDiagnosticPayload *diskWritePayload =
  400. [self createDiskWriteExceptionDiagnosticPayload];
  401. [self.metricKitManager didReceiveDiagnosticPayloads:@[ diskWritePayload ]];
  402. XCTAssertTrue([self metricKitFileExistsInCurrentReport:YES fatalReport:NO],
  403. "MetricKit report should exist");
  404. NSDictionary *fileDictionary = [self contentsOfMetricKitFileAsDictionary:YES fatalReport:NO];
  405. XCTAssertNotNil(fileDictionary, "MetricKit file should not be empty");
  406. NSDictionary *diskWriteDictionary =
  407. [[fileDictionary objectForKey:@"disk_write_exception_event"] objectForKey:@"exception"];
  408. XCTAssertNotNil(diskWriteDictionary,
  409. "MetricKit event should include a disk write exception diagnostic");
  410. XCTAssertEqual([[diskWriteDictionary objectForKey:@"total_writes_caused"] longValue], 24);
  411. XCTAssertTrue([[diskWriteDictionary objectForKey:@"app_version"] isEqualToString:@"1"]);
  412. XCTAssertEqual([[diskWriteDictionary objectForKey:@"time"] longValue],
  413. [[NSNumber numberWithDouble:[self.beginTime timeIntervalSince1970]] longValue]);
  414. XCTAssertEqual([[diskWriteDictionary objectForKey:@"end_time"] longValue],
  415. [[NSNumber numberWithDouble:[self.endTime timeIntervalSince1970]] longValue]);
  416. NSDictionary *metadata = [diskWriteDictionary objectForKey:@"metadata"];
  417. NSDictionary *threads = [[diskWriteDictionary objectForKey:@"threads"] objectAtIndex:0];
  418. [self checkMetadata:metadata andThreads:threads];
  419. }
  420. - (void)testFullDiagnosticHandling {
  421. [self createUnsentFatalReport];
  422. FIRCLSMockMXDiagnosticPayload *fullPayload = [self createFullDiagnosticPayload];
  423. [self.metricKitManager didReceiveDiagnosticPayloads:@[ fullPayload ]];
  424. XCTAssertTrue([self metricKitFileExistsInCurrentReport:NO fatalReport:YES],
  425. "MetricKit fatal report should exist");
  426. XCTAssertTrue([self metricKitFileExistsInCurrentReport:YES fatalReport:NO],
  427. "MetricKit nonfatal report should exist");
  428. NSDictionary *fatalFileDictionary = [self contentsOfMetricKitFileAsDictionary:NO fatalReport:YES];
  429. NSDictionary *fileDictionary = [self contentsOfMetricKitFileAsDictionary:YES fatalReport:NO];
  430. XCTAssertNotNil(fileDictionary, "MetricKit nonfatal file should not be empty");
  431. XCTAssertNotNil(fatalFileDictionary, "MetricKit fatal file should not be empty");
  432. XCTAssertNil([fatalFileDictionary objectForKey:@"hang_event"]);
  433. XCTAssertNil([fatalFileDictionary objectForKey:@"cpu_exception_event"]);
  434. XCTAssertNil([fatalFileDictionary objectForKey:@"disk_write_exception_event"]);
  435. XCTAssertNil([fileDictionary objectForKey:@"crash_event"]);
  436. XCTAssertNil([fileDictionary objectForKey:@"time"]);
  437. NSDictionary *hangDictionary =
  438. [[fileDictionary objectForKey:@"hang_event"] objectForKey:@"exception"];
  439. NSDictionary *cpuDictionary =
  440. [[fileDictionary objectForKey:@"cpu_exception_event"] objectForKey:@"exception"];
  441. NSDictionary *diskDictionary =
  442. [[fileDictionary objectForKey:@"disk_write_exception_event"] objectForKey:@"exception"];
  443. NSDictionary *crashDictionary =
  444. [[fatalFileDictionary objectForKey:@"crash_event"] objectForKey:@"metric_kit_fatal"];
  445. XCTAssertNotNil(hangDictionary, "MetricKit event should include a hang diagnostic");
  446. XCTAssertNotNil(cpuDictionary, "MetricKit event should include a CPU exception diagnostic");
  447. XCTAssertNotNil(diskDictionary,
  448. "MetricKit event should include a disk write exception diagnostic");
  449. XCTAssertNotNil(crashDictionary, "MetricKit event should include a crash diagnostic");
  450. }
  451. - (void)testPayloadWithMultipleCrashesHandling {
  452. [self createUnsentFatalReport];
  453. FIRCLSMockMXDiagnosticPayload *payloadWithMultipleCrashes =
  454. [self createDiagnosticPayloadWithMultipleCrashes];
  455. [self.metricKitManager didReceiveDiagnosticPayloads:@[ payloadWithMultipleCrashes ]];
  456. XCTAssertTrue([self metricKitFileExistsInCurrentReport:NO fatalReport:YES],
  457. "MetricKit report should exist");
  458. NSDictionary *fileDictionary = [self contentsOfMetricKitFileAsDictionary:NO fatalReport:YES];
  459. XCTAssertNotNil(fileDictionary, "MetricKit file should not be empty");
  460. NSDictionary *crashDictionary =
  461. [[fileDictionary objectForKey:@"crash_event"] objectForKey:@"metric_kit_fatal"];
  462. XCTAssertNotNil(crashDictionary, "MetricKit event should include a crash diagnostic");
  463. }
  464. - (void)testMultiplePayloadsWithCrashesHandling {
  465. [self createUnsentFatalReport];
  466. FIRCLSMockMXDiagnosticPayload *crashPayload = [self createCrashDiagnosticPayload];
  467. FIRCLSMockMXDiagnosticPayload *hangPayload = [self createHangDiagnosticPayload];
  468. FIRCLSMockMXDiagnosticPayload *cpuPayload = [self createCPUExceptionDiagnosticPayload];
  469. [self.metricKitManager
  470. didReceiveDiagnosticPayloads:@[ crashPayload, hangPayload, crashPayload, cpuPayload ]];
  471. XCTAssertTrue([self metricKitFileExistsInCurrentReport:NO fatalReport:YES],
  472. "MetricKit fatal report should exist");
  473. XCTAssertTrue([self metricKitFileExistsInCurrentReport:YES fatalReport:NO],
  474. "MetricKit nonfatal report should exist");
  475. NSDictionary *fatalFileDictionary = [self contentsOfMetricKitFileAsDictionary:NO fatalReport:YES];
  476. NSDictionary *fileDictionary = [self contentsOfMetricKitFileAsDictionary:YES fatalReport:NO];
  477. XCTAssertNotNil(fileDictionary, "MetricKit nonfatal file should not be empty");
  478. XCTAssertNotNil(fatalFileDictionary, "MetricKit fatal file should not be empty");
  479. XCTAssertNil([fatalFileDictionary objectForKey:@"hang_event"]);
  480. XCTAssertNil([fatalFileDictionary objectForKey:@"cpu_exception_event"]);
  481. XCTAssertNil([fatalFileDictionary objectForKey:@"disk_write_exception_event"]);
  482. XCTAssertNil([fileDictionary objectForKey:@"crash_event"]);
  483. XCTAssertNil([fileDictionary objectForKey:@"time"]);
  484. NSDictionary *hangDictionary =
  485. [[fileDictionary objectForKey:@"hang_event"] objectForKey:@"exception"];
  486. NSDictionary *cpuDictionary =
  487. [[fileDictionary objectForKey:@"cpu_exception_event"] objectForKey:@"exception"];
  488. NSDictionary *crashDictionary =
  489. [[fatalFileDictionary objectForKey:@"crash_event"] objectForKey:@"metric_kit_fatal"];
  490. XCTAssertNotNil(hangDictionary, "MetricKit event should include a hang diagnostic");
  491. XCTAssertNotNil(cpuDictionary, "MetricKit event should include a CPU exception diagnostic");
  492. XCTAssertNotNil(crashDictionary, "MetricKit event should include a crash diagnostic");
  493. }
  494. @end
  495. #endif