HeartbeatStorageTests.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. @testable import FirebaseCoreInternal
  15. import XCTest
  16. class HeartbeatStorageTests: XCTestCase {
  17. // MARK: - Instance Management
  18. func testGettingInstance_WithSameID_ReturnsSameInstance() {
  19. // Given
  20. let heartbeatStorage1 = HeartbeatStorage.getInstance(id: "sparky")
  21. // When
  22. let heartbeatStorage2 = HeartbeatStorage.getInstance(id: "sparky")
  23. // Then
  24. XCTAssert(
  25. heartbeatStorage1 === heartbeatStorage2,
  26. "Instances should reference the same object."
  27. )
  28. addTeardownBlock { [weak heartbeatStorage1, weak heartbeatStorage2] in
  29. XCTAssertNil(heartbeatStorage1)
  30. XCTAssertNil(heartbeatStorage2)
  31. }
  32. }
  33. func testGettingInstance_WithDifferentID_ReturnsDifferentInstances() {
  34. // Given
  35. let heartbeatStorage1 = HeartbeatStorage.getInstance(id: "sparky_jr")
  36. // When
  37. let heartbeatStorage2 = HeartbeatStorage.getInstance(id: "sparky_sr")
  38. // Then
  39. XCTAssert(
  40. heartbeatStorage1 !== heartbeatStorage2,
  41. "Instances should NOT reference the same object."
  42. )
  43. addTeardownBlock { [weak heartbeatStorage1, weak heartbeatStorage2] in
  44. XCTAssertNil(heartbeatStorage1)
  45. XCTAssertNil(heartbeatStorage2)
  46. }
  47. }
  48. func testCachedInstancesCannotBeRetainedWeakly() {
  49. // Given
  50. var strongHeartbeatStorage: HeartbeatStorage? = .getInstance(id: "sparky")
  51. weak var weakHeartbeatStorage: HeartbeatStorage? = .getInstance(id: "sparky")
  52. XCTAssert(
  53. strongHeartbeatStorage === weakHeartbeatStorage,
  54. "Instances should reference the same object."
  55. )
  56. // When
  57. strongHeartbeatStorage = nil
  58. // Then
  59. XCTAssertNil(strongHeartbeatStorage)
  60. XCTAssertNil(weakHeartbeatStorage)
  61. }
  62. func testCachedInstancesAreRemovedUponDeinitAndCanBeRetainedStrongly() {
  63. // Given
  64. var heartbeatStorage1: HeartbeatStorage? = .getInstance(id: "sparky")
  65. var heartbeatStorage2: HeartbeatStorage? = .getInstance(id: "sparky")
  66. XCTAssert(
  67. heartbeatStorage1 === heartbeatStorage2,
  68. "Instances should reference the same object."
  69. )
  70. // When
  71. heartbeatStorage1 = nil
  72. XCTAssertNil(heartbeatStorage1)
  73. XCTAssertNotNil(heartbeatStorage2)
  74. // Then
  75. heartbeatStorage2 = nil
  76. XCTAssertNil(heartbeatStorage2)
  77. }
  78. // MARK: - HeartbeatStorageProtocol
  79. func testReadAndWrite_ReadsOldValueAndWritesNewValue() throws {
  80. // Given
  81. let expectation = expectation(description: #function)
  82. let heartbeatStorage = HeartbeatStorage(id: #function, storage: StorageFake())
  83. var dummyHeartbeatsBundle = HeartbeatsBundle(capacity: 1)
  84. dummyHeartbeatsBundle.append(Heartbeat(agent: "dummy_agent", date: Date()))
  85. // When
  86. heartbeatStorage.readAndWriteAsync { heartbeatsBundle in
  87. // Assert that heartbeat storage is empty.
  88. XCTAssertNil(heartbeatsBundle)
  89. // Write new value.
  90. return dummyHeartbeatsBundle
  91. }
  92. heartbeatStorage.readAndWriteAsync { heartbeatsBundle in
  93. expectation.fulfill()
  94. // Assert old value is read.
  95. XCTAssertEqual(
  96. heartbeatsBundle?.makeHeartbeatsPayload(),
  97. dummyHeartbeatsBundle.makeHeartbeatsPayload()
  98. )
  99. // Write some new value.
  100. return heartbeatsBundle
  101. }
  102. // Then
  103. wait(for: [expectation], timeout: 0.5)
  104. }
  105. func testReadAndWrite_WhenLoadFails_PassesNilToBlock() throws {
  106. // Given
  107. let expectation = expectation(description: #function)
  108. let heartbeatStorage = HeartbeatStorage(id: #function, storage: StorageFake())
  109. // When
  110. heartbeatStorage.readAndWriteAsync { heartbeatsBundle in
  111. expectation.fulfill()
  112. XCTAssertNil(heartbeatsBundle)
  113. return heartbeatsBundle
  114. }
  115. // Then
  116. wait(for: [expectation], timeout: 0.5)
  117. }
  118. func testReadAndWrite_WhenSaveFails_DoesNotAttemptRecovery() throws {
  119. // Given
  120. let expectation = expectation(description: #function)
  121. expectation.expectedFulfillmentCount = 4
  122. let storageFake = StorageFake()
  123. let heartbeatStorage = HeartbeatStorage(id: #function, storage: storageFake)
  124. var dummyHeartbeatsBundle = HeartbeatsBundle(capacity: 1)
  125. dummyHeartbeatsBundle.append(Heartbeat(agent: "dummy_agent", date: Date()))
  126. // When
  127. storageFake.onWrite = { _ in
  128. expectation.fulfill() // Fulfilled 2 times.
  129. throw StorageError.writeError
  130. }
  131. heartbeatStorage.readAndWriteAsync { heartbeatsBundle in
  132. expectation.fulfill()
  133. return dummyHeartbeatsBundle
  134. }
  135. // Then
  136. heartbeatStorage.readAndWriteAsync { heartbeatsBundle in
  137. expectation.fulfill()
  138. XCTAssertNotEqual(
  139. heartbeatsBundle?.makeHeartbeatsPayload(),
  140. dummyHeartbeatsBundle.makeHeartbeatsPayload(),
  141. "They should not be equal because the previous save failed."
  142. )
  143. return dummyHeartbeatsBundle
  144. }
  145. wait(for: [expectation], timeout: 0.5)
  146. }
  147. func testGetAndSet_ReturnsOldValueAndSetsNewValue() throws {
  148. // Given
  149. let expectation = expectation(description: #function)
  150. let heartbeatStorage = HeartbeatStorage(id: #function, storage: StorageFake())
  151. var dummyHeartbeatsBundle = HeartbeatsBundle(capacity: 1)
  152. dummyHeartbeatsBundle.append(Heartbeat(agent: "dummy_agent", date: Date()))
  153. // When
  154. XCTAssertNoThrow(
  155. try heartbeatStorage.getAndSet { heartbeatsBundle in
  156. // Assert that heartbeat storage is empty.
  157. XCTAssertNil(heartbeatsBundle)
  158. // Write new value.
  159. return dummyHeartbeatsBundle
  160. }
  161. )
  162. // Then
  163. XCTAssertNoThrow(
  164. try heartbeatStorage.getAndSet { heartbeatsBundle in
  165. expectation.fulfill()
  166. // Assert old value is read.
  167. XCTAssertEqual(
  168. heartbeatsBundle?.makeHeartbeatsPayload(),
  169. dummyHeartbeatsBundle.makeHeartbeatsPayload()
  170. )
  171. // Write some new value.
  172. return heartbeatsBundle
  173. }
  174. )
  175. wait(for: [expectation], timeout: 0.5)
  176. }
  177. func testGetAndSetAsync_ReturnsOldValueAndSetsNewValue() throws {
  178. // Given
  179. let heartbeatStorage = HeartbeatStorage(id: #function, storage: StorageFake())
  180. var dummyHeartbeatsBundle = HeartbeatsBundle(capacity: 1)
  181. dummyHeartbeatsBundle.append(Heartbeat(agent: "dummy_agent", date: Date()))
  182. // When
  183. let expectation1 = expectation(description: #function + "_1")
  184. heartbeatStorage.getAndSetAsync { heartbeatsBundle in
  185. // Assert that heartbeat storage is empty.
  186. XCTAssertNil(heartbeatsBundle)
  187. // Write new value.
  188. return dummyHeartbeatsBundle
  189. } completion: { result in
  190. switch result {
  191. case .success: break
  192. case let .failure(error): XCTFail("Error: \(error)")
  193. }
  194. expectation1.fulfill()
  195. }
  196. // Then
  197. let expectation2 = expectation(description: #function + "_2")
  198. XCTAssertNoThrow(
  199. try heartbeatStorage.getAndSet { heartbeatsBundle in
  200. // Assert old value is read.
  201. XCTAssertEqual(
  202. heartbeatsBundle?.makeHeartbeatsPayload(),
  203. dummyHeartbeatsBundle.makeHeartbeatsPayload()
  204. )
  205. // Write some new value.
  206. expectation2.fulfill()
  207. return heartbeatsBundle
  208. }
  209. )
  210. wait(for: [expectation1, expectation2], timeout: 0.5, enforceOrder: true)
  211. }
  212. func testGetAndSet_WhenLoadFails_PassesNilToBlockAndReturnsNil() throws {
  213. // Given
  214. let expectation = expectation(description: #function)
  215. expectation.expectedFulfillmentCount = 2
  216. let storageFake = StorageFake()
  217. let heartbeatStorage = HeartbeatStorage(id: #function, storage: storageFake)
  218. // When
  219. storageFake.onRead = {
  220. expectation.fulfill()
  221. return try XCTUnwrap("BAD_DATA".data(using: .utf8))
  222. }
  223. // Then
  224. try heartbeatStorage.getAndSet { heartbeatsBundle in
  225. expectation.fulfill()
  226. XCTAssertNil(heartbeatsBundle)
  227. return heartbeatsBundle
  228. }
  229. wait(for: [expectation], timeout: 0.5)
  230. }
  231. func testGetAndSetAsync_WhenLoadFails_PassesNilToBlockAndReturnsNil() throws {
  232. // Given
  233. let readExpectation = expectation(description: #function + "_1")
  234. let transformExpectation = expectation(description: #function + "_2")
  235. let completionExpectation = expectation(description: #function + "_3")
  236. let storageFake = StorageFake()
  237. let heartbeatStorage = HeartbeatStorage(id: #function, storage: storageFake)
  238. // When
  239. storageFake.onRead = {
  240. readExpectation.fulfill()
  241. return try XCTUnwrap("BAD_DATA".data(using: .utf8))
  242. }
  243. // Then
  244. heartbeatStorage.getAndSetAsync { heartbeatsBundle in
  245. XCTAssertNil(heartbeatsBundle)
  246. transformExpectation.fulfill()
  247. return heartbeatsBundle
  248. } completion: { result in
  249. switch result {
  250. case .success: break
  251. case let .failure(error): XCTFail("Error: \(error)")
  252. }
  253. completionExpectation.fulfill()
  254. }
  255. wait(
  256. for: [readExpectation, transformExpectation, completionExpectation],
  257. timeout: 0.5,
  258. enforceOrder: true
  259. )
  260. }
  261. func testGetAndSet_WhenSaveFails_ThrowsError() throws {
  262. // Given
  263. let expectation = expectation(description: #function)
  264. let storageFake = StorageFake()
  265. let heartbeatStorage = HeartbeatStorage(id: #function, storage: storageFake)
  266. // When
  267. storageFake.onWrite = { _ in
  268. expectation.fulfill()
  269. throw StorageError.writeError
  270. }
  271. // Then
  272. XCTAssertThrowsError(try heartbeatStorage.getAndSet { $0 })
  273. wait(for: [expectation], timeout: 0.5)
  274. }
  275. func testGetAndSetAsync_WhenSaveFails_ThrowsError() throws {
  276. // Given
  277. let transformExpectation = expectation(description: #function + "_1")
  278. let writeExpectation = expectation(description: #function + "_2")
  279. let completionExpectation = expectation(description: #function + "_3")
  280. let storageFake = StorageFake()
  281. let heartbeatStorage = HeartbeatStorage(id: #function, storage: storageFake)
  282. // When
  283. storageFake.onWrite = { _ in
  284. writeExpectation.fulfill()
  285. throw StorageError.writeError
  286. }
  287. // Then
  288. heartbeatStorage.getAndSetAsync { heartbeatsBundle in
  289. transformExpectation.fulfill()
  290. XCTAssertNil(heartbeatsBundle)
  291. return heartbeatsBundle
  292. } completion: { result in
  293. switch result {
  294. case .success: XCTFail("Error: unexpected success")
  295. case .failure: break
  296. }
  297. completionExpectation.fulfill()
  298. }
  299. wait(
  300. for: [transformExpectation, writeExpectation, completionExpectation],
  301. timeout: 0.5,
  302. enforceOrder: true
  303. )
  304. }
  305. func testOperationsAreSyncrononizedSerially() throws {
  306. // Given
  307. let heartbeatStorage = HeartbeatStorage(id: #function, storage: StorageFake())
  308. // When
  309. let expectations: [XCTestExpectation] = try (0 ... 1000).map { i in
  310. let expectation = expectation(description: "\(#function)_\(i)")
  311. let transform: (HeartbeatsBundle?) -> HeartbeatsBundle? = { heartbeatsBundle in
  312. expectation.fulfill()
  313. return heartbeatsBundle
  314. }
  315. switch Int.random(in: 1 ... 3) {
  316. case 1:
  317. heartbeatStorage.readAndWriteAsync(using: transform)
  318. case 2:
  319. XCTAssertNoThrow(try heartbeatStorage.getAndSet(using: transform))
  320. case 3:
  321. let getAndSet = self.expectation(description: "GetAndSetAsync_\(i)")
  322. heartbeatStorage.getAndSetAsync(using: transform) { result in
  323. switch result {
  324. case .success: break
  325. case let .failure(error):
  326. XCTFail("Unexpected: Error occurred in getAndSet_\(i), \(error)")
  327. }
  328. getAndSet.fulfill()
  329. }
  330. wait(for: [getAndSet], timeout: 1.0)
  331. default:
  332. XCTFail("Unexpected: Random number is out of range.")
  333. }
  334. return expectation
  335. }
  336. // Then
  337. wait(for: expectations, timeout: 1.0, enforceOrder: true)
  338. }
  339. }
  340. private class StorageFake: Storage {
  341. var fakeFile: Data?
  342. var onRead: (() throws -> Data)?
  343. var onWrite: ((Data?) throws -> Void)?
  344. func read() throws -> Data {
  345. if let onRead {
  346. return try onRead()
  347. } else if let data = fakeFile {
  348. return data
  349. } else {
  350. throw StorageError.readError
  351. }
  352. }
  353. func write(_ data: Data?) throws {
  354. if let onWrite {
  355. return try onWrite(data)
  356. } else {
  357. fakeFile = data
  358. }
  359. }
  360. }