GDTCORFlatFileStorageTest.m 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196
  1. /*
  2. * Copyright 2018 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "GoogleDataTransport/GDTCORTests/Unit/GDTCORTestCase.h"
  17. #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage.h"
  18. #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
  19. #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
  20. #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORPlatform.h"
  21. #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORRegistrar.h"
  22. #import "GoogleDataTransport/GDTCORTests/Unit/Helpers/GDTCORAssertHelper.h"
  23. #import "GoogleDataTransport/GDTCORTests/Unit/Helpers/GDTCORDataObjectTesterClasses.h"
  24. #import "GoogleDataTransport/GDTCORTests/Unit/Helpers/GDTCOREventGenerator.h"
  25. #import "GoogleDataTransport/GDTCORTests/Unit/Helpers/GDTCORTestUploader.h"
  26. #import "GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORUploadCoordinatorFake.h"
  27. #import "GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORFlatFileStorage+Testing.h"
  28. #import "GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORRegistrar+Testing.h"
  29. /** A category that adds finding a random element to NSSet. NSSet's -anyObject isn't random. */
  30. @interface NSSet (GDTCORRandomElement)
  31. /** Returns a random element of the set.
  32. *
  33. * @return A random element of the set.
  34. */
  35. - (id)randomElement;
  36. @end
  37. @implementation NSSet (GDTCORRandomElement)
  38. - (id)randomElement {
  39. if (self.count) {
  40. NSArray *elements = [self allObjects];
  41. return elements[arc4random_uniform((uint32_t)self.count)];
  42. }
  43. return nil;
  44. }
  45. @end
  46. @interface GDTCORFlatFileStorageTest : GDTCORTestCase
  47. /** The uploader fake. */
  48. @property(nonatomic) GDTCORUploadCoordinatorFake *uploaderFake;
  49. @end
  50. @implementation GDTCORFlatFileStorageTest
  51. - (void)setUp {
  52. [super setUp];
  53. [[GDTCORRegistrar sharedInstance] reset];
  54. [[GDTCORFlatFileStorage sharedInstance] reset];
  55. self.uploaderFake = [[GDTCORUploadCoordinatorFake alloc] init];
  56. [GDTCORFlatFileStorage sharedInstance].uploadCoordinator = self.uploaderFake;
  57. [[GDTCORFlatFileStorage sharedInstance] reset];
  58. [[NSFileManager defaultManager] fileExistsAtPath:[GDTCORFlatFileStorage eventDataStoragePath]];
  59. }
  60. - (void)tearDown {
  61. dispatch_sync([GDTCORFlatFileStorage sharedInstance].storageQueue, ^{
  62. });
  63. // Destroy these objects before the next test begins.
  64. [GDTCORFlatFileStorage sharedInstance].uploadCoordinator =
  65. [GDTCORUploadCoordinator sharedInstance];
  66. self.uploaderFake = nil;
  67. [super tearDown];
  68. }
  69. /** Generates and returns a set of events that are generated randomly and stored.
  70. *
  71. * @return A set of randomly generated and stored events.
  72. */
  73. - (NSSet<GDTCOREvent *> *)generateEventsForStorageTesting {
  74. NSMutableSet<GDTCOREvent *> *generatedEvents = [[NSMutableSet alloc] init];
  75. // Generate 100 test target events
  76. [generatedEvents unionSet:[self generateEventsForTarget:kGDTCORTargetTest
  77. expiringIn:1000
  78. count:100]];
  79. // Generate 50 FLL target events.
  80. [generatedEvents unionSet:[self generateEventsForTarget:kGDTCORTargetFLL
  81. expiringIn:1000
  82. count:50]];
  83. return generatedEvents;
  84. }
  85. - (NSSet<GDTCOREvent *> *)generateEventsForTarget:(GDTCORTarget)target
  86. expiringIn:(NSTimeInterval)eventsExpireIn
  87. count:(NSInteger)count {
  88. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  89. NSMutableSet<GDTCOREvent *> *generatedEvents = [[NSMutableSet alloc] init];
  90. XCTestExpectation *generatedEventsStoredExpectation =
  91. [self expectationWithDescription:@"generatedEventsStoredExpectation"];
  92. generatedEventsStoredExpectation.expectedFulfillmentCount = count;
  93. for (int i = 0; i < count; i++) {
  94. GDTCOREvent *event = [GDTCOREventGenerator generateEventForTarget:target
  95. qosTier:nil
  96. mappingID:nil];
  97. event.expirationDate = [NSDate dateWithTimeIntervalSinceNow:eventsExpireIn];
  98. [generatedEvents addObject:event];
  99. [storage storeEvent:event
  100. onComplete:^(BOOL wasWritten, NSError *_Nullable error) {
  101. XCTAssertTrue(wasWritten);
  102. XCTAssertNil(error);
  103. [generatedEventsStoredExpectation fulfill];
  104. }];
  105. }
  106. [self waitForExpectations:@[ generatedEventsStoredExpectation ] timeout:0.2 * count];
  107. return generatedEvents;
  108. }
  109. /** Tests the singleton pattern. */
  110. - (void)testInit {
  111. XCTAssertEqual([GDTCORFlatFileStorage sharedInstance], [GDTCORFlatFileStorage sharedInstance]);
  112. }
  113. /** Tests storing an event. */
  114. - (void)testStoreEvent {
  115. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  116. GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"404" target:kGDTCORTargetTest];
  117. event.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"testString"];
  118. event.clockSnapshot = [GDTCORClock snapshot];
  119. XCTestExpectation *expectation = [self expectationWithDescription:@"hasEvents completion called"];
  120. [storage hasEventsForTarget:kGDTCORTargetTest
  121. onComplete:^(BOOL hasEvents) {
  122. XCTAssertFalse(hasEvents);
  123. [expectation fulfill];
  124. }];
  125. [self waitForExpectations:@[ expectation ] timeout:10];
  126. XCTestExpectation *writtenExpectation = [self expectationWithDescription:@"event written"];
  127. XCTAssertNoThrow([storage storeEvent:event
  128. onComplete:^(BOOL wasWritten, NSError *_Nullable error) {
  129. XCTAssertTrue(wasWritten);
  130. XCTAssertNotEqualObjects(event.eventID, @0);
  131. XCTAssertNil(error);
  132. [writtenExpectation fulfill];
  133. }]);
  134. [self waitForExpectations:@[ writtenExpectation ] timeout:10.0];
  135. expectation = [self expectationWithDescription:@"hasEvents completion called"];
  136. [storage hasEventsForTarget:kGDTCORTargetTest
  137. onComplete:^(BOOL hasEvents) {
  138. XCTAssertTrue(hasEvents);
  139. [expectation fulfill];
  140. }];
  141. [self waitForExpectations:@[ expectation ] timeout:10];
  142. GDTCORStorageEventSelector *eventSelector =
  143. [GDTCORStorageEventSelector eventSelectorForTarget:kGDTCORTargetTest];
  144. expectation = [self expectationWithDescription:@"batch fetched"];
  145. [storage batchWithEventSelector:eventSelector
  146. batchExpiration:[NSDate dateWithTimeIntervalSinceNow:60]
  147. onComplete:^(NSNumber *_Nullable batchID,
  148. NSSet<GDTCOREvent *> *_Nullable events) {
  149. XCTAssertEqual(events.count, 1);
  150. XCTAssertEqualObjects(event.eventID, [events anyObject].eventID);
  151. [expectation fulfill];
  152. }];
  153. [self waitForExpectations:@[ expectation ] timeout:10];
  154. }
  155. /** Tests storing an event whose mappingID contains path components. */
  156. - (void)testStoreEventWithPathComponentsInMappingID {
  157. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  158. GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"this/messes/up/things"
  159. target:kGDTCORTargetTest];
  160. event.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"testString"];
  161. event.clockSnapshot = [GDTCORClock snapshot];
  162. XCTestExpectation *expectation = [self expectationWithDescription:@"hasEvents completion called"];
  163. [storage hasEventsForTarget:kGDTCORTargetTest
  164. onComplete:^(BOOL hasEvents) {
  165. XCTAssertFalse(hasEvents);
  166. [expectation fulfill];
  167. }];
  168. [self waitForExpectations:@[ expectation ] timeout:10];
  169. XCTestExpectation *writtenExpectation = [self expectationWithDescription:@"event written"];
  170. XCTAssertNoThrow([storage storeEvent:event
  171. onComplete:^(BOOL wasWritten, NSError *_Nullable error) {
  172. XCTAssertTrue(wasWritten);
  173. XCTAssertNotEqualObjects(event.eventID, @0);
  174. XCTAssertNil(error);
  175. [writtenExpectation fulfill];
  176. }]);
  177. [self waitForExpectations:@[ writtenExpectation ] timeout:10.0];
  178. expectation = [self expectationWithDescription:@"hasEvents completion called"];
  179. [storage hasEventsForTarget:kGDTCORTargetTest
  180. onComplete:^(BOOL hasEvents) {
  181. XCTAssertTrue(hasEvents);
  182. [expectation fulfill];
  183. }];
  184. [self waitForExpectations:@[ expectation ] timeout:10];
  185. GDTCORStorageEventSelector *eventSelector =
  186. [GDTCORStorageEventSelector eventSelectorForTarget:kGDTCORTargetTest];
  187. expectation = [self expectationWithDescription:@"batch fetched"];
  188. [storage batchWithEventSelector:eventSelector
  189. batchExpiration:[NSDate dateWithTimeIntervalSinceNow:60]
  190. onComplete:^(NSNumber *_Nullable batchID,
  191. NSSet<GDTCOREvent *> *_Nullable events) {
  192. XCTAssertEqual(events.count, 1);
  193. XCTAssertEqualObjects(event.eventID, [events anyObject].eventID);
  194. [expectation fulfill];
  195. }];
  196. [self waitForExpectations:@[ expectation ] timeout:10];
  197. }
  198. /** Tests storing a few different events. */
  199. - (void)testStoreMultipleEvents {
  200. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  201. GDTCOREvent *event1 = [[GDTCOREvent alloc] initWithMappingID:@"404" target:kGDTCORTargetTest];
  202. event1.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"testString1"];
  203. XCTestExpectation *writtenExpectation = [self expectationWithDescription:@"event written"];
  204. XCTAssertNoThrow([storage storeEvent:event1
  205. onComplete:^(BOOL wasWritten, NSError *_Nullable error) {
  206. XCTAssertNotEqualObjects(event1.eventID, @0);
  207. XCTAssertNil(error);
  208. [writtenExpectation fulfill];
  209. }]);
  210. [self waitForExpectations:@[ writtenExpectation ] timeout:10.0];
  211. GDTCOREvent *event2 = [[GDTCOREvent alloc] initWithMappingID:@"100" target:kGDTCORTargetTest];
  212. event2.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"testString2"];
  213. writtenExpectation = [self expectationWithDescription:@"event written"];
  214. XCTAssertNoThrow([storage storeEvent:event2
  215. onComplete:^(BOOL wasWritten, NSError *_Nullable error) {
  216. XCTAssertNotEqualObjects(event2.eventID, @0);
  217. XCTAssertNil(error);
  218. [writtenExpectation fulfill];
  219. }]);
  220. [self waitForExpectations:@[ writtenExpectation ] timeout:10.0];
  221. GDTCOREvent *event3 = [[GDTCOREvent alloc] initWithMappingID:@"404" target:kGDTCORTargetTest];
  222. event3.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"testString3"];
  223. writtenExpectation = [self expectationWithDescription:@"event written"];
  224. XCTAssertNoThrow([storage storeEvent:event3
  225. onComplete:^(BOOL wasWritten, NSError *_Nullable error) {
  226. XCTAssertNotEqualObjects(event3.eventID, @0);
  227. XCTAssertNil(error);
  228. [writtenExpectation fulfill];
  229. }]);
  230. [self waitForExpectations:@[ writtenExpectation ] timeout:10.0];
  231. XCTestExpectation *expectation = [self expectationWithDescription:@"batch created"];
  232. GDTCORStorageEventSelector *eventSelector =
  233. [GDTCORStorageEventSelector eventSelectorForTarget:kGDTCORTargetTest];
  234. [storage batchWithEventSelector:eventSelector
  235. batchExpiration:[NSDate dateWithTimeIntervalSinceNow:60]
  236. onComplete:^(NSNumber *_Nullable batchID,
  237. NSSet<GDTCOREvent *> *_Nullable events) {
  238. XCTAssertEqual(events.count, 3);
  239. [expectation fulfill];
  240. }];
  241. [self waitForExpectations:@[ expectation ] timeout:10];
  242. }
  243. /** Tests sending a fast priority event causes an upload attempt. */
  244. - (void)testQoSTierFast {
  245. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  246. GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"404" target:kGDTCORTargetTest];
  247. event.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"testString"];
  248. event.qosTier = GDTCOREventQoSFast;
  249. event.clockSnapshot = [GDTCORClock snapshot];
  250. XCTAssertFalse(self.uploaderFake.forceUploadCalled);
  251. XCTestExpectation *writtenExpectation = [self expectationWithDescription:@"event written"];
  252. XCTAssertNoThrow([storage storeEvent:event
  253. onComplete:^(BOOL wasWritten, NSError *error) {
  254. XCTAssertNotEqualObjects(event.eventID, @0);
  255. XCTAssertNil(error);
  256. [writtenExpectation fulfill];
  257. }]);
  258. [self waitForExpectations:@[ writtenExpectation ] timeout:10.0];
  259. dispatch_sync(storage.storageQueue, ^{
  260. XCTAssertTrue(self.uploaderFake.forceUploadCalled);
  261. });
  262. }
  263. /** Fuzz tests the storing of events at the same time as a terminate lifecycle notification. This
  264. * test can fail if there's simultaneous access to ivars of GDTCORFlatFileStorage with one access
  265. * being off the storage's queue. The terminate lifecycle event should operate on and flush the
  266. * queue.
  267. */
  268. - (void)testStoringEventsDuringTerminate {
  269. BOOL originalValueOfContinueAfterFailure = self.continueAfterFailure;
  270. self.continueAfterFailure = NO;
  271. int numberOfIterations = 1000;
  272. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  273. for (int i = 0; i < numberOfIterations; i++) {
  274. NSString *testString = [NSString stringWithFormat:@"testString %d", i];
  275. GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"404" target:kGDTCORTargetTest];
  276. event.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:testString];
  277. event.clockSnapshot = [GDTCORClock snapshot];
  278. XCTestExpectation *writtenExpectation = [self expectationWithDescription:@"event written"];
  279. XCTAssertNoThrow([storage storeEvent:event
  280. onComplete:^(BOOL wasWritten, NSError *error) {
  281. XCTAssertNotEqualObjects(event.eventID, @0);
  282. [writtenExpectation fulfill];
  283. }]);
  284. [self waitForExpectationsWithTimeout:10 handler:nil];
  285. if (i % 5 == 0) {
  286. GDTCORStorageEventSelector *eventSelector =
  287. [GDTCORStorageEventSelector eventSelectorForTarget:kGDTCORTargetTest];
  288. [storage batchWithEventSelector:eventSelector
  289. batchExpiration:[NSDate dateWithTimeIntervalSinceNow:60]
  290. onComplete:^(NSNumber *_Nullable batchID,
  291. NSSet<GDTCOREvent *> *_Nullable events) {
  292. [storage removeBatchWithID:batchID deleteEvents:YES onComplete:nil];
  293. }];
  294. }
  295. [NSNotificationCenter.defaultCenter
  296. postNotificationName:kGDTCORApplicationWillTerminateNotification
  297. object:nil];
  298. }
  299. self.continueAfterFailure = originalValueOfContinueAfterFailure;
  300. }
  301. - (void)testSaveAndLoadLibraryData {
  302. __weak NSData *weakData;
  303. NSString *dataKey = NSStringFromSelector(_cmd);
  304. @autoreleasepool {
  305. NSData *data = [@"test data" dataUsingEncoding:NSUTF8StringEncoding];
  306. weakData = data;
  307. XCTestExpectation *expectation = [self expectationWithDescription:@"storage completion called"];
  308. [[GDTCORFlatFileStorage sharedInstance] storeLibraryData:data
  309. forKey:dataKey
  310. onComplete:^(NSError *_Nullable error) {
  311. XCTAssertNil(error);
  312. [expectation fulfill];
  313. }];
  314. [self waitForExpectations:@[ expectation ] timeout:10.0];
  315. }
  316. XCTAssertNil(weakData);
  317. XCTestExpectation *expectation = [self expectationWithDescription:@"retrieval completion called"];
  318. [[GDTCORFlatFileStorage sharedInstance]
  319. libraryDataForKey:dataKey
  320. onFetchComplete:^(NSData *_Nullable data, NSError *_Nullable error) {
  321. [expectation fulfill];
  322. XCTAssertNil(error);
  323. XCTAssertEqualObjects(@"test data", [[NSString alloc] initWithData:data
  324. encoding:NSUTF8StringEncoding]);
  325. }
  326. setNewValue:nil];
  327. [self waitForExpectations:@[ expectation ] timeout:10.0];
  328. }
  329. - (void)testSavingNilLibraryData {
  330. XCTestExpectation *expectation = [self expectationWithDescription:@"storage completion called"];
  331. [[GDTCORFlatFileStorage sharedInstance] storeLibraryData:[NSData data]
  332. forKey:@"test data key"
  333. onComplete:^(NSError *_Nullable error) {
  334. XCTAssertNotNil(error);
  335. [expectation fulfill];
  336. }];
  337. [self waitForExpectations:@[ expectation ] timeout:10.0];
  338. }
  339. - (void)testSaveAndRemoveLibraryData {
  340. NSString *dataKey = NSStringFromSelector(_cmd);
  341. NSData *data = [@"test data" dataUsingEncoding:NSUTF8StringEncoding];
  342. XCTestExpectation *expectation = [self expectationWithDescription:@"storage completion called"];
  343. [[GDTCORFlatFileStorage sharedInstance] storeLibraryData:data
  344. forKey:dataKey
  345. onComplete:^(NSError *_Nullable error) {
  346. XCTAssertNil(error);
  347. [expectation fulfill];
  348. }];
  349. [self waitForExpectations:@[ expectation ] timeout:10.0];
  350. expectation = [self expectationWithDescription:@"retrieval completion called"];
  351. [[GDTCORFlatFileStorage sharedInstance] libraryDataForKey:dataKey
  352. onFetchComplete:^(NSData *_Nullable data, NSError *_Nullable error) {
  353. XCTAssertNil(error);
  354. XCTAssertEqualObjects(@"test data", [[NSString alloc] initWithData:data
  355. encoding:NSUTF8StringEncoding]);
  356. [expectation fulfill];
  357. }
  358. setNewValue:^NSData *_Nullable {
  359. return nil;
  360. }];
  361. [self waitForExpectations:@[ expectation ] timeout:10.0];
  362. expectation = [self expectationWithDescription:@"removal completion called"];
  363. [[GDTCORFlatFileStorage sharedInstance] removeLibraryDataForKey:dataKey
  364. onComplete:^(NSError *error) {
  365. [expectation fulfill];
  366. XCTAssertNil(error);
  367. }];
  368. [self waitForExpectations:@[ expectation ] timeout:10.0];
  369. expectation = [self expectationWithDescription:@"retrieval completion called"];
  370. [[GDTCORFlatFileStorage sharedInstance]
  371. libraryDataForKey:dataKey
  372. onFetchComplete:^(NSData *_Nullable data, NSError *_Nullable error) {
  373. XCTAssertNotNil(error);
  374. XCTAssertNil(data);
  375. [expectation fulfill];
  376. }
  377. setNewValue:nil];
  378. [self waitForExpectations:@[ expectation ] timeout:10.0];
  379. }
  380. /** Tests -pathForTarget:qosTier:mappingID: searching by target. */
  381. - (void)testSearchingPathsByTarget {
  382. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  383. NSSet<GDTCOREvent *> *generatedEvents = [self generateEventsForStorageTesting];
  384. NSSet<GDTCOREvent *> *expectedEvents = [generatedEvents
  385. filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
  386. GDTCOREvent *_Nullable event,
  387. NSDictionary<NSString *, id> *_Nullable bindings) {
  388. return event.target == kGDTCORTargetTest;
  389. }]];
  390. XCTestExpectation *expectation = [self expectationWithDescription:@"paths found"];
  391. [storage pathsForTarget:kGDTCORTargetTest
  392. eventIDs:nil
  393. qosTiers:nil
  394. mappingIDs:nil
  395. onComplete:^(NSSet<NSString *> *paths) {
  396. XCTAssertEqual(paths.count, expectedEvents.count);
  397. [expectation fulfill];
  398. }];
  399. [self waitForExpectations:@[ expectation ] timeout:1.0];
  400. }
  401. /** Tests -pathForTarget:qosTier:mappingID: searching by eventID. */
  402. - (void)testSearchingPathWithEventID {
  403. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  404. NSSet<GDTCOREvent *> *generatedEvents = [self generateEventsForStorageTesting];
  405. GDTCOREvent *anyEvent = [generatedEvents randomElement];
  406. NSSet<GDTCOREvent *> *expectedEvents = [generatedEvents
  407. filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
  408. GDTCOREvent *_Nullable event,
  409. NSDictionary<NSString *, id> *_Nullable bindings) {
  410. return anyEvent.target == event.target && [event.eventID isEqualToString:anyEvent.eventID];
  411. }]];
  412. XCTestExpectation *expectation = [self expectationWithDescription:@"paths found"];
  413. [storage pathsForTarget:anyEvent.target
  414. eventIDs:[NSSet setWithObject:anyEvent.eventID]
  415. qosTiers:nil
  416. mappingIDs:nil
  417. onComplete:^(NSSet<NSString *> *paths) {
  418. XCTAssertEqual(paths.count, expectedEvents.count);
  419. [expectation fulfill];
  420. }];
  421. [self waitForExpectations:@[ expectation ] timeout:1.0];
  422. GDTCOREvent *anotherEvent;
  423. do {
  424. anotherEvent = [generatedEvents randomElement];
  425. } while (anotherEvent == anyEvent || anotherEvent.target != anyEvent.target);
  426. expectedEvents = [generatedEvents
  427. filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
  428. GDTCOREvent *_Nullable event,
  429. NSDictionary<NSString *, id> *_Nullable bindings) {
  430. return (anyEvent.target == event.target &&
  431. [event.eventID isEqualToString:anyEvent.eventID]) ||
  432. (anotherEvent.target == event.target &&
  433. [event.eventID isEqualToString:anotherEvent.eventID]);
  434. }]];
  435. expectation = [self expectationWithDescription:@"paths found"];
  436. [storage pathsForTarget:anyEvent.target
  437. eventIDs:[NSSet setWithObjects:anyEvent.eventID, anotherEvent.eventID, nil]
  438. qosTiers:nil
  439. mappingIDs:nil
  440. onComplete:^(NSSet<NSString *> *paths) {
  441. XCTAssertEqual(paths.count, expectedEvents.count);
  442. [expectation fulfill];
  443. }];
  444. [self waitForExpectations:@[ expectation ] timeout:1.0];
  445. }
  446. /** Tests -pathForTarget:qosTier:mappingID: searching by qosTier. */
  447. - (void)testSearchingPathWithQoSTier {
  448. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  449. NSSet<GDTCOREvent *> *generatedEvents = [self generateEventsForStorageTesting];
  450. GDTCOREvent *anyEvent = [generatedEvents randomElement];
  451. NSSet<GDTCOREvent *> *expectedEvents = [generatedEvents
  452. filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
  453. GDTCOREvent *_Nullable event,
  454. NSDictionary<NSString *, id> *_Nullable bindings) {
  455. return event.target == anyEvent.target && event.qosTier == anyEvent.qosTier;
  456. }]];
  457. XCTestExpectation *expectation = [self expectationWithDescription:@"paths found"];
  458. [storage pathsForTarget:anyEvent.target
  459. eventIDs:nil
  460. qosTiers:[NSSet setWithObject:@(anyEvent.qosTier)]
  461. mappingIDs:nil
  462. onComplete:^(NSSet<NSString *> *paths) {
  463. XCTAssertEqual(paths.count, expectedEvents.count);
  464. [expectation fulfill];
  465. }];
  466. [self waitForExpectations:@[ expectation ] timeout:1.0];
  467. }
  468. /** Tests -pathForTarget:qosTier:mappingID: searching by mappingID. */
  469. - (void)testSearchingPathWithMappingID {
  470. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  471. NSSet<GDTCOREvent *> *generatedEvents = [self generateEventsForStorageTesting];
  472. GDTCOREvent *anyEvent = [generatedEvents randomElement];
  473. NSSet<GDTCOREvent *> *expectedEvents = [generatedEvents
  474. filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
  475. GDTCOREvent *_Nullable event,
  476. NSDictionary<NSString *, id> *_Nullable bindings) {
  477. return event.target == anyEvent.target &&
  478. [event.mappingID isEqualToString:anyEvent.mappingID];
  479. }]];
  480. XCTestExpectation *expectation = [self expectationWithDescription:@"paths found"];
  481. [storage pathsForTarget:anyEvent.target
  482. eventIDs:nil
  483. qosTiers:nil
  484. mappingIDs:[NSSet setWithObject:anyEvent.mappingID]
  485. onComplete:^(NSSet<NSString *> *paths) {
  486. XCTAssertEqual(paths.count, expectedEvents.count);
  487. [expectation fulfill];
  488. }];
  489. [self waitForExpectations:@[ expectation ] timeout:1.0];
  490. }
  491. /** Tests -pathForTarget:qosTier:mappingID: searching by mappingID that contains path components. */
  492. - (void)testSearchingPathWithMappingIDThatHasPathComponents {
  493. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  494. GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"this/messes/up/things"
  495. target:kGDTCORTargetTest];
  496. event.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"testString"];
  497. event.clockSnapshot = [GDTCORClock snapshot];
  498. [storage storeEvent:event onComplete:nil];
  499. NSSet<GDTCOREvent *> *expectedEvents = [NSSet setWithObject:event];
  500. XCTestExpectation *expectation = [self expectationWithDescription:@"paths found"];
  501. [storage pathsForTarget:event.target
  502. eventIDs:nil
  503. qosTiers:nil
  504. mappingIDs:[NSSet setWithObject:event.mappingID]
  505. onComplete:^(NSSet<NSString *> *paths) {
  506. XCTAssertEqual(paths.count, expectedEvents.count);
  507. [expectation fulfill];
  508. }];
  509. [self waitForExpectations:@[ expectation ] timeout:1.0];
  510. }
  511. /** Tests -pathForTarget:qosTier:mappingID: searching by eventID and qosTier. */
  512. - (void)testSearchingPathWithEventIDAndQoSTier {
  513. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  514. NSSet<GDTCOREvent *> *generatedEvents = [self generateEventsForStorageTesting];
  515. GDTCOREvent *anyEvent = [generatedEvents randomElement];
  516. NSSet<GDTCOREvent *> *expectedEvents = [generatedEvents
  517. filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
  518. GDTCOREvent *_Nullable event,
  519. NSDictionary<NSString *, id> *_Nullable bindings) {
  520. return event.target == anyEvent.target &&
  521. [event.eventID isEqualToString:anyEvent.eventID] &&
  522. event.qosTier == anyEvent.qosTier;
  523. }]];
  524. XCTestExpectation *expectation = [self expectationWithDescription:@"paths found"];
  525. [storage pathsForTarget:anyEvent.target
  526. eventIDs:[NSSet setWithObject:anyEvent.eventID]
  527. qosTiers:[NSSet setWithObject:@(anyEvent.qosTier)]
  528. mappingIDs:nil
  529. onComplete:^(NSSet<NSString *> *paths) {
  530. XCTAssertEqual(paths.count, expectedEvents.count);
  531. [expectation fulfill];
  532. }];
  533. [self waitForExpectations:@[ expectation ] timeout:1.0];
  534. }
  535. /** Tests -pathForTarget:qosTier:mappingID: searching by eventID and qosTier without results. */
  536. - (void)testSearchingPathWithEventIDAndQoSTierNoResults {
  537. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  538. NSSet<GDTCOREvent *> *generatedEvents = [self generateEventsForStorageTesting];
  539. XCTAssertGreaterThan(generatedEvents.count, 0);
  540. XCTestExpectation *expectation = [self expectationWithDescription:@"paths found"];
  541. [storage pathsForTarget:kGDTCORTargetFLL
  542. eventIDs:[NSSet setWithObject:@"made up"]
  543. qosTiers:nil
  544. mappingIDs:nil
  545. onComplete:^(NSSet<NSString *> *paths) {
  546. XCTAssertEqual(paths.count, 0);
  547. [expectation fulfill];
  548. }];
  549. [self waitForExpectations:@[ expectation ] timeout:1.0];
  550. }
  551. /** Tests -pathForTarget:qosTier:mappingID: searching by qosTier and mappingID. */
  552. - (void)testSearchingPathWithQoSTierAndMappingID {
  553. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  554. NSSet<GDTCOREvent *> *generatedEvents = [self generateEventsForStorageTesting];
  555. GDTCOREvent *anyEvent = [generatedEvents randomElement];
  556. NSSet<GDTCOREvent *> *expectedEvents = [generatedEvents
  557. filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
  558. GDTCOREvent *_Nullable event,
  559. NSDictionary<NSString *, id> *_Nullable bindings) {
  560. return event.target == anyEvent.target && event.qosTier == anyEvent.qosTier &&
  561. [event.mappingID isEqualToString:anyEvent.mappingID];
  562. }]];
  563. XCTestExpectation *expectation = [self expectationWithDescription:@"paths found"];
  564. [storage pathsForTarget:anyEvent.target
  565. eventIDs:nil
  566. qosTiers:[NSSet setWithObject:@(anyEvent.qosTier)]
  567. mappingIDs:[NSSet setWithObject:anyEvent.mappingID]
  568. onComplete:^(NSSet<NSString *> *paths) {
  569. XCTAssertEqual(paths.count, expectedEvents.count);
  570. [expectation fulfill];
  571. }];
  572. [self waitForExpectations:@[ expectation ] timeout:1.0];
  573. }
  574. /** Tests hasEventsForTarget: returns YES when events are stored and NO otherwise. */
  575. - (void)testHasEventsForTarget {
  576. XCTestExpectation *expectation = [self expectationWithDescription:@"hasEvent completion called"];
  577. [[GDTCORFlatFileStorage sharedInstance] hasEventsForTarget:kGDTCORTargetTest
  578. onComplete:^(BOOL hasEvents) {
  579. XCTAssertFalse(hasEvents);
  580. [expectation fulfill];
  581. }];
  582. [self waitForExpectations:@[ expectation ] timeout:10];
  583. GDTCOREvent *event = [GDTCOREventGenerator generateEventForTarget:kGDTCORTargetTest
  584. qosTier:nil
  585. mappingID:nil];
  586. [[GDTCORFlatFileStorage sharedInstance] storeEvent:event onComplete:nil];
  587. expectation = [self expectationWithDescription:@"hasEvent completion called"];
  588. [[GDTCORFlatFileStorage sharedInstance] hasEventsForTarget:kGDTCORTargetTest
  589. onComplete:^(BOOL hasEvents) {
  590. XCTAssertTrue(hasEvents);
  591. [expectation fulfill];
  592. }];
  593. [self waitForExpectations:@[ expectation ] timeout:10];
  594. }
  595. /** Tests that the size of the storage is returned accurately. */
  596. - (void)testStorageSizeWithCallback {
  597. NSUInteger ongoingSize = 0;
  598. XCTestExpectation *expectation = [self expectationWithDescription:@"storageSize complete"];
  599. [[GDTCORFlatFileStorage sharedInstance] storageSizeWithCallback:^(uint64_t storageSize) {
  600. XCTAssertEqual(storageSize, 0);
  601. [expectation fulfill];
  602. }];
  603. [self waitForExpectations:@[ expectation ] timeout:10.0];
  604. expectation = [self expectationWithDescription:@"storageSize complete"];
  605. NSData *data = [@"this is a test" dataUsingEncoding:NSUTF8StringEncoding];
  606. ongoingSize += data.length;
  607. [[GDTCORFlatFileStorage sharedInstance] storeLibraryData:data forKey:@"testKey" onComplete:nil];
  608. [[GDTCORFlatFileStorage sharedInstance] storageSizeWithCallback:^(uint64_t storageSize) {
  609. XCTAssertEqual(storageSize, ongoingSize);
  610. [expectation fulfill];
  611. }];
  612. [self waitForExpectations:@[ expectation ] timeout:10.0];
  613. NSSet<GDTCOREvent *> *generatedEvents = [self generateEventsForStorageTesting];
  614. for (GDTCOREvent *event in generatedEvents) {
  615. NSError *error;
  616. NSData *serializedEventData = GDTCOREncodeArchive(event, nil, &error);
  617. XCTAssertNil(error);
  618. ongoingSize += serializedEventData.length;
  619. }
  620. expectation = [self expectationWithDescription:@"storageSize complete"];
  621. [[GDTCORFlatFileStorage sharedInstance] storageSizeWithCallback:^(uint64_t storageSize) {
  622. // TODO(mikehaney24): Figure out why storageSize is ~2% higher than ongoingSize.
  623. XCTAssertGreaterThanOrEqual(storageSize, ongoingSize);
  624. [expectation fulfill];
  625. }];
  626. [self waitForExpectations:@[ expectation ] timeout:10.0];
  627. }
  628. /** Tests generating the next batchID. */
  629. - (void)testNextBatchID {
  630. BOOL originalContinueAfterFailure = self.continueAfterFailure;
  631. self.continueAfterFailure = NO;
  632. NSNumber *expectedBatchID = @0;
  633. XCTestExpectation *expectation = [self expectationWithDescription:@"nextBatchID completion"];
  634. [[GDTCORFlatFileStorage sharedInstance] nextBatchID:^(NSNumber *_Nonnull batchID) {
  635. XCTAssertNotNil(batchID);
  636. XCTAssertEqualObjects(batchID, expectedBatchID);
  637. [expectation fulfill];
  638. }];
  639. [self waitForExpectations:@[ expectation ] timeout:10.0];
  640. expectedBatchID = @1;
  641. expectation = [self expectationWithDescription:@"nextBatchID completion"];
  642. [[GDTCORFlatFileStorage sharedInstance] nextBatchID:^(NSNumber *_Nonnull batchID) {
  643. XCTAssertNotNil(batchID);
  644. XCTAssertEqualObjects(batchID, expectedBatchID);
  645. [expectation fulfill];
  646. }];
  647. [self waitForExpectations:@[ expectation ] timeout:10.0];
  648. for (int i = 0; i < 1000; i++) {
  649. XCTestExpectation *expectation = [self expectationWithDescription:@"nextBatchID completion"];
  650. [[GDTCORFlatFileStorage sharedInstance] nextBatchID:^(NSNumber *_Nonnull batchID) {
  651. NSNumber *expectedBatchID = @(i + 2); // 2 because of the 2 we generated.
  652. XCTAssertEqualObjects(batchID, expectedBatchID);
  653. [expectation fulfill];
  654. }];
  655. [self waitForExpectations:@[ expectation ] timeout:10.0];
  656. }
  657. self.continueAfterFailure = originalContinueAfterFailure;
  658. }
  659. /** Tests the thread safety of nextBatchID by making a lot of simultaneous calls to it. */
  660. - (void)testNextBatchIDThreadSafety {
  661. NSUInteger numberOfIterations = 1000;
  662. NSUInteger expectedBatchID = 2 * numberOfIterations - 1;
  663. __block NSNumber *batchID;
  664. NSMutableArray *expectations = [[NSMutableArray alloc] init];
  665. for (NSUInteger i = 0; i < numberOfIterations; i++) {
  666. XCTestExpectation *firstExpectation = [self expectationWithDescription:@"first block run"];
  667. [expectations addObject:firstExpectation];
  668. dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
  669. [[GDTCORFlatFileStorage sharedInstance] nextBatchID:^(NSNumber *_Nonnull newBatchID) {
  670. batchID = newBatchID;
  671. [firstExpectation fulfill];
  672. }];
  673. });
  674. XCTestExpectation *secondExpectation = [self expectationWithDescription:@"first block run"];
  675. [expectations addObject:secondExpectation];
  676. dispatch_async(dispatch_get_global_queue(QOS_CLASS_UNSPECIFIED, 0), ^{
  677. [[GDTCORFlatFileStorage sharedInstance] nextBatchID:^(NSNumber *_Nonnull newBatchID) {
  678. batchID = newBatchID;
  679. [secondExpectation fulfill];
  680. }];
  681. });
  682. }
  683. [self waitForExpectations:expectations timeout:30];
  684. XCTAssertEqualObjects(batchID, @(expectedBatchID));
  685. }
  686. /** Tests basic batch creation and removal. */
  687. - (void)testBatchIDWithTarget {
  688. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  689. NSSet<GDTCOREvent *> *generatedEvents = [self generateEventsForStorageTesting];
  690. NSSet<GDTCOREvent *> *testTargetEvents = [generatedEvents
  691. filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
  692. GDTCOREvent *_Nullable event,
  693. NSDictionary<NSString *, id> *_Nullable bindings) {
  694. return event.target == kGDTCORTargetTest;
  695. }]];
  696. XCTAssertNotNil(testTargetEvents);
  697. XCTestExpectation *expectation = [self expectationWithDescription:@"batch callback invoked"];
  698. GDTCORStorageEventSelector *eventSelector =
  699. [GDTCORStorageEventSelector eventSelectorForTarget:kGDTCORTargetTest];
  700. __block NSNumber *batchID;
  701. [storage batchWithEventSelector:eventSelector
  702. batchExpiration:[NSDate dateWithTimeIntervalSinceNow:600]
  703. onComplete:^(NSNumber *_Nullable newBatchID,
  704. NSSet<GDTCOREvent *> *_Nullable events) {
  705. batchID = newBatchID;
  706. XCTAssertNotNil(batchID);
  707. XCTAssertEqual(events.count, testTargetEvents.count);
  708. [expectation fulfill];
  709. }];
  710. [self waitForExpectations:@[ expectation ] timeout:10];
  711. XCTAssertNotNil(batchID);
  712. expectation = [self expectationWithDescription:@"pathsForTarget completion invoked"];
  713. [storage pathsForTarget:kGDTCORTargetTest
  714. eventIDs:nil
  715. qosTiers:nil
  716. mappingIDs:nil
  717. onComplete:^(NSSet<NSString *> *_Nonnull paths) {
  718. XCTAssertEqual(paths.count, 0);
  719. [expectation fulfill];
  720. }];
  721. [self waitForExpectations:@[ expectation ] timeout:10];
  722. }
  723. - (void)testBatchIDsForTarget {
  724. __auto_type expectedBatch = [self generateAndBatchEvents];
  725. XCTestExpectation *batchIDsExpectation = [self expectationWithDescription:@"batchIDsExpectation"];
  726. [[GDTCORFlatFileStorage sharedInstance]
  727. batchIDsForTarget:kGDTCORTargetTest
  728. onComplete:^(NSSet<NSNumber *> *_Nullable batchIDs) {
  729. [batchIDsExpectation fulfill];
  730. XCTAssertEqual(batchIDs.count, 1);
  731. XCTAssertEqualObjects([expectedBatch.allKeys firstObject], [batchIDs anyObject]);
  732. }];
  733. [self waitForExpectations:@[ batchIDsExpectation ] timeout:5];
  734. }
  735. #pragma mark - Expiration tests
  736. /** Tests events expiring at a given time. */
  737. - (void)testCheckForExpirations_WhenEventsExpire {
  738. NSTimeInterval delay = 10.0;
  739. XCTestExpectation *expectation = [self expectationWithDescription:@"hasEvent completion called"];
  740. [[GDTCORFlatFileStorage sharedInstance] hasEventsForTarget:kGDTCORTargetTest
  741. onComplete:^(BOOL hasEvents) {
  742. XCTAssertFalse(hasEvents);
  743. [expectation fulfill];
  744. }];
  745. [self waitForExpectations:@[ expectation ] timeout:10];
  746. GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"testing" target:kGDTCORTargetTest];
  747. event.expirationDate = [NSDate dateWithTimeIntervalSinceNow:delay];
  748. event.clockSnapshot = [GDTCORClock snapshot];
  749. event.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"testString"];
  750. expectation = [self expectationWithDescription:@"storeEvent completion"];
  751. [[GDTCORFlatFileStorage sharedInstance] storeEvent:event
  752. onComplete:^(BOOL wasWritten, NSError *_Nullable error) {
  753. XCTAssertTrue(wasWritten);
  754. XCTAssertNil(error);
  755. [expectation fulfill];
  756. }];
  757. [self waitForExpectations:@[ expectation ] timeout:5.0];
  758. expectation = [self expectationWithDescription:@"hasEvent completion called"];
  759. [[GDTCORFlatFileStorage sharedInstance] hasEventsForTarget:kGDTCORTargetTest
  760. onComplete:^(BOOL hasEvents) {
  761. XCTAssertTrue(hasEvents);
  762. [expectation fulfill];
  763. }];
  764. [self waitForExpectations:@[ expectation ] timeout:10];
  765. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:delay]];
  766. [[GDTCORFlatFileStorage sharedInstance] checkForExpirations];
  767. expectation = [self expectationWithDescription:@"hasEvent completion called"];
  768. [[GDTCORFlatFileStorage sharedInstance] hasEventsForTarget:kGDTCORTargetTest
  769. onComplete:^(BOOL hasEvents) {
  770. XCTAssertFalse(hasEvents);
  771. [expectation fulfill];
  772. }];
  773. [self waitForExpectations:@[ expectation ] timeout:10];
  774. }
  775. - (void)testCheckForExpirations_WhenBatchWithNotExpiredEventsExpires {
  776. NSTimeInterval batchExpiresIn = 0.5;
  777. // 0.1. Generate and batch events
  778. __auto_type generatedBatch = [self generateAndBatchEventsExpiringIn:1000
  779. batchExpiringIn:batchExpiresIn];
  780. NSNumber *generatedBatchID = [[generatedBatch allKeys] firstObject];
  781. NSSet<GDTCOREvent *> *generatedEvents = generatedBatch[generatedBatchID];
  782. // 0.2. Wait for batch expiration.
  783. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:batchExpiresIn]];
  784. // 1. Check for expiration.
  785. [[GDTCORFlatFileStorage sharedInstance] checkForExpirations];
  786. // 2. Check events.
  787. // 2.1. Expect no batches left.
  788. XCTestExpectation *getBatchesExpectation =
  789. [self expectationWithDescription:@"getBatchesExpectation"];
  790. [[GDTCORFlatFileStorage sharedInstance]
  791. batchIDsForTarget:kGDTCORTargetTest
  792. onComplete:^(NSSet<NSNumber *> *_Nullable batchIDs) {
  793. [getBatchesExpectation fulfill];
  794. XCTAssertEqual(batchIDs.count, 0);
  795. }];
  796. // 2.2. Expect the events back in the main storage.
  797. XCTestExpectation *getEventsExpectation =
  798. [self expectationWithDescription:@"getEventsExpectation"];
  799. [[GDTCORFlatFileStorage sharedInstance]
  800. batchWithEventSelector:[GDTCORStorageEventSelector eventSelectorForTarget:kGDTCORTargetTest]
  801. batchExpiration:[NSDate dateWithTimeIntervalSinceNow:1000]
  802. onComplete:^(NSNumber *_Nullable newBatchID,
  803. NSSet<GDTCOREvent *> *_Nullable batchEvents) {
  804. [getEventsExpectation fulfill];
  805. XCTAssertNotNil(newBatchID);
  806. NSSet<NSString *> *batchEventsIDs = [batchEvents valueForKeyPath:@"eventID"];
  807. NSSet<NSString *> *generatedEventsIDs =
  808. [generatedEvents valueForKeyPath:@"eventID"];
  809. XCTAssertEqualObjects(batchEventsIDs, generatedEventsIDs);
  810. }];
  811. [self waitForExpectations:@[ getBatchesExpectation, getEventsExpectation ] timeout:0.5];
  812. }
  813. - (void)testCheckForExpirations_WhenBatchWithExpiredEventsExpires {
  814. NSTimeInterval batchExpiresIn = 0.5;
  815. NSTimeInterval eventsExpireIn = 0.5;
  816. // 0.1. Generate and batch events
  817. __unused __auto_type generatedBatch = [self generateAndBatchEventsExpiringIn:eventsExpireIn
  818. batchExpiringIn:batchExpiresIn];
  819. // 0.2. Wait for batch expiration.
  820. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:batchExpiresIn]];
  821. // 1. Check for expiration.
  822. [[GDTCORFlatFileStorage sharedInstance] checkForExpirations];
  823. // 2. Check events.
  824. // 2.1. Expect no batches left.
  825. XCTestExpectation *getBatchesExpectation =
  826. [self expectationWithDescription:@"getBatchesExpectation"];
  827. [[GDTCORFlatFileStorage sharedInstance]
  828. batchIDsForTarget:kGDTCORTargetTest
  829. onComplete:^(NSSet<NSNumber *> *_Nullable batchIDs) {
  830. [getBatchesExpectation fulfill];
  831. XCTAssertEqual(batchIDs.count, 0);
  832. }];
  833. // 2.2. Expect events to be deleted.
  834. XCTestExpectation *getEventsExpectation =
  835. [self expectationWithDescription:@"getEventsExpectation"];
  836. [[GDTCORFlatFileStorage sharedInstance]
  837. batchWithEventSelector:[GDTCORStorageEventSelector eventSelectorForTarget:kGDTCORTargetTest]
  838. batchExpiration:[NSDate dateWithTimeIntervalSinceNow:1000]
  839. onComplete:^(NSNumber *_Nullable newBatchID,
  840. NSSet<GDTCOREvent *> *_Nullable batchEvents) {
  841. [getEventsExpectation fulfill];
  842. XCTAssertNil(newBatchID);
  843. XCTAssertEqual(batchEvents.count, 0);
  844. }];
  845. [self waitForExpectations:@[ getBatchesExpectation, getEventsExpectation ] timeout:0.5];
  846. }
  847. #pragma mark - Remove Batch tests
  848. - (void)testRemoveBatchWithIDWithNoDeletingEvents {
  849. GDTCORFlatFileStorage *storage = [[GDTCORFlatFileStorage alloc] init];
  850. // 0. Prepare a batch to remove.
  851. __auto_type generatedBatch = [self generateAndBatchEvents];
  852. NSNumber *batchIDToRemove = [generatedBatch.allKeys firstObject];
  853. NSSet<GDTCOREvent *> *generatedEvents = generatedBatch[batchIDToRemove];
  854. // 2. Remove batch.
  855. XCTestExpectation *batchRemovedExpectation =
  856. [self expectationWithDescription:@"batchRemovedExpectation"];
  857. [storage removeBatchWithID:batchIDToRemove
  858. deleteEvents:NO
  859. onComplete:^{
  860. [batchRemovedExpectation fulfill];
  861. }];
  862. [self waitForExpectations:@[ batchRemovedExpectation ] timeout:0.5];
  863. // 3. Validate no batches.
  864. [self assertBatchIDs:nil inStorage:storage];
  865. // 4. Validate events.
  866. GDTCORStorageEventSelector *testEventsSelector =
  867. [[GDTCORStorageEventSelector alloc] initWithTarget:kGDTCORTargetTest
  868. eventIDs:nil
  869. mappingIDs:nil
  870. qosTiers:nil];
  871. XCTestExpectation *eventsBatchedExpectation2 =
  872. [self expectationWithDescription:@"eventsBatchedExpectation1"];
  873. [storage batchWithEventSelector:testEventsSelector
  874. batchExpiration:[NSDate distantFuture]
  875. onComplete:^(NSNumber *_Nullable newBatchID,
  876. NSSet<GDTCOREvent *> *_Nullable batchEvents) {
  877. [eventsBatchedExpectation2 fulfill];
  878. XCTAssertNotNil(newBatchID);
  879. XCTAssertEqual(generatedEvents.count, batchEvents.count);
  880. NSSet<NSString *> *batchEventsIDs =
  881. [batchEvents valueForKeyPath:@"eventID"];
  882. NSSet<NSString *> *generatedEventsIDs =
  883. [generatedEvents valueForKeyPath:@"eventID"];
  884. XCTAssertEqualObjects(batchEventsIDs, generatedEventsIDs);
  885. }];
  886. [self waitForExpectations:@[ eventsBatchedExpectation2 ] timeout:0.5];
  887. }
  888. - (void)testRemoveBatchWithIDWithNoDeletingEventsConflictingEvents {
  889. GDTCORFlatFileStorage *storage = [[GDTCORFlatFileStorage alloc] init];
  890. // 0.1. Prepare a batch to remove.
  891. __auto_type generatedBatch = [self generateAndBatchEvents];
  892. NSNumber *batchIDToRemove = [generatedBatch.allKeys firstObject];
  893. NSSet<GDTCOREvent *> *generatedEvents = generatedBatch[batchIDToRemove];
  894. // 0.2. Store an event with conflicting ID.
  895. [self storeEvent:[generatedEvents anyObject] inStorage:storage];
  896. // 0.3. Store another event.
  897. GDTCOREvent *differentEvent = [GDTCOREventGenerator generateEventForTarget:kGDTCORTargetTest
  898. qosTier:nil
  899. mappingID:nil];
  900. [self storeEvent:differentEvent inStorage:storage];
  901. NSMutableSet<GDTCOREvent *> *expectedEvents = [generatedEvents mutableCopy];
  902. [expectedEvents addObject:differentEvent];
  903. // 2. Remove batch.
  904. XCTestExpectation *batchRemovedExpectation =
  905. [self expectationWithDescription:@"batchRemovedExpectation"];
  906. [storage removeBatchWithID:batchIDToRemove
  907. deleteEvents:NO
  908. onComplete:^{
  909. [batchRemovedExpectation fulfill];
  910. }];
  911. [self waitForExpectations:@[ batchRemovedExpectation ] timeout:0.5];
  912. // 3. Validate no batches.
  913. [self assertBatchIDs:nil inStorage:storage];
  914. // 4. Validate events.
  915. XCTestExpectation *eventsBatchedExpectation2 =
  916. [self expectationWithDescription:@"eventsBatchedExpectation1"];
  917. GDTCORStorageEventSelector *testEventsSelector =
  918. [[GDTCORStorageEventSelector alloc] initWithTarget:kGDTCORTargetTest
  919. eventIDs:nil
  920. mappingIDs:nil
  921. qosTiers:nil];
  922. [storage batchWithEventSelector:testEventsSelector
  923. batchExpiration:[NSDate distantFuture]
  924. onComplete:^(NSNumber *_Nullable newBatchID,
  925. NSSet<GDTCOREvent *> *_Nullable batchEvents) {
  926. [eventsBatchedExpectation2 fulfill];
  927. XCTAssertNotNil(newBatchID);
  928. XCTAssertEqual(expectedEvents.count, batchEvents.count);
  929. NSSet<NSString *> *batchEventsIDs =
  930. [batchEvents valueForKeyPath:@"eventID"];
  931. NSSet<NSString *> *expectedEventsIDs =
  932. [expectedEvents valueForKeyPath:@"eventID"];
  933. XCTAssertEqualObjects(batchEventsIDs, expectedEventsIDs);
  934. }];
  935. [self waitForExpectations:@[ eventsBatchedExpectation2 ] timeout:0.5];
  936. }
  937. - (void)testRemoveBatchWithIDDeletingEvents {
  938. GDTCORFlatFileStorage *storage = [[GDTCORFlatFileStorage alloc] init];
  939. // 0. Prepare a batch to remove.
  940. __auto_type generatedBatch = [self generateAndBatchEvents];
  941. NSNumber *batchIDToRemove = [generatedBatch.allKeys firstObject];
  942. // 2. Remove batch.
  943. XCTestExpectation *batchRemovedExpectation =
  944. [self expectationWithDescription:@"batchRemovedExpectation"];
  945. [storage removeBatchWithID:batchIDToRemove
  946. deleteEvents:YES
  947. onComplete:^{
  948. [batchRemovedExpectation fulfill];
  949. }];
  950. [self waitForExpectations:@[ batchRemovedExpectation ] timeout:0.5];
  951. // 3. Validate no batches.
  952. [self assertBatchIDs:nil inStorage:storage];
  953. // 4. Validate events.
  954. XCTestExpectation *eventsBatchedExpectation2 =
  955. [self expectationWithDescription:@"eventsBatchedExpectation1"];
  956. GDTCORStorageEventSelector *testEventsSelector =
  957. [[GDTCORStorageEventSelector alloc] initWithTarget:kGDTCORTargetTest
  958. eventIDs:nil
  959. mappingIDs:nil
  960. qosTiers:nil];
  961. [storage batchWithEventSelector:testEventsSelector
  962. batchExpiration:[NSDate distantFuture]
  963. onComplete:^(NSNumber *_Nullable newBatchID,
  964. NSSet<GDTCOREvent *> *_Nullable batchEvents) {
  965. [eventsBatchedExpectation2 fulfill];
  966. XCTAssertNil(newBatchID);
  967. XCTAssertEqual(batchEvents.count, 0);
  968. }];
  969. [self waitForExpectations:@[ eventsBatchedExpectation2 ] timeout:500];
  970. }
  971. /** Tests creating a batch and then deleting the files. */
  972. - (void)testRemoveBatchWithIDDeletingEventsStorageSize {
  973. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  974. NSSet<GDTCOREvent *> *generatedEvents = [self generateEventsForStorageTesting];
  975. __block NSUInteger testTargetSize = 0;
  976. NSSet<GDTCOREvent *> *testTargetEvents = [generatedEvents
  977. filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
  978. GDTCOREvent *_Nullable event,
  979. NSDictionary<NSString *, id> *_Nullable bindings) {
  980. NSError *error;
  981. testTargetSize +=
  982. event.target == kGDTCORTargetTest ? GDTCOREncodeArchive(event, nil, &error).length : 0;
  983. XCTAssertNil(error);
  984. return event.target == kGDTCORTargetTest;
  985. }]];
  986. XCTAssertNotNil(testTargetEvents);
  987. __block uint64_t totalSize;
  988. [storage storageSizeWithCallback:^(uint64_t storageSize) {
  989. totalSize = storageSize;
  990. }];
  991. XCTestExpectation *expectation = [self expectationWithDescription:@"batch callback invoked"];
  992. GDTCORStorageEventSelector *eventSelector =
  993. [GDTCORStorageEventSelector eventSelectorForTarget:kGDTCORTargetTest];
  994. __block NSNumber *batchID;
  995. [storage batchWithEventSelector:eventSelector
  996. batchExpiration:[NSDate dateWithTimeIntervalSinceNow:600]
  997. onComplete:^(NSNumber *_Nullable newBatchID,
  998. NSSet<GDTCOREvent *> *_Nullable events) {
  999. batchID = newBatchID;
  1000. XCTAssertNotNil(batchID);
  1001. XCTAssertEqual(events.count, testTargetEvents.count);
  1002. [expectation fulfill];
  1003. }];
  1004. [self waitForExpectations:@[ expectation ] timeout:10];
  1005. expectation = [self expectationWithDescription:@"batch removal completion invoked"];
  1006. [storage removeBatchWithID:batchID
  1007. deleteEvents:YES
  1008. onComplete:^{
  1009. [expectation fulfill];
  1010. }];
  1011. [self waitForExpectations:@[ expectation ] timeout:10];
  1012. expectation = [self expectationWithDescription:@"storageSize callback invoked"];
  1013. [storage storageSizeWithCallback:^(uint64_t storageSize) {
  1014. XCTAssertLessThan(storageSize * .95, totalSize - testTargetSize); // .95 to allow overhead
  1015. [expectation fulfill];
  1016. }];
  1017. [self waitForExpectations:@[ expectation ] timeout:10];
  1018. }
  1019. #pragma mark - Helpers
  1020. - (NSDictionary<NSNumber *, NSSet<GDTCOREvent *> *> *)generateAndBatchEvents {
  1021. return [self generateAndBatchEventsExpiringIn:1000 batchExpiringIn:1000];
  1022. }
  1023. - (NSDictionary<NSNumber *, NSSet<GDTCOREvent *> *> *)
  1024. generateAndBatchEventsExpiringIn:(NSTimeInterval)eventsExpireIn
  1025. batchExpiringIn:(NSTimeInterval)batchExpiresIn {
  1026. GDTCORFlatFileStorage *storage = [GDTCORFlatFileStorage sharedInstance];
  1027. NSSet<GDTCOREvent *> *events = [self generateEventsForTarget:kGDTCORTargetTest
  1028. expiringIn:eventsExpireIn
  1029. count:100];
  1030. XCTestExpectation *eventsGeneratedExpectation =
  1031. [self expectationWithDescription:@"eventsGeneratedExpectation"];
  1032. [storage hasEventsForTarget:kGDTCORTargetTest
  1033. onComplete:^(BOOL hasEvents) {
  1034. XCTAssertTrue(hasEvents);
  1035. [eventsGeneratedExpectation fulfill];
  1036. }];
  1037. [self waitForExpectations:@[ eventsGeneratedExpectation ] timeout:5];
  1038. // Batch generated events.
  1039. XCTestExpectation *batchCreatedExpectation =
  1040. [self expectationWithDescription:@"batchCreatedExpectation"];
  1041. __block NSNumber *batchID;
  1042. [storage
  1043. batchWithEventSelector:[GDTCORStorageEventSelector eventSelectorForTarget:kGDTCORTargetTest]
  1044. batchExpiration:[NSDate dateWithTimeIntervalSinceNow:batchExpiresIn]
  1045. onComplete:^(NSNumber *_Nullable newBatchID,
  1046. NSSet<GDTCOREvent *> *_Nullable events) {
  1047. batchID = newBatchID;
  1048. XCTAssertGreaterThan(events.count, 0);
  1049. [batchCreatedExpectation fulfill];
  1050. }];
  1051. [self waitForExpectations:@[ batchCreatedExpectation ] timeout:5];
  1052. return @{batchID : events};
  1053. }
  1054. - (void)assertBatchIDs:(NSSet<NSNumber *> *)expectedBatchIDs
  1055. inStorage:(GDTCORFlatFileStorage *)storage {
  1056. XCTestExpectation *batchIDsFetchedExpectation =
  1057. [self expectationWithDescription:@"batchIDsFetchedExpectation"];
  1058. [storage batchIDsForTarget:kGDTCORTargetTest
  1059. onComplete:^(NSSet<NSNumber *> *_Nullable batchIDs) {
  1060. [batchIDsFetchedExpectation fulfill];
  1061. XCTAssertEqualObjects(batchIDs, expectedBatchIDs);
  1062. }];
  1063. [self waitForExpectations:@[ batchIDsFetchedExpectation ] timeout:0.5];
  1064. }
  1065. - (void)storeEvent:(GDTCOREvent *)event inStorage:(GDTCORFlatFileStorage *)storage {
  1066. XCTestExpectation *eventStoredExpectation =
  1067. [self expectationWithDescription:@"eventStoredExpectation"];
  1068. [storage storeEvent:event
  1069. onComplete:^(BOOL wasWritten, NSError *_Nullable error) {
  1070. [eventStoredExpectation fulfill];
  1071. XCTAssertTrue(wasWritten);
  1072. XCTAssertNil(error);
  1073. }];
  1074. [self waitForExpectations:@[ eventStoredExpectation ] timeout:0.5];
  1075. }
  1076. @end