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() {