xref: /aosp_15_r20/external/cronet/base/test/scoped_run_loop_timeout.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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