@@ -36,16 +36,39 @@ using ReadResponse =
3636using ::google::cloud::storage_experimental::ObjectDescriptorConnection;
3737using ::google::cloud::storage_mocks::MockAsyncObjectDescriptorConnection;
3838using ::google::cloud::storage_mocks::MockAsyncReaderConnection;
39+ using ::google::cloud::storage_experimental::ReadPayload;
3940using ::google::cloud::testing_util::EventNamed;
4041using ::google::cloud::testing_util::InstallSpanCatcher;
4142using ::google::cloud::testing_util::OTelAttribute;
43+ using ::google::cloud::testing_util::OTelContextCaptured;
44+ using ::google::cloud::testing_util::ThereIsAnActiveSpan;
45+ using ::google::cloud::testing_util::PromiseWithOTelContext;
4246using ::google::cloud::testing_util::SpanEventAttributesAre;
4347using ::google::cloud::testing_util::SpanHasInstrumentationScope;
4448using ::google::cloud::testing_util::SpanKindIsClient;
4549using ::google::cloud::testing_util::SpanNamed;
4650using ::google::cloud::testing_util::SpanWithStatus;
4751using ::testing::_;
4852
53+ // A helper to set expectations on a mock async reader. It captures the OTel
54+ // context and returns a future that can be controlled by the test.
55+ auto expect_context = [](auto & p) {
56+ return [&p] {
57+ EXPECT_TRUE (ThereIsAnActiveSpan ());
58+ EXPECT_TRUE (OTelContextCaptured ());
59+ return p.get_future ();
60+ };
61+ };
62+
63+ // A helper to be used in a `.then()` clause. It verifies the OTel context
64+ // has been detached before the user receives the result.
65+ auto expect_no_context = [](auto f) {
66+ auto t = f.get ();
67+ EXPECT_FALSE (ThereIsAnActiveSpan ());
68+ EXPECT_FALSE (OTelContextCaptured ());
69+ return t;
70+ };
71+
4972TEST (ObjectDescriptorConnectionTracing, Read) {
5073 namespace sc = ::opentelemetry::trace::SemanticConventions;
5174 auto span_catcher = InstallSpanCatcher ();
@@ -76,6 +99,56 @@ TEST(ObjectDescriptorConnectionTracing, Read) {
7699 OTelAttribute<std::string>(sc::kThreadId , _)))))));
77100}
78101
102+ TEST (ObjectDescriptorConnectionTracing, ReadThenRead) {
103+ namespace sc = ::opentelemetry::trace::SemanticConventions;
104+ auto span_catcher = InstallSpanCatcher ();
105+
106+ auto mock_connection = std::make_shared<MockAsyncObjectDescriptorConnection>();
107+ auto * mock_reader_ptr = new MockAsyncReaderConnection;
108+ PromiseWithOTelContext<ReadResponse> p;
109+ EXPECT_CALL (*mock_reader_ptr, Read).WillOnce (expect_context (p));
110+
111+ EXPECT_CALL (*mock_connection, Read)
112+ .WillOnce ([&](ObjectDescriptorConnection::ReadParams) {
113+ return std::unique_ptr<storage_experimental::AsyncReaderConnection>(
114+ mock_reader_ptr);
115+ });
116+
117+ auto connection = MakeTracingObjectDescriptorConnection (
118+ internal::MakeSpan (" test-span" ), std::move (mock_connection));
119+
120+ auto reader = connection->Read ({});
121+ auto f = reader->Read ().then (expect_no_context);
122+ p.set_value (ReadPayload (" test-payload" ).set_offset (123 ));
123+ (void )f.get ();
124+
125+ connection.reset (); // End the span
126+
127+ auto spans = span_catcher->GetSpans ();
128+ EXPECT_THAT (
129+ spans,
130+ ElementsAre (AllOf (
131+ SpanNamed (" test-span" ),
132+ SpanWithStatus (opentelemetry::trace::StatusCode::kOk ),
133+ SpanHasInstrumentationScope (), SpanKindIsClient (),
134+ SpanHasEvents (
135+ AllOf (EventNamed (" gl-cpp.open.read" ),
136+ SpanEventAttributesAre (
137+ OTelAttribute<std::int64_t >(" read-length" , 0 ),
138+ OTelAttribute<std::int64_t >(" read-start" , 0 ),
139+ OTelAttribute<std::string>(sc::kThreadId , _))),
140+ AllOf (
141+ EventNamed (" gl-cpp.read" ),
142+ SpanEventAttributesAre (
143+ OTelAttribute<std::int64_t >(" message.starting_offset" ,
144+ 123 ),
145+ OTelAttribute<std::string>(sc::kThreadId , _),
146+ OTelAttribute<std::int64_t >(" rpc.message.id" , 1 ),
147+ // THIS WAS THE MISSING ATTRIBUTE:
148+ OTelAttribute<std::string>(" rpc.message.type" ,
149+ " RECEIVED" )))))));
150+ }
151+
79152} // namespace
80153GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
81154} // namespace storage_internal
0 commit comments