| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737 |
- //
- // ImageDownloaderTests.swift
- // Kingfisher
- //
- // Created by Wei Wang on 15/4/10.
- //
- // Copyright (c) 2019 Wei Wang <onevcat@gmail.com>
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- import XCTest
- @testable import Kingfisher
- class ImageDownloaderTests: XCTestCase {
- var downloader: ImageDownloader!
- override class func setUp() {
- super.setUp()
- LSNocilla.sharedInstance().start()
- }
-
- override class func tearDown() {
- LSNocilla.sharedInstance().stop()
- super.tearDown()
- }
-
- override func setUp() {
- super.setUp()
- downloader = ImageDownloader(name: "test")
- }
-
- override func tearDown() {
- LSNocilla.sharedInstance().clearStubs()
- downloader = nil
- super.tearDown()
- }
-
- func testDownloadAnImage() {
- let exp = expectation(description: #function)
-
- let url = testURLs[0]
- stub(url, data: testImageData)
- downloader.downloadImage(with: url) { result in
- XCTAssertNotNil(result.value)
- exp.fulfill()
- }
- waitForExpectations(timeout: 3, handler: nil)
- }
-
- func testDownloadAnImageAsync() async throws {
- let url = testURLs[0]
- stub(url, data: testImageData)
-
- let result = try await downloader.downloadImage(with: url, options: .empty)
- XCTAssertEqual(result.originalData, testImageData)
- }
-
- func testDownloadMultipleImages() {
- let exp = expectation(description: #function)
- let group = DispatchGroup()
-
- for url in testURLs {
- group.enter()
- stub(url, data: testImageData)
- downloader.downloadImage(with: url) { result in
- XCTAssertNotNil(result.value)
- group.leave()
- }
- }
-
- group.notify(queue: .main, execute: exp.fulfill)
- waitForExpectations(timeout: 3, handler: nil)
- }
-
- func testDownloadMultipleImagesAsync() async throws {
- try await withThrowingTaskGroup(of: ImageLoadingResult.self) { group in
- for url in testURLs {
- stub(url, data: testImageData)
- group.addTask {
- try await self.downloader.downloadImage(with: url)
- }
- }
-
- for try await result in group {
- XCTAssertEqual(result.originalData, testImageData)
- }
- }
- }
-
- func testDownloadAnImageWithMultipleCallback() {
- let exp = expectation(description: #function)
-
- let group = DispatchGroup()
- let url = testURLs[0]
- stub(url, data: testImageData)
- for _ in 0...5 {
- group.enter()
- downloader.downloadImage(with: url) { result in
- XCTAssertNotNil(result.value)
- group.leave()
- }
- }
- group.notify(queue: .main, execute: exp.fulfill)
- waitForExpectations(timeout: 5, handler: nil)
- }
-
- func testDownloadWithModifyingRequest() {
- let exp = expectation(description: #function)
- let url = testURLs[0]
- stub(url, data: testImageData)
-
- let modifier = URLModifier(url: url)
-
- let someURL = URL(string: "some_strange_url")!
- let task = downloader.downloadImage(with: someURL, options: [.requestModifier(modifier)]) { result in
- XCTAssertNotNil(result.value)
- XCTAssertEqual(result.value?.url, url)
- exp.fulfill()
- }
- XCTAssertTrue(task.isInitialized)
- waitForExpectations(timeout: 3, handler: nil)
- }
- func testDownloadWithAsyncModifyingRequest() {
- let exp = expectation(description: #function)
- let url = testURLs[0]
- stub(url, data: testImageData)
- let downloadTaskCalled = ActorBox(false)
- let asyncModifier = AsyncURLModifier(url: url, onDownloadTaskStarted: { task in
- XCTAssertNotNil(task)
- Task {
- await downloadTaskCalled.setValue(true)
- }
- })
- let someURL = URL(string: "some_strange_url")!
- let task = downloader.downloadImage(with: someURL, options: [.requestModifier(asyncModifier)]) { result in
- XCTAssertNotNil(result.value)
- XCTAssertEqual(result.value?.url, url)
- Task {
- let result = await downloadTaskCalled.value
- XCTAssertTrue(result)
- exp.fulfill()
- }
- }
- // The returned task is nil since the download is not starting immediately.
- XCTAssertFalse(task.isInitialized)
- waitForExpectations(timeout: 3, handler: nil)
- }
- func testDownloadWithModifyingRequestToNil() {
- let nilModifier = AnyModifier { _ in
- return nil
- }
- let exp = expectation(description: #function)
- let someURL = URL(string: "some_strange_url")!
- downloader.downloadImage(with: someURL, options: [.requestModifier(nilModifier)]) { result in
- XCTAssertNotNil(result.error)
- guard case .requestError(reason: .emptyRequest) = result.error! else {
- XCTFail()
- fatalError()
- }
- exp.fulfill()
- }
- waitForExpectations(timeout: 3, handler: nil)
- }
-
- func testServerInvalidStatusCode() {
- let exp = expectation(description: #function)
-
- let url = testURLs[0]
- stub(url, data: testImageData, statusCode: 404)
-
- downloader.downloadImage(with: url) { result in
- XCTAssertNotNil(result.error)
- XCTAssertTrue(result.error!.isInvalidResponseStatusCode(404))
- exp.fulfill()
- }
-
- waitForExpectations(timeout: 3, handler: nil)
- }
-
- func testDownloadResultErrorAndRetry() {
- let exp = expectation(description: #function)
-
- let url = testURLs[0]
- stub(url, errorCode: -1)
- downloader.downloadImage(with: url) { result in
- XCTAssertNotNil(result.error)
-
- LSNocilla.sharedInstance().clearStubs()
- stub(url, data: testImageData)
- // Retry the download
- self.downloader.downloadImage(with: url) { result in
- XCTAssertNil(result.error)
- exp.fulfill()
- }
- }
-
- waitForExpectations(timeout: 3, handler: nil)
- }
-
- func testDownloadEmptyURL() {
- let exp = expectation(description: #function)
-
- let modifier = URLModifier(url: nil)
-
- let url = URL(string: "http://onevcat.com")!
- downloader.downloadImage(
- with: url,
- options: [.requestModifier(modifier)],
- progressBlock: { received, totalSize in XCTFail("The progress block should not be called.") })
- {
- result in
- XCTAssertNotNil(result.error)
- if case .requestError(reason: .invalidURL(let request)) = result.error! {
- XCTAssertNil(request.url)
- } else {
- XCTFail()
- }
- exp.fulfill()
- }
-
- waitForExpectations(timeout: 3, handler: nil)
- }
-
- func testDownloadTaskProperty() {
- let task = downloader.downloadImage(with: URL(string: "1234")!)
- XCTAssertNotNil(task, "The task should exist.")
- }
-
- func testCancelDownloadTask() {
- let exp = expectation(description: #function)
- let url = testURLs[0]
- let stub = delayedStub(url, data: testImageData, length: 123)
-
- let task = downloader.downloadImage(
- with: url,
- progressBlock: { _, _ in XCTFail() })
- {
- result in
- XCTAssertNotNil(result.error)
- XCTAssertTrue(result.error!.isTaskCancelled)
- delay(0.1) { exp.fulfill() }
- }
-
- XCTAssertTrue(task.isInitialized)
- task.cancel()
- _ = stub.go()
-
- waitForExpectations(timeout: 3, handler: nil)
- }
-
- func testCancelDownloadTaskAsync() async throws {
- let url = testURLs[0]
- let stub = delayedStub(url, data: testImageData, length: 123)
-
- let checker = CallingChecker()
- try await checker.checkCancelBehavior(stub: stub) {
- _ = try await self.downloader.downloadImage(with: url)
- }
- }
- func testCancelOneDownloadTask() {
- let exp = expectation(description: #function)
- let url = testURLs[0]
- let stub = delayedStub(url, data: testImageData)
- let group = DispatchGroup()
- group.enter()
- let task1 = downloader.downloadImage(with: url) { result in
- XCTAssertNotNil(result.error)
- group.leave()
- }
- group.enter()
- _ = downloader.downloadImage(with: url) { result in
- XCTAssertNotNil(result.value?.image)
- group.leave()
- }
- task1.cancel()
- delay(0.1) { _ = stub.go() }
- group.notify(queue: .main) {
- delay(0.1) { exp.fulfill() }
- }
- waitForExpectations(timeout: 3, handler: nil)
- }
-
- func testCancelAllDownloadTasks() {
- let exp = expectation(description: #function)
- let url1 = testURLs[0]
- let stub1 = delayedStub(url1, data: testImageData)
- let url2 = testURLs[1]
- let stub2 = delayedStub(url2, data: testImageData)
- let group = DispatchGroup()
- let urls = [url1, url1, url2]
- urls.forEach {
- group.enter()
- downloader.downloadImage(with: $0) { result in
- XCTAssertNotNil(result.error)
- XCTAssertTrue(result.error!.isTaskCancelled)
- group.leave()
- }
- }
- delay(0.1) {
- self.downloader.cancelAll()
- _ = stub1.go()
- _ = stub2.go()
- }
- group.notify(queue: .main) {
- delay(0.1) { exp.fulfill() }
- }
- waitForExpectations(timeout: 3, handler: nil)
- }
-
- func testCancelDownloadTaskForURL() {
- let exp = expectation(description: #function)
-
- let url1 = testURLs[0]
- let stub1 = delayedStub(url1, data: testImageData)
-
- let url2 = testURLs[1]
- let stub2 = delayedStub(url2, data: testImageData)
-
- let group = DispatchGroup()
-
- group.enter()
- downloader.downloadImage(with: url1) { result in
- XCTAssertNotNil(result.error)
- XCTAssertTrue(result.error!.isTaskCancelled)
- group.leave()
- }
-
- group.enter()
- downloader.downloadImage(with: url1) { result in
- XCTAssertNotNil(result.error)
- XCTAssertTrue(result.error!.isTaskCancelled)
- group.leave()
- }
-
- group.enter()
- downloader.downloadImage(with: url2) { result in
- XCTAssertNotNil(result.value)
- group.leave()
- }
-
- delay(0.1) {
- self.downloader.cancel(url: url1)
- _ = stub1.go()
- _ = stub2.go()
- }
-
- group.notify(queue: .main) {
- delay(0.1) { exp.fulfill() }
- }
- waitForExpectations(timeout: 3, handler: nil)
- }
-
- // Issue 532 https://github.com/onevcat/Kingfisher/issues/532#issuecomment-305644311
- func testCancelThenRestartSameDownload() {
- let exp = expectation(description: #function)
-
- let url = testURLs[0]
- let stub = delayedStub(url, data: testImageData, length: 123)
- let group = DispatchGroup()
-
- group.enter()
- let downloadTask = downloader.downloadImage(
- with: url,
- progressBlock: { _, _ in XCTFail()})
- {
- result in
- XCTAssertNotNil(result.error)
- XCTAssertTrue(result.error!.isTaskCancelled)
- group.leave()
- }
-
- XCTAssertTrue(downloadTask.isInitialized)
- downloadTask.cancel()
- _ = stub.go()
-
- group.enter()
- downloader.downloadImage(with: url) {
- result in
- XCTAssertNotNil(result.value)
- if let error = result.error {
- print(error)
- }
- group.leave()
- }
-
- group.notify(queue: .main) {
- delay(0.1) { exp.fulfill() }
- }
- waitForExpectations(timeout: 3, handler: nil)
- }
-
- func testDownloadTaskNilWithNilURL() {
- let modifier = URLModifier(url: nil)
- let downloadTask = downloader.downloadImage(with: URL(string: "url")!, options: [.requestModifier(modifier)])
- XCTAssertFalse(downloadTask.isInitialized)
- }
-
- func testDownloadWithProcessor() {
- let exp = expectation(description: #function)
-
- let url = testURLs[0]
- stub(url, data: testImageData)
- let p = RoundCornerImageProcessor(cornerRadius: 40)
- let roundCornered = testImage.kf.image(withRoundRadius: 40, fit: testImage.kf.size)
-
- downloader.downloadImage(with: url, options: [.processor(p)]) { result in
- XCTAssertNotNil(result.value)
- let image = result.value!.image
- XCTAssertFalse(image.renderEqual(to: testImage))
- XCTAssertTrue(image.renderEqual(to: roundCornered))
- XCTAssertEqual(result.value!.originalData, testImageData)
- exp.fulfill()
- }
-
- waitForExpectations(timeout: 3, handler: nil)
- }
-
- func testDownloadWithDifferentProcessors() {
- let exp = expectation(description: #function)
- let url = testURLs[0]
- let stub = delayedStub(url, data: testImageData)
- let p1 = RoundCornerImageProcessor(cornerRadius: 40)
- let roundCornered = testImage.kf.image(withRoundRadius: 40, fit: testImage.kf.size)
- let p2 = BlurImageProcessor(blurRadius: 3.0)
- let blurred = testImage.kf.blurred(withRadius: 3.0)
-
- let group = DispatchGroup()
- group.enter()
- let task1 = downloader.downloadImage(with: url, options: [.processor(p1)]) { result in
- XCTAssertTrue(result.value!.image.renderEqual(to: roundCornered))
- group.leave()
- }
- group.enter()
- let task2 = downloader.downloadImage(with: url, options: [.processor(p2)]) { result in
- XCTAssertTrue(result.value!.image.renderEqual(to: blurred))
- group.leave()
- }
- XCTAssertNotNil(task1)
- XCTAssertEqual(task1.sessionTask?.task, task2.sessionTask?.task)
- _ = stub.go()
-
- group.notify(queue: .main, execute: exp.fulfill)
- waitForExpectations(timeout: 3, handler: nil)
- }
-
- func testDownloadedDataCouldBeModified() {
- let exp = expectation(description: #function)
-
- let url = testURLs[0]
- stub(url, data: testImageData)
-
- let modifier = URLNilDataModifier()
- downloader.delegate = modifier
- downloader.downloadImage(with: url) { result in
- XCTAssertNil(result.value)
- XCTAssertNotNil(result.error)
- if case .responseError(reason: .dataModifyingFailed) = result.error! {
- } else {
- XCTFail()
- }
- self.downloader.delegate = nil
- // hold delegate
- _ = modifier
- exp.fulfill()
- }
- waitForExpectations(timeout: 3, handler: nil)
- }
- func testDownloadedDataCouldBeModifiedWithTask() {
- let exp = expectation(description: #function)
-
- let url = testURLs[0]
- stub(url, data: testImageData)
-
- let modifier = TaskNilDataModifier()
- downloader.delegate = modifier
- downloader.downloadImage(with: url) { result in
- XCTAssertNil(result.value)
- XCTAssertNotNil(result.error)
- if case .responseError(reason: .dataModifyingFailed) = result.error! {
- } else {
- XCTFail()
- }
- self.downloader.delegate = nil
- // hold delegate
- _ = modifier
- exp.fulfill()
- }
- waitForExpectations(timeout: 3, handler: nil)
- }
-
- #if os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)
- func testModifierShouldOnlyApplyForFinalResultWhenDownload() {
- let exp = expectation(description: #function)
- let url = testURLs[0]
- stub(url, data: testImageData)
- let modifierCalled = ActorBox(false)
- let modifier = AnyImageModifier { image in
- Task {
- await modifierCalled.setValue(true)
- }
- return image.withRenderingMode(.alwaysTemplate)
- }
- downloader.downloadImage(with: url, options: [.imageModifier(modifier)]) { result in
- XCTAssertEqual(result.value?.image.renderingMode, .automatic)
- Task {
- let called = await modifierCalled.value
- XCTAssertFalse(called)
- exp.fulfill()
- }
- }
- waitForExpectations(timeout: 3, handler: nil)
- }
- #endif
-
- func testDownloadTaskTakePriorityOption() {
- let exp = expectation(description: #function)
-
- let url = testURLs[0]
- stub(url, data: testImageData)
- let task = downloader.downloadImage(with: url, options: [.downloadPriority(URLSessionTask.highPriority)])
- {
- _ in
- exp.fulfill()
- }
- XCTAssertEqual(task.sessionTask?.task.priority, URLSessionTask.highPriority)
- waitForExpectations(timeout: 3, handler: nil)
- }
-
-
- func testSessionDelegate() {
- class ExtensionDelegate: SessionDelegate, @unchecked Sendable {
- //'exp' only for test
- public let exp: XCTestExpectation
- init(_ expectation:XCTestExpectation) {
- exp = expectation
- }
- func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
- exp.fulfill()
- }
- }
- downloader.sessionDelegate = ExtensionDelegate(expectation(description: #function))
-
- let url = testURLs[0]
- stub(url, data: testImageData)
- downloader.downloadImage(with: url) { result in
- XCTAssertNotNil(result.value)
- }
- waitForExpectations(timeout: 3, handler: nil)
- }
- func testDownloaderReceiveResponsePass() {
- let exp = expectation(description: #function)
- let url = testURLs[0]
- stub(url, data: testImageData, headers: ["someKey": "someValue"])
- let handler = TaskResponseCompletion()
- let obj = NSObject()
- handler.onReceiveResponse.delegate(on: obj) { (obj, response) in
- guard let httpResponse = response as? HTTPURLResponse else {
- XCTFail("Should be an HTTP response.")
- return .cancel
- }
- XCTAssertEqual(httpResponse.statusCode, 200)
- XCTAssertEqual(httpResponse.url, url)
- XCTAssertEqual(httpResponse.allHeaderFields["someKey"] as? String, "someValue")
- return .allow
- }
- downloader.delegate = handler
- downloader.downloadImage(with: url) { result in
- XCTAssertNotNil(result.value)
- XCTAssertNil(result.error)
- self.downloader.delegate = nil
- // hold delegate
- _ = handler
- exp.fulfill()
- }
- waitForExpectations(timeout: 3, handler: nil)
- }
- func testDownloaderReceiveResponseFailure() {
- let exp = expectation(description: #function)
- let url = testURLs[0]
- stub(url, data: testImageData, headers: ["someKey": "someValue"])
- let handler = TaskResponseCompletion()
- let obj = NSObject()
- handler.onReceiveResponse.delegate(on: obj) { (obj, response) in
- guard let httpResponse = response as? HTTPURLResponse else {
- XCTFail("Should be an HTTP response.")
- return .cancel
- }
- XCTAssertEqual(httpResponse.statusCode, 200)
- XCTAssertEqual(httpResponse.url, url)
- XCTAssertEqual(httpResponse.allHeaderFields["someKey"] as? String, "someValue")
- return .cancel
- }
- downloader.delegate = handler
- downloader.downloadImage(with: url) { result in
- XCTAssertNil(result.value)
- XCTAssertNotNil(result.error)
- if case .responseError(reason: .cancelledByDelegate) = result.error! {
- } else {
- XCTFail()
- }
- self.downloader.delegate = nil
- // hold delegate
- _ = handler
- exp.fulfill()
- }
- waitForExpectations(timeout: 3, handler: nil)
- }
-
- func testDownloadingLivePhotoResources() async throws {
- let url = testURLs[0]
- stub(url, data: testImageData)
- let result = try await downloader.downloadLivePhotoResource(with: url, options: .init(.empty))
- XCTAssertEqual(result.originalData, testImageData)
- XCTAssertEqual(result.url, url)
- }
- }
- class URLNilDataModifier: ImageDownloaderDelegate {
- func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, for url: URL) -> Data? {
- return nil
- }
- }
- class TaskNilDataModifier: ImageDownloaderDelegate {
- func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, with dataTask: SessionDataTask) -> Data? {
- return nil
- }
- }
- class TaskResponseCompletion: ImageDownloaderDelegate {
- let onReceiveResponse = Delegate<URLResponse, URLSession.ResponseDisposition>()
- func imageDownloader(_ downloader: ImageDownloader, didReceive response: URLResponse) async -> URLSession.ResponseDisposition {
- return onReceiveResponse.call(response)!
- }
- }
- final class URLModifier: ImageDownloadRequestModifier {
- let url: URL?
- init(url: URL?) {
- self.url = url
- }
- func modified(for request: URLRequest) -> URLRequest? {
- var r = request
- r.url = url
- return r
- }
- }
- final class AsyncURLModifier: AsyncImageDownloadRequestModifier {
- let url: URL?
- let onDownloadTaskStarted: (@Sendable (DownloadTask?) -> Void)?
-
- init(url: URL?, onDownloadTaskStarted: (@Sendable (DownloadTask?) -> Void)?) {
- self.url = url
- self.onDownloadTaskStarted = onDownloadTaskStarted
- }
- func modified(for request: URLRequest) async -> URLRequest? {
- var r = request
- r.url = url
- // Simulate an async action
- try? await Task.sleep(nanoseconds: 1_000_000)
- return r
- }
- }
|