GDTCORStorageTest.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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 "GDTCORTests/Unit/GDTCORTestCase.h"
  17. #import <GoogleDataTransport/GDTCOREvent.h>
  18. #import <GoogleDataTransport/GDTCORStoredEvent.h>
  19. #import "GDTCORLibrary/Private/GDTCOREvent_Private.h"
  20. #import "GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
  21. #import "GDTCORLibrary/Private/GDTCORStorage.h"
  22. #import "GDTCORLibrary/Private/GDTCORStorage_Private.h"
  23. #import "GDTCORLibrary/Public/GDTCORRegistrar.h"
  24. #import "GDTCORTests/Unit/Helpers/GDTCORAssertHelper.h"
  25. #import "GDTCORTests/Unit/Helpers/GDTCORTestPrioritizer.h"
  26. #import "GDTCORTests/Unit/Helpers/GDTCORTestUploader.h"
  27. #import "GDTCORTests/Common/Fakes/GDTCORUploadCoordinatorFake.h"
  28. #import "GDTCORTests/Common/Categories/GDTCORRegistrar+Testing.h"
  29. #import "GDTCORTests/Common/Categories/GDTCORStorage+Testing.h"
  30. static NSInteger target = kGDTCORTargetCCT;
  31. @interface GDTCORStorageTest : GDTCORTestCase
  32. /** The test backend implementation. */
  33. @property(nullable, nonatomic) GDTCORTestUploader *testBackend;
  34. /** The test prioritizer implementation. */
  35. @property(nullable, nonatomic) GDTCORTestPrioritizer *testPrioritizer;
  36. /** The uploader fake. */
  37. @property(nonatomic) GDTCORUploadCoordinatorFake *uploaderFake;
  38. @end
  39. @implementation GDTCORStorageTest
  40. - (void)setUp {
  41. [super setUp];
  42. self.testBackend = [[GDTCORTestUploader alloc] init];
  43. self.testPrioritizer = [[GDTCORTestPrioritizer alloc] init];
  44. [[GDTCORRegistrar sharedInstance] registerUploader:_testBackend target:target];
  45. [[GDTCORRegistrar sharedInstance] registerPrioritizer:_testPrioritizer target:target];
  46. self.uploaderFake = [[GDTCORUploadCoordinatorFake alloc] init];
  47. [GDTCORStorage sharedInstance].uploadCoordinator = self.uploaderFake;
  48. }
  49. - (void)tearDown {
  50. [super tearDown];
  51. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  52. });
  53. // Destroy these objects before the next test begins.
  54. self.testBackend = nil;
  55. self.testPrioritizer = nil;
  56. [[GDTCORRegistrar sharedInstance] reset];
  57. [[GDTCORStorage sharedInstance] reset];
  58. [GDTCORStorage sharedInstance].uploadCoordinator = [GDTCORUploadCoordinator sharedInstance];
  59. self.uploaderFake = nil;
  60. }
  61. /** Tests the singleton pattern. */
  62. - (void)testInit {
  63. XCTAssertEqual([GDTCORStorage sharedInstance], [GDTCORStorage sharedInstance]);
  64. }
  65. /** Tests storing an event. */
  66. - (void)testStoreEvent {
  67. // event is autoreleased, and the pool needs to drain.
  68. @autoreleasepool {
  69. GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"404" target:target];
  70. event.dataObjectTransportBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding];
  71. event.clockSnapshot = [GDTCORClock snapshot];
  72. XCTAssertNoThrow([[GDTCORStorage sharedInstance] storeEvent:event]);
  73. }
  74. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  75. XCTAssertEqual([GDTCORStorage sharedInstance].storedEvents.count, 1);
  76. XCTAssertEqual([GDTCORStorage sharedInstance].targetToEventSet[@(target)].count, 1);
  77. NSURL *eventFile = [[GDTCORStorage sharedInstance].storedEvents lastObject].dataFuture.fileURL;
  78. XCTAssertNotNil(eventFile);
  79. XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]);
  80. NSError *error;
  81. XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:eventFile error:&error]);
  82. XCTAssertNil(error, @"There was an error deleting the eventFile: %@", error);
  83. });
  84. }
  85. /** Tests removing an event. */
  86. - (void)testRemoveEvent {
  87. // event is autoreleased, and the pool needs to drain.
  88. @autoreleasepool {
  89. GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"404" target:target];
  90. event.dataObjectTransportBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding];
  91. event.clockSnapshot = [GDTCORClock snapshot];
  92. XCTAssertNoThrow([[GDTCORStorage sharedInstance] storeEvent:event]);
  93. }
  94. __block NSURL *eventFile;
  95. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  96. eventFile = [[GDTCORStorage sharedInstance].storedEvents lastObject].dataFuture.fileURL;
  97. XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]);
  98. });
  99. [[GDTCORStorage sharedInstance] removeEvents:[GDTCORStorage sharedInstance].storedEvents.set];
  100. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  101. XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]);
  102. XCTAssertEqual([GDTCORStorage sharedInstance].storedEvents.count, 0);
  103. XCTAssertEqual([GDTCORStorage sharedInstance].targetToEventSet[@(target)].count, 0);
  104. });
  105. }
  106. /** Tests removing a set of events. */
  107. - (void)testRemoveEvents {
  108. GDTCORStorage *storage = [GDTCORStorage sharedInstance];
  109. __block GDTCORStoredEvent *storedEvent1, *storedEvent2, *storedEvent3;
  110. // events are autoreleased, and the pool needs to drain.
  111. @autoreleasepool {
  112. GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"404" target:target];
  113. event.dataObjectTransportBytes = [@"testString1" dataUsingEncoding:NSUTF8StringEncoding];
  114. XCTAssertNoThrow([storage storeEvent:event]);
  115. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  116. storedEvent1 = [storage.storedEvents lastObject];
  117. });
  118. event = [[GDTCOREvent alloc] initWithMappingID:@"100" target:target];
  119. event.dataObjectTransportBytes = [@"testString2" dataUsingEncoding:NSUTF8StringEncoding];
  120. XCTAssertNoThrow([storage storeEvent:event]);
  121. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  122. storedEvent2 = [storage.storedEvents lastObject];
  123. });
  124. event = [[GDTCOREvent alloc] initWithMappingID:@"404" target:target];
  125. event.dataObjectTransportBytes = [@"testString3" dataUsingEncoding:NSUTF8StringEncoding];
  126. XCTAssertNoThrow([storage storeEvent:event]);
  127. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  128. storedEvent3 = [storage.storedEvents lastObject];
  129. });
  130. }
  131. NSSet<GDTCORStoredEvent *> *eventSet =
  132. [NSSet setWithObjects:storedEvent1, storedEvent2, storedEvent3, nil];
  133. [storage removeEvents:eventSet];
  134. dispatch_sync(storage.storageQueue, ^{
  135. XCTAssertFalse([storage.storedEvents containsObject:storedEvent1]);
  136. XCTAssertFalse([storage.storedEvents containsObject:storedEvent2]);
  137. XCTAssertFalse([storage.storedEvents containsObject:storedEvent3]);
  138. XCTAssertEqual(storage.targetToEventSet[@(target)].count, 0);
  139. for (GDTCORStoredEvent *event in eventSet) {
  140. XCTAssertFalse(
  141. [[NSFileManager defaultManager] fileExistsAtPath:event.dataFuture.fileURL.path]);
  142. }
  143. });
  144. }
  145. /** Tests storing a few different events. */
  146. - (void)testStoreMultipleEvents {
  147. __block GDTCORStoredEvent *storedEvent1, *storedEvent2, *storedEvent3;
  148. // events are autoreleased, and the pool needs to drain.
  149. @autoreleasepool {
  150. GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"404" target:target];
  151. event.dataObjectTransportBytes = [@"testString1" dataUsingEncoding:NSUTF8StringEncoding];
  152. XCTAssertNoThrow([[GDTCORStorage sharedInstance] storeEvent:event]);
  153. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  154. storedEvent1 = [[GDTCORStorage sharedInstance].storedEvents lastObject];
  155. });
  156. event = [[GDTCOREvent alloc] initWithMappingID:@"100" target:target];
  157. event.dataObjectTransportBytes = [@"testString2" dataUsingEncoding:NSUTF8StringEncoding];
  158. XCTAssertNoThrow([[GDTCORStorage sharedInstance] storeEvent:event]);
  159. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  160. storedEvent2 = [[GDTCORStorage sharedInstance].storedEvents lastObject];
  161. });
  162. event = [[GDTCOREvent alloc] initWithMappingID:@"404" target:target];
  163. event.dataObjectTransportBytes = [@"testString3" dataUsingEncoding:NSUTF8StringEncoding];
  164. XCTAssertNoThrow([[GDTCORStorage sharedInstance] storeEvent:event]);
  165. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  166. storedEvent3 = [[GDTCORStorage sharedInstance].storedEvents lastObject];
  167. });
  168. }
  169. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  170. XCTAssertEqual([GDTCORStorage sharedInstance].storedEvents.count, 3);
  171. XCTAssertEqual([GDTCORStorage sharedInstance].targetToEventSet[@(target)].count, 3);
  172. NSURL *event1File = storedEvent1.dataFuture.fileURL;
  173. XCTAssertNotNil(event1File);
  174. XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:event1File.path]);
  175. NSError *error;
  176. XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:event1File error:&error]);
  177. XCTAssertNil(error, @"There was an error deleting the eventFile: %@", error);
  178. NSURL *event2File = storedEvent2.dataFuture.fileURL;
  179. XCTAssertNotNil(event2File);
  180. XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:event2File.path]);
  181. error = nil;
  182. XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:event2File error:&error]);
  183. XCTAssertNil(error, @"There was an error deleting the eventFile: %@", error);
  184. NSURL *event3File = storedEvent3.dataFuture.fileURL;
  185. XCTAssertNotNil(event3File);
  186. XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:event3File.path]);
  187. error = nil;
  188. XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:event3File error:&error]);
  189. XCTAssertNil(error, @"There was an error deleting the eventFile: %@", error);
  190. });
  191. }
  192. /** Tests enforcing that a prioritizer does not retain an event in memory. */
  193. - (void)testEventDeallocationIsEnforced {
  194. __weak GDTCOREvent *weakEvent;
  195. GDTCORStoredEvent *storedEvent;
  196. @autoreleasepool {
  197. GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"404" target:target];
  198. weakEvent = event;
  199. event.dataObjectTransportBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding];
  200. event.clockSnapshot = [GDTCORClock snapshot];
  201. // Store the event and wait for the expectation.
  202. [[GDTCORStorage sharedInstance] storeEvent:event];
  203. GDTCORDataFuture *dataFuture =
  204. [[GDTCORDataFuture alloc] initWithFileURL:[NSURL fileURLWithPath:@"/test"]];
  205. storedEvent = [event storedEventWithDataFuture:dataFuture];
  206. }
  207. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  208. XCTAssertNil(weakEvent);
  209. XCTAssertNotNil(storedEvent);
  210. });
  211. NSURL *eventFile;
  212. eventFile = [[GDTCORStorage sharedInstance].storedEvents lastObject].dataFuture.fileURL;
  213. // This isn't strictly necessary because of the -waitForExpectations above.
  214. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  215. XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]);
  216. });
  217. // Ensure event was removed.
  218. [[GDTCORStorage sharedInstance] removeEvents:[GDTCORStorage sharedInstance].storedEvents.set];
  219. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  220. XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]);
  221. XCTAssertEqual([GDTCORStorage sharedInstance].storedEvents.count, 0);
  222. XCTAssertEqual([GDTCORStorage sharedInstance].targetToEventSet[@(target)].count, 0);
  223. });
  224. }
  225. /** Tests encoding and decoding the storage singleton correctly. */
  226. - (void)testNSSecureCoding {
  227. XCTAssertTrue([GDTCORStorage supportsSecureCoding]);
  228. GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"404" target:target];
  229. event.clockSnapshot = [GDTCORClock snapshot];
  230. event.dataObjectTransportBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding];
  231. XCTAssertNoThrow([[GDTCORStorage sharedInstance] storeEvent:event]);
  232. event = nil;
  233. __block NSData *storageData;
  234. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  235. if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
  236. storageData = [NSKeyedArchiver archivedDataWithRootObject:[GDTCORStorage sharedInstance]
  237. requiringSecureCoding:YES
  238. error:nil];
  239. } else {
  240. #if !TARGET_OS_MACCATALYST
  241. storageData = [NSKeyedArchiver archivedDataWithRootObject:[GDTCORStorage sharedInstance]];
  242. #endif
  243. }
  244. });
  245. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  246. XCTAssertNotNil([[GDTCORStorage sharedInstance].storedEvents lastObject]);
  247. });
  248. [[GDTCORStorage sharedInstance] removeEvents:[GDTCORStorage sharedInstance].storedEvents.set];
  249. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  250. XCTAssertNil([[GDTCORStorage sharedInstance].storedEvents lastObject]);
  251. });
  252. GDTCORStorage *unarchivedStorage;
  253. NSError *error;
  254. if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
  255. unarchivedStorage = [NSKeyedUnarchiver unarchivedObjectOfClass:[GDTCORStorage class]
  256. fromData:storageData
  257. error:&error];
  258. } else {
  259. #if !TARGET_OS_MACCATALYST
  260. unarchivedStorage = [NSKeyedUnarchiver unarchiveObjectWithData:storageData];
  261. #endif
  262. }
  263. XCTAssertNotNil([unarchivedStorage.storedEvents lastObject]);
  264. }
  265. /** Tests encoding and decoding the storage singleton when calling -sharedInstance. */
  266. - (void)testNSSecureCodingWithSharedInstance {
  267. GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"404" target:target];
  268. event.dataObjectTransportBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding];
  269. event.clockSnapshot = [GDTCORClock snapshot];
  270. XCTAssertNoThrow([[GDTCORStorage sharedInstance] storeEvent:event]);
  271. event = nil;
  272. __block NSData *storageData;
  273. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  274. if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
  275. storageData = [NSKeyedArchiver archivedDataWithRootObject:[GDTCORStorage sharedInstance]
  276. requiringSecureCoding:YES
  277. error:nil];
  278. } else {
  279. #if !TARGET_OS_MACCATALYST
  280. storageData = [NSKeyedArchiver archivedDataWithRootObject:[GDTCORStorage sharedInstance]];
  281. #endif
  282. }
  283. });
  284. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  285. XCTAssertNotNil([[GDTCORStorage sharedInstance].storedEvents lastObject]);
  286. });
  287. [[GDTCORStorage sharedInstance] removeEvents:[GDTCORStorage sharedInstance].storedEvents.set];
  288. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  289. XCTAssertNil([[GDTCORStorage sharedInstance].storedEvents lastObject]);
  290. });
  291. GDTCORStorage *unarchivedStorage;
  292. if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
  293. unarchivedStorage = [NSKeyedUnarchiver unarchivedObjectOfClass:[GDTCORStorage class]
  294. fromData:storageData
  295. error:nil];
  296. } else {
  297. #if !TARGET_OS_MACCATALYST
  298. unarchivedStorage = [NSKeyedUnarchiver unarchiveObjectWithData:storageData];
  299. #endif
  300. }
  301. XCTAssertNotNil([unarchivedStorage.storedEvents lastObject]);
  302. }
  303. /** Tests sending a fast priority event causes an upload attempt. */
  304. - (void)testQoSTierFast {
  305. // event is autoreleased, and the pool needs to drain.
  306. @autoreleasepool {
  307. GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"404" target:target];
  308. event.dataObjectTransportBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding];
  309. event.qosTier = GDTCOREventQoSFast;
  310. event.clockSnapshot = [GDTCORClock snapshot];
  311. XCTAssertFalse(self.uploaderFake.forceUploadCalled);
  312. XCTAssertNoThrow([[GDTCORStorage sharedInstance] storeEvent:event]);
  313. }
  314. dispatch_sync([GDTCORStorage sharedInstance].storageQueue, ^{
  315. XCTAssertTrue(self.uploaderFake.forceUploadCalled);
  316. XCTAssertEqual([GDTCORStorage sharedInstance].storedEvents.count, 1);
  317. XCTAssertEqual([GDTCORStorage sharedInstance].targetToEventSet[@(target)].count, 1);
  318. NSURL *eventFile = [[GDTCORStorage sharedInstance].storedEvents lastObject].dataFuture.fileURL;
  319. XCTAssertNotNil(eventFile);
  320. XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]);
  321. NSError *error;
  322. XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:eventFile error:&error]);
  323. XCTAssertNil(error, @"There was an error deleting the eventFile: %@", error);
  324. });
  325. }
  326. @end