ImageCacheTests.swift 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. //
  2. // ImageCacheTests.swift
  3. // Kingfisher
  4. //
  5. // Created by Wei Wang on 15/4/10.
  6. //
  7. // Copyright (c) 2018 Wei Wang <onevcat@gmail.com>
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. import XCTest
  27. @testable import Kingfisher
  28. class ImageCacheTests: XCTestCase {
  29. var cache: ImageCache!
  30. var observer: NSObjectProtocol!
  31. private var cacheName = "com.onevcat.Kingfisher.ImageCache.test"
  32. override func setUp() {
  33. super.setUp()
  34. // Put setup code here. This method is called before the invocation of each test method in the class.
  35. let uuid = UUID().uuidString
  36. cacheName = "test-\(uuid)"
  37. cache = ImageCache(name: cacheName)
  38. }
  39. override func tearDown() {
  40. // Put teardown code here. This method is called after the invocation of each test method in the class.
  41. super.tearDown()
  42. clearCaches([cache])
  43. cache = nil
  44. observer = nil
  45. }
  46. func testCustomCachePath() {
  47. let customPath = "/path/to/image/cache"
  48. let cache = ImageCache(name: "test", path: customPath)
  49. XCTAssertEqual(cache.diskCachePath, customPath + "/com.onevcat.Kingfisher.ImageCache.test", "Custom disk cache path set correctly")
  50. }
  51. func testMaxCachePeriodInSecond() {
  52. cache.maxCachePeriodInSecond = 1
  53. XCTAssert(cache.maxCachePeriodInSecond == 1, "maxCachePeriodInSecond should be able to be set.")
  54. }
  55. func testMaxMemorySize() {
  56. cache.maxMemoryCost = 1
  57. XCTAssert(cache.maxMemoryCost == 1, "maxMemoryCost should be able to be set.")
  58. }
  59. func testMaxDiskCacheSize() {
  60. cache.maxDiskCacheSize = 1
  61. XCTAssert(cache.maxDiskCacheSize == 1, "maxDiskCacheSize should be able to be set.")
  62. }
  63. func testClearDiskCache() {
  64. let expectation = self.expectation(description: "wait for clearing disk cache")
  65. let key = testKeys[0]
  66. cache.store(testImage, original: testImageData as Data, forKey: key, toDisk: true) {
  67. self.cache.clearMemoryCache()
  68. let cacheResult = self.cache.imageCachedType(forKey: key)
  69. XCTAssertTrue(cacheResult.cached, "Should be cached")
  70. XCTAssert(cacheResult == .disk, "Should be cached in disk")
  71. self.cache.clearDiskCache {
  72. let cacheResult = self.cache.imageCachedType(forKey: key)
  73. XCTAssertFalse(cacheResult.cached, "Should be not cached")
  74. expectation.fulfill()
  75. }
  76. }
  77. waitForExpectations(timeout: 10, handler:nil)
  78. }
  79. func testClearMemoryCache() {
  80. let expectation = self.expectation(description: "wait for retrieving image")
  81. cache.store(testImage, original: testImageData as Data, forKey: testKeys[0], toDisk: true) { () -> Void in
  82. self.cache.clearMemoryCache()
  83. self.cache.retrieveImage(forKey: testKeys[0], options: nil, completionHandler: { (image, type) -> Void in
  84. XCTAssert(image != nil && type == .disk, "Should be cached in disk. But \(type)")
  85. expectation.fulfill()
  86. })
  87. }
  88. waitForExpectations(timeout: 5, handler: nil)
  89. }
  90. func testNoImageFound() {
  91. let expectation = self.expectation(description: "wait for retrieving image")
  92. cache.clearDiskCache {
  93. self.cache.retrieveImage(forKey: testKeys[0], options: nil, completionHandler: { (image, type) -> Void in
  94. XCTAssert(image == nil, "Should not be cached in memory yet")
  95. expectation.fulfill()
  96. })
  97. return
  98. }
  99. waitForExpectations(timeout: 5, handler: nil)
  100. }
  101. func testStoreImageInMemory() {
  102. let expectation = self.expectation(description: "wait for retrieving image")
  103. cache.store(testImage, forKey: testKeys[0], toDisk: false) { () -> Void in
  104. self.cache.retrieveImage(forKey: testKeys[0], options: nil, completionHandler: { (image, type) -> Void in
  105. XCTAssert(image != nil && type == .memory, "Should be cached in memory.")
  106. expectation.fulfill()
  107. })
  108. return
  109. }
  110. waitForExpectations(timeout: 5, handler: nil)
  111. }
  112. func testStoreMultipleImages() {
  113. let expectation = self.expectation(description: "wait for writing image")
  114. storeMultipleImages { () -> Void in
  115. let diskCachePath = self.cache.diskCachePath
  116. let files: [String]?
  117. do {
  118. files = try FileManager.default.contentsOfDirectory(atPath: diskCachePath)
  119. } catch _ {
  120. files = nil
  121. }
  122. XCTAssert(files?.count == 4, "All test images should be at locaitons. Expected 4, actually \(String(describing: files?.count))")
  123. expectation.fulfill()
  124. }
  125. waitForExpectations(timeout: 5, handler: nil)
  126. }
  127. func testCachedFileExists() {
  128. let expectation = self.expectation(description: "cache does contain image")
  129. let URLString = testKeys[0]
  130. let url = URL(string: URLString)!
  131. let exists = cache.imageCachedType(forKey: url.cacheKey).cached
  132. XCTAssertFalse(exists)
  133. cache.retrieveImage(forKey: URLString, options: nil, completionHandler: { (image, type) -> Void in
  134. XCTAssertNil(image, "Should not be cached yet")
  135. XCTAssertEqual(type, .none)
  136. self.cache.store(testImage, forKey: URLString, toDisk: true) { () -> Void in
  137. self.cache.retrieveImage(forKey: URLString, options: nil, completionHandler: { (image, type) -> Void in
  138. XCTAssertNotNil(image, "Should be cached (memory or disk)")
  139. XCTAssertEqual(type, .memory)
  140. let exists = self.cache.imageCachedType(forKey: url.cacheKey).cached
  141. XCTAssertTrue(exists, "Image should exist in the cache (memory or disk)")
  142. self.cache.clearMemoryCache()
  143. self.cache.retrieveImage(forKey: URLString, options: nil, completionHandler: { (image, type) -> Void in
  144. XCTAssertNotNil(image, "Should be cached (disk)")
  145. XCTAssertEqual(type, CacheType.disk)
  146. let exists = self.cache.imageCachedType(forKey: url.cacheKey).cached
  147. XCTAssertTrue(exists, "Image should exist in the cache (disk)")
  148. expectation.fulfill()
  149. })
  150. })
  151. }
  152. })
  153. waitForExpectations(timeout: 5, handler: nil)
  154. }
  155. func testCachedFileDoesNotExist() {
  156. let URLString = testKeys[0]
  157. let url = URL(string: URLString)!
  158. let exists = cache.imageCachedType(forKey: url.cacheKey).cached
  159. XCTAssertFalse(exists)
  160. }
  161. func testCachedFileWithCustomPathExtensionExists() {
  162. cache.pathExtension = "jpg"
  163. let expectation = self.expectation(description: "cache with custom path extension does contain image")
  164. let URLString = testKeys[0]
  165. let url = URL(string: URLString)!
  166. let exists = cache.imageCachedType(forKey: url.cacheKey).cached
  167. XCTAssertFalse(exists)
  168. cache.retrieveImage(forKey: URLString, options: nil, completionHandler: { (image, type) -> Void in
  169. XCTAssertNil(image, "Should not be cached yet")
  170. XCTAssertEqual(type, .none)
  171. self.cache.store(testImage, forKey: URLString, toDisk: true) { () -> Void in
  172. self.cache.retrieveImage(forKey: URLString, options: nil, completionHandler: { (image, type) -> Void in
  173. XCTAssertNotNil(image, "Should be cached (memory or disk)")
  174. XCTAssertEqual(type, .memory)
  175. let exists = self.cache.imageCachedType(forKey: url.cacheKey).cached
  176. XCTAssertTrue(exists, "Image should exist in the cache (memory or disk)")
  177. self.cache.clearMemoryCache()
  178. self.cache.retrieveImage(forKey: URLString, options: nil, completionHandler: { (image, type) -> Void in
  179. XCTAssertNotNil(image, "Should be cached (disk)")
  180. XCTAssertEqual(type, CacheType.disk)
  181. let exists = self.cache.imageCachedType(forKey: url.cacheKey).cached
  182. XCTAssertTrue(exists, "Image should exist in the cache (disk)")
  183. let cachePath = self.cache.cachePath(forKey: url.cacheKey)
  184. let hasExtension = cachePath.hasSuffix(".jpg")
  185. XCTAssert(hasExtension, "Should have .jpg file extension")
  186. expectation.fulfill()
  187. })
  188. })
  189. }
  190. })
  191. waitForExpectations(timeout: 5, handler: nil)
  192. }
  193. func testCachedImageIsFetchedSyncronouslyFromTheMemoryCache() {
  194. cache.store(testImage, forKey: testKeys[0], toDisk: false) { () -> Void in
  195. // do nothing
  196. }
  197. var foundImage: Image?
  198. cache.retrieveImage(forKey: testKeys[0], options: [.backgroundDecode]) { (image, type) -> Void in
  199. foundImage = image
  200. }
  201. XCTAssertEqual(testImage, foundImage, "should have found the image immediately")
  202. }
  203. func testIsImageCachedForKey() {
  204. let expectation = self.expectation(description: "wait for caching image")
  205. XCTAssert(self.cache.imageCachedType(forKey: testKeys[0]).cached == false, "This image should not be cached yet.")
  206. self.cache.store(testImage, original: testImageData as Data, forKey: testKeys[0], toDisk: true) { () -> Void in
  207. XCTAssert(self.cache.imageCachedType(forKey: testKeys[0]).cached == true, "This image should be already cached.")
  208. expectation.fulfill()
  209. }
  210. self.waitForExpectations(timeout: 5, handler: nil)
  211. }
  212. func testRetrievingImagePerformance() {
  213. let expectation = self.expectation(description: "wait for retrieving image")
  214. self.cache.store(testImage, original: testImageData as Data, forKey: testKeys[0], toDisk: true) { () -> Void in
  215. self.measure({ () -> Void in
  216. for _ in 1 ..< 200 {
  217. _ = self.cache.retrieveImageInDiskCache(forKey: testKeys[0])
  218. }
  219. })
  220. expectation.fulfill()
  221. }
  222. self.waitForExpectations(timeout: 20, handler: nil)
  223. }
  224. func testCleanDiskCacheNotification() {
  225. let expectation = self.expectation(description: "wait for retrieving image")
  226. cache.store(testImage, original: testImageData as Data?, forKey: testKeys[0], toDisk: true) { () -> Void in
  227. self.observer = NotificationCenter.default.addObserver(forName: .KingfisherDidCleanDiskCache, object: self.cache, queue: OperationQueue.main, using: { (noti) -> Void in
  228. let receivedCache = noti.object as? ImageCache
  229. XCTAssertNotNil(receivedCache)
  230. XCTAssert(receivedCache === self.cache, "The object of notification should be the cache object.")
  231. guard let hashes = (noti as NSNotification).userInfo?[KingfisherDiskCacheCleanedHashKey] as? [String] else {
  232. XCTFail("The clean disk cache notification should contains Strings in key 'KingfisherDiskCacheCleanedHashKey'")
  233. expectation.fulfill()
  234. return
  235. }
  236. XCTAssertEqual(1, hashes.count, "There should be one and only one file cleaned")
  237. XCTAssertEqual(hashes.first!, self.cache.hash(forKey: testKeys[0]), "The cleaned file should be the stored one.")
  238. NotificationCenter.default.removeObserver(self.observer)
  239. expectation.fulfill()
  240. })
  241. self.cache.maxCachePeriodInSecond = 0
  242. self.cache.cleanExpiredDiskCache()
  243. }
  244. waitForExpectations(timeout: 5, handler: nil)
  245. }
  246. func testCannotRetrieveCacheWithProcessorIdentifier() {
  247. let expectation = self.expectation(description: "wait for retrieving image")
  248. let p = RoundCornerImageProcessor(cornerRadius: 40)
  249. cache.store(testImage, original: testImageData as Data?, forKey: testKeys[0], toDisk: true) { () -> Void in
  250. self.cache.retrieveImage(forKey: testKeys[0], options: [.processor(p)], completionHandler: { (image, type) -> Void in
  251. XCTAssert(image == nil, "The image with prosossor should not be cached yet.")
  252. expectation.fulfill()
  253. })
  254. }
  255. waitForExpectations(timeout: 5, handler: nil)
  256. }
  257. func testRetrieveCacheWithProcessorIdentifier() {
  258. let expectation = self.expectation(description: "wait for retrieving image")
  259. let p = RoundCornerImageProcessor(cornerRadius: 40)
  260. cache.store(testImage, original: testImageData as Data?, forKey: testKeys[0], processorIdentifier: p.identifier,toDisk: true) { () -> Void in
  261. self.cache.retrieveImage(forKey: testKeys[0], options: [.processor(p)], completionHandler: { (image, type) -> Void in
  262. XCTAssert(image != nil, "The image with prosossor should already be cached.")
  263. expectation.fulfill()
  264. })
  265. }
  266. waitForExpectations(timeout: 5, handler: nil)
  267. }
  268. #if os(iOS) || os(tvOS) || os(watchOS)
  269. func testGettingMemoryCachedImageCouldBeModified() {
  270. let expectation = self.expectation(description: "wait for retrieving image")
  271. var modifierCalled = false
  272. let modifier = AnyImageModifier { image in
  273. modifierCalled = true
  274. return image.withRenderingMode(.alwaysTemplate)
  275. }
  276. cache.store(testImage, original: testImageData as Data?, forKey: testKeys[0]) {
  277. self.cache.retrieveImage(forKey: testKeys[0], options: [.imageModifier(modifier)]) {
  278. image, _ in
  279. XCTAssertTrue(modifierCalled)
  280. XCTAssertEqual(image?.renderingMode, .alwaysTemplate)
  281. expectation.fulfill()
  282. }
  283. }
  284. waitForExpectations(timeout: 5, handler: nil)
  285. }
  286. func testGettingDiskCachedImageCouldBeModified() {
  287. let expectation = self.expectation(description: "wait for retrieving image")
  288. var modifierCalled = false
  289. let modifier = AnyImageModifier { image in
  290. modifierCalled = true
  291. return image.withRenderingMode(.alwaysTemplate)
  292. }
  293. cache.store(testImage, original: testImageData as Data?, forKey: testKeys[0]) {
  294. self.cache.clearMemoryCache()
  295. self.cache.retrieveImage(forKey: testKeys[0], options: [.imageModifier(modifier)]) {
  296. image, _ in
  297. XCTAssertTrue(modifierCalled)
  298. XCTAssertEqual(image?.renderingMode, .alwaysTemplate)
  299. expectation.fulfill()
  300. }
  301. }
  302. waitForExpectations(timeout: 5, handler: nil)
  303. }
  304. #endif
  305. // MARK: - Helper
  306. func storeMultipleImages(_ completionHandler:@escaping ()->()) {
  307. let group = DispatchGroup()
  308. group.enter()
  309. cache.store(testImage, original: testImageData as Data?, forKey: testKeys[0], toDisk: true) { () -> Void in
  310. group.leave()
  311. }
  312. group.enter()
  313. cache.store(testImage, original: testImageData as Data?, forKey: testKeys[1], toDisk: true) { () -> Void in
  314. group.leave()
  315. }
  316. group.enter()
  317. cache.store(testImage, original: testImageData as Data?, forKey: testKeys[2], toDisk: true) { () -> Void in
  318. group.leave()
  319. }
  320. group.enter()
  321. cache.store(testImage, original: testImageData as Data?, forKey: testKeys[3], toDisk: true) { () -> Void in
  322. group.leave()
  323. }
  324. group.notify(queue: .main, execute: completionHandler)
  325. }
  326. }