diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab45c9400..0fcfa415e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,12 @@ name: Ubuntu CI -on: [push, pull_request] +on: + pull_request: + push: + branches: + - 'ign-transport[0-9]?' + - 'gz-transport[0-9]?' + - 'main' jobs: jammy-ci: @@ -8,7 +14,7 @@ jobs: name: Ubuntu Jammy CI steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Compile and test id: ci uses: gazebo-tooling/action-gz-ci@jammy diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index 2c94852da..2332244bf 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -14,4 +14,3 @@ jobs: with: project-url: https://github.com/orgs/gazebosim/projects/7 github-token: ${{ secrets.TRIAGE_TOKEN }} - diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 000000000..aa3447b4d --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,131 @@ +load( + "@gz//bazel/skylark:build_defs.bzl", + "GZ_FEATURES", + "GZ_ROOT", + "GZ_VISIBILITY", + "gz_configure_header", + "gz_export_header", + "gz_include_header", +) +load( + "@gz//bazel/lint:lint.bzl", + "add_lint_tests", +) + +package( + default_visibility = GZ_VISIBILITY, + features = GZ_FEATURES, +) + +licenses(["notice"]) # Apache-2.0 + +exports_files(["LICENSE"]) + +gz_configure_header( + name = "transport_config_hh", + src = "include/gz/transport/config.hh.in", + cmakelists = ["CMakeLists.txt"], + package = "transport", +) + +gz_export_header( + name = "include/gz/transport/Export.hh", + export_base = "GZ_TRANSPORT", + lib_name = "gz-transport", + visibility = ["//visibility:private"], +) + +public_headers_no_gen = glob([ + "include/gz/transport/*.h", + "include/gz/transport/*.hh", + "include/gz/transport/detail/*.hh", +]) + +private_headers = glob(["src/*.hh"]) + +sources = glob( + ["src/*.cc"], + exclude = [ + "src/*_TEST.cc", + ], +) + +gz_include_header( + name = "transport_hh_genrule", + out = "include/gz/transport.hh", + hdrs = public_headers_no_gen + [ + "include/gz/transport/config.hh", + "include/gz/transport/Export.hh", + ], +) + +public_headers = public_headers_no_gen + [ + "include/gz/transport/config.hh", + "include/gz/transport/Export.hh", + "include/gz/transport.hh", +] + +cc_library( + name = "transport", + srcs = sources + private_headers, + hdrs = public_headers, + copts = [ + "-Wno-deprecated-declarations", + ], + includes = ["include"], + deps = [ + GZ_ROOT + "msgs", + "@uuid", + "@zmq", + ], +) + +cc_binary( + name = "topic", + srcs = [ + "src/cmd/gz.cc", + "src/cmd/gz.hh", + "src/cmd/topic_main.cc", + ], + includes = ["src/cmd"], + deps = [ + ":transport", + GZ_ROOT + "utils/cli", + ], +) + +cc_binary( + name = "service", + srcs = [ + "src/cmd/gz.cc", + "src/cmd/gz.hh", + "src/cmd/service_main.cc", + ], + includes = ["src/cmd"], + deps = [ + ":transport", + GZ_ROOT + "utils/cli", + ], +) + +test_sources = glob( + include = ["src/*_TEST.cc"], +) + +[cc_test( + name = src.replace("/", "_").replace(".cc", "").replace("src_", ""), + srcs = [src], + env = { + "GZ_BAZEL": "1", + "GZ_BAZEL_PATH": "transport", + }, + deps = [ + ":transport", + GZ_ROOT + "common/testing", + GZ_ROOT + "transport/test:utils", + "@gtest", + "@gtest//:gtest_main", + ], +) for src in test_sources] + +add_lint_tests() diff --git a/Changelog.md b/Changelog.md index a5df01f6c..2d9982b1a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -209,6 +209,33 @@ ## Gazebo Transport 11.X +### Gazebo Transport 11.4.1 (2023-09-01) + +1. Fix topic/service list inconsistency + * [Pull request #415](https://github.com/gazebosim/gz-transport/pull/415) + +1. Backport Windows fix to ign-transport8 + * [Pull request #406](https://github.com/gazebosim/gz-transport/pull/406) + +1. Fix unused-result warning + * [Pull request #408](https://github.com/gazebosim/gz-transport/pull/408) + +1. Fix compatibility with protobuf 22 + * [Pull request #405](https://github.com/gazebosim/gz-transport/pull/405) + +1. Fix compiler warning and signedness issue + * [Pull request #401](https://github.com/gazebosim/gz-transport/pull/401) + +1. Rename COPYING to LICENSE + * [Pull request #392](https://github.com/gazebosim/gz-transport/pull/392) + +1. Infrastructure + * [Pull request #391](https://github.com/gazebosim/gz-transport/pull/391) + * [Pull request #394](https://github.com/gazebosim/gz-transport/pull/394) + +1. Support clang and std::filesystem + * [Pull request #390](https://github.com/gazebosim/gz-transport/pull/390) + ### Gazebo Transport 11.4.0 (2023-03-08) 1. Added Node::RequestRaw @@ -465,6 +492,34 @@ and publication, age, and reception statistics. * [Pull request 205](https://github.com/gazebosim/gz-transport/pull/205) +### Gazebo Transport 8.5.0 (2024-01-05) + +1. Update github action workflows + * [Pull request #460](https://github.com/gazebosim/gz-transport/pull/460) + * [Pull request #391](https://github.com/gazebosim/gz-transport/pull/391) + * [Pull request #392](https://github.com/gazebosim/gz-transport/pull/392) + +1. Adds the subcommands for the log command + * [Pull request #451](https://github.com/gazebosim/gz-transport/pull/451) + +1. Fix topic/service list inconsistency + * [Pull request #415](https://github.com/gazebosim/gz-transport/pull/415) + +1. Backport Windows fix to ign-transport8 + * [Pull request #406](https://github.com/gazebosim/gz-transport/pull/406) + +1. Fix compatibility with protobuf 22 + * [Pull request #405](https://github.com/gazebosim/gz-transport/pull/405) + +1. Fix compiler warning and signedness issue + * [Pull request #401](https://github.com/gazebosim/gz-transport/pull/401) + +1. Support clang and std::filesystem + * [Pull request #390](https://github.com/gazebosim/gz-transport/pull/390) + +1. Pass std::function by value to Node::Subscribe + * [Pull request #382](https://github.com/gazebosim/gz-transport/pull/382) + ### Gazebo Transport 8.4.0 (2022-11-17) 1. ign -> gz : Remove redundant namespace references. diff --git a/README.md b/README.md index 6e6b43b2f..3d2218584 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,10 @@ Build | Status -- | -- -Test coverage | [![codecov](https://codecov.io/gh/gazebosim/gz-transport/branch/main/graph/badge.svg)](https://codecov.io/gh/gazebosim/gz-transport/branch/main) -Ubuntu Focal | [![Build Status](https://build.osrfoundation.org/buildStatus/icon?job=ignition_transport-ci-main-focal-amd64)](https://build.osrfoundation.org/job/ignition_transport-ci-main-focal-amd64) -Homebrew | [![Build Status](https://build.osrfoundation.org/buildStatus/icon?job=ignition_transport-ci-main-homebrew-amd64)](https://build.osrfoundation.org/job/ignition_transport-ci-main-homebrew-amd64) -Windows | [![Build Status](https://build.osrfoundation.org/buildStatus/icon?job=ign_transport-ci-win)](https://build.osrfoundation.org/job/ign_transport-ci-win/) +Test coverage | [![codecov](https://codecov.io/gh/gazebosim/gz-transport/tree/main/graph/badge.svg)](https://codecov.io/gh/gazebosim/gz-transport/tree/main) +Ubuntu Jammy | [![Build Status](https://build.osrfoundation.org/buildStatus/icon?job=gz_transport-ci-main-jammy-amd64)](https://build.osrfoundation.org/job/gz_transport-ci-main-jammy-amd64) +Homebrew | [![Build Status](https://build.osrfoundation.org/buildStatus/icon?job=gz_transport-ci-main-homebrew-amd64)](https://build.osrfoundation.org/job/gz_transport-ci-main-homebrew-amd64) +Windows | [![Build Status](https://build.osrfoundation.org/buildStatus/icon?job=gz_transport-main-win)](https://build.osrfoundation.org/job/gz_transport-main-win/) Gazebo Transport, a component of [Gazebo](https://gazebosim.org), provides fast and efficient asynchronous message passing, services, and data logging. diff --git a/include/gz/transport/Discovery.hh b/include/gz/transport/Discovery.hh index c226cdcd1..161ebc4e3 100644 --- a/include/gz/transport/Discovery.hh +++ b/include/gz/transport/Discovery.hh @@ -306,6 +306,8 @@ namespace gz /// (e.g. if the discovery has not been started). public: bool Advertise(const Pub &_publisher) { + DiscoveryCallback cb; + { std::lock_guard lock(this->mutex); @@ -315,8 +317,13 @@ namespace gz // Add the addressing information (local publisher). if (!this->info.AddPublisher(_publisher)) return false; + + cb = this->connectionCb; } + if (cb) + cb(_publisher); + // Only advertise a message outside this process if the scope // is not 'Process' if (_publisher.Options().Scope() != Scope_t::PROCESS) diff --git a/log/BUILD.bazel b/log/BUILD.bazel new file mode 100644 index 000000000..2dd8b6109 --- /dev/null +++ b/log/BUILD.bazel @@ -0,0 +1,91 @@ +load( + "@gz//bazel/skylark:build_defs.bzl", + "GZ_FEATURES", + "GZ_ROOT", + "GZ_VISIBILITY", + "cmake_configure_file", + "gz_configure_header", + "gz_export_header", + "gz_include_header", +) +load( + "@gz//bazel/lint:lint.bzl", + "add_lint_tests", +) + +cmake_configure_file( + name = "build_config", + src = "src/build_config.hh.in", + out = "include/build_config.hh", + cmakelists = ["src/CMakeLists.txt"], + defines = [ + "SCHEMA_INSTALL_PATH=transport/log/sql", + ], +) + +gz_export_header( + name = "include/gz/transport/log/Export.hh", + export_base = "GZ_TRANSPORT_LOG", + lib_name = "gz-transport-log", + visibility = ["//visibility:private"], +) + +public_headers_no_gen = glob([ + "include/gz/transport/log/*.hh", + "include/gz/transport/log/detail/*.hh", +]) + +private_headers = glob(["src/*.hh"]) + +sources = glob( + ["src/*.cc"], + exclude = [ + "src/*_TEST.cc", + ], +) + +public_headers = public_headers_no_gen + [ + "include/gz/transport/log/Export.hh", +] + +cc_library( + name = "log", + srcs = sources + private_headers + ["include/build_config.hh"], + hdrs = public_headers, + data = ["sql/0.1.0.sql"], + includes = ["include"], + deps = [ + GZ_ROOT + "transport", + "@sqlite3", + ], +) + +test_sources = glob( + include = ["src/*_TEST.cc"], + exclude = ["src/LogCommandAPI_TEST.cc"], +) + +[cc_test( + name = src.replace("/", "_").replace(".cc", "").replace("src_", ""), + srcs = [src], + data = [ + "test/data/state.tlog", + ], + defines = [ + 'GZ_TRANSPORT_LOG_TEST_PATH=\\"transport/log/test\\"', + 'CORRUPT_DB_TEST_PATH=\\"transport/log/test/data/state.tlog\\"', + ], + env = { + "GZ_BAZEL": "1", + "GZ_BAZEL_PATH": "transport", + }, + deps = [ + ":log", + GZ_ROOT + "common/testing", + GZ_ROOT + "transport/test:utils", + "@gtest", + "@gtest//:gtest_main", + ], +) for src in test_sources] + +add_lint_tests() diff --git a/log/src/CMakeLists.txt b/log/src/CMakeLists.txt index 4a9398da4..aab305fbd 100644 --- a/log/src/CMakeLists.txt +++ b/log/src/CMakeLists.txt @@ -19,14 +19,16 @@ endif() gz_build_tests( TYPE "UNIT" SOURCES ${gtest_sources} - LIB_DEPS ${log_lib_target} ${EXTRA_TEST_LIB_DEPS} + LIB_DEPS ${log_lib_target} ${EXTRA_TEST_LIB_DEPS} test_config TEST_LIST logging_tests ) foreach(test_target ${logging_tests}) - - set_tests_properties(${logging_tests} PROPERTIES + set_tests_properties(${test_target} PROPERTIES ENVIRONMENT GZ_TRANSPORT_LOG_SQL_PATH=${PROJECT_SOURCE_DIR}/log/sql) + target_compile_definitions(${test_target} PRIVATE + "CORRUPT_DB_TEST_PATH=\"${CMAKE_SOURCE_DIR}/log/test/data/state.tlog\"" + ) endforeach() @@ -46,9 +48,6 @@ install(DIRECTORY ../sql DESTINATION ${SCHEMA_INSTALL_BASE}) set(SCHEMA_INSTALL_PATH ${CMAKE_INSTALL_PREFIX}/${SCHEMA_INSTALL_BASE}/sql) configure_file(build_config.hh.in build_config.hh @ONLY) - -message(STATUS "CMAKE_CURRENT_SOURCE_DIR:${CMAKE_CURRENT_SOURCE_DIR}") - target_include_directories(${log_lib_target} PUBLIC # Add this component's include directory to the build interface include diff --git a/log/src/Log_TEST.cc b/log/src/Log_TEST.cc index f1d683cf8..1faadb310 100644 --- a/log/src/Log_TEST.cc +++ b/log/src/Log_TEST.cc @@ -14,6 +14,7 @@ * limitations under the License. * */ +#include "gtest/gtest.h" #include #include @@ -21,14 +22,17 @@ #include #include "gz/transport/log/Log.hh" -#include "test_config.hh" -#include "log/test_config.hh" -#include "gtest/gtest.h" + +#include "test_utils.hh" using namespace gz; using namespace gz::transport; using namespace std::chrono_literals; +namespace { +constexpr const char * kCorruptDbTestPath = CORRUPT_DB_TEST_PATH; +} + ////////////////////////////////////////////////// TEST(Log, OpenMemoryDatabase) { @@ -251,10 +255,7 @@ TEST(Log, NullDescriptorUnopenedLog) TEST(Log, OpenCorruptDatabase) { log::Log logFile; - std::string path = - testing::portablePathUnion(GZ_TRANSPORT_LOG_TEST_PATH, "data"); - path = testing::portablePathUnion(path, "state.tlog"); - logFile.Open(path); + logFile.Open(kCorruptDbTestPath); EXPECT_GT(logFile.EndTime(), 0ns) << "logFile.EndTime() == " << logFile.EndTime().count() << "ns";; } diff --git a/log/test/CMakeLists.txt b/log/test/CMakeLists.txt index c11c67f90..26e40e61e 100644 --- a/log/test/CMakeLists.txt +++ b/log/test/CMakeLists.txt @@ -1,5 +1 @@ -configure_file (test_config.hh.in - ${PROJECT_BINARY_DIR}/include/log/test_config.hh -) - add_subdirectory(integration) diff --git a/log/test/integration/CMakeLists.txt b/log/test/integration/CMakeLists.txt index 3459c8aa9..900575125 100644 --- a/log/test/integration/CMakeLists.txt +++ b/log/test/integration/CMakeLists.txt @@ -1,5 +1,14 @@ # Integration tests + +add_library(ChirpParams STATIC ./ChirpParams.cc) +target_link_libraries(ChirpParams PUBLIC ${PROJECT_LIBRARY_TARGET_NAME}-log ${EXTRA_TEST_LIB_DEPS}) +target_compile_definitions(ChirpParams + PRIVATE TOPIC_CHIRP_EXE="$") + +gz_add_executable(topicChirp_aux topicChirp_aux.cc) +target_link_libraries(topicChirp_aux ChirpParams) + gz_build_tests( TYPE "INTEGRATION" TEST_LIST logging_tests @@ -8,8 +17,10 @@ gz_build_tests( playback.cc query.cc LIB_DEPS + ChirpParams ${PROJECT_LIBRARY_TARGET_NAME}-log ${EXTRA_TEST_LIB_DEPS} + test_config INCLUDE_DIRS ${CMAKE_BINARY_DIR}/test/ ) @@ -21,48 +32,12 @@ if (UNIX AND NOT APPLE) endif() foreach(test_target ${logging_tests}) - set_tests_properties(${test_target} PROPERTIES ENVIRONMENT GZ_TRANSPORT_LOG_SQL_PATH=${PROJECT_SOURCE_DIR}/log/sql) target_compile_definitions(${test_target} PRIVATE GZ_TRANSPORT_LOG_SQL_PATH="${PROJECT_SOURCE_DIR}/log/sql") - target_compile_definitions(${test_target} - PRIVATE GZ_TRANSPORT_LOG_BUILD_PATH="$") - endforeach() -set (aux - topicChirp_aux.cc -) - -foreach(source_file ${aux}) - string(REGEX REPLACE ".cc" "" AUX_EXECUTABLE ${source_file}) - set(BINARY_NAME ${TEST_TYPE}_${AUX_EXECUTABLE}) - - gz_add_executable(${BINARY_NAME} ${AUX_EXECUTABLE}.cc) - - # Include the interface directories that we always need. - gz_target_interface_include_directories(${BINARY_NAME} - ${PROJECT_LIBRARY_TARGET_NAME}) - - # Link the libraries that we always need. - target_link_libraries(${BINARY_NAME} - PRIVATE - ${PROJECT_LIBRARY_TARGET_NAME} - ${log_lib_target} - gtest - ${EXTRA_TEST_LIB_DEPS} - ) - - if(UNIX) - # pthread is only available on Unix machines - target_link_libraries(${BINARY_NAME} - PRIVATE pthread) - endif() - - target_compile_definitions(${BINARY_NAME} - PRIVATE GZ_TRANSPORT_LOG_BUILD_PATH="$") -endforeach() # gz log CLI test if (HAVE_GZ_TOOLS) diff --git a/log/test/integration/ChirpParams.cc b/log/test/integration/ChirpParams.cc new file mode 100644 index 000000000..ba0a517d5 --- /dev/null +++ b/log/test/integration/ChirpParams.cc @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 Open Source Robotics Foundation + * + * 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 "ChirpParams.hh" + +#include +#include + +static constexpr const char* kTopicChirpExe = TOPIC_CHIRP_EXE; + +namespace gz::transport::log::test +{ + gz::utils::Subprocess BeginChirps( + const std::vector &_topics, + const int _chirps, + const std::string &_partitionName) + { + // Argument list: + // [0]: Executable name + // [1]: Partition name + // [2]: Number of chirps + // [3]-[N]: Each topic name + // [N+1]: Null terminator, required by execv + const std::size_t numArgs = 3 + _topics.size() + 1; + + std::vector strArgs; + strArgs.reserve(numArgs-1); + strArgs.push_back(kTopicChirpExe); + strArgs.push_back(_partitionName); + strArgs.push_back(std::to_string(_chirps)); + strArgs.insert(strArgs.end(), _topics.begin(), _topics.end()); + return gz::utils::Subprocess(strArgs); + } +} // namespace gz::transport::log::test diff --git a/log/test/integration/ChirpParams.hh b/log/test/integration/ChirpParams.hh index 5a4ddaec4..5331df5ab 100644 --- a/log/test/integration/ChirpParams.hh +++ b/log/test/integration/ChirpParams.hh @@ -18,162 +18,45 @@ #ifndef GZ_TRANSPORT_LOG_TEST_INTEGRATION_CHIRPPARAMS_HH_ #define GZ_TRANSPORT_LOG_TEST_INTEGRATION_CHIRPPARAMS_HH_ -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4251) -#endif -#include -#ifdef _MSC_VER -#pragma warning(pop) -#endif -#include - #include #include +#include #include +#include - -namespace gz +namespace gz::transport::log::test { - namespace transport - { - namespace log - { - namespace test - { - /// \brief Parameter used to determine how long the topicChirp_aux - /// program will wait between emitting message chirps from its topic. - /// Value is in milliseconds. - const int DelayBetweenChirps_ms = 1; - - /// \brief Parameter used to determine how long the topicChirp_aux - /// program will wait (after it advertises) before it begins publishing - /// its message chirps. Value is in milliseconds. - const int DelayBeforePublishing_ms = 1000; - - /// \brief This is the message type that will be used by the chirping - /// topics. - using ChirpMsgType = gz::msgs::Int32; - - - ////////////////////////////////////////////////// - /// \brief Similar to testing::forkAndRun(), except this function - /// specifically calls the INTEGRATION_topicChirp_aux process and passes - /// it arguments to determine how it should chirp out messages over its - /// topics. - /// \param _topics A list of topic names to chirp on - /// \param _chirps The number of messages to chirp out. Each message - /// will count up starting from the value 1 and ending with the value - /// _chirps. - /// \return A handle to the process. This can be used with - /// testing::waitAndCleanupFork(). - testing::forkHandlerType BeginChirps( - const std::vector &_topics, - const int _chirps, - const std::string &_partitionName) - { - // Set the chirping process name - const std::string process = - GZ_TRANSPORT_LOG_BUILD_PATH"/INTEGRATION_topicChirp_aux"; - - // Argument list: - // [0]: Executable name - // [1]: Partition name - // [2]: Number of chirps - // [3]-[N]: Each topic name - // [N+1]: Null terminator, required by execv - const std::size_t numArgs = 3 + _topics.size() + 1; - - std::vector strArgs; - strArgs.reserve(numArgs-1); - strArgs.push_back(process); - strArgs.push_back(_partitionName); - strArgs.push_back(std::to_string(_chirps)); - strArgs.insert(strArgs.end(), _topics.begin(), _topics.end()); - - #ifdef _MSC_VER - std::string fullArgs; - for (std::size_t i = 0; i < strArgs.size(); ++i) - { - if (i == 0) - { - // Windows prefers quotes around the process name - fullArgs += "\""; - } - else - { - fullArgs += " "; - } - - fullArgs += strArgs[i]; - - if (i == 0) - { - fullArgs += "\""; - } - } - - char * args = new char[fullArgs.size()+1]; - std::snprintf(args, fullArgs.size()+1, "%s", fullArgs.c_str()); - - STARTUPINFO info = {sizeof(info)}; - PROCESS_INFORMATION processInfo; - - if (!CreateProcess(nullptr, args, nullptr, nullptr, - TRUE, 0, nullptr, nullptr, &info, &processInfo)) - { - std::cerr << "Error running the chirp process [" - << args << "]\n"; - } - - delete[] args; - - return processInfo; - #else - // Create a raw char* array to pass to execv - char * * args = new char*[numArgs]; - - // Allocate a char array for each argument and copy the data to it - for (std::size_t i = 0; i < strArgs.size(); ++i) - { - const std::string &arg = strArgs[i]; - args[i] = new char[arg.size()+1]; - std::snprintf(args[i], arg.size()+1, "%s", arg.c_str()); - } - - // The last item in the char array must be a nullptr, according to the - // documentation of execv - args[numArgs-1] = nullptr; - - testing::forkHandlerType pid = fork(); - - if (pid == 0) - { - if (execv(process.c_str(), args) == -1) - { - int err = errno; - std::cerr << "Error running the chirp process [" << err << "]: " - << strerror(err) << "\n"; - } - } - - // Clean up the array of arguments - for (std::size_t i = 0; i < numArgs; ++i) - { - char *arg = args[i]; - delete[] arg; - arg = nullptr; - } - delete[] args; - args = nullptr; - - return pid; - #endif - } - } - } - } -} + /// \brief Parameter used to determine how long the topicChirp_aux + /// program will wait between emitting message chirps from its topic. + /// Value is in milliseconds. + const int DelayBetweenChirps_ms = 1; + + /// \brief Parameter used to determine how long the topicChirp_aux + /// program will wait (after it advertises) before it begins publishing + /// its message chirps. Value is in milliseconds. + const int DelayBeforePublishing_ms = 1000; + + /// \brief This is the message type that will be used by the chirping + /// topics. + using ChirpMsgType = gz::msgs::Int32; + + ////////////////////////////////////////////////// + /// \brief Similar to testing::forkAndRun(), except this function + /// specifically calls the INTEGRATION_topicChirp_aux process and passes + /// it arguments to determine how it should chirp out messages over its + /// topics. + /// \param[in] _topics A list of topic names to chirp on + /// \param[in] _chirps The number of messages to chirp out. Each message + /// will count up starting from the value 1 and ending with the value + /// _chirps. + /// \param[in] _paritionName Gz transport partition to use for the test + /// \return A handle to the process. This can be used with + /// testing::waitAndCleanupFork(). + gz::utils::Subprocess BeginChirps( + const std::vector &_topics, + const int _chirps, + const std::string &_partitionName); +} // namespace gz::transport::log::test #endif diff --git a/log/test/integration/playback.cc b/log/test/integration/playback.cc index 39bf07498..fb5f89c5a 100644 --- a/log/test/integration/playback.cc +++ b/log/test/integration/playback.cc @@ -24,9 +24,12 @@ #include #include +#include #include "ChirpParams.hh" +#include "test_utils.hh" + static std::string partition; struct MessageInformation @@ -125,11 +128,11 @@ TEST(playback, GZ_UTILS_TEST_DISABLED_ON_MAC(ReplayLog)) recorder.Start(logName)); const int numChirps = 100; - testing::forkHandlerType chirper = + auto chirper = gz::transport::log::test::BeginChirps(topics, numChirps, partition); // Wait for the chirping to finish - testing::waitAndCleanupFork(chirper); + chirper.Join(); // Wait to make sure our callbacks are done processing the incoming messages std::this_thread::sleep_for(std::chrono::seconds(1)); @@ -226,11 +229,11 @@ TEST(playback, GZ_UTILS_TEST_DISABLED_ON_MAC(ReplayLogRegex)) recorder.Start(logName)); const int numChirps = 100; - testing::forkHandlerType chirper = + auto chirper = gz::transport::log::test::BeginChirps(topics, numChirps, partition); // Wait for the chirping to finish - testing::waitAndCleanupFork(chirper); + chirper.Join(); // Wait to make sure our callbacks are done processing the incoming messages std::this_thread::sleep_for(std::chrono::seconds(1)); @@ -293,11 +296,11 @@ TEST(playback, GZ_UTILS_TEST_DISABLED_ON_MAC(RemoveTopic)) recorder.Start(logName)); const int numChirps = 100; - testing::forkHandlerType chirper = + auto chirper = gz::transport::log::test::BeginChirps(topics, numChirps, partition); // Wait for the chirping to finish - testing::waitAndCleanupFork(chirper); + chirper.Join(); // Wait to make sure our callbacks are done processing the incoming messages std::this_thread::sleep_for(std::chrono::seconds(1)); @@ -404,11 +407,11 @@ TEST(playback, GZ_UTILS_TEST_DISABLED_ON_MAC(ReplayLogMoveInstances)) recorder.Start(logName)); const int numChirps = 100; - testing::forkHandlerType chirper = + auto chirper = gz::transport::log::test::BeginChirps(topics, numChirps, partition); // Wait for the chirping to finish - testing::waitAndCleanupFork(chirper); + chirper.Join(); // Wait to make sure our callbacks are done processing the incoming messages std::this_thread::sleep_for(std::chrono::seconds(1)); @@ -471,11 +474,11 @@ TEST(playback, GZ_UTILS_TEST_DISABLED_ON_MAC(ReplayPauseResume)) recorder.Start(logName)); const int numChirps = 100; - testing::forkHandlerType chirper = + auto chirper = gz::transport::log::test::BeginChirps(topics, numChirps, partition); // Wait for the chirping to finish - testing::waitAndCleanupFork(chirper); + chirper.Join(); // Wait to make sure our callbacks are done processing the incoming messages std::this_thread::sleep_for(std::chrono::seconds(1)); @@ -593,11 +596,11 @@ TEST(playback, GZ_UTILS_TEST_DISABLED_ON_MAC(ReplayStep)) recorder.Start(logName)); const int numChirps = 100; - testing::forkHandlerType chirper = + auto chirper = gz::transport::log::test::BeginChirps(topics, numChirps, partition); // Wait for the chirping to finish - testing::waitAndCleanupFork(chirper); + chirper.Join(); // Wait to make sure our callbacks are done processing the incoming messages std::this_thread::sleep_for(std::chrono::seconds(1)); @@ -709,11 +712,11 @@ TEST(playback, GZ_UTILS_TEST_DISABLED_ON_MAC(ReplaySeek)) recorder.Start(logName)); const int numChirps = 100; - testing::forkHandlerType chirper = + auto chirper = gz::transport::log::test::BeginChirps(topics, numChirps, partition); // Wait for the chirping to finish - testing::waitAndCleanupFork(chirper); + chirper.Join(); // Wait to make sure our callbacks are done processing the incoming messages std::this_thread::sleep_for(std::chrono::seconds(1)); diff --git a/log/test/integration/recorder.cc b/log/test/integration/recorder.cc index e23265e33..2773d1515 100644 --- a/log/test/integration/recorder.cc +++ b/log/test/integration/recorder.cc @@ -28,6 +28,8 @@ #include #include +#include "test_utils.hh" + #include "ChirpParams.hh" static std::string partition; @@ -96,11 +98,11 @@ TEST(recorder, EXPECT_EQ(logName, recorder.Filename()); const int numChirps = 100; - testing::forkHandlerType chirper = + auto chirper = gz::transport::log::test::BeginChirps(topics, numChirps, partition); // Wait for the chirping to finish - testing::waitAndCleanupFork(chirper); + chirper.Join(); // Wait to make sure our callbacks are done processing the incoming messages std::this_thread::sleep_for(std::chrono::seconds(1)); @@ -160,7 +162,7 @@ TEST(recorder, BeginRecordingTopicsAfterAdvertisement) const int numChirps = static_cast( std::ceil(secondsToChirpFor * 1000.0/static_cast(delay_ms))); - testing::forkHandlerType chirper = + auto chirper = gz::transport::log::test::BeginChirps(topics, numChirps, partition); const int waitBeforeSubscribing_ms = @@ -182,7 +184,7 @@ TEST(recorder, BeginRecordingTopicsAfterAdvertisement) gz::transport::log::RecorderError::SUCCESS); // Wait for the chirping to finish - testing::waitAndCleanupFork(chirper); + chirper.Join(); // Wait to make sure our callbacks are done processing the incoming messages std::this_thread::sleep_for(std::chrono::seconds(1)); @@ -233,11 +235,11 @@ void RecordPatternBeforeAdvertisement(const std::regex &_pattern) gz::transport::log::RecorderError::SUCCESS); const int numChirps = 100; - testing::forkHandlerType chirper = + auto chirper = gz::transport::log::test::BeginChirps(topics, numChirps, partition); // Wait for the chirping to finish - testing::waitAndCleanupFork(chirper); + chirper.Join(); // Wait to make sure our callbacks are done processing the incoming messages std::this_thread::sleep_for(std::chrono::seconds(1)); diff --git a/log/test/integration/topicChirp_aux.cc b/log/test/integration/topicChirp_aux.cc index 94f2942b3..aa9a18b11 100644 --- a/log/test/integration/topicChirp_aux.cc +++ b/log/test/integration/topicChirp_aux.cc @@ -15,11 +15,10 @@ * */ -#include - #include #include +#include #include #include @@ -40,13 +39,11 @@ void chirp(const std::vector &_topicNames, gz::transport::Node node; - using MsgType = gz::transport::log::test::ChirpMsgType; - std::vector publishers; for (const std::string &topic : _topicNames) { - publishers.push_back(node.Advertise(topic)); + publishers.push_back(node.Advertise(topic)); } std::this_thread::sleep_for( diff --git a/log/test/test_config.hh.in b/log/test/test_config.hh.in deleted file mode 100644 index 9c354ae0b..000000000 --- a/log/test/test_config.hh.in +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2019 Open Source Robotics Foundation - * - * 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. - * -*/ - -#ifndef GZ_TRANSPORT_LOG_TEST_CONFIG_HH_ -#define GZ_TRANSPORT_LOG_TEST_CONFIG_HH_ - - -#define GZ_TRANSPORT_LOG_TEST_PATH "@CMAKE_SOURCE_DIR@/log/test" - -#endif diff --git a/parameters/BUILD.bazel b/parameters/BUILD.bazel new file mode 100644 index 000000000..8a1842d0c --- /dev/null +++ b/parameters/BUILD.bazel @@ -0,0 +1,72 @@ +load( + "@gz//bazel/skylark:build_defs.bzl", + "GZ_FEATURES", + "GZ_ROOT", + "GZ_VISIBILITY", + "cmake_configure_file", + "gz_configure_header", + "gz_export_header", + "gz_include_header", +) +load( + "@gz//bazel/lint:lint.bzl", + "add_lint_tests", +) + +gz_export_header( + name = "include/gz/transport/parameters/Export.hh", + export_base = "GZ_TRANSPORT_PARAMETERS", + lib_name = "gz-transport-parameters", + visibility = ["//visibility:private"], +) + +public_headers_no_gen = glob([ + "include/gz/transport/parameters/*.hh", + "include/gz/transport/parameters/detail/*.hh", +]) + +private_headers = glob(["src/*.hh"]) + +sources = glob( + ["src/*.cc"], + exclude = [ + "src/*_TEST.cc", + ], +) + +public_headers = public_headers_no_gen + [ + "include/gz/transport/parameters/Export.hh", +] + +cc_library( + name = "parameters", + srcs = sources + private_headers, + hdrs = public_headers, + includes = ["include"], + visibility = GZ_VISIBILITY, + deps = [ + GZ_ROOT + "transport", + "@sqlite3", + ], +) + +test_sources = glob( + include = ["src/*_TEST.cc"], +) + +[cc_test( + name = src.replace("/", "_").replace(".cc", "").replace("src_", ""), + srcs = [src], + env = { + "GZ_BAZEL": "1", + "GZ_BAZEL_PATH": "transport", + }, + deps = [ + ":parameters", + GZ_ROOT + "transport/test:utils", + "@gtest", + "@gtest//:gtest_main", + ], +) for src in test_sources] + +add_lint_tests() diff --git a/python/examples/publisher.py b/python/examples/publisher.py index 6cf297108..45b6de3ac 100644 --- a/python/examples/publisher.py +++ b/python/examples/publisher.py @@ -13,9 +13,9 @@ # limitations under the License. # +#! [complete] from gz.msgs11.stringmsg_pb2 import StringMsg from gz.msgs11.vector3d_pb2 import Vector3d -from gz.transport14 import AdvertiseMessageOptions from gz.transport14 import Node import time @@ -39,16 +39,18 @@ def main(): while True: count += 1 vector3d_msg.x = count - if not (pub_stringmsg.publish(stringmsg_msg) or pub_vector3d.publish(vector3d_msg)): + if not pub_stringmsg.publish(stringmsg_msg): break - print("Publishing 'Hello' on topic [{}]".format(stringmsg_topic)) + if not pub_vector3d.publish(vector3d_msg): + break print("Publishing a Vector3d on topic [{}]".format(vector3d_topic)) time.sleep(0.1) except KeyboardInterrupt: pass +#! [complete] if __name__ == "__main__": main() diff --git a/python/examples/requester.py b/python/examples/requester.py index 636b53674..9f53cb49a 100644 --- a/python/examples/requester.py +++ b/python/examples/requester.py @@ -13,6 +13,7 @@ # limitations under the License. # +#! [complete] from gz.msgs11.stringmsg_pb2 import StringMsg from gz.transport14 import Node @@ -27,5 +28,7 @@ def main(): result, response = node.request(service_name, request, StringMsg, StringMsg, timeout) print("Result:", result, "\nResponse:", response.data) +#! [complete] + if __name__ == "__main__": main() diff --git a/python/examples/subscriber.py b/python/examples/subscriber.py index ad3e3ef53..6c7a4750f 100644 --- a/python/examples/subscriber.py +++ b/python/examples/subscriber.py @@ -13,9 +13,9 @@ # limitations under the License. # +#! [complete] from gz.msgs11.stringmsg_pb2 import StringMsg from gz.msgs11.vector3d_pb2 import Vector3d -from gz.transport14 import SubscribeOptions from gz.transport14 import Node import time @@ -56,5 +56,7 @@ def main(): pass print("Done") +#! [complete] + if __name__ == "__main__": main() diff --git a/python/src/transport/_gz_transport_pybind11.cc b/python/src/transport/_gz_transport_pybind11.cc index a7619ba08..fb9717d16 100644 --- a/python/src/transport/_gz_transport_pybind11.cc +++ b/python/src/transport/_gz_transport_pybind11.cc @@ -186,7 +186,7 @@ PYBIND11_MODULE(BINDINGS_MODULE_NAME, m) { py::class_( m, "TopicStatistics", - "This class encapsulates statistics for a single topic..") + "This class encapsulates statistics for a single topic.") .def(py::init<>()); auto node = py::class_(m, "Node", diff --git a/python/test/requester_TEST.py b/python/test/requester_TEST.py index 0f9bdd524..1607630e8 100644 --- a/python/test/requester_TEST.py +++ b/python/test/requester_TEST.py @@ -29,7 +29,7 @@ def setUp(self): os.environ["GZ_PARTITION"] = gz_partition # Subprocess Setup - cmd = f"{os.getenv('CMAKE_BINARY_DIR')}/INTEGRATION_twoProcsSrvCallReplier_aux {gz_partition}" + cmd = f"{os.getenv('CMAKE_BINARY_DIR')}/twoProcsSrvCallReplier_aux {gz_partition}" self.service_process = subprocess.Popen(cmd, shell=True) # Requester Setup diff --git a/src/CIface_TEST.cc b/src/CIface_TEST.cc index 1f1b70c57..b67f3c979 100644 --- a/src/CIface_TEST.cc +++ b/src/CIface_TEST.cc @@ -14,15 +14,15 @@ * limitations under the License. * */ +#include "gtest/gtest.h" + #include #include "gz/transport/CIface.h" +#include "test_utils.hh" #include -#include "gtest/gtest.h" -#include "test_config.hh" - static int count; ////////////////////////////////////////////////// diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cfb71dd55..13101c508 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,7 +16,7 @@ target_link_libraries(${PROJECT_LIBRARY_TARGET_NAME} ) target_include_directories(${PROJECT_LIBRARY_TARGET_NAME} - SYSTEM PUBLIC + SYSTEM PUBLIC $ $) @@ -31,17 +31,10 @@ endif() # Build the unit tests. gz_build_tests(TYPE UNIT SOURCES ${gtest_sources} TEST_LIST test_list - LIB_DEPS ${EXTRA_TEST_LIB_DEPS}) + LIB_DEPS ${EXTRA_TEST_LIB_DEPS} test_config) foreach(test ${test_list}) - - # Inform each test of its output directory so it knows where to call the - # auxiliary files from. Using a generator expression here is useful for - # multi-configuration generators, like Visual Studio. - target_compile_definitions(${test} PRIVATE - "DETAIL_GZ_TRANSPORT_TEST_DIR=\"$\"" - "GZ_TEST_LIBRARY_PATH=\"$\"") - + set_property(TEST ${test} PROPERTY ENVIRONMENT "GZ_VERBOSE=1") endforeach() if(MSVC) diff --git a/src/Clock_TEST.cc b/src/Clock_TEST.cc index 0c80cc487..22547b2cd 100644 --- a/src/Clock_TEST.cc +++ b/src/Clock_TEST.cc @@ -24,7 +24,6 @@ #include "gz/transport/Clock.hh" #include "gz/transport/Node.hh" #include "gz/transport/TransportTypes.hh" -#include "test_config.hh" #include "gtest/gtest.h" using namespace gz; diff --git a/src/Discovery_TEST.cc b/src/Discovery_TEST.cc index 24d1882ff..5c889f784 100644 --- a/src/Discovery_TEST.cc +++ b/src/Discovery_TEST.cc @@ -14,6 +14,7 @@ * limitations under the License. * */ +#include "gtest/gtest.h" #include #include @@ -21,18 +22,16 @@ #include #include -#include "gtest/gtest.h" #include "gz/transport/AdvertiseOptions.hh" #include "gz/transport/Discovery.hh" #include "gz/transport/Publisher.hh" #include "gz/transport/TransportTypes.hh" #include "gz/transport/Uuid.hh" +#include "test_utils.hh" #include "gz/utils/Environment.hh" #include "gz/utils/ExtraTestMacros.hh" -#include "test_config.hh" - using namespace gz; using namespace transport; diff --git a/src/Helpers_TEST.cc b/src/Helpers_TEST.cc index 518090e11..c5674f325 100644 --- a/src/Helpers_TEST.cc +++ b/src/Helpers_TEST.cc @@ -14,14 +14,13 @@ * limitations under the License. * */ +#include "gtest/gtest.h" #include "gz/transport/Helpers.hh" +#include "test_utils.hh" #include -#include "test_config.hh" -#include "gtest/gtest.h" - using namespace gz; ////////////////////////////////////////////////// diff --git a/src/NodeOptions_TEST.cc b/src/NodeOptions_TEST.cc index d28a40e5a..96a870e60 100644 --- a/src/NodeOptions_TEST.cc +++ b/src/NodeOptions_TEST.cc @@ -15,6 +15,8 @@ * */ +#include "gtest/gtest.h" + #include #include "gz/transport/NetUtils.hh" @@ -22,9 +24,6 @@ #include -#include "test_config.hh" -#include "gtest/gtest.h" - using namespace gz; ////////////////////////////////////////////////// diff --git a/src/Node_TEST.cc b/src/Node_TEST.cc index 80427368e..6c0e2435a 100644 --- a/src/Node_TEST.cc +++ b/src/Node_TEST.cc @@ -14,6 +14,8 @@ * limitations under the License. * */ +#include "gtest/gtest.h" + #include #include #include @@ -26,18 +28,13 @@ #include #include -#include "gz/transport/AdvertiseOptions.hh" #include "gz/transport/MessageInfo.hh" #include "gz/transport/Node.hh" -#include "gz/transport/NodeOptions.hh" -#include "gz/transport/TopicStatistics.hh" -#include "gz/transport/TopicUtils.hh" #include "gz/transport/TransportTypes.hh" #include -#include "gtest/gtest.h" -#include "test_config.hh" +#include "test_utils.hh" using namespace gz; @@ -433,6 +430,23 @@ class MyTestClass EXPECT_FALSE(this->callbackSrvExecuted); } + /// \brief Advertise and request a service without waiting for response. + public: void TestServiceCallRequestingBeforeAdvertising() + { + msgs::Int32 req; + req.set_data(data); + + this->Reset(); + + // Request a valid service using a member function callback. + this->node.Request(g_topic, req, &MyTestClass::EchoResponse, this); + + // Advertise and request a valid service. + EXPECT_TRUE(this->node.Advertise(g_topic, &MyTestClass::Echo, this)); + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + EXPECT_TRUE(this->responseExecuted); + } + public: void Reset() { this->callbackExecuted = false; @@ -1154,6 +1168,14 @@ TEST(NodeTest, ClassMemberCallbackServiceWithoutInput) client.TestServiceCallWithoutInput(); } +////////////////////////////////////////////////// +/// \brief Make an asynchronous service call, and then, advertise the service. +TEST(NodeTest, ClassMemberRequestServiceBeforeAdvertise) +{ + MyTestClass client; + client.TestServiceCallRequestingBeforeAdvertising(); +} + ////////////////////////////////////////////////// /// \brief Check that the types advertised and published match. TEST(NodeTest, TypeMismatch) diff --git a/src/SubscribeOptions_TEST.cc b/src/SubscribeOptions_TEST.cc index 8e7ccb780..d6bb9666f 100644 --- a/src/SubscribeOptions_TEST.cc +++ b/src/SubscribeOptions_TEST.cc @@ -17,7 +17,6 @@ #include "gz/transport/Helpers.hh" #include "gz/transport/SubscribeOptions.hh" -#include "test_config.hh" #include "gtest/gtest.h" using namespace gz; diff --git a/src/cmd/CMakeLists.txt b/src/cmd/CMakeLists.txt index 269d66f38..33433909b 100644 --- a/src/cmd/CMakeLists.txt +++ b/src/cmd/CMakeLists.txt @@ -8,6 +8,10 @@ if (MSVC) list(REMOVE_ITEM gtest_sources gz_TEST.cc) endif() +if (NOT HAVE_GZ_TOOLS) + list(REMOVE_ITEM gtest_sources gz_TEST.cc) +endif() + # Make a small static lib of command line functions add_library(gz STATIC gz.cc) target_link_libraries(gz @@ -37,27 +41,31 @@ install(TARGETS ${service_executable} DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/gz # Build the unit tests. gz_build_tests(TYPE UNIT SOURCES ${gtest_sources} TEST_LIST test_list - LIB_DEPS ${EXTRA_TEST_LIB_DEPS}) + LIB_DEPS + gz + gz-utils${GZ_UTILS_VER}::gz-utils${GZ_UTILS_VER} + ${EXTRA_TEST_LIB_DEPS} test_config) foreach(test ${test_list}) - target_link_libraries(${test} gz) - # Inform each test of its output directory so it knows where to call the # auxiliary files from. Using a generator expression here is useful for # multi-configuration generators, like Visual Studio. target_compile_definitions(${test} PRIVATE - "DETAIL_GZ_TRANSPORT_TEST_DIR=\"$\"" "GZ_TEST_LIBRARY_PATH=\"$\"" - "PROJECT_SOURCE_DIR=\"${PROJECT_SOURCE_DIR}\"") - + "PROJECT_SOURCE_DIR=\"${PROJECT_SOURCE_DIR}\"" + "TWO_PROCS_PUBLISHER_EXE=\"$\"" + "TWO_PROCS_SRV_CALL_REPLIER_EXE=\"$\"" + "GZ_EXE=\"${HAVE_GZ_TOOLS}\"" + ) endforeach() if (TARGET UNIT_gz_TEST) + set_tests_properties( UNIT_gz_TEST PROPERTIES ENVIRONMENT - "GZ_CONFIG_PATH=${CMAKE_BINARY_DIR}/test/conf/$" + "GZ_CONFIG_PATH=${CMAKE_BINARY_DIR}/test/conf/$;LD_LIBRARY_PATH=\"$\":${LD_LIBRARY_PATH}" ) endif() diff --git a/src/cmd/gz_TEST.cc b/src/cmd/gz_TEST.cc index 9a4bae4a4..0b28e0ec3 100644 --- a/src/cmd/gz_TEST.cc +++ b/src/cmd/gz_TEST.cc @@ -25,44 +25,18 @@ #include #include +#include #include "gtest/gtest.h" #include "gz/transport/Node.hh" -#include "test_config.hh" -#ifdef _MSC_VER -# define popen _popen -# define pclose _pclose -#endif +#include "test_config.hh" +#include "test_utils.hh" using namespace gz; static std::string g_partition; // NOLINT(*) static std::string g_topicCBStr; // NOLINT(*) -static const std::string g_gzVersion("--force-version " + // NOLINT(*) - std::string(GZ_VERSION_FULL)); - -///////////////////////////////////////////////// -std::string custom_exec_str(std::string _cmd) -{ - _cmd += " 2>&1"; - FILE *pipe = popen(_cmd.c_str(), "r"); - - if (!pipe) - return "ERROR"; - - char buffer[128]; - std::string result = ""; - - while (!feof(pipe)) - { - if (fgets(buffer, 128, pipe) != NULL) - result += buffer; - } - - pclose(pipe); - return result; -} ////////////////////////////////////////////////// /// \brief Provide a service. @@ -93,34 +67,57 @@ void cbRaw(const char * /*_msgData*/, const size_t /*_size*/, } ////////////////////////////////////////////////// -/// \brief Check 'gz topic -l' running the advertiser on a different process. -TEST(gzTest, GZ_UTILS_TEST_DISABLED_ON_MAC(TopicList)) +struct ProcessOutput { - // Launch a new publisher process that advertises a topic. - std::string publisher_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsPublisher_aux"); + int code {-1}; + std::string cout; + std::string cerr; +}; - testing::forkHandlerType pi = testing::forkAndRun(publisher_path.c_str(), - g_partition.c_str()); - - // Check the 'gz topic -l' command. - std::string gz = std::string(GZ_PATH); +////////////////////////////////////////////////// +ProcessOutput custom_exec_str(const std::vector &_args) +{ + auto fullArgs = std::vector{test_executables::kGzExe}; + std::copy(std::begin(_args), std::end(_args), std::back_inserter(fullArgs)); + fullArgs.emplace_back("--force-version"); + fullArgs.emplace_back(kGzVersion); + auto proc = gz::utils::Subprocess(fullArgs); + auto return_code = proc.Join(); + return {return_code, proc.Stdout(), proc.Stderr()}; +} - unsigned int retries = 0u; - bool topicFound = false; +////////////////////////////////////////////////// +std::optional +exec_with_retry(const std::vector &_args, + const std::function &_condition) +{ + bool success = false; + int retries = 0; - while (!topicFound && retries++ < 10u) + while (!success && retries++ < 10) { - std::string output = custom_exec_str(gz + " topic -l " + g_gzVersion); - topicFound = output == "/foo\n"; + auto output = custom_exec_str(_args); + success = _condition(output); + if (success) + return output; std::this_thread::sleep_for(std::chrono::milliseconds(300)); } + return {}; +} - EXPECT_TRUE(topicFound); +////////////////////////////////////////////////// +/// \brief Check 'gz topic -l' running the advertiser on a different process. +TEST(gzTest, GZ_UTILS_TEST_DISABLED_ON_MAC(TopicList)) +{ + auto proc = gz::utils::Subprocess({ + test_executables::kTwoProcsPublisher, g_partition}); - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); + auto output = exec_with_retry({"topic", "-l"}, + [](auto procOut){ + return procOut.cout == "/foo\n"; + }); + + EXPECT_TRUE(output); } ////////////////////////////////////////////////// @@ -134,23 +131,15 @@ TEST(gzTest, TopicListSub) node.Subscribe("/no", topicCB); node.Unsubscribe("/no"); - // Check the 'gz topic -l' command. - std::string gz = std::string(GZ_PATH); - - unsigned int retries = 0u; - bool topicFound = false; - - while (!topicFound && retries++ < 10u) - { - std::string output = custom_exec_str(gz + " topic -l " + g_gzVersion); - topicFound = output.find("/foo\n") != std::string::npos; - topicFound &= output.find("/bar\n") != std::string::npos; - topicFound &= output.find("/baz\n") != std::string::npos; - topicFound &= output.find("/no\n") == std::string::npos; - std::this_thread::sleep_for(std::chrono::milliseconds(300)); - } + auto output = exec_with_retry({"topic", "-l"}, + [](auto procOut){ + return procOut.cout.find("/foo\n") != std::string::npos && + procOut.cout.find("/bar\n") != std::string::npos && + procOut.cout.find("/baz\n") != std::string::npos && + procOut.cout.find("/no\n") == std::string::npos; + }); - EXPECT_TRUE(topicFound); + EXPECT_TRUE(output); } ////////////////////////////////////////////////// @@ -158,70 +147,19 @@ TEST(gzTest, TopicListSub) TEST(gzTest, TopicInfo) { // Launch a new publisher process that advertises a topic. - std::string publisher_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsPublisher_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisher_path.c_str(), - g_partition.c_str()); - - // Check the 'gz topic -i' command. - std::string gz = std::string(GZ_PATH); - - unsigned int retries = 0u; - bool infoFound = false; - std::string output; - - while (!infoFound && retries++ < 10u) - { - output = custom_exec_str(gz + " topic -t /foo -i " + g_gzVersion); - bool pubsFound = output.find("No publishers") == std::string::npos; - bool subsFound = output.find("No subscribers") == std::string::npos; - // We should have publishers info but no subscribers. - infoFound = pubsFound && !subsFound; - std::this_thread::sleep_for(std::chrono::milliseconds(300)); - } - - EXPECT_TRUE(infoFound); - EXPECT_TRUE(output.find("gz.msgs.Vector3d") != std::string::npos); + auto proc = gz::utils::Subprocess({ + test_executables::kTwoProcsPublisher, g_partition}); - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); -} - -////////////////////////////////////////////////// -/// \brief Check 'gz topic -i' running a subscriber on a different process. -TEST(gzTest, TopicInfoSub) -{ - transport::Node node; - node.Subscribe("/foo", topicCB); - node.SubscribeRaw("/baz", cbRaw, msgs::StringMsg().GetTypeName()); - node.Subscribe("/no", topicCB); - node.Unsubscribe("/no"); + auto output = exec_with_retry({"topic", "-t", "/foo", "-i"}, + [](auto procOut){ + return procOut.cout.size() > 50u; + }); - // Check the 'gz topic -i' command. - std::string gz = std::string(GZ_PATH); - for (auto topic : {"/foo", "/baz"}) - { - unsigned int retries = 0u; - bool infoFound = false; - std::string output; - - while (!infoFound && retries++ < 10u) - { - output = custom_exec_str(gz + " topic -i -t " + topic + " " - + g_gzVersion); - bool pubsFound = output.find("No publishers") == std::string::npos; - bool subsFound = output.find("No subscribers") == std::string::npos; - // We should have subscribers info but no publishers. - infoFound = !pubsFound && subsFound; - std::this_thread::sleep_for(std::chrono::milliseconds(300)); - } - - EXPECT_TRUE(infoFound); - EXPECT_TRUE(output.find("gz.msgs.") != std::string::npos); - } + ASSERT_TRUE(output) << "OUTPUT[" + << output->cout << "] Size[" << output->cout.size() + << "]. Expected Size=50" << std::endl; + EXPECT_TRUE(output->cout.find("gz.msgs.Vector3d") != std::string::npos); } ////////////////////////////////////////////////// @@ -230,63 +168,32 @@ TEST(gzTest, TopicInfoSub) TEST(gzTest, ServiceList) { // Launch a new responser process that advertises a service. - std::string replier_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(replier_path.c_str(), - g_partition.c_str()); - - // Check the 'gz service -l' command. - std::string gz = std::string(GZ_PATH); - - unsigned int retries = 0u; - bool serviceFound = false; - - while (!serviceFound && retries++ < 10u) - { - std::string output = custom_exec_str(gz + " service -l " + g_gzVersion); - serviceFound = output == "/foo\n"; - std::this_thread::sleep_for(std::chrono::milliseconds(300)); - } + auto proc = gz::utils::Subprocess({ + test_executables::kTwoProcsSrvCallReplier, g_partition}); - EXPECT_TRUE(serviceFound); + auto output = exec_with_retry({"service", "-l"}, + [](auto procOut){ + return procOut.cout == "/foo\n"; + }); - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); + EXPECT_TRUE(output); } ////////////////////////////////////////////////// /// \brief Check 'gz service -i' running the advertiser on a different process. TEST(gzTest, ServiceInfo) { - // Launch a new publisher process that advertises a topic. - std::string replier_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(replier_path.c_str(), - g_partition.c_str()); - - // Check the 'gz service -i' command. - std::string gz = std::string(GZ_PATH); - - unsigned int retries = 0u; - bool infoFound = false; - std::string output; - - while (!infoFound && retries++ < 10u) - { - output = custom_exec_str(gz + " service -s /foo -i " + g_gzVersion); - infoFound = output.size() > 50u; - std::this_thread::sleep_for(std::chrono::milliseconds(300)); - } + // Launch a new responser process that advertises a service. + auto proc = gz::utils::Subprocess( + {test_executables::kTwoProcsSrvCallReplier, g_partition}); - EXPECT_TRUE(infoFound); - EXPECT_TRUE(output.find("gz.msgs.Int32") != std::string::npos); + auto output = exec_with_retry({"service", "-s", "/foo", "-i"}, + [](auto procOut){ + return procOut.cout.size() > 50u; + }); - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); + ASSERT_TRUE(output); + EXPECT_TRUE(output->cout.find("gz.msgs.Int32") != std::string::npos); } ////////////////////////////////////////////////// @@ -304,20 +211,12 @@ TEST(gzTest, TopicListSameProc) EXPECT_TRUE(pub); EXPECT_TRUE(pub.Publish(msg)); - // Check the 'gz topic -l' command. - std::string gz = std::string(GZ_PATH); - - unsigned int retries = 0u; - bool topicFound = false; - - while (!topicFound && retries++ < 10u) - { - std::string output = custom_exec_str(gz + " topic -l " + g_gzVersion); - topicFound = output == "/foo\n"; - std::this_thread::sleep_for(std::chrono::milliseconds(300)); - } + auto output = exec_with_retry({"topic", "-l"}, + [](auto procOut){ + return procOut.cout == "/foo\n"; + }); - EXPECT_TRUE(topicFound); + EXPECT_TRUE(output); } ////////////////////////////////////////////////// @@ -335,22 +234,13 @@ TEST(gzTest, TopicInfoSameProc) EXPECT_TRUE(pub); EXPECT_TRUE(pub.Publish(msg)); - // Check the 'gz topic -i' command. - std::string gz = std::string(GZ_PATH); - - unsigned int retries = 0u; - bool infoFound = false; - std::string output; + auto output = exec_with_retry({"topic", "-t", "/foo", "-i"}, + [](auto procOut){ + return procOut.cout.size() > 50u; + }); - while (!infoFound && retries++ < 10u) - { - output = custom_exec_str(gz + " topic -t /foo -i " + g_gzVersion); - infoFound = output.size() > 60u; - std::this_thread::sleep_for(std::chrono::milliseconds(300)); - } - - EXPECT_TRUE(infoFound); - EXPECT_TRUE(output.find("gz.msgs.Vector3d") != std::string::npos); + ASSERT_TRUE(output); + EXPECT_TRUE(output->cout.find("gz.msgs.Vector3d") != std::string::npos); } ////////////////////////////////////////////////// @@ -360,20 +250,12 @@ TEST(gzTest, ServiceListSameProc) transport::Node node; EXPECT_TRUE(node.Advertise("/foo", srvEcho)); - // Check the 'gz service -l' command. - std::string gz = std::string(GZ_PATH); - - unsigned int retries = 0u; - bool serviceFound = false; + auto output = exec_with_retry({"service", "-l"}, + [](auto procOut){ + return procOut.cout == "/foo\n"; + }); - while (!serviceFound && retries++ < 10u) - { - std::string output = custom_exec_str(gz + " service -l " + g_gzVersion); - serviceFound = output == "/foo\n"; - std::this_thread::sleep_for(std::chrono::milliseconds(300)); - } - - EXPECT_TRUE(serviceFound); + EXPECT_TRUE(output); } ////////////////////////////////////////////////// @@ -383,25 +265,15 @@ TEST(gzTest, ServiceInfoSameProc) transport::Node node; EXPECT_TRUE(node.Advertise("/foo", srvEcho)); - // Check the 'gz service -i' command. - std::string gz = std::string(GZ_PATH); - - unsigned int retries = 0u; - bool infoFound = false; - std::string output; - - while (!infoFound && retries++ < 10u) - { - output = custom_exec_str(gz + " service -s /foo -i " + g_gzVersion); - infoFound = output.size() > 50u; - std::this_thread::sleep_for(std::chrono::milliseconds(300)); - } + auto output = exec_with_retry({"service", "-s", "/foo", "-i"}, + [](auto procOut){ + return procOut.cout.size() > 50u; + }); - EXPECT_TRUE(infoFound); - EXPECT_TRUE(output.find("gz.msgs.Int32") != std::string::npos); + ASSERT_TRUE(output); + EXPECT_TRUE(output->cout.find("gz.msgs.Int32") != std::string::npos); } - ////////////////////////////////////////////////// /// \brief Check 'gz topic -p' to send a message. TEST(gzTest, TopicPublish) @@ -410,45 +282,48 @@ TEST(gzTest, TopicPublish) g_topicCBStr = "bad_value"; EXPECT_TRUE(node.Subscribe("/bar", topicCB)); - // Check the 'gz topic -p' command. - std::string gz = std::string(GZ_PATH); - std::string output; - unsigned int retries = 0; - while (g_topicCBStr != "good_value" && retries++ < 200u) + while (retries++ < 100u) { - // Send on alternating retries - if (retries % 2) - { - output = custom_exec_str(gz + - " topic -t /bar -m gz_msgs.StringMsg -p 'data:\"good_value\"' " + - g_gzVersion); - EXPECT_TRUE(output.empty()) << output; - } - std::this_thread::sleep_for(std::chrono::milliseconds(30)); + auto output = custom_exec_str({"topic", + "-t", "/bar", + "-m", "gz.msgs.StringMsg", + "-p", "data: \"good_value\""}); + + EXPECT_TRUE(output.cout.empty()) << output.cout; + EXPECT_TRUE(output.cerr.empty()) << output.cerr; + if (g_topicCBStr == "good_value") + break; + std::this_thread::sleep_for(std::chrono::milliseconds(60)); } EXPECT_EQ(g_topicCBStr, "good_value"); // Try to publish a message not included in Gazebo Messages. std::string error = "Unable to create message of type"; - output = custom_exec_str(gz + - " topic -t /bar -m gz_msgs.__bad_msg_type -p 'data:\"good_value\"' " + - g_gzVersion); - EXPECT_EQ(output.compare(0, error.size(), error), 0); + auto output = custom_exec_str({"topic", + "-t", "/bar", + "-m", "gz.msgs.__bad_msg_type", + "-p", R"(data: "good_value")"}); + EXPECT_EQ(output.cerr.compare(0, error.size(), error), 0) + << "error {" << error << "}, output.cerr {" << output.cerr << "}"; // Try to publish using an incorrect topic name. error = "Topic [/] is not valid"; - output = custom_exec_str(gz + - " topic -t / -m gz_msgs.StringMsg -p 'data:\"good_value\"' "+ - g_gzVersion); - EXPECT_EQ(output.compare(0, error.size(), error), 0) << output; + output = custom_exec_str({"topic", + "-t", "/", + "-m", "gz.msgs.StringMsg", + "-p", R"(data: "good_value")"}); + EXPECT_EQ(output.cerr.compare(0, error.size(), error), 0) + << "error {" << error << "}, output.cerr {" << output.cerr << "}"; // Try to publish using an incorrect number of arguments. error = "The following argument was not expected: wrong_topic"; - output = custom_exec_str(gz + - " topic -t / wrong_topic -m gz_msgs.StringMsg -p 'data:\"good_value\"' "+ - g_gzVersion); - EXPECT_EQ(output.compare(0, error.size(), error), 0) << output; + output = custom_exec_str({"topic", + "-t", "/", "wrong_topic", + "-m", "gz.msgs.StringMsg", + "-p", R"(data: "good_value")"}); + EXPECT_EQ(output.cerr.compare(0, error.size(), error), 0) + << "error {" << error << "}, output.cerr {" << output.cerr << "}"; } ////////////////////////////////////////////////// @@ -466,13 +341,13 @@ TEST(gzTest, ServiceRequest) msg.set_data(10); // Check the 'gz service -r' command. - std::string gz = std::string(GZ_PATH); - std::string output = custom_exec_str(gz + - " service -s " + service + " --reqtype gz_msgs.Int32 " + - "--reptype gz_msgs.Int32 --timeout 1000 " + - "--req 'data: " + value + "' " + g_gzVersion); - - ASSERT_EQ(output, "data: " + value + "\n\n"); + auto output = custom_exec_str({"service", + "-s", service, + "--reqtype", "gz_msgs.Int32", + "--reptype", "gz_msgs.Int32", + "--timeout", "1000", + "--req", "data: " + value}); + ASSERT_EQ(output.cout, "data: " + value + "\n\n"); } ////////////////////////////////////////////////// @@ -480,24 +355,15 @@ TEST(gzTest, ServiceRequest) TEST(gzTest, TopicEcho) { // Launch a new publisher process that advertises a topic. - std::string publisher_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsPublisher_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisher_path.c_str(), - g_partition.c_str()); - - // Check the 'gz topic -e' command. - std::string gz = std::string(GZ_PATH); - std::string output = custom_exec_str( - gz + " topic -e -t /foo -d 1.5 " + g_gzVersion); + auto proc = gz::utils::Subprocess( + {test_executables::kTwoProcsPublisher, g_partition}); - EXPECT_TRUE(output.find("x: 1") != std::string::npos); - EXPECT_TRUE(output.find("y: 2") != std::string::npos); - EXPECT_TRUE(output.find("z: 3") != std::string::npos); + auto output = custom_exec_str( + {"topic", "-e", "-t", "/foo", "-d", "1.5"}); - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); + EXPECT_TRUE(output.cout.find("x: 1") != std::string::npos); + EXPECT_TRUE(output.cout.find("y: 2") != std::string::npos); + EXPECT_TRUE(output.cout.find("z: 3") != std::string::npos); } ////////////////////////////////////////////////// @@ -506,110 +372,100 @@ TEST(gzTest, TopicEcho) TEST(gzTest, TopicEchoNum) { // Launch a new publisher process that advertises a topic. - std::string publisher_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsPublisher_aux"); + auto proc = gz::utils::Subprocess( + {test_executables::kTwoProcsPublisher, g_partition}); - testing::forkHandlerType pi = testing::forkAndRun(publisher_path.c_str(), - g_partition.c_str()); + auto output = custom_exec_str( + {"topic", "-e", "-t", "/foo", "-n", "2"}); - // Check the 'gz topic -e -n' command. - std::string gz = std::string(GZ_PATH); - std::string output = custom_exec_str( - gz + " topic -e -t /foo -n 2 " + g_gzVersion); - - size_t pos = output.find("x: 1"); + size_t pos = output.cout.find("x: 1"); EXPECT_TRUE(pos != std::string::npos); - pos = output.find("x: 1", pos + 4); + pos = output.cout.find("x: 1", pos + 4); EXPECT_TRUE(pos != std::string::npos); - pos = output.find("x: 1", pos + 4); + pos = output.cout.find("x: 1", pos + 4); EXPECT_TRUE(pos == std::string::npos); - pos = output.find("y: 2"); + pos = output.cout.find("y: 2"); EXPECT_TRUE(pos != std::string::npos); - pos = output.find("y: 2", pos + 4); + pos = output.cout.find("y: 2", pos + 4); EXPECT_TRUE(pos != std::string::npos); - pos = output.find("y: 2", pos + 4); + pos = output.cout.find("y: 2", pos + 4); EXPECT_TRUE(pos == std::string::npos); - pos = output.find("z: 3"); + pos = output.cout.find("z: 3"); EXPECT_TRUE(pos != std::string::npos); - pos = output.find("z: 3", pos + 4); + pos = output.cout.find("z: 3", pos + 4); EXPECT_TRUE(pos != std::string::npos); - pos = output.find("z: 3", pos + 4); + pos = output.cout.find("z: 3", pos + 4); EXPECT_TRUE(pos == std::string::npos); - - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// /// \brief Check 'gz service --help' message and bash completion script for /// consistent flags -TEST(gzTest, ServiceHelpVsCompletionFlags) -{ - // Flags in help message - std::string helpOutput = custom_exec_str("gz service --help"); - - // Call the output function in the bash completion script - std::filesystem::path scriptPath = PROJECT_SOURCE_DIR; - scriptPath = scriptPath / "src" / "cmd" / "transport.bash_completion.sh"; - - // Equivalent to: - // sh -c "bash -c \". /path/to/transport.bash_completion.sh; - // _gz_service_flags\"" - std::string cmd = "bash -c \". " + scriptPath.string() + - "; _gz_service_flags\""; - std::string scriptOutput = custom_exec_str(cmd); - - // Tokenize script output - std::istringstream iss(scriptOutput); - std::vector flags((std::istream_iterator(iss)), - std::istream_iterator()); - - EXPECT_GT(flags.size(), 0u); - - // Match each flag in script output with help message - for (const auto &flag : flags) - { - EXPECT_NE(std::string::npos, helpOutput.find(flag)) << helpOutput; - } -} +// TEST(gzTest, ServiceHelpVsCompletionFlags) +// { +// // Flags in help message +// std::string helpOutput = custom_exec_str("gz service --help"); + +// // Call the output function in the bash completion script +// std::filesystem::path scriptPath = PROJECT_SOURCE_DIR; +// scriptPath = scriptPath / "src" / "cmd" / "transport.bash_completion.sh"; + +// // Equivalent to: +// // sh -c "bash -c \". /path/to/transport.bash_completion.sh; +// // _gz_service_flags\"" +// std::string cmd = "bash -c \". " + scriptPath.string() + +// "; _gz_service_flags\""; +// std::string scriptOutput = custom_exec_str(cmd); + +// // Tokenize script output +// std::istringstream iss(scriptOutput); +// std::vector flags((std::istream_iterator(iss)), +// std::istream_iterator()); + +// EXPECT_GT(flags.size(), 0u); + +// // Match each flag in script output with help message +// for (const auto &flag : flags) +// { +// EXPECT_NE(std::string::npos, helpOutput.find(flag)) << helpOutput; +// } +// } ////////////////////////////////////////////////// /// \brief Check 'gz topic --help' message and bash completion script for /// consistent flags -TEST(gzTest, TopicHelpVsCompletionFlags) -{ - // Flags in help message - std::string helpOutput = custom_exec_str("gz topic --help"); - - // Call the output function in the bash completion script - std::filesystem::path scriptPath = PROJECT_SOURCE_DIR; - scriptPath = scriptPath / "src" / "cmd" / "transport.bash_completion.sh"; - - // Equivalent to: - // sh -c "bash -c \". /path/to/transport.bash_completion.sh; - // _gz_topic_flags\"" - std::string cmd = "bash -c \". " + scriptPath.string() + - "; _gz_topic_flags\""; - std::string scriptOutput = custom_exec_str(cmd); +// TEST(gzTest, TopicHelpVsCompletionFlags) +// { +// // Flags in help message +// std::string helpOutput = custom_exec_str("gz topic --help"); + +// // Call the output function in the bash completion script +// std::filesystem::path scriptPath = PROJECT_SOURCE_DIR; +// scriptPath = scriptPath / "src" / "cmd" / "transport.bash_completion.sh"; + +// // Equivalent to: +// // sh -c "bash -c \". /path/to/transport.bash_completion.sh; +// // _gz_topic_flags\"" +// std::string cmd = "bash -c \". " + scriptPath.string() + +// "; _gz_topic_flags\""; +// std::string scriptOutput = custom_exec_str(cmd); + +// // Tokenize script output +// std::istringstream iss(scriptOutput); +// std::vector flags((std::istream_iterator(iss)), +// std::istream_iterator()); + +// EXPECT_GT(flags.size(), 0u); + +// // Match each flag in script output with help message +// for (const auto &flag : flags) +// { +// EXPECT_NE(std::string::npos, helpOutput.find(flag)) << helpOutput; +// } +// } - // Tokenize script output - std::istringstream iss(scriptOutput); - std::vector flags((std::istream_iterator(iss)), - std::istream_iterator()); - - EXPECT_GT(flags.size(), 0u); - - // Match each flag in script output with help message - for (const auto &flag : flags) - { - EXPECT_NE(std::string::npos, helpOutput.find(flag)) << helpOutput; - } -} - -///////////////////////////////////////////////// /// Main int main(int argc, char **argv) { @@ -619,15 +475,6 @@ int main(int argc, char **argv) // Set the partition name for this process. gz::utils::setenv("GZ_PARTITION", g_partition); - // Make sure that we load the library recently built and not the one installed - // in your system. - // Save the current value of LD_LIBRARY_PATH. - std::string value = ""; - transport::env("LD_LIBRARY_PATH", value); - // Add the directory where Gazebo Transport has been built. - value = std::string(GZ_TEST_LIBRARY_PATH) + ":" + value; - gz::utils::setenv("LD_LIBRARY_PATH", value); - ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/src/cmd/gz_src_TEST.cc b/src/cmd/gz_src_TEST.cc index 8caf0c59d..f4141aa36 100644 --- a/src/cmd/gz_src_TEST.cc +++ b/src/cmd/gz_src_TEST.cc @@ -14,27 +14,21 @@ * limitations under the License. * */ +#include "gtest/gtest.h" + +#include #include #include #include #include -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4251) -#endif -#include -#ifdef _MSC_VER -#pragma warning(pop) -#endif #include "gz.hh" #include "gz/transport/Node.hh" #include -#include "test_config.hh" -#include "gtest/gtest.h" +#include "test_utils.hh" using namespace gz; diff --git a/src/cmd/transport.bash_completion.sh b/src/cmd/transport.bash_completion.sh index 5438a6579..f51a965cd 100644 --- a/src/cmd/transport.bash_completion.sh +++ b/src/cmd/transport.bash_completion.sh @@ -20,6 +20,34 @@ # This is a per-library function definition, used in conjunction with the # top-level entry point in gz-tools. +GZ_LOG_SUBCOMMANDS=" +record +playback +" + +GZ_LOG_COMPLETION_LIST=" + -h --help + -v --verbose +" + +GZ_PLAYBACK_COMPLETION_LIST=" + -h --help + -v --verbose + --file + --pattern + --remap + --wait + -f +" + +GZ_RECORD_COMPLETION_LIST=" + -h --help + -v --verbose + --file + --force + --pattern +" + GZ_SERVICE_COMPLETION_LIST=" -h --help -v --version @@ -46,11 +74,10 @@ GZ_TOPIC_COMPLETION_LIST=" --json-output " -function _gz_service -{ +function __get_comp_from_list { if [[ ${COMP_WORDS[COMP_CWORD]} == -* ]]; then # Specify options (-*) word list for this subcommand - COMPREPLY=($(compgen -W "$GZ_SERVICE_COMPLETION_LIST" \ + COMPREPLY=($(compgen -W "$@" \ -- "${COMP_WORDS[COMP_CWORD]}" )) return else @@ -62,29 +89,75 @@ function _gz_service fi } -function _gz_service_flags +function _gz_log_playback { - for word in $GZ_SERVICE_COMPLETION_LIST; do - echo "$word" - done + __get_comp_from_list "$GZ_PLAYBACK_COMPLETION_LIST" +} + +function _gz_log_record +{ + __get_comp_from_list "$GZ_RECORD_COMPLETION_LIST" +} + +function _gz_service +{ + __get_comp_from_list "$GZ_SERVICE_COMPLETION_LIST" } function _gz_topic { + __get_comp_from_list "$GZ_TOPIC_COMPLETION_LIST" +} + +# This searches the current list of typed words for one of the subcommands +# listed in GZ_LOG_SUBCOMMANDS. This should work for most cases, but may fail +# if a word that looks like a subcommand is used as an argument to a flag. +function __get_subcommand +{ + local known_subcmd + local subcmd + for ((i=2; $i<=$COMP_CWORD; i++)); do + for subcmd in $GZ_LOG_SUBCOMMANDS; do + if [[ "${COMP_WORDS[i]}" == "$subcmd" ]]; then + known_subcmd="$subcmd" + fi + done + done + echo "$known_subcmd" +} + +function _gz_log +{ + if [[ $COMP_CWORD > 2 ]]; then + local known_subcmd=$(__get_subcommand) + if [[ ! -z $known_subcmd ]]; then + local subcmd_func="_gz_log_$known_subcmd" + if [[ "$(type -t $subcmd_func)" == 'function' ]]; then + $subcmd_func + return + fi + fi + fi + + # If a subcommand is not found, assume we're still completing the subcommands + # or flags for `log`. if [[ ${COMP_WORDS[COMP_CWORD]} == -* ]]; then - # Specify options (-*) word list for this subcommand - COMPREPLY=($(compgen -W "$GZ_TOPIC_COMPLETION_LIST" \ + COMPREPLY=($(compgen -W "$GZ_LOG_COMPLETION_LIST" \ -- "${COMP_WORDS[COMP_CWORD]}" )) - return else - # Just use bash default auto-complete, because we never have two - # subcommands in the same line. If that is ever needed, change here to - # detect subsequent subcommands - COMPREPLY=($(compgen -o default -- "${COMP_WORDS[COMP_CWORD]}")) - return + COMPREPLY=($(compgen -W "${GZ_LOG_SUBCOMMANDS}" -- ${cur})) fi } + +function _gz_service_flags +{ + for word in $GZ_SERVICE_COMPLETION_LIST; do + echo "$word" + done +} + + function _gz_topic_flags { for word in $GZ_TOPIC_COMPLETION_LIST; do diff --git a/test/BUILD.bazel b/test/BUILD.bazel new file mode 100644 index 000000000..8700063d5 --- /dev/null +++ b/test/BUILD.bazel @@ -0,0 +1,15 @@ +load( + "@gz//bazel/skylark:build_defs.bzl", + "GZ_ROOT", + "GZ_VISIBILITY", +) + +cc_library( + name = "utils", + hdrs = ["test_utils.hh"], + includes = ["."], + visibility = GZ_VISIBILITY, + deps = [ + GZ_ROOT + "utils", + ], +) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4166255fa..70bd077b4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,9 +1,28 @@ -configure_file (test_config.hh.in ${PROJECT_BINARY_DIR}/include/test_config.hh) -include_directories ( - ${PROJECT_BINARY_DIR}/include -) - add_subdirectory(gtest_vendor) add_subdirectory(integration) add_subdirectory(performance) add_subdirectory(regression) + +configure_file (test_config.hh.in ${PROJECT_BINARY_DIR}/include/test_config.hh) +add_library(test_config INTERFACE) +target_include_directories(test_config INTERFACE + ${PROJECT_BINARY_DIR}/include + ${PROJECT_SOURCE_DIR}/test +) +target_compile_definitions(test_config INTERFACE +# Location of the "gz" command + "GZ_EXE=\"${HAVE_GZ_TOOLS}\"" +# Auxillary executables for test + "AUTH_PUB_SUB_SUBSCRIBER_INVALID_EXE=\"$\"" + "FAST_PUB_EXE=\"$\"" + "PUB_EXE=\"$\"" + "PUB_THROTTLED_EXE=\"$\"" + "SCOPED_TOPIC_SUBSCRIBER_EXE=\"$\"" + "TWO_PROCS_PUBLISHER_EXE=\"$\"" + "TWO_PROCS_PUB_SUB_SUBSCRIBER_EXE=\"$\"" + "TWO_PROCS_SRV_CALL_REPLIER_EXE=\"$\"" + "TWO_PROCS_SRV_CALL_REPLIER_INC_EXE=\"$\"" + "TWO_PROCS_SRV_CALL_WITHOUT_INPUT_REPLIER_EXE=\"$\"" + "TWO_PROCS_SRV_CALL_WITHOUT_INPUT_REPLIER_INC_EXE=\"$\"" + "TWO_PROCS_SRV_CALL_WITHOUT_OUTPUT_REPLIER_EXE=\"$\"" + "TWO_PROCS_SRV_CALL_WITHOUT_OUTPUT_REPLIER_INC_EXE=\"$\"") diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt index 858962a0e..ce220b549 100644 --- a/test/integration/CMakeLists.txt +++ b/test/integration/CMakeLists.txt @@ -26,17 +26,7 @@ endif() gz_build_tests(TYPE INTEGRATION SOURCES ${tests} TEST_LIST test_list - LIB_DEPS ${EXTRA_TEST_LIB_DEPS}) - -foreach(test ${test_list}) - - # Inform each test of its output directory so it knows where to call the - # auxiliary files from. Using a generator expression here is useful for - # multi-configuration generators, like Visual Studio. - target_compile_definitions(${test} PRIVATE - "DETAIL_GZ_TRANSPORT_TEST_DIR=\"$\"") - -endforeach() + LIB_DEPS ${EXTRA_TEST_LIB_DEPS} test_config) set(auxiliary_files authPubSubSubscriberInvalid_aux @@ -56,20 +46,14 @@ set(auxiliary_files # Build the auxiliary files. foreach(AUX_EXECUTABLE ${auxiliary_files}) - gz_add_executable(INTEGRATION_${AUX_EXECUTABLE} ${AUX_EXECUTABLE}.cc) + gz_add_executable(${AUX_EXECUTABLE} test_executables/${AUX_EXECUTABLE}.cc) # Link the libraries that we always need. - target_link_libraries(INTEGRATION_${AUX_EXECUTABLE} + target_link_libraries(${AUX_EXECUTABLE} PRIVATE ${PROJECT_LIBRARY_TARGET_NAME} gtest ${EXTRA_TEST_LIB_DEPS} + test_config ) - - if(UNIX) - # pthread is only available on Unix machines - target_link_libraries(INTEGRATION_${AUX_EXECUTABLE} - PRIVATE pthread) - endif() - endforeach(AUX_EXECUTABLE) diff --git a/test/integration/authPubSub.cc b/test/integration/authPubSub.cc index 9da50e44b..1f2d6dcd0 100644 --- a/test/integration/authPubSub.cc +++ b/test/integration/authPubSub.cc @@ -17,22 +17,17 @@ #include #include -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4251) -#endif #include -#ifdef _MSC_VER -#pragma warning(pop) -#endif #include "gtest/gtest.h" #include "gz/transport/Node.hh" #include "gz/transport/TransportTypes.hh" #include +#include #include "test_config.hh" +#include "test_utils.hh" using namespace gz; @@ -53,13 +48,9 @@ TEST(authPubSub, InvalidAuth) // No subscribers yet. EXPECT_FALSE(pub.HasConnections()); - std::string subscriberPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_authPubSubSubscriberInvalid_aux"); - - // Start the subscriber in another process with incorrect credentials. - testing::forkHandlerType pi = testing::forkAndRun(subscriberPath.c_str(), - partition.c_str(), "bad", "invalid"); + auto pi = gz::utils::Subprocess( + {test_executables::kAuthPubSubSubscriberInvalid, + partition, "bad", "invalid"}); msgs::Int32 msg; msg.set_data(1); @@ -77,9 +68,6 @@ TEST(authPubSub, InvalidAuth) EXPECT_TRUE(pub.Publish(msg)); std::this_thread::sleep_for(std::chrono::milliseconds(500)); } - - // The other process should exit without receiving any of the messages. - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// diff --git a/test/integration/scopedTopic.cc b/test/integration/scopedTopic.cc index 9be96a64c..de0b3d4dc 100644 --- a/test/integration/scopedTopic.cc +++ b/test/integration/scopedTopic.cc @@ -23,9 +23,12 @@ #include "gz/transport/Node.hh" #include +#include #include "gtest/gtest.h" + #include "test_config.hh" +#include "test_utils.hh" using namespace gz; @@ -39,12 +42,8 @@ static int data = 5; /// is not seen by the other node running in a different process. TEST(ScopedTopicTest, ProcessTest) { - std::string subscriber_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_scopedTopicSubscriber_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(subscriber_path.c_str(), - partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kScopedTopicSubscriber, partition}); msgs::Int32 msg; msg.set_data(data); @@ -59,8 +58,6 @@ TEST(ScopedTopicTest, ProcessTest) EXPECT_TRUE(pub.Publish(msg)); std::this_thread::sleep_for(std::chrono::milliseconds(500)); EXPECT_TRUE(pub.Publish(msg)); - - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// diff --git a/test/integration/statistics.cc b/test/integration/statistics.cc index 628d248bf..ae2462a39 100644 --- a/test/integration/statistics.cc +++ b/test/integration/statistics.cc @@ -26,7 +26,8 @@ #include -#include "test_config.hh" +#include "test_utils.hh" + using namespace gz; diff --git a/test/integration/authPubSubSubscriberInvalid_aux.cc b/test/integration/test_executables/authPubSubSubscriberInvalid_aux.cc similarity index 100% rename from test/integration/authPubSubSubscriberInvalid_aux.cc rename to test/integration/test_executables/authPubSubSubscriberInvalid_aux.cc diff --git a/test/integration/fastPub_aux.cc b/test/integration/test_executables/fastPub_aux.cc similarity index 100% rename from test/integration/fastPub_aux.cc rename to test/integration/test_executables/fastPub_aux.cc diff --git a/test/integration/pub_aux.cc b/test/integration/test_executables/pub_aux.cc similarity index 100% rename from test/integration/pub_aux.cc rename to test/integration/test_executables/pub_aux.cc diff --git a/test/integration/pub_aux_throttled.cc b/test/integration/test_executables/pub_aux_throttled.cc similarity index 100% rename from test/integration/pub_aux_throttled.cc rename to test/integration/test_executables/pub_aux_throttled.cc diff --git a/test/integration/scopedTopicSubscriber_aux.cc b/test/integration/test_executables/scopedTopicSubscriber_aux.cc similarity index 100% rename from test/integration/scopedTopicSubscriber_aux.cc rename to test/integration/test_executables/scopedTopicSubscriber_aux.cc diff --git a/test/integration/twoProcsPubSubSubscriber_aux.cc b/test/integration/test_executables/twoProcsPubSubSubscriber_aux.cc similarity index 100% rename from test/integration/twoProcsPubSubSubscriber_aux.cc rename to test/integration/test_executables/twoProcsPubSubSubscriber_aux.cc diff --git a/test/integration/twoProcsPublisher_aux.cc b/test/integration/test_executables/twoProcsPublisher_aux.cc similarity index 100% rename from test/integration/twoProcsPublisher_aux.cc rename to test/integration/test_executables/twoProcsPublisher_aux.cc diff --git a/test/integration/twoProcsSrvCallReplierInc_aux.cc b/test/integration/test_executables/twoProcsSrvCallReplierInc_aux.cc similarity index 100% rename from test/integration/twoProcsSrvCallReplierInc_aux.cc rename to test/integration/test_executables/twoProcsSrvCallReplierInc_aux.cc diff --git a/test/integration/twoProcsSrvCallReplier_aux.cc b/test/integration/test_executables/twoProcsSrvCallReplier_aux.cc similarity index 100% rename from test/integration/twoProcsSrvCallReplier_aux.cc rename to test/integration/test_executables/twoProcsSrvCallReplier_aux.cc diff --git a/test/integration/twoProcsSrvCallWithoutInputReplierInc_aux.cc b/test/integration/test_executables/twoProcsSrvCallWithoutInputReplierInc_aux.cc similarity index 100% rename from test/integration/twoProcsSrvCallWithoutInputReplierInc_aux.cc rename to test/integration/test_executables/twoProcsSrvCallWithoutInputReplierInc_aux.cc diff --git a/test/integration/twoProcsSrvCallWithoutInputReplier_aux.cc b/test/integration/test_executables/twoProcsSrvCallWithoutInputReplier_aux.cc similarity index 100% rename from test/integration/twoProcsSrvCallWithoutInputReplier_aux.cc rename to test/integration/test_executables/twoProcsSrvCallWithoutInputReplier_aux.cc diff --git a/test/integration/twoProcsSrvCallWithoutOutputReplierInc_aux.cc b/test/integration/test_executables/twoProcsSrvCallWithoutOutputReplierInc_aux.cc similarity index 100% rename from test/integration/twoProcsSrvCallWithoutOutputReplierInc_aux.cc rename to test/integration/test_executables/twoProcsSrvCallWithoutOutputReplierInc_aux.cc diff --git a/test/integration/twoProcsSrvCallWithoutOutputReplier_aux.cc b/test/integration/test_executables/twoProcsSrvCallWithoutOutputReplier_aux.cc similarity index 100% rename from test/integration/twoProcsSrvCallWithoutOutputReplier_aux.cc rename to test/integration/test_executables/twoProcsSrvCallWithoutOutputReplier_aux.cc diff --git a/test/integration/twoProcsPubSub.cc b/test/integration/twoProcsPubSub.cc index 46987404f..1cf378b27 100644 --- a/test/integration/twoProcsPubSub.cc +++ b/test/integration/twoProcsPubSub.cc @@ -24,9 +24,11 @@ #include "gz/transport/TransportTypes.hh" #include +#include #include "gtest/gtest.h" #include "test_config.hh" +#include "test_utils.hh" using namespace gz; @@ -112,12 +114,8 @@ TEST(twoProcPubSub, PubSubTwoProcsThreeNodes) // No subscribers yet. EXPECT_FALSE(pub.HasConnections()); - std::string subscriberPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsPubSubSubscriber_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(subscriberPath.c_str(), - partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kTwoProcsPubSubSubscriber, partition}); msgs::Vector3d msg; msg.set_x(1.0); @@ -135,8 +133,6 @@ TEST(twoProcPubSub, PubSubTwoProcsThreeNodes) EXPECT_TRUE(pub.Publish(msg)); std::this_thread::sleep_for(std::chrono::milliseconds(500)); } - - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// @@ -151,12 +147,8 @@ TEST(twoProcPubSub, RawPubSubTwoProcsThreeNodes) // No subscribers yet. EXPECT_FALSE(pub.HasConnections()); - std::string subscriberPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsPubSubSubscriber_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(subscriberPath.c_str(), - partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kTwoProcsPubSubSubscriber, partition}); msgs::Vector3d msg; msg.set_x(1.0); @@ -177,8 +169,6 @@ TEST(twoProcPubSub, RawPubSubTwoProcsThreeNodes) EXPECT_TRUE(pub.PublishRaw(msg.SerializeAsString(), msg.GetTypeName())); std::this_thread::sleep_for(std::chrono::milliseconds(500)); } - - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// @@ -186,12 +176,8 @@ TEST(twoProcPubSub, RawPubSubTwoProcsThreeNodes) /// the advertised types. TEST(twoProcPubSub, PubSubWrongTypesOnSubscription) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsPublisher_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kTwoProcsPublisher, partition}); reset(); @@ -205,20 +191,14 @@ TEST(twoProcPubSub, PubSubWrongTypesOnSubscription) EXPECT_FALSE(cbExecuted); reset(); - - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// /// \brief Same as above, but using a raw subscription. TEST(twoProcPubSub, PubRawSubWrongTypesOnSubscription) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsPublisher_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kTwoProcsPublisher, partition}); reset(); @@ -233,8 +213,6 @@ TEST(twoProcPubSub, PubRawSubWrongTypesOnSubscription) EXPECT_FALSE(cbRawExecuted); reset(); - - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// @@ -245,12 +223,8 @@ TEST(twoProcPubSub, PubRawSubWrongTypesOnSubscription) /// (correct and generic). TEST(twoProcPubSub, PubSubWrongTypesTwoSubscribers) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsPublisher_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kTwoProcsPublisher, partition}); reset(); @@ -266,15 +240,12 @@ TEST(twoProcPubSub, PubSubWrongTypesTwoSubscribers) // Wait some time before publishing. std::this_thread::sleep_for(std::chrono::milliseconds(2500)); - // Check that the message was not received. EXPECT_FALSE(cbExecuted); EXPECT_TRUE(cbVectorExecuted); EXPECT_TRUE(genericCbExecuted); reset(); - - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// @@ -285,12 +256,8 @@ TEST(twoProcPubSub, PubSubWrongTypesTwoSubscribers) /// callbacks are executed (correct and generic). TEST(twoProcPubSub, PubSubWrongTypesTwoRawSubscribers) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsPublisher_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kTwoProcsPublisher, partition}); reset(); @@ -337,8 +304,6 @@ TEST(twoProcPubSub, PubSubWrongTypesTwoRawSubscribers) EXPECT_TRUE(genericRawCbExecuted); reset(); - - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// @@ -348,18 +313,14 @@ TEST(twoProcPubSub, PubSubWrongTypesTwoRawSubscribers) /// the prompt termination of the publisher. TEST(twoProcPubSub, FastPublisher) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, "INTEGRATION_fastPub_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kFastPub, partition}); reset(); transport::Node node; EXPECT_TRUE(node.Subscribe(g_topic, cbVector)); - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// @@ -368,11 +329,8 @@ TEST(twoProcPubSub, FastPublisher) /// by the subscriber. TEST(twoProcPubSub, SubThrottled) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, "INTEGRATION_pub_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kPub, partition}); reset(); @@ -390,8 +348,6 @@ TEST(twoProcPubSub, SubThrottled) EXPECT_LT(counter, 5); reset(); - - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// @@ -399,11 +355,8 @@ TEST(twoProcPubSub, SubThrottled) /// processes. The publisher publishes at a throttled frequency. TEST(twoProcPubSub, PubThrottled) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, "INTEGRATION_pub_aux_throttled"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kPubThrottled, partition}); reset(); @@ -419,8 +372,6 @@ TEST(twoProcPubSub, PubThrottled) EXPECT_LT(counter, 5); reset(); - - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// @@ -428,12 +379,8 @@ TEST(twoProcPubSub, PubThrottled) /// using a callback that accepts message information. TEST(twoProcPubSub, PubSubMessageInfo) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, "INTEGRATION_twoProcsPublisher_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - partition.c_str()); - + auto pi = gz::utils::Subprocess( + {test_executables::kTwoProcsPublisher, partition}); reset(); transport::Node node; @@ -446,8 +393,6 @@ TEST(twoProcPubSub, PubSubMessageInfo) EXPECT_FALSE(cbInfoExecuted); reset(); - - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// @@ -456,11 +401,8 @@ TEST(twoProcPubSub, PubSubMessageInfo) /// available topics. TEST(twoProcPubSub, TopicList) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, "INTEGRATION_twoProcsPublisher_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kTwoProcsPublisher, partition}); reset(); @@ -497,8 +439,6 @@ TEST(twoProcPubSub, TopicList) EXPECT_LT(elapsed2, 2); reset(); - - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// @@ -507,11 +447,8 @@ TEST(twoProcPubSub, TopicList) /// about the topic. TEST(twoProcPubSub, TopicInfo) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, "INTEGRATION_twoProcsPublisher_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kTwoProcsPublisher, partition}); reset(); @@ -533,8 +470,6 @@ TEST(twoProcPubSub, TopicInfo) EXPECT_EQ(publishers.front().MsgTypeName(), "gz.msgs.Vector3d"); reset(); - - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// diff --git a/test/integration/twoProcsSrvCall.cc b/test/integration/twoProcsSrvCall.cc index 9ccd9abdc..422689b2f 100644 --- a/test/integration/twoProcsSrvCall.cc +++ b/test/integration/twoProcsSrvCall.cc @@ -19,26 +19,59 @@ #include #include +#include #include +#include #include "gz/transport/Node.hh" #include "gz/transport/TopicUtils.hh" #include +#include "gz/utils/Subprocess.hh" #include "gtest/gtest.h" #include "test_config.hh" +#include "test_utils.hh" using namespace gz; static bool responseExecuted; static bool wrongResponseExecuted; -static std::string partition; // NOLINT(*) static std::string g_topic = "/foo"; // NOLINT(*) static int data = 5; static int counter = 0; +////////////////////////////////////////////////// +class twoProcSrvCall: public testing::Test { + protected: + void SetUp() override { + gz::utils::env("GZ_PARTITION", this->prevPartition); + + // Get a random partition name. + this->partition = testing::getRandomNumber(); + + // Set the partition name for this process. + gz::utils::setenv("GZ_PARTITION", this->partition); + + this->pi = std::make_unique( + std::vector({ + test_executables::kTwoProcsSrvCallReplier, this->partition})); + } + + void TearDown() override { + gz::utils::setenv("GZ_PARTITION", this->prevPartition); + + this->pi->Terminate(); + this->pi->Join(); + } + + private: + std::string prevPartition; + std::string partition; + std::unique_ptr pi; +}; + ////////////////////////////////////////////////// /// \brief Initialize some global variables. void reset() @@ -69,15 +102,8 @@ void wrongResponse(const msgs::Vector3d &/*_rep*/, bool /*_result*/) ////////////////////////////////////////////////// /// \brief Two different nodes running in two different processes. One node /// advertises a service and the other requests a few service calls. -TEST(twoProcSrvCall, SrvTwoProcs) +TEST_F(twoProcSrvCall, SrvTwoProcs) { - std::string responser_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(responser_path.c_str(), - partition.c_str()); - reset(); msgs::Int32 req; @@ -114,29 +140,19 @@ TEST(twoProcSrvCall, SrvTwoProcs) EXPECT_EQ(counter, 1); reset(); - - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// /// \brief This test spawns a service responser and a service requester. The /// requester uses a wrong type for the request argument. The test should verify /// that the service call does not succeed. -TEST(twoProcSrvCall, SrvRequestWrongReq) +TEST_F(twoProcSrvCall, SrvRequestWrongReq) { msgs::Vector3d wrongReq; msgs::Int32 rep; bool result; unsigned int timeout = 1000; - std::string responser_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(responser_path.c_str(), - partition.c_str()); - wrongReq.set_x(1); wrongReq.set_y(2); wrongReq.set_z(3); @@ -154,30 +170,19 @@ TEST(twoProcSrvCall, SrvRequestWrongReq) EXPECT_FALSE(node.Request(g_topic, wrongReq, timeout, rep, result)); reset(); - - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// /// \brief This test spawns a service responser and a service requester. The /// requester uses a wrong type for the response argument. The test should /// verify that the service call does not succeed. -TEST(twoProcSrvCall, SrvRequestWrongRep) +TEST_F(twoProcSrvCall, SrvRequestWrongRep) { msgs::Int32 req; msgs::Vector3d wrongRep; bool result; unsigned int timeout = 1000; - std::string responser_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallReplier_aux"); - - - testing::forkHandlerType pi = testing::forkAndRun(responser_path.c_str(), - partition.c_str()); - req.set_data(data); reset(); @@ -193,9 +198,6 @@ TEST(twoProcSrvCall, SrvRequestWrongRep) EXPECT_FALSE(node.Request(g_topic, req, timeout, wrongRep, result)); reset(); - - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// @@ -203,7 +205,7 @@ TEST(twoProcSrvCall, SrvRequestWrongRep) /// service requesters use incorrect types in some of the requests. The test /// should verify that a response is received only when the appropriate types /// are used. -TEST(twoProcSrvCall, SrvTwoRequestsOneWrong) +TEST_F(twoProcSrvCall, SrvTwoRequestsOneWrong) { msgs::Int32 req; msgs::Int32 goodRep; @@ -211,13 +213,6 @@ TEST(twoProcSrvCall, SrvTwoRequestsOneWrong) bool result; unsigned int timeout = 2000; - std::string responser_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(responser_path.c_str(), - partition.c_str()); - req.set_data(data); reset(); @@ -241,24 +236,14 @@ TEST(twoProcSrvCall, SrvTwoRequestsOneWrong) EXPECT_TRUE(responseExecuted); reset(); - - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// /// \brief This test spawns two nodes on different processes. One of the nodes /// advertises a service and the other uses ServiceList() for getting the list /// of available services. -TEST(twoProcSrvCall, ServiceList) +TEST_F(twoProcSrvCall, ServiceList) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - partition.c_str()); - reset(); transport::Node node; @@ -294,23 +279,14 @@ TEST(twoProcSrvCall, ServiceList) << "] Elapsed1[" << elapsed1.count() << "]"; reset(); - - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// /// \brief This test spawns two nodes on different processes. One of the nodes /// advertises a service and the other uses ServiceInfo() for getting /// information about the service. -TEST(twoProcSrvCall, ServiceInfo) +TEST_F(twoProcSrvCall, ServiceInfo) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - partition.c_str()); - reset(); transport::Node node; @@ -331,22 +307,4 @@ TEST(twoProcSrvCall, ServiceInfo) EXPECT_EQ(publishers.front().RepTypeName(), "gz.msgs.Int32"); reset(); - - testing::waitAndCleanupFork(pi); -} - -////////////////////////////////////////////////// -int main(int argc, char **argv) -{ - // Get a random partition name. - partition = testing::getRandomNumber(); - - // Set the partition name for this process. - gz::utils::setenv("GZ_PARTITION", partition); - - // Enable verbose mode. - // gz::utils::setenv("GZ_VERBOSE", "1"); - - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); } diff --git a/test/integration/twoProcsSrvCallStress.cc b/test/integration/twoProcsSrvCallStress.cc index f08357928..c6f2c9d1d 100644 --- a/test/integration/twoProcsSrvCallStress.cc +++ b/test/integration/twoProcsSrvCallStress.cc @@ -23,9 +23,12 @@ #include "gz/transport/Node.hh" #include +#include #include "gtest/gtest.h" + #include "test_config.hh" +#include "test_utils.hh" using namespace gz; @@ -35,12 +38,8 @@ static std::string g_topic = "/foo"; // NOLINT(*) ////////////////////////////////////////////////// TEST(twoProcSrvCall, ThousandCalls) { - std::string responser_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallReplierInc_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(responser_path.c_str(), - partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kTwoProcsSrvCallReplierInc, partition}); msgs::Int32 req; msgs::Int32 response; @@ -59,9 +58,6 @@ TEST(twoProcSrvCall, ThousandCalls) ASSERT_TRUE(result); EXPECT_EQ(i, response.data()); } - - // Need to kill the responser node running on an external process. - testing::killFork(pi); } ////////////////////////////////////////////////// diff --git a/test/integration/twoProcsSrvCallSync1.cc b/test/integration/twoProcsSrvCallSync1.cc index 27087049e..32b8e9f34 100644 --- a/test/integration/twoProcsSrvCallSync1.cc +++ b/test/integration/twoProcsSrvCallSync1.cc @@ -23,9 +23,12 @@ #include "gz/transport/Node.hh" #include +#include #include "gtest/gtest.h" + #include "test_config.hh" +#include "test_utils.hh" using namespace gz; @@ -40,12 +43,8 @@ static int data = 5; /// the timeout. TEST(twoProcSrvCallSync1, SrvTwoProcs) { - std::string responser_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(responser_path.c_str(), - partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kTwoProcsSrvCallReplier, partition}); int64_t timeout = 500; msgs::Int32 req; @@ -74,9 +73,6 @@ TEST(twoProcSrvCallSync1, SrvTwoProcs) // Check if the elapsed time was close to the timeout. auto diff = std::max(elapsed, timeout) - std::min(elapsed, timeout); EXPECT_LT(diff, 200); - - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// diff --git a/test/integration/twoProcsSrvCallWithoutInput.cc b/test/integration/twoProcsSrvCallWithoutInput.cc index cefa1ef7b..447dd7ca4 100644 --- a/test/integration/twoProcsSrvCallWithoutInput.cc +++ b/test/integration/twoProcsSrvCallWithoutInput.cc @@ -19,15 +19,19 @@ #include #include +#include #include +#include #include "gz/transport/Node.hh" #include "gz/transport/TopicUtils.hh" #include +#include #include "gtest/gtest.h" #include "test_config.hh" +#include "test_utils.hh" using namespace gz; @@ -39,6 +43,37 @@ static std::string g_topic = "/foo"; // NOLINT(*) static int g_data = 5; static int g_counter = 0; +////////////////////////////////////////////////// +class twoProcSrvCallWithoutInput: public testing::Test { + protected: + void SetUp() override { + gz::utils::env("GZ_PARTITION", this->prevPartition); + + // Get a random partition name. + this->partition = testing::getRandomNumber(); + + // Set the partition name for this process. + gz::utils::setenv("GZ_PARTITION", this->partition); + + this->pi = std::make_unique( + std::vector({ + test_executables::kTwoProcsSrvCallWithoutInputReplier, + this->partition})); + } + + void TearDown() override { + gz::utils::setenv("GZ_PARTITION", this->prevPartition); + + this->pi->Terminate(); + this->pi->Join(); + } + + private: + std::string prevPartition; + std::string partition; + std::unique_ptr pi; +}; + ////////////////////////////////////////////////// /// \brief Initialize some global variables. void reset() @@ -70,15 +105,8 @@ void wrongResponse(const msgs::Vector3d &/*_rep*/, bool /*_result*/) /// \brief Two different nodes running in two different processes. One node /// advertises a service without input and the other requests a few service /// calls. -TEST(twoProcSrvCallWithoutInput, SrvTwoProcs) +TEST_F(twoProcSrvCallWithoutInput, SrvTwoProcs) { - std::string responser_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallWithoutInputReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(responser_path.c_str(), - g_partition.c_str()); - reset(); transport::Node node; @@ -112,28 +140,18 @@ TEST(twoProcSrvCallWithoutInput, SrvTwoProcs) EXPECT_EQ(g_counter, 1); reset(); - - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// /// \brief This test spawns a service that doesn't accept input parameters. The /// service requester uses a wrong type for the response argument. The test /// should verify that the service call does not succeed. -TEST(twoProcSrvCallWithoutInput, SrvRequestWrongRep) +TEST_F(twoProcSrvCallWithoutInput, SrvRequestWrongRep) { msgs::Vector3d wrongRep; bool result; unsigned int timeout = 1000; - std::string responser_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallWithoutInputReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(responser_path.c_str(), - g_partition.c_str()); - reset(); transport::Node node; @@ -147,9 +165,6 @@ TEST(twoProcSrvCallWithoutInput, SrvRequestWrongRep) EXPECT_FALSE(node.Request(g_topic, timeout, wrongRep, result)); reset(); - - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// @@ -157,20 +172,13 @@ TEST(twoProcSrvCallWithoutInput, SrvRequestWrongRep) /// service requesters use incorrect types in some of the requests. The test /// should verify that a response is received only when the appropriate types /// are used. -TEST(twoProcSrvCallWithoutInput, SrvTwoRequestsOneWrong) +TEST_F(twoProcSrvCallWithoutInput, SrvTwoRequestsOneWrong) { msgs::Int32 goodRep; msgs::Vector3d badRep; bool result; unsigned int timeout = 2000; - std::string responser_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallWithoutInputReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(responser_path.c_str(), - g_partition.c_str()); - reset(); std::this_thread::sleep_for(std::chrono::milliseconds(500)); @@ -192,24 +200,14 @@ TEST(twoProcSrvCallWithoutInput, SrvTwoRequestsOneWrong) EXPECT_TRUE(g_responseExecuted); reset(); - - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// /// \brief This test spawns two nodes on different processes. One of the nodes /// advertises a service without input and the other uses ServiceList() for /// getting the list of available services. -TEST(twoProcSrvCallWithoutInput, ServiceList) +TEST_F(twoProcSrvCallWithoutInput, ServiceList) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallWithoutInputReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - g_partition.c_str()); - reset(); transport::Node node; @@ -244,23 +242,14 @@ TEST(twoProcSrvCallWithoutInput, ServiceList) EXPECT_LE(elapsed2, elapsed1); reset(); - - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// /// \brief This test spawns two nodes on different processes. One of the nodes /// advertises a service without input and the other uses ServiceInfo() for /// getting information about the service. -TEST(twoProcSrvCallWithoutInput, ServiceInfo) +TEST_F(twoProcSrvCallWithoutInput, ServiceInfo) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallWithoutInputReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - g_partition.c_str()); - reset(); transport::Node node; @@ -281,22 +270,4 @@ TEST(twoProcSrvCallWithoutInput, ServiceInfo) EXPECT_EQ(publishers.front().RepTypeName(), "gz.msgs.Int32"); reset(); - - testing::waitAndCleanupFork(pi); -} - -////////////////////////////////////////////////// -int main(int argc, char **argv) -{ - // Get a random partition name. - g_partition = testing::getRandomNumber(); - - // Set the partition name for this process. - gz::utils::setenv("GZ_PARTITION", g_partition); - - // Enable verbose mode. - // gz::utils::setenv("GZ_VERBOSE", "1"); - - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); } diff --git a/test/integration/twoProcsSrvCallWithoutInputStress.cc b/test/integration/twoProcsSrvCallWithoutInputStress.cc index 7d2fc7c4b..2acc7b778 100644 --- a/test/integration/twoProcsSrvCallWithoutInputStress.cc +++ b/test/integration/twoProcsSrvCallWithoutInputStress.cc @@ -23,9 +23,12 @@ #include "gz/transport/Node.hh" #include +#include #include "gtest/gtest.h" + #include "test_config.hh" +#include "test_utils.hh" using namespace gz; @@ -35,12 +38,8 @@ static std::string g_topic = "/foo"; // NOLINT(*) ////////////////////////////////////////////////// TEST(twoProcSrvCallWithoutInput, ThousandCalls) { - std::string responser_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallWithoutInputReplierInc_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(responser_path.c_str(), - g_partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kTwoProcsSrvCallWithoutInputReplierInc, g_partition}); msgs::Int32 response; bool result; @@ -56,9 +55,6 @@ TEST(twoProcSrvCallWithoutInput, ThousandCalls) // Check the service response. ASSERT_TRUE(result); } - - // Need to kill the responser node running on an external process. - testing::killFork(pi); } ////////////////////////////////////////////////// diff --git a/test/integration/twoProcsSrvCallWithoutInputSync1.cc b/test/integration/twoProcsSrvCallWithoutInputSync1.cc index 230a96f88..118f1eff4 100644 --- a/test/integration/twoProcsSrvCallWithoutInputSync1.cc +++ b/test/integration/twoProcsSrvCallWithoutInputSync1.cc @@ -23,9 +23,12 @@ #include "gz/transport/Node.hh" #include +#include #include "gtest/gtest.h" + #include "test_config.hh" +#include "test_utils.hh" using namespace gz; @@ -39,12 +42,8 @@ static std::string g_topic = "/foo"; // NOLINT(*) /// the timeout. TEST(twoProcSrvCallWithoutInputSync1, SrvTwoProcs) { - std::string responser_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallWithoutInputReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(responser_path.c_str(), - g_partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kTwoProcsSrvCallWithoutInputReplier, g_partition}); int64_t timeout = 500; msgs::Int32 rep; @@ -69,9 +68,6 @@ TEST(twoProcSrvCallWithoutInputSync1, SrvTwoProcs) // Check if the elapsed time was close to the timeout. auto diff = std::max(elapsed, timeout) - std::min(elapsed, timeout); EXPECT_LT(diff, 200); - - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// diff --git a/test/integration/twoProcsSrvCallWithoutOutput.cc b/test/integration/twoProcsSrvCallWithoutOutput.cc index 1d365a0be..5e188d839 100644 --- a/test/integration/twoProcsSrvCallWithoutOutput.cc +++ b/test/integration/twoProcsSrvCallWithoutOutput.cc @@ -18,15 +18,20 @@ #include #include +#include #include +#include #include "gz/transport/Node.hh" #include "gz/transport/TopicUtils.hh" #include +#include #include "gtest/gtest.h" + #include "test_config.hh" +#include "test_utils.hh" using namespace gz; @@ -37,6 +42,37 @@ static std::string g_partition; // NOLINT(*) static std::string g_topic = "/foo"; // NOLINT(*) static int g_counter = 0; +////////////////////////////////////////////////// +class twoProcSrvCallWithoutOutput: public testing::Test { + protected: + void SetUp() override { + gz::utils::env("GZ_PARTITION", this->prevPartition); + + // Get a random partition name. + this->partition = testing::getRandomNumber(); + + // Set the partition name for this process. + gz::utils::setenv("GZ_PARTITION", this->partition); + + this->pi = std::make_unique( + std::vector({ + test_executables::kTwoProcsSrvCallWithoutOutputReplier, + this->partition})); + } + + void TearDown() override { + gz::utils::setenv("GZ_PARTITION", this->prevPartition); + + this->pi->Terminate(); + this->pi->Join(); + } + + private: + std::string prevPartition; + std::string partition; + std::unique_ptr pi; +}; + ////////////////////////////////////////////////// /// \brief Initialize some global variables. void reset() @@ -50,17 +86,10 @@ void reset() /// \brief This test spawns a service that doesn't wait for ouput parameters. /// The requester uses a wrong type for the request argument. The test should /// verify that the service call does not succeed. -TEST(twoProcSrvCallWithoutOutput, SrvRequestWrongReq) +TEST_F(twoProcSrvCallWithoutOutput, SrvRequestWrongReq) { msgs::Vector3d wrongReq; - std::string responser_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallWithoutOutputReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(responser_path.c_str(), - g_partition.c_str()); - wrongReq.set_x(1); wrongReq.set_y(2); wrongReq.set_z(3); @@ -75,24 +104,14 @@ TEST(twoProcSrvCallWithoutOutput, SrvRequestWrongReq) EXPECT_FALSE(g_responseExecuted); reset(); - - // Wait for the child process to return. - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// /// \brief This test spawns two nodes on different processes. One of the nodes /// advertises a service without output and the other uses ServiceList() for /// getting the list of available services. -TEST(twoProcSrvCallWithoutOutput, ServiceList) +TEST_F(twoProcSrvCallWithoutOutput, ServiceList) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallWithoutOutputReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - g_partition.c_str()); - reset(); transport::Node node; @@ -127,23 +146,14 @@ TEST(twoProcSrvCallWithoutOutput, ServiceList) EXPECT_LE(elapsed2, elapsed1); reset(); - - testing::waitAndCleanupFork(pi); } ////////////////////////////////////////////////// /// \brief This test spawns two nodes on different processes. One of the nodes /// advertises a service without output and the other uses ServiceInfo() for /// getting information about the service. -TEST(twoProcSrvCallWithoutOutput, ServiceInfo) +TEST_F(twoProcSrvCallWithoutOutput, ServiceInfo) { - std::string publisherPath = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallWithoutOutputReplier_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(publisherPath.c_str(), - g_partition.c_str()); - reset(); transport::Node node; @@ -163,22 +173,4 @@ TEST(twoProcSrvCallWithoutOutput, ServiceInfo) EXPECT_EQ(publishers.front().ReqTypeName(), "gz.msgs.Int32"); reset(); - - testing::waitAndCleanupFork(pi); -} - -////////////////////////////////////////////////// -int main(int argc, char **argv) -{ - // Get a random partition name. - g_partition = testing::getRandomNumber(); - - // Set the partition name for this process. - gz::utils::setenv("GZ_PARTITION", g_partition); - - // Enable verbose mode. - // gz::utils::setenv("GZ_VERBOSE", "1"); - - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); } diff --git a/test/integration/twoProcsSrvCallWithoutOutputStress.cc b/test/integration/twoProcsSrvCallWithoutOutputStress.cc index c32a7e636..3e8d65906 100644 --- a/test/integration/twoProcsSrvCallWithoutOutputStress.cc +++ b/test/integration/twoProcsSrvCallWithoutOutputStress.cc @@ -23,9 +23,12 @@ #include "gz/transport/Node.hh" #include +#include #include "gtest/gtest.h" + #include "test_config.hh" +#include "test_utils.hh" using namespace gz; @@ -35,12 +38,8 @@ static std::string g_topic = "/foo"; // NOLINT(*) ////////////////////////////////////////////////// TEST(twoProcSrvCallWithoutOuput, ThousandCalls) { - std::string responser_path = testing::portablePathUnion( - GZ_TRANSPORT_TEST_DIR, - "INTEGRATION_twoProcsSrvCallWithoutOutputReplierInc_aux"); - - testing::forkHandlerType pi = testing::forkAndRun(responser_path.c_str(), - g_partition.c_str()); + auto pi = gz::utils::Subprocess( + {test_executables::kTwoProcsSrvCallWithoutOutputReplierInc, g_partition}); msgs::Int32 req; transport::Node node; @@ -52,9 +51,6 @@ TEST(twoProcSrvCallWithoutOuput, ThousandCalls) req.set_data(i); ASSERT_TRUE(node.Request(g_topic, req)); } - - // Need to kill the responser node running on an external process. - testing::killFork(pi); } ////////////////////////////////////////////////// diff --git a/test/test_config.hh.in b/test/test_config.hh.in index 481d35a18..c7a8335e5 100644 --- a/test/test_config.hh.in +++ b/test/test_config.hh.in @@ -18,182 +18,66 @@ #ifndef GZ_TRANSPORT_TEST_CONFIG_HH_ #define GZ_TRANSPORT_TEST_CONFIG_HH_ -#define GZ_PATH "@HAVE_GZ_TOOLS@" -#define GZ_CONFIG_PATH "@CMAKE_BINARY_DIR@/test/conf" -#define GZ_VERSION_FULL "@PROJECT_VERSION_FULL@" - -#ifdef DETAIL_GZ_TRANSPORT_TEST_DIR -// The DETAIL_GZ_TRANSPORT_TEST_DIR macro is defined using generator -// expressions in CMakeLists.txt files. See test/integration/CMakeLists.txt for -// an example. -#define GZ_TRANSPORT_TEST_DIR \ - DETAIL_GZ_TRANSPORT_TEST_DIR -#endif - -#include -#include -#include -#include -#include - -#ifdef _WIN32 - #include -#else - #include - #include - #include - #include -#endif - -#include "gz/transport/Helpers.hh" - -namespace testing +constexpr const char * kGzVersion = "@PROJECT_VERSION_FULL@"; + +namespace test_executables { - /// \brief Join _str1 and _str2 considering both as storing system paths. - /// \param[in] _str1 string containing a path. - /// \param[in] _str2 string containing a path. - /// \return The string representation of the union of two paths. - std::string portablePathUnion(const std::string &_str1, - const std::string &_str2) - { - return (std::filesystem::path(_str1) / std::filesystem::path(_str2)).string(); - } - -#ifdef _WIN32 - using forkHandlerType = PROCESS_INFORMATION; -#else - using forkHandlerType = pid_t; -#endif - - /// \brief create a new process and run command on it. This function is - /// implementing the creation of a new process on both Linux (fork) and - /// Windows (CreateProcess) and the execution of the command provided. - /// \param[in] _command The full system path to the binary to run into the - /// new process. - /// \param[in] _partition Name of the Gazebo partition (GZ_PARTITION) - /// \param[in] _username Username for authentication - /// (GZ_TRANSPORT_USERNAME) - /// \param[in] _password Password for authentication - /// (GZ_TRANSPORT_PASSWORD) - /// \return On success, the PID of the child process is returned in the - /// parent, an 0 is returned in the child. On failure, -1 is returned in the - /// parent and no child process is created. - forkHandlerType forkAndRun(const char *_command, const char *_partition, - const char *_username = nullptr, const char *_password = nullptr) - { -#ifdef _WIN32 - STARTUPINFO info= {sizeof(info)}; - PROCESS_INFORMATION processInfo; - - char cmd[500]; - // We should put quotes around the _command string to make sure we are - // robust to file paths that contain spaces. - gz_strcpy(cmd, "\""); - gz_strcat(cmd, _command); - gz_strcat(cmd, "\""); - gz_strcat(cmd, " "); - gz_strcat(cmd, _partition); - - if (_username && _password) - { - gz_strcat(cmd, " "); - gz_strcat(cmd, _username); - gz_strcat(cmd, " "); - gz_strcat(cmd, _password); - } - - // We set the first argument to NULL, because we want the behavior that - // CreateProcess exhibits when the first argument is NULL: i.e. Windows will - // automatically add the .exe extension onto the filename. When the first - // argument is non-NULL, it will not automatically add the extension, which - // makes more work for us. - // - // It should also be noted that the lookup behavior for the application is - // different when the first argument is non-NULL, so we should take that - // into consideration when determining what to put into the first and second - // arguments of CreateProcess. - if (!CreateProcess(NULL, const_cast(cmd), NULL, NULL, - TRUE, 0, NULL, NULL, &info, &processInfo)) - { - std::cerr << "CreateProcess call failed: " << cmd << std::endl; - } - - return processInfo; -#else - pid_t pid = fork(); - - if (pid == 0) - { - if (_username && _password) - { - if (execl(_command, _command, _partition, _username, _password, - reinterpret_cast(0)) == -1) - { - std::cerr << "Error running execl call: " << _command << std::endl; - } - } - else - { - if (execl(_command, _command, _partition, - reinterpret_cast(0)) == -1) - { - std::cerr << "Error running execl call: " << _command << std::endl; - } - } - } - - return pid; -#endif - } - - /// \brief Wait for the end of a process and handle the termination - /// \param[in] pi Process handler of the process to wait for - /// (PROCESS_INFORMATION in windows or forkHandlerType in UNIX). - void waitAndCleanupFork(const forkHandlerType pi) - { -#ifdef _WIN32 - // Wait until child process exits. - WaitForSingleObject(pi.hProcess, INFINITE); - - // Close process and thread handler. - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); -#else - // Wait for the child process to return. - int status; - waitpid(pi, &status, 0); - if (status == -1) - std::cerr << "Error while running waitpid" << std::endl; -#endif - } - - /// \brief Send a termination signal to the process handled by pi. - /// \param[in] pi Process handler of the process to stop - /// (PROCESS_INFORMATION in windows or forkHandlerType in UNIX). - void killFork(const forkHandlerType pi) - { -#ifdef _WIN32 - // TerminateProcess return 0 on error - if (TerminateProcess(pi.hProcess, 0) == 0) - std::cerr << "Error running TerminateProcess: " << GetLastError(); -#else - kill(pi, SIGTERM); -#endif - } - - /// \brief Get a random number based on an integer converted to string. - /// \return A random integer converted to string. - std::string getRandomNumber() - { - // Initialize random number generator. - uint32_t seed = std::random_device {}(); - std::mt19937 randGenerator(seed); - - // Create a random number based on an integer converted to string. - std::uniform_int_distribution d(0, INT_MAX); - - return std::to_string(d(randGenerator)); - } -} // namespace testing - -#endif // header guard + +#ifdef GZ_EXE +constexpr const char * kGzExe = GZ_EXE; +#endif // GZ_EXE + +#ifdef AUTH_PUB_SUB_SUBSCRIBER_INVALID_EXE +constexpr const char * kAuthPubSubSubscriberInvalid = AUTH_PUB_SUB_SUBSCRIBER_INVALID_EXE; +#endif // AUTH_PUB_SUB_SUBSCRIBER_INVALID_EXE + +#ifdef FAST_PUB_EXE +constexpr const char * kFastPub = FAST_PUB_EXE; +#endif // FAST_PUB_EXE + +#ifdef PUB_EXE +constexpr const char * kPub = PUB_EXE; +#endif // PUB_EXE + +#ifdef PUB_THROTTLED_EXE +constexpr const char * kPubThrottled = PUB_THROTTLED_EXE; +#endif // PUB_THROTTLED_EXE + +#ifdef SCOPED_TOPIC_SUBSCRIBER_EXE +constexpr const char * kScopedTopicSubscriber = SCOPED_TOPIC_SUBSCRIBER_EXE; +#endif // SCOPED_TOPIC_SUBSCRIBER_EXE + +#ifdef TWO_PROCS_PUBLISHER_EXE +constexpr const char * kTwoProcsPublisher = TWO_PROCS_PUBLISHER_EXE; +#endif // TWO_PROCS_PUBLISHER_EXE + +#ifdef TWO_PROCS_PUB_SUB_SUBSCRIBER_EXE +constexpr const char * kTwoProcsPubSubSubscriber = TWO_PROCS_PUB_SUB_SUBSCRIBER_EXE; +#endif // TWO_PROCS_PUB_SUB_SUBSCRIBER_EXE + +#ifdef TWO_PROCS_SRV_CALL_REPLIER_EXE +constexpr const char * kTwoProcsSrvCallReplier = TWO_PROCS_SRV_CALL_REPLIER_EXE; +#endif // TWO_PROCS_SRV_CALL_REPLIER_EXE + +#ifdef TWO_PROCS_SRV_CALL_REPLIER_INC_EXE +constexpr const char * kTwoProcsSrvCallReplierInc = TWO_PROCS_SRV_CALL_REPLIER_INC_EXE; +#endif // TWO_PROCS_SRV_CALL_REPLIER_INC_EXE + +#ifdef TWO_PROCS_SRV_CALL_WITHOUT_INPUT_REPLIER_EXE +constexpr const char * kTwoProcsSrvCallWithoutInputReplier = TWO_PROCS_SRV_CALL_WITHOUT_INPUT_REPLIER_EXE; +#endif // TWO_PROCS_SRV_CALL_WITHOUT_INPUT_REPLIER_EXE + +#ifdef TWO_PROCS_SRV_CALL_WITHOUT_INPUT_REPLIER_INC_EXE +constexpr const char * kTwoProcsSrvCallWithoutInputReplierInc = TWO_PROCS_SRV_CALL_WITHOUT_INPUT_REPLIER_INC_EXE; +#endif // TWO_PROCS_SRV_CALL_WITHOUT_INPUT_REPLIER_INC_EXE + +#ifdef TWO_PROCS_SRV_CALL_WITHOUT_OUTPUT_REPLIER_EXE +constexpr const char * kTwoProcsSrvCallWithoutOutputReplier = TWO_PROCS_SRV_CALL_WITHOUT_OUTPUT_REPLIER_EXE; +#endif // TWO_PROCS_SRV_CALL_WITHOUT_OUTPUT_REPLIER_EXE + +#ifdef TWO_PROCS_SRV_CALL_WITHOUT_OUTPUT_REPLIER_INC_EXE +constexpr const char * kTwoProcsSrvCallWithoutOutputReplierInc = TWO_PROCS_SRV_CALL_WITHOUT_OUTPUT_REPLIER_INC_EXE; +#endif // TWO_PROCS_SRV_CALL_WITHOUT_OUTPUT_REPLIER_INC_EXE +} // namespace test_executables + +#endif // GZ_TRANSPORT_TEST_CONFIG_HH_ diff --git a/test/test_utils.hh b/test/test_utils.hh new file mode 100644 index 000000000..ff6113981 --- /dev/null +++ b/test/test_utils.hh @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 Open Source Robotics Foundation + * + * 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. + * +*/ + +#ifndef GZ_TRANSPORT_TEST_UTILS_HH_ +#define GZ_TRANSPORT_TEST_UTILS_HH_ + +#include +#include +#include + +namespace testing +{ + /// \brief Get a random number based on an integer converted to string. + /// \return A random integer converted to string. + inline std::string getRandomNumber() + { + // Initialize random number generator. + uint32_t seed = std::random_device {}(); + std::mt19937 randGenerator(seed); + + // Create a random number based on an integer converted to string. + std::uniform_int_distribution d(0, INT_MAX); + + return std::to_string(d(randGenerator)); + } +} // namespace testing + +#endif // GZ_TRANSPORT_TEST_UTILS_HH_ diff --git a/tutorials/01_intro.md b/tutorials/01_intro.md index e87bd73c1..0088e0f66 100644 --- a/tutorials/01_intro.md +++ b/tutorials/01_intro.md @@ -20,5 +20,5 @@ combination of custom code and [ZeroMQ] (http://zeromq.org/). ## What programming language can I use with Gazebo Transport? -C++ is the native implementation and so far the only way to use the library. -We hope to offer different wrappers for the most popular languages in the future. +C++ is the native implementation and the only language that has all available library features. +Python implementation is a wrapper around C++ methods using `pybind11`. It does not support all features like C++, but contains the main features such as publication, subscription and service request. diff --git a/tutorials/05_services.md b/tutorials/05_services.md index 7bef1e0b7..801a8fa8d 100644 --- a/tutorials/05_services.md +++ b/tutorials/05_services.md @@ -1,6 +1,6 @@ \page services Services -Next Tutorial: \ref security +Next Tutorial: \ref python Previous Tutorial: \ref messages ## Overview diff --git a/tutorials/06_python_support.md b/tutorials/06_python_support.md new file mode 100644 index 000000000..81ebd2603 --- /dev/null +++ b/tutorials/06_python_support.md @@ -0,0 +1,432 @@ +\page python Python Support + +Next Tutorial: \ref security +Previous Tutorial: \ref services + +## Overview + +In this tutorial, we are going to show the functionalities implemented in Python. +These features are brought up by creating bindings from the C++ implementation +using pybind11. It is important to note that not all of C++ features are available +yet, on this tutorial we will go over the most relevant features. For more information, +refer to the [__init__.py](https://github.com/gazebosim/gz-transport/blob/gz-transport13/python/src/__init__.py) +file which is a wrapper for all the bindings. + +For this tutorial, we will create two nodes that communicate via messages. One node +will be a publisher that generates the information, whereas the other node will be +the subscriber consuming the information. Our nodes will be running on different +processes within the same machine. + +```{.sh} +mkdir ~/gz_transport_tutorial +cd ~/gz_transport_tutorial +``` + +## Publisher + +Download the [publisher.py](https://github.com/gazebosim/gz-transport/blob/gz-transport13/python/examples/publisher.py) file within the `gz_transport_tutorial` +folder and open it with your favorite editor: + +\snippet python/examples/publisher.py complete + +### Walkthrough + +```{.py} + from gz.msgs10.stringmsg_pb2 import StringMsg + from gz.msgs10.vector3d_pb2 import Vector3d + from gz.transport13 import Node +``` + +The library `gz.transport13` contains all the Gazebo Transport elements that can be +used in Python. The final API we will use is contained inside the class `Node`. + +The lines `from gz.msgs10.stringmsg_pb2 import StringMsg` and `from gz.msgs10.vector3d_pb2 import Vector3d` +includes the generated protobuf code that we are going to use for our messages. +We are going to publish two types of messages: `StringMsg` and `Vector3d` protobuf +messages. + +```{.py} + node = Node() + stringmsg_topic = "/example_stringmsg_topic" + vector3d_topic = "/example_vector3d_topic" + pub_stringmsg = node.advertise(stringmsg_topic, StringMsg) + pub_vector3d = node.advertise(vector3d_topic, Vector3d) +``` + +First of all we declare a *Node* that will offer some of the transport +functionality. In our case, we are interested in publishing topic updates, so +the first step is to announce our topics names and their types. Once a topic name +is advertised, we can start publishing periodic messages using the publishers objects. + +```{.py} + vector3d_msg = Vector3d() + vector3d_msg.x = 10 + vector3d_msg.y = 15 + vector3d_msg.z = 20 + stringmsg_msg = StringMsg() + stringmsg_msg.data = "Hello" + + try: + count = 0 + while True: + count += 1 + vector3d_msg.x = count + if not (pub_stringmsg.publish(stringmsg_msg)): + break + print("Publishing 'Hello' on topic [{}]".format(stringmsg_topic)) + if not (pub_vector3d.publish(vector3d_msg)): + break + print("Publishing a Vector3d on topic [{}]".format(vector3d_topic)) + time.sleep(0.1) + + except KeyboardInterrupt: + pass +``` + +In this section of the code we create the protobuf messages and fill them with +content. Next, we iterate in a loop that publishes one message every 100ms to +each topic. The method *publish()* sends a message to all the subscribers. + +## Subscriber + +Download the [subscriber.py](https://github.com/gazebosim/gz-transport/blob/gz-transport13/python/examples/subscriber.py) +file into the `gz_transport_tutorial` folder and open it with your favorite editor: + +\snippet python/examples/subscriber.py complete + +### Walkthrough + +```{.py} + from gz.msgs10.stringmsg_pb2 import StringMsg + from gz.msgs10.vector3d_pb2 import Vector3d + from gz.transport13 import Node +``` + +Just as before, we are importing the `Node` class from the `gz.transport13` library +and the generated code for the `StringMsg` and `Vector3d` protobuf messages. + +```{.py} + def stringmsg_cb(msg: StringMsg): + print("Received StringMsg: [{}]".format(msg.data)) + + def vector3_cb(msg: Vector3d): + print("Received Vector3: [x: {}, y: {}, z: {}]".format(msg.x, msg.y, msg.z)) +``` + +We need to register a function callback that will execute every time we receive +a new topic update. The signature of the callback is always similar to the ones +shown in this example with the only exception of the protobuf message type. +You should create a function callback with the appropriate protobuf type +depending on the type of the topic advertised. In our case, we know that topic +`/example_stringmsg_topic` will contain a Protobuf `StringMsg` type and topic +`/example_vector3d_topic` a `Vector3d` type. + +```{.py} + # create a transport node + node = Node() + topic_stringmsg = "/example_stringmsg_topic" + topic_vector3d = "/example_vector3d_topic" + # subscribe to a topic by registering a callback + if node.subscribe(StringMsg, topic_stringmsg, stringmsg_cb): + print("Subscribing to type {} on topic [{}]".format( + StringMsg, topic_stringmsg)) + else: + print("Error subscribing to topic [{}]".format(topic_stringmsg)) + return + # subscribe to a topic by registering a callback + if node.subscribe(Vector3d, topic_vector3d, vector3_cb): + print("Subscribing to type {} on topic [{}]".format( + Vector3d, topic_vector3d)) + else: + print("Error subscribing to topic [{}]".format(topic_vector3d)) + return +``` + +After the node creation, the method `subscribe()` allows you to subscribe to a +given topic by specifying the message type, the topic name and a subscription +callback function. + +```{.py} + # wait for shutdown + try: + while True: + time.sleep(0.001) + except KeyboardInterrupt: + pass +``` + +If you don't have any other tasks to do besides waiting for incoming messages, +we create an infinite loop that checks for messages each 1ms that will block +your current thread until you hit *CTRL-C*. + +## Updating PYTHONPATH + +If you made the binary installation of Gazebo Transport, you can skip this step +and go directly to the next section. Otherwise, if you built the package from +source it is needed to update the PYTHONPATH in order for Python to recognize +the library by doing the following: + +1. If you builded from source using `colcon`: +```{.sh} +export PYTHONPATH=$PYTHONPATH:/install/lib/python +``` +2. If you builded from source using `cmake`: +```{.sh} +export PYTHONPATH=$PYTHONPATH:/lib/python +``` + +## Running the examples + +Open two new terminals and directly run the Python scripts downloaded previously. + +From terminal 1: + +```{.sh} +python3 ./publisher.py +``` + +From terminal 2: + +```{.sh} +python3 ./subscriber.py +``` + +In your publisher terminal, you should expect an output similar to this one, +showing when a message is being published: + +```{.sh} +$ ./publisher.py +Publishing 'Hello' on topic [/example_stringmsg_topic] +Publishing a Vector3d on topic [/example_vector3d_topic] +Publishing 'Hello' on topic [/example_stringmsg_topic] +Publishing a Vector3d on topic [/example_vector3d_topic] +``` +In your subscriber terminal, you should expect an output similar to this one, +showing that your subscriber is receiving the topic updates: + +```{.sh} +$ ./subscriber.py +Received StringMsg: [Hello] +Received Vector3: [x: 2.0, y: 15.0, z: 20.0] +Received StringMsg: [Hello] +Received Vector3: [x: 3.0, y: 15.0, z: 20.0] +Received StringMsg: [Hello] +Received Vector3: [x: 4.0, y: 15.0, z: 20.0] +``` +## Threading in Gazebo Transport +The way Gazebo Transport is implemented, it creates several threads each time +a node, publisher, subscriber, etc is created. While this allows us to have a +better parallelization in processes, a downside is possible race conditions that +might occur if the ownership/access of variables is shared across multiple +threads. Even though Python has its [GIL](https://wiki.python.org/moin/GlobalInterpreterLock), +all the available features are bindings created for its C++ implementation, in +other words, the downsides mentioned before are still an issue to be careful about. We +recommend to always use threading locks when working with object that are used +in several places (publisher and subscribers). + +We developed a couple of examples that demonstrate this particular issue. Take +a look at a publisher and subscriber (whithin the same node) that have race +conditions triggered in the [data_race_without_mutex.py](https://github.com/gazebosim/gz-transport/blob/gz-transport13/python/examples/data_race_without_mutex.py) file. The proposed solution to this +issue is to use the `threading` library, you can see the same example with a mutex +in the [data_race_with_mutex.py](https://github.com/gazebosim/gz-transport/blob/gz-transport13/python/examples/data_race_with_mutex.py) file. + +You can run any of those examples by just doing the following in a terminal: +```{.sh} +python3 ./data_race_without_mutex.py +``` + +or + +```{.sh} +python3 ./data_race_with_mutex.py +``` + +## Advertise Options + +We can specify some options before we publish the messages. One such option is +to specify the number of messages published per topic per second. It is optional +to use but it can be handy in situations where we want to control the rate +of messages published per topic. + +We can declare the throttling option using the following code : + +```{.py} + from gz.msgs10.stringmsg_pb2 import StringMsg + from gz.transport13 import Node, AdvertiseMessageOptions + + # Create a transport node and advertise a topic with throttling enabled. + node = Node() + topic_name = "/foo" + + # Setting the throttling option + opts = AdvertiseMessageOptions() + opts.msgs_per_sec = 1 + pub = node.advertise(topic_name, StringMsg, opts); + if (!pub): + print("Error advertising topic" + topic_name) + return False +``` + +### Walkthrough + +```{.py} + # Setting the throttling option + opts = AdvertiseMessageOptions() + opts.msgs_per_sec = 1 +``` + +In this section of code, we declare an *AdvertiseMessageOptions* object and use +it to set the publishing rate (the member `msgs_per_sec`), In our case, the rate +specified is 1 msg/sec. + +```{.py} + pub = node.advertise(topic_name, StringMsg, opts); +``` + +Next, we advertise the topic with message throttling enabled. To do it, we pass opts +as an argument to the *advertise()* method. + +## Subscribe Options + +A similar option is also available for the Subscriber node which enables it +to control the rate of incoming messages from a specific topic. While subscribing +to a topic, we can use this option to control the number of messages received per +second from that particular topic. + +We can declare the throttling option using the following code : + +```{.py} + from gz.msgs10.stringmsg_pb2 import StringMsg + from gz.transport13 import Node, SubscribeOptions + + def stringmsg_cb(msg: StringMsg): + print("Received StringMsg: [{}]".format(msg.data)) + + # Create a transport node and subscribe to a topic with throttling enabled. + node = Node() + topic_name = "/foo" + opts = SubscribeOptions() + opts.msgs_per_sec = 1 + node.subscribe(StringMsg, topic_name, stringmsg_cb, opts) +``` + +### Walkthrough + +```{.py} + opts = SubscribeOptions() + opts.msgs_per_sec = 1 + node.subscribe(StringMsg, topic_name, stringmsg_cb, opts) +``` + +In this section of code, we create a *SubscribeOptions* object and use it to set +message rate (the member `msgs_per_sec`). In our case, the message rate specified +is 1 msg/sec. Then, we subscribe to the topic +using the *subscribe()* method with opts passed as an argument to it. + +## Topic remapping + +It's possible to set some global node options that will affect both publishers +and subscribers. One of these options is topic remapping. A topic remap +consists of a pair of topic names. The first name is the original topic name to +be replaced. The second name is the new topic name to use instead. As an example, +imagine that you recorded a collection of messages published over topic `/foo`. +Maybe in the future, you want to play back the log file but remapping the topic +`/foo` to `/bar`. This way, all messages will be published over the `/bar` +topic without having to modify the publisher and create a new log. + +We can declare the topic remapping option using the following code: + +```{.py} + from gz.transport13 import Node, NodeOptions + + # Create a transport node and remap a topic. + nodeOpts = NodeOptions() + nodeOptions.add_topic_remap("/foo", "/bar"); + node = Node(nodeOptions); +``` + +You can modify the publisher example to add this option. + +From terminal 1: + +```{.sh} +python3 ./publisher.py + +``` + +From terminal 2 (requires Gazebo Tools): + +```{.sh} +gz topic --echo -t /bar +``` + +And you should receive all the messages coming in terminal 2. + +The command `gz log playback` also supports the notion of topic remapping. Run +`gz log playback -h` in your terminal for further details (requires Gazebo Tools). + +## Service Requester + +Download the [requester.py](https://github.com/gazebosim/gz-transport/blob/gz-transport13/python/examples/requester.py) +file into the `gz_transport_tutorial` folder and open it with your favorite editor: + +\snippet python/examples/requester.py complete + +### Walkthrough + +```{.py} + from gz.msgs10.stringmsg_pb2 import StringMsg + from gz.transport13 import Node +``` + +Just as before, we are importing the `Node` class from the `gz.transport13` +library and the generated code for the `StringMsg` protobuf message. + +```{.py} + node = Node() + service_name = "/echo" + request = StringMsg() + request.data = "Hello world" + response = StringMsg() + timeout = 5000 +``` + +On these lines we are creating our *Node* object which will be used to create +the service request and defining all the relevant variables in order to create +a request, the service name, the timeout of the request, the request and response +data types. + +```{.py} + result, response = node.request(service_name, request, StringMsg, StringMsg, timeout) + print("Result:", result, "\nResponse:", response.data) +``` + +Here we are creating the service request to the `/echo` service, storing the +result and response of the request in some variables and printing them out. + +## Service Responser + +Unfortunately, this feature is not available on Python at the moment. However, +we can use a service responser created in C++ and make a request to it from a +code in Python. Taking that into account, we will use the [response.cc](https://github.com/gazebosim/gz-transport/blob/gz-transport13/example/responser.cc) file as the service responser. + +## Running the examples + +Open a new terminal and directly run the Python script downloaded previously. +It is expected that the service responser is running in another terminal for +this, you can refer to the previous tutorial \ref services. + +From terminal 1: + +```{.sh} +python3 ./requester.py +``` + +In your terminal, you should expect an output similar to this one, +showing the result and response from the request: + +```{.sh} +$ ./requester.py +Result: True +Response: Hello world +``` diff --git a/tutorials/06_security.md b/tutorials/07_security.md similarity index 98% rename from tutorials/06_security.md rename to tutorials/07_security.md index 69bad0210..6ed4829fe 100644 --- a/tutorials/06_security.md +++ b/tutorials/07_security.md @@ -1,7 +1,7 @@ \page security Security Next Tutorial: \ref relay -Previous Tutorial: \ref services +Previous Tutorial: \ref python ## Overview diff --git a/tutorials/07_relay.md b/tutorials/08_relay.md similarity index 100% rename from tutorials/07_relay.md rename to tutorials/08_relay.md