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