HeartbeatStorageTests.swift 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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. import XCTest
  15. @testable import FirebaseCoreInternal
  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 testGetAndSet_WhenLoadFails_PassesNilToBlockAndReturnsNil() throws {
  178. // Given
  179. let expectation = expectation(description: #function)
  180. expectation.expectedFulfillmentCount = 2
  181. let storageFake = StorageFake()
  182. let heartbeatStorage = HeartbeatStorage(id: #function, storage: storageFake)
  183. // When
  184. storageFake.onRead = {
  185. expectation.fulfill()
  186. return try XCTUnwrap("BAD_DATA".data(using: .utf8))
  187. }
  188. // Then
  189. try heartbeatStorage.getAndSet { heartbeatsBundle in
  190. expectation.fulfill()
  191. XCTAssertNil(heartbeatsBundle)
  192. return heartbeatsBundle
  193. }
  194. wait(for: [expectation], timeout: 0.5)
  195. }
  196. func testGetAndSet_WhenSaveFails_ThrowsError() throws {
  197. // Given
  198. let expectation = expectation(description: #function)
  199. let storageFake = StorageFake()
  200. let heartbeatStorage = HeartbeatStorage(id: #function, storage: storageFake)
  201. // When
  202. storageFake.onWrite = { _ in
  203. expectation.fulfill()
  204. throw StorageError.writeError
  205. }
  206. // Then
  207. XCTAssertThrowsError(try heartbeatStorage.getAndSet { $0 })
  208. wait(for: [expectation], timeout: 0.5)
  209. }
  210. func testOperationsAreSynrononizedSerially() throws {
  211. // Given
  212. let heartbeatStorage = HeartbeatStorage(id: #function, storage: StorageFake())
  213. // When
  214. let expectations: [XCTestExpectation] = try (0 ... 1000).map { i in
  215. let expectation = expectation(description: "\(#function)_\(i)")
  216. let transform: (HeartbeatsBundle?) -> HeartbeatsBundle? = { heartbeatsBundle in
  217. expectation.fulfill()
  218. return heartbeatsBundle
  219. }
  220. if /* randomChoice */ .random() {
  221. heartbeatStorage.readAndWriteAsync(using: transform)
  222. } else {
  223. XCTAssertNoThrow(try heartbeatStorage.getAndSet(using: transform))
  224. }
  225. return expectation
  226. }
  227. // Then
  228. wait(for: expectations, timeout: 1.0, enforceOrder: true)
  229. }
  230. }
  231. private class StorageFake: Storage {
  232. var fakeFile: Data?
  233. var onRead: (() throws -> Data)?
  234. var onWrite: ((Data?) throws -> Void)?
  235. func read() throws -> Data {
  236. if let onRead = onRead {
  237. return try onRead()
  238. } else if let data = fakeFile {
  239. return data
  240. } else {
  241. throw StorageError.readError
  242. }
  243. }
  244. func write(_ data: Data?) throws {
  245. if let onWrite = onWrite {
  246. return try onWrite(data)
  247. } else {
  248. fakeFile = data
  249. }
  250. }
  251. }