Преглед на файлове

Add Upload Progress Test (#3765)

### Issue Link :link:
#3722

### Goals :soccer:
This PR adds a test to show that intermediate upload progress values are
properly received.

One this I noticed here is that, when multiple progress values are
issued, it's not guaranteed that there's a final 100% progress value.

### Testing Details :mag:
This test uploads from a stream with a delay between each buffer to
ensure that `URLSession` properly issues at least 2 upload progress
callbacks.
Jon Shier преди 2 години
родител
ревизия
dc71baf4f1
променени са 2 файла, в които са добавени 101 реда и са изтрити 0 реда
  1. 92 0
      Tests/ConcurrencyTests.swift
  2. 9 0
      Tests/TestHelpers.swift

+ 92 - 0
Tests/ConcurrencyTests.swift

@@ -565,6 +565,98 @@ final class DataStreamConcurrencyTests: BaseTestCase {
     }
 }
 
+// Avoid when using swift-corelibs-foundation.
+// Only Xcode 14.3+ has async fulfillment.
+#if !canImport(FoundationNetworking) && swift(>=5.8)
+@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
+final class UploadConcurrencyTests: BaseTestCase {
+    func testThatDelayedUploadStreamResultsInMultipleProgressValues() async throws {
+        // Given
+        let count = 75
+        let baseString = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
+        let baseData = Data(baseString.utf8)
+        var request = Endpoint.upload.urlRequest
+        request.headers.add(name: "Content-Length", value: "\(baseData.count * count)")
+        let expectation = expectation(description: "Bytes upload progress should be reported: \(request.url!)")
+
+        var uploadProgressValues: [Double] = []
+        var downloadProgressValues: [Double] = []
+
+        var response: DataResponse<UploadResponse, AFError>?
+
+        var inputStream: InputStream!
+        var outputStream: OutputStream!
+        Stream.getBoundStreams(withBufferSize: baseData.count, inputStream: &inputStream, outputStream: &outputStream)
+        CFWriteStreamSetDispatchQueue(outputStream, .main)
+        outputStream.open()
+
+        // When
+        AF.upload(inputStream, with: request)
+            .uploadProgress { progress in
+                uploadProgressValues.append(progress.fractionCompleted)
+            }
+            .downloadProgress { progress in
+                downloadProgressValues.append(progress.fractionCompleted)
+            }
+            .responseDecodable(of: UploadResponse.self) { resp in
+                response = resp
+                expectation.fulfill()
+                inputStream.close()
+            }
+
+        func sendData() {
+            baseData.withUnsafeBytes { (pointer: UnsafeRawBufferPointer) in
+                let bytesStreamed = outputStream.write(pointer.baseAddress!, maxLength: baseData.count)
+                switch bytesStreamed {
+                case baseData.count:
+                    // Successfully sent.
+                    break
+                case 0:
+                    XCTFail("outputStream somehow reached end")
+                case -1:
+                    if let streamError = outputStream.streamError {
+                        XCTFail("outputStream.write failed with error: \(streamError)")
+                    } else {
+                        XCTFail("outputStream.write failed with unknown error")
+                    }
+                default:
+                    XCTFail("outputStream failed to send \(baseData.count) bytes, sent \(bytesStreamed) instead.")
+                }
+            }
+        }
+
+        for _ in 0..<count {
+            sendData()
+
+            try await Task.sleep(nanoseconds: 3 * 1_000_000) // milliseconds
+        }
+
+        outputStream.close()
+
+        await fulfillment(of: [expectation], timeout: timeout)
+
+        // Then
+        XCTAssertNotNil(response?.request)
+        XCTAssertNotNil(response?.response)
+        XCTAssertNotNil(response?.data)
+        XCTAssertNil(response?.error)
+
+        for (progress, nextProgress) in zip(uploadProgressValues, uploadProgressValues.dropFirst()) {
+            XCTAssertGreaterThanOrEqual(nextProgress, progress)
+        }
+
+        XCTAssertGreaterThan(uploadProgressValues.count, 1, "there should more than 1 uploadProgressValues")
+
+        for (progress, nextProgress) in zip(downloadProgressValues, downloadProgressValues.dropFirst()) {
+            XCTAssertGreaterThanOrEqual(nextProgress, progress)
+        }
+
+        XCTAssertEqual(downloadProgressValues.last, 1.0, "last item in downloadProgressValues should equal 1.0")
+        XCTAssertEqual(response?.value?.bytes, baseData.count * count)
+    }
+}
+#endif
+
 @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
 final class ClosureAPIConcurrencyTests: BaseTestCase {
     func testThatDownloadProgressStreamReturnsProgress() async {

+ 9 - 0
Tests/TestHelpers.swift

@@ -77,6 +77,7 @@ struct Endpoint {
         case responseHeaders
         case status(Int)
         case stream(count: Int)
+        case upload
         case xml
 
         var string: String {
@@ -117,6 +118,8 @@ struct Endpoint {
                 return "/status/\(code)"
             case let .stream(count):
                 return "/stream/\(count)"
+            case .upload:
+                return "/upload"
             case .xml:
                 return "/xml"
             }
@@ -217,6 +220,8 @@ struct Endpoint {
         Endpoint(path: .stream(count: count))
     }
 
+    static let upload: Endpoint = .init(path: .upload, method: .post, headers: [.contentType("application/octet-stream")])
+
     static var xml: Endpoint {
         Endpoint(path: .xml, headers: [.contentType("application/xml")])
     }
@@ -406,3 +411,7 @@ struct TestParameters: Encodable {
 
     let property: String
 }
+
+struct UploadResponse: Decodable {
+    let bytes: Int
+}