diff --git a/rosbag2_test_common/CMakeLists.txt b/rosbag2_test_common/CMakeLists.txt index 91400fc1cb..d5801f6b39 100644 --- a/rosbag2_test_common/CMakeLists.txt +++ b/rosbag2_test_common/CMakeLists.txt @@ -42,8 +42,26 @@ install( DESTINATION include/${PROJECT_NAME}) if(BUILD_TESTING) + find_package(ament_cmake_gmock REQUIRED) find_package(ament_lint_auto REQUIRED) + find_package(rcpputils REQUIRED) ament_lint_auto_find_test_dependencies() + + add_executable(loop_with_ctrl_c_handler test/rosbag2_test_common/loop_with_ctrl_c_handler.cpp) + install( + TARGETS loop_with_ctrl_c_handler + EXPORT export_loop_with_ctrl_c_handler + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin + ) + + ament_add_gmock(test_process_execution_helpers + test/rosbag2_test_common/test_process_execution_helpers.cpp + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + if(TARGET test_process_execution_helpers) + target_link_libraries(test_process_execution_helpers ${PROJECT_NAME}) + endif() endif() ament_python_install_package(${PROJECT_NAME}) diff --git a/rosbag2_test_common/package.xml b/rosbag2_test_common/package.xml index 27a1cfaf38..f8ea1e0298 100644 --- a/rosbag2_test_common/package.xml +++ b/rosbag2_test_common/package.xml @@ -23,6 +23,7 @@ ament_lint_auto ament_lint_common + rcpputils ament_cmake diff --git a/rosbag2_test_common/test/rosbag2_test_common/loop_with_ctrl_c_handler.cpp b/rosbag2_test_common/test/rosbag2_test_common/loop_with_ctrl_c_handler.cpp new file mode 100644 index 0000000000..c574e9225a --- /dev/null +++ b/rosbag2_test_common/test/rosbag2_test_common/loop_with_ctrl_c_handler.cpp @@ -0,0 +1,78 @@ +// Copyright 2023 Apex.AI, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#ifdef _WIN32 +#include +int main(void) +{ + // Enable default CTRL+C handler first. This is workaround and needed for the cases when + // process created with CREATE_NEW_PROCESS_GROUP flag. Without it, installing custom Ctrl+C + // handler will not work. + if (!SetConsoleCtrlHandler(nullptr, false)) { + std::cerr << "Error: Failed to enable default CTL+C handler. \n"; + } + + static std::atomic_bool running = true; + // Installing our own control handler + auto CtrlHandler = [](DWORD fdwCtrlType) -> BOOL { + switch (fdwCtrlType) { + case CTRL_C_EVENT: + printf("Ctrl-C event\n"); + running = false; + return TRUE; + default: + return FALSE; + } + }; + if (!SetConsoleCtrlHandler(CtrlHandler, TRUE)) { + std::cerr << "\nError. Can't install SIGINT handler\n"; + return EXIT_FAILURE; + } else { + std::cout << "\nWaiting in a loop for CTRL+C event\n"; + std::cout.flush(); + while (running) { + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + } + } + return EXIT_SUCCESS; +} +#else +#include + +int main() +{ + auto old_sigint_handler = std::signal( + SIGINT, [](int /* signal */) { + printf("Ctrl-C event\n"); + exit(EXIT_SUCCESS); + }); + + if (old_sigint_handler != SIG_ERR) { + std::cout << "\nWaiting in a loop for CTRL+C event\n"; + std::cout.flush(); + while (1) { + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + } + } else { + std::cerr << "\nError. Can't install SIGINT handler\n"; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +#endif diff --git a/rosbag2_test_common/test/rosbag2_test_common/test_process_execution_helpers.cpp b/rosbag2_test_common/test/rosbag2_test_common/test_process_execution_helpers.cpp new file mode 100644 index 0000000000..10b91afc42 --- /dev/null +++ b/rosbag2_test_common/test/rosbag2_test_common/test_process_execution_helpers.cpp @@ -0,0 +1,44 @@ +// Copyright 2023 Apex.AI, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "rosbag2_test_common/process_execution_helpers.hpp" +#include "rcpputils/scope_exit.hpp" + +class ProcessExecutionHelpersTest : public ::testing::Test +{ +public: + ProcessExecutionHelpersTest() = default; +}; + +TEST_F(ProcessExecutionHelpersTest, ctrl_c_event_can_be_send_and_received) { + testing::internal::CaptureStdout(); + auto process_id = start_execution("loop_with_ctrl_c_handler"); + auto cleanup_process_handle = rcpputils::make_scope_exit( + [process_id]() { + stop_execution(process_id); + }); + + // Sleep for 1 second to yield CPU resources to the newly spawned process, to make sure that + // signal handlers has been installed. + std::this_thread::sleep_for(std::chrono::seconds(1)); + + std::string test_output = testing::internal::GetCapturedStdout(); + EXPECT_THAT(test_output, HasSubstr("Waiting in a loop for CTRL+C event")); + + // Send SIGINT to child process and check exit code + stop_execution(process_id, SIGINT); + cleanup_process_handle.cancel(); +}