1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150

base / test / scoped_run_loop_timeout.cc [blame]

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/test/scoped_run_loop_timeout.h"

#include <optional>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base::test {

namespace {

bool g_add_gtest_failure_on_timeout = false;

std::unique_ptr<ScopedRunLoopTimeout::TimeoutCallback>
    g_handle_timeout_for_testing = nullptr;

std::string TimeoutMessage(const RepeatingCallback<std::string()>& get_log,
                           const Location& timeout_enabled_from_here) {
  std::string message = "RunLoop::Run() timed out. Timeout set at ";
  message += timeout_enabled_from_here.ToString() + ".";
  if (get_log)
    StrAppend(&message, {"\n", get_log.Run()});
  return message;
}

void StandardTimeoutCallback(const Location& timeout_enabled_from_here,
                             RepeatingCallback<std::string()> on_timeout_log,
                             const Location& run_from_here) {
  const std::string message =
      TimeoutMessage(on_timeout_log, timeout_enabled_from_here);
  logging::LogMessage(run_from_here.file_name(), run_from_here.line_number(),
                      message.data());
}

void TimeoutCallbackWithGtestFailure(
    const Location& timeout_enabled_from_here,
    RepeatingCallback<std::string()> on_timeout_log,
    const Location& run_from_here) {
  // Add a non-fatal failure to GTest result and cause the test to fail.
  // A non-fatal failure is preferred over a fatal one because LUCI Analysis
  // will select the fatal failure over the non-fatal one as the primary error
  // message for the test. The RunLoop::Run() function is generally called by
  // the test framework and generates similar error messages and stack traces,
  // making it difficult to cluster the failures. Making the failure non-fatal
  // will propagate the ASSERT fatal failures in the test body as the primary
  // error message.
  // Also note that, the GTest fatal failure will not actually stop the test
  // execution if not directly used in the test body. A non-fatal/fatal failure
  // here makes no difference to the test running flow.
  ADD_FAILURE_AT(run_from_here.file_name(), run_from_here.line_number())
      << TimeoutMessage(on_timeout_log, timeout_enabled_from_here);
}

}  // namespace

ScopedRunLoopTimeout::ScopedRunLoopTimeout(const Location& from_here,
                                           TimeDelta timeout)
    : ScopedRunLoopTimeout(from_here, timeout, NullCallback()) {}

ScopedRunLoopTimeout::~ScopedRunLoopTimeout() {
  // Out-of-order destruction could result in UAF.
  CHECK_EQ(&run_timeout_, RunLoop::GetTimeoutForCurrentThread());
  RunLoop::SetTimeoutForCurrentThread(nested_timeout_);
}

ScopedRunLoopTimeout::ScopedRunLoopTimeout(
    const Location& timeout_enabled_from_here,
    std::optional<TimeDelta> timeout,
    RepeatingCallback<std::string()> on_timeout_log)
    : nested_timeout_(RunLoop::GetTimeoutForCurrentThread()) {
  CHECK(timeout.has_value() || nested_timeout_)
      << "Cannot use default timeout if no default is set.";
  // We can't use value_or() here because it gets the value in parentheses no
  // matter timeout has a value or not, causing null pointer dereference if
  // nested_timeout_ is nullptr.
  run_timeout_.timeout =
      timeout.has_value() ? timeout.value() : nested_timeout_->timeout;
  CHECK_GT(run_timeout_.timeout, TimeDelta());

  run_timeout_.on_timeout =
      BindRepeating(GetTimeoutCallback(), timeout_enabled_from_here,
                    std::move(on_timeout_log));

  RunLoop::SetTimeoutForCurrentThread(&run_timeout_);
}

ScopedRunLoopTimeout::TimeoutCallback
ScopedRunLoopTimeout::GetTimeoutCallback() {
  // In case both g_handle_timeout_for_testing and
  // g_add_gtest_failure_on_timeout are set, we chain the callbacks so that they
  // both get called eventually. This avoids confusion on what exactly is
  // happening, especially for tests that are not controlling the call to
  // `SetAddGTestFailureOnTimeout` directly.
  if (g_handle_timeout_for_testing) {
    if (g_add_gtest_failure_on_timeout) {
      return ForwardRepeatingCallbacks(
          {BindRepeating(&TimeoutCallbackWithGtestFailure),
           *g_handle_timeout_for_testing});
    }
    return *g_handle_timeout_for_testing;
  } else if (g_add_gtest_failure_on_timeout) {
    return BindRepeating(&TimeoutCallbackWithGtestFailure);
  } else {
    return BindRepeating(&StandardTimeoutCallback);
  }
}

// static
bool ScopedRunLoopTimeout::ExistsForCurrentThread() {
  return RunLoop::GetTimeoutForCurrentThread() != nullptr;
}

// static
void ScopedRunLoopTimeout::SetAddGTestFailureOnTimeout() {
  g_add_gtest_failure_on_timeout = true;
}

// static
const RunLoop::RunLoopTimeout*
ScopedRunLoopTimeout::GetTimeoutForCurrentThread() {
  return RunLoop::GetTimeoutForCurrentThread();
}

// static
void ScopedRunLoopTimeout::SetTimeoutCallbackForTesting(
    std::unique_ptr<ScopedRunLoopTimeout::TimeoutCallback> cb) {
  g_handle_timeout_for_testing = std::move(cb);
}

ScopedDisableRunLoopTimeout::ScopedDisableRunLoopTimeout()
    : nested_timeout_(RunLoop::GetTimeoutForCurrentThread()) {
  RunLoop::SetTimeoutForCurrentThread(nullptr);
}

ScopedDisableRunLoopTimeout::~ScopedDisableRunLoopTimeout() {
  // Out-of-order destruction could result in UAF.
  CHECK_EQ(nullptr, RunLoop::GetTimeoutForCurrentThread());
  RunLoop::SetTimeoutForCurrentThread(nested_timeout_);
}

}  // namespace base::test