Skip to content

Commit 0d8ac58

Browse files
authored
test(GCS+gRPC): missing unit tests for AsyncClient (#13421)
In the process I found a function that was undefined, probably for years.
1 parent cd5a575 commit 0d8ac58

3 files changed

Lines changed: 279 additions & 0 deletions

File tree

google/cloud/storage/async/client_test.cc

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include "google/cloud/storage/async/client.h"
1616
#include "google/cloud/storage/mocks/mock_async_connection.h"
17+
#include "google/cloud/storage/mocks/mock_async_reader_connection.h"
1718
#include "google/cloud/storage/mocks/mock_async_writer_connection.h"
1819
#include "google/cloud/testing_util/status_matchers.h"
1920
#include <gmock/gmock.h>
@@ -26,8 +27,14 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
2627
namespace {
2728

2829
using ::google::cloud::storage_mocks::MockAsyncConnection;
30+
using ::google::cloud::storage_mocks::MockAsyncReaderConnection;
2931
using ::google::cloud::storage_mocks::MockAsyncWriterConnection;
32+
using ::google::cloud::testing_util::IsOk;
3033
using ::google::cloud::testing_util::IsOkAndHolds;
34+
using ::testing::ElementsAre;
35+
using ::testing::Eq;
36+
using ::testing::Optional;
37+
using ::testing::ResultOf;
3138
using ::testing::Return;
3239
using ::testing::VariantWith;
3340

@@ -43,6 +50,180 @@ storage::ObjectMetadata TestObject() {
4350
.set_size(0);
4451
}
4552

53+
TEST(AsyncClient, InsertObject) {
54+
auto mock = std::make_shared<MockAsyncConnection>();
55+
EXPECT_CALL(*mock, options)
56+
.WillRepeatedly(
57+
Return(Options{}.set<TestOption<0>>("O0").set<TestOption<1>>("O1")));
58+
59+
EXPECT_CALL(*mock, InsertObject)
60+
.WillOnce([](AsyncConnection::InsertObjectParams const& p) {
61+
EXPECT_THAT(p.options.get<TestOption<0>>(), "O0");
62+
EXPECT_THAT(p.options.get<TestOption<1>>(), "O1-function");
63+
EXPECT_THAT(p.options.get<TestOption<2>>(), "O2-function");
64+
EXPECT_EQ(p.request.bucket_name(), "test-bucket");
65+
EXPECT_EQ(p.request.object_name(), "test-object");
66+
EXPECT_EQ(p.request.GetOption<storage::IfGenerationMatch>().value_or(0),
67+
42);
68+
return make_ready_future(make_status_or(TestObject()));
69+
});
70+
71+
auto client = AsyncClient(mock);
72+
auto response = client
73+
.InsertObject("test-bucket", "test-object", "Contents",
74+
storage::IfGenerationMatch(42),
75+
Options{}
76+
.set<TestOption<1>>("O1-function")
77+
.set<TestOption<2>>("O2-function"))
78+
.get();
79+
EXPECT_THAT(response, IsOkAndHolds(TestObject()));
80+
}
81+
82+
TEST(AsyncClient, ReadObject) {
83+
auto mock = std::make_shared<MockAsyncConnection>();
84+
EXPECT_CALL(*mock, options)
85+
.WillRepeatedly(
86+
Return(Options{}.set<TestOption<0>>("O0").set<TestOption<1>>("O1")));
87+
88+
EXPECT_CALL(*mock, ReadObject)
89+
.WillOnce([](AsyncConnection::ReadObjectParams const& p) {
90+
EXPECT_THAT(p.options.get<TestOption<0>>(), "O0");
91+
EXPECT_THAT(p.options.get<TestOption<1>>(), "O1-function");
92+
EXPECT_THAT(p.options.get<TestOption<2>>(), "O2-function");
93+
EXPECT_EQ(p.request.bucket_name(), "test-bucket");
94+
EXPECT_EQ(p.request.object_name(), "test-object");
95+
EXPECT_EQ(p.request.GetOption<storage::Generation>().value_or(0), 42);
96+
auto reader = std::make_unique<MockAsyncReaderConnection>();
97+
EXPECT_CALL(*reader, Read).WillOnce([] {
98+
return make_ready_future(
99+
AsyncReaderConnection::ReadResponse{Status{}});
100+
});
101+
return make_ready_future(make_status_or(
102+
std::unique_ptr<AsyncReaderConnection>(std::move(reader))));
103+
});
104+
105+
auto client = AsyncClient(mock);
106+
auto rt =
107+
client
108+
.ReadObject("test-bucket", "test-object", storage::Generation(42),
109+
Options{}
110+
.set<TestOption<1>>("O1-function")
111+
.set<TestOption<2>>("O2-function"))
112+
.get();
113+
ASSERT_STATUS_OK(rt);
114+
AsyncReader r;
115+
AsyncToken t;
116+
std::tie(r, t) = *std::move(rt);
117+
EXPECT_TRUE(t.valid());
118+
119+
auto pt = r.Read(std::move(t)).get();
120+
AsyncReaderConnection::ReadResponse p;
121+
AsyncToken t2;
122+
ASSERT_STATUS_OK(pt);
123+
std::tie(p, t2) = *std::move(pt);
124+
EXPECT_FALSE(t2.valid());
125+
EXPECT_THAT(
126+
p, VariantWith<ReadPayload>(ResultOf(
127+
"empty response", [](auto const& p) { return p.size(); }, 0)));
128+
}
129+
130+
TEST(AsyncClient, ReadObjectRange) {
131+
auto mock = std::make_shared<MockAsyncConnection>();
132+
EXPECT_CALL(*mock, options)
133+
.WillRepeatedly(
134+
Return(Options{}.set<TestOption<0>>("O0").set<TestOption<1>>("O1")));
135+
136+
EXPECT_CALL(*mock, ReadObjectRange)
137+
.WillOnce([](AsyncConnection::ReadObjectParams const& p) {
138+
EXPECT_THAT(p.options.get<TestOption<0>>(), "O0");
139+
EXPECT_THAT(p.options.get<TestOption<1>>(), "O1-function");
140+
EXPECT_THAT(p.options.get<TestOption<2>>(), "O2-function");
141+
EXPECT_EQ(p.request.bucket_name(), "test-bucket");
142+
EXPECT_EQ(p.request.object_name(), "test-object");
143+
EXPECT_EQ(p.request.GetOption<storage::Generation>().value_or(0), 42);
144+
auto const range = p.request.GetOption<storage::ReadRange>().value_or(
145+
storage::ReadRangeData{0, 0});
146+
EXPECT_EQ(range.begin, 100);
147+
EXPECT_EQ(range.end, 142);
148+
return make_ready_future(
149+
make_status_or(ReadPayload{}.set_metadata(TestObject())));
150+
});
151+
152+
auto client = AsyncClient(mock);
153+
auto payload = client
154+
.ReadObjectRange("test-bucket", "test-object", 100, 42,
155+
storage::Generation(42),
156+
Options{}
157+
.set<TestOption<1>>("O1-function")
158+
.set<TestOption<2>>("O2-function"))
159+
.get();
160+
ASSERT_STATUS_OK(payload);
161+
EXPECT_THAT(payload->metadata(), Optional(Eq(TestObject())));
162+
}
163+
164+
TEST(AsyncClient, StartUnbufferedUpload) {
165+
auto mock = std::make_shared<MockAsyncConnection>();
166+
EXPECT_CALL(*mock, options)
167+
.WillRepeatedly(
168+
Return(Options{}.set<TestOption<0>>("O0").set<TestOption<1>>("O1")));
169+
170+
EXPECT_CALL(*mock, StartUnbufferedUpload)
171+
.WillOnce([](AsyncConnection::UploadParams const& p) {
172+
EXPECT_THAT(p.options.get<TestOption<0>>(), "O0");
173+
EXPECT_THAT(p.options.get<TestOption<1>>(), "O1-function");
174+
EXPECT_THAT(p.options.get<TestOption<2>>(), "O2-function");
175+
EXPECT_EQ(p.request.bucket_name(), "test-bucket");
176+
EXPECT_EQ(p.request.object_name(), "test-object");
177+
EXPECT_EQ(p.request.GetOption<storage::IfGenerationMatch>().value_or(0),
178+
42);
179+
auto writer = std::make_unique<MockAsyncWriterConnection>();
180+
EXPECT_CALL(*writer, PersistedState).WillOnce(Return(0));
181+
EXPECT_CALL(*writer, Finalize).WillRepeatedly([] {
182+
return make_ready_future(make_status_or(TestObject()));
183+
});
184+
return make_ready_future(make_status_or(
185+
std::unique_ptr<AsyncWriterConnection>(std::move(writer))));
186+
});
187+
188+
auto client = AsyncClient(mock);
189+
auto wt = client
190+
.StartUnbufferedUpload("test-bucket", "test-object",
191+
storage::IfGenerationMatch(42),
192+
Options{}
193+
.set<TestOption<1>>("O1-function")
194+
.set<TestOption<2>>("O2-function"))
195+
.get();
196+
ASSERT_STATUS_OK(wt);
197+
AsyncWriter w;
198+
AsyncToken t;
199+
std::tie(w, t) = *std::move(wt);
200+
EXPECT_TRUE(t.valid());
201+
auto object = w.Finalize(std::move(t)).get();
202+
EXPECT_THAT(object, IsOkAndHolds(TestObject()));
203+
}
204+
205+
TEST(AsyncClient, StartUnbufferedUploadResumeFinalized) {
206+
auto mock = std::make_shared<MockAsyncConnection>();
207+
EXPECT_CALL(*mock, options).WillRepeatedly(Return(Options{}));
208+
EXPECT_CALL(*mock, StartUnbufferedUpload).WillOnce([] {
209+
auto writer = std::make_unique<MockAsyncWriterConnection>();
210+
EXPECT_CALL(*writer, PersistedState).WillRepeatedly(Return(TestObject()));
211+
212+
return make_ready_future(make_status_or(
213+
std::unique_ptr<AsyncWriterConnection>(std::move(writer))));
214+
});
215+
216+
auto client = AsyncClient(mock);
217+
auto wt = client.StartUnbufferedUpload("test-bucket", "test-object").get();
218+
ASSERT_STATUS_OK(wt);
219+
AsyncWriter w;
220+
AsyncToken t;
221+
std::tie(w, t) = *std::move(wt);
222+
EXPECT_FALSE(t.valid());
223+
EXPECT_THAT(w.PersistedState(),
224+
VariantWith<storage::ObjectMetadata>(TestObject()));
225+
}
226+
46227
TEST(AsyncClient, StartBufferedUpload) {
47228
auto mock = std::make_shared<MockAsyncConnection>();
48229
EXPECT_CALL(*mock, options)
@@ -106,6 +287,76 @@ TEST(AsyncClient, StartBufferedUploadResumeFinalized) {
106287
VariantWith<storage::ObjectMetadata>(TestObject()));
107288
}
108289

290+
TEST(AsyncClient, ComposeObject) {
291+
auto mock = std::make_shared<MockAsyncConnection>();
292+
EXPECT_CALL(*mock, options)
293+
.WillRepeatedly(
294+
Return(Options{}.set<TestOption<0>>("O0").set<TestOption<1>>("O1")));
295+
296+
EXPECT_CALL(*mock, ComposeObject)
297+
.WillOnce([](AsyncConnection::ComposeObjectParams const& p) {
298+
EXPECT_THAT(p.options.get<TestOption<0>>(), "O0");
299+
EXPECT_THAT(p.options.get<TestOption<1>>(), "O1-function");
300+
EXPECT_THAT(p.options.get<TestOption<2>>(), "O2-function");
301+
EXPECT_EQ(p.request.bucket_name(), "test-bucket");
302+
auto source_is = [](std::string name) {
303+
return ResultOf(
304+
"source object name", [](auto const& s) { return s.object_name; },
305+
std::move(name));
306+
};
307+
EXPECT_THAT(p.request.source_objects(),
308+
ElementsAre(source_is("source0"), source_is("source1")));
309+
EXPECT_EQ(p.request.object_name(), "test-object");
310+
EXPECT_EQ(p.request.GetOption<storage::IfGenerationMatch>().value_or(0),
311+
42);
312+
return make_ready_future(make_status_or(TestObject()));
313+
});
314+
315+
auto client = AsyncClient(mock);
316+
auto response =
317+
client
318+
.ComposeObject("test-bucket",
319+
{storage::ComposeSourceObject{"source0", absl::nullopt,
320+
absl::nullopt},
321+
storage::ComposeSourceObject{"source1", absl::nullopt,
322+
absl::nullopt}},
323+
"test-object", storage::IfGenerationMatch(42),
324+
Options{}
325+
.set<TestOption<1>>("O1-function")
326+
.set<TestOption<2>>("O2-function"))
327+
.get();
328+
EXPECT_THAT(response, IsOkAndHolds(TestObject()));
329+
}
330+
331+
TEST(AsyncClient, DeleteObject) {
332+
auto mock = std::make_shared<MockAsyncConnection>();
333+
EXPECT_CALL(*mock, options)
334+
.WillRepeatedly(
335+
Return(Options{}.set<TestOption<0>>("O0").set<TestOption<1>>("O1")));
336+
337+
EXPECT_CALL(*mock, DeleteObject)
338+
.WillOnce([](AsyncConnection::DeleteObjectParams const& p) {
339+
EXPECT_THAT(p.options.get<TestOption<0>>(), "O0");
340+
EXPECT_THAT(p.options.get<TestOption<1>>(), "O1-function");
341+
EXPECT_THAT(p.options.get<TestOption<2>>(), "O2-function");
342+
EXPECT_EQ(p.request.bucket_name(), "test-bucket");
343+
EXPECT_EQ(p.request.object_name(), "test-object");
344+
EXPECT_EQ(p.request.GetOption<storage::IfGenerationMatch>().value_or(0),
345+
42);
346+
return make_ready_future(Status{});
347+
});
348+
349+
auto client = AsyncClient(mock);
350+
auto response = client
351+
.DeleteObject("test-bucket", "test-object",
352+
storage::IfGenerationMatch(42),
353+
Options{}
354+
.set<TestOption<1>>("O1-function")
355+
.set<TestOption<2>>("O2-function"))
356+
.get();
357+
EXPECT_THAT(response, IsOk());
358+
}
359+
109360
} // namespace
110361
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
111362
} // namespace storage_experimental

