diff --git a/CMakeLists.txt b/CMakeLists.txt index 25d46af..e91c7b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ find_package(rcutils REQUIRED) find_package(std_msgs REQUIRED) find_package(std_srvs REQUIRED) find_package(rmw REQUIRED) +find_package(rcl_interfaces REQUIRED) find_package(Boost REQUIRED COMPONENTS program_options filesystem) find_package(yaml_cpp_vendor REQUIRED) @@ -148,7 +149,27 @@ target_include_directories(client_save_on_update $ ) -install(TARGETS client_default client_with_node_options client_save_on_update DESTINATION lib/${PROJECT_NAME}) +add_executable(client_dynamic_param_change + test/src/test_dynamic_param_change.cpp + test/src/persist_parameter_client.cpp +) + +target_link_libraries(client_dynamic_param_change + PUBLIC + rclcpp::rclcpp + rclcpp_components::component + rcutils::rcutils + ${rcl_interfaces_TARGETS} + ${std_msgs_TARGETS} + ${std_srvs_TARGETS} +) + +target_include_directories(client_dynamic_param_change + PUBLIC + $ +) + +install(TARGETS client_default client_with_node_options client_save_on_update client_dynamic_param_change DESTINATION lib/${PROJECT_NAME}) # Install launch files. install(DIRECTORY diff --git a/server/include/parameter_server.h b/server/include/parameter_server.h index 8017316..d7020c7 100644 --- a/server/include/parameter_server.h +++ b/server/include/parameter_server.h @@ -38,7 +38,15 @@ class ParameterServer : public rclcpp::Node ~ParameterServer(); private: +#if RCLCPP_VERSION_MAJOR < 17 + std::shared_ptr server_param_subscriber_; + std::shared_ptr storing_period_callback_handle_; + std::shared_ptr dynamic_typing_callback_handle_; +#endif + // Initialize parameters value bool must_save_on_update_ = false; + bool allow_dynamic_typing_ = false; + int64_t storing_period_ = 0; // Using custom yaml file same as yaml format of ros2 parameter as much as possible, // so use rcl_yaml_param_parser functions directly to load custom persistent yaml file. void LoadYamlFile(); @@ -52,6 +60,10 @@ class ParameterServer : public rclcpp::Node void CheckYamlFile(const std::string& file); void ValidateYamlFile(YAML::Node node, const std::string& key = ""); void SaveNode(YAML::Emitter& out, YAML::Node node, const std::string& key = ""); +#if RCLCPP_VERSION_MAJOR >= 17 + void updateStoringTimer(const rclcpp::Parameter & param); + void updateDynamicTyping(const rclcpp::Parameter & param); +#endif // Check whether parameter name contains "persistent." in the parameter list bool CheckPersistentParam(const std::vector & parameters); @@ -80,8 +92,8 @@ class ParameterServer : public rclcpp::Node // for periodic storing to the file system rclcpp::TimerBase::SharedPtr timer_; + void TimerCallback(); - bool allow_dynamic_typing_ = false; // For manual triggering of save rclcpp::Service::SharedPtr save_trigger_; rclcpp::Service::SharedPtr reload_trigger_; diff --git a/server/src/parameter_server.cpp b/server/src/parameter_server.cpp index 409b040..73c43bc 100644 --- a/server/src/parameter_server.cpp +++ b/server/src/parameter_server.cpp @@ -75,7 +75,10 @@ ParameterServer::ParameterServer( RCLCPP_DEBUG( this->get_logger(), "%s yaml:%s", PARAMETER_SERVER_FUNCTION, persistent_yaml_file_.c_str()); - int storing_period = 0; +#if RCLCPP_VERSION_MAJOR < 17 + server_param_subscriber_ = std::make_shared(this); +#endif + // if automatically_declare_parameters_from_overrides is false, then the parameter_overrides will not be declared. // So it is safer to fetch the passed parameters directly from options.parameter_overrides() const std::vector & parameter_overrides = options.parameter_overrides(); @@ -84,7 +87,7 @@ ParameterServer::ParameterServer( allow_dynamic_typing_ = param.as_bool(); } if (param.get_name() == "storing_period") { - storing_period = param.as_int(); + storing_period_ = param.as_int(); } if (param.get_name() == "must_save_on_update") { this->must_save_on_update_ = param.as_bool(); @@ -103,34 +106,94 @@ ParameterServer::ParameterServer( "Save on update enabled. Parameters will be saved when set."); } - if (storing_period < 0) { + if (storing_period_ < 0) { RCLCPP_WARN( this->get_logger(), - "storing_period parameter value (%d) is not valid, treating as 0", storing_period); - storing_period = 0; + "storing_period parameter value (%ld) is not valid, treating as 0", storing_period_); + storing_period_ = 0; } - if (!storing_period) { + if (!storing_period_) { RCLCPP_INFO( this->get_logger(), "Period is 0. Will not perform periodic persistent parameter storing"); } else { timer_ = this->create_wall_timer( - std::chrono::seconds(storing_period), - [this]{ - StoreYamlFile(); - } + std::chrono::seconds(storing_period_), + std::bind(&ParameterServer::TimerCallback, this) ); RCLCPP_INFO( - this->get_logger(), "Will perform periodic persistent parameter storing every %ds", - storing_period); + this->get_logger(), "Will perform periodic persistent parameter storing every %lds", + storing_period_); } +#if RCLCPP_VERSION_MAJOR < 17 + auto allow_dynamic_typing_param_change_callback = + [this](const rclcpp::Parameter & p) { + RCLCPP_INFO( + this->get_logger(), "Allow dynamic typing param value changed to %s", + p.as_bool() ? "true" : "false"); + allow_dynamic_typing_ = p.as_bool(); + RCLCPP_WARN( + this->get_logger(), + "Changing allow_dynamic_typing at runtime only affects parameters declared after " + "this change; already-declared parameter descriptors are not updated automatically."); + }; + dynamic_typing_callback_handle_ = server_param_subscriber_->add_parameter_callback( + "allow_dynamic_typing", + allow_dynamic_typing_param_change_callback + ); + + auto storing_period_param_change_callback = + [this](const rclcpp::Parameter & p) { + + const int64_t new_storing_period = p.as_int(); + + RCLCPP_INFO( + this->get_logger(), "Storing period param value changed to %lld", + static_cast(new_storing_period)); + + if (timer_) { + timer_->cancel(); + timer_.reset(); + } + + storing_period_ = new_storing_period; + if (storing_period_ > 0) { + timer_ = this->create_wall_timer( + std::chrono::seconds(storing_period_), + std::bind(&ParameterServer::TimerCallback, this) + ); + } + }; + storing_period_callback_handle_ = server_param_subscriber_->add_parameter_callback( + "storing_period", + storing_period_param_change_callback + ); +#endif + // Declare a parameter change request callback auto param_change_callback = [this](const std::vector & parameters) { auto result = rcl_interfaces::msg::SetParametersResult(); + + // Check internal param values are valid + for (const rclcpp::Parameter & param : parameters) { + if(param.get_name() == "storing_period" && param.as_int() < 0) { + result.successful = false; + result.reason = "Storing period cannot be a negative value."; + return result; + } +#if RCLCPP_VERSION_MAJOR < 17 + if(param.get_name() == "must_save_on_update") { + RCLCPP_INFO( + this->get_logger(), "Save on update parameter value changed to %s", + param.as_bool() ? "true" : "false"); + must_save_on_update_ = param.as_bool(); + } +#endif + } result.successful = true; if (CheckPersistentParam(parameters)) @@ -161,6 +224,20 @@ ParameterServer::ParameterServer( auto post_param_change_callback = [this](const std::vector & parameters) { + // Update behavior based on the updated parameter + for (const rclcpp::Parameter & param : parameters) { + if(param.get_name() == "must_save_on_update") { + RCLCPP_INFO( + this->get_logger(), "Save on update parameter value changed to %s", + param.as_bool() ? "true" : "false"); + must_save_on_update_ = param.as_bool(); + } else if(param.get_name() == "storing_period") { + updateStoringTimer(param); + } else if(param.get_name() == "allow_dynamic_typing") { + updateDynamicTyping(param); + } + } + if (CheckPersistentParam(parameters)) { if(must_save_on_update_) @@ -221,6 +298,46 @@ ParameterServer::~ParameterServer() StoreYamlFile(); } +void ParameterServer::TimerCallback() { + StoreYamlFile(); +} + +#if RCLCPP_VERSION_MAJOR >= 17 +void ParameterServer::updateStoringTimer(const rclcpp::Parameter & param) +{ + const int64_t new_storing_period = param.as_int(); + + RCLCPP_INFO( + this->get_logger(), "Storing period param value changed to %lld", + static_cast(new_storing_period)); + + if (timer_) { + timer_->cancel(); + timer_.reset(); + } + + storing_period_ = new_storing_period; + if (storing_period_ > 0) { + timer_ = this->create_wall_timer( + std::chrono::seconds(storing_period_), + std::bind(&ParameterServer::TimerCallback, this) + ); + } +} + +void ParameterServer::updateDynamicTyping(const rclcpp::Parameter & param) +{ + RCLCPP_INFO( + this->get_logger(), "Allow dynamic typing param value changed to %s", + param.as_bool() ? "true" : "false"); + allow_dynamic_typing_ = param.as_bool(); + RCLCPP_WARN( + this->get_logger(), + "Changing allow_dynamic_typing at runtime only affects parameters declared after " + "this change; already-declared parameter descriptors are not updated automatically."); +} +#endif + // Add a limitation that A node that is a map in custom YAML file can't contain '.' in the key name void ParameterServer::ValidateYamlFile(YAML::Node node, const std::string& key) { for (YAML::const_iterator it = node.begin(); it != node.end(); ++it) diff --git a/test/include/persist_parameter_client.hpp b/test/include/persist_parameter_client.hpp index a97d091..bbd40af 100644 --- a/test/include/persist_parameter_client.hpp +++ b/test/include/persist_parameter_client.hpp @@ -17,6 +17,8 @@ #include "rclcpp/rclcpp.hpp" #include "std_srvs/srv/trigger.hpp" +#include "rcl_interfaces/srv/get_parameters.hpp" +#include "rcl_interfaces/srv/set_parameters.hpp" using namespace std::chrono_literals; @@ -95,17 +97,80 @@ class PersistParametersClient : public rclcpp::Node return ret; } + rclcpp::Parameter read_server_parameter(const std::string & param_name) + { + auto request = std::make_shared(); + request->names.push_back(param_name); + + auto future = get_server_param_client_->async_send_request(request); + auto rc = rclcpp::spin_until_future_complete(this->get_node_base_interface(), future, 5s); + if (rc != rclcpp::FutureReturnCode::SUCCESS) { + RCLCPP_ERROR(this->get_logger(), + "GET OPERATION : spin_until_future_complete failed (%s) for parameter: %s", + rclcpp::to_string(rc).c_str(), param_name.c_str()); + return rclcpp::Parameter(param_name); + } + + auto response = future.get(); + if(response->values.empty()) { + RCLCPP_ERROR(this->get_logger(), + "GET OPERATION : No values in response for parameter %s", param_name.c_str()); + return rclcpp::Parameter(param_name); + } + return rclcpp::Parameter(param_name, response->values[0]); + } + + template + bool modify_server_parameter(const std::string & param_name, const ValueType & param_value) + { + auto request = std::make_shared(); + request->parameters.push_back( + rclcpp::Parameter(param_name, param_value).to_parameter_msg() + ); + + auto future = set_server_param_client_->async_send_request(request); + auto rc = rclcpp::spin_until_future_complete(this->get_node_base_interface(), future, 5s); + if (rc != rclcpp::FutureReturnCode::SUCCESS) { + RCLCPP_ERROR(this->get_logger(), + "SET OPERATION : spin_until_future_complete failed (%s) for parameter: %s", + rclcpp::to_string(rc).c_str(), param_name.c_str()); + return false; + } + + auto response = future.get(); + for (auto & result : response->results) { + if (!result.successful) { + RCLCPP_INFO(this->get_logger(), + "SET OPERATION : Failed to set server parameter: %s", result.reason.c_str()); + return false; + } + } + RCLCPP_INFO(this->get_logger(), + "SET OPERATION : Set server parameter %s successfully.", param_name.c_str()); + return true; + } + inline std::shared_ptr trigger_save() { auto trigger = std::make_shared(); auto fut = this->save_trigger_client_->async_send_request(trigger); - rclcpp::spin_until_future_complete(this->get_node_base_interface(), fut); + auto rc = rclcpp::spin_until_future_complete(this->get_node_base_interface(), fut, 5s); + if (rc != rclcpp::FutureReturnCode::SUCCESS) { + RCLCPP_ERROR(this->get_logger(), + "SAVE TRIGGER : spin_until_future_complete failed (%s)", rclcpp::to_string(rc).c_str()); + return nullptr; + } return fut.get(); } inline std::shared_ptr reload_yaml() { auto trigger = std::make_shared(); auto fut = this->reload_trigger_client_->async_send_request(trigger); - rclcpp::spin_until_future_complete(this->get_node_base_interface(), fut); + auto rc = rclcpp::spin_until_future_complete(this->get_node_base_interface(), fut, 5s); + if (rc != rclcpp::FutureReturnCode::SUCCESS) { + RCLCPP_ERROR(this->get_logger(), + "RELOAD TRIGGER : spin_until_future_complete failed (%s)", rclcpp::to_string(rc).c_str()); + return nullptr; + } return fut.get(); } @@ -113,6 +178,8 @@ class PersistParametersClient : public rclcpp::Node std::unique_ptr sync_param_client_; std::shared_ptr> save_trigger_client_; std::shared_ptr> reload_trigger_client_; + std::shared_ptr> set_server_param_client_; + std::shared_ptr> get_server_param_client_; }; #endif diff --git a/test/include/test_common.h b/test/include/test_common.h index 3d038a5..a723f06 100644 --- a/test/include/test_common.h +++ b/test/include/test_common.h @@ -212,6 +212,45 @@ class TestPersistParameter return do_read_and_check(param_name, expected_value, testcase); } + /* + * Change the value of a server parameter, and then check it + * @param param_name The name of the server parameter. + * @param changed_value The value that you want to set. + * @param testcase The test case description. + */ + template + void do_server_param_change_and_check(const std::string & param_name, const ValueType & changed_value, const std::optional & expected_value, const std::string & testcase) { + bool ret = false; + ret = persist_param_client_.modify_server_parameter(param_name, changed_value); + + if(!ret) { + this->set_result(testcase, false); + throw SetOperationError(); + } + + return do_read_server_param_and_check(param_name, expected_value, testcase); + } + + /* + * Check the value of a server parameter + * @param param_name The name of the server parameter. + * @param expected_value The value the parameter should have. + * @param testcase The test case description. + */ + template + void do_read_server_param_and_check(const std::string & param_name, const std::optional & expected_value, const std::string & testcase) { + + auto param = persist_param_client_.read_server_parameter(param_name); + + ValueType val = param.get_value(); + if (expected_value.has_value() && val != expected_value.value()) { + this->set_result(testcase, false); + throw SetOperationError(); + } + + this->set_result(testcase, true); + } + /* * Change the value of parameter, save, read, then check. * @param param_name The name of parameter. diff --git a/test/src/persist_parameter_client.cpp b/test/src/persist_parameter_client.cpp index 1292d34..0801811 100644 --- a/test/src/persist_parameter_client.cpp +++ b/test/src/persist_parameter_client.cpp @@ -27,6 +27,8 @@ PersistParametersClient::PersistParametersClient( sync_param_client_ = std::make_unique(this, remote_node_name); save_trigger_client_ = create_client(remote_node_name + "/save_params"); reload_trigger_client_ = create_client(remote_node_name + "/reload_params"); + set_server_param_client_ = create_client(remote_node_name + "/set_parameters"); + get_server_param_client_ = create_client(remote_node_name + "/get_parameters"); } bool PersistParametersClient::read_parameter(const std::string & param_name, std::vector & parameter) diff --git a/test/src/test_default.cpp b/test/src/test_default.cpp index 1fdbf56..58d60cc 100644 --- a/test/src/test_default.cpp +++ b/test/src/test_default.cpp @@ -165,6 +165,7 @@ int main(int argc, char ** argv) "persistent.mixed_notation_double_array", mixed_array, "u. Set and saved mixed notation double array successfully"); } + ret_code = test_client->print_result(); } catch (const rclcpp::exceptions::RCLError & e) { ret_code = -1; @@ -176,9 +177,7 @@ int main(int argc, char ** argv) ret_code = -3; RCLCPP_ERROR(test_client->get_logger(), "unexpectedly failed: %s", e.what()); } - - // if any tests are not passed, return EXIT_FAILURE. - ret_code = test_client->print_result(); + rclcpp::shutdown(); return ret_code; diff --git a/test/src/test_dynamic_param_change.cpp b/test/src/test_dynamic_param_change.cpp new file mode 100644 index 0000000..de30a7b --- /dev/null +++ b/test/src/test_dynamic_param_change.cpp @@ -0,0 +1,169 @@ +// Copyright 2025 Sony Corporation +// +// 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 +#include +#include + +#include "persist_parameter_client.hpp" +#include "test_common.h" + +rclcpp::Logger TestPersistParameter::client_logger_ = rclcpp::get_logger("client"); + +// This test must be run simultaneously with the server node launched with +int main(int argc, char ** argv) +{ + // force flush of the stdout buffer. + // this ensures a correct sync of all prints + // even when executed simultaneously within the launch file. + setvbuf(stdout, NULL, _IONBF, BUFSIZ); + + rclcpp::init(argc, argv); + std::shared_ptr test_client; + + int ret_code = 0; + try { + test_client = std::make_shared("client", rclcpp::NodeOptions()); + + { + RCLCPP_INFO(test_client->get_logger(), "Test save_on_update param dynamic turn on"); + + // Start with save on update at false + test_client->do_read_server_param_and_check("must_save_on_update", + false, "a. Check initial must_save_on_update value"); + // Modify the parameter and check that it is changed + test_client->do_server_param_change_and_check("must_save_on_update", + true, true, "b. Dynamically enable save on update"); + // Check that a persistent change can be made + test_client->do_reload_and_check( + "persistent.a_string", std::string{"Hi"}, std::string{"Hi"}, + "c. Check that change is saved on update"); + } + + { + RCLCPP_INFO(test_client->get_logger(), "Test save_on_update param dynamic turn off"); + // Start with save on update at true + test_client->do_read_server_param_and_check("must_save_on_update", + true, "d. Check initial must_save_on_update value"); + // Modify the parameter and check that it is changed + test_client->do_server_param_change_and_check("must_save_on_update", + false, false, "e. Dynamically disable save on update"); + // Check that a modified value is not saved on update + test_client->do_reload_and_check( + "persistent.a_string", std::string{"there"}, std::string{"Hi"}, + "f. Check that change is not saved on update"); + } + + { + RCLCPP_INFO(test_client->get_logger(), "Test storing timer gets dynamically turned on"); + // Start with storing period at 0 + test_client->do_read_server_param_and_check("storing_period", + 0, "g. Check initial storing period value"); + // Modify the parameter and check that it is changed + test_client->do_server_param_change_and_check("storing_period", + 2, 2, "h. Dynamically change the value of storing period"); + // Make a change that should be saved when the timer fires + test_client->do_change_and_check( + "persistent.a_string", std::string{"General"}, "i. Change persistent parameter"); + + // Wait for the timer to fire + std::this_thread::sleep_for(std::chrono::seconds(3)); + + // Reload from YAML and check the timer saved the parameter + test_client->do_reload_and_check( + "persistent.a_string", std::string{"General"}, std::string{"General"}, + "j. Timer saved the value to disk"); + } + + { + RCLCPP_INFO(test_client->get_logger(), "Test storing timer can be modified"); + // Start with storing period at 2 + test_client->do_read_server_param_and_check("storing_period", + 2, "k. Check storing period is 2"); + // Change to 30s so the timer won't fire after the old 3s timer + test_client->do_server_param_change_and_check("storing_period", + 30, 30, "l. Increase storing period to 30s"); + // Change a parameter to see if it will be stored + test_client->do_change_and_check( + "persistent.a_string", std::string{"Kenobi"}, "m. Change persistent parameter"); + + // Wait 3s — old 2s timer is cancelled, new 30s timer hasn't fired + std::this_thread::sleep_for(std::chrono::seconds(3)); + + // Reload from YAML and check the timer didn't save it + test_client->do_reload_and_check( + "persistent.a_string", std::string{"Kenobi"}, std::string{"General"}, + "n. Timer didn't save the value to disk"); + } + + { + RCLCPP_INFO(test_client->get_logger(), "Test storing timer can be turned off"); + // Start with storing period at 30 from previous block + test_client->do_read_server_param_and_check("storing_period", + 30, "o. Check storing period is 30"); + // Go back to a 2 seconds timer to accelerate test + test_client->do_server_param_change_and_check("storing_period", + 2, 2, "p. Reduce timer period"); + + // Wait 2s for the parameter to be set + std::this_thread::sleep_for(std::chrono::seconds(2)); + + // Turn off the timer + test_client->do_server_param_change_and_check("storing_period", + 0, 0, "q. Disable storing period"); + // Make a change that shouldn't be made persistent + test_client->do_change_and_check( + "persistent.a_string", std::string{"Kenobi"}, "r. Change persistent parameter"); + + // Wait 3s — no timer should fire + std::this_thread::sleep_for(std::chrono::seconds(3)); + + // Reload from YAML and check the timer didn't save it + test_client->do_reload_and_check( + "persistent.a_string", std::string{"Kenobi"}, std::string{"General"}, + "s. Timer didn't save the value to disk"); + } + + { + RCLCPP_INFO(test_client->get_logger(), "Test allow_dynamic_typing param dynamic turn on"); + // Start with allow_dynamic_typing at false + test_client->do_read_server_param_and_check("allow_dynamic_typing", + false, "t. Check initial allow_dynamic_typing value"); + // Verify type change fails with dynamic typing off + test_client->do_fail_to_change( + "persistent.some_int", std::string{"mutated"}, + "u. Type change fails with dynamic typing off"); + // Dynamically enable allow_dynamic_typing + test_client->do_server_param_change_and_check("allow_dynamic_typing", + true, true, "v. Dynamically enable allow_dynamic_typing"); + } + + ret_code = test_client->print_result(); + } catch (const rclcpp::exceptions::RCLError & e) { + ret_code = -1; + RCLCPP_ERROR(test_client->get_logger(), "unexpectedly failed: %s", e.what()); + } catch (const NoServerError & e) { + ret_code = -2; + RCLCPP_ERROR(test_client->get_logger(), "unexpectedly failed: %s", e.what()); + } catch (const SetOperationError & e) { + ret_code = -3; + RCLCPP_ERROR(test_client->get_logger(), "unexpectedly failed: %s", e.what()); + } + + rclcpp::shutdown(); + + return ret_code; +} diff --git a/test/src/test_save_on_update.cpp b/test/src/test_save_on_update.cpp index a263a63..cd97526 100644 --- a/test/src/test_save_on_update.cpp +++ b/test/src/test_save_on_update.cpp @@ -125,6 +125,7 @@ int main(int argc, char ** argv) "persistent.reload_test", std::string{"Changed"}, "m. Change the parameter (also auto-saved)"); } + ret_code = test_client->print_result(); } catch (const rclcpp::exceptions::RCLError & e) { ret_code = -1; @@ -137,8 +138,6 @@ int main(int argc, char ** argv) RCLCPP_ERROR(test_client->get_logger(), "unexpectedly failed: %s", e.what()); } - // if any tests are not passed, return EXIT_FAILURE. - ret_code = test_client->print_result(); rclcpp::shutdown(); return ret_code; diff --git a/test/src/test_with_node_options.cpp b/test/src/test_with_node_options.cpp index 34bffac..d333618 100644 --- a/test/src/test_with_node_options.cpp +++ b/test/src/test_with_node_options.cpp @@ -55,6 +55,7 @@ int main(int argc, char ** argv) "persistent.new_double", "3.14", "d. change the type of the new parameter to string"); } + ret_code = test_client->print_result(); } catch (const rclcpp::exceptions::RCLError & e) { ret_code = -1; RCLCPP_ERROR(test_client->get_logger(), "unexpectedly failed: %s", e.what()); @@ -66,8 +67,6 @@ int main(int argc, char ** argv) RCLCPP_ERROR(test_client->get_logger(), "unexpectedly failed: %s", e.what()); } - // if any tests are not passed, return EXIT_FAILURE. - ret_code = test_client->print_result(); rclcpp::shutdown(); return ret_code; diff --git a/test/test.py b/test/test.py index bf7f0ca..d999ca9 100755 --- a/test/test.py +++ b/test/test.py @@ -28,6 +28,12 @@ launchClientCmdSaveOnUpdate = [ 'ros2', 'run', 'persist_parameter_server', 'client_save_on_update'] +launchServerCmdDynamicParamChange = [ + 'ros2', 'launch', 'persist_parameter_server', 'test.launch.py', + 'save_on_update:=false', 'storing_period:=0'] +launchClientCmdDynamicParamChange = [ + 'ros2', 'run', 'persist_parameter_server', 'client_dynamic_param_change'] + if shutil.which('ros2') is None: print("source /install/setup.bash...then retry.") sys.exit(1) @@ -99,11 +105,30 @@ def kill_server(): return_code3 = client_process.wait() os.killpg(os.getpgid(server_process.pid), signal.SIGTERM) +print("\nTest save-on-update finished. Proceeding to testing dynamic param change") + +# Start the server with save-on-update disabled (will be enabled dynamically) +server_process = subprocess.Popen( + launchServerCmdDynamicParamChange, preexec_fn=os.setsid) +print(f"Parameter Server Process started with PID: {server_process.pid}") + +# Start test client process +client_process = subprocess.Popen(launchClientCmdDynamicParamChange) +print(f"Parameter Client Process started with PID: {client_process.pid}") + +# Wait until the client process finishes and then kill the server +return_code4 = client_process.wait() +os.killpg(os.getpgid(server_process.pid), signal.SIGTERM) + print("\nTest process finished.") -print(f"Return Code: {return_code}") +print("Return code summary:") +print(f" default options: {return_code}") +print(f" node options: {return_code2}") +print(f" save-on-update: {return_code3}") +print(f" dynamic param change: {return_code4}") # Check if the client process completed successfully -if return_code == return_code2 == return_code3 == 0: +if return_code == return_code2 == return_code3 == return_code4 == 0: print("The process completed successfully.") sys.exit(0) else: