[clearcut uploader] Try more than 5 times and log response headers.

Also in this CL:
- Uploader takes a SteadyClockInterface and a SleeperInterface so that
these may be faked in tests.
- Added more test cases to uploader_test.
- Start actually building and running the clearcut_uploader_test
- gitfmt.py, and clang-tidy.py: Adds the ability to override some
  skipped directories so that subdirectories of skipped directories
  can be non-skipped. We apply this to third_party/clearcut.

Change-Id: Ie0c80829cb82dd1b77b440e9bd5ffcbfd9ea6ed6
diff --git a/BUILD.gn b/BUILD.gn
index 829cae1..a3481a4 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -3,8 +3,8 @@
 # found in the LICENSE file.
 
 import("//build/test.gni")
-import("$cobalt_root/metrics_registry.gni")
 import("//third_party/protobuf/proto_library.gni")
+import("$cobalt_root/metrics_registry.gni")
 
 declare_args() {
   extra_package_labels = []
@@ -33,6 +33,7 @@
   deps = [
     "keys:tests",
     "src:tests",
+    "$cobalt_root/third_party/clearcut:tests",
   ]
 }
 
diff --git a/third_party/clearcut/BUILD.gn b/third_party/clearcut/BUILD.gn
index 3630033..90189f7 100644
--- a/third_party/clearcut/BUILD.gn
+++ b/third_party/clearcut/BUILD.gn
@@ -4,6 +4,8 @@
 
 import("//third_party/protobuf/proto_library.gni")
 
+#TODO(rudominer) Move this module out of //third_party. fxb/38558.
+
 proto_library("clearcut_proto") {
   sources = [
     "clearcut.proto",
@@ -23,7 +25,30 @@
   public_deps = [
     ":clearcut_proto",
     "$cobalt_root/src:logging",
+    "$cobalt_root/src/lib/util:clock",
+    "$cobalt_root/src/lib/util:sleeper",
+    "$cobalt_root/src/lib/util:status",
     "$cobalt_root/third_party/statusor:statusor",
     "//third_party/abseil-cpp",
   ]
 }
+
+source_set("uploader_test") {
+  testonly = true
+  sources = [
+    "uploader_test.cc",
+  ]
+  configs += [ "$cobalt_root:cobalt_config" ]
+  deps = [
+    ":clearcut",
+    "//third_party/gflags",
+    "//third_party/googletest:gtest",
+  ]
+}
+
+group("tests") {
+  testonly = true
+  deps = [
+    ":uploader_test",
+  ]
+}
diff --git a/third_party/clearcut/http_client.h b/third_party/clearcut/http_client.h
index fa6b3e3..b67f0e6 100644
--- a/third_party/clearcut/http_client.h
+++ b/third_party/clearcut/http_client.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef THIRD_PARTY_CLEARCUT_HTTP_CLIENT_H_
-#define THIRD_PARTY_CLEARCUT_HTTP_CLIENT_H_
+#ifndef COBALT_THIRD_PARTY_CLEARCUT_HTTP_CLIENT_H_
+#define COBALT_THIRD_PARTY_CLEARCUT_HTTP_CLIENT_H_
 
 #include <future>
 #include <map>
@@ -24,10 +24,15 @@
   std::string response;
   Status status;
   int64_t http_code;
+  std::map<std::string, std::string> headers;
 
   HTTPResponse() {}
-  HTTPResponse(std::string response, Status status, int64_t http_code)
-      : response(std::move(response)), status(status), http_code(http_code) {}
+  HTTPResponse(std::string response, Status status, int64_t http_code,
+               std::map<std::string, std::string> headers = std::map<std::string, std::string>())
+      : response(std::move(response)),
+        status(status),
+        http_code(http_code),
+        headers(std::move(headers)) {}
 
   HTTPResponse(HTTPResponse&&) = default;
   HTTPResponse& operator=(HTTPResponse&&) = default;
@@ -65,4 +70,4 @@
 
 }  // namespace clearcut
 
