Allow setting a delay for a response.

This serves as infrastructure for testing that timing metrics
measure what they're supposed to
(see https://chromium-review.googlesource.com/c/chromium/src/+/3815441)

PiperOrigin-RevId: 468108169
diff --git a/quiche/quic/tools/quic_backend_response.cc b/quiche/quic/tools/quic_backend_response.cc
index 8fce5d5..8a54204 100644
--- a/quiche/quic/tools/quic_backend_response.cc
+++ b/quiche/quic/tools/quic_backend_response.cc
@@ -20,7 +20,8 @@
       priority(other.priority),
       body(other.body) {}
 
-QuicBackendResponse::QuicBackendResponse() : response_type_(REGULAR_RESPONSE) {}
+QuicBackendResponse::QuicBackendResponse()
+    : response_type_(REGULAR_RESPONSE), delay_(QuicTime::Delta::Zero()) {}
 
 QuicBackendResponse::~QuicBackendResponse() = default;
 
diff --git a/quiche/quic/tools/quic_backend_response.h b/quiche/quic/tools/quic_backend_response.h
index 8e52e2e..6d1b105 100644
--- a/quiche/quic/tools/quic_backend_response.h
+++ b/quiche/quic/tools/quic_backend_response.h
@@ -6,6 +6,7 @@
 #define QUICHE_QUIC_TOOLS_QUIC_BACKEND_RESPONSE_H_
 
 #include "absl/strings/string_view.h"
+#include "quiche/quic/core/quic_time.h"
 #include "quiche/quic/tools/quic_url.h"
 #include "quiche/spdy/core/http2_header_block.h"
 #include "quiche/spdy/core/spdy_protocol.h"
@@ -78,12 +79,18 @@
     body_.assign(body.data(), body.size());
   }
 
+  // This would simulate a delay before sending the response
+  // back to the client. Intended for testing purposes.
+  void set_delay(QuicTime::Delta delay) { delay_ = delay; }
+  QuicTime::Delta delay() const { return delay_; }
+
  private:
   std::vector<spdy::Http2HeaderBlock> early_hints_;
   SpecialResponseType response_type_;
   spdy::Http2HeaderBlock headers_;
   spdy::Http2HeaderBlock trailers_;
   std::string body_;
+  QuicTime::Delta delay_;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/tools/quic_memory_cache_backend.cc b/quiche/quic/tools/quic_memory_cache_backend.cc
index 0e50139..0fe984d 100644
--- a/quiche/quic/tools/quic_memory_cache_backend.cc
+++ b/quiche/quic/tools/quic_memory_cache_backend.cc
@@ -215,6 +215,17 @@
                   std::vector<spdy::Http2HeaderBlock>());
 }
 
+bool QuicMemoryCacheBackend::SetResponseDelay(absl::string_view host,
+                                              absl::string_view path,
+                                              QuicTime::Delta delay) {
+  QuicWriterMutexLock lock(&response_mutex_);
+  auto it = responses_.find(GetKey(host, path));
+  if (it == responses_.end()) return false;
+
+  it->second->set_delay(delay);
+  return true;
+}
+
 void QuicMemoryCacheBackend::AddResponseWithEarlyHints(
     absl::string_view host, absl::string_view path,
     spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
diff --git a/quiche/quic/tools/quic_memory_cache_backend.h b/quiche/quic/tools/quic_memory_cache_backend.h
index 7dfd372..ccdaf59 100644
--- a/quiche/quic/tools/quic_memory_cache_backend.h
+++ b/quiche/quic/tools/quic_memory_cache_backend.h
@@ -121,6 +121,12 @@
       spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
       QuicBackendResponse::SpecialResponseType response_type);
 
+  // Finds a response with the given host and path, and assign it a simulated
+  // delay. Returns true if the requisite response was found and the delay was
+  // set.
+  bool SetResponseDelay(absl::string_view host, absl::string_view path,
+                        QuicTime::Delta delay);
+
   // Sets a default response in case of cache misses.  Takes ownership of
   // 'response'.
   void AddDefaultResponse(QuicBackendResponse* response);
