• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/test/scoped_run_loop_timeout.h"
6 
7 #include "base/functional/bind.h"
8 #include "base/functional/callback_helpers.h"
9 #include "base/location.h"
10 #include "base/logging.h"
11 #include "base/strings/strcat.h"
12 #include "base/time/time.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14 #include "third_party/abseil-cpp/absl/types/optional.h"
15 
16 namespace base::test {
17 
18 namespace {
19 
20 bool g_add_gtest_failure_on_timeout = false;
21 
22 std::unique_ptr<ScopedRunLoopTimeout::TimeoutCallback>
23     g_handle_timeout_for_testing = nullptr;
24 
TimeoutMessage(const RepeatingCallback<std::string ()> & get_log,const Location & timeout_enabled_from_here)25 std::string TimeoutMessage(const RepeatingCallback<std::string()>& get_log,
26                            const Location& timeout_enabled_from_here) {
27   std::string message = "RunLoop::Run() timed out. Timeout set at ";
28   message += timeout_enabled_from_here.ToString() + ".";
29   if (get_log)
30     StrAppend(&message, {"\n", get_log.Run()});
31   return message;
32 }
33 
StandardTimeoutCallback(const Location & timeout_enabled_from_here,RepeatingCallback<std::string ()> on_timeout_log,const Location & run_from_here)34 void StandardTimeoutCallback(const Location& timeout_enabled_from_here,
35                              RepeatingCallback<std::string()> on_timeout_log,
36                              const Location& run_from_here) {
37   const std::string message =
38       TimeoutMessage(on_timeout_log, timeout_enabled_from_here);
39   logging::LogMessage(run_from_here.file_name(), run_from_here.line_number(),
40                       message.data());
41 }
42 
TimeoutCallbackWithGtestFailure(const Location & timeout_enabled_from_here,RepeatingCallback<std::string ()> on_timeout_log,const Location & run_from_here)43 void TimeoutCallbackWithGtestFailure(
44     const Location& timeout_enabled_from_here,
45     RepeatingCallback<std::string()> on_timeout_log,
46     const Location& run_from_here) {
47   // Add a non-fatal failure to GTest result and cause the test to fail.
48   // A non-fatal failure is preferred over a fatal one because LUCI Analysis
49   // will select the fatal failure over the non-fatal one as the primary error
50   // message for the test. The RunLoop::Run() function is generally called by
51   // the test framework and generates similar error messages and stack traces,
52   // making it difficult to cluster the failures. Making the failure non-fatal
53   // will propagate the ASSERT fatal failures in the test body as the primary
54   // error message.
55   // Also note that, the GTest fatal failure will not actually stop the test
56   // execution if not directly used in the test body. A non-fatal/fatal failure
57   // here makes no difference to the test running flow.
58   ADD_FAILURE_AT(run_from_here.file_name(), run_from_here.line_number())
59       << TimeoutMessage(on_timeout_log, timeout_enabled_from_here);
60 }
61 
62 }  // namespace
63 
ScopedRunLoopTimeout(const Location & from_here,TimeDelta timeout)64 ScopedRunLoopTimeout::ScopedRunLoopTimeout(const Location& from_here,
65                                            TimeDelta timeout)
66     : ScopedRunLoopTimeout(from_here, timeout, NullCallback()) {}
67 
~ScopedRunLoopTimeout()68 ScopedRunLoopTimeout::~ScopedRunLoopTimeout() {
69   // Out-of-order destruction could result in UAF.
70   CHECK_EQ(&run_timeout_, RunLoop::GetTimeoutForCurrentThread());
71   RunLoop::SetTimeoutForCurrentThread(nested_timeout_);
72 }
73 
ScopedRunLoopTimeout(const Location & timeout_enabled_from_here,absl::optional<TimeDelta> timeout,RepeatingCallback<std::string ()> on_timeout_log)74 ScopedRunLoopTimeout::ScopedRunLoopTimeout(
75     const Location& timeout_enabled_from_here,
76     absl::optional<TimeDelta> timeout,
77     RepeatingCallback<std::string()> on_timeout_log)
78     : nested_timeout_(RunLoop::GetTimeoutForCurrentThread()) {
79   CHECK(timeout.has_value() || nested_timeout_)
80       << "Cannot use default timeout if no default is set.";
81   // We can't use value_or() here because it gets the value in parentheses no
82   // matter timeout has a value or not, causing null pointer dereference if
83   // nested_timeout_ is nullptr.
84   run_timeout_.timeout =
85       timeout.has_value() ? timeout.value() : nested_timeout_->timeout;
86   CHECK_GT(run_timeout_.timeout, TimeDelta());
87 
88   run_timeout_.on_timeout =
89       BindRepeating(GetTimeoutCallback(), timeout_enabled_from_here,
90                     std::move(on_timeout_log));
91 
92   RunLoop::SetTimeoutForCurrentThread(&run_timeout_);
93 }
94 
95 ScopedRunLoopTimeout::TimeoutCallback
GetTimeoutCallback()96 ScopedRunLoopTimeout::GetTimeoutCallback() {
97   // In case both g_handle_timeout_for_testing and
98   // g_add_gtest_failure_on_timeout are set, we chain the callbacks so that they
99   // both get called eventually. This avoids confusion on what exactly is
100   // happening, especially for tests that are not controlling the call to
101   // `SetAddGTestFailureOnTimeout` directly.
102   if (g_handle_timeout_for_testing) {
103     if (g_add_gtest_failure_on_timeout) {
104       return ForwardRepeatingCallbacks(
105           {BindRepeating(&TimeoutCallbackWithGtestFailure),
106            *g_handle_timeout_for_testing});
107     }
108     return *g_handle_timeout_for_testing;
109   } else if (g_add_gtest_failure_on_timeout) {
110     return BindRepeating(&TimeoutCallbackWithGtestFailure);
111   } else {
112     return BindRepeating(&StandardTimeoutCallback);
113   }
114 }
115 
116 // static
ExistsForCurrentThread()117 bool ScopedRunLoopTimeout::ExistsForCurrentThread() {
118   return RunLoop::GetTimeoutForCurrentThread() != nullptr;
119 }
120 
121 // static
SetAddGTestFailureOnTimeout()122 void ScopedRunLoopTimeout::SetAddGTestFailureOnTimeout() {
123   g_add_gtest_failure_on_timeout = true;
124 }
125 
126 // static
127 const RunLoop::RunLoopTimeout*
GetTimeoutForCurrentThread()128 ScopedRunLoopTimeout::GetTimeoutForCurrentThread() {
129   return RunLoop::GetTimeoutForCurrentThread();
130 }
131 
132 // static
SetTimeoutCallbackForTesting(std::unique_ptr<ScopedRunLoopTimeout::TimeoutCallback> cb)133 void ScopedRunLoopTimeout::SetTimeoutCallbackForTesting(
134     std::unique_ptr<ScopedRunLoopTimeout::TimeoutCallback> cb) {
135   g_handle_timeout_for_testing = std::move(cb);
136 }
137 
ScopedDisableRunLoopTimeout()138 ScopedDisableRunLoopTimeout::ScopedDisableRunLoopTimeout()
139     : nested_timeout_(RunLoop::GetTimeoutForCurrentThread()) {
140   RunLoop::SetTimeoutForCurrentThread(nullptr);
141 }
142 
~ScopedDisableRunLoopTimeout()143 ScopedDisableRunLoopTimeout::~ScopedDisableRunLoopTimeout() {
144   // Out-of-order destruction could result in UAF.
145   CHECK_EQ(nullptr, RunLoop::GetTimeoutForCurrentThread());
146   RunLoop::SetTimeoutForCurrentThread(nested_timeout_);
147 }
148 
149 }  // namespace base::test
150