KingfisherManagerTests.swift 71 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859
  1. //
  2. // KingfisherManagerTests.swift
  3. // Kingfisher
  4. //
  5. // Created by Wei Wang on 15/10/22.
  6. //
  7. // Copyright (c) 2019 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. actor CallingChecker {
  29. var called = false
  30. func mark() {
  31. called = true
  32. }
  33. func checkCancelBehavior(
  34. stub: LSStubResponseDSL,
  35. block: @escaping () async throws -> Void
  36. ) async throws {
  37. let task = Task {
  38. do {
  39. _ = try await block()
  40. XCTFail()
  41. } catch {
  42. mark()
  43. XCTAssertTrue((error as! KingfisherError).isTaskCancelled)
  44. }
  45. }
  46. try await Task.sleep(nanoseconds: NSEC_PER_SEC / 10)
  47. task.cancel()
  48. _ = stub.go()
  49. try await Task.sleep(nanoseconds: NSEC_PER_SEC / 10)
  50. XCTAssertTrue(called)
  51. }
  52. }
  53. class KingfisherManagerTests: XCTestCase {
  54. var manager: KingfisherManager!
  55. override class func setUp() {
  56. super.setUp()
  57. LSNocilla.sharedInstance().start()
  58. }
  59. override class func tearDown() {
  60. LSNocilla.sharedInstance().stop()
  61. super.tearDown()
  62. }
  63. override func setUp() {
  64. super.setUp()
  65. // Put setup code here. This method is called before the invocation of each test method in the class.
  66. let uuid = UUID()
  67. let downloader = ImageDownloader(name: "test.manager.\(uuid.uuidString)")
  68. let cache = ImageCache(name: "test.cache.\(uuid.uuidString)")
  69. manager = KingfisherManager(downloader: downloader, cache: cache)
  70. manager.defaultOptions = [.waitForCache]
  71. }
  72. override func tearDown() {
  73. LSNocilla.sharedInstance().clearStubs()
  74. clearCaches([manager.cache])
  75. cleanDefaultCache()
  76. manager = nil
  77. super.tearDown()
  78. }
  79. func testRetrieveImage() {
  80. let exp = expectation(description: #function)
  81. let url = testURLs[0]
  82. stub(url, data: testImageData)
  83. let manager = self.manager!
  84. manager.retrieveImage(with: url) { result in
  85. XCTAssertNotNil(result.value?.image)
  86. XCTAssertEqual(result.value!.cacheType, .none)
  87. manager.retrieveImage(with: url) { result in
  88. XCTAssertNotNil(result.value?.image)
  89. XCTAssertEqual(result.value!.cacheType, .memory)
  90. manager.cache.clearMemoryCache()
  91. manager.retrieveImage(with: url) { result in
  92. XCTAssertNotNil(result.value?.image)
  93. XCTAssertEqual(result.value!.cacheType, .disk)
  94. manager.cache.clearMemoryCache()
  95. manager.cache.clearDiskCache {
  96. manager.retrieveImage(with: url) { result in
  97. XCTAssertNotNil(result.value?.image)
  98. XCTAssertEqual(result.value!.cacheType, .none)
  99. exp.fulfill()
  100. }}}}}
  101. waitForExpectations(timeout: 3, handler: nil)
  102. }
  103. func testRetrieveImageAsync() async throws {
  104. let url = testURLs[0]
  105. stub(url, data: testImageData)
  106. let manager = self.manager!
  107. var result = try await manager.retrieveImage(with: url)
  108. XCTAssertNotNil(result.image)
  109. XCTAssertEqual(result.cacheType, .none)
  110. result = try await manager.retrieveImage(with: url)
  111. XCTAssertNotNil(result.image)
  112. XCTAssertEqual(result.cacheType, .memory)
  113. manager.cache.clearMemoryCache()
  114. result = try await manager.retrieveImage(with: url)
  115. XCTAssertNotNil(result.image)
  116. XCTAssertEqual(result.cacheType, .disk)
  117. manager.cache.clearMemoryCache()
  118. await manager.cache.clearDiskCache()
  119. result = try await manager.retrieveImage(with: url)
  120. XCTAssertNotNil(result.image)
  121. XCTAssertEqual(result.cacheType, .none)
  122. }
  123. func testRetrieveImageWithProcessor() {
  124. let exp = expectation(description: #function)
  125. let url = testURLs[0]
  126. stub(url, data: testImageData)
  127. let p = RoundCornerImageProcessor(cornerRadius: 20)
  128. let manager = self.manager!
  129. manager.retrieveImage(with: url, options: [.processor(p)]) { result in
  130. XCTAssertNotNil(result.value?.image)
  131. XCTAssertEqual(result.value!.cacheType, .none)
  132. manager.retrieveImage(with: url) { result in
  133. XCTAssertNotNil(result.value?.image)
  134. XCTAssertEqual(result.value!.cacheType, .none,
  135. "Need a processor to get correct image. Cannot get from cache, need download again.")
  136. manager.retrieveImage(with: url, options: [.processor(p)]) { result in
  137. XCTAssertNotNil(result.value?.image)
  138. XCTAssertEqual(result.value!.cacheType, .memory)
  139. self.manager.cache.clearMemoryCache()
  140. manager.retrieveImage(with: url, options: [.processor(p)]) { result in
  141. XCTAssertNotNil(result.value?.image)
  142. XCTAssertEqual(result.value!.cacheType, .disk)
  143. self.manager.cache.clearMemoryCache()
  144. self.manager.cache.clearDiskCache {
  145. self.manager.retrieveImage(with: url, options: [.processor(p)]) { result in
  146. XCTAssertNotNil(result.value?.image)
  147. XCTAssertEqual(result.value!.cacheType, .none)
  148. exp.fulfill()
  149. }}}}}}
  150. waitForExpectations(timeout: 3, handler: nil)
  151. }
  152. func testRetrieveImageForceRefresh() {
  153. let exp = expectation(description: #function)
  154. let url = testURLs[0]
  155. stub(url, data: testImageData)
  156. manager.cache.store(
  157. testImage,
  158. original: testImageData,
  159. forKey: url.cacheKey,
  160. processorIdentifier: DefaultImageProcessor.default.identifier,
  161. cacheSerializer: DefaultCacheSerializer.default,
  162. toDisk: true)
  163. {
  164. _ in
  165. XCTAssertTrue(self.manager.cache.imageCachedType(forKey: url.cacheKey).cached)
  166. self.manager.retrieveImage(with: url, options: [.forceRefresh]) { result in
  167. XCTAssertNotNil(result.value?.image)
  168. XCTAssertEqual(result.value!.cacheType, .none)
  169. exp.fulfill()
  170. }
  171. }
  172. waitForExpectations(timeout: 3, handler: nil)
  173. }
  174. func testRetrieveImageCancel() {
  175. let exp = expectation(description: #function)
  176. let url = testURLs[0]
  177. let stub = delayedStub(url, data: testImageData, length: 123)
  178. let task = manager.retrieveImage(with: url) {
  179. result in
  180. XCTAssertNotNil(result.error)
  181. XCTAssertTrue(result.error!.isTaskCancelled)
  182. exp.fulfill()
  183. }
  184. XCTAssertNotNil(task)
  185. task?.cancel()
  186. _ = stub.go()
  187. waitForExpectations(timeout: 3, handler: nil)
  188. }
  189. func testRetrieveImageCancelAsync() async throws {
  190. let url = testURLs[0]
  191. let stub = delayedStub(url, data: testImageData, length: 123)
  192. let checker = CallingChecker()
  193. try await checker.checkCancelBehavior(stub: stub) {
  194. _ = try await self.manager.retrieveImage(with: url)
  195. }
  196. }
  197. /// Test to reproduce the Swift Task Continuation Misuse issue
  198. /// This test verifies that continuations are properly resumed even under rapid cancellation scenarios
  199. ///
  200. /// NOTE: Single test run may not reproduce the issue, but running this test repeatedly
  201. /// (e.g., 100 times in Xcode) will almost certainly trigger the SWIFT TASK CONTINUATION MISUSE warning.
  202. /// This confirms the existence of a race condition in the async retrieveImage implementation.
  203. func testRetrieveImageContinuationMisuseReproduction() async throws {
  204. let url = testURLs[0]
  205. let stub = delayedStub(url, data: testImageData, length: 123)
  206. // Create multiple concurrent tasks that are cancelled quickly
  207. // This should reproduce the continuation leak scenario
  208. let taskCount = 50 // Increased to make race condition more likely
  209. var tasks: [Task<Void, Never>] = []
  210. for i in 0..<taskCount {
  211. let task = Task {
  212. do {
  213. _ = try await self.manager.retrieveImage(with: url)
  214. // If we reach here without cancellation, something is wrong
  215. print("Task \(i) completed without cancellation - unexpected")
  216. } catch {
  217. // This should be a cancellation error
  218. if let kfError = error as? KingfisherError, kfError.isTaskCancelled {
  219. // Expected cancellation
  220. } else if error is CancellationError {
  221. // Expected cancellation
  222. } else {
  223. print("Task \(i) failed with unexpected error: \(error)")
  224. }
  225. }
  226. }
  227. tasks.append(task)
  228. // Cancel immediately after creation to create race conditions
  229. task.cancel()
  230. // Add a tiny delay to create more variation in timing
  231. if i % 5 == 0 {
  232. try await Task.sleep(nanoseconds: NSEC_PER_SEC / 1000) // 1ms
  233. }
  234. }
  235. // Wait a bit to ensure all tasks have had a chance to start and be cancelled
  236. try await Task.sleep(nanoseconds: NSEC_PER_SEC / 10) // 100ms
  237. // Complete the stub to allow any pending operations to finish
  238. _ = stub.go()
  239. // Wait for all tasks to complete
  240. for task in tasks {
  241. await task.value
  242. }
  243. // If we get here without hanging, the continuation handling is working correctly
  244. // The test passes if no SWIFT TASK CONTINUATION MISUSE warning is printed to console
  245. }
  246. /// Another test that creates a more specific race condition scenario
  247. /// This test checks the exact timing described in the issue
  248. ///
  249. /// NOTE: Like the previous test, run this repeatedly to increase chances of reproducing the issue.
  250. func testRetrieveImageRaceConditionSpecific() async throws {
  251. let url = testURLs[0]
  252. let stub = delayedStub(url, data: testImageData, length: 5000) // Longer delay
  253. // This creates the specific race condition:
  254. // 1. Task starts
  255. // 2. Gets to the withCheckedThrowingContinuation
  256. // 3. Cancel happens before the inner retrieveImage call completes setup
  257. let task = Task {
  258. do {
  259. _ = try await self.manager.retrieveImage(with: url)
  260. XCTFail("Task should have been cancelled")
  261. } catch {
  262. // Should be cancelled
  263. XCTAssertTrue((error as? KingfisherError)?.isTaskCancelled == true)
  264. }
  265. }
  266. // Very short delay to let the task start but not complete
  267. try await Task.sleep(nanoseconds: NSEC_PER_SEC / 1000) // 1ms
  268. // Cancel before the network stub is triggered
  269. task.cancel()
  270. // Now trigger the network response
  271. _ = stub.go()
  272. // Wait for the task to complete
  273. await task.value
  274. }
  275. func testSuccessCompletionHandlerRunningOnMainQueueByDefault() {
  276. let progressExpectation = expectation(description: "progressBlock running on main queue")
  277. let completionExpectation = expectation(description: "completionHandler running on main queue")
  278. let url = testURLs[0]
  279. stub(url, data: testImageData, length: 123)
  280. manager.retrieveImage(with: url, options: nil, progressBlock: { _, _ in
  281. XCTAssertTrue(Thread.isMainThread)
  282. progressExpectation.fulfill()})
  283. {
  284. result in
  285. XCTAssertNil(result.error)
  286. XCTAssertTrue(Thread.isMainThread)
  287. completionExpectation.fulfill()
  288. }
  289. waitForExpectations(timeout: 3, handler: nil)
  290. }
  291. func testShouldNotDownloadImageIfCacheOnlyAndNotInCache() {
  292. let exp = expectation(description: #function)
  293. let url = testURLs[0]
  294. stub(url, data: testImageData)
  295. manager.retrieveImage(with: url, options: [.onlyFromCache]) { result in
  296. XCTAssertNil(result.value)
  297. XCTAssertNotNil(result.error)
  298. if case .cacheError(reason: .imageNotExisting(let key)) = result.error! {
  299. XCTAssertEqual(key, url.cacheKey)
  300. } else {
  301. XCTFail()
  302. }
  303. exp.fulfill()
  304. }
  305. waitForExpectations(timeout: 3, handler: nil)
  306. }
  307. func testErrorCompletionHandlerRunningOnMainQueueByDefault() {
  308. let exp = expectation(description: #function)
  309. let url = testURLs[0]
  310. stub(url, data: testImageData, statusCode: 404)
  311. manager.retrieveImage(with: url) { result in
  312. XCTAssertNotNil(result.error)
  313. XCTAssertTrue(Thread.isMainThread)
  314. XCTAssertTrue(result.error!.isInvalidResponseStatusCode(404))
  315. exp.fulfill()
  316. }
  317. waitForExpectations(timeout: 3, handler: nil)
  318. }
  319. func testSuccessCompletionHandlerRunningOnCustomQueue() {
  320. let progressExpectation = expectation(description: "progressBlock running on custom queue")
  321. let completionExpectation = expectation(description: "completionHandler running on custom queue")
  322. let url = testURLs[0]
  323. stub(url, data: testImageData, length: 123)
  324. let customQueue = DispatchQueue(label: "com.kingfisher.testQueue")
  325. let options: KingfisherOptionsInfo = [.callbackQueue(.dispatch(customQueue))]
  326. manager.retrieveImage(with: url, options: options, progressBlock: { _, _ in
  327. XCTAssertTrue(Thread.isMainThread)
  328. progressExpectation.fulfill()
  329. })
  330. {
  331. result in
  332. XCTAssertNil(result.error)
  333. dispatchPrecondition(condition: .onQueue(customQueue))
  334. completionExpectation.fulfill()
  335. }
  336. waitForExpectations(timeout: 3, handler: nil)
  337. }
  338. func testLoadCacheCompletionHandlerRunningOnCustomQueue() {
  339. let completionExpectation = expectation(description: "completionHandler running on custom queue")
  340. let url = testURLs[0]
  341. manager.cache.store(testImage, forKey: url.cacheKey)
  342. let customQueue = DispatchQueue(label: "com.kingfisher.testQueue")
  343. manager.retrieveImage(with: url, options: [.callbackQueue(.dispatch(customQueue))]) {
  344. result in
  345. XCTAssertNil(result.error)
  346. dispatchPrecondition(condition: .onQueue(customQueue))
  347. completionExpectation.fulfill()
  348. }
  349. waitForExpectations(timeout: 3, handler: nil)
  350. }
  351. func testDefaultOptionCouldApply() {
  352. let exp = expectation(description: #function)
  353. let url = testURLs[0]
  354. stub(url, data: testImageData)
  355. manager.defaultOptions = [.scaleFactor(2)]
  356. manager.retrieveImage(with: url, completionHandler: { result in
  357. #if !os(macOS)
  358. XCTAssertEqual(result.value!.image.scale, 2.0)
  359. #endif
  360. exp.fulfill()
  361. })
  362. waitForExpectations(timeout: 3, handler: nil)
  363. }
  364. func testOriginalImageCouldBeStored() {
  365. let exp = expectation(description: #function)
  366. let url = testURLs[0]
  367. stub(url, data: testImageData)
  368. let manager = self.manager!
  369. let p = SimpleProcessor()
  370. let options = KingfisherParsedOptionsInfo([.processor(p), .cacheOriginalImage])
  371. let source = Source.network(url)
  372. let context = RetrievingContext(options: options, originalSource: source)
  373. manager.loadAndCacheImage(source: .network(url), context: context) { result in
  374. var imageCached = manager.cache.imageCachedType(forKey: url.cacheKey, processorIdentifier: p.identifier)
  375. var originalCached = manager.cache.imageCachedType(forKey: url.cacheKey)
  376. XCTAssertEqual(imageCached, .memory)
  377. delay(0.3) {
  378. manager.cache.clearMemoryCache()
  379. imageCached = manager.cache.imageCachedType(forKey: url.cacheKey, processorIdentifier: p.identifier)
  380. originalCached = manager.cache.imageCachedType(forKey: url.cacheKey)
  381. XCTAssertEqual(imageCached, .disk)
  382. XCTAssertEqual(originalCached, .disk)
  383. exp.fulfill()
  384. }
  385. }
  386. waitForExpectations(timeout: 3, handler: nil)
  387. }
  388. func testOriginalImageNotBeStoredWithoutOptionSet() {
  389. let exp = expectation(description: #function)
  390. let url = testURLs[0]
  391. stub(url, data: testImageData)
  392. let p = SimpleProcessor()
  393. let options = KingfisherParsedOptionsInfo([.processor(p), .waitForCache])
  394. let source = Source.network(url)
  395. let context = RetrievingContext(options: options, originalSource: source)
  396. manager.loadAndCacheImage(source: .network(url), context: context) {
  397. result in
  398. var imageCached = self.manager.cache.imageCachedType(forKey: url.cacheKey, processorIdentifier: p.identifier)
  399. var originalCached = self.manager.cache.imageCachedType(forKey: url.cacheKey)
  400. XCTAssertEqual(imageCached, .memory)
  401. XCTAssertEqual(originalCached, .none)
  402. self.manager.cache.clearMemoryCache()
  403. imageCached = self.manager.cache.imageCachedType(forKey: url.cacheKey, processorIdentifier: p.identifier)
  404. originalCached = self.manager.cache.imageCachedType(forKey: url.cacheKey)
  405. XCTAssertEqual(imageCached, .disk)
  406. XCTAssertEqual(originalCached, .none)
  407. exp.fulfill()
  408. }
  409. waitForExpectations(timeout: 3, handler: nil)
  410. }
  411. func testCouldProcessOnOriginalImage() {
  412. let exp = expectation(description: #function)
  413. let url = testURLs[0]
  414. manager.cache.store(
  415. testImage,
  416. original: testImageData,
  417. forKey: url.cacheKey,
  418. processorIdentifier: DefaultImageProcessor.default.identifier,
  419. cacheSerializer: DefaultCacheSerializer.default,
  420. toDisk: true)
  421. {
  422. _ in
  423. let p = SimpleProcessor()
  424. let cached = self.manager.cache.imageCachedType(forKey: url.cacheKey, processorIdentifier: p.identifier)
  425. XCTAssertFalse(cached.cached)
  426. // No downloading will happen
  427. self.manager.retrieveImage(with: url, options: [.processor(p)]) { result in
  428. XCTAssertNotNil(result.value?.image)
  429. XCTAssertEqual(result.value!.cacheType, .none)
  430. XCTAssertTrue(p.processed)
  431. // The processed image should be cached
  432. let cached = self.manager.cache.imageCachedType(forKey: url.cacheKey, processorIdentifier: p.identifier)
  433. XCTAssertTrue(cached.cached)
  434. exp.fulfill()
  435. }
  436. }
  437. waitForExpectations(timeout: 3, handler: nil)
  438. }
  439. func testFailingProcessOnOriginalImage() {
  440. let exp = expectation(description: #function)
  441. let url = testURLs[0]
  442. manager.cache.store(
  443. testImage,
  444. original: testImageData,
  445. forKey: url.cacheKey,
  446. processorIdentifier: DefaultImageProcessor.default.identifier,
  447. cacheSerializer: DefaultCacheSerializer.default,
  448. toDisk: true)
  449. {
  450. _ in
  451. let p = FailingProcessor()
  452. let cached = self.manager.cache.imageCachedType(forKey: url.cacheKey, processorIdentifier: p.identifier)
  453. XCTAssertFalse(cached.cached)
  454. // No downloading will happen
  455. self.manager.retrieveImage(with: url, options: [.processor(p)]) { result in
  456. XCTAssertNotNil(result.error)
  457. XCTAssertTrue(p.processed)
  458. if case .processorError(reason: .processingFailed(let processor, _)) = result.error! {
  459. XCTAssertEqual(processor.identifier, p.identifier)
  460. } else {
  461. XCTFail()
  462. }
  463. exp.fulfill()
  464. }
  465. }
  466. waitForExpectations(timeout: 3, handler: nil)
  467. }
  468. func testFailingProcessOnDataProviderImage() {
  469. let provider = SimpleImageDataProvider(cacheKey: "key") { .success(testImageData) }
  470. let called = ActorBox(false)
  471. let p = FailingProcessor()
  472. let options = [KingfisherOptionsInfoItem.processor(p), .processingQueue(.mainCurrentOrAsync)]
  473. _ = manager.retrieveImage(with: .provider(provider), options: options) { result in
  474. Task {
  475. await called.setValue(true)
  476. }
  477. XCTAssertNotNil(result.error)
  478. if case .processorError(reason: .processingFailed(let processor, _)) = result.error! {
  479. XCTAssertEqual(processor.identifier, p.identifier)
  480. } else {
  481. XCTFail()
  482. }
  483. }
  484. Task {
  485. let result = await called.value
  486. XCTAssertTrue(result)
  487. }
  488. }
  489. func testCacheOriginalImageWithOriginalCache() {
  490. let exp = expectation(description: #function)
  491. let url = testURLs[0]
  492. let originalCache = ImageCache(name: "test-originalCache")
  493. // Clear original cache first.
  494. originalCache.clearMemoryCache()
  495. originalCache.clearDiskCache {
  496. XCTAssertEqual(originalCache.imageCachedType(forKey: url.cacheKey), .none)
  497. stub(url, data: testImageData)
  498. let p = RoundCornerImageProcessor(cornerRadius: 20)
  499. self.manager.retrieveImage(
  500. with: url,
  501. options: [.processor(p), .cacheOriginalImage, .originalCache(originalCache)])
  502. {
  503. result in
  504. let originalCached = originalCache.imageCachedType(forKey: url.cacheKey)
  505. XCTAssertEqual(originalCached, .disk)
  506. exp.fulfill()
  507. }
  508. }
  509. waitForExpectations(timeout: 5, handler: nil)
  510. }
  511. func testCouldProcessOnOriginalImageWithOriginalCache() {
  512. let exp = expectation(description: #function)
  513. let url = testURLs[0]
  514. let originalCache = ImageCache(name: "test-originalCache")
  515. // Clear original cache first.
  516. originalCache.clearMemoryCache()
  517. originalCache.clearDiskCache {
  518. originalCache.store(
  519. testImage,
  520. original: testImageData,
  521. forKey: url.cacheKey,
  522. processorIdentifier: DefaultImageProcessor.default.identifier,
  523. cacheSerializer: DefaultCacheSerializer.default,
  524. toDisk: true)
  525. {
  526. _ in
  527. let p = SimpleProcessor()
  528. let cached = self.manager.cache.imageCachedType(forKey: url.cacheKey, processorIdentifier: p.identifier)
  529. XCTAssertFalse(cached.cached)
  530. // No downloading will happen
  531. self.manager.retrieveImage(with: url, options: [.processor(p), .originalCache(originalCache)]) {
  532. result in
  533. XCTAssertNotNil(result.value?.image)
  534. XCTAssertEqual(result.value!.cacheType, .none)
  535. XCTAssertTrue(p.processed)
  536. // The processed image should be cached
  537. let cached = self.manager.cache.imageCachedType(forKey: url.cacheKey, processorIdentifier: p.identifier)
  538. XCTAssertTrue(cached.cached)
  539. exp.fulfill()
  540. }
  541. }
  542. }
  543. waitForExpectations(timeout: 3, handler: nil)
  544. }
  545. func testCouldProcessDoNotHappenWhenSerializerCachesTheProcessedData() {
  546. let exp = expectation(description: #function)
  547. let url = testURLs[0]
  548. stub(url, data: testImageData)
  549. let s = DefaultCacheSerializer()
  550. let p1 = SimpleProcessor()
  551. let options1: KingfisherOptionsInfo = [.processor(p1), .cacheSerializer(s), .waitForCache]
  552. let source = Source.network(url)
  553. manager.retrieveImage(with: source, options: options1) { result in
  554. XCTAssertTrue(p1.processed)
  555. let p2 = SimpleProcessor()
  556. let options2: KingfisherOptionsInfo = [.processor(p2), .cacheSerializer(s), .waitForCache]
  557. self.manager.cache.clearMemoryCache()
  558. self.manager.retrieveImage(with: source, options: options2) { result in
  559. XCTAssertEqual(result.value?.cacheType, .disk)
  560. XCTAssertFalse(p2.processed)
  561. exp.fulfill()
  562. }
  563. }
  564. waitForExpectations(timeout: 3, handler: nil)
  565. }
  566. func testCouldProcessAgainWhenSerializerCachesOriginalData() {
  567. let exp = expectation(description: #function)
  568. let url = testURLs[0]
  569. stub(url, data: testImageData)
  570. var s = DefaultCacheSerializer()
  571. s.preferCacheOriginalData = true
  572. let p1 = SimpleProcessor()
  573. let options1: KingfisherOptionsInfo = [.processor(p1), .cacheSerializer(s), .waitForCache]
  574. let source = Source.network(url)
  575. manager.retrieveImage(with: source, options: options1) { [s] result in
  576. XCTAssertTrue(p1.processed)
  577. let p2 = SimpleProcessor()
  578. let options2: KingfisherOptionsInfo = [.processor(p2), .cacheSerializer(s), .waitForCache]
  579. self.manager.cache.clearMemoryCache()
  580. self.manager.retrieveImage(with: source, options: options2) { result in
  581. XCTAssertEqual(result.value?.cacheType, .disk)
  582. XCTAssertTrue(p2.processed)
  583. exp.fulfill()
  584. }
  585. }
  586. waitForExpectations(timeout: 3, handler: nil)
  587. }
  588. func testWaitForCacheOnRetrieveImage() {
  589. let exp = expectation(description: #function)
  590. let url = testURLs[0]
  591. stub(url, data: testImageData)
  592. self.manager.retrieveImage(with: url) { result in
  593. XCTAssertNotNil(result.value?.image)
  594. XCTAssertEqual(result.value!.cacheType, .none)
  595. self.manager.cache.clearMemoryCache()
  596. let cached = self.manager.cache.imageCachedType(forKey: url.cacheKey)
  597. XCTAssertEqual(cached, .disk)
  598. exp.fulfill()
  599. }
  600. waitForExpectations(timeout: 3, handler: nil)
  601. }
  602. func testNotWaitForCacheOnRetrieveImage() {
  603. let exp = expectation(description: #function)
  604. let url = testURLs[0]
  605. stub(url, data: testImageData)
  606. self.manager.defaultOptions = .empty
  607. self.manager.retrieveImage(with: url, options: [.callbackQueue(.untouch)]) { result in
  608. XCTAssertNotNil(result.value?.image)
  609. XCTAssertEqual(result.value!.cacheType, .none)
  610. // We are not waiting for cache finishing here. So only sync memory cache is done.
  611. XCTAssertEqual(self.manager.cache.imageCachedType(forKey: url.cacheKey), .memory)
  612. // Clear the memory cache.
  613. self.manager.cache.clearMemoryCache()
  614. // After some time, the disk cache should be done.
  615. delay(0.5) {
  616. XCTAssertEqual(self.manager.cache.imageCachedType(forKey: url.cacheKey), .disk)
  617. exp.fulfill()
  618. }
  619. }
  620. waitForExpectations(timeout: 3, handler: nil)
  621. }
  622. func testWaitForCacheOnRetrieveImageWithProcessor() {
  623. let exp = expectation(description: #function)
  624. let url = testURLs[0]
  625. stub(url, data: testImageData)
  626. let p = RoundCornerImageProcessor(cornerRadius: 20)
  627. self.manager.retrieveImage(with: url, options: [.processor(p)]) { result in
  628. XCTAssertNotNil(result.value?.image)
  629. XCTAssertEqual(result.value!.cacheType, .none)
  630. exp.fulfill()
  631. }
  632. waitForExpectations(timeout: 3, handler: nil)
  633. }
  634. func testImageShouldOnlyFromMemoryCacheOrRefreshCanBeGotFromMemory() {
  635. let exp = expectation(description: #function)
  636. let url = testURLs[0]
  637. stub(url, data: testImageData)
  638. manager.retrieveImage(with: url, options: [.fromMemoryCacheOrRefresh]) { result in
  639. // Can be downloaded and cached normally.
  640. XCTAssertNotNil(result.value?.image)
  641. XCTAssertEqual(result.value!.cacheType, .none)
  642. // Can still be got from memory even when disk cache cleared.
  643. self.manager.cache.clearDiskCache {
  644. self.manager.retrieveImage(with: url, options: [.fromMemoryCacheOrRefresh]) { result in
  645. XCTAssertNotNil(result.value?.image)
  646. XCTAssertEqual(result.value!.cacheType, .memory)
  647. exp.fulfill()
  648. }
  649. }
  650. }
  651. waitForExpectations(timeout: 3, handler: nil)
  652. }
  653. func testImageShouldOnlyFromMemoryCacheOrRefreshCanRefreshIfNotInMemory() {
  654. let exp = expectation(description: #function)
  655. let url = testURLs[0]
  656. stub(url, data: testImageData)
  657. manager.retrieveImage(with: url, options: [.fromMemoryCacheOrRefresh]) { result in
  658. XCTAssertNotNil(result.value?.image)
  659. XCTAssertEqual(result.value!.cacheType, .none)
  660. XCTAssertEqual(self.manager.cache.imageCachedType(forKey: url.cacheKey), .memory)
  661. self.manager.cache.clearMemoryCache()
  662. XCTAssertEqual(self.manager.cache.imageCachedType(forKey: url.cacheKey), .disk)
  663. // Should skip disk cache and download again.
  664. self.manager.retrieveImage(with: url, options: [.fromMemoryCacheOrRefresh]) { result in
  665. XCTAssertNotNil(result.value?.image)
  666. XCTAssertEqual(result.value!.cacheType, .none)
  667. XCTAssertEqual(self.manager.cache.imageCachedType(forKey: url.cacheKey), .memory)
  668. exp.fulfill()
  669. }
  670. }
  671. waitForExpectations(timeout: 5, handler: nil)
  672. }
  673. func testShouldDownloadAndCacheProcessedImage() {
  674. let exp = expectation(description: #function)
  675. let url = testURLs[0]
  676. stub(url, data: testImageData)
  677. let size = CGSize(width: 1, height: 1)
  678. let processor = ResizingImageProcessor(referenceSize: size)
  679. manager.retrieveImage(with: url, options: [.processor(processor)]) { result in
  680. // Can download and cache normally
  681. XCTAssertNotNil(result.value?.image)
  682. XCTAssertEqual(result.value!.image.size, size)
  683. XCTAssertEqual(result.value!.cacheType, .none)
  684. self.manager.cache.clearMemoryCache()
  685. let cached = self.manager.cache.imageCachedType(
  686. forKey: url.cacheKey, processorIdentifier: processor.identifier)
  687. XCTAssertEqual(cached, .disk)
  688. self.manager.retrieveImage(with: url, options: [.processor(processor)]) { result in
  689. XCTAssertNotNil(result.value?.image)
  690. XCTAssertEqual(result.value!.image.size, size)
  691. XCTAssertEqual(result.value!.cacheType, .disk)
  692. exp.fulfill()
  693. }
  694. }
  695. waitForExpectations(timeout: 3, handler: nil)
  696. }
  697. #if os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)
  698. func testShouldApplyImageModifierWhenDownload() {
  699. let exp = expectation(description: #function)
  700. let url = testURLs[0]
  701. stub(url, data: testImageData)
  702. let modifierCalled = ActorBox(false)
  703. let modifier = AnyImageModifier { image in
  704. Task {
  705. await modifierCalled.setValue(true)
  706. }
  707. return image.withRenderingMode(.alwaysTemplate)
  708. }
  709. manager.retrieveImage(with: url, options: [.imageModifier(modifier)]) { result in
  710. XCTAssertEqual(result.value?.image.renderingMode, .alwaysTemplate)
  711. Task {
  712. let called = await modifierCalled.value
  713. XCTAssertTrue(called)
  714. exp.fulfill()
  715. }
  716. }
  717. waitForExpectations(timeout: 3, handler: nil)
  718. }
  719. func testShouldApplyImageModifierWhenLoadFromMemoryCache() {
  720. let exp = expectation(description: #function)
  721. let url = testURLs[0]
  722. stub(url, data: testImageData)
  723. let modifierCalled = ActorBox(false)
  724. let modifier = AnyImageModifier { image in
  725. Task {
  726. await modifierCalled.setValue(true)
  727. }
  728. return image.withRenderingMode(.alwaysTemplate)
  729. }
  730. manager.cache.store(testImage, forKey: url.cacheKey)
  731. manager.retrieveImage(with: url, options: [.imageModifier(modifier)]) { result in
  732. XCTAssertEqual(result.value?.cacheType, .memory)
  733. XCTAssertEqual(result.value?.image.renderingMode, .alwaysTemplate)
  734. Task {
  735. let called = await modifierCalled.value
  736. XCTAssertTrue(called)
  737. exp.fulfill()
  738. }
  739. }
  740. waitForExpectations(timeout: 3, handler: nil)
  741. }
  742. func testShouldApplyImageModifierWhenLoadFromDiskCache() {
  743. let exp = expectation(description: #function)
  744. let url = testURLs[0]
  745. stub(url, data: testImageData)
  746. let modifierCalled = ActorBox(false)
  747. let modifier = AnyImageModifier { image in
  748. Task {
  749. await modifierCalled.setValue(true)
  750. }
  751. return image.withRenderingMode(.alwaysTemplate)
  752. }
  753. manager.cache.store(testImage, forKey: url.cacheKey) { _ in
  754. self.manager.cache.clearMemoryCache()
  755. self.manager.retrieveImage(with: url, options: [.imageModifier(modifier)]) { result in
  756. XCTAssertEqual(result.value!.cacheType, .disk)
  757. XCTAssertEqual(result.value!.image.renderingMode, .alwaysTemplate)
  758. Task {
  759. let result = await modifierCalled.value
  760. XCTAssertTrue(result)
  761. }
  762. exp.fulfill()
  763. }
  764. }
  765. waitForExpectations(timeout: 3, handler: nil)
  766. }
  767. func testImageModifierResultShouldNotBeCached() {
  768. let exp = expectation(description: #function)
  769. let url = testURLs[0]
  770. stub(url, data: testImageData)
  771. let modifierCalled = ActorBox(false)
  772. let modifier = AnyImageModifier { image in
  773. Task {
  774. await modifierCalled.setValue(true)
  775. }
  776. return image.withRenderingMode(.alwaysTemplate)
  777. }
  778. manager.retrieveImage(with: url, options: [.imageModifier(modifier)]) { result in
  779. XCTAssertEqual(result.value?.image.renderingMode, .alwaysTemplate)
  780. let memoryCached = self.manager.cache.retrieveImageInMemoryCache(forKey: url.absoluteString)
  781. XCTAssertNotNil(memoryCached)
  782. XCTAssertEqual(memoryCached?.renderingMode, .automatic)
  783. self.manager.cache.retrieveImageInDiskCache(forKey: url.absoluteString) { result in
  784. XCTAssertNotNil(result.value!)
  785. XCTAssertEqual(result.value??.renderingMode, .automatic)
  786. Task {
  787. let result = await modifierCalled.value
  788. XCTAssertTrue(result)
  789. exp.fulfill()
  790. }
  791. }
  792. }
  793. waitForExpectations(timeout: 3, handler: nil)
  794. }
  795. #endif
  796. func testRetrieveWithImageProvider() {
  797. let provider = SimpleImageDataProvider(cacheKey: "key") { .success(testImageData) }
  798. let called = ActorBox(false)
  799. manager.defaultOptions = .empty
  800. _ = manager.retrieveImage(with: .provider(provider), options: [.processingQueue(.mainCurrentOrAsync)]) {
  801. result in
  802. XCTAssertNotNil(result.value)
  803. XCTAssertTrue(result.value!.image.renderEqual(to: testImage))
  804. Task {
  805. await called.setValue(true)
  806. }
  807. }
  808. Task {
  809. let result = await called.value
  810. XCTAssertTrue(result)
  811. }
  812. }
  813. func testRetrieveWithImageProviderFail() {
  814. let provider = SimpleImageDataProvider(cacheKey: "key") { .failure(SimpleImageDataProvider.E()) }
  815. let called = ActorBox(false)
  816. _ = manager.retrieveImage(with: .provider(provider)) { result in
  817. XCTAssertNotNil(result.error)
  818. if case .imageSettingError(reason: .dataProviderError(_, let error)) = result.error! {
  819. XCTAssertTrue(error is SimpleImageDataProvider.E)
  820. } else {
  821. XCTFail()
  822. }
  823. Task {
  824. await called.setValue(true)
  825. }
  826. }
  827. Task {
  828. let result = await called.value
  829. XCTAssertTrue(result)
  830. }
  831. }
  832. func testContextRemovingAlternativeSource() {
  833. let allSources: [Source] = [
  834. .network(URL(string: "1")!),
  835. .network(URL(string: "2")!)
  836. ]
  837. let info = KingfisherParsedOptionsInfo([.alternativeSources(allSources)])
  838. let context = RetrievingContext<Source>(
  839. options: info, originalSource: .network(URL(string: "0")!))
  840. let source1 = context.popAlternativeSource()
  841. XCTAssertNotNil(source1)
  842. guard case .network(let r1) = source1! else {
  843. XCTFail("Should be a network source, but \(source1!)")
  844. return
  845. }
  846. XCTAssertEqual(r1.downloadURL.absoluteString, "1")
  847. let source2 = context.popAlternativeSource()
  848. XCTAssertNotNil(source2)
  849. guard case .network(let r2) = source2! else {
  850. XCTFail("Should be a network source, but \(source2!)")
  851. return
  852. }
  853. XCTAssertEqual(r2.downloadURL.absoluteString, "2")
  854. XCTAssertNil(context.popAlternativeSource())
  855. }
  856. func testRetrievingWithAlternativeSource() {
  857. let exp = expectation(description: #function)
  858. let url = testURLs[0]
  859. stub(url, data: testImageData)
  860. let brokenURL = URL(string: "brokenurl")!
  861. stub(brokenURL, data: Data())
  862. _ = manager.retrieveImage(
  863. with: .network(brokenURL),
  864. options: [.alternativeSources([.network(url)])])
  865. {
  866. result in
  867. XCTAssertNotNil(result.value)
  868. XCTAssertEqual(result.value!.source.url, url)
  869. XCTAssertEqual(result.value!.originalSource.url, brokenURL)
  870. exp.fulfill()
  871. }
  872. waitForExpectations(timeout: 3, handler: nil)
  873. }
  874. func testRetrievingErrorsWithAlternativeSource() {
  875. let exp = expectation(description: #function)
  876. let url = testURLs[0]
  877. stub(url, data: Data())
  878. let brokenURL = URL(string: "brokenurl")!
  879. stub(brokenURL, data: Data())
  880. let anotherBrokenURL = URL(string: "anotherBrokenURL")!
  881. stub(anotherBrokenURL, data: Data())
  882. _ = manager.retrieveImage(
  883. with: .network(brokenURL),
  884. options: [.alternativeSources([.network(anotherBrokenURL), .network(url)])])
  885. {
  886. result in
  887. defer { exp.fulfill() }
  888. XCTAssertNil(result.value)
  889. XCTAssertNotNil(result.error)
  890. guard case .imageSettingError(reason: let reason) = result.error! else {
  891. XCTFail("The error should be image setting error")
  892. return
  893. }
  894. guard case .alternativeSourcesExhausted(let errorInfo) = reason else {
  895. XCTFail("The error reason should be alternativeSourcesFailed")
  896. return
  897. }
  898. XCTAssertEqual(errorInfo.count, 3)
  899. XCTAssertEqual(errorInfo[0].source.url, brokenURL)
  900. XCTAssertEqual(errorInfo[1].source.url, anotherBrokenURL)
  901. XCTAssertEqual(errorInfo[2].source.url, url)
  902. }
  903. waitForExpectations(timeout: 3, handler: nil)
  904. }
  905. func testRetrievingAlternativeSourceTaskUpdateBlockCalled() {
  906. let exp = expectation(description: #function)
  907. let url = testURLs[0]
  908. stub(url, data: testImageData)
  909. let brokenURL = URL(string: "brokenurl")!
  910. stub(brokenURL, data: Data())
  911. let downloadTaskUpdatedCount = ActorBox(0)
  912. let task = manager.retrieveImage(
  913. with: .network(brokenURL),
  914. options: [.alternativeSources([.network(url)])],
  915. downloadTaskUpdated: { newTask in
  916. Task {
  917. let value = await downloadTaskUpdatedCount.value + 1
  918. await downloadTaskUpdatedCount.setValue(value)
  919. }
  920. XCTAssertEqual(newTask?.sessionTask?.task.currentRequest?.url, url)
  921. })
  922. {
  923. result in
  924. Task {
  925. let result = await downloadTaskUpdatedCount.value
  926. XCTAssertEqual(result, 1)
  927. exp.fulfill()
  928. }
  929. }
  930. XCTAssertEqual(task?.sessionTask?.task.currentRequest?.url, brokenURL)
  931. waitForExpectations(timeout: 3, handler: nil)
  932. }
  933. func testRetrievingAlternativeSourceCancelled() {
  934. let exp = expectation(description: #function)
  935. let url = testURLs[0]
  936. stub(url, data: testImageData)
  937. let brokenURL = URL(string: "brokenurl")!
  938. stub(brokenURL, data: Data())
  939. let task = manager.retrieveImage(
  940. with: .network(brokenURL),
  941. options: [.alternativeSources([.network(url)])]
  942. )
  943. {
  944. result in
  945. XCTAssertNotNil(result.error)
  946. XCTAssertTrue(result.error!.isTaskCancelled)
  947. exp.fulfill()
  948. }
  949. task?.cancel()
  950. waitForExpectations(timeout: 3, handler: nil)
  951. }
  952. func testRetrievingAlternativeSourceCanCancelUpdatedTask() {
  953. let exp = expectation(description: #function)
  954. let url = testURLs[0]
  955. let dataStub = delayedStub(url, data: testImageData)
  956. let called = ActorBox(false)
  957. let brokenURL = URL(string: "brokenurl")!
  958. stub(brokenURL, data: Data())
  959. let task = manager.retrieveImage(
  960. with: .network(brokenURL),
  961. options: [.alternativeSources([.network(url)])],
  962. downloadTaskUpdated: { newTask in
  963. XCTAssertNotNil(newTask)
  964. newTask?.cancel()
  965. Task {
  966. await called.setValue(true)
  967. }
  968. }
  969. )
  970. {
  971. result in
  972. XCTAssertNotNil(result.error)
  973. XCTAssertTrue(result.error?.isTaskCancelled ?? false)
  974. delay(0.3) {
  975. _ = dataStub.go()
  976. Task {
  977. let result = await called.value
  978. XCTAssertTrue(result)
  979. exp.fulfill()
  980. }
  981. }
  982. }
  983. XCTAssertNotNil(task)
  984. XCTAssertTrue(task!.isInitialized)
  985. waitForExpectations(timeout: 3, handler: nil)
  986. }
  987. func testDownsamplingHandleScale2x() {
  988. let exp = expectation(description: #function)
  989. let url = testURLs[0]
  990. stub(url, data: testImageData)
  991. _ = manager.retrieveImage(
  992. with: .network(url),
  993. options: [.processor(DownsamplingImageProcessor(size: .init(width: 4, height: 4))), .scaleFactor(2)])
  994. {
  995. result in
  996. let image = result.value?.image
  997. XCTAssertNotNil(image)
  998. #if os(macOS)
  999. XCTAssertEqual(image?.size, .init(width: 8, height: 8))
  1000. XCTAssertEqual(image?.kf.scale, 1)
  1001. #else
  1002. XCTAssertEqual(image?.size, .init(width: 4, height: 4))
  1003. XCTAssertEqual(image?.kf.scale, 2)
  1004. #endif
  1005. exp.fulfill()
  1006. }
  1007. waitForExpectations(timeout: 3, handler: nil)
  1008. }
  1009. func testDownsamplingHandleScale3x() {
  1010. let exp = expectation(description: #function)
  1011. let url = testURLs[0]
  1012. stub(url, data: testImageData)
  1013. _ = manager.retrieveImage(
  1014. with: .network(url),
  1015. options: [.processor(DownsamplingImageProcessor(size: .init(width: 4, height: 4))), .scaleFactor(3)])
  1016. {
  1017. result in
  1018. let image = result.value?.image
  1019. XCTAssertNotNil(image)
  1020. #if os(macOS)
  1021. XCTAssertEqual(image?.size, .init(width: 12, height: 12))
  1022. XCTAssertEqual(image?.kf.scale, 1)
  1023. #else
  1024. XCTAssertEqual(image?.size, .init(width: 4, height: 4))
  1025. XCTAssertEqual(image?.kf.scale, 3)
  1026. #endif
  1027. exp.fulfill()
  1028. }
  1029. waitForExpectations(timeout: 3, handler: nil)
  1030. }
  1031. func testCacheCallbackCoordinatorStateChanging() {
  1032. var coordinator = CacheCallbackCoordinator(
  1033. shouldWaitForCache: false, shouldCacheOriginal: false)
  1034. var called = false
  1035. coordinator.apply(.cacheInitiated) {
  1036. called = true
  1037. }
  1038. XCTAssertTrue(called)
  1039. XCTAssertEqual(coordinator.state, .done)
  1040. coordinator.apply(.cachingImage) { XCTFail() }
  1041. XCTAssertEqual(coordinator.state, .done)
  1042. coordinator = CacheCallbackCoordinator(
  1043. shouldWaitForCache: true, shouldCacheOriginal: false)
  1044. called = false
  1045. coordinator.apply(.cacheInitiated) { XCTFail() }
  1046. XCTAssertEqual(coordinator.state, .idle)
  1047. coordinator.apply(.cachingImage) {
  1048. called = true
  1049. }
  1050. XCTAssertTrue(called)
  1051. XCTAssertEqual(coordinator.state, .done)
  1052. coordinator = CacheCallbackCoordinator(
  1053. shouldWaitForCache: false, shouldCacheOriginal: true)
  1054. coordinator.apply(.cacheInitiated) {
  1055. called = true
  1056. }
  1057. XCTAssertEqual(coordinator.state, .done)
  1058. coordinator.apply(.cachingOriginalImage) { XCTFail() }
  1059. XCTAssertEqual(coordinator.state, .done)
  1060. coordinator = CacheCallbackCoordinator(
  1061. shouldWaitForCache: true, shouldCacheOriginal: true)
  1062. coordinator.apply(.cacheInitiated) { XCTFail() }
  1063. XCTAssertEqual(coordinator.state, .idle)
  1064. coordinator.apply(.cachingOriginalImage) { XCTFail() }
  1065. XCTAssertEqual(coordinator.state, .originalImageCached)
  1066. coordinator.apply(.cachingImage) { called = true }
  1067. XCTAssertEqual(coordinator.state, .done)
  1068. coordinator = CacheCallbackCoordinator(
  1069. shouldWaitForCache: true, shouldCacheOriginal: true)
  1070. coordinator.apply(.cacheInitiated) { XCTFail() }
  1071. XCTAssertEqual(coordinator.state, .idle)
  1072. coordinator.apply(.cachingImage) { XCTFail() }
  1073. XCTAssertEqual(coordinator.state, .imageCached)
  1074. coordinator.apply(.cachingOriginalImage) { called = true }
  1075. XCTAssertEqual(coordinator.state, .done)
  1076. }
  1077. func testCallbackClearAfterSuccess() {
  1078. let exp = expectation(description: #function)
  1079. let url = testURLs[0]
  1080. stub(url, data: testImageData)
  1081. let task = ActorBox<DownloadTask?>(nil)
  1082. let called = ActorBox(false)
  1083. let t: DownloadTask? = manager.retrieveImage(with: url) { result in
  1084. Task {
  1085. let calledResult = await called.value
  1086. XCTAssertFalse(calledResult)
  1087. XCTAssertNotNil(result.value?.image)
  1088. if !calledResult {
  1089. Task {
  1090. await task.value?.cancel()
  1091. }
  1092. DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
  1093. exp.fulfill()
  1094. }
  1095. } else {
  1096. XCTFail("Callback should not be invoked again.")
  1097. }
  1098. }
  1099. }
  1100. Task {
  1101. await task.setValue(t)
  1102. }
  1103. waitForExpectations(timeout: 3, handler: nil)
  1104. }
  1105. func testCanUseCustomizeDefaultCacheSerializer() {
  1106. let exp = expectation(description: #function)
  1107. let url = testURLs[0]
  1108. var cacheSerializer = DefaultCacheSerializer()
  1109. cacheSerializer.preferCacheOriginalData = true
  1110. manager.cache.store(
  1111. testImage,
  1112. original: testImageData,
  1113. forKey: url.cacheKey,
  1114. processorIdentifier: DefaultImageProcessor.default.identifier,
  1115. cacheSerializer: cacheSerializer, toDisk: true) {
  1116. result in
  1117. let computedKey = url.cacheKey.computedKey(with: DefaultImageProcessor.default.identifier)
  1118. let fileURL = self.manager.cache.diskStorage.cacheFileURL(forKey: computedKey)
  1119. let data = try! Data(contentsOf: fileURL)
  1120. XCTAssertEqual(data, testImageData)
  1121. exp.fulfill()
  1122. }
  1123. waitForExpectations(timeout: 3.0)
  1124. }
  1125. func testCanUseCustomizeDefaultCacheSerializerStoreEncoded() {
  1126. let exp = expectation(description: #function)
  1127. let url = testURLs[0]
  1128. var cacheSerializer = DefaultCacheSerializer()
  1129. cacheSerializer.compressionQuality = 0.8
  1130. manager.cache.store(
  1131. testImage,
  1132. original: testImageJEPGData,
  1133. forKey: url.cacheKey,
  1134. processorIdentifier: DefaultImageProcessor.default.identifier,
  1135. cacheSerializer: cacheSerializer, toDisk: true) {
  1136. result in
  1137. let computedKey = url.cacheKey.computedKey(with: DefaultImageProcessor.default.identifier)
  1138. let fileURL = self.manager.cache.diskStorage.cacheFileURL(forKey: computedKey)
  1139. let data = try! Data(contentsOf: fileURL)
  1140. XCTAssertNotEqual(data, testImageJEPGData)
  1141. XCTAssertEqual(data, testImage.kf.jpegRepresentation(compressionQuality: 0.8))
  1142. exp.fulfill()
  1143. }
  1144. waitForExpectations(timeout: 3.0)
  1145. }
  1146. func testImageResultContainsDataWhenDownloaded() {
  1147. let exp = expectation(description: #function)
  1148. let url = testURLs[0]
  1149. stub(url, data: testImageData)
  1150. manager.retrieveImage(with: url) { result in
  1151. XCTAssertNotNil(result.value?.data())
  1152. XCTAssertEqual(result.value!.data(), testImageData)
  1153. XCTAssertEqual(result.value!.cacheType, .none)
  1154. exp.fulfill()
  1155. }
  1156. waitForExpectations(timeout: 3, handler: nil)
  1157. }
  1158. func testImageResultContainsDataWhenLoadFromMemoryCache() {
  1159. let exp = expectation(description: #function)
  1160. let url = testURLs[0]
  1161. stub(url, data: testImageData)
  1162. manager.retrieveImage(with: url) { _ in
  1163. self.manager.retrieveImage(with: url) { result in
  1164. XCTAssertEqual(result.value!.cacheType, .memory)
  1165. XCTAssertNotNil(result.value?.data())
  1166. XCTAssertEqual(
  1167. result.value!.data(),
  1168. DefaultCacheSerializer.default.data(with: result.value!.image, original: nil)
  1169. )
  1170. exp.fulfill()
  1171. }
  1172. }
  1173. waitForExpectations(timeout: 3, handler: nil)
  1174. }
  1175. func testImageResultContainsDataWhenLoadFromDiskCache() {
  1176. let exp = expectation(description: #function)
  1177. let url = testURLs[0]
  1178. stub(url, data: testImageData)
  1179. manager.retrieveImage(with: url) { _ in
  1180. self.manager.cache.clearMemoryCache()
  1181. self.manager.retrieveImage(with: url) { result in
  1182. XCTAssertEqual(result.value!.cacheType, .disk)
  1183. XCTAssertNotNil(result.value?.data())
  1184. XCTAssertEqual(
  1185. result.value!.data(),
  1186. DefaultCacheSerializer.default.data(with: result.value!.image, original: nil)
  1187. )
  1188. exp.fulfill()
  1189. }
  1190. }
  1191. waitForExpectations(timeout: 3, handler: nil)
  1192. }
  1193. // https://github.com/onevcat/Kingfisher/issues/1923
  1194. func testAnimatedImageShouldRecreateFromCache() {
  1195. let exp = expectation(description: #function)
  1196. let url = testURLs[0]
  1197. let data = testImageGIFData
  1198. stub(url, data: data)
  1199. let p = SimpleProcessor()
  1200. manager.retrieveImage(with: url, options: [.processor(p), .onlyLoadFirstFrame]) { result in
  1201. XCTAssertTrue(p.processed)
  1202. XCTAssertTrue(result.value!.image.creatingOptions!.onlyFirstFrame)
  1203. p.processed = false
  1204. self.manager.retrieveImage(with: url, options: [.processor(p)]) { result in
  1205. XCTAssertTrue(p.processed)
  1206. XCTAssertFalse(result.value!.image.creatingOptions!.onlyFirstFrame)
  1207. exp.fulfill()
  1208. }
  1209. }
  1210. waitForExpectations(timeout: 3, handler: nil)
  1211. }
  1212. func testAnimatedImageShouldNotRecreateWithSameOptions() {
  1213. let exp = expectation(description: #function)
  1214. let url = testURLs[0]
  1215. let data = testImageGIFData
  1216. stub(url, data: data)
  1217. let p = SimpleProcessor()
  1218. manager.retrieveImage(with: url, options: [.processor(p), .onlyLoadFirstFrame]) { result in
  1219. XCTAssertTrue(p.processed)
  1220. XCTAssertTrue(result.value!.image.creatingOptions!.onlyFirstFrame)
  1221. p.processed = false
  1222. self.manager.retrieveImage(with: url, options: [.processor(p), .onlyLoadFirstFrame]) { result in
  1223. XCTAssertFalse(p.processed)
  1224. XCTAssertTrue(result.value!.image.creatingOptions!.onlyFirstFrame)
  1225. exp.fulfill()
  1226. }
  1227. }
  1228. waitForExpectations(timeout: 3, handler: nil)
  1229. }
  1230. func testMissingResourceOfLivePhotoFound() {
  1231. let resource = KF.ImageResource(downloadURL: LivePhotoURL.mov)
  1232. let source = LivePhotoSource(resources: [resource])
  1233. let missing = manager.missingResources(source, options: .init(.empty))
  1234. XCTAssertEqual(missing.count, 1)
  1235. }
  1236. func testMissingResourceOfLivePhotoNotFound() async throws {
  1237. let resource = KF.ImageResource(downloadURL: LivePhotoURL.mov)
  1238. try await manager.cache.storeToDisk(
  1239. testImageData,
  1240. forKey: resource.cacheKey,
  1241. forcedExtension: resource.downloadURL.pathExtension
  1242. )
  1243. let source = LivePhotoSource(resources: [resource])
  1244. let missing = manager.missingResources(source, options: .init(.empty))
  1245. XCTAssertEqual(missing.count, 0)
  1246. }
  1247. func testMissingResourceOfLivePhotoFoundOne() async throws {
  1248. let resource1 = KF.ImageResource(downloadURL: LivePhotoURL.heic)
  1249. let resource2 = KF.ImageResource(downloadURL: LivePhotoURL.mov)
  1250. try await manager.cache.storeToDisk(
  1251. testImageData,
  1252. forKey: resource1.cacheKey,
  1253. forcedExtension: resource1.downloadURL.pathExtension
  1254. )
  1255. let source = LivePhotoSource(resources: [resource1, resource2])
  1256. let missing = manager.missingResources(source, options: .init(.empty))
  1257. XCTAssertEqual(missing.count, 1)
  1258. XCTAssertEqual(missing[0].downloadURL, resource2.downloadURL)
  1259. }
  1260. func testMissingResourceOfLivePhotoForceRefresh() async throws {
  1261. let resource1 = KF.ImageResource(downloadURL: LivePhotoURL.heic)
  1262. let resource2 = KF.ImageResource(downloadURL: LivePhotoURL.mov)
  1263. try await manager.cache.storeToDisk(
  1264. testImageData,
  1265. forKey: resource1.cacheKey,
  1266. forcedExtension: resource1.downloadURL.pathExtension
  1267. )
  1268. let source = LivePhotoSource(resources: [resource1, resource2])
  1269. let missing = manager.missingResources(source, options: .init([.forceRefresh]))
  1270. XCTAssertEqual(missing.count, 2)
  1271. XCTAssertEqual(missing[0].downloadURL, resource1.downloadURL)
  1272. XCTAssertEqual(missing[1].downloadURL, resource2.downloadURL)
  1273. }
  1274. func testDownloadAndCacheLivePhotoResourcesAll() async throws {
  1275. let resource1 = KF.ImageResource(downloadURL: LivePhotoURL.mov)
  1276. let resource2 = KF.ImageResource(downloadURL: LivePhotoURL.heic)
  1277. stub(resource1.downloadURL, data: testImageData)
  1278. stub(resource2.downloadURL, data: testImageData)
  1279. let result = try await manager.downloadAndCache(
  1280. resources: [resource1, resource2].map { LivePhotoResource.init(resource: $0)
  1281. },
  1282. options: .init(.empty))
  1283. XCTAssertEqual(result.count, 2)
  1284. let urls = result.compactMap(\.url)
  1285. XCTAssertTrue(urls.contains(LivePhotoURL.mov))
  1286. XCTAssertTrue(urls.contains(LivePhotoURL.heic))
  1287. let resourceCached1 = manager.cache.imageCachedType(
  1288. forKey: resource1.cacheKey,
  1289. forcedExtension: resource1.downloadURL.pathExtension
  1290. )
  1291. let resourceCached2 = manager.cache.imageCachedType(
  1292. forKey: resource2.cacheKey,
  1293. forcedExtension: resource2.downloadURL.pathExtension
  1294. )
  1295. XCTAssertEqual(resourceCached1, .disk)
  1296. XCTAssertEqual(resourceCached2, .disk)
  1297. }
  1298. func testRetrieveLivePhotoFromNetwork() async throws {
  1299. let resource1 = KF.ImageResource(downloadURL: LivePhotoURL.mov)
  1300. let resource2 = KF.ImageResource(downloadURL: LivePhotoURL.heic)
  1301. stub(resource1.downloadURL, data: testImageData)
  1302. stub(resource2.downloadURL, data: testImageData)
  1303. let resource1Cached = manager.cache.isCached(
  1304. forKey: resource1.cacheKey,
  1305. processorIdentifier: LivePhotoImageProcessor.default.identifier
  1306. )
  1307. let resource2Cached = manager.cache.isCached(
  1308. forKey: resource2.cacheKey,
  1309. processorIdentifier: LivePhotoImageProcessor.default.identifier
  1310. )
  1311. XCTAssertFalse(resource1Cached)
  1312. XCTAssertFalse(resource2Cached)
  1313. let source = LivePhotoSource(resources: [resource1, resource2])
  1314. let result = try await manager.retrieveLivePhoto(with: source)
  1315. XCTAssertEqual(result.fileURLs.count, 2)
  1316. result.fileURLs.forEach { url in
  1317. XCTAssertTrue(FileManager.default.fileExists(atPath: url.path))
  1318. }
  1319. XCTAssertEqual(result.cacheType, .none)
  1320. XCTAssertEqual(result.data(), [testImageData, testImageData])
  1321. let urlsInSource = result.source.resources.map(\.downloadURL)
  1322. XCTAssertTrue(urlsInSource.contains(LivePhotoURL.mov))
  1323. XCTAssertTrue(urlsInSource.contains(LivePhotoURL.heic))
  1324. }
  1325. func testRetrieveLivePhotoFromLocal() async throws {
  1326. let resource1 = KF.ImageResource(downloadURL: LivePhotoURL.mov)
  1327. let resource2 = KF.ImageResource(downloadURL: LivePhotoURL.heic)
  1328. try await manager.cache.storeToDisk(
  1329. testImageData,
  1330. forKey: resource1.cacheKey,
  1331. processorIdentifier: LivePhotoImageProcessor.default.identifier,
  1332. forcedExtension: resource1.downloadURL.pathExtension
  1333. )
  1334. try await manager.cache.storeToDisk(
  1335. testImageData,
  1336. forKey: resource2.cacheKey,
  1337. processorIdentifier: LivePhotoImageProcessor.default.identifier,
  1338. forcedExtension: resource2.downloadURL.pathExtension
  1339. )
  1340. let resource1Cached = manager.cache.isCached(
  1341. forKey: resource1.cacheKey,
  1342. processorIdentifier: LivePhotoImageProcessor.default.identifier,
  1343. forcedExtension: resource1.downloadURL.pathExtension
  1344. )
  1345. let resource2Cached = manager.cache.isCached(
  1346. forKey: resource2.cacheKey,
  1347. processorIdentifier: LivePhotoImageProcessor.default.identifier,
  1348. forcedExtension: resource2.downloadURL.pathExtension
  1349. )
  1350. XCTAssertTrue(resource1Cached)
  1351. XCTAssertTrue(resource2Cached)
  1352. let source = LivePhotoSource(resources: [resource1, resource2])
  1353. let result = try await manager.retrieveLivePhoto(with: source)
  1354. XCTAssertEqual(result.fileURLs.count, 2)
  1355. result.fileURLs.forEach { url in
  1356. XCTAssertTrue(FileManager.default.fileExists(atPath: url.path))
  1357. }
  1358. XCTAssertEqual(result.cacheType, .disk)
  1359. XCTAssertEqual(result.data(), [])
  1360. let urlsInSource = result.source.resources.map(\.downloadURL)
  1361. XCTAssertTrue(urlsInSource.contains(LivePhotoURL.mov))
  1362. XCTAssertTrue(urlsInSource.contains(LivePhotoURL.heic))
  1363. }
  1364. func testRetrieveLivePhotoMixed() async throws {
  1365. let resource1 = KF.ImageResource(downloadURL: LivePhotoURL.mov)
  1366. let resource2 = KF.ImageResource(downloadURL: LivePhotoURL.heic)
  1367. try await manager.cache.storeToDisk(
  1368. testImageData,
  1369. forKey: resource1.cacheKey,
  1370. processorIdentifier: LivePhotoImageProcessor.default.identifier,
  1371. forcedExtension: resource1.downloadURL.pathExtension
  1372. )
  1373. stub(resource2.downloadURL, data: testImageData)
  1374. let resource1Cached = manager.cache.isCached(
  1375. forKey: resource1.cacheKey,
  1376. processorIdentifier: LivePhotoImageProcessor.default.identifier,
  1377. forcedExtension: resource1.downloadURL.pathExtension
  1378. )
  1379. let resource2Cached = manager.cache.isCached(
  1380. forKey: resource2.cacheKey,
  1381. processorIdentifier: LivePhotoImageProcessor.default.identifier,
  1382. forcedExtension: resource2.downloadURL.pathExtension
  1383. )
  1384. XCTAssertTrue(resource1Cached)
  1385. XCTAssertFalse(resource2Cached)
  1386. let source = LivePhotoSource(resources: [resource1, resource2])
  1387. let result = try await manager.retrieveLivePhoto(with: source)
  1388. XCTAssertEqual(result.fileURLs.count, 2)
  1389. result.fileURLs.forEach { url in
  1390. XCTAssertTrue(FileManager.default.fileExists(atPath: url.path))
  1391. }
  1392. XCTAssertEqual(result.cacheType, .none)
  1393. XCTAssertEqual(result.data(), [testImageData])
  1394. let urlsInSource = result.source.resources.map(\.downloadURL)
  1395. XCTAssertTrue(urlsInSource.contains(LivePhotoURL.mov))
  1396. XCTAssertTrue(urlsInSource.contains(LivePhotoURL.heic))
  1397. }
  1398. func testRetrieveLivePhotoNetworkThenCache() async throws {
  1399. let resource1 = KF.ImageResource(downloadURL: LivePhotoURL.mov)
  1400. let resource2 = KF.ImageResource(downloadURL: LivePhotoURL.heic)
  1401. stub(resource1.downloadURL, data: testImageData)
  1402. stub(resource2.downloadURL, data: testImageData)
  1403. let resource1Cached = manager.cache.isCached(
  1404. forKey: resource1.cacheKey,
  1405. processorIdentifier: LivePhotoImageProcessor.default.identifier,
  1406. forcedExtension: resource1.downloadURL.pathExtension
  1407. )
  1408. let resource2Cached = manager.cache.isCached(
  1409. forKey: resource2.cacheKey,
  1410. processorIdentifier: LivePhotoImageProcessor.default.identifier,
  1411. forcedExtension: resource2.downloadURL.pathExtension
  1412. )
  1413. XCTAssertFalse(resource1Cached)
  1414. XCTAssertFalse(resource2Cached)
  1415. let source = LivePhotoSource(resources: [resource1, resource2])
  1416. let result = try await manager.retrieveLivePhoto(with: source)
  1417. XCTAssertEqual(result.fileURLs.count, 2)
  1418. result.fileURLs.forEach { url in
  1419. XCTAssertTrue(FileManager.default.fileExists(atPath: url.path))
  1420. }
  1421. XCTAssertEqual(result.cacheType, .none)
  1422. XCTAssertEqual(result.data(), [testImageData, testImageData])
  1423. let urlsInSource = result.source.resources.map(\.downloadURL)
  1424. XCTAssertTrue(urlsInSource.contains(LivePhotoURL.mov))
  1425. XCTAssertTrue(urlsInSource.contains(LivePhotoURL.heic))
  1426. let localResult = try await manager.retrieveLivePhoto(with: source)
  1427. XCTAssertEqual(localResult.fileURLs.count, 2)
  1428. XCTAssertEqual(localResult.cacheType, .disk)
  1429. }
  1430. func testDownloadAndCacheLivePhotoWithEmptyResources() async throws {
  1431. let result = try await manager.downloadAndCache(resources: [], options: .init([]))
  1432. XCTAssertTrue(result.isEmpty)
  1433. }
  1434. func testDownloadAndCacheLivePhotoWithSingleResource() async throws {
  1435. let resource = LivePhotoResource(downloadURL: LivePhotoURL.heic)
  1436. stub(resource.downloadURL!, data: testImageData)
  1437. let result = try await manager.downloadAndCache(resources: [resource], options: .init([]))
  1438. XCTAssertEqual(result.count, 1)
  1439. let t = manager.cache.imageCachedType(forKey: resource.cacheKey, forcedExtension: "heic")
  1440. XCTAssertEqual(t, .disk)
  1441. }
  1442. func testDownloadAndCacheLivePhotoWithSingleResourceGuessingUnsupportedExtension() async throws {
  1443. let resource = LivePhotoResource(downloadURL: URL(string: "https://example.com")!)
  1444. stub(resource.downloadURL!, data: testImageData)
  1445. XCTAssertEqual(resource.referenceFileType, .other(""))
  1446. let result = try await manager.downloadAndCache(resources: [resource], options: .init([]))
  1447. XCTAssertEqual(result.count, 1)
  1448. var cacheType = manager.cache.imageCachedType(forKey: resource.cacheKey, forcedExtension: "heic")
  1449. XCTAssertEqual(cacheType, .none)
  1450. cacheType = manager.cache.imageCachedType(forKey: resource.cacheKey)
  1451. XCTAssertEqual(cacheType, .disk)
  1452. }
  1453. func testDownloadAndCacheLivePhotoWithSingleResourceExplicitSetExtension() async throws {
  1454. let resource = LivePhotoResource(downloadURL: URL(string: "https://example.com")!, fileType: .heic)
  1455. stub(resource.downloadURL!, data: testImageData)
  1456. XCTAssertEqual(resource.referenceFileType, .heic)
  1457. let result = try await manager.downloadAndCache(resources: [resource], options: .init([]))
  1458. XCTAssertEqual(result.count, 1)
  1459. var cacheType = manager.cache.imageCachedType(forKey: resource.cacheKey, forcedExtension: "heic")
  1460. XCTAssertEqual(cacheType, .disk)
  1461. cacheType = manager.cache.imageCachedType(forKey: resource.cacheKey)
  1462. XCTAssertEqual(cacheType, .none)
  1463. }
  1464. func testDownloadAndCacheLivePhotoWithSingleResourceGuessingHEICExtension() async throws {
  1465. let resource = LivePhotoResource(downloadURL: URL(string: "https://example.com")!)
  1466. stub(resource.downloadURL!, data: partitalHEICData)
  1467. XCTAssertEqual(resource.referenceFileType, .other(""))
  1468. let result = try await manager.downloadAndCache(resources: [resource], options: .init([]))
  1469. XCTAssertEqual(result.count, 1)
  1470. var cacheType = manager.cache.imageCachedType(forKey: resource.cacheKey, forcedExtension: "heic")
  1471. XCTAssertEqual(cacheType, .disk)
  1472. cacheType = manager.cache.imageCachedType(forKey: resource.cacheKey)
  1473. XCTAssertEqual(cacheType, .none)
  1474. }
  1475. func testDownloadAndCacheLivePhotoWithSingleResourceGuessingMOVExtension() async throws {
  1476. let resource = LivePhotoResource(downloadURL: URL(string: "https://example.com")!)
  1477. stub(resource.downloadURL!, data: partitalMOVData)
  1478. XCTAssertEqual(resource.referenceFileType, .other(""))
  1479. let result = try await manager.downloadAndCache(resources: [resource], options: .init([]))
  1480. XCTAssertEqual(result.count, 1)
  1481. var cacheType = manager.cache.imageCachedType(forKey: resource.cacheKey, forcedExtension: "mov")
  1482. XCTAssertEqual(cacheType, .disk)
  1483. cacheType = manager.cache.imageCachedType(forKey: resource.cacheKey)
  1484. XCTAssertEqual(cacheType, .none)
  1485. }
  1486. }
  1487. private var imageCreatingOptionsKey: Void?
  1488. extension KFCrossPlatformImage {
  1489. var creatingOptions: ImageCreatingOptions? {
  1490. get { return getAssociatedObject(self, &imageCreatingOptionsKey) }
  1491. set { setRetainedAssociatedObject(self, &imageCreatingOptionsKey, newValue) }
  1492. }
  1493. }
  1494. final class SimpleProcessor: ImageProcessor, @unchecked Sendable {
  1495. public let identifier = "id"
  1496. var processed = false
  1497. /// Initialize a `DefaultImageProcessor`
  1498. public init() {}
  1499. /// Process an input `ImageProcessItem` item to an image for this processor.
  1500. ///
  1501. /// - parameter item: Input item which will be processed by `self`
  1502. /// - parameter options: Options when processing the item.
  1503. ///
  1504. /// - returns: The processed image.
  1505. ///
  1506. /// - Note: See documentation of `ImageProcessor` protocol for more.
  1507. public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
  1508. processed = true
  1509. switch item {
  1510. case .image(let image):
  1511. return image
  1512. case .data(let data):
  1513. let creatingOptions = options.imageCreatingOptions
  1514. let image = KingfisherWrapper<KFCrossPlatformImage>.image(data: data, options: creatingOptions)
  1515. image?.creatingOptions = creatingOptions
  1516. return image
  1517. }
  1518. }
  1519. }
  1520. final class FailingProcessor: ImageProcessor, @unchecked Sendable {
  1521. public let identifier = "FailingProcessor"
  1522. var processed = false
  1523. public init() {}
  1524. public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
  1525. processed = true
  1526. return nil
  1527. }
  1528. }
  1529. struct SimpleImageDataProvider: ImageDataProvider, @unchecked Sendable {
  1530. let cacheKey: String
  1531. let provider: () -> (Result<Data, any Error>)
  1532. func data(handler: @escaping (Result<Data, any Error>) -> Void) {
  1533. handler(provider())
  1534. }
  1535. struct E: Error {}
  1536. }
  1537. actor ActorBox<T> {
  1538. var value: T
  1539. init(_ value: T) {
  1540. self.value = value
  1541. }
  1542. func setValue(_ value: T) {
  1543. self.value = value
  1544. }
  1545. }
  1546. actor ActorArray<Element> {
  1547. var value: [Element]
  1548. init(_ value: [Element]) {
  1549. self.value = value
  1550. }
  1551. func append(_ newElement: Element) {
  1552. value.append(newElement)
  1553. }
  1554. }