Ports a test utility from //gfe/gfe2/test_tools to //third_party/http2/adapter.
PiperOrigin-RevId: 364558763
Change-Id: I2573bb6c5fffbe5873fa418c039f18f4b952a186
diff --git a/http2/adapter/test_utils.cc b/http2/adapter/test_utils.cc
new file mode 100644
index 0000000..6477e9c
--- /dev/null
+++ b/http2/adapter/test_utils.cc
@@ -0,0 +1,116 @@
+#include "http2/adapter/test_utils.h"
+
+#include "spdy/core/spdy_frame_reader.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+namespace {
+
+using TypeAndOptionalLength =
+ std::pair<spdy::SpdyFrameType, absl::optional<size_t>>;
+
+std::vector<std::pair<const char*, std::string>> LogFriendly(
+ const std::vector<TypeAndOptionalLength>& types_and_lengths) {
+ std::vector<std::pair<const char*, std::string>> out;
+ out.reserve(types_and_lengths.size());
+ for (const auto type_and_length : types_and_lengths) {
+ out.push_back({spdy::FrameTypeToString(type_and_length.first),
+ type_and_length.second
+ ? absl::StrCat(type_and_length.second.value())
+ : "<unspecified>"});
+ }
+ return out;
+}
+
+// Custom gMock matcher, used to determine if a particular type of frame
+// is in a string. This is useful in tests where we want to show that a
+// particular control frame type is serialized for sending to the peer.
+class SpdyControlFrameMatcher
+ : public testing::MatcherInterface<const std::string> {
+ public:
+ explicit SpdyControlFrameMatcher(
+ std::vector<TypeAndOptionalLength> types_and_lengths)
+ : expected_types_and_lengths_(std::move(types_and_lengths)) {}
+
+ bool MatchAndExplain(const std::string s,
+ testing::MatchResultListener* listener) const override {
+ spdy::SpdyFrameReader reader(s.data(), s.size());
+
+ for (TypeAndOptionalLength expected : expected_types_and_lengths_) {
+ if (!MatchAndExplainOneFrame(expected.first, expected.second, &reader,
+ listener)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool MatchAndExplainOneFrame(spdy::SpdyFrameType expected_type,
+ absl::optional<size_t> expected_length,
+ spdy::SpdyFrameReader* reader,
+ testing::MatchResultListener* listener) const {
+ uint32_t payload_length;
+ if (!reader->ReadUInt24(&payload_length)) {
+ *listener << "; unable to read length field for expected_type "
+ << FrameTypeToString(expected_type) << ". data too short!";
+ return false;
+ }
+
+ if (expected_length && payload_length != expected_length.value()) {
+ *listener << "; actual length: " << payload_length
+ << " but expected length: " << expected_length.value();
+ return false;
+ }
+
+ uint8_t raw_type;
+ if (!reader->ReadUInt8(&raw_type)) {
+ *listener << "; unable to read type field for expected_type "
+ << FrameTypeToString(expected_type) << ". data too short!";
+ return false;
+ }
+
+ if (!spdy::IsDefinedFrameType(raw_type)) {
+ *listener << "; expected type " << FrameTypeToString(expected_type)
+ << " but raw type " << static_cast<int>(raw_type)
+ << " is not a defined frame type!";
+ return false;
+ }
+
+ spdy::SpdyFrameType actual_type = spdy::ParseFrameType(raw_type);
+ if (actual_type != expected_type) {
+ *listener << "; actual type: " << FrameTypeToString(actual_type)
+ << " but expected type: " << FrameTypeToString(expected_type);
+ return false;
+ }
+
+ // Seek past flags (1B), stream ID (4B), and payload. Reach the next frame.
+ reader->Seek(5 + payload_length);
+ return true;
+ }
+
+ void DescribeTo(std::ostream* os) const override {
+ *os << "Data contains frames of types in sequence "
+ << LogFriendly(expected_types_and_lengths_);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ *os << "Data does not contain frames of types in sequence "
+ << LogFriendly(expected_types_and_lengths_);
+ }
+
+ private:
+ const std::vector<TypeAndOptionalLength> expected_types_and_lengths_;
+};
+
+} // namespace
+
+testing::Matcher<const std::string> ContainsFrames(
+ std::vector<std::pair<spdy::SpdyFrameType, absl::optional<size_t>>>
+ types_and_lengths) {
+ return MakeMatcher(new SpdyControlFrameMatcher(std::move(types_and_lengths)));
+}
+
+} // namespace test
+} // namespace adapter
+} // namespace http2
diff --git a/http2/adapter/test_utils.h b/http2/adapter/test_utils.h
new file mode 100644
index 0000000..6d585ee
--- /dev/null
+++ b/http2/adapter/test_utils.h
@@ -0,0 +1,24 @@
+#ifndef QUICHE_HTTP2_ADAPTER_TEST_UTILS_H_
+#define QUICHE_HTTP2_ADAPTER_TEST_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "common/platform/api/quiche_test.h"
+#include "spdy/core/spdy_protocol.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+
+// Matcher that checks whether a string contains HTTP/2 frames of the specified
+// ordered sequence of types and lengths.
+testing::Matcher<const std::string> ContainsFrames(
+ std::vector<std::pair<spdy::SpdyFrameType, absl::optional<size_t>>>
+ types_and_lengths);
+
+} // namespace test
+} // namespace adapter
+} // namespace http2
+
+#endif // QUICHE_HTTP2_ADAPTER_TEST_UTILS_H_
diff --git a/http2/adapter/test_utils_test.cc b/http2/adapter/test_utils_test.cc
new file mode 100644
index 0000000..1f8c557
--- /dev/null
+++ b/http2/adapter/test_utils_test.cc
@@ -0,0 +1,88 @@
+#include "http2/adapter/test_utils.h"
+
+#include "testing/base/public/gunit.h"
+#include "spdy/core/spdy_framer.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+namespace {
+
+using spdy::SpdyFramer;
+
+TEST(ContainsFrames, Empty) {
+ EXPECT_THAT("", ContainsFrames({}));
+}
+
+TEST(ContainsFrames, SingleFrameWithLength) {
+ SpdyFramer framer{SpdyFramer::ENABLE_COMPRESSION};
+
+ spdy::SpdyPingIR ping{511};
+ EXPECT_THAT(framer.SerializeFrame(ping),
+ ContainsFrames({{spdy::SpdyFrameType::PING, 8}}));
+
+ spdy::SpdyWindowUpdateIR window_update{1, 101};
+ EXPECT_THAT(framer.SerializeFrame(window_update),
+ ContainsFrames({{spdy::SpdyFrameType::WINDOW_UPDATE, 4}}));
+
+ spdy::SpdyDataIR data{3, "Some example data, ha ha!"};
+ EXPECT_THAT(framer.SerializeFrame(data),
+ ContainsFrames({{spdy::SpdyFrameType::DATA, 25}}));
+}
+
+TEST(ContainsFrames, SingleFrameWithoutLength) {
+ SpdyFramer framer{SpdyFramer::ENABLE_COMPRESSION};
+
+ spdy::SpdyRstStreamIR rst_stream{7, spdy::ERROR_CODE_REFUSED_STREAM};
+ EXPECT_THAT(
+ framer.SerializeFrame(rst_stream),
+ ContainsFrames({{spdy::SpdyFrameType::RST_STREAM, absl::nullopt}}));
+
+ spdy::SpdyGoAwayIR goaway{13, spdy::ERROR_CODE_ENHANCE_YOUR_CALM,
+ "Consider taking some deep breaths."};
+ EXPECT_THAT(framer.SerializeFrame(goaway),
+ ContainsFrames({{spdy::SpdyFrameType::GOAWAY, absl::nullopt}}));
+
+ spdy::Http2HeaderBlock block;
+ block[":method"] = "GET";
+ block[":path"] = "/example";
+ block[":authority"] = "example.com";
+ spdy::SpdyHeadersIR headers{17, std::move(block)};
+ EXPECT_THAT(framer.SerializeFrame(headers),
+ ContainsFrames({{spdy::SpdyFrameType::HEADERS, absl::nullopt}}));
+}
+
+TEST(ContainsFrames, MultipleFrames) {
+ SpdyFramer framer{SpdyFramer::ENABLE_COMPRESSION};
+
+ spdy::SpdyPingIR ping{511};
+ spdy::SpdyWindowUpdateIR window_update{1, 101};
+ spdy::SpdyDataIR data{3, "Some example data, ha ha!"};
+ spdy::SpdyRstStreamIR rst_stream{7, spdy::ERROR_CODE_REFUSED_STREAM};
+ spdy::SpdyGoAwayIR goaway{13, spdy::ERROR_CODE_ENHANCE_YOUR_CALM,
+ "Consider taking some deep breaths."};
+ spdy::Http2HeaderBlock block;
+ block[":method"] = "GET";
+ block[":path"] = "/example";
+ block[":authority"] = "example.com";
+ spdy::SpdyHeadersIR headers{17, std::move(block)};
+
+ EXPECT_THAT(
+ absl::StrCat(absl::string_view(framer.SerializeFrame(ping)),
+ absl::string_view(framer.SerializeFrame(window_update)),
+ absl::string_view(framer.SerializeFrame(data)),
+ absl::string_view(framer.SerializeFrame(rst_stream)),
+ absl::string_view(framer.SerializeFrame(goaway)),
+ absl::string_view(framer.SerializeFrame(headers))),
+ ContainsFrames({{spdy::SpdyFrameType::PING, absl::nullopt},
+ {spdy::SpdyFrameType::WINDOW_UPDATE, absl::nullopt},
+ {spdy::SpdyFrameType::DATA, 25},
+ {spdy::SpdyFrameType::RST_STREAM, absl::nullopt},
+ {spdy::SpdyFrameType::GOAWAY, 42},
+ {spdy::SpdyFrameType::HEADERS, 19}}));
+}
+
+} // namespace
+} // namespace test
+} // namespace adapter
+} // namespace http2
diff --git a/spdy/core/spdy_protocol.h b/spdy/core/spdy_protocol.h
index fcd6ada..d4a671b 100644
--- a/spdy/core/spdy_protocol.h
+++ b/spdy/core/spdy_protocol.h
@@ -1039,6 +1039,12 @@
// Returns the actual size of the underlying buffer.
size_t size() const { return size_; }
+ operator absl::string_view() const {
+ return absl::string_view{frame_, size_};
+ }
+
+ operator std::string() const { return std::string{frame_, size_}; }
+
// Returns a buffer containing the contents of the frame, of which the caller
// takes ownership, and clears this SpdySerializedFrame.
char* ReleaseBuffer() {