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