Add RecordingHttp2Visitor for testing.
This CL introduces a new Http2VisitorInterface implementation that records the
sequence of events it receives. That sequence of events can then be compared
between adapter implementations.
PiperOrigin-RevId: 374210574
diff --git a/http2/adapter/adapter_impl_comparison_test.cc b/http2/adapter/adapter_impl_comparison_test.cc
new file mode 100644
index 0000000..11f03bb
--- /dev/null
+++ b/http2/adapter/adapter_impl_comparison_test.cc
@@ -0,0 +1,83 @@
+#include "http2/adapter/recording_http2_visitor.h"
+
+#include "http2/adapter/http2_protocol.h"
+#include "http2/adapter/nghttp2_adapter.h"
+#include "http2/adapter/oghttp2_adapter.h"
+#include "http2/adapter/test_frame_sequence.h"
+#include "common/platform/api/quiche_test.h"
+#include "spdy/core/spdy_protocol.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+namespace {
+
+TEST(AdapterImplComparisonTest, ClientHandlesFrames) {
+ RecordingHttp2Visitor nghttp2_visitor;
+ std::unique_ptr<NgHttp2Adapter> nghttp2_adapter =
+ NgHttp2Adapter::CreateClientAdapter(nghttp2_visitor);
+
+ RecordingHttp2Visitor oghttp2_visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kClient};
+ std::unique_ptr<OgHttp2Adapter> oghttp2_adapter =
+ OgHttp2Adapter::Create(oghttp2_visitor, options);
+
+ const std::string initial_frames = TestFrameSequence()
+ .ServerPreface()
+ .Ping(42)
+ .WindowUpdate(0, 1000)
+ .Serialize();
+
+ nghttp2_adapter->ProcessBytes(initial_frames);
+ oghttp2_adapter->ProcessBytes(initial_frames);
+
+ EXPECT_EQ(nghttp2_visitor.GetEventSequence(),
+ oghttp2_visitor.GetEventSequence());
+
+ // TODO(b/181586191): Consider consistent behavior for delivering events on
+ // non-existent streams between nghttp2_adapter and oghttp2_adapter.
+}
+
+TEST(AdapterImplComparisonTest, ServerHandlesFrames) {
+ RecordingHttp2Visitor nghttp2_visitor;
+ std::unique_ptr<NgHttp2Adapter> nghttp2_adapter =
+ NgHttp2Adapter::CreateServerAdapter(nghttp2_visitor);
+
+ RecordingHttp2Visitor oghttp2_visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kServer};
+ std::unique_ptr<OgHttp2Adapter> oghttp2_adapter =
+ OgHttp2Adapter::Create(oghttp2_visitor, options);
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Ping(42)
+ .WindowUpdate(0, 1000)
+ .Headers(1,
+ {{":method", "POST"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/false)
+ .WindowUpdate(1, 2000)
+ .Data(1, "This is the request body.")
+ .Headers(3,
+ {{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/two"}},
+ /*fin=*/true)
+ .RstStream(3, Http2ErrorCode::CANCEL)
+ .Ping(47)
+ .Serialize();
+
+ nghttp2_adapter->ProcessBytes(frames);
+ oghttp2_adapter->ProcessBytes(frames);
+
+ EXPECT_EQ(nghttp2_visitor.GetEventSequence(),
+ oghttp2_visitor.GetEventSequence());
+}
+
+} // namespace
+} // namespace test
+} // namespace adapter
+} // namespace http2
diff --git a/http2/adapter/recording_http2_visitor.cc b/http2/adapter/recording_http2_visitor.cc
new file mode 100644
index 0000000..78ec423
--- /dev/null
+++ b/http2/adapter/recording_http2_visitor.cc
@@ -0,0 +1,155 @@
+#include "http2/adapter/recording_http2_visitor.h"
+
+#include "absl/strings/str_format.h"
+#include "http2/adapter/http2_protocol.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+
+void RecordingHttp2Visitor::OnConnectionError() {
+ events_.push_back("OnConnectionError");
+}
+
+void RecordingHttp2Visitor::OnFrameHeader(Http2StreamId stream_id,
+ size_t length,
+ uint8_t type,
+ uint8_t flags) {
+ events_.push_back(absl::StrFormat("OnFrameHeader %d %d %d %d", stream_id,
+ length, type, flags));
+}
+
+void RecordingHttp2Visitor::OnSettingsStart() {
+ events_.push_back("OnSettingsStart");
+}
+
+void RecordingHttp2Visitor::OnSetting(Http2Setting setting) {
+ events_.push_back(absl::StrFormat(
+ "OnSetting %s %d", Http2SettingsIdToString(setting.id), setting.value));
+}
+
+void RecordingHttp2Visitor::OnSettingsEnd() {
+ events_.push_back("OnSettingsEnd");
+}
+
+void RecordingHttp2Visitor::OnSettingsAck() {
+ events_.push_back("OnSettingsAck");
+}
+
+void RecordingHttp2Visitor::OnBeginHeadersForStream(Http2StreamId stream_id) {
+ events_.push_back(absl::StrFormat("OnBeginHeadersForStream %d", stream_id));
+}
+
+void RecordingHttp2Visitor::OnHeaderForStream(Http2StreamId stream_id,
+ absl::string_view name,
+ absl::string_view value) {
+ events_.push_back(
+ absl::StrFormat("OnHeaderForStream %d %s %s", stream_id, name, value));
+}
+
+void RecordingHttp2Visitor::OnEndHeadersForStream(Http2StreamId stream_id) {
+ events_.push_back(absl::StrFormat("OnEndHeadersForStream %d", stream_id));
+}
+
+void RecordingHttp2Visitor::OnBeginDataForStream(Http2StreamId stream_id,
+ size_t payload_length) {
+ events_.push_back(
+ absl::StrFormat("OnBeginDataForStream %d %d", stream_id, payload_length));
+}
+
+void RecordingHttp2Visitor::OnDataForStream(Http2StreamId stream_id,
+ absl::string_view data) {
+ events_.push_back(absl::StrFormat("OnDataForStream %d %s", stream_id, data));
+}
+
+void RecordingHttp2Visitor::OnEndStream(Http2StreamId stream_id) {
+ events_.push_back(absl::StrFormat("OnEndStream %d", stream_id));
+}
+
+void RecordingHttp2Visitor::OnRstStream(Http2StreamId stream_id,
+ Http2ErrorCode error_code) {
+ events_.push_back(absl::StrFormat("OnRstStream %d %s", stream_id,
+ Http2ErrorCodeToString(error_code)));
+}
+
+void RecordingHttp2Visitor::OnCloseStream(Http2StreamId stream_id,
+ Http2ErrorCode error_code) {
+ events_.push_back(absl::StrFormat("OnCloseStream %d %s", stream_id,
+ Http2ErrorCodeToString(error_code)));
+}
+
+void RecordingHttp2Visitor::OnPriorityForStream(Http2StreamId stream_id,
+ Http2StreamId parent_stream_id,
+ int weight,
+ bool exclusive) {
+ events_.push_back(absl::StrFormat("OnPriorityForStream %d %d %d %d",
+ stream_id, parent_stream_id, weight,
+ exclusive));
+}
+
+void RecordingHttp2Visitor::OnPing(Http2PingId ping_id, bool is_ack) {
+ events_.push_back(absl::StrFormat("OnPing %d %d", ping_id, is_ack));
+}
+
+void RecordingHttp2Visitor::OnPushPromiseForStream(
+ Http2StreamId stream_id,
+ Http2StreamId promised_stream_id) {
+ events_.push_back(absl::StrFormat("OnPushPromiseForStream %d %d", stream_id,
+ promised_stream_id));
+}
+
+void RecordingHttp2Visitor::OnGoAway(Http2StreamId last_accepted_stream_id,
+ Http2ErrorCode error_code,
+ absl::string_view opaque_data) {
+ events_.push_back(
+ absl::StrFormat("OnGoAway %d %s %s", last_accepted_stream_id,
+ Http2ErrorCodeToString(error_code), opaque_data));
+}
+
+void RecordingHttp2Visitor::OnWindowUpdate(Http2StreamId stream_id,
+ int window_increment) {
+ events_.push_back(
+ absl::StrFormat("OnWindowUpdate %d %d", stream_id, window_increment));
+}
+
+void RecordingHttp2Visitor::OnReadyToSendDataForStream(Http2StreamId stream_id,
+ char* destination_buffer,
+ size_t length,
+ ssize_t* written,
+ bool* end_stream) {
+ // TODO(b/181586191): Revisit this. The visitor is expected to write to the
+ // |destination_buffer| and set the other pointer values appropriately.
+ events_.push_back(
+ absl::StrFormat("OnReadyToSendDataForStream %d %d", stream_id, length));
+}
+
+void RecordingHttp2Visitor::OnReadyToSendMetadataForStream(
+ Http2StreamId stream_id,
+ char* buffer,
+ size_t length,
+ ssize_t* written) {
+ // TODO(b/181586191): Revisit this. The visitor is expected to write to the
+ // |buffer| and set *written appropriately.
+ events_.push_back(absl::StrFormat("OnReadyToSendMetadataForStream %d %d",
+ stream_id, length));
+}
+
+void RecordingHttp2Visitor::OnBeginMetadataForStream(Http2StreamId stream_id,
+ size_t payload_length) {
+ events_.push_back(absl::StrFormat("OnBeginMetadataForStream %d %d", stream_id,
+ payload_length));
+}
+
+void RecordingHttp2Visitor::OnMetadataForStream(Http2StreamId stream_id,
+ absl::string_view metadata) {
+ events_.push_back(
+ absl::StrFormat("OnMetadataForStream %d %s", stream_id, metadata));
+}
+
+void RecordingHttp2Visitor::OnMetadataEndForStream(Http2StreamId stream_id) {
+ events_.push_back(absl::StrFormat("OnMetadataEndForStream %d", stream_id));
+}
+
+} // namespace test
+} // namespace adapter
+} // namespace http2
diff --git a/http2/adapter/recording_http2_visitor.h b/http2/adapter/recording_http2_visitor.h
new file mode 100644
index 0000000..452b451
--- /dev/null
+++ b/http2/adapter/recording_http2_visitor.h
@@ -0,0 +1,80 @@
+#ifndef QUICHE_HTTP2_ADAPTER_RECORDING_HTTP2_VISITOR_H_
+#define QUICHE_HTTP2_ADAPTER_RECORDING_HTTP2_VISITOR_H_
+
+#include <list>
+#include <string>
+
+#include "http2/adapter/http2_visitor_interface.h"
+#include "common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+
+// A visitor implementation that records the sequence of callbacks it receives.
+class RecordingHttp2Visitor : public Http2VisitorInterface {
+ public:
+ using Event = std::string;
+ using EventSequence = std::list<Event>;
+
+ // From Http2VisitorInterface
+ void OnConnectionError() override;
+ void OnFrameHeader(Http2StreamId stream_id,
+ size_t length,
+ uint8_t type,
+ uint8_t flags) override;
+ void OnSettingsStart() override;
+ void OnSetting(Http2Setting setting) override;
+ void OnSettingsEnd() override;
+ void OnSettingsAck() override;
+ void OnBeginHeadersForStream(Http2StreamId stream_id) override;
+ void OnHeaderForStream(Http2StreamId stream_id,
+ absl::string_view name,
+ absl::string_view value) override;
+ void OnEndHeadersForStream(Http2StreamId stream_id) override;
+ void OnBeginDataForStream(Http2StreamId stream_id,
+ size_t payload_length) override;
+ void OnDataForStream(Http2StreamId stream_id,
+ absl::string_view data) override;
+ void OnEndStream(Http2StreamId stream_id) override;
+ void OnRstStream(Http2StreamId stream_id, Http2ErrorCode error_code) override;
+ void OnCloseStream(Http2StreamId stream_id,
+ Http2ErrorCode error_code) override;
+ void OnPriorityForStream(Http2StreamId stream_id,
+ Http2StreamId parent_stream_id,
+ int weight,
+ bool exclusive) override;
+ void OnPing(Http2PingId ping_id, bool is_ack) override;
+ void OnPushPromiseForStream(Http2StreamId stream_id,
+ Http2StreamId promised_stream_id) override;
+ void OnGoAway(Http2StreamId last_accepted_stream_id,
+ Http2ErrorCode error_code,
+ absl::string_view opaque_data) override;
+ void OnWindowUpdate(Http2StreamId stream_id, int window_increment) override;
+ void OnReadyToSendDataForStream(Http2StreamId stream_id,
+ char* destination_buffer,
+ size_t length,
+ ssize_t* written,
+ bool* end_stream) override;
+ void OnReadyToSendMetadataForStream(Http2StreamId stream_id,
+ char* buffer,
+ size_t length,
+ ssize_t* written) override;
+ void OnBeginMetadataForStream(Http2StreamId stream_id,
+ size_t payload_length) override;
+ void OnMetadataForStream(Http2StreamId stream_id,
+ absl::string_view metadata) override;
+ void OnMetadataEndForStream(Http2StreamId stream_id) override;
+
+ const EventSequence& GetEventSequence() const { return events_; }
+ void Clear() { events_.clear(); }
+
+ private:
+ EventSequence events_;
+};
+
+} // namespace test
+} // namespace adapter
+} // namespace http2
+
+#endif // QUICHE_HTTP2_ADAPTER_RECORDING_HTTP2_VISITOR_H_
diff --git a/http2/adapter/recording_http2_visitor_test.cc b/http2/adapter/recording_http2_visitor_test.cc
new file mode 100644
index 0000000..8f278d7
--- /dev/null
+++ b/http2/adapter/recording_http2_visitor_test.cc
@@ -0,0 +1,135 @@
+#include "http2/adapter/recording_http2_visitor.h"
+
+#include <list>
+
+#include "http2/adapter/http2_protocol.h"
+#include "http2/test_tools/http2_random.h"
+#include "common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+namespace {
+
+using ::testing::IsEmpty;
+
+TEST(RecordingHttp2VisitorTest, EmptySequence) {
+ RecordingHttp2Visitor chocolate_visitor;
+ RecordingHttp2Visitor vanilla_visitor;
+
+ EXPECT_THAT(chocolate_visitor.GetEventSequence(), IsEmpty());
+ EXPECT_THAT(vanilla_visitor.GetEventSequence(), IsEmpty());
+ EXPECT_EQ(chocolate_visitor.GetEventSequence(),
+ vanilla_visitor.GetEventSequence());
+
+ chocolate_visitor.OnSettingsStart();
+
+ EXPECT_THAT(chocolate_visitor.GetEventSequence(), testing::Not(IsEmpty()));
+ EXPECT_THAT(vanilla_visitor.GetEventSequence(), IsEmpty());
+ EXPECT_NE(chocolate_visitor.GetEventSequence(),
+ vanilla_visitor.GetEventSequence());
+
+ chocolate_visitor.Clear();
+
+ EXPECT_THAT(chocolate_visitor.GetEventSequence(), IsEmpty());
+ EXPECT_THAT(vanilla_visitor.GetEventSequence(), IsEmpty());
+ EXPECT_EQ(chocolate_visitor.GetEventSequence(),
+ vanilla_visitor.GetEventSequence());
+}
+
+TEST(RecordingHttp2VisitorTest, SameEventsProduceSameSequence) {
+ RecordingHttp2Visitor chocolate_visitor;
+ RecordingHttp2Visitor vanilla_visitor;
+
+ // Prepare some random values to deliver with the events.
+ http2::test::Http2Random random;
+ const Http2StreamId stream_id = random.Uniform(kMaxStreamId);
+ const Http2StreamId another_stream_id = random.Uniform(kMaxStreamId);
+ const size_t length = random.Rand16();
+ const uint8_t type = random.Rand8();
+ const uint8_t flags = random.Rand8();
+ const Http2ErrorCode error_code = static_cast<Http2ErrorCode>(
+ random.Uniform(static_cast<int>(Http2ErrorCode::MAX_ERROR_CODE)));
+ const Http2Setting setting = {.id = random.Rand16(),
+ .value = random.Rand32()};
+ const absl::string_view alphabet = "abcdefghijklmnopqrstuvwxyz0123456789-";
+ const std::string some_string =
+ random.RandStringWithAlphabet(random.Rand8(), alphabet);
+ const std::string another_string =
+ random.RandStringWithAlphabet(random.Rand8(), alphabet);
+ const uint16_t some_int = random.Rand16();
+ const bool some_bool = random.OneIn(2);
+
+ // Send the same arbitrary sequence of events to both visitors.
+ std::list<RecordingHttp2Visitor*> visitors = {&chocolate_visitor,
+ &vanilla_visitor};
+ for (RecordingHttp2Visitor* visitor : visitors) {
+ visitor->OnConnectionError();
+ visitor->OnFrameHeader(stream_id, length, type, flags);
+ visitor->OnSettingsStart();
+ visitor->OnSetting(setting);
+ visitor->OnSettingsEnd();
+ visitor->OnSettingsAck();
+ visitor->OnBeginHeadersForStream(stream_id);
+ visitor->OnHeaderForStream(stream_id, some_string, another_string);
+ visitor->OnEndHeadersForStream(stream_id);
+ visitor->OnBeginDataForStream(stream_id, length);
+ visitor->OnDataForStream(stream_id, some_string);
+ visitor->OnDataForStream(stream_id, another_string);
+ visitor->OnEndStream(stream_id);
+ visitor->OnRstStream(stream_id, error_code);
+ visitor->OnCloseStream(stream_id, error_code);
+ visitor->OnPriorityForStream(stream_id, another_stream_id, some_int,
+ some_bool);
+ visitor->OnPing(some_int, some_bool);
+ visitor->OnPushPromiseForStream(stream_id, another_stream_id);
+ visitor->OnGoAway(stream_id, error_code, some_string);
+ visitor->OnWindowUpdate(stream_id, some_int);
+ visitor->OnReadyToSendDataForStream(
+ stream_id, /*destination_buffer=*/nullptr, length, /*written=*/nullptr,
+ /*end_stream=*/nullptr);
+ visitor->OnReadyToSendMetadataForStream(stream_id, /*buffer=*/nullptr,
+ length, /*written=*/nullptr);
+ visitor->OnBeginMetadataForStream(stream_id, length);
+ visitor->OnMetadataForStream(stream_id, some_string);
+ visitor->OnMetadataForStream(stream_id, another_string);
+ visitor->OnMetadataEndForStream(stream_id);
+ }
+
+ EXPECT_EQ(chocolate_visitor.GetEventSequence(),
+ vanilla_visitor.GetEventSequence());
+}
+
+TEST(RecordingHttp2VisitorTest, DifferentEventsProduceDifferentSequence) {
+ RecordingHttp2Visitor chocolate_visitor;
+ RecordingHttp2Visitor vanilla_visitor;
+ EXPECT_EQ(chocolate_visitor.GetEventSequence(),
+ vanilla_visitor.GetEventSequence());
+
+ const Http2StreamId stream_id = 1;
+ const size_t length = 42;
+
+ // Different events with the same method arguments should produce different
+ // event sequences.
+ chocolate_visitor.OnBeginDataForStream(stream_id, length);
+ vanilla_visitor.OnBeginMetadataForStream(stream_id, length);
+ EXPECT_NE(chocolate_visitor.GetEventSequence(),
+ vanilla_visitor.GetEventSequence());
+
+ chocolate_visitor.Clear();
+ vanilla_visitor.Clear();
+ EXPECT_EQ(chocolate_visitor.GetEventSequence(),
+ vanilla_visitor.GetEventSequence());
+
+ // The same events with different method arguments should produce different
+ // event sequences.
+ chocolate_visitor.OnBeginHeadersForStream(stream_id);
+ vanilla_visitor.OnBeginHeadersForStream(stream_id + 2);
+ EXPECT_NE(chocolate_visitor.GetEventSequence(),
+ vanilla_visitor.GetEventSequence());
+}
+
+} // namespace
+} // namespace test
+} // namespace adapter
+} // namespace http2