diff --git a/MODULE.bazel b/MODULE.bazel index 754f0308..b6cd1023 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -16,6 +16,7 @@ module( ) # Bazel global rules +bazel_dep(name = "bazel_skylib", version = "1.9.0") bazel_dep(name = "rules_pkg", version = "1.1.0") bazel_dep(name = "rules_python", version = "1.8.5") bazel_dep(name = "rules_rust", version = "0.68.1-score") diff --git a/config/BUILD b/config/BUILD index 2057a774..ae6bb3ec 100644 --- a/config/BUILD +++ b/config/BUILD @@ -10,6 +10,8 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") + config_setting( name = "x86_64-linux", define_values = { @@ -52,3 +54,17 @@ config_setting( name = "ub_sanitizer_enabled", define_values = {"sanitize": "undefined"}, ) + +bool_flag( + name = "use_cout_log", + build_setting_default = False, + visibility = ["//visibility:public"], +) + +config_setting( + name = "lm_use_cout_log", + flag_values = { + ":use_cout_log": "True", + }, + visibility = ["//visibility:public"], +) diff --git a/src/control_client_lib/BUILD b/src/control_client_lib/BUILD index d119de27..d4e304e0 100644 --- a/src/control_client_lib/BUILD +++ b/src/control_client_lib/BUILD @@ -32,6 +32,5 @@ cc_library( "//src/launch_manager_daemon/common:log", "//src/launch_manager_daemon/common:osal", "@score_baselibs//score/concurrency/future", - "@score_baselibs//score/mw/log", ], ) diff --git a/src/launch_manager_daemon/common/BUILD b/src/launch_manager_daemon/common/BUILD index 6a39acb4..c378f4ad 100644 --- a/src/launch_manager_daemon/common/BUILD +++ b/src/launch_manager_daemon/common/BUILD @@ -51,8 +51,16 @@ cc_library( cc_library( name = "log", hdrs = ["include/score/lcm/internal/log.hpp"], + defines = select({ + "//config:lm_use_cout_log": [], + "//conditions:default": ["LC_LOG_SCORE_MW_LOG"], + }), includes = ["include"], visibility = ["//src:__subpackages__"], + deps = [] + select({ + "//config:lm_use_cout_log": [], + "//conditions:default": ["@score_baselibs//score/mw/log"], + }), ) cc_library( diff --git a/src/launch_manager_daemon/common/include/score/lcm/identifier_hash.hpp b/src/launch_manager_daemon/common/include/score/lcm/identifier_hash.hpp index 8fe24d1f..b1e3b0a7 100644 --- a/src/launch_manager_daemon/common/include/score/lcm/identifier_hash.hpp +++ b/src/launch_manager_daemon/common/include/score/lcm/identifier_hash.hpp @@ -15,16 +15,13 @@ #define IDENTIFIER_HASH_H_ #include -#include #include +#include #include #include #include -namespace score -{ - -namespace lcm +namespace score::lcm { /// @file identifier_hash.hpp @@ -132,6 +129,27 @@ class IdentifierHash final std::size_t hash_id_ = 0; }; +namespace detail +{ + +template +inline StreamT& streamIdentifierHash(StreamT& stream, const IdentifierHash& id_hash) +{ + const auto& reg = IdentifierHash::get_registry(); + const auto it = reg.find(id_hash.data()); + if (it != reg.end()) + { + stream << it->second; + } + else + { + stream << ""; + } + return stream; +} + +} // namespace detail + /// @brief Overloaded stream insertion operator for IdentifierHash. /// /// This operator allows IdentifierHash objects to be output to an ostream. @@ -145,23 +163,27 @@ class IdentifierHash final /// @param os The output stream. /// @param id The IdentifierHash object to output. /// @return A reference to the output stream. -inline std::ostream& operator<<(std::ostream& os, const IdentifierHash& id) +inline std::ostream& operator<<(std::ostream& os, const IdentifierHash& id) noexcept(false) { - const auto& reg = IdentifierHash::get_registry(); - const auto it = reg.find(id.data()); - if (it != reg.end()) - { - os << it->second; - } - else - { - os << ""; - } - return os; + return detail::streamIdentifierHash(os, id); +} + +} // namespace score::lcm + +#ifdef LC_LOG_SCORE_MW_LOG +#include "score/mw/log/logger.h" + +namespace score::lcm +{ + +inline score::mw::log::LogStream& operator<<(score::mw::log::LogStream& log_stream, + const IdentifierHash& id) noexcept(false) +{ + return detail::streamIdentifierHash(log_stream, id); } -} // namespace lcm +} // namespace score::lcm -} // namespace score +#endif // LC_LOG_SCORE_MW_LOG #endif // IDENTIFIER_HASH_H_ diff --git a/src/launch_manager_daemon/common/include/score/lcm/internal/config.hpp b/src/launch_manager_daemon/common/include/score/lcm/internal/config.hpp index 95243a7c..0401a157 100644 --- a/src/launch_manager_daemon/common/include/score/lcm/internal/config.hpp +++ b/src/launch_manager_daemon/common/include/score/lcm/internal/config.hpp @@ -70,6 +70,9 @@ enum class ProcessLimits : std::uint32_t { maxLocalBuffSize = 32U ///< Maximum size for local buffer }; +// coverity[autosar_cpp14_a0_1_1_violation:INTENTIONAL] These are constants that are used globally. +constexpr std::uint32_t kCoreDumps = 1U; ///< Enable core dumps for managed processes (1 = enabled, 0 = disabled) + } // namespace lcm } // namespace internal diff --git a/src/launch_manager_daemon/common/include/score/lcm/internal/log.hpp b/src/launch_manager_daemon/common/include/score/lcm/internal/log.hpp index de02e145..df2f76ac 100644 --- a/src/launch_manager_daemon/common/include/score/lcm/internal/log.hpp +++ b/src/launch_manager_daemon/common/include/score/lcm/internal/log.hpp @@ -11,7 +11,6 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ - #ifndef LCM_LOG_HPP_INCLUDED #define LCM_LOG_HPP_INCLUDED @@ -23,43 +22,44 @@ #include "score/mw/log/logger.h" -namespace score { +namespace score +{ -namespace lcm { +namespace lcm +{ -namespace internal { +namespace internal +{ /// @brief Function to access global logging context, for Launch Manager. /// Launch Manager (LM) daemon, uses a single global logging context. /// This context is stored as a static variable inside this function and used all over LM daemon implementation. /// Please note that code should not call this function directly, but should use a set of wrapper macros. /// More information can be found in docs/architecture/concepts/logging/logging.rst file. -inline score::mw::log::Logger& _getLmLogger() noexcept { - // RULECHECKER_comment(1, 1, check_static_object_dynamic_initialization, "This is safe because the static is a function local.", true); +inline score::mw::log::Logger& _getLmLogger() noexcept +{ + // RULECHECKER_comment(1, 1, check_static_object_dynamic_initialization, "This is safe because the static is a + // function local.", true); static score::mw::log::Logger& log{score::mw::log::CreateLogger("LM", "Launch Manager logging context")}; return log; } -} // namespace lcm - } // namespace internal +} // namespace lcm + } // namespace score #else // LC_LOG_SCORE_MW_LOG // The only other solution supported is console logging. -#include -#include -#include #include +#include +#include #include -namespace score { - -namespace lcm { - -namespace internal { +namespace score::lcm::internal +{ enum class LogLevel { @@ -71,53 +71,62 @@ enum class LogLevel kVerbose = 5, }; -inline LogLevel GetLevelFromEnv() { - if (const char* levelStr = std::getenv("LC_STDOUT_LOG_LEVEL")) { +inline LogLevel GetLevelFromEnv() +{ + if (const char* levelStr = std::getenv("LC_STDOUT_LOG_LEVEL")) + { std::string_view levelSv{levelStr}; int logLevelTmp; - try { + try + { logLevelTmp = std::stoi(levelSv.data()); - }catch(...) { + } + catch (...) + { return LogLevel::kInfo; - } + } - if(logLevelTmp >= static_cast(LogLevel::kFatal) && logLevelTmp <= static_cast(LogLevel::kVerbose)) { + if (logLevelTmp >= static_cast(LogLevel::kFatal) && logLevelTmp <= static_cast(LogLevel::kVerbose)) + { return LogLevel(logLevelTmp); - } else { + } + else + { return LogLevel::kInfo; - } - - } else { + } + } + else + { return LogLevel::kInfo; } } -static LogLevel GetLevel() { +static LogLevel GetLevel() +{ const static LogLevel logLevel = GetLevelFromEnv(); return logLevel; } -inline std::ostream& operator<<(std::ostream& os, const std::tm* now ) { - std::cout << (now->tm_year + 1900) << '/' - << (now->tm_mon + 1) << '/' - << now->tm_mday << " " - << now->tm_hour << ":" - << now->tm_min << ":" - << now->tm_sec; +inline std::ostream& operator<<(std::ostream& os, const std::tm* now) +{ + std::cout << (now->tm_year + 1900) << '/' << (now->tm_mon + 1) << '/' << now->tm_mday << " " << now->tm_hour << ":" + << now->tm_min << ":" << now->tm_sec; return os; } class Stream { -public: + public: Stream() noexcept = default; - Stream(const Stream &) = delete; - Stream(Stream && other) noexcept { + Stream(const Stream&) = delete; + Stream(Stream&& other) noexcept + { print_ = other.print_; moved_ = true; }; - void SetPrint() { + void SetPrint() + { print_ = true; } @@ -125,7 +134,7 @@ class Stream Stream& operator<<(const T* value) noexcept { - if(print_) + if (print_) std::cout << " " << value; return *this; } @@ -133,17 +142,18 @@ class Stream template Stream& operator<<(const T& value) noexcept { - if(print_) + if (print_) std::cout << " " << value; return *this; } ~Stream() { - if(print_ && moved_) + if (print_ && moved_) std::cout << " ]" << reset_color_ << std::endl; } -private: + + private: bool print_{false}; bool moved_{false}; std::string_view reset_color_{"\033[0m"}; @@ -151,21 +161,22 @@ class Stream class Logger { -public: - Logger(std::string_view f_context, std::string_view f_description) : - ctxId_(f_context), ctxDescription_{f_description} { - + public: + Logger(std::string_view f_context, std::string_view f_description) + : ctxId_(f_context), ctxDescription_{f_description} + { } Stream LogFatal() noexcept { Stream stream; - if(GetLevel() >= LogLevel::kFatal) { + if (GetLevel() >= LogLevel::kFatal) + { stream.SetPrint(); std::time_t t = std::time(0); std::tm now; localtime_r(&t, &now); - stream << check_it_ << text_color_ << &now << appId_ << ctxId_ << "FATAL: ["; + stream << check_it_ << text_color_ << &now << appId_ << ctxId_ << "FATAL: ["; } return std::move(stream); } @@ -173,12 +184,13 @@ class Logger Stream LogError() noexcept { Stream stream; - if(GetLevel() >= LogLevel::kError) { + if (GetLevel() >= LogLevel::kError) + { stream.SetPrint(); std::time_t t = std::time(0); std::tm now; localtime_r(&t, &now); - stream << check_it_ << text_color_ << &now << appId_ << ctxId_ << "ERROR: ["; + stream << check_it_ << text_color_ << &now << appId_ << ctxId_ << "ERROR: ["; } return std::move(stream); @@ -187,12 +199,13 @@ class Logger Stream LogWarn() noexcept { Stream stream; - if(GetLevel() >= LogLevel::kWarn) { + if (GetLevel() >= LogLevel::kWarn) + { stream.SetPrint(); std::time_t t = std::time(0); std::tm now; localtime_r(&t, &now); - stream << check_it_ << text_color_ << &now << appId_ << ctxId_ << "WARNING: ["; + stream << check_it_ << text_color_ << &now << appId_ << ctxId_ << "WARNING: ["; } return std::move(stream); } @@ -200,12 +213,13 @@ class Logger Stream LogInfo() noexcept { Stream stream; - if(GetLevel() >= LogLevel::kInfo) { + if (GetLevel() >= LogLevel::kInfo) + { stream.SetPrint(); std::time_t t = std::time(0); std::tm now; localtime_r(&t, &now); - stream << text_color_ << &now << appId_ << ctxId_ << "INFO: ["; + stream << text_color_ << &now << appId_ << ctxId_ << "INFO: ["; } return std::move(stream); } @@ -213,12 +227,13 @@ class Logger Stream LogDebug() noexcept { Stream stream; - if(GetLevel() >= LogLevel::kDebug) { + if (GetLevel() >= LogLevel::kDebug) + { stream.SetPrint(); std::time_t t = std::time(0); std::tm now; localtime_r(&t, &now); - stream << text_color_ << &now << appId_ << ctxId_ << "DEBUG: ["; + stream << text_color_ << &now << appId_ << ctxId_ << "DEBUG: ["; } return std::move(stream); } @@ -226,16 +241,18 @@ class Logger Stream LogVerbose() noexcept { Stream stream; - if(GetLevel() >= LogLevel::kVerbose) { + if (GetLevel() >= LogLevel::kVerbose) + { stream.SetPrint(); std::time_t t = std::time(0); std::tm now; localtime_r(&t, &now); - stream << text_color_ << &now << appId_ << ctxId_ << "VERBOSE: ["; + stream << text_color_ << &now << appId_ << ctxId_ << "VERBOSE: ["; } return std::move(stream); } -private: + + private: const std::string_view appId_{"LCLM"}; const std::string_view ctxId_{"####"}; const std::string_view ctxDescription_{"####"}; @@ -243,17 +260,17 @@ class Logger const std::string_view check_it_{"\033[101;30m !!! -> \033[0m"}; }; -inline Logger& _getLmLogger() noexcept { - // RULECHECKER_comment(1, 1, check_static_object_dynamic_initialization, "This is safe because the static is a function local.", true); +inline Logger& _getLmLogger() noexcept +{ + // RULECHECKER_comment(1, 1, check_static_object_dynamic_initialization, "This is safe because the static is a + // function local.", true); static Logger log{"LCLM", "Launch Manager logging context"}; return log; } -} // namespace lcm - -} // namespace internal +} // namespace score::lcm::internal -} // namespace score +// namespace internal #endif // LC_LOG_SCORE_MW_LOG diff --git a/src/launch_manager_daemon/common/include/score/lcm/internal/osal/set_core_dumps.hpp b/src/launch_manager_daemon/common/include/score/lcm/internal/osal/set_core_dumps.hpp new file mode 100644 index 00000000..91501486 --- /dev/null +++ b/src/launch_manager_daemon/common/include/score/lcm/internal/osal/set_core_dumps.hpp @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + + +#ifndef SET_CORE_DUMPS_HPP_INCLUDED +#define SET_CORE_DUMPS_HPP_INCLUDED + +#include + +namespace score { + +namespace lcm { + +namespace internal { + +namespace osal { + +/// @brief Re-enable the dumpable flag after setuid(), which may clear it. +/// @details This doesn't need to be done on QNX and so this is a NO-OP. +/// @returns 0 on success, -1 on failure. +std::int32_t setCoreDumps() noexcept(true); + +} // namespace osal +} // namespace internal +} // namespace lcm +} // namespace score + +#endif diff --git a/src/launch_manager_daemon/common/src/internal/controlclientchannel.cpp b/src/launch_manager_daemon/common/src/internal/controlclientchannel.cpp index 3536aa81..d9536f3e 100644 --- a/src/launch_manager_daemon/common/src/internal/controlclientchannel.cpp +++ b/src/launch_manager_daemon/common/src/internal/controlclientchannel.cpp @@ -171,8 +171,6 @@ ControlClientChannelP ControlClientChannel::initializeControlClientChannel(int f std::next(static_cast(channelMemory), static_cast(sizeof(osal::IpcCommsSync))); result = ControlClientChannelP(static_cast(static_cast(controlClientStartPtr)), [](ControlClientChannel*) {}); - LM_LOG_DEBUG() << "ControlClientChannel mapped (creation path: " << std::boolalpha << (mem_ptr != nullptr) << ")"; - if (result) { std::unique_lock lock(init_mutex_); diff --git a/src/launch_manager_daemon/common/src/internal/osal/linux/set_core_dumps.cpp b/src/launch_manager_daemon/common/src/internal/osal/linux/set_core_dumps.cpp new file mode 100644 index 00000000..00543b4a --- /dev/null +++ b/src/launch_manager_daemon/common/src/internal/osal/linux/set_core_dumps.cpp @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include + +#include + +namespace score::lcm::internal::osal +{ + +std::int32_t setCoreDumps() noexcept(true) +{ + return ::prctl(PR_SET_DUMPABLE, 1); +} + +} // namespace score::lcm::internal::osal diff --git a/src/launch_manager_daemon/common/src/internal/osal/qnx/set_core_dumps.cpp b/src/launch_manager_daemon/common/src/internal/osal/qnx/set_core_dumps.cpp new file mode 100644 index 00000000..cb294ba4 --- /dev/null +++ b/src/launch_manager_daemon/common/src/internal/osal/qnx/set_core_dumps.cpp @@ -0,0 +1,23 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include + +namespace score::lcm::internal::osal +{ + +std::int32_t setCoreDumps() noexcept(true) +{ + return 0; +} + +} // namespace score::lcm::internal::osal diff --git a/src/launch_manager_daemon/health_monitor_lib/BUILD b/src/launch_manager_daemon/health_monitor_lib/BUILD index ecafe216..e5ac4dda 100644 --- a/src/launch_manager_daemon/health_monitor_lib/BUILD +++ b/src/launch_manager_daemon/health_monitor_lib/BUILD @@ -42,6 +42,10 @@ cc_library_with_common_opts( name = "phm_logging", srcs = ["src/score/lcm/saf/logging/PhmLogger.cpp"], hdrs = ["src/score/lcm/saf/logging/PhmLogger.hpp"], + defines = select({ + "//config:lm_use_cout_log": [], + "//conditions:default": ["LC_LOG_SCORE_MW_LOG"], + }), includes = [ "src", "src/score/lcm/saf/logging", diff --git a/src/launch_manager_daemon/health_monitor_lib/src/score/lcm/saf/daemon/SwClusterHandler.cpp b/src/launch_manager_daemon/health_monitor_lib/src/score/lcm/saf/daemon/SwClusterHandler.cpp index 8ed7de8e..d1282643 100644 --- a/src/launch_manager_daemon/health_monitor_lib/src/score/lcm/saf/daemon/SwClusterHandler.cpp +++ b/src/launch_manager_daemon/health_monitor_lib/src/score/lcm/saf/daemon/SwClusterHandler.cpp @@ -61,7 +61,7 @@ bool SwClusterHandler::constructWorkers(std::shared_ptr #include #include +#include #include #include #include @@ -44,6 +45,37 @@ using score::lcm::internal::osal::CommsType; using score::lcm::internal::osal::IpcCommsSync; using score::lcm::internal::osal::sysexit; +/// @brief Applies the given limit. +/// @warning This will sysexit if the set is not succesful. +void applyLimitOrDie(const int resource, const rlimit& limit) noexcept(false) +{ + if (::setrlimit(resource, &limit) == -1) + { + LM_LOG_FATAL() << "[New process] Failed to set rlimit " + << std::strerror(errno); + sysexit(EXIT_FAILURE); + } +} + + +/// @brief Sets the limit if given a non-zero value, otherwise skips. +/// @warning This will sysexit if the set is not succesful. +void setLimit(const int resource, const std::size_t amount) noexcept +{ + if (amount == 0U) + { + return; + } + + const struct rlimit limit { + .rlim_cur = amount, + .rlim_max = amount, + }; + + applyLimitOrDie(resource, limit); +} + + void handleComms(score::lcm::internal::osal::ChildProcessConfig& param) { // kNoComms !fd3 & !fd4 @@ -121,55 +153,19 @@ void changeCurrentWorkingDirectory(const score::lcm::internal::osal::OsalConfig& void implementMemoryResourceLimits(const score::lcm::internal::osal::OsalConfig& config) { - rlimit limit; - - if (config.resource_limits_.data_ != 0U) - { - limit.rlim_max = limit.rlim_cur = config.resource_limits_.data_; - if (setrlimit(RLIMIT_DATA, &limit) == -1) - { - LM_LOG_ERROR() << "[New process] setrlimit(RLIMIT_DATA," << limit.rlim_cur - << ") failed:" << std::strerror(errno); - sysexit(EXIT_FAILURE); - } - } - - if (config.resource_limits_.as_ != 0U) - { - limit.rlim_max = limit.rlim_cur = config.resource_limits_.as_; - if (setrlimit(RLIMIT_AS, &limit) == -1) - { - LM_LOG_ERROR() << "[New process] setrlimit(RLIMIT_AS," << limit.rlim_cur - << "failed:" << std::strerror(errno); - sysexit(EXIT_FAILURE); - } - } - - if (config.resource_limits_.stack_ != 0U) - { - limit.rlim_max = limit.rlim_cur = config.resource_limits_.stack_; - if (setrlimit(RLIMIT_STACK, &limit) == -1) - { - LM_LOG_ERROR() << "[New process] setrlimit(RLIMIT_STACK," << limit.rlim_cur - << ") failed:" << std::strerror(errno); - sysexit(EXIT_FAILURE); - } - } + setLimit(RLIMIT_DATA, config.resource_limits_.data_); + setLimit(RLIMIT_AS, config.resource_limits_.as_); + setLimit(RLIMIT_STACK, config.resource_limits_.stack_); // Note about cpu limit: // Using setrlimit, this imposes a maximum time that a process will run for, which might not be // what you intend? Probably you'll want a maximum time in a time-slice, but you don't get that // with limits set by setrlimit... - if (config.resource_limits_.cpu_ != 0U) - { - limit.rlim_max = limit.rlim_cur = config.resource_limits_.cpu_; - if (setrlimit(RLIMIT_CPU, &limit) == -1) - { - LM_LOG_ERROR() << "[New process] setrlimit(RLIMIT_CPU," << limit.rlim_cur - << ") failed:" << std::strerror(errno); - sysexit(EXIT_FAILURE); - } - } + setLimit(RLIMIT_CPU, config.resource_limits_.cpu_); + + // just set the max limit to enable core dumps + struct rlimit core_limit {.rlim_max = RLIM_INFINITY}; + applyLimitOrDie(RLIMIT_CORE, core_limit); } void changeSecurityPolicy(const score::lcm::internal::osal::OsalConfig& config) @@ -424,6 +420,13 @@ OsalReturnType IProcess::setSchedulingAndSecurity(const OsalConfig& config) retval = OsalReturnType::kFail; } + // setuid() clears the dumpable flag + if (-1 == score::lcm::internal::osal::setCoreDumps()) + { + LM_LOG_ERROR() << "setCoreDumps() failed:" << std::strerror(errno); + retval = OsalReturnType::kFail; + } + return retval; } diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..d1321517 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,23 @@ +# Test Options + +## Bazel Args + +| Arg | What It Does | +|:-------------------------------|:-----------------------------------------------------------------------------| +|`--sandbox_add_mount_pair=/tmp` | Uses the users real `/tmp` dir for testing (only for `--config=host` builds | +|`--test_output=streamed` | logs apear while running | +|`--test_output=all` | outputs all logs after test | +|`--nocache_test_results` | Reruns tests even if they have previously passed | +|`--runs_per_test=N` | Reruns the tests N times | +|`--compilation_mode=dbg` | Builds the binaries in debug mode (debug symbols & `NDEBUG` **not** defined)| + + +## Pytest Args + +Note the arguments are written so that you can add them to the end of the bazel +test command. + +| Arg | What It Does | Example Command | +|:-------------------------------|:----------------------------------------------|:------------------------------------------------| +|`--test_arg=--no-local-cleanup` | Integration tests don't cleanup after running |`bazel test //... --test_arg=--no-local-cleanup` | +|`--test_arg=-s` | More logging from the python test framework |`bazel test //... --test_arg=-s` | diff --git a/tests/integration/smoke/control_daemon_mock.cpp b/tests/integration/smoke/control_daemon_mock.cpp index 7b70bb49..6045049d 100644 --- a/tests/integration/smoke/control_daemon_mock.cpp +++ b/tests/integration/smoke/control_daemon_mock.cpp @@ -20,9 +20,11 @@ #include #include "tests/utils/test_helper/test_helper.hpp" -score::lcm::ControlClient client; TEST(Smoke, Daemon) { + + score::lcm::ControlClient client {}; + TEST_STEP("Control daemon report kRunning") { // report kRunning auto result = score::lcm::LifecycleClient{}.ReportExecutionState(score::lcm::ExecutionState::kRunning); diff --git a/tests/utils/testing_utils/setup_test.py b/tests/utils/testing_utils/setup_test.py index c34fdd31..e3d4197a 100644 --- a/tests/utils/testing_utils/setup_test.py +++ b/tests/utils/testing_utils/setup_test.py @@ -39,3 +39,24 @@ def setup_test(request, target): assert res == 0, f"Couldn't extract tar {remote_tar}" logger.info("Test case setup finished") + + +@pytest.fixture(autouse=True, scope="function") +def download_core_dumps(target, remote_test_dir, test_output_dir): + """Downloads any core dump files from the remote after a test completes.""" + yield + + res, stdout = target.execute(f"find {remote_test_dir} -name 'core*' -type f") + if res != 0: + return + core_files = stdout.decode().strip().splitlines() + for remote_path in core_files: + remote_path = remote_path.strip() + if not remote_path: + continue + local_path = test_output_dir / Path(remote_path).name + try: + target.download(remote_path, str(local_path)) + logger.info(f"Downloaded core dump: {remote_path} -> {local_path}") + except Exception as e: + logger.warning(f"Failed to download core dump {remote_path}: {e}")