FIRCLSMetricKitManagerTests.m 30 KB

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