Skip to content

Commit

Permalink
Fix a bug where empty files can't be uploaded with multipart. (#649)
Browse files Browse the repository at this point in the history
* Fix a bug where empty files can't be uploaded with multipart.

* Add a test for empty S3 multipart upload.
  • Loading branch information
bridger committed Jan 23, 2023
1 parent 23acc9e commit 2e68afa
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 2 deletions.
6 changes: 4 additions & 2 deletions Sources/Soto/Extensions/S3/S3+multipart_API.swift
Original file line number Diff line number Diff line change
Expand Up @@ -694,8 +694,10 @@ extension S3 {
} else {
// supply payload data
inputStream(eventLoop).flatMap { payload -> EventLoopFuture<Bool> in
// if no data returned then return success
guard let size = payload.size, size > 0 else {
// if no data returned then return success. If this is the first part
// and it is empty then that means the entire file is empty. In that
// case, we do still "upload" this first empty part.
guard let size = payload.size, size > 0 || partNumber == 1 else {
return eventLoop.makeSucceededFuture(true)
}
// upload payload
Expand Down
40 changes: 40 additions & 0 deletions Tests/SotoTests/Services/S3/S3ExtensionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,46 @@ class S3ExtensionTests: XCTestCase {
XCTAssertNoThrow(try response.wait())
}

func testMultiPartUploadEmpty() {
let s3 = Self.s3.with(timeout: .minutes(2))
let data = Data() // Empty
let name = TestEnvironment.generateResourceName()
let filenameUpload = "S3MultipartUploadTestEmpty"
let filenameDownload = "S3MultipartUploadTestEmpty-Downloaded"

XCTAssertNoThrow(try data.write(to: URL(fileURLWithPath: filenameUpload)))
defer {
XCTAssertNoThrow(try FileManager.default.removeItem(atPath: filenameUpload))
}

let response = S3Tests.createBucket(name: name, s3: s3)
.flatMap { (_) -> EventLoopFuture<S3.CompleteMultipartUploadOutput> in
let request = S3.CreateMultipartUploadRequest(
bucket: name,
key: name
)
return s3.multipartUpload(request, partSize: 5 * 1024 * 1024, filename: filenameUpload, logger: TestEnvironment.logger) { print("Progress \($0 * 100)%") }
}
.flatMap { _ -> EventLoopFuture<Int64> in
// Download the empty file
let request = S3.GetObjectRequest(bucket: name, key: name)
return s3.multipartDownload(request, partSize: 1024 * 1024, filename: filenameDownload, logger: TestEnvironment.logger) { print("Progress \($0 * 100)%") }
}
.flatMapErrorThrowing { error in
print("\(error)")
throw error
}
.flatMapThrowing { size in
XCTAssertEqual(size, 0) // Empty
XCTAssert(FileManager.default.fileExists(atPath: filenameDownload))
try FileManager.default.removeItem(atPath: filenameDownload)
}
.flatAlways { _ in
return S3Tests.deleteBucket(name: name, s3: s3)
}
XCTAssertNoThrow(try response.wait())
}

func testMultiPartUploadFailure() {
let data = S3Tests.createRandomBuffer(size: 10 * 1024 * 1024)
let name = TestEnvironment.generateResourceName()
Expand Down

0 comments on commit 2e68afa

Please sign in to comment.