{"id":13702988,"url":"https://github.com/osrf/osrf_testing_tools_cpp","last_synced_at":"2025-07-07T17:03:30.756Z","repository":{"id":30977885,"uuid":"126552902","full_name":"osrf/osrf_testing_tools_cpp","owner":"osrf","description":"Common testing tools for C++ which are used for testing in various OSRF projects.","archived":false,"fork":false,"pushed_at":"2025-04-24T19:04:21.000Z","size":6567,"stargazers_count":34,"open_issues_count":11,"forks_count":29,"subscribers_count":11,"default_branch":"rolling","last_synced_at":"2025-04-25T10:38:23.206Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/osrf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-24T00:48:57.000Z","updated_at":"2025-04-24T19:04:24.000Z","dependencies_parsed_at":"2024-04-30T21:54:32.188Z","dependency_job_id":"c90d3aae-0207-4fab-ae6e-b3f6ef1a0edf","html_url":"https://github.com/osrf/osrf_testing_tools_cpp","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osrf%2Fosrf_testing_tools_cpp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osrf%2Fosrf_testing_tools_cpp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osrf%2Fosrf_testing_tools_cpp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osrf%2Fosrf_testing_tools_cpp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/osrf","download_url":"https://codeload.github.com/osrf/osrf_testing_tools_cpp/tar.gz/refs/heads/rolling","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252458316,"owners_count":21751014,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T21:00:48.071Z","updated_at":"2025-05-05T07:30:30.938Z","avatar_url":"https://github.com/osrf.png","language":"C++","funding_links":[],"categories":["Development Environment"],"sub_categories":["Unit and Integration Test"],"readme":"## osrf_testing_tools_cpp Repository\n\nThis repository contains testing tools for C++, and is used in OSRF projects.\n\n### osrf_testing_tools_cpp\n\nThe `osrf_testing_tools_cpp` folder is where all the actually useful code lives within a cmake project of the same name.\n\nAny CMake based project can use `osrf_testing_tools_cpp` by using CMake's `find_package()` and then using the various CMake macros, C++ headers and libraries, and other tools as described below.\n\nThis package requires C++11 only, but should also work with C++14 and C++17.\n\n#### Contents\n\nThere are several useful components in the `osrf_testing_tools_cpp` project, and they're briefly described below.\n\n##### Googletest\n\nThere are a few CMake macros which can provide access to one of a few versions of `googletest` so that it can be used within your own projects without having to include a copy in each of them.\n\n###### Example\n\n```cmake\ninclude(CTest)\nif(BUILD_TESTING)\n  find_package(osrf_testing_tools_cpp REQUIRED)\n  osrf_testing_tools_cpp_require_googletest(VERSION_GTE 1.8)  # ensures target gtest_main exists\n\n  add_executable(my_gtest ...)\n  target_link_libraries(my_gtest gtest_main ...)\nendif()\n```\n\nYou can also list the available versions with `osrf_testing_tools_cpp_get_googletest_versions()`.\n\nThis provides access to both \"gtest\" and \"gmock\" headers.\n\n##### add_test with Environment Variables\n\nThere is a CMake macro `osrf_testing_tools_cpp_add_test()` which acts much like CMake/CTest's `add_test()` macro, but also has some additional arguments `ENV`, `APPEND_ENV` (for `PATH`-like environment variables), and an OS agnostic `APPEND_LIBRARY_DIRS`.\n\nThis is accomplished with an executable (writtin in C++) which gets put in from of your test executable with additional arguments for any environment variable changes you desire.\nThis \"test runner\" executable modifies the environment and then executes your test command as specified.\n\n###### Example\n\n```cmake\nosrf_testing_tools_cpp_add_test(test_my_executable\n  COMMAND \"$\u003cTARGET_FILE:my_executable\u003e\" arg1 --arg2\n  ENV FOO=bar\n  APPEND_ENV PATH=/some/additional/path/bin\n  APPEND_LIBRARY_DIRS /some/additional/library/path\n)\n```\n\nThis might result in CTest output something like this (this example output is from macOS, hence the `DYLD_LIBRARY_PATH` versus `LD_LIBRARY_PATH` on Linux or `PATH` on Windows):\n\n```\ntest 1\n    Start 1: test_my_executable\n\n1: Test command: /some/path/install/osrf_testing_tools_cpp/lib/osrf_testing_tools_cpp/test_runner \"--env\" \"FOO=bar\" \"--append-env\" \"PATH=/some/additional/path/bin\" \"DYLD_LIBRARY_PATH=/some/additional/library/path\" \"--\" \"/some/path/$\nbuild/my_cmake_project/test_example_memory_tools_gtest\" \"arg1\" \"--arg2\"\n```\n\n##### memory_tools\n\nThis API lets you intercept calls to dynamic memory calls like `malloc` and `free`, and provides some convenience functions for differentiating between expected and unexpected calls to dynamic memory functions.\n\nRight now it only works on Linux and macOS.\n\n###### Example Test\n\nHere's a simple, googletest based example of how it is used:\n\n```c++\n#include \u003ccstdlib\u003e\n#include \u003cthread\u003e\n\n#include \u003cgtest/gtest.h\u003e\n#include \u003cgtest/gtest-spi.h\u003e\n\n#include \"osrf_testing_tools_cpp/memory_tools/memory_tools.hpp\"\n#include \"osrf_testing_tools_cpp/scope_exit.hpp\"\n\nvoid my_first_function()\n{\n  void * some_memory = std::malloc(1024);\n  // .. do something with it\n  std::free(some_memory);\n}\n\nint my_second_function(int a, int b)\n{\n  return a + b;\n}\n\nTEST(TestMemoryTools, test_example) {\n  // you must initialize memory tools, but uninitialization is optional\n  osrf_testing_tools_cpp::memory_tools::initialize();\n  OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({\n    osrf_testing_tools_cpp::memory_tools::uninitialize();\n  });\n\n  // create a callback for \"unexpected\" mallocs that does a non-fatal gtest\n  // failure and then register it with memory tools\n  auto on_unexpected_malloc =\n    [](osrf_testing_tools_cpp::memory_tools::MemoryToolsService \u0026 service) {\n      ADD_FAILURE() \u003c\u003c \"unexpected malloc\";\n      // this will cause a bracktrace to be printed for each unexpected malloc\n      service.print_backtrace();\n    };\n  osrf_testing_tools_cpp::memory_tools::on_unexpected_malloc(on_unexpected_malloc);\n\n  // at this point, you'll still not get callbacks, since monitoring is not enabled\n  // so calling either user function should not fail\n  my_first_function();\n  EXPECT_EQ(my_second_function(1, 2), 3);\n\n  // enabling monitoring will allow checking to begin, but the default state is\n  // that dynamic memory calls are expected, so again either function will pass\n  osrf_testing_tools_cpp::memory_tools::enable_monitoring();\n  my_first_function();\n  EXPECT_EQ(my_second_function(1, 2), 3);\n\n  // if you then tell memory tools that malloc is unexpected, then it will call\n  // your above callback, at least until you indicate malloc is expected again\n  EXPECT_NONFATAL_FAILURE({\n    EXPECT_NO_MALLOC({\n      my_first_function();\n    });\n  }, \"unexpected malloc\");\n  // There are also explicit begin/end functions if you need variables to leave the scope\n  osrf_testing_tools_cpp::memory_tools::expect_no_malloc_begin();\n  int result = my_second_function(1, 2);\n  osrf_testing_tools_cpp::memory_tools::expect_no_malloc_end();\n  EXPECT_EQ(result, 3);\n\n  // enable monitoring only works in the current thread, but you can enable it for all threads\n  osrf_testing_tools_cpp::memory_tools::enable_monitoring_in_all_threads();\n  std::thread t1([]() {\n    EXPECT_NONFATAL_FAILURE({\n      EXPECT_NO_MALLOC({\n        my_first_function();\n      });\n    }, \"unexpected malloc\");\n    osrf_testing_tools_cpp::memory_tools::expect_no_malloc_begin();\n    int result = my_second_function(1, 2);\n    osrf_testing_tools_cpp::memory_tools::expect_no_malloc_end();\n    EXPECT_EQ(result, 3);\n  });\n  t1.join();\n\n  // disabling monitoring in all threads should not catch the malloc in my_first_function()\n  osrf_testing_tools_cpp::memory_tools::disable_monitoring_in_all_threads();\n  osrf_testing_tools_cpp::memory_tools::enable_monitoring();\n  std::thread t2([]() {\n    EXPECT_NO_MALLOC({\n      my_first_function();\n    });\n    osrf_testing_tools_cpp::memory_tools::expect_no_malloc_begin();\n    int result = my_second_function(1, 2);\n    osrf_testing_tools_cpp::memory_tools::expect_no_malloc_end();\n    EXPECT_EQ(result, 3);\n  });\n  t2.join();\n}\n```\n\nIn order for memory tools to work properly and intercept dynamic memory calls in all libraries, the library pre-load environment variable needs to be used, `LD_PRELOAD` on Linux and `DYLD_INSERT_LIBRARIES` on macOS.\nYou can use the above `osrf_testing_tools_cpp_add_test()` macro to set this environment variable before running any tests, unless you have another way to do so.\n\nFor example, here is some CMake code that will build a test and add a CTest for it that properly sets the library pre-load environment variable on all support OS's:\n\n```cmake\ninclude(CTest)\nif(BUILD_TESTING)\n  find_package(osrf_testing_tools_cpp REQUIRED)\n  osrf_testing_tools_cpp_require_googletest(VERSION_GTE 1.8)  # ensures target gtest_main exists\n\n  add_executable(test_example_memory_tools_gtest test/test_example_memory_tools.cpp)\n  target_link_libraries(test_example_memory_tools_gtest\n    gtest_main\n    osrf_testing_tools_cpp::memory_tools\n  )\n  get_target_property(extra_env_vars\n    osrf_testing_tools_cpp::memory_tools LIBRARY_PRELOAD_ENVIRONMENT_VARIABLE)\n  osrf_testing_tools_cpp_add_test(test_example_memory_tools\n    COMMAND \"$\u003cTARGET_FILE:test_example_memory_tools_gtest\u003e\"\n    ENV ${extra_env_vars}\n  )\nendif()\n```\n\nThe `LIBRARY_PRELOAD_ENVIRONMENT_VARIABLE` property of the `osrf_testing_tools_cpp::memory_tools` target contains the appropriate environment variable, but is not used automatically.\nIt must be set before the test is run, and note that the `$ENV{}` syntax in CMake is not sufficient, as that only affects the environment variables at configure time and not at test time.\n\nYou can see this code in action in the `test_osrf_testing_tools_cpp` example CMake project.\n\n##### Various C++ Utilities\n\n###### std::variant Helper\n\nThe `std::variant` feature isn't available until C++17, but using the [mpark/variant](https://github.com/mpark/variant) project the `osrf_testing_tools_cpp` project provides access to a C++11 and C++14 compatible version via the `osrf_testing_tools_cpp/variant.hpp` header.\n\nIn the case that you're using C++17, the normal `std::variant` is used.\nSee cppreference.com for documentation:\n\nhttp://en.cppreference.com/w/cpp/utility/variant\n\n###### Scope Exit Idiom\n\nThe `osrf_testing_tools_cpp/scope_exit.hpp` header contains a class and a macro to make it easy to perform some code at the exit of the current scope, which is often useful for setting up automatic resource cleanup.\n\nFor example:\n\n```c++\n{\n  auto foo = new int;\n  OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({\n    delete foo;\n  });\n  if (condition) {\n    throw std::runtime_error(\"error that might have caused foo to leak\");\n  }\n}\n```\n\nIn the above example, `foo` will be deleted when the scope ends.\n\n###### Demangling C++ Symbols\n\nThese functions are in `osrf_testing_tools_cpp/demangle.hpp` and can be used to generate human readable symbols (on supported) platforms from the result of `typeid` or a mangled string (gathered from a backtrace or from the output of `nm`, for example).\n\nYou can use it with a type directly:\n\n```c++\n#include \u003ccstdio\u003e\n#include \u003cmap\u003e\n\n#include \u003cosrf_testing_tools_cpp/demangle.hpp\u003e\n\nint main(void) {\n  std::map\u003cint, float\u003e some_map;\n  printf(\"%s\\n\", osrf_testing_tools_cpp::demangle(some_map).c_str());\n  auto type_name = typeid(int).name();\n  printf(\"%s\\n\", osrf_testing_tools_cpp::demangle_str(type_name).c_str());\n  return 0;\n}\n\n```\n\nThis program might output (on supported platforms):\n\n```\nstd::__1::map\u003cint, float, std::__1::less\u003cint\u003e, std::__1::allocator\u003cstd::__1::pair\u003cint const, float\u003e \u003e \u003e\nint\n```\n\n### test_osrf_testing_tools_cpp\n\nThe `test_osrf_testing_tools_cpp` folder is an example or test cmake project, which demonstrates how to use it, including `find_package()`'ing it and using `memory_tools` to implement a test.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosrf%2Fosrf_testing_tools_cpp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fosrf%2Fosrf_testing_tools_cpp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosrf%2Fosrf_testing_tools_cpp/lists"}