google/cloud/storage/object_metadata.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
2626

2727
using ::google::cloud::internal::FormatRfc3339;
2828

29+
std::ostream& operator<<(std::ostream& os, ComposeSourceObject const& r) {
30+
os << "ComposeSourceObject={object_name=" << r.object_name;
31+
if (r.generation) os << ", generation=" << *r.generation;
32+
if (r.if_generation_match) {
33+
os << ", if_generation_match=" << *r.if_generation_match;
34+
}
35+
return os << "}";
36+
}
37+
2938
bool operator==(ObjectMetadata const& lhs, ObjectMetadata const& rhs) {
3039
return lhs.acl_ == rhs.acl_ && lhs.bucket_ == rhs.bucket_ //
3140
&& lhs.cache_control_ == rhs.cache_control_ //

google/cloud/storage/object_metadata_test.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,32 @@
1717
#include "google/cloud/storage/internal/object_metadata_parser.h"
1818
#include "google/cloud/internal/parse_rfc3339.h"
1919
#include <gmock/gmock.h>
20+
#include <sstream>
2021

2122
namespace google {
2223
namespace cloud {
2324
namespace storage {
2425
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
2526
namespace {
2627

28+
TEST(ComposeSourceObject, IOStream) {
29+
auto to_string = [](ComposeSourceObject const& r) {
30+
std::ostringstream os;
31+
os << r;
32+
return std::move(os).str();
33+
};
34+
EXPECT_THAT(
35+
to_string(ComposeSourceObject{"name", absl::nullopt, absl::nullopt}),
36+
"ComposeSourceObject={object_name=name}");
37+
EXPECT_THAT(to_string(ComposeSourceObject{"name", 42, absl::nullopt}),
38+
"ComposeSourceObject={object_name=name, generation=42}");
39+
EXPECT_THAT(to_string(ComposeSourceObject{"name", absl::nullopt, 42}),
40+
"ComposeSourceObject={object_name=name, if_generation_match=42}");
41+
EXPECT_THAT(to_string(ComposeSourceObject{"name", 7, 42}),
42+
"ComposeSourceObject={object_name=name, generation=7, "
43+
"if_generation_match=42}");
44+
}
45+
2746
ObjectMetadata CreateObjectMetadataForTest() {
2847
// This metadata object has some impossible combination of fields in it. The
2948
// goal is to fully test the parsing, not to simulate valid objects.

0 commit comments

Comments
 (0)