From bc037b72455e33cff81b8e2c3e35b4d3dcaf4d74 Mon Sep 17 00:00:00 2001 From: v-pratap Date: Wed, 3 Dec 2025 04:50:57 +0000 Subject: [PATCH 01/13] feat(storage): Add full object checksum validation at finalization for JSON path --- .bazelignore | 1 + .../cloud/storage/internal/object_requests.h | 3 ++ .../internal/object_write_streambuf.cc | 15 +++++--- google/cloud/storage/internal/rest/stub.cc | 23 ++++++++---- google/cloud/storage/quickstart/quickstart.cc | 36 +++++++++++-------- 5 files changed, 53 insertions(+), 25 deletions(-) diff --git a/.bazelignore b/.bazelignore index 98112232070b2..c4f4ebf8126e7 100644 --- a/.bazelignore +++ b/.bazelignore @@ -5,3 +5,4 @@ cmake-build-debug/ cmake-build-coverage/ cmake-build-release/ .build/ +_build/ diff --git a/google/cloud/storage/internal/object_requests.h b/google/cloud/storage/internal/object_requests.h index 4e18ef4bc190d..6329b76eda038 100644 --- a/google/cloud/storage/internal/object_requests.h +++ b/google/cloud/storage/internal/object_requests.h @@ -534,6 +534,9 @@ class UploadChunkRequest HashValues const& known_object_hashes() const { return known_object_hashes_; } HashFunction& hash_function() const { return *hash_function_; } + std::shared_ptr hash_function_ptr() const { + return hash_function_; + } bool last_chunk() const { return upload_size_.has_value(); } std::size_t payload_size() const { return TotalBytes(payload_); } diff --git a/google/cloud/storage/internal/object_write_streambuf.cc b/google/cloud/storage/internal/object_write_streambuf.cc index 486b4e15b2b03..b995ca7bc8bb9 100644 --- a/google/cloud/storage/internal/object_write_streambuf.cc +++ b/google/cloud/storage/internal/object_write_streambuf.cc @@ -144,12 +144,18 @@ void ObjectWriteStreambuf::FlushFinal() { // Calculate the portion of the buffer that needs to be uploaded, if any. auto const actual_size = put_area_size(); + HashValues final_hashes = known_hashes_; + if (hash_function_) { + hash_function_->Update(committed_size_, {pbase(), actual_size}); + final_hashes = hash_function_->Finish(); + hash_function_.reset(); + } // After this point the session will be closed, and no more calls to the hash // function are possible. auto upload_request = UploadChunkRequest(upload_id_, committed_size_, {ConstBuffer(pbase(), actual_size)}, - hash_function_, known_hashes_); + hash_function_, final_hashes); request_.ForEachOption(internal::CopyCommonOptions(upload_request)); OptionsSpan const span(span_options_); auto response = connection_->UploadChunk(upload_request); @@ -157,9 +163,7 @@ void ObjectWriteStreambuf::FlushFinal() { last_status_ = std::move(response).status(); return; } - - auto function = std::move(hash_function_); - hash_values_ = std::move(*function).Finish(); + hash_values_ = final_hashes; committed_size_ = response->committed_size.value_or(0); metadata_ = std::move(response->payload); @@ -203,6 +207,9 @@ void ObjectWriteStreambuf::FlushRoundChunk(ConstBufferSequence buffers) { auto upload_request = UploadChunkRequest(upload_id_, committed_size_, payload, hash_function_); request_.ForEachOption(internal::CopyCommonOptions(upload_request)); + upload_request.ForEachOption([](auto const& opt) { + std::cout << "DEBUG: option=" << opt << "\n"; + }); OptionsSpan const span(span_options_); auto response = connection_->UploadChunk(upload_request); if (!response) { diff --git a/google/cloud/storage/internal/rest/stub.cc b/google/cloud/storage/internal/rest/stub.cc index 32c0e93542be9..bd0a4796d9c4e 100644 --- a/google/cloud/storage/internal/rest/stub.cc +++ b/google/cloud/storage/internal/rest/stub.cc @@ -770,12 +770,23 @@ StatusOr RestStub::UploadChunk( // default (at least in this case), and that wastes bandwidth as the content // length is known. builder.AddHeader("Transfer-Encoding", {}); - auto offset = request.offset(); - for (auto const& b : request.payload()) { - request.hash_function().Update(offset, - absl::string_view{b.data(), b.size()}); - offset += b.size(); - } + auto hash_function = request.hash_function_ptr(); + if (hash_function) { + auto offset = request.offset(); + for (auto const& b : request.payload()) { + hash_function->Update(offset, absl::string_view{b.data(), b.size()}); + offset += b.size(); + } + } + if (request.last_chunk()) { + auto const& hashes = request.known_object_hashes(); + if (!hashes.crc32c.empty()) { + builder.AddHeader("x-goog-hash", "crc32c=" + hashes.crc32c); + } + if (!hashes.md5.empty()) { + builder.AddHeader("x-goog-hash", "md5=" + hashes.md5); + } + } auto failure_predicate = [](rest::HttpStatusCode code) { return (code != rest::HttpStatusCode::kResumeIncomplete && diff --git a/google/cloud/storage/quickstart/quickstart.cc b/google/cloud/storage/quickstart/quickstart.cc index 0faff90d343f3..0edf184a540db 100644 --- a/google/cloud/storage/quickstart/quickstart.cc +++ b/google/cloud/storage/quickstart/quickstart.cc @@ -20,12 +20,12 @@ #include int main(int argc, char* argv[]) { - if (argc != 2) { - std::cerr << "Missing bucket name.\n"; - std::cerr << "Usage: quickstart \n"; - return 1; - } - std::string const bucket_name = argv[1]; + // if (argc != 2) { + // std::cerr << "Missing bucket name.\n"; + // std::cerr << "Usage: quickstart \n"; + // return 1; + // } + std::string const bucket_name = "vaibhav-test-001"; // Create a client to communicate with Google Cloud Storage. This client // uses the default configuration for authentication and project id. @@ -41,8 +41,14 @@ int main(int argc, char* argv[]) { auto client = google::cloud::storage::Client(options); - auto writer = client.WriteObject(bucket_name, "quickstart.txt"); - writer << "Hello World!"; + auto writer = client.WriteObject(bucket_name, "quickstart1.txt"); + std::string x ="Hello World1!"; + for(int i=0;i<17;i++){ + x+=x; + } + for(int i=0;i<15;i++){ + writer << x.data(); + } writer.Close(); if (!writer.metadata()) { std::cerr << "Error creating object: " << writer.metadata().status() @@ -51,14 +57,14 @@ int main(int argc, char* argv[]) { } std::cout << "Successfully created object: " << *writer.metadata() << "\n"; - auto reader = client.ReadObject(bucket_name, "quickstart.txt"); - if (!reader) { - std::cerr << "Error reading object: " << reader.status() << "\n"; - return 1; - } + // auto reader = client.ReadObject(bucket_name, "quickstart.txt"); + // if (!reader) { + // std::cerr << "Error reading object: " << reader.status() << "\n"; + // return 1; + // } - std::string contents{std::istreambuf_iterator{reader}, {}}; - std::cout << contents << "\n"; + // std::string contents{std::istreambuf_iterator{reader}, {}}; + // std::cout << contents << "\n"; return 0; } From e2b3363dacb4887c0f1049090cae1e6e0c1549b6 Mon Sep 17 00:00:00 2001 From: v-pratap Date: Thu, 4 Dec 2025 05:33:17 +0000 Subject: [PATCH 02/13] Add tests --- .../internal/object_write_streambuf_test.cc | 31 +++++ .../cloud/storage/internal/rest/stub_test.cc | 122 +++++++++++++++++- 2 files changed, 149 insertions(+), 4 deletions(-) diff --git a/google/cloud/storage/internal/object_write_streambuf_test.cc b/google/cloud/storage/internal/object_write_streambuf_test.cc index 9b401ab4cd750..077dc83a67e55 100644 --- a/google/cloud/storage/internal/object_write_streambuf_test.cc +++ b/google/cloud/storage/internal/object_write_streambuf_test.cc @@ -670,6 +670,37 @@ TEST(ObjectWriteStreambufTest, WriteObjectWithCustomHeader) { EXPECT_STATUS_OK(response); } +/// @test Verify that hashes are computed and passed in FlushFinal. +TEST(ObjectWriteStreambufTest, FlushFinalWithHashes) { + auto mock = std::make_unique(); + auto const quantum = UploadChunkRequest::kChunkSizeQuantum; + std::string const payload = "small test payload"; + + EXPECT_CALL(*mock, UploadChunk).WillOnce([&](UploadChunkRequest const& r) { + EXPECT_EQ(payload.size(), r.payload_size()); + EXPECT_EQ(0, r.offset()); + EXPECT_TRUE(r.last_chunk()); + EXPECT_EQ(r.hash_function_ptr(), nullptr); + EXPECT_EQ(r.known_object_hashes().crc32c, ComputeCrc32cChecksum(payload)); + EXPECT_EQ(r.known_object_hashes().md5, ComputeMD5Hash(payload)); + return QueryResumableUploadResponse{payload.size(), ObjectMetadata()}; + }); + + ResumableUploadRequest request; + request.set_option(DisableCrc32cChecksum(false)); + request.set_option(DisableMD5Hash(false)); + ObjectWriteStreambuf streambuf( + std::move(mock), request, "test-only-upload-id", + /*committed_size=*/0, absl::nullopt, /*max_buffer_size=*/quantum, + CreateHashFunction(Crc32cChecksumValue(), DisableCrc32cChecksum(false), + MD5HashValue(), DisableMD5Hash(false)), + HashValues{}, CreateHashValidator(request), AutoFinalizeConfig::kEnabled); + + streambuf.sputn(payload.data(), payload.size()); + auto response = streambuf.Close(); + EXPECT_STATUS_OK(response); +} + } // namespace } // namespace internal GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END diff --git a/google/cloud/storage/internal/rest/stub_test.cc b/google/cloud/storage/internal/rest/stub_test.cc index b915bb2a3b07d..2eee218c788c1 100644 --- a/google/cloud/storage/internal/rest/stub_test.cc +++ b/google/cloud/storage/internal/rest/stub_test.cc @@ -13,6 +13,7 @@ // limitations under the License. #include "google/cloud/storage/internal/rest/stub.h" +#include "google/cloud/storage/internal/hash_function.h" #include "google/cloud/storage/testing/canonical_errors.h" #include "google/cloud/internal/api_client_header.h" #include "google/cloud/testing_util/mock_rest_client.h" @@ -45,9 +46,35 @@ using ::testing::Pair; using ::testing::ResultOf; using ::testing::Return; +class NoOpHashFunction : public HashFunction { + public: + std::string Name() const override { return "NoOp"; } + void Update(absl::string_view b) override { Cormorant(b); } + Status Update(std::int64_t o, absl::string_view b) override { + Cormorant(o, b); + return Status{}; + } + Status Update(std::int64_t o, absl::string_view b, + std::uint32_t c) override { + Cormorant(o, b, c); + return Status{}; + } + Status Update(std::int64_t o, absl::Cord const& b, + std::uint32_t c) override { + Cormorant(o, b, c); + return Status{}; + } + HashValues Finish() override { return {}; } + + private: + template + void Cormorant(Args const&...) {} +}; + TEST(RestStubTest, ResolveStorageAuthorityProdEndpoint) { auto options = - Options{}.set("https://storage.googleapis.com"); + Options{}.set("https://storage.googleapis.com") + .set("storage.googleapis.com"); auto result_options = RestStub::ResolveStorageAuthority(options); EXPECT_THAT(result_options.get(), Eq("storage.googleapis.com")); @@ -55,7 +82,8 @@ TEST(RestStubTest, ResolveStorageAuthorityProdEndpoint) { TEST(RestStubTest, ResolveStorageAuthorityEapEndpoint) { auto options = - Options{}.set("https://eap.googleapis.com"); + Options{}.set("https://eap.googleapis.com") + .set("storage.googleapis.com"); auto result_options = RestStub::ResolveStorageAuthority(options); EXPECT_THAT(result_options.get(), Eq("storage.googleapis.com")); @@ -78,13 +106,15 @@ TEST(RestStubTest, ResolveStorageAuthorityOptionSpecified) { TEST(RestStubTest, ResolveIamAuthorityProdEndpoint) { auto options = Options{}.set("https://iamcredentials.googleapis.com"); - auto result_options = RestStub::ResolveIamAuthority(options); + auto result_options = RestStub::ResolveIamAuthority(options) + .set("iamcredentials.googleapis.com"); EXPECT_THAT(result_options.get(), Eq("iamcredentials.googleapis.com")); } TEST(RestStubTest, ResolveIamAuthorityEapEndpoint) { - auto options = Options{}.set("https://eap.googleapis.com"); + auto options = Options{}.set("https://eap.googleapis.com") + .set("iamcredentials.googleapis.com"); auto result_options = RestStub::ResolveIamAuthority(options); EXPECT_THAT(result_options.get(), Eq("iamcredentials.googleapis.com")); @@ -921,6 +951,90 @@ TEST(RestStubTest, DeleteNotification) { StatusIs(PermanentError().code(), PermanentError().message())); } +TEST(RestStubTest, UploadChunkLastChunkWithCrc32c) { + auto mock = std::make_shared(); + EXPECT_CALL( + *mock, + Put(ExpectedContext(), + ResultOf( + "request headers contain x-goog-hash with crc32c", + [](RestRequest const& r) { return r.headers(); }, + Contains(Pair("x-goog-hash", ElementsAre("crc32c=test-crc32")))), + ExpectedPayload())) + .WillOnce(Return(PermanentError())); + auto tested = std::make_unique(Options{}, mock, mock); + auto context = TestContext(); + auto status = tested->UploadChunk( + context, TestOptions(), + UploadChunkRequest("test-url", 0, {}, + std::make_shared(), + {"test-crc32c", ""})); + EXPECT_THAT(status, + StatusIs(PermanentError().code(), PermanentError().message())); +} + +TEST(RestStubTest, UploadChunkLastChunkWithMd5) { + auto mock = std::make_shared(); + EXPECT_CALL(*mock, + Put(ExpectedContext(), + ResultOf("request headers contain x-goog-hash with md5", + [](RestRequest const& r) { return r.headers(); }, + Contains(Pair("x-goog-hash", + ElementsAre("md5=test-md5")))), + ExpectedPayload())) + .WillOnce(Return(PermanentError())); + auto tested = std::make_unique(Options{}, mock, mock); + auto context = TestContext(); + auto status = tested->UploadChunk( + context, TestOptions(), + UploadChunkRequest("test-url", 0, {}, + std::make_shared(), + {"", "test-md5"})); + EXPECT_THAT(status, + StatusIs(PermanentError().code(), PermanentError().message())); +} + +TEST(RestStubTest, UploadChunkLastChunkWithBoth) { + auto mock = std::make_shared(); + EXPECT_CALL( + *mock, + Put(ExpectedContext(), + ResultOf( + "request headers contain x-goog-hash with crc32c and md5", + [](RestRequest const& r) { return r.headers(); }, + Contains(Pair("x-goog-hash", ElementsAre("crc32c=test-crc32c", + "md5=test-md5")))), + ExpectedPayload())) + .WillOnce(Return(PermanentError())); + auto tested = std::make_unique(Options{}, mock, mock); + auto context = TestContext(); + auto status = tested->UploadChunk( + context, TestOptions(), + UploadChunkRequest("test-url", 0, {}, + std::make_shared(), + {"test-crc32c", "test-md5"})); + EXPECT_THAT(status, + StatusIs(PermanentError().code(), PermanentError().message())); +} + +TEST(RestStubTest, UploadChunkIntermediate) { + auto mock = std::make_shared(); + EXPECT_CALL(*mock, Put(ExpectedContext(), + ResultOf("request headers do not contain x-goog-hash", + [](RestRequest const& r) { return r.headers(); }, + Not(Contains(Pair("x-goog-hash", _)))), + ExpectedPayload())) + .WillOnce(Return(PermanentError())); + auto tested = std::make_unique(Options{}, mock, mock); + auto context = TestContext(); + auto status = tested->UploadChunk( + context, TestOptions(), + UploadChunkRequest("test-url", 0, {}, + std::make_shared())); + EXPECT_THAT(status, + StatusIs(PermanentError().code(), PermanentError().message())); +} + } // namespace } // namespace internal GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END From abaa917b52802fda3c5ca0354cd77335a5978bf7 Mon Sep 17 00:00:00 2001 From: v-pratap Date: Thu, 4 Dec 2025 05:57:50 +0000 Subject: [PATCH 03/13] remove unnecesary changes --- .bazelignore | 1 - .../cloud/storage/internal/rest/stub_test.cc | 12 ++---- google/cloud/storage/quickstart/quickstart.cc | 38 ++++++++----------- 3 files changed, 20 insertions(+), 31 deletions(-) diff --git a/.bazelignore b/.bazelignore index c4f4ebf8126e7..98112232070b2 100644 --- a/.bazelignore +++ b/.bazelignore @@ -5,4 +5,3 @@ cmake-build-debug/ cmake-build-coverage/ cmake-build-release/ .build/ -_build/ diff --git a/google/cloud/storage/internal/rest/stub_test.cc b/google/cloud/storage/internal/rest/stub_test.cc index 2eee218c788c1..53328adf9144c 100644 --- a/google/cloud/storage/internal/rest/stub_test.cc +++ b/google/cloud/storage/internal/rest/stub_test.cc @@ -73,8 +73,7 @@ class NoOpHashFunction : public HashFunction { TEST(RestStubTest, ResolveStorageAuthorityProdEndpoint) { auto options = - Options{}.set("https://storage.googleapis.com") - .set("storage.googleapis.com"); + Options{}.set("https://storage.googleapis.com"); auto result_options = RestStub::ResolveStorageAuthority(options); EXPECT_THAT(result_options.get(), Eq("storage.googleapis.com")); @@ -82,8 +81,7 @@ TEST(RestStubTest, ResolveStorageAuthorityProdEndpoint) { TEST(RestStubTest, ResolveStorageAuthorityEapEndpoint) { auto options = - Options{}.set("https://eap.googleapis.com") - .set("storage.googleapis.com"); + Options{}.set("https://eap.googleapis.com"); auto result_options = RestStub::ResolveStorageAuthority(options); EXPECT_THAT(result_options.get(), Eq("storage.googleapis.com")); @@ -106,15 +104,13 @@ TEST(RestStubTest, ResolveStorageAuthorityOptionSpecified) { TEST(RestStubTest, ResolveIamAuthorityProdEndpoint) { auto options = Options{}.set("https://iamcredentials.googleapis.com"); - auto result_options = RestStub::ResolveIamAuthority(options) - .set("iamcredentials.googleapis.com"); + auto result_options = RestStub::ResolveIamAuthority(options); EXPECT_THAT(result_options.get(), Eq("iamcredentials.googleapis.com")); } TEST(RestStubTest, ResolveIamAuthorityEapEndpoint) { - auto options = Options{}.set("https://eap.googleapis.com") - .set("iamcredentials.googleapis.com"); + auto options = Options{}.set("https://eap.googleapis.com"); auto result_options = RestStub::ResolveIamAuthority(options); EXPECT_THAT(result_options.get(), Eq("iamcredentials.googleapis.com")); diff --git a/google/cloud/storage/quickstart/quickstart.cc b/google/cloud/storage/quickstart/quickstart.cc index 0edf184a540db..b8cea7468d2ed 100644 --- a/google/cloud/storage/quickstart/quickstart.cc +++ b/google/cloud/storage/quickstart/quickstart.cc @@ -20,12 +20,12 @@ #include int main(int argc, char* argv[]) { - // if (argc != 2) { - // std::cerr << "Missing bucket name.\n"; - // std::cerr << "Usage: quickstart \n"; - // return 1; - // } - std::string const bucket_name = "vaibhav-test-001"; + if (argc != 2) { + std::cerr << "Missing bucket name.\n"; + std::cerr << "Usage: quickstart \n"; + return 1; + } + std::string const bucket_name = argv[1]; // Create a client to communicate with Google Cloud Storage. This client // uses the default configuration for authentication and project id. @@ -41,14 +41,8 @@ int main(int argc, char* argv[]) { auto client = google::cloud::storage::Client(options); - auto writer = client.WriteObject(bucket_name, "quickstart1.txt"); - std::string x ="Hello World1!"; - for(int i=0;i<17;i++){ - x+=x; - } - for(int i=0;i<15;i++){ - writer << x.data(); - } + auto writer = client.WriteObject(bucket_name, "quickstart.txt"); + writer << "Hello World!"; writer.Close(); if (!writer.metadata()) { std::cerr << "Error creating object: " << writer.metadata().status() @@ -57,15 +51,15 @@ int main(int argc, char* argv[]) { } std::cout << "Successfully created object: " << *writer.metadata() << "\n"; - // auto reader = client.ReadObject(bucket_name, "quickstart.txt"); - // if (!reader) { - // std::cerr << "Error reading object: " << reader.status() << "\n"; - // return 1; - // } + auto reader = client.ReadObject(bucket_name, "quickstart.txt"); + if (!reader) { + std::cerr << "Error reading object: " << reader.status() << "\n"; + return 1; + } - // std::string contents{std::istreambuf_iterator{reader}, {}}; - // std::cout << contents << "\n"; + std::string contents{std::istreambuf_iterator{reader}, {}}; + std::cout << contents << "\n"; return 0; } -//! [all] +//! [all] \ No newline at end of file From dfe00394a9310dd0bc752b5cdf9f80f6649de22a Mon Sep 17 00:00:00 2001 From: v-pratap Date: Thu, 4 Dec 2025 06:00:37 +0000 Subject: [PATCH 04/13] remove unnecessary change --- google/cloud/storage/quickstart/quickstart.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/cloud/storage/quickstart/quickstart.cc b/google/cloud/storage/quickstart/quickstart.cc index b8cea7468d2ed..0faff90d343f3 100644 --- a/google/cloud/storage/quickstart/quickstart.cc +++ b/google/cloud/storage/quickstart/quickstart.cc @@ -62,4 +62,4 @@ int main(int argc, char* argv[]) { return 0; } -//! [all] \ No newline at end of file +//! [all] From c50093d462be4ca113ec3b978378b5a0dce2c6fe Mon Sep 17 00:00:00 2001 From: v-pratap Date: Thu, 4 Dec 2025 06:03:08 +0000 Subject: [PATCH 05/13] remove debug statement --- google/cloud/storage/internal/object_write_streambuf.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/google/cloud/storage/internal/object_write_streambuf.cc b/google/cloud/storage/internal/object_write_streambuf.cc index b995ca7bc8bb9..74709a860bb1d 100644 --- a/google/cloud/storage/internal/object_write_streambuf.cc +++ b/google/cloud/storage/internal/object_write_streambuf.cc @@ -207,9 +207,6 @@ void ObjectWriteStreambuf::FlushRoundChunk(ConstBufferSequence buffers) { auto upload_request = UploadChunkRequest(upload_id_, committed_size_, payload, hash_function_); request_.ForEachOption(internal::CopyCommonOptions(upload_request)); - upload_request.ForEachOption([](auto const& opt) { - std::cout << "DEBUG: option=" << opt << "\n"; - }); OptionsSpan const span(span_options_); auto response = connection_->UploadChunk(upload_request); if (!response) { From d253afdf266d93bcb5569fa17d67969ba49c6c6a Mon Sep 17 00:00:00 2001 From: v-pratap Date: Tue, 16 Dec 2025 13:52:49 +0000 Subject: [PATCH 06/13] added integration test --- .../tests/object_checksum_integration_test.cc | 65 +++++++++++++++---- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/google/cloud/storage/tests/object_checksum_integration_test.cc b/google/cloud/storage/tests/object_checksum_integration_test.cc index c5f88f529f862..ffd4911df44bf 100644 --- a/google/cloud/storage/tests/object_checksum_integration_test.cc +++ b/google/cloud/storage/tests/object_checksum_integration_test.cc @@ -145,14 +145,8 @@ TEST_F(ObjectChecksumIntegrationTest, WriteObjectDefault) { EXPECT_THAT(os.computed_hash(), HasSubstr(ComputeCrc32cChecksum(LoremIpsum()))); if (meta->has_metadata("x_emulator_upload")) { - if (UsingGrpc()) { - EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_crc32c", _))); - EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_no_md5", _))); - } else { - // Streaming uploads over REST cannot include checksums - EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_no_crc32c", _))); - EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_no_md5", _))); - } + EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_crc32c", _))); + EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_no_md5", _))); } } @@ -200,14 +194,8 @@ TEST_F(ObjectChecksumIntegrationTest, WriteObjectExplicitEnable) { EXPECT_THAT(os.computed_hash(), HasSubstr(ComputeCrc32cChecksum(LoremIpsum()))); if (meta->has_metadata("x_emulator_upload")) { - if (UsingGrpc()) { EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_crc32c", _))); EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_no_md5", _))); - } else { - // Streaming uploads over REST cannot include checksums - EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_no_crc32c", _))); - EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_no_md5", _))); - } } } @@ -289,6 +277,54 @@ TEST_F(ObjectChecksumIntegrationTest, WriteObjectUploadBadChecksum) { ASSERT_THAT(stream.metadata(), Not(IsOk())); } +/// @test Verify that full object checksums are sent in the final chunk. +TEST_F(ObjectChecksumIntegrationTest, WriteObjectWithFullChecksumValidation) { + auto client = MakeIntegrationTestClient(); + auto object_name = MakeRandomObjectName(); + auto content = LoremIpsum(); + auto expected_crc32c = ComputeCrc32cChecksum(content); + + auto os = client.WriteObject(bucket_name_, object_name, + DisableCrc32cChecksum(false), + DisableMD5Hash(true), + IfGenerationMatch(0)); + os << content; + os.Close(); + auto meta = os.metadata(); + ASSERT_STATUS_OK(meta); + ScheduleForDelete(*meta); + + EXPECT_EQ(os.computed_hash(), expected_crc32c); + + if (meta->has_metadata("x_emulator_upload")) { + EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_crc32c", expected_crc32c))); + EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_no_md5", "true"))); + } +} + +/// @test Verify that the upload fails when the provided CRC32C checksum does not match the data. +TEST_F(ObjectChecksumIntegrationTest, WriteObjectWithIncorrectChecksumValue) { + auto client = MakeIntegrationTestClient(); + auto object_name = MakeRandomObjectName(); + auto content = LoremIpsum(); + + auto bad_crc32c = ComputeCrc32cChecksum("this is not the data being uploaded"); + + auto os = client.WriteObject(bucket_name_, object_name, + Crc32cChecksumValue(bad_crc32c), + DisableMD5Hash(true), + IfGenerationMatch(0)); + + os << content; + os.Close(); + EXPECT_TRUE(os.bad()); + auto meta = os.metadata(); + EXPECT_THAT(meta, Not(IsOk())); + + // The server returns FAILED_PRECONDITION (412) for checksum mismatches during finalization + EXPECT_THAT(meta, StatusIs(StatusCode::kFailedPrecondition)); +} + /// @test Verify that CRC32C checksums are computed by default on downloads. TEST_F(ObjectChecksumIntegrationTest, ReadObjectDefault) { // TODO(#14385) - the emulator does not support this feature for gRPC. @@ -385,3 +421,4 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace storage } // namespace cloud } // namespace google + \ No newline at end of file From 7ef96f3fd21ada5c16937ca175511ec2a3352573 Mon Sep 17 00:00:00 2001 From: v-pratap Date: Fri, 19 Dec 2025 07:04:36 +0000 Subject: [PATCH 07/13] fix the test --- google/cloud/storage/internal/rest/stub_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/cloud/storage/internal/rest/stub_test.cc b/google/cloud/storage/internal/rest/stub_test.cc index 53328adf9144c..a8b1d81b4ccfe 100644 --- a/google/cloud/storage/internal/rest/stub_test.cc +++ b/google/cloud/storage/internal/rest/stub_test.cc @@ -955,7 +955,7 @@ TEST(RestStubTest, UploadChunkLastChunkWithCrc32c) { ResultOf( "request headers contain x-goog-hash with crc32c", [](RestRequest const& r) { return r.headers(); }, - Contains(Pair("x-goog-hash", ElementsAre("crc32c=test-crc32")))), + Contains(Pair("x-goog-hash", ElementsAre("crc32c=test-crc32c")))), ExpectedPayload())) .WillOnce(Return(PermanentError())); auto tested = std::make_unique(Options{}, mock, mock); From b5b494bd2fca8ff6dd6fab637d22bb969d549e71 Mon Sep 17 00:00:00 2001 From: v-pratap Date: Fri, 19 Dec 2025 07:13:13 +0000 Subject: [PATCH 08/13] fix the format --- .../internal/object_write_streambuf.cc | 14 ++++---- google/cloud/storage/internal/rest/stub.cc | 34 +++++++++---------- .../cloud/storage/internal/rest/stub_test.cc | 30 ++++++++-------- .../tests/object_checksum_integration_test.cc | 29 ++++++++-------- 4 files changed, 54 insertions(+), 53 deletions(-) diff --git a/google/cloud/storage/internal/object_write_streambuf.cc b/google/cloud/storage/internal/object_write_streambuf.cc index 74709a860bb1d..3d4238fe37f77 100644 --- a/google/cloud/storage/internal/object_write_streambuf.cc +++ b/google/cloud/storage/internal/object_write_streambuf.cc @@ -144,12 +144,12 @@ void ObjectWriteStreambuf::FlushFinal() { // Calculate the portion of the buffer that needs to be uploaded, if any. auto const actual_size = put_area_size(); - HashValues final_hashes = known_hashes_; - if (hash_function_) { - hash_function_->Update(committed_size_, {pbase(), actual_size}); - final_hashes = hash_function_->Finish(); - hash_function_.reset(); - } + HashValues final_hashes = known_hashes_; + if (hash_function_) { + hash_function_->Update(committed_size_, {pbase(), actual_size}); + final_hashes = hash_function_->Finish(); + hash_function_.reset(); + } // After this point the session will be closed, and no more calls to the hash // function are possible. @@ -163,7 +163,7 @@ void ObjectWriteStreambuf::FlushFinal() { last_status_ = std::move(response).status(); return; } - hash_values_ = final_hashes; + hash_values_ = final_hashes; committed_size_ = response->committed_size.value_or(0); metadata_ = std::move(response->payload); diff --git a/google/cloud/storage/internal/rest/stub.cc b/google/cloud/storage/internal/rest/stub.cc index bd0a4796d9c4e..4a1e338f64e7c 100644 --- a/google/cloud/storage/internal/rest/stub.cc +++ b/google/cloud/storage/internal/rest/stub.cc @@ -770,23 +770,23 @@ StatusOr RestStub::UploadChunk( // default (at least in this case), and that wastes bandwidth as the content // length is known. builder.AddHeader("Transfer-Encoding", {}); - auto hash_function = request.hash_function_ptr(); - if (hash_function) { - auto offset = request.offset(); - for (auto const& b : request.payload()) { - hash_function->Update(offset, absl::string_view{b.data(), b.size()}); - offset += b.size(); - } - } - if (request.last_chunk()) { - auto const& hashes = request.known_object_hashes(); - if (!hashes.crc32c.empty()) { - builder.AddHeader("x-goog-hash", "crc32c=" + hashes.crc32c); - } - if (!hashes.md5.empty()) { - builder.AddHeader("x-goog-hash", "md5=" + hashes.md5); - } - } + auto hash_function = request.hash_function_ptr(); + if (hash_function) { + auto offset = request.offset(); + for (auto const& b : request.payload()) { + hash_function->Update(offset, absl::string_view{b.data(), b.size()}); + offset += b.size(); + } + } + if (request.last_chunk()) { + auto const& hashes = request.known_object_hashes(); + if (!hashes.crc32c.empty()) { + builder.AddHeader("x-goog-hash", "crc32c=" + hashes.crc32c); + } + if (!hashes.md5.empty()) { + builder.AddHeader("x-goog-hash", "md5=" + hashes.md5); + } + } auto failure_predicate = [](rest::HttpStatusCode code) { return (code != rest::HttpStatusCode::kResumeIncomplete && diff --git a/google/cloud/storage/internal/rest/stub_test.cc b/google/cloud/storage/internal/rest/stub_test.cc index a8b1d81b4ccfe..05202648577d2 100644 --- a/google/cloud/storage/internal/rest/stub_test.cc +++ b/google/cloud/storage/internal/rest/stub_test.cc @@ -54,13 +54,11 @@ class NoOpHashFunction : public HashFunction { Cormorant(o, b); return Status{}; } - Status Update(std::int64_t o, absl::string_view b, - std::uint32_t c) override { + Status Update(std::int64_t o, absl::string_view b, std::uint32_t c) override { Cormorant(o, b, c); return Status{}; } - Status Update(std::int64_t o, absl::Cord const& b, - std::uint32_t c) override { + Status Update(std::int64_t o, absl::Cord const& b, std::uint32_t c) override { Cormorant(o, b, c); return Status{}; } @@ -971,13 +969,14 @@ TEST(RestStubTest, UploadChunkLastChunkWithCrc32c) { TEST(RestStubTest, UploadChunkLastChunkWithMd5) { auto mock = std::make_shared(); - EXPECT_CALL(*mock, - Put(ExpectedContext(), - ResultOf("request headers contain x-goog-hash with md5", - [](RestRequest const& r) { return r.headers(); }, - Contains(Pair("x-goog-hash", - ElementsAre("md5=test-md5")))), - ExpectedPayload())) + EXPECT_CALL( + *mock, + Put(ExpectedContext(), + ResultOf( + "request headers contain x-goog-hash with md5", + [](RestRequest const& r) { return r.headers(); }, + Contains(Pair("x-goog-hash", ElementsAre("md5=test-md5")))), + ExpectedPayload())) .WillOnce(Return(PermanentError())); auto tested = std::make_unique(Options{}, mock, mock); auto context = TestContext(); @@ -999,7 +998,7 @@ TEST(RestStubTest, UploadChunkLastChunkWithBoth) { "request headers contain x-goog-hash with crc32c and md5", [](RestRequest const& r) { return r.headers(); }, Contains(Pair("x-goog-hash", ElementsAre("crc32c=test-crc32c", - "md5=test-md5")))), + "md5=test-md5")))), ExpectedPayload())) .WillOnce(Return(PermanentError())); auto tested = std::make_unique(Options{}, mock, mock); @@ -1016,9 +1015,10 @@ TEST(RestStubTest, UploadChunkLastChunkWithBoth) { TEST(RestStubTest, UploadChunkIntermediate) { auto mock = std::make_shared(); EXPECT_CALL(*mock, Put(ExpectedContext(), - ResultOf("request headers do not contain x-goog-hash", - [](RestRequest const& r) { return r.headers(); }, - Not(Contains(Pair("x-goog-hash", _)))), + ResultOf( + "request headers do not contain x-goog-hash", + [](RestRequest const& r) { return r.headers(); }, + Not(Contains(Pair("x-goog-hash", _)))), ExpectedPayload())) .WillOnce(Return(PermanentError())); auto tested = std::make_unique(Options{}, mock, mock); diff --git a/google/cloud/storage/tests/object_checksum_integration_test.cc b/google/cloud/storage/tests/object_checksum_integration_test.cc index ffd4911df44bf..0c7c3e9b3548f 100644 --- a/google/cloud/storage/tests/object_checksum_integration_test.cc +++ b/google/cloud/storage/tests/object_checksum_integration_test.cc @@ -194,8 +194,8 @@ TEST_F(ObjectChecksumIntegrationTest, WriteObjectExplicitEnable) { EXPECT_THAT(os.computed_hash(), HasSubstr(ComputeCrc32cChecksum(LoremIpsum()))); if (meta->has_metadata("x_emulator_upload")) { - EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_crc32c", _))); - EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_no_md5", _))); + EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_crc32c", _))); + EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_no_md5", _))); } } @@ -286,8 +286,7 @@ TEST_F(ObjectChecksumIntegrationTest, WriteObjectWithFullChecksumValidation) { auto os = client.WriteObject(bucket_name_, object_name, DisableCrc32cChecksum(false), - DisableMD5Hash(true), - IfGenerationMatch(0)); + DisableMD5Hash(true), IfGenerationMatch(0)); os << content; os.Close(); auto meta = os.metadata(); @@ -295,33 +294,36 @@ TEST_F(ObjectChecksumIntegrationTest, WriteObjectWithFullChecksumValidation) { ScheduleForDelete(*meta); EXPECT_EQ(os.computed_hash(), expected_crc32c); - + if (meta->has_metadata("x_emulator_upload")) { - EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_crc32c", expected_crc32c))); + EXPECT_THAT(meta->metadata(), + Contains(Pair("x_emulator_crc32c", expected_crc32c))); EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_no_md5", "true"))); } } -/// @test Verify that the upload fails when the provided CRC32C checksum does not match the data. +/// @test Verify that the upload fails when the provided CRC32C checksum does +/// not match the data. TEST_F(ObjectChecksumIntegrationTest, WriteObjectWithIncorrectChecksumValue) { auto client = MakeIntegrationTestClient(); auto object_name = MakeRandomObjectName(); auto content = LoremIpsum(); - auto bad_crc32c = ComputeCrc32cChecksum("this is not the data being uploaded"); + auto bad_crc32c = + ComputeCrc32cChecksum("this is not the data being uploaded"); auto os = client.WriteObject(bucket_name_, object_name, Crc32cChecksumValue(bad_crc32c), - DisableMD5Hash(true), - IfGenerationMatch(0)); - + DisableMD5Hash(true), IfGenerationMatch(0)); + os << content; os.Close(); EXPECT_TRUE(os.bad()); auto meta = os.metadata(); EXPECT_THAT(meta, Not(IsOk())); - - // The server returns FAILED_PRECONDITION (412) for checksum mismatches during finalization + + // The server returns FAILED_PRECONDITION (412) for checksum mismatches during + // finalization EXPECT_THAT(meta, StatusIs(StatusCode::kFailedPrecondition)); } @@ -421,4 +423,3 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace storage } // namespace cloud } // namespace google - \ No newline at end of file From 0a2f3c041a730a23efa3cc7a5ade55072cac0206 Mon Sep 17 00:00:00 2001 From: v-pratap Date: Mon, 22 Dec 2025 07:18:44 +0000 Subject: [PATCH 09/13] update the testbench version --- ci/cloudbuild/builds/lib/integration.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/cloudbuild/builds/lib/integration.sh b/ci/cloudbuild/builds/lib/integration.sh index d6fba90073441..3506c661c860c 100644 --- a/ci/cloudbuild/builds/lib/integration.sh +++ b/ci/cloudbuild/builds/lib/integration.sh @@ -31,7 +31,7 @@ source module ci/lib/io.sh export PATH="${HOME}/.local/bin:${PATH}" python3 -m pip uninstall -y --quiet googleapis-storage-testbench python3 -m pip install --upgrade --user --quiet --disable-pip-version-check \ - "git+https://github.com/googleapis/storage-testbench@v0.59.0" + "git+https://github.com/googleapis/storage-testbench@v0.60.0" # Some of the tests will need a valid roots.pem file. rm -f /dev/shm/roots.pem From 1c02ea379ec4b6207fef9ebb9795e95191b87a07 Mon Sep 17 00:00:00 2001 From: v-pratap Date: Mon, 22 Dec 2025 09:05:55 +0000 Subject: [PATCH 10/13] update the grpc path for checksum --- .../internal/grpc/object_request_parser.cc | 16 ++++++++++++++-- .../internal/grpc/object_request_parser.h | 4 ++++ google/cloud/storage/internal/grpc/stub.cc | 4 +++- google/cloud/storage/internal/object_requests.cc | 5 ++++- .../tests/object_checksum_integration_test.cc | 15 +++++++++++++-- 5 files changed, 38 insertions(+), 6 deletions(-) diff --git a/google/cloud/storage/internal/grpc/object_request_parser.cc b/google/cloud/storage/internal/grpc/object_request_parser.cc index 1ff0bc6ed1279..40ac5ce2e813f 100644 --- a/google/cloud/storage/internal/grpc/object_request_parser.cc +++ b/google/cloud/storage/internal/grpc/object_request_parser.cc @@ -851,6 +851,15 @@ Status Finalize(google::storage::v2::WriteObjectRequest& write_request, Merge(std::move(hashes), hash_function.Finish())); } +Status Finalize(google::storage::v2::WriteObjectRequest& write_request, + grpc::WriteOptions& options, + storage::internal::HashValues hashes) { + write_request.set_finish_write(true); + options.set_last_message(); + return FinalizeChecksums(*write_request.mutable_object_checksums(), + std::move(hashes)); +} + Status Finalize(google::storage::v2::BidiWriteObjectRequest& write_request, grpc::WriteOptions& options, storage::internal::HashFunction& hash_function, @@ -879,8 +888,11 @@ Status MaybeFinalize(google::storage::v2::WriteObjectRequest& write_request, bool chunk_has_more) { if (!chunk_has_more) options.set_last_message(); if (!request.last_chunk() || chunk_has_more) return {}; - return Finalize(write_request, options, request.hash_function(), - request.known_object_hashes()); + if (request.hash_function_ptr()) { + return Finalize(write_request, options, request.hash_function(), + request.known_object_hashes()); + } + return Finalize(write_request, options, request.known_object_hashes()); } GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END diff --git a/google/cloud/storage/internal/grpc/object_request_parser.h b/google/cloud/storage/internal/grpc/object_request_parser.h index 6e90bcb942e45..b1cdee22fcc14 100644 --- a/google/cloud/storage/internal/grpc/object_request_parser.h +++ b/google/cloud/storage/internal/grpc/object_request_parser.h @@ -87,6 +87,10 @@ Status Finalize(google::storage::v2::WriteObjectRequest& write_request, storage::internal::HashFunction& hash_function, storage::internal::HashValues = {}); +Status Finalize(google::storage::v2::WriteObjectRequest& write_request, + grpc::WriteOptions& options, + storage::internal::HashValues hashes); + Status Finalize(google::storage::v2::BidiWriteObjectRequest& write_request, grpc::WriteOptions& options, storage::internal::HashFunction& hash_function, diff --git a/google/cloud/storage/internal/grpc/stub.cc b/google/cloud/storage/internal/grpc/stub.cc index 9a7120c6ebae8..733b42bf4f94d 100644 --- a/google/cloud/storage/internal/grpc/stub.cc +++ b/google/cloud/storage/internal/grpc/stub.cc @@ -630,7 +630,9 @@ StatusOr GrpcStub::UploadChunk( auto& data = *proto_request.mutable_checksummed_data(); SetMutableContent(data, splitter.Next()); data.set_crc32c(Crc32c(GetContent(data))); - request.hash_function().Update(offset, GetContent(data), data.crc32c()); + if (request.hash_function_ptr()) { + request.hash_function().Update(offset, GetContent(data), data.crc32c()); + } offset += GetContent(data).size(); auto wopts = grpc::WriteOptions(); diff --git a/google/cloud/storage/internal/object_requests.cc b/google/cloud/storage/internal/object_requests.cc index 1a61dc38d19af..37857aff7bfdc 100644 --- a/google/cloud/storage/internal/object_requests.cc +++ b/google/cloud/storage/internal/object_requests.cc @@ -533,7 +533,10 @@ UploadChunkRequest UploadChunkRequest::RemainingChunk( HashValues FinishHashes(UploadChunkRequest const& request) { // Prefer the hashes provided via *Value options in the request. If those // are not set, use the computed hashes from the data. - return Merge(request.known_object_hashes(), request.hash_function().Finish()); + if (auto hf = request.hash_function_ptr()) { + return Merge(request.known_object_hashes(), hf->Finish()); + } + return request.known_object_hashes(); } std::ostream& operator<<(std::ostream& os, UploadChunkRequest const& r) { diff --git a/google/cloud/storage/tests/object_checksum_integration_test.cc b/google/cloud/storage/tests/object_checksum_integration_test.cc index 0c7c3e9b3548f..a61b87ed44525 100644 --- a/google/cloud/storage/tests/object_checksum_integration_test.cc +++ b/google/cloud/storage/tests/object_checksum_integration_test.cc @@ -131,6 +131,15 @@ TEST_F(ObjectChecksumIntegrationTest, WriteObjectDefault) { auto client = MakeIntegrationTestClient(); auto object_name = MakeRandomObjectName(); + auto create = client.CreateBucket( + bucket_name_, storage::BucketMetadata{}.set_location("us-west4") + .set_storage_class("STANDARD")); + if (!create && create.status().code() != StatusCode::kAlreadyExists) { + GTEST_FAIL() << "cannot create bucket: " << create.status(); + } else { + std::cout << "Bucket successfully created." << std::endl; + } + auto os = client.WriteObject(bucket_name_, object_name, DisableMD5Hash(true), IfGenerationMatch(0)); os << LoremIpsum(); @@ -322,8 +331,10 @@ TEST_F(ObjectChecksumIntegrationTest, WriteObjectWithIncorrectChecksumValue) { auto meta = os.metadata(); EXPECT_THAT(meta, Not(IsOk())); - // The server returns FAILED_PRECONDITION (412) for checksum mismatches during - // finalization + if(UsingGrpc()) { + EXPECT_THAT(meta, StatusIs(StatusCode::kInvalidArgument)); + return; + } EXPECT_THAT(meta, StatusIs(StatusCode::kFailedPrecondition)); } From 9d6152ef31d0b57a7ae7216faf522624532ef47c Mon Sep 17 00:00:00 2001 From: v-pratap Date: Mon, 22 Dec 2025 09:26:44 +0000 Subject: [PATCH 11/13] correct the test --- .../storage/tests/object_checksum_integration_test.cc | 6 ------ .../storage/tests/object_hash_integration_test.cc | 10 ++-------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/google/cloud/storage/tests/object_checksum_integration_test.cc b/google/cloud/storage/tests/object_checksum_integration_test.cc index a61b87ed44525..f7927c104a0fb 100644 --- a/google/cloud/storage/tests/object_checksum_integration_test.cc +++ b/google/cloud/storage/tests/object_checksum_integration_test.cc @@ -330,12 +330,6 @@ TEST_F(ObjectChecksumIntegrationTest, WriteObjectWithIncorrectChecksumValue) { EXPECT_TRUE(os.bad()); auto meta = os.metadata(); EXPECT_THAT(meta, Not(IsOk())); - - if(UsingGrpc()) { - EXPECT_THAT(meta, StatusIs(StatusCode::kInvalidArgument)); - return; - } - EXPECT_THAT(meta, StatusIs(StatusCode::kFailedPrecondition)); } /// @test Verify that CRC32C checksums are computed by default on downloads. diff --git a/google/cloud/storage/tests/object_hash_integration_test.cc b/google/cloud/storage/tests/object_hash_integration_test.cc index d8c9325a78121..d4d6a1f060a31 100644 --- a/google/cloud/storage/tests/object_hash_integration_test.cc +++ b/google/cloud/storage/tests/object_hash_integration_test.cc @@ -192,14 +192,8 @@ TEST_F(ObjectHashIntegrationTest, WriteObjectExplicitEnable) { EXPECT_THAT(os.computed_hash(), HasSubstr(ComputeMD5Hash(LoremIpsum()))); EXPECT_THAT(os.received_hash(), HasSubstr(ComputeMD5Hash(LoremIpsum()))); if (meta->has_metadata("x_emulator_upload")) { - if (UsingGrpc()) { - EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_no_crc32c", _))); - EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_md5", _))); - } else { - // REST cannot send the checksums computed at the end of the upload. - EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_no_crc32c", _))); - EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_no_md5", _))); - } + EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_no_crc32c", _))); + EXPECT_THAT(meta->metadata(), Contains(Pair("x_emulator_md5", _))); } } From d67a25729a0ef51b09fd095f5ec36769eae74555 Mon Sep 17 00:00:00 2001 From: v-pratap Date: Mon, 22 Dec 2025 09:45:37 +0000 Subject: [PATCH 12/13] remove the test for grpc as it is not present for backend --- google/cloud/storage/tests/object_checksum_integration_test.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/google/cloud/storage/tests/object_checksum_integration_test.cc b/google/cloud/storage/tests/object_checksum_integration_test.cc index f7927c104a0fb..ea972738302cd 100644 --- a/google/cloud/storage/tests/object_checksum_integration_test.cc +++ b/google/cloud/storage/tests/object_checksum_integration_test.cc @@ -314,6 +314,8 @@ TEST_F(ObjectChecksumIntegrationTest, WriteObjectWithFullChecksumValidation) { /// @test Verify that the upload fails when the provided CRC32C checksum does /// not match the data. TEST_F(ObjectChecksumIntegrationTest, WriteObjectWithIncorrectChecksumValue) { + // TODO(#14385) - the emulator does not support this feature for gRPC. + if (UsingEmulator() && UsingGrpc()) GTEST_SKIP(); auto client = MakeIntegrationTestClient(); auto object_name = MakeRandomObjectName(); auto content = LoremIpsum(); From e23371588563b0e13428dbd427b14064bd5ac5e2 Mon Sep 17 00:00:00 2001 From: v-pratap Date: Mon, 22 Dec 2025 09:54:13 +0000 Subject: [PATCH 13/13] remove the bucket creation --- .../storage/tests/object_checksum_integration_test.cc | 9 --------- 1 file changed, 9 deletions(-) diff --git a/google/cloud/storage/tests/object_checksum_integration_test.cc b/google/cloud/storage/tests/object_checksum_integration_test.cc index ea972738302cd..2247a3c26871b 100644 --- a/google/cloud/storage/tests/object_checksum_integration_test.cc +++ b/google/cloud/storage/tests/object_checksum_integration_test.cc @@ -131,15 +131,6 @@ TEST_F(ObjectChecksumIntegrationTest, WriteObjectDefault) { auto client = MakeIntegrationTestClient(); auto object_name = MakeRandomObjectName(); - auto create = client.CreateBucket( - bucket_name_, storage::BucketMetadata{}.set_location("us-west4") - .set_storage_class("STANDARD")); - if (!create && create.status().code() != StatusCode::kAlreadyExists) { - GTEST_FAIL() << "cannot create bucket: " << create.status(); - } else { - std::cout << "Bucket successfully created." << std::endl; - } - auto os = client.WriteObject(bucket_name_, object_name, DisableMD5Hash(true), IfGenerationMatch(0)); os << LoremIpsum();