FIRCLSLoggingTests.m 13 KB

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