Skip to content

Commit 079c926

Browse files
authored
test(storage): resumable uploads abort on errors (#6790)
This new test verifies that resumable uploads abort (do not finalize) if one of the intermediate `UploadChunk()` requests has a permanent error or otherwise exhaust the retry policy. I wrote this test expecting that the library had a bug and needed fixing, but turns out I do not give myself enough credit.
1 parent bf0a350 commit 079c926

1 file changed

Lines changed: 43 additions & 15 deletions

File tree

google/cloud/storage/client_write_object_test.cc

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ using ::testing::ReturnRef;
4141
using ms = std::chrono::milliseconds;
4242

4343
/**
44-
* Test the functions in Storage::Client related to writing objects.'Objects:
45-
* *'.
44+
* Test the functions in Storage::Client related to writing objects.
4645
*/
4746
class WriteObjectTest
4847
: public ::google::cloud::storage::testing::ClientUnitTest {};
@@ -118,6 +117,48 @@ TEST_F(WriteObjectTest, WriteObjectPermanentFailure) {
118117
EXPECT_THAT(stream.metadata(), StatusIs(PermanentError().code()));
119118
}
120119

120+
TEST_F(WriteObjectTest, WriteObjectErrorInChunk) {
121+
auto const session_id = std::string{"test-session-id"};
122+
EXPECT_CALL(*mock_, CreateResumableSession)
123+
.WillOnce([&](internal::ResumableUploadRequest const& request) {
124+
EXPECT_EQ("test-bucket-name", request.bucket_name());
125+
EXPECT_EQ("test-object-name", request.object_name());
126+
127+
auto mock = absl::make_unique<testing::MockResumableUploadSession>();
128+
using internal::ResumableUploadResponse;
129+
EXPECT_CALL(*mock, done()).WillRepeatedly(Return(false));
130+
EXPECT_CALL(*mock, next_expected_byte()).WillRepeatedly(Return(0));
131+
EXPECT_CALL(*mock, UploadChunk)
132+
.WillOnce(Return(Status(StatusCode::kDataLoss, "ooops")));
133+
EXPECT_CALL(*mock, session_id()).WillRepeatedly(ReturnRef(session_id));
134+
// The call to Close() below should have no effect and no "final" chunk
135+
// should be uplaoded.
136+
EXPECT_CALL(*mock, UploadFinalChunk).Times(0);
137+
138+
return make_status_or(
139+
std::unique_ptr<internal::ResumableUploadSession>(std::move(mock)));
140+
});
141+
auto constexpr kQuantum = internal::UploadChunkRequest::kChunkSizeQuantum;
142+
client_options_.SetUploadBufferSize(kQuantum);
143+
auto client = ClientForMock();
144+
auto stream = client.WriteObject("test-bucket-name", "test-object-name",
145+
IfGenerationMatch(0));
146+
auto const data = std::string(2 * kQuantum, 'A');
147+
auto const size = static_cast<std::streamsize>(data.size());
148+
// The stream is set up to flush for buffers of `data`'s size. This triggers
149+
// the UploadChunk() mock, which returns an error. Because this is a permanent
150+
// error, no further upload attempts are made.
151+
stream.write(data.data(), size);
152+
EXPECT_TRUE(stream.bad());
153+
EXPECT_THAT(stream.last_status(), StatusIs(StatusCode::kDataLoss));
154+
stream.write(data.data(), size);
155+
EXPECT_TRUE(stream.bad());
156+
EXPECT_THAT(stream.last_status(), StatusIs(StatusCode::kDataLoss));
157+
EXPECT_THAT(stream.metadata(), StatusIs(StatusCode::kUnknown));
158+
stream.Close();
159+
EXPECT_THAT(stream.metadata(), StatusIs(StatusCode::kDataLoss));
160+
}
161+
121162
TEST_F(WriteObjectTest, WriteObjectPermanentSessionFailurePropagates) {
122163
auto* mock_session = new testing::MockResumableUploadSession;
123164
auto returner = [mock_session](internal::ResumableUploadRequest const&) {
@@ -154,19 +195,6 @@ class MockFilebuf : public std::filebuf {
154195
}
155196
};
156197

157-
class MockFilebufStream : public std::ifstream {
158-
public:
159-
explicit MockFilebufStream(std::string const& s) {
160-
buf_.open(s.c_str(), std::ios_base::in);
161-
init(&buf_);
162-
clear();
163-
std::cout << "OPEN? " << buf_.is_open() << std::endl;
164-
}
165-
166-
private:
167-
MockFilebuf buf_;
168-
};
169-
170198
TEST_F(WriteObjectTest, UploadStreamResumable) {
171199
auto const quantum = internal::UploadChunkRequest::kChunkSizeQuantum;
172200
auto rng = google::cloud::internal::MakeDefaultPRNG();

0 commit comments

Comments
 (0)