diff --git a/testing/BUILD.gn b/testing/BUILD.gn index e7c968caab3b9..04a563494209c 100644 --- a/testing/BUILD.gn +++ b/testing/BUILD.gn @@ -27,6 +27,8 @@ source_set("testing") { testonly = true sources = [ + "debugger_detection.cc", + "debugger_detection.h", "run_all_unittests.cc", "test_timeout_listener.cc", "test_timeout_listener.h", diff --git a/testing/debugger_detection.cc b/testing/debugger_detection.cc new file mode 100644 index 0000000000000..2e8c93dec9049 --- /dev/null +++ b/testing/debugger_detection.cc @@ -0,0 +1,71 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/testing/debugger_detection.h" + +#include "flutter/fml/build_config.h" +#include "flutter/fml/logging.h" + +#if OS_MACOSX + +#include +#include +#include +#include +#include + +#endif // OS_MACOSX + +#if OS_WIN + +#include + +#endif // OS_WIN + +namespace flutter { +namespace testing { + +DebuggerStatus GetDebuggerStatus() { +#if OS_MACOSX + // From Technical Q&A QA1361 Detecting the Debugger + // https://developer.apple.com/library/archive/qa/qa1361/_index.html + int management_info_base[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + info.kp_proc.p_flag = 0; + + // Initialize management_info_base, which tells sysctl the info we want, in + // this case we're looking for information about a specific process ID. + management_info_base[0] = CTL_KERN; + management_info_base[1] = KERN_PROC; + management_info_base[2] = KERN_PROC_PID; + management_info_base[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + auto status = + ::sysctl(management_info_base, + sizeof(management_info_base) / sizeof(*management_info_base), + &info, &size, NULL, 0); + FML_CHECK(status == 0); + + // We're being debugged if the P_TRACED flag is set. + return ((info.kp_proc.p_flag & P_TRACED) != 0) ? DebuggerStatus::kAttached + : DebuggerStatus::kDontKnow; + +#elif OS_WIN + return ::IsDebuggerPresent() ? DebuggerStatus::kAttached + : DebuggerStatus::kDontKnow; + +#else + return DebuggerStatus::kDontKnow; +#endif +} // namespace testing + +} // namespace testing +} // namespace flutter diff --git a/testing/debugger_detection.h b/testing/debugger_detection.h new file mode 100644 index 0000000000000..7745cfb319aa5 --- /dev/null +++ b/testing/debugger_detection.h @@ -0,0 +1,23 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_TESTING_DEBUGGER_DETECTION_H_ +#define FLUTTER_TESTING_DEBUGGER_DETECTION_H_ + +#include "flutter/fml/macros.h" + +namespace flutter { +namespace testing { + +enum class DebuggerStatus { + kDontKnow, + kAttached, +}; + +DebuggerStatus GetDebuggerStatus(); + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_TESTING_DEBUGGER_DETECTION_H_ diff --git a/testing/run_all_unittests.cc b/testing/run_all_unittests.cc index 27af1c56ad249..8e579246eb639 100644 --- a/testing/run_all_unittests.cc +++ b/testing/run_all_unittests.cc @@ -2,7 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include +#include +#include + #include "flutter/fml/build_config.h" +#include "flutter/fml/command_line.h" +#include "flutter/testing/debugger_detection.h" #include "flutter/testing/test_timeout_listener.h" #include "gtest/gtest.h" @@ -10,6 +16,24 @@ #include #endif // OS_IOS +std::optional GetTestTimeoutFromArgs(int argc, char** argv) { + const auto command_line = fml::CommandLineFromArgcArgv(argc, argv); + + std::string timeout_seconds; + if (!command_line.GetOptionValue("timeout", &timeout_seconds)) { + // No timeout specified. Default to 30s. + return fml::TimeDelta::FromSeconds(30u); + } + + const auto seconds = std::stoi(timeout_seconds); + + if (seconds < 1) { + return std::nullopt; + } + + return fml::TimeDelta::FromSeconds(seconds); +} + int main(int argc, char** argv) { #ifdef OS_IOS asl_log_descriptor(NULL, NULL, ASL_LEVEL_NOTICE, STDOUT_FILENO, @@ -19,7 +43,23 @@ int main(int argc, char** argv) { #endif // OS_IOS ::testing::InitGoogleTest(&argc, argv); - auto timeout_listener = new flutter::testing::TestTimeoutListener(); + + // Check if the user has specified a timeout. + const auto timeout = GetTestTimeoutFromArgs(argc, argv); + if (!timeout.has_value()) { + FML_LOG(INFO) << "Timeouts disabled via a command line flag."; + return RUN_ALL_TESTS(); + } + + // Check if the user is debugging the process. + if (flutter::testing::GetDebuggerStatus() == + flutter::testing::DebuggerStatus::kAttached) { + FML_LOG(INFO) << "Debugger is attached. Suspending test timeouts."; + return RUN_ALL_TESTS(); + } + + auto timeout_listener = + new flutter::testing::TestTimeoutListener(timeout.value()); auto& listeners = ::testing::UnitTest::GetInstance()->listeners(); listeners.Append(timeout_listener); auto result = RUN_ALL_TESTS(); diff --git a/testing/test_timeout_listener.cc b/testing/test_timeout_listener.cc index 801ca0c3f708e..e4604cee0fa84 100644 --- a/testing/test_timeout_listener.cc +++ b/testing/test_timeout_listener.cc @@ -72,7 +72,10 @@ TestTimeoutListener::TestTimeoutListener(fml::TimeDelta timeout) : timeout_(timeout), listener_thread_("test_timeout_listener"), listener_thread_runner_(listener_thread_.GetTaskRunner()), - pending_tests_(PendingTests::Create(listener_thread_runner_, timeout_)) {} + pending_tests_(PendingTests::Create(listener_thread_runner_, timeout_)) { + FML_LOG(INFO) << "Test timeout of " << timeout_.ToSeconds() + << " seconds per test case will be enforced."; +} TestTimeoutListener::~TestTimeoutListener() { listener_thread_runner_->PostTask( diff --git a/testing/test_timeout_listener.h b/testing/test_timeout_listener.h index a4f273ccf0202..37e2c23a2f40e 100644 --- a/testing/test_timeout_listener.h +++ b/testing/test_timeout_listener.h @@ -19,8 +19,7 @@ class PendingTests; class TestTimeoutListener : public ::testing::EmptyTestEventListener { public: - TestTimeoutListener( - fml::TimeDelta timeout = fml::TimeDelta::FromSeconds(30u)); + TestTimeoutListener(fml::TimeDelta timeout); ~TestTimeoutListener();