FIRCLSLoggingTests.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. // Copyright 2019 Google
  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. #include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
  17. #include "Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h"
  18. #include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
  19. #import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
  20. @interface FIRCLSLoggingTests : XCTestCase
  21. @property(nonatomic, strong) NSString* kvPath;
  22. @property(nonatomic, strong) NSString* compactedKVPath;
  23. @property(nonatomic, strong) NSString* logAPath;
  24. @property(nonatomic, strong) NSString* logBPath;
  25. @property(nonatomic, strong) NSString* errorAPath;
  26. @property(nonatomic, strong) NSString* errorBPath;
  27. @end
  28. @implementation FIRCLSLoggingTests
  29. - (void)setUp {
  30. [super setUp];
  31. FIRCLSContextBaseInit();
  32. NSString* tempDir = NSTemporaryDirectory();
  33. self.kvPath = [tempDir stringByAppendingPathComponent:@"kv.clsrecord"];
  34. self.compactedKVPath = [tempDir stringByAppendingPathComponent:@"compacted_kv.clsrecord"];
  35. self.logAPath = [tempDir stringByAppendingPathComponent:@"loga.clsrecord"];
  36. self.logBPath = [tempDir stringByAppendingPathComponent:@"logb.clsrecord"];
  37. self.errorAPath = [tempDir stringByAppendingPathComponent:FIRCLSReportErrorAFile];
  38. self.errorBPath = [tempDir stringByAppendingPathComponent:FIRCLSReportErrorBFile];
  39. _firclsContext.readonly->logging.userKVStorage.incrementalPath =
  40. strdup([self.kvPath fileSystemRepresentation]);
  41. _firclsContext.readonly->logging.userKVStorage.compactedPath =
  42. strdup([self.compactedKVPath fileSystemRepresentation]);
  43. _firclsContext.readonly->logging.logStorage.aPath =
  44. strdup([self.logAPath fileSystemRepresentation]);
  45. _firclsContext.readonly->logging.logStorage.bPath =
  46. strdup([self.logBPath fileSystemRepresentation]);
  47. _firclsContext.readonly->logging.errorStorage.aPath =
  48. strdup([self.errorAPath fileSystemRepresentation]);
  49. _firclsContext.readonly->logging.errorStorage.bPath =
  50. strdup([self.errorBPath fileSystemRepresentation]);
  51. _firclsContext.readonly->logging.userKVStorage.maxIncrementalCount =
  52. FIRCLSUserLoggingMaxKVEntries;
  53. _firclsContext.readonly->logging.logStorage.maxSize = 64 * 1024;
  54. _firclsContext.readonly->logging.logStorage.restrictBySize = true;
  55. _firclsContext.readonly->logging.errorStorage.maxSize = 64 * 1024;
  56. _firclsContext.readonly->logging.errorStorage.restrictBySize = false;
  57. _firclsContext.readonly->logging.errorStorage.maxEntries = 8;
  58. _firclsContext.readonly->logging.errorStorage.entryCount =
  59. &_firclsContext.writable->logging.errorsCount;
  60. _firclsContext.readonly->logging.userKVStorage.maxCount = 64;
  61. _firclsContext.writable->logging.activeUserLogPath =
  62. _firclsContext.readonly->logging.logStorage.aPath;
  63. _firclsContext.writable->logging.activeErrorLogPath =
  64. _firclsContext.readonly->logging.errorStorage.aPath;
  65. _firclsContext.writable->logging.userKVCount = 0;
  66. _firclsContext.writable->logging.internalKVCount = 0;
  67. _firclsContext.writable->logging.errorsCount = 0;
  68. _firclsContext.readonly->initialized = true;
  69. for (NSString* path in @[
  70. self.kvPath, self.compactedKVPath, self.logAPath, self.logBPath, self.errorAPath,
  71. self.errorBPath
  72. ]) {
  73. [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
  74. }
  75. }
  76. - (void)tearDown {
  77. free((void*)_firclsContext.readonly->logging.userKVStorage.incrementalPath);
  78. free((void*)_firclsContext.readonly->logging.userKVStorage.compactedPath);
  79. free((void*)_firclsContext.readonly->logging.logStorage.aPath);
  80. free((void*)_firclsContext.readonly->logging.logStorage.bPath);
  81. free((void*)_firclsContext.readonly->logging.errorStorage.aPath);
  82. free((void*)_firclsContext.readonly->logging.errorStorage.bPath);
  83. FIRCLSContextBaseDeinit();
  84. [super tearDown];
  85. }
  86. - (NSArray*)incrementalKeyValues {
  87. return FIRCLSUserLoggingStoredKeyValues(
  88. _firclsContext.readonly->logging.userKVStorage.incrementalPath);
  89. }
  90. - (NSArray*)compactedKeyValues {
  91. return FIRCLSUserLoggingStoredKeyValues(
  92. _firclsContext.readonly->logging.userKVStorage.compactedPath);
  93. }
  94. - (NSArray*)logAContents {
  95. return FIRCLSFileReadSections([self.logAPath fileSystemRepresentation], true, nil);
  96. }
  97. - (NSArray*)logBContents {
  98. return FIRCLSFileReadSections([self.logBPath fileSystemRepresentation], true, nil);
  99. }
  100. - (NSArray*)errorAContents {
  101. return FIRCLSFileReadSections([self.errorAPath fileSystemRepresentation], true, nil);
  102. }
  103. - (NSArray*)errorBContents {
  104. return FIRCLSFileReadSections([self.errorBPath fileSystemRepresentation], true, nil);
  105. }
  106. - (void)testKeyValueWithNilKey {
  107. FIRCLSUserLoggingRecordUserKeyValue(nil, @"some string value");
  108. XCTAssertEqual([self incrementalKeyValues].count, 0, @"");
  109. }
  110. - (void)testKeyValueWithNilValue {
  111. FIRCLSUserLoggingRecordUserKeyValue(@"mykey", nil);
  112. XCTAssertEqual([[self incrementalKeyValues] count], 1, @"");
  113. }
  114. - (void)testKeyValueWithNilValueCompaction {
  115. for (int i = 0; i < FIRCLSUserLoggingMaxKVEntries - 1; i++) {
  116. FIRCLSUserLoggingRecordUserKeyValue(@"mykey", [NSString stringWithFormat:@"myvalue%i", i]);
  117. }
  118. FIRCLSUserLoggingRecordUserKeyValue(@"mykey", nil);
  119. XCTAssertEqual([[self compactedKeyValues] count], 0,
  120. @"Key with last value of nil was not removed in compaction.");
  121. }
  122. - (void)testKeyValueWithNilKeyAndValue {
  123. FIRCLSUserLoggingRecordUserKeyValue(nil, nil);
  124. XCTAssertEqual([[self incrementalKeyValues] count], 0, @"");
  125. }
  126. - (void)testKeyValueLog {
  127. FIRCLSUserLoggingRecordUserKeyValue(@"mykey", @"some string value");
  128. NSArray* keyValues = [self incrementalKeyValues];
  129. XCTAssertEqual([keyValues count], 1, @"");
  130. XCTAssertEqualObjects(keyValues[0][@"key"], @"6d796b6579", @"");
  131. XCTAssertEqualObjects(keyValues[0][@"value"], @"736f6d6520737472696e672076616c7565", @"");
  132. }
  133. - (void)testKeyValueLogSingleKeyCompaction {
  134. for (NSUInteger i = 0; i < FIRCLSUserLoggingMaxKVEntries; ++i) {
  135. FIRCLSUserLoggingRecordUserKeyValue(
  136. @"mykey", [NSString stringWithFormat:@"some string value: %lu", (unsigned long)i]);
  137. }
  138. // we now need to wait for compaction to complete
  139. dispatch_sync(FIRCLSGetLoggingQueue(), ^{
  140. NSLog(@"queue emptied");
  141. });
  142. NSArray* keyValues = [self incrementalKeyValues];
  143. NSArray* compactedKeyValues = [self compactedKeyValues];
  144. XCTAssertEqual([keyValues count], 0, @"");
  145. XCTAssertEqual([compactedKeyValues count], 1, @"");
  146. XCTAssertEqualObjects(compactedKeyValues[0][@"key"], @"6d796b6579", @"");
  147. // the final value of this key should be "some string value: 63"
  148. XCTAssertEqualObjects(compactedKeyValues[0][@"value"],
  149. @"736f6d6520737472696e672076616c75653a203633", @"");
  150. }
  151. - (void)testKeyValueLogMoreThanMaxKeys {
  152. // we need to end up with max + 1 keys written
  153. for (NSUInteger i = 0; i <= _firclsContext.readonly->logging.userKVStorage.maxCount + 1; ++i) {
  154. NSString* key = [NSString stringWithFormat:@"key%lu", (unsigned long)i];
  155. NSString* value = [NSString stringWithFormat:@"some string value: %lu", (unsigned long)i];
  156. FIRCLSUserLoggingRecordUserKeyValue(key, value);
  157. }
  158. // Do a full compaction here. This does two things. First, it makes sure
  159. // we don't have any incremental keys. It also accounts for differences between
  160. // the max and incremental values.
  161. dispatch_sync(FIRCLSGetLoggingQueue(), ^{
  162. FIRCLSUserLoggingCompactKVEntries(&_firclsContext.readonly->logging.userKVStorage);
  163. });
  164. NSArray* keyValues = [self incrementalKeyValues];
  165. NSArray* compactedKeyValues = [self compactedKeyValues];
  166. XCTAssertEqual([keyValues count], 0, @"");
  167. XCTAssertEqual([compactedKeyValues count], 64, @"");
  168. }
  169. - (void)testEmptyKeysAndValues {
  170. FIRCLSUserLoggingRecordUserKeysAndValues(@{});
  171. XCTAssertEqual([self incrementalKeyValues].count, 0, @"");
  172. }
  173. - (void)testKeysAndValuesWithNilValue {
  174. FIRCLSUserLoggingRecordUserKeysAndValues(@{@"mykey" : [NSNull null]});
  175. XCTAssertEqual([[self incrementalKeyValues] count], 1, @"");
  176. }
  177. - (void)testKeysAndValuesLog {
  178. NSDictionary* keysAndValues =
  179. @{@"mykey" : @"some string value", @"mykey2" : @"some string value 2"};
  180. FIRCLSUserLoggingRecordUserKeysAndValues(keysAndValues);
  181. NSArray* keyValues = [self incrementalKeyValues];
  182. XCTAssertEqual([keyValues count], 2, @"");
  183. XCTAssertEqualObjects(keyValues[0][@"key"], @"6d796b6579", @"");
  184. XCTAssertEqualObjects(keyValues[0][@"value"], @"736f6d6520737472696e672076616c7565", @"");
  185. XCTAssertEqualObjects(keyValues[1][@"key"], @"6d796b657932", @"");
  186. XCTAssertEqualObjects(keyValues[1][@"value"], @"736f6d6520737472696e672076616c75652032", @"");
  187. }
  188. - (void)testKeysAndValuesLogKeyCompaction {
  189. for (NSUInteger i = 0; i < FIRCLSUserLoggingMaxKVEntries; ++i) {
  190. NSString* value = [NSString stringWithFormat:@"some string value: %lu", (unsigned long)i];
  191. FIRCLSUserLoggingRecordUserKeysAndValues(@{@"mykey" : value});
  192. }
  193. // we now need to wait for compaction to complete
  194. dispatch_sync(FIRCLSGetLoggingQueue(), ^{
  195. NSLog(@"queue emptied");
  196. });
  197. NSArray* keyValues = [self incrementalKeyValues];
  198. NSArray* compactedKeyValues = [self compactedKeyValues];
  199. XCTAssertEqual([keyValues count], 0, @"");
  200. XCTAssertEqual([compactedKeyValues count], 1, @"");
  201. XCTAssertEqualObjects(compactedKeyValues[0][@"key"], @"6d796b6579", @"");
  202. // the final value of this key should be "some string value: 63"
  203. XCTAssertEqualObjects(compactedKeyValues[0][@"value"],
  204. @"736f6d6520737472696e672076616c75653a203633", @"");
  205. }
  206. - (void)testKeysAndValuesLogMoreThanMaxKeys {
  207. NSUInteger keysAndValuesCount = _firclsContext.readonly->logging.userKVStorage.maxCount + 1;
  208. NSMutableDictionary* keysAndValuesToBeCompactedIn = [NSMutableDictionary dictionary];
  209. // we need to end up with max + 1 keys written
  210. for (NSUInteger i = 0; i <= keysAndValuesCount; ++i) {
  211. NSString* key = [NSString stringWithFormat:@"key%lu", (unsigned long)i];
  212. NSString* value = [NSString stringWithFormat:@"some string value: %lu", (unsigned long)i];
  213. if (i == 0) {
  214. FIRCLSUserLoggingRecordUserKeyValue(key, value);
  215. } else {
  216. keysAndValuesToBeCompactedIn[key] = value;
  217. }
  218. }
  219. FIRCLSUserLoggingRecordUserKeysAndValues(keysAndValuesToBeCompactedIn);
  220. // Do a full compaction here. This does two things. First, it makes sure
  221. // we don't have any incremental keys. It also accounts for differences between
  222. // the max and incremental values.
  223. dispatch_sync(FIRCLSGetLoggingQueue(), ^{
  224. FIRCLSUserLoggingCompactKVEntries(&_firclsContext.readonly->logging.userKVStorage);
  225. });
  226. NSArray* keyValues = [self incrementalKeyValues];
  227. NSArray* compactedKeyValues = [self compactedKeyValues];
  228. XCTAssertEqual([keyValues count], 0, @"");
  229. XCTAssertEqual([compactedKeyValues count], 64, @"");
  230. }
  231. - (void)testUserLogNil {
  232. #pragma clang diagnostic push
  233. #pragma clang diagnostic ignored "-Wnonnull"
  234. FIRCLSLog(nil);
  235. #pragma clang diagnostic pop
  236. XCTAssertEqual([[self logAContents] count], 0, @"");
  237. }
  238. - (void)testLargeLogLine {
  239. size_t strLength = 100 * 1024; // Attempt to write 100k of data
  240. char* longLine = calloc(1, strLength + 1);
  241. memset(longLine, 'a', strLength);
  242. longLine[strLength] = '\0';
  243. NSString* longStr = [[NSString alloc] initWithBytesNoCopy:longLine
  244. length:strLength
  245. encoding:NSUTF8StringEncoding
  246. freeWhenDone:YES];
  247. FIRCLSLog(@"%@", longStr);
  248. NSArray* array = [self logAContents];
  249. NSString* message = array[0][@"log"][@"msg"];
  250. XCTAssertEqual(message.length, _firclsContext.readonly->logging.logStorage.maxSize * 2,
  251. "message: \"%@\"", message);
  252. }
  253. - (void)testUserLog {
  254. FIRCLSLog(@"some value");
  255. NSArray* array = [self logAContents];
  256. XCTAssertEqual([array count], 1, @"");
  257. XCTAssertEqualObjects(array[0][@"log"][@"msg"], @"736f6d652076616c7565", @"");
  258. }
  259. - (void)testUserLogRotation {
  260. // tune this carefully, based on max file size
  261. for (int i = 0; i < 969; ++i) {
  262. FIRCLSLog(@"some value %d", i);
  263. }
  264. NSArray* logA = [self logAContents];
  265. NSArray* logB = [self logBContents];
  266. XCTAssertEqual([logA count], 968, @"");
  267. XCTAssertEqual([logB count], 1, @"");
  268. }
  269. - (void)testUserLogRotationBackToBeginning {
  270. // careful tuning needs to be done to make sure there's exactly one entry
  271. for (int i = 0; i < 1907; ++i) {
  272. FIRCLSLog(@"some value %d", i);
  273. }
  274. NSArray* logA = [self logAContents];
  275. NSArray* logB = [self logBContents];
  276. XCTAssertEqual([logA count], 1, @"");
  277. XCTAssertEqual([logB count], 938, @"");
  278. // We need to verify that things have rolled correctly. This means log A should now have the
  279. // very last value, and log b should have the second-to-last.
  280. XCTAssertEqualObjects(logA[0][@"log"][@"msg"], @"736f6d652076616c75652031393036",
  281. @""); // "some value 1906"
  282. XCTAssertEqualObjects(logB[937][@"log"][@"msg"], @"736f6d652076616c75652031393035",
  283. @""); // "some value 1905"
  284. }
  285. - (void)testLoggedError {
  286. NSError* error = [NSError errorWithDomain:@"My Custom Domain"
  287. code:-1
  288. userInfo:@{@"key1" : @"value", @"key2" : @"value2"}];
  289. FIRCLSUserLoggingRecordError(error, @{@"additional" : @"key"}, nil);
  290. NSArray* errors = [self errorAContents];
  291. XCTAssertEqual([errors count], 1, @"");
  292. NSDictionary* errorDict = errors[0][@"error"];
  293. XCTAssertNotNil(errorDict, @"");
  294. XCTAssert([errorDict[@"stacktrace"] count] > 5, @"should have at least a few frames");
  295. XCTAssertEqualObjects(errorDict[@"domain"], @"4d7920437573746f6d20446f6d61696e", @"");
  296. XCTAssertEqual([errorDict[@"code"] integerValue], -1, @"");
  297. // this requires a sort to be non-flakey
  298. NSArray* userInfoEntries =
  299. [errorDict[@"info"] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
  300. return [obj1[0] compare:obj2[0]];
  301. }];
  302. NSArray* entryOne = @[ @"6b657931", @"76616c7565" ];
  303. NSArray* entryTwo = @[ @"6b657932", @"76616c756532" ];
  304. XCTAssertEqual([userInfoEntries count], 2, @"");
  305. XCTAssertEqualObjects(userInfoEntries[0], entryOne, @"");
  306. XCTAssertEqualObjects(userInfoEntries[1], entryTwo, @"");
  307. NSArray* additionalEntries = errorDict[@"extra_info"];
  308. entryOne = @[ @"6164646974696f6e616c", @"6b6579" ];
  309. XCTAssertEqual([additionalEntries count], 1, @"");
  310. XCTAssertEqualObjects(additionalEntries[0], entryOne, @"");
  311. }
  312. - (void)testWritingMaximumNumberOfLoggedErrors {
  313. NSError* error = [NSError errorWithDomain:@"My Custom Domain"
  314. code:-1
  315. userInfo:@{@"key1" : @"value", @"key2" : @"value2"}];
  316. for (size_t i = 0; i < _firclsContext.readonly->logging.errorStorage.maxEntries; ++i) {
  317. FIRCLSUserLoggingRecordError(error, nil, nil);
  318. }
  319. NSArray* errors = [self errorAContents];
  320. XCTAssertEqual([errors count], 8, @"");
  321. // at this point, if we log one more, we should expect a roll over to the next file
  322. FIRCLSUserLoggingRecordError(error, nil, nil);
  323. XCTAssertEqual([[self errorAContents] count], 8, @"");
  324. XCTAssertEqual([[self errorBContents] count], 1, @"");
  325. XCTAssertEqual(*_firclsContext.readonly->logging.errorStorage.entryCount, 1);
  326. // and our next entry should continue into the B file
  327. FIRCLSUserLoggingRecordError(error, nil, nil);
  328. XCTAssertEqual([[self errorAContents] count], 8, @"");
  329. XCTAssertEqual([[self errorBContents] count], 2, @"");
  330. XCTAssertEqual(*_firclsContext.readonly->logging.errorStorage.entryCount, 2);
  331. }
  332. - (void)testLoggedErrorWithNullsInAdditionalInfo {
  333. NSError* error = [NSError errorWithDomain:@"Domain" code:-1 userInfo:nil];
  334. FIRCLSUserLoggingRecordError(error, @{@"null-key" : [NSNull null]}, nil);
  335. NSArray* errors = [self errorAContents];
  336. XCTAssertEqual([errors count], 1, @"");
  337. NSDictionary* errorDict = errors[0][@"error"];
  338. XCTAssertNotNil(errorDict, @"");
  339. XCTAssert([errorDict[@"stacktrace"] count] > 5, @"should have at least a few frames");
  340. XCTAssertEqualObjects(errorDict[@"domain"], @"446f6d61696e", @"");
  341. XCTAssertEqual([errorDict[@"code"] integerValue], -1, @"");
  342. XCTAssertEqual([errorDict[@"info"] count], 0, @"");
  343. NSArray* additionalEntries = errorDict[@"extra_info"];
  344. NSArray* entryOne = @[ @"6e756c6c2d6b6579", @"3c6e756c6c3e" ];
  345. XCTAssertEqual([additionalEntries count], 1, @"");
  346. XCTAssertEqualObjects(additionalEntries[0], entryOne, @"");
  347. }
  348. @end