-#endif  // THIRD_PARTY_CLEARCUT_HTTP_CLIENT_H_
+#endif  // COBALT_THIRD_PARTY_CLEARCUT_HTTP_CLIENT_H_
diff --git a/third_party/clearcut/uploader.cc b/third_party/clearcut/uploader.cc
index 69bfadf..f1e8a31 100644
--- a/third_party/clearcut/uploader.cc
+++ b/third_party/clearcut/uploader.cc
@@ -14,26 +14,35 @@
 
 namespace clearcut {
 
+using cobalt::util::SleeperInterface;
 using cobalt::util::Status;
 using cobalt::util::StatusCode;
+using cobalt::util::SteadyClockInterface;
 
-ClearcutUploader::ClearcutUploader(const std::string& url, std::unique_ptr<HTTPClient> client,
-                                   int64_t upload_timeout, int64_t initial_backoff_millis)
-    : url_(url),
+ClearcutUploader::ClearcutUploader(std::string url, std::unique_ptr<HTTPClient> client,
+                                   int64_t upload_timeout_millis, int64_t initial_backoff_millis,
+                                   std::unique_ptr<SteadyClockInterface> steady_clock,
+                                   std::unique_ptr<cobalt::util::SleeperInterface> sleeper)
+    : url_(std::move(url)),
       client_(std::move(client)),
-      upload_timeout_(upload_timeout),
-      initial_backoff_millis_(initial_backoff_millis),
-      pause_uploads_until_(std::chrono::steady_clock::now())  // Set this to now() so that we
-                                                              // can immediately upload.
-{}
+      upload_timeout_(std::chrono::milliseconds(upload_timeout_millis)),
+      initial_backoff_(std::chrono::milliseconds(initial_backoff_millis)),
+      steady_clock_(std::move(steady_clock)),
+      sleeper_(std::move(sleeper)),
+      pause_uploads_until_(steady_clock_->now())  // Set this to now() so that we
+                                                  // can immediately upload.
+{
+  CHECK(steady_clock_);
+  CHECK(sleeper_);
+}
 
 Status ClearcutUploader::UploadEvents(LogRequest* log_request, int32_t max_retries) {
   int32_t i = 0;
   auto deadline = std::chrono::steady_clock::time_point::max();
-  if (upload_timeout_ > 0) {
-    deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(upload_timeout_);
+  if (upload_timeout_ > std::chrono::milliseconds(0)) {
+    deadline = steady_clock_->now() + upload_timeout_;
   }
-  auto backoff = std::chrono::milliseconds(initial_backoff_millis_);
+  auto backoff = initial_backoff_;
   while (true) {
     Status response = TryUploadEvents(log_request, deadline);
     if (response.ok() || ++i == max_retries) {
@@ -49,15 +58,20 @@
       default:
         break;
     }
-    if (std::chrono::steady_clock::now() > deadline) {
+    if (steady_clock_->now() > deadline) {
       return Status(StatusCode::DEADLINE_EXCEEDED, "Deadline exceeded.");
     }
     // Exponential backoff.
-    auto time_until_pause_end = pause_uploads_until_ - std::chrono::steady_clock::now();
+    auto time_until_pause_end = pause_uploads_until_ - steady_clock_->now();
     if (time_until_pause_end > backoff) {
-      std::this_thread::sleep_for(time_until_pause_end);
+      VLOG(5) << "ClearcutUploader: Sleeping for time requested by server: "
+              << std::chrono::duration<double>(time_until_pause_end).count() << "s";
+      sleeper_->sleep_for(
+          std::chrono::duration_cast<std::chrono::milliseconds>(time_until_pause_end));
     } else {
-      std::this_thread::sleep_for(backoff);
+      VLOG(5) << "ClearcutUploader: Sleeping for backoff time: "
+              << std::chrono::duration<double>(backoff).count() << "s";
+      sleeper_->sleep_for(backoff);
     }
     backoff *= 2;
   }
@@ -65,58 +79,83 @@
 
 Status ClearcutUploader::TryUploadEvents(LogRequest* log_request,
                                          std::chrono::steady_clock::time_point deadline) {
-  if (std::chrono::steady_clock::now() < pause_uploads_until_) {
+  if (steady_clock_->now() < pause_uploads_until_) {
     return Status(StatusCode::RESOURCE_EXHAUSTED,
                   "Uploads are currently paused at the request of the "
                   "clearcut server");
   }
 
-  HTTPRequest request(url_);
   log_request->mutable_client_info()->set_client_type(kFuchsiaClientType);
-  if (!log_request->SerializeToString(&request.body)) {
-    return Status(StatusCode::INVALID_ARGUMENT,
-                  "ClearcutUploader: Unable to serialize log_request to binary proto.");
-  }
-  VLOG(5) << "ClearcutUploader: Sending POST request to " << url_ << ".";
-  auto response_future = client_->Post(std::move(request), deadline);
-  auto response_or = response_future.get();
-  if (!response_or.ok()) {
-    const Status& status = response_or.status();
-    VLOG(5) << "ClearcutUploader: Failed to send POST request: (" << status.error_code() << ") "
-            << status.error_message();
-    return status;
+  HTTPResponse response;
+  // Because we will be moving the request body into the Post() method it will not be available to
+  // us later. Here we keep an escaped copy of the request body just in case we need to use it
+  // in an error log message later.
+  std::string escaped_request_body;
+  {
+    HTTPRequest request(url_);
+    if (!log_request->SerializeToString(&request.body)) {
+      return Status(StatusCode::INVALID_ARGUMENT,
+                    "ClearcutUploader: Unable to serialize log_request to binary proto.");
+    }
+    escaped_request_body = absl::CEscape(request.body);
+    VLOG(5) << "ClearcutUploader: Sending POST request to " << url_ << ".";
+    auto response_future = client_->Post(std::move(request), deadline);
+
+    auto response_or = response_future.get();
+    if (!response_or.ok()) {
+      const Status& status = response_or.status();
+      VLOG(5) << "ClearcutUploader: Failed to send POST request: (" << status.error_code() << ") "
+              << status.error_message();
+      return status;
+    }
+
+    response = response_or.ConsumeValueOrDie();
   }
 
-  auto response = response_or.ConsumeValueOrDie();
+  constexpr auto kHttpOk = 200;
+  constexpr auto kHttpBadRequest = 400;
+  constexpr auto kHttpUnauhtorized = 401;
+  constexpr auto kHttpForbidden = 403;
+  constexpr auto kHttpFNotFound = 404;
+  constexpr auto kHttpServiceUnavailable = 503;
 
   VLOG(5) << "ClearcutUploader: Received POST response: " << response.http_code << ".";
-  if (response.http_code != 200) {
-    std::string escaped_body = absl::CEscape(request.body);
-    VLOG(1) << "ClearcutUploader: Failed POST request to " << url_
-            << " with request body=" << escaped_body << ".";
-  }
+  if (response.http_code != kHttpOk) {
+    std::ostringstream s;
+    std::string escaped_response_body = absl::CEscape(response.response);
+    s << "ClearcutUploader: Response was not OK: " << response.http_code << ".";
+    s << " url=" << url_;
+    s << " response contained " << response.headers.size() << " <headers>";
+    for (const auto& pair : response.headers) {
+      s << "<key>" << pair.first << "</key>"
+        << ":"
+        << "<value>" << pair.second << "</value>,";
+    }
+    s << "</headers>";
+    s << " request <body>" << escaped_request_body << "</body>.";
+    s << " response <body>" << escaped_response_body << "</body>.";
+    VLOG(1) << s.str();
 
-  std::ostringstream s;
-  s << response.http_code << ": ";
-  switch (response.http_code) {
-    case 200:  // success
-      break;
-    case 400:  // bad request
-      s << "Bad Request";
-      return Status(StatusCode::INVALID_ARGUMENT, s.str());
-    case 401:  // Unauthorized
-    case 403:  // forbidden
-      s << "Permission Denied";
-      return Status(StatusCode::PERMISSION_DENIED, s.str());
-    case 404:  // not found
-      s << "Not Found";
-      return Status(StatusCode::NOT_FOUND, s.str());
-    case 503:  // service unavailable
-      s << "Service Unavailable";
-      return Status(StatusCode::RESOURCE_EXHAUSTED, s.str());
-    default:
-      s << "Unknown Error Code";
-      return Status(StatusCode::UNKNOWN, s.str());
+    std::ostringstream stauts_string_stream;
+    stauts_string_stream << response.http_code << ": ";
+    switch (response.http_code) {
+      case kHttpBadRequest:  // bad request
+        stauts_string_stream << "Bad Request";
+        return Status(StatusCode::INVALID_ARGUMENT, stauts_string_stream.str());
+      case kHttpUnauhtorized:  // Unauthorized
+      case kHttpForbidden:     // forbidden
+        stauts_string_stream << "Permission Denied";
+        return Status(StatusCode::PERMISSION_DENIED, stauts_string_stream.str());
+      case kHttpFNotFound:  // not found
+        stauts_string_stream << "Not Found";
+        return Status(StatusCode::NOT_FOUND, stauts_string_stream.str());
+      case kHttpServiceUnavailable:  // service unavailable
+        stauts_string_stream << "Service Unavailable";
+        return Status(StatusCode::RESOURCE_EXHAUSTED, stauts_string_stream.str());
+      default:
+        stauts_string_stream << "Unknown Error Code";
+        return Status(StatusCode::UNKNOWN, stauts_string_stream.str());
+    }
   }
 
   LogResponse log_response;
@@ -125,8 +164,8 @@
   }
 
   if (log_response.next_request_wait_millis() >= 0) {
-    pause_uploads_until_ = std::chrono::steady_clock::now() +
-                           std::chrono::milliseconds(log_response.next_request_wait_millis());
+    pause_uploads_until_ =
+        steady_clock_->now() + std::chrono::milliseconds(log_response.next_request_wait_millis());
   }
 
   return Status::OK;
diff --git a/third_party/clearcut/uploader.h b/third_party/clearcut/uploader.h
index 028633b..8c69c4c 100644
--- a/third_party/clearcut/uploader.h
+++ b/third_party/clearcut/uploader.h
@@ -2,13 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef THIRD_PARTY_CLEARCUT_LOGGER_H_
-#define THIRD_PARTY_CLEARCUT_LOGGER_H_
+#ifndef COBALT_THIRD_PARTY_CLEARCUT_UPLOADER_H_
+#define COBALT_THIRD_PARTY_CLEARCUT_UPLOADER_H_
 
 #include <chrono>
 #include <memory>
 #include <vector>
 
+#include "src/lib/util/clock.h"
+#include "src/lib/util/sleeper.h"
 #include "src/lib/util/status.h"
 #include "third_party/abseil-cpp/absl/strings/escaping.h"
 #include "third_party/clearcut/clearcut.pb.h"
@@ -18,15 +20,35 @@
 namespace clearcut {
 
 static const int32_t kFuchsiaClientType = 17;
-static const int32_t kMaxRetries = 5;
+static const int32_t kMaxRetries = 8;  // Wait between tries: 0.25s 0.5.s 1s 2s 4s 8s 16s
+static const int64_t kInitialBackoffMillis = 250;
 
 // A ClearcutUploader sends events to clearcut using the given HTTPClient.
 //
 // Note: This class is not threadsafe.
 class ClearcutUploader {
  public:
-  ClearcutUploader(const std::string &url, std::unique_ptr<HTTPClient> client,
-                   int64_t upload_timeout = 0, int64_t initial_backoff_millis = 250);
+  // url: The URL of the Clearcut server to which POST requests should be sent.
+  //
+  // client: An implementation of HTTPClient to use.
+  //
+  // upload_timeout_millis: If a positive value is specified then this number of milliseconds
+  //  will be used as the HTTP request timeout. Otherwise no timeout will be used.
+  //
+  // initial_backoff_millis: The initial value to use for exponential backoff, in milliseconds.
+  //   This is used when the ClearcutUploader retries on failures. Optionally override the default.
+  //   Mostly useful for tests.
+  //
+  // steady_clock: Optionally replace the system steady clock. Mostly useful for tests.
+  //
+  // sleeper: Optionally replace the system sleep. Mostly useful for tests.
+  ClearcutUploader(std::string url, std::unique_ptr<HTTPClient> client,
+                   int64_t upload_timeout_millis = 0,
+                   int64_t initial_backoff_millis = kInitialBackoffMillis,
+                   std::unique_ptr<cobalt::util::SteadyClockInterface> steady_clock =
+                       std::make_unique<cobalt::util::SteadyClock>(),
+                   std::unique_ptr<cobalt::util::SleeperInterface> sleeper =
+                       std::make_unique<cobalt::util::Sleeper>());
 
   // Uploads the |log_request|  with retries.
   Status UploadEvents(LogRequest *log_request, int32_t max_retries = kMaxRetries);
@@ -37,8 +59,11 @@
 
   const std::string url_;
   const std::unique_ptr<HTTPClient> client_;
-  const int64_t upload_timeout_;
-  const int64_t initial_backoff_millis_;
+  const std::chrono::milliseconds upload_timeout_;
+  const std::chrono::milliseconds initial_backoff_;
+
+  const std::unique_ptr<cobalt::util::SteadyClockInterface> steady_clock_;
+  const std::unique_ptr<cobalt::util::SleeperInterface> sleeper_;
 
  protected:
   friend class UploaderTest;
@@ -50,4 +75,4 @@
 
 }  // namespace clearcut
 
-#endif  // THIRD_PARTY_CLEARCUT_LOGGER_H_
+#endif  // COBALT_THIRD_PARTY_CLEARCUT_UPLOADER_H_
diff --git a/third_party/clearcut/uploader_test.cc b/third_party/clearcut/uploader_test.cc
index 6f78038..ad3f086 100644
--- a/third_party/clearcut/uploader_test.cc
+++ b/third_party/clearcut/uploader_test.cc
@@ -7,42 +7,57 @@
 #include <chrono>
 #include <thread>
 
-#include "src/gtest.h"
+#include "src/lib/util/clock.h"
+#include "src/lib/util/sleeper.h"
 #include "src/logging.h"
 #include "third_party/clearcut/clearcut.pb.h"
 #include "third_party/gflags/include/gflags/gflags.h"
+#include "third_party/googletest/googletest/include/gtest/gtest.h"
 
 namespace clearcut {
 
+using cobalt::util::FakeSleeper;
+using cobalt::util::IncrementingSteadyClock;
 using cobalt::util::StatusCode;
+using std::chrono::steady_clock;
 
 namespace {
 
-static const int64_t kInitialBackoffMillis = 10;
+constexpr char kFakeClearcutURL[] = "http://test.com";
+constexpr int64_t kUploadTimeoutMillis = 31 * 1000;
+constexpr int64_t kInitialBackoffMillisForTest = 250;
+constexpr std::chrono::milliseconds kFakeClockIncrement(1);
 
-static std::set<uint32_t> seen_event_codes;
-static int next_request_wait_millis;
-static bool fail_next_request;
-
-class TestHTTPClient : public HTTPClient {
+class FakeHttpClient : public HTTPClient {
  public:
-  std::future<StatusOr<HTTPResponse>> Post(HTTPRequest request,
-                                           std::chrono::steady_clock::time_point _ignored) {
-    if (fail_next_request) {
-      fail_next_request = false;
+  std::future<StatusOr<HTTPResponse>> Post(
+      HTTPRequest request, std::chrono::steady_clock::time_point deadline) override {
+    EXPECT_EQ(request.url, kFakeClearcutURL);
+    last_deadline_seen = deadline;
+    EXPECT_EQ(0, request.headers.size());
+    if (!error_statuses_to_return.empty()) {
+      auto next_status = error_statuses_to_return.front();
+      error_statuses_to_return.pop_front();
       std::promise<StatusOr<HTTPResponse>> response_promise;
-      response_promise.set_value(Status(StatusCode::DEADLINE_EXCEEDED, "Artificial post failure"));
+      response_promise.set_value(Status(next_status, "Artificial post failure"));
       return response_promise.get_future();
     }
 
     LogRequest req;
     req.ParseFromString(request.body);
-    for (auto event : req.log_event()) {
+    for (auto &event : req.log_event()) {
       seen_event_codes.insert(event.event_code());
     }
 
     HTTPResponse response;
-    response.http_code = 200;
+    int http_response_code = 200;
+    if (!http_response_codes_to_return.empty()) {
+      http_response_code = http_response_codes_to_return.front();
+      http_response_codes_to_return.pop_front();
+    }
+    response.http_code = http_response_code;
+    response.headers["Size"] = "LARGE";
+    response.headers["Color"] = "PURPLE";
     LogResponse resp;
     if (next_request_wait_millis != -1) {
       resp.set_next_request_wait_millis(next_request_wait_millis);
@@ -54,27 +69,41 @@
 
     return response_promise.get_future();
   }
+
+  std::set<uint32_t> seen_event_codes = {};
+  int next_request_wait_millis = -1;
+  std::chrono::steady_clock::time_point last_deadline_seen;
+  std::deque<StatusCode> error_statuses_to_return;
+  std::deque<int> http_response_codes_to_return;
 };
 
 }  // namespace
 
 class UploaderTest : public ::testing::Test {
-  void SetUp() {
-    seen_event_codes = {};
-    next_request_wait_millis = -1;
-    fail_next_request = false;
-    auto unique_client = std::make_unique<TestHTTPClient>();
+ protected:
+  void DoSetUp(int64_t upload_timeout_millis) {
+    auto unique_client = std::make_unique<FakeHttpClient>();
     client = unique_client.get();
-    uploader = std::make_unique<ClearcutUploader>("http://test.com", std::move(unique_client), 0,
-                                                  kInitialBackoffMillis);
+    auto unique_clock = std::make_unique<IncrementingSteadyClock>(kFakeClockIncrement);
+    fake_clock = unique_clock.get();
+    auto unique_sleeper = std::make_unique<FakeSleeper>();
+    fake_sleeper = unique_sleeper.get();
+    fake_sleeper->set_incrementing_steady_clock(fake_clock);
+    uploader = std::make_unique<ClearcutUploader>(
+        kFakeClearcutURL, std::move(unique_client), upload_timeout_millis,
+        kInitialBackoffMillisForTest, std::move(unique_clock), std::move(unique_sleeper));
   }
 
+  void SetUp() override { DoSetUp(kUploadTimeoutMillis); }
+
  public:
   Status UploadClearcutDemoEvent(uint32_t event_code, int32_t max_retries = 1) {
     LogRequest request;
+    constexpr int32_t kClearcutDemoSource = 12345;
     request.set_log_source(kClearcutDemoSource);
     request.add_log_event()->set_event_code(event_code);
-    return uploader->UploadEvents(&request, max_retries);
+    auto status = uploader->UploadEvents(&request, max_retries);
+    return status;
   }
 
   std::chrono::steady_clock::time_point get_pause_uploads_until() {
@@ -82,11 +111,13 @@
   }
 
   bool SawEventCode(uint32_t event_code) {
-    return seen_event_codes.find(event_code) != seen_event_codes.end();
+    return client->seen_event_codes.find(event_code) != client->seen_event_codes.end();
   }
 
   std::unique_ptr<ClearcutUploader> uploader;
-  TestHTTPClient *client;
+  FakeHttpClient *client;
+  IncrementingSteadyClock *fake_clock;
+  FakeSleeper *fake_sleeper;
 };
 
 TEST_F(UploaderTest, BasicClearcutDemoUpload) {
@@ -101,44 +132,149 @@
   ASSERT_TRUE(SawEventCode(4));
 }
 
+// Tests that the correct value for the timeout is passed to the HttpClient.
+TEST_F(UploaderTest, CorrectDeadlineUsed) {
+  auto expected_deadline =
+      fake_clock->peek_later() + std::chrono::milliseconds(kUploadTimeoutMillis);
+  ASSERT_TRUE(UploadClearcutDemoEvent(1).ok());
+  EXPECT_EQ(client->last_deadline_seen, expected_deadline);
+
+  // Advance time by 10 minutes.
+  fake_clock->increment_by(std::chrono::minutes(10));
+  expected_deadline = fake_clock->peek_later() + std::chrono::milliseconds(kUploadTimeoutMillis);
+  ASSERT_TRUE(UploadClearcutDemoEvent(2).ok());
+  EXPECT_EQ(client->last_deadline_seen, expected_deadline);
+}
+
+// Tests the functionality of obeying a request from the server to rate-limit.
 TEST_F(UploaderTest, RateLimitingWorks) {
-  next_request_wait_millis = 10;
+  // Have the fake HTTP client return a response to the Uploader telling it that the server
+  // is asking it to wait 10ms.
+  client->next_request_wait_millis = 10;
+  fake_clock->set_time(std::chrono::steady_clock::time_point(std::chrono::milliseconds(0)));
   ASSERT_TRUE(UploadClearcutDemoEvent(100).ok());
   ASSERT_TRUE(SawEventCode(100));
 
-  int code = 150;
-  // Repeatedly try to upload until we pass the uploader's
-  // "pause_uploads_until_" field.
-  while (std::chrono::steady_clock::now() < get_pause_uploads_until()) {
-    ASSERT_FALSE(UploadClearcutDemoEvent(code).ok());
+  // Haven't waited long enough. Expect the Uploader to return an error.
+  fake_clock->set_time(std::chrono::steady_clock::time_point(std::chrono::milliseconds(8)));
+  ASSERT_EQ(UploadClearcutDemoEvent(150).error_code(), StatusCode::RESOURCE_EXHAUSTED);
+  ASSERT_FALSE(SawEventCode(150));
 
-    // We haven't waited long enough yet.
-    ASSERT_FALSE(SawEventCode(code));
+  // Haven't waited long enough. Expect the Uploader to return an error.
+  fake_clock->set_time(std::chrono::steady_clock::time_point(std::chrono::milliseconds(9)));
+  ASSERT_EQ(UploadClearcutDemoEvent(150).error_code(), StatusCode::RESOURCE_EXHAUSTED);
+  ASSERT_FALSE(SawEventCode(150));
 
-    code++;
-
-    std::this_thread::sleep_for(std::chrono::milliseconds(1));
-  }
-
-  // Assert that we've made at least 1 rate limit verification pass.
-  ASSERT_GT(code, 150);
-
-  // Now that the pause has expired, we should be able to upload.
-  ASSERT_TRUE(UploadClearcutDemoEvent(code).ok());
-
-  // Now it should work.
-  ASSERT_TRUE(SawEventCode(code));
+  // Now we have waited long enough.
+  fake_clock->set_time(std::chrono::steady_clock::time_point(std::chrono::milliseconds(12)));
+  ASSERT_TRUE(UploadClearcutDemoEvent(150).ok());
+  ASSERT_TRUE(SawEventCode(150));
 }
 
+// Tests the functionality of retrying multiple times with exponential backoff.
 TEST_F(UploaderTest, ShouldRetryOnFailedUpload) {
-  fail_next_request = true;
-  ASSERT_TRUE(UploadClearcutDemoEvent(1, 2).ok());
-  ASSERT_TRUE(SawEventCode(1));
+  // Arrange for the upload to fail three times.
+  client->error_statuses_to_return = {StatusCode::DEADLINE_EXCEEDED, StatusCode::RESOURCE_EXHAUSTED,
+                                      StatusCode::DEADLINE_EXCEEDED};
+  // Try to upload three times. Expect this to fail and return the third status code.
+  fake_sleeper->Reset();
+  EXPECT_EQ(UploadClearcutDemoEvent(101, 3).error_code(), StatusCode::DEADLINE_EXCEEDED);
+  EXPECT_FALSE(SawEventCode(101));
+  // There should have been two sleeps.
+  EXPECT_EQ(fake_sleeper->last_sleep_duration().count(), kInitialBackoffMillisForTest * 2);
 
-  fail_next_request = true;
-  ASSERT_FALSE(UploadClearcutDemoEvent(2).ok());
-  ASSERT_TRUE(UploadClearcutDemoEvent(3).ok());
-  ASSERT_TRUE(SawEventCode(3));
+  // Arrange for the upload to fail three times.
+  client->error_statuses_to_return = {StatusCode::DEADLINE_EXCEEDED, StatusCode::RESOURCE_EXHAUSTED,
+                                      StatusCode::DEADLINE_EXCEEDED};
+  // Try to upload four times. It should succeed the fourth time.
+  fake_sleeper->Reset();
+  ASSERT_TRUE(UploadClearcutDemoEvent(102, 4).ok());
+  ASSERT_TRUE(SawEventCode(102));
+  // There should have been three sleeps.
+  EXPECT_EQ(fake_sleeper->last_sleep_duration().count(), kInitialBackoffMillisForTest * 4);
+
+  // Arrange for the upload to fail four times.
+  client->error_statuses_to_return = {StatusCode::DEADLINE_EXCEEDED, StatusCode::RESOURCE_EXHAUSTED,
+                                      StatusCode::DEADLINE_EXCEEDED,
+                                      StatusCode::RESOURCE_EXHAUSTED};
+  // Try to upload four times. Expect this to fail and return the fourth status code.
+  fake_sleeper->Reset();
+  EXPECT_EQ(UploadClearcutDemoEvent(103, 4).error_code(), StatusCode::RESOURCE_EXHAUSTED);
+  ASSERT_FALSE(SawEventCode(103));
+  // There should have be three sleeps.
+  EXPECT_EQ(fake_sleeper->last_sleep_duration().count(), kInitialBackoffMillisForTest * 4);
+
+  // Arrange for the upload to fail four times.
+  client->error_statuses_to_return = {StatusCode::DEADLINE_EXCEEDED, StatusCode::RESOURCE_EXHAUSTED,
+                                      StatusCode::DEADLINE_EXCEEDED,
+                                      StatusCode::RESOURCE_EXHAUSTED};
+  // Try to upload five times. Expect this to succeed the fifth time.
+  fake_sleeper->Reset();
+  EXPECT_TRUE(UploadClearcutDemoEvent(104, 5).ok());
+  ASSERT_TRUE(SawEventCode(104));
+  // There should have be 4 sleeps.
+  EXPECT_EQ(fake_sleeper->last_sleep_duration().count(), kInitialBackoffMillisForTest * 8);
+
+  // Arrange for the upload to fail three times but return a non-retryable error the first time.
+  client->error_statuses_to_return = {StatusCode::INVALID_ARGUMENT, StatusCode::NOT_FOUND,
+                                      StatusCode::PERMISSION_DENIED};
+  // Set up to try four times but we will in fact only try once because the first time
+  // we return a non-retryable error.
+  fake_sleeper->Reset();
+  ASSERT_EQ(UploadClearcutDemoEvent(105, 4).error_code(), StatusCode::INVALID_ARGUMENT);
+  ASSERT_FALSE(SawEventCode(105));
+  EXPECT_EQ(fake_sleeper->last_sleep_duration().count(), 0);
+}
+
+// Tests that we will stop retrying when the timeout is exceeded.
+TEST_F(UploaderTest, ExceedsTimeout) {
+  // Do the setup again with a short timeout of 2s.
+  DoSetUp(2000);
+  // Arrange for the upload to fail seven times.
+  client->error_statuses_to_return = {
+      StatusCode::RESOURCE_EXHAUSTED, StatusCode::RESOURCE_EXHAUSTED,
+      StatusCode::RESOURCE_EXHAUSTED, StatusCode::RESOURCE_EXHAUSTED,
+      StatusCode::RESOURCE_EXHAUSTED, StatusCode::RESOURCE_EXHAUSTED,
+      StatusCode::RESOURCE_EXHAUSTED};
+  fake_sleeper->Reset();
+  // Arrange to try to upload ten times. This will fail after only five attempts because
+  // the two-second deadline will have been exceeded. The returned error will be
+  // DEADLINE_EXCEEDED.
+  EXPECT_EQ(UploadClearcutDemoEvent(101, 10).error_code(), StatusCode::DEADLINE_EXCEEDED);
+  EXPECT_FALSE(SawEventCode(101));
+  // There should have be only 4 sleeps because after five upload attempts the deadline
+  // will have been exceeded.
+  EXPECT_EQ(fake_sleeper->last_sleep_duration().count(), kInitialBackoffMillisForTest * 8);
+}
+
+// Tests the logic for handling non-200 HTTP response codes.
+TEST_F(UploaderTest, HttpResponseCodes) {
+  std::vector<std::pair<int, StatusCode>> codes = {
+      {400, StatusCode::INVALID_ARGUMENT},   {401, StatusCode::PERMISSION_DENIED},
+      {403, StatusCode::PERMISSION_DENIED},  {404, StatusCode::NOT_FOUND},
+      {503, StatusCode::RESOURCE_EXHAUSTED}, {500, StatusCode::UNKNOWN}};
+  for (const auto &code : codes) {
+    client->http_response_codes_to_return = {code.first};
+    EXPECT_EQ(UploadClearcutDemoEvent(1, 1).error_code(), code.second);
+  }
+  // Arrange for the upload to fail three times.
+  client->http_response_codes_to_return = {500, 500, 503};
+  // Try to upload three times. Expect this to fail and return the third status code.
+  fake_sleeper->Reset();
+  EXPECT_EQ(UploadClearcutDemoEvent(101, 3).error_code(), StatusCode::RESOURCE_EXHAUSTED);
+  // The server returned an error but still saw the request.
+  EXPECT_TRUE(SawEventCode(101));
+  // There should have been two sleeps.
+  EXPECT_EQ(fake_sleeper->last_sleep_duration().count(), kInitialBackoffMillisForTest * 2);
+
+  // Arrange for the upload to fail three times.
+  client->http_response_codes_to_return = {503, 503, 503};
+  // Try to upload four times. It should succeed the fourth time.
+  fake_sleeper->Reset();
+  ASSERT_TRUE(UploadClearcutDemoEvent(102, 4).ok());
+  ASSERT_TRUE(SawEventCode(102));
+  // There should have been three sleeps.
+  EXPECT_EQ(fake_sleeper->last_sleep_duration().count(), kInitialBackoffMillisForTest * 4);
 }
 
 }  // namespace clearcut
diff --git a/tools/clang_tidy.py b/tools/clang_tidy.py
index fa2acde..0044779 100755
--- a/tools/clang_tidy.py
+++ b/tools/clang_tidy.py
@@ -32,6 +32,14 @@
                  'source_generator', 'source_generator_test_files'),
 ]
 
+# A list of directory prefixes that extend directory prefixes from SKIP_LINT_DIRS
+# but that should not be skipped.
+# TODO(rudominer) Can eliminate third_party/clearcut below when
+# fxb/38558 is resolved.
+OVERRIDE_SKIP_LINT_DIRS = [
+    os.path.join(SRC_ROOT_DIR, 'third_party', 'clearcut')
+]
+
 TEST_FILE_REGEX = re.compile('.*_(unit)?tests?.cc$')
 TEST_FILE_CLANG_TIDY_CHECKS = [
     '-readability-magic-numbers',
@@ -39,6 +47,21 @@
 ]
 
 
+# Given a directory's parent path and name, returns a boolean indicating whether
+# or not the directory should be skipped for liniting.
+def should_skip_dir(parent_path, name):
+  if name.startswith('.'):
+    return True
+  full_path = os.path.join(parent_path, name)
+  for p in OVERRIDE_SKIP_LINT_DIRS:
+    if p.startswith(full_path):
+      return False
+  for p in SKIP_LINT_DIRS:
+    if full_path.startswith(p):
+      return True
+  return False
+
+
 def main(only_directories=[]):
   status = 0
 
@@ -62,10 +85,7 @@
           clang_tidy_files.append(full_path)
 
     # Before recursing into directories remove the ones we want to skip.
-    dirs_to_skip = [
-        dir for dir in dirs
-        if dir.startswith('.') or os.path.join(root, dir) in SKIP_LINT_DIRS
-    ]
+    dirs_to_skip = [dir for dir in dirs if should_skip_dir(root, dir)]
     for d in dirs_to_skip:
       dirs.remove(d)
 
@@ -74,26 +94,29 @@
       if f.endswith('.h') and not os.path.islink(f)
   ]
   print('Running check-header-guards.py on %d files' % len(header_files))
-  try:
-    subprocess.check_call([CHECK_HEADER_GUARDS] + header_files)
-  except:
-    status += 1
+  if len(header_files) > 0:
+    try:
+      subprocess.check_call([CHECK_HEADER_GUARDS] + header_files)
+    except:
+      status += 1
 
   clang_tidy_command = [CLANG_TIDY, '-quiet', '-p', OUT_DIR]
   print('Running clang-tidy on %d source files' % len(clang_tidy_files))
-  try:
-    subprocess.check_call(clang_tidy_command + clang_tidy_files)
-  except:
-    status += 1
+  if len(clang_tidy_files) > 0:
+    try:
+      subprocess.check_call(clang_tidy_command + clang_tidy_files)
+    except:
+      status += 1
 
   print('Running clang-tidy on %d test files' % len(clang_tidy_test_files))
-  try:
-    subprocess.check_call(
-        clang_tidy_command +
-        ['-checks=%s' % ','.join(TEST_FILE_CLANG_TIDY_CHECKS)] +
-        clang_tidy_test_files)
-  except:
-    status += 1
+  if len(clang_tidy_test_files) > 0:
+    try:
+      subprocess.check_call(
+          clang_tidy_command +
+          ['-checks=%s' % ','.join(TEST_FILE_CLANG_TIDY_CHECKS)] +
+          clang_tidy_test_files)
+    except:
+      status += 1
 
   return status
 
diff --git a/tools/gitfmt.py b/tools/gitfmt.py
index 1f84f25..98f6609 100755
--- a/tools/gitfmt.py
+++ b/tools/gitfmt.py
@@ -186,8 +186,17 @@
     "src/bin/config_parser/src/source_generator/source_generator_test_files",
 ]
 
+# A list of file prefixes that extend file prefixes from IGNORED_FILES
+# but that should not be ignored.
+# TODO(rudominer) Can eliminate third_party/clearcut below when
+# fxb/38558 is resolved.
+OVERRIDE_IGNORED_FILES = ["third_party/clearcut"]
+
 
 def _ignored_file(f):
+  for name in OVERRIDE_IGNORED_FILES:
+    if f.startswith(name):
+      return False
   for name in IGNORED_FILES:
     if f.startswith(name):
       return True
@@ -230,7 +239,7 @@
     session = FormattingSession(repo_path)
     session.format_changed(changes)
   finally:
-    os.chdir(repo_path)
+    os.chdir(cwd)
 
 
 if __name__ == "__main__":