diff --git a/quiche/quic/tools/quic_simple_server_stream.cc b/quiche/quic/tools/quic_simple_server_stream.cc
index 1b66aac..beea65a 100644
--- a/quiche/quic/tools/quic_simple_server_stream.cc
+++ b/quiche/quic/tools/quic_simple_server_stream.cc
@@ -245,6 +245,31 @@
   return spdy_session()->peer_address().host().ToString();
 }
 
+namespace {
+
+class DelayedResponseAlarm : public QuicAlarm::DelegateWithContext {
+ public:
+  DelayedResponseAlarm(QuicSimpleServerStream* stream,
+                       const QuicBackendResponse* response)
+      : QuicAlarm::DelegateWithContext(
+            stream->spdy_session()->connection()->context()),
+        stream_(stream),
+        response_(response) {
+    stream_ = stream;
+    response_ = response;
+  }
+
+  ~DelayedResponseAlarm() override = default;
+
+  void OnAlarm() override { stream_->Respond(response_); }
+
+ private:
+  QuicSimpleServerStream* stream_;
+  const QuicBackendResponse* response_;
+};
+
+}  // namespace
+
 void QuicSimpleServerStream::OnResponseBackendComplete(
     const QuicBackendResponse* response) {
   if (response == nullptr) {
@@ -253,6 +278,19 @@
     return;
   }
 
+  auto delay = response->delay();
+  if (delay.IsZero()) {
+    Respond(response);
+    return;
+  }
+
+  auto* connection = session()->connection();
+  delayed_response_alarm_.reset(connection->alarm_factory()->CreateAlarm(
+      new DelayedResponseAlarm(this, response)));
+  delayed_response_alarm_->Set(connection->clock()->Now() + delay);
+}
+
+void QuicSimpleServerStream::Respond(const QuicBackendResponse* response) {
   // Send Early Hints first.
   for (const auto& headers : response->early_hints()) {
     QUIC_DVLOG(1) << "Stream " << id() << " sending an Early Hints response: "
diff --git a/quiche/quic/tools/quic_simple_server_stream.h b/quiche/quic/tools/quic_simple_server_stream.h
index 62c6339..3bcd9ed 100644
--- a/quiche/quic/tools/quic_simple_server_stream.h
+++ b/quiche/quic/tools/quic_simple_server_stream.h
@@ -61,6 +61,8 @@
   void SendStreamData(absl::string_view data, bool close_stream) override;
   void TerminateStreamWithError(QuicResetStreamError error) override;
 
+  void Respond(const QuicBackendResponse* response);
+
  protected:
   // Handles fresh body data whenever received when method is CONNECT.
   void HandleRequestConnectData(bool fin_received);
@@ -118,6 +120,8 @@
   // Whether response headers have already been sent.
   bool response_sent_ = false;
 
+  std::unique_ptr<QuicAlarm> delayed_response_alarm_;
+
   QuicSimpleServerBackend* quic_simple_server_backend_;  // Not owned.
 };
 
diff --git a/quiche/quic/tools/quic_simple_server_stream_test.cc b/quiche/quic/tools/quic_simple_server_stream_test.cc
index 1aa0a7f..92d06bd 100644
--- a/quiche/quic/tools/quic_simple_server_stream_test.cc
+++ b/quiche/quic/tools/quic_simple_server_stream_test.cc
@@ -15,6 +15,8 @@
 #include "quiche/quic/core/crypto/null_encrypter.h"
 #include "quiche/quic/core/http/http_encoder.h"
 #include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/core/quic_alarm_factory.h"
+#include "quiche/quic/core/quic_default_clock.h"
 #include "quiche/quic/core/quic_error_codes.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_utils.h"
@@ -29,6 +31,7 @@
 #include "quiche/quic/test_tools/quic_spdy_session_peer.h"
 #include "quiche/quic/test_tools/quic_stream_peer.h"
 #include "quiche/quic/test_tools/quic_test_utils.h"
+#include "quiche/quic/test_tools/simulator/simulator.h"
 #include "quiche/quic/tools/quic_backend_response.h"
 #include "quiche/quic/tools/quic_memory_cache_backend.h"
 #include "quiche/quic/tools/quic_simple_server_backend.h"
@@ -63,6 +66,7 @@
 
   ~TestStream() override = default;
 
+  MOCK_METHOD(void, FireAlarmMock, (), ());
   MOCK_METHOD(void, WriteHeadersMock, (bool fin), ());
   MOCK_METHOD(void, WriteEarlyHintsHeadersMock, (bool fin), ());
   MOCK_METHOD(void, WriteOrBufferBody, (absl::string_view data, bool fin),
@@ -205,7 +209,7 @@
  public:
   QuicSimpleServerStreamTest()
       : connection_(new StrictMock<MockQuicConnection>(
-            &helper_, &alarm_factory_, Perspective::IS_SERVER,
+            &simulator_, simulator_.GetAlarmFactory(), Perspective::IS_SERVER,
             SupportedVersions(GetParam()))),
         crypto_config_(new QuicCryptoServerConfig(
             QuicCryptoServerConfig::TESTING, QuicRandom::GetInstance(),
@@ -275,9 +279,9 @@
     stream_->ReplaceBackend(replacement_backend_.get());
   }
 
+  quic::simulator::Simulator simulator_;
   spdy::Http2HeaderBlock response_headers_;
   MockQuicConnectionHelper helper_;
-  MockAlarmFactory alarm_factory_;
   StrictMock<MockQuicConnection>* connection_;
   StrictMock<MockQuicSessionVisitor> session_owner_;
   StrictMock<MockQuicCryptoServerStreamHelper> session_helper_;
@@ -565,6 +569,61 @@
   EXPECT_TRUE(stream_->write_side_closed());
 }
 
+class AlarmTestDelegate : public QuicAlarm::DelegateWithoutContext {
+ public:
+  AlarmTestDelegate(TestStream* stream) : stream_(stream) {}
+
+  void OnAlarm() override { stream_->FireAlarmMock(); }
+
+ private:
+  TestStream* stream_;
+};
+
+TEST_P(QuicSimpleServerStreamTest, SendResponseWithDelay) {
+  // Add a request and response with valid headers.
+  spdy::Http2HeaderBlock* request_headers = stream_->mutable_headers();
+  std::string host = "www.google.com";
+  std::string path = "/bar";
+  (*request_headers)[":path"] = path;
+  (*request_headers)[":authority"] = host;
+  (*request_headers)[":method"] = "GET";
+
+  response_headers_[":status"] = "200";
+  response_headers_["content-length"] = "5";
+  std::string body = "Yummm";
+  QuicTime::Delta delay = QuicTime::Delta::FromMilliseconds(3000);
+
+  quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+      body.length(), quiche::SimpleBufferAllocator::Get());
+
+  memory_cache_backend_.AddResponse(host, path, std::move(response_headers_),
+                                    body);
+  auto did_delay_succeed =
+      memory_cache_backend_.SetResponseDelay(host, path, delay);
+  EXPECT_TRUE(did_delay_succeed);
+  auto did_invalid_delay_succeed =
+      memory_cache_backend_.SetResponseDelay(host, "nonsense", delay);
+  EXPECT_FALSE(did_invalid_delay_succeed);
+  std::unique_ptr<QuicAlarm> alarm(connection_->alarm_factory()->CreateAlarm(
+      new AlarmTestDelegate(stream_)));
+  alarm->Set(connection_->clock()->Now() + delay);
+  QuicStreamPeer::SetFinReceived(stream_);
+  InSequence s;
+  EXPECT_CALL(*stream_, FireAlarmMock());
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+
+  if (UsesHttp3()) {
+    EXPECT_CALL(session_, WritevData(_, header.size(), _, NO_FIN, _, _));
+  }
+  EXPECT_CALL(session_, WritevData(_, body.length(), _, FIN, _, _));
+
+  stream_->DoSendResponse();
+  simulator_.RunFor(delay);
+
+  EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_));
+  EXPECT_TRUE(stream_->write_side_closed());
+}
+
 TEST_P(QuicSimpleServerStreamTest, PushResponseOnClientInitiatedStream) {
   // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
   if (GetParam() != AllSupportedVersions()[0]) {