/* * (c) Copyright, Real-Time Innovations, 2014-2022. * * All rights reserved. * No duplications, whole or partial, manual or electronic, may be made * without express written permission. Any such copies, or * revisions thereof, must display this notice unaltered. * This code contains trade secrets of Real-Time Innovations, Inc. */ #ifndef RTI_TEST_TESTER_HPP_ #define RTI_TEST_TESTER_HPP_ #include #include #include #include #include // for std::mem_fun #include #include "rtitest/test_setting.h" #include "rtitest/TestAssertion.hpp" /** * @mainpage Unit Test Framework (Modern C++) * @brief Modern C++ wrapper of Unit Test Framework. */ /** * @defgroup RTITestCPPClasses Test definitions * @brief Implements Unit Test Framework Modern C++ API. See the * user's * manual for examples of how to use most of these APIs. */ namespace rti { namespace test { // This adapter function takes a member function of a C++ Tester and runs it as // the C test framework expects: it catches exceptions, reports errors as // return codes and prints log info. template int run_test_function( const struct RTITestSetting *setting, const std::string& function_name, Tester& tester, TestFunc test_function) { // Log the test that is running RTITestLog_printTestNameExtWithFunctionName(function_name.c_str(), NULL); // Set the current test name in the global variable if (RTITest_g_currentTestNameAllocated != NULL) { size_t pos = function_name.find("::"); if (pos == std::string::npos) { if (function_name.size() < RTI_TEST_MAX_CURRENT_TEST_NAME_ALLOCATED_SIZE - 1) { RTITest_g_currentTestNameAllocated = strncpy(RTITest_g_currentTestNameAllocated, function_name.c_str(), function_name.size()); } } else { size_t method_name_size = pos + strlen("::"); if (function_name.size() - method_name_size < RTI_TEST_MAX_CURRENT_TEST_NAME_ALLOCATED_SIZE - 1) { RTITest_g_currentTestNameAllocated = strncpy(RTITest_g_currentTestNameAllocated, function_name.c_str() + method_name_size, function_name.size() - method_name_size); } } } try { tester.set_settings(setting); if (tester.file_system_is_required()) { tester.assert_file_system_support(); } // Run the actual test test_function(&tester); } catch (const TestFailure& ex) { // Test fails because an assertion failed RTITest_assertLogWithFunctionName( function_name.c_str(), &RTI_TEST_LOG_EMPTY_TEMPLATE, "%s", ex.what()); if (RTITestConnext_isAbortOnErrorEnabled()) { throw; } else { return 0; } } catch (const TestIncomplete& ex) { // Test fails because an assertion failed return ex.retcode(); } catch (const std::exception& ex) { // Test fails because some operation failed (not an assertion) RTITest_assertLogWithFunctionName( function_name.c_str(), &RTI_TEST_LOG_EMPTY_TEMPLATE, "Unhandled exception in test: %s", ex.what()); if (RTITestConnext_isAbortOnErrorEnabled()) { throw; } else { return 0; } } catch (...) { // Test fails because of an unknown exception RTITest_assertLogWithFunctionName( function_name.c_str(), &RTI_TEST_LOG_EMPTY_TEMPLATE, "Unknown exception in test"); if (RTITestConnext_isAbortOnErrorEnabled()) { throw; } else { return 0; } } return 1; // test passes } /** * @ingroup RTITestCPPClasses * @brief This macro generates and registers a function that the C test * framework can run. This macro must be called in a Tester constructor. */ #define RTI_TEST_FUNCTION_ADD(class_name__, test_function__) \ struct runner_##test_function__ { \ static int run(const struct RTITestSetting *setting) \ { \ return run_test_function( \ setting, \ class_name__::get_instance().get_name() + "::" #test_function__, \ class_name__::get_instance(), \ std::mem_fn(&class_name__::test_function__)); \ } \ }; \ \ add_test_function((RTITestFunction) runner_##test_function__::run) // The RTI_TEST_FUNCTION_ADD_ADDITIONAL macro was created to support splitting // tests as basic and additional. Tests added with this macro will belong to // the additional test set #define RTI_TEST_FUNCTION_ADD_ADDITIONAL(class_name__, test_function__) \ struct runner_##test_function__ { \ static int run(const struct RTITestSetting *setting) \ { \ return run_test_function( \ setting, \ class_name__::get_instance().get_name() + "::" #test_function__, \ class_name__::get_instance(), \ std::mem_fn(&class_name__::test_function__)); \ } \ }; \ \ add_test_function_additional((RTITestFunction) runner_##test_function__::run) // The RTI_TEST_FUNCTION_ADD_BASICPUBSUB macro was created to support running a // basic publish subscribe test as part of // CORE_BASICPUBSUB_VALIDATION_ARCHITECTURES. Tests added with this macro will // belong to _test_functions_basic_pubsub test set #define RTI_TEST_FUNCTION_ADD_BASICPUBSUB(class_name__, test_function__) \ struct runner_basicpubsub_##test_function__ { \ static int run(const struct RTITestSetting *setting) \ { \ return run_test_function( \ setting, \ class_name__::get_instance().get_name() + "::" #test_function__, \ class_name__::get_instance(), \ std::mem_fn(&class_name__::test_function__)); \ } \ }; \ \ add_test_function_basic_pubsub( \ (RTITestFunction) runner_basicpubsub_##test_function__::run) #define RTI_TEST_FUNCTION_ADD_FROM_BASE_CLASS( \ CLASS_NAME, BASE_CLASS, TEST_FUNCTION) \ struct runner_ ## TEST_FUNCTION { \ static int run(const struct RTITestSetting* setting) \ { \ return rti::test::run_test_function( \ setting, \ CLASS_NAME::get_instance().get_name()+"::"#TEST_FUNCTION, \ static_cast(CLASS_NAME::get_instance()), \ std::mem_fn(&BASE_CLASS::TEST_FUNCTION)); \ } \ }; \ \ add_test_function((RTITestFunction) runner_ ## TEST_FUNCTION::run); /* Inherit from this class to get a singleton behavior: class MyClass : public Singleton { private: MyClass(); friend Singleton; }; */ template class Singleton { public: static T& get_instance() { static T instance; return instance; } }; /** * @brief Auxiliary class that augments C structs with a destructor. * * @param T The type of the struct to augment. * @param R1 The return type of the initializer function. * @param InitializeFn The function to call when the struct is created. * @param R2 The return type of the finalizer function. * @param FinalizeFn The function to call when the struct is destroyed. * * @details This class is used to wrap C structs that have a constructor and * destructor function. The constructor will call the provided initializer * function, and the destructor will call the provided finalizer function. * The struct must be trivially destructible. */ template < typename T, typename R1, R1 InitializeFn(T *), typename R2, R2 FinalizeFn(T *)> struct RaiiWrapper : public T { RaiiWrapper() { (void) InitializeFn(this); } ~RaiiWrapper() { (void) FinalizeFn(this); } RaiiWrapper(RaiiWrapper const&) = delete; RaiiWrapper(RaiiWrapper&&) = delete; RaiiWrapper& operator=(RaiiWrapper const&) = delete; RaiiWrapper& operator=(RaiiWrapper&&) = delete; }; /* Provides basic functionality all Testers can benefit from: * Registration of test functions * Running those test functions using the underlying test framework * Accessing the test settings * Indicating that a test requires file system--that will ignore all tests if the current platform doesn't have one. */ class Tester { public: Tester(const std::string& name, RTITestLevel test_level = RTI_TEST_LEVEL_UNIT, bool tests_require_file_system = false) : _settings(NULL), _name(name), _test_level(test_level), _tests_require_file_system(tests_require_file_system) { } virtual ~Tester() { } const std::string& get_name() const { return _name; } // Methods to register and run tests: int get_test_count() const { return static_cast(_test_functions.size()); } int get_test_count_additional() const { return static_cast(_test_functions_additional.size()); } int get_test_count_basic_pubsub() const { return static_cast(_test_functions_basic_pubsub.size()); } const RTITestFunction *get_test_functions() const { return (_test_functions.empty() ? NULL : &_test_functions[0]); } const RTITestFunction *get_test_functions_additional() const { return (_test_functions_additional.empty() ? NULL : &_test_functions_additional[0]); } const RTITestFunction *get_test_functions_basic_pubsub() const { return (_test_functions_basic_pubsub.empty() ? NULL : &_test_functions_basic_pubsub[0]); } void add_test_function(RTITestFunction fnc) { _test_functions.push_back(fnc); } void add_test_function_additional(RTITestFunction fnc) { _test_functions_additional.push_back(fnc); } void add_test_function_basic_pubsub(RTITestFunction fnc) { _test_functions_basic_pubsub.push_back(fnc); } template void merge_tester() { merge_tester(SingletonTester::get_instance()); } void set_settings(const RTITestSetting *settings) { _settings = settings; } const RTITestSetting *get_settings() const { if (_settings == NULL) { throw std::logic_error("test settings not specified"); } return _settings; } std::string get_resource_path(const std::string& file) const { char resourcePath[RTI_TEST_MAX_FILE_NAME_LENGTH] = { 0 }; RTI_TEST_ASSERT(RTITestSetting_getFullResourcePathFileName( get_settings(), resourcePath, file.c_str())); return std::string(resourcePath); } int32_t domain_id() const { return get_settings()->id; } bool file_system_is_required() const { return _tests_require_file_system; } bool file_system_support() const { return (RTITestSetting_fileTestIsSupport(get_settings()) != 0); } void assert_file_system_support() const { if (!file_system_support()) { RTI_TEST_THROW_UNSUPPORTED("File system not supported"); } } virtual RTIBool run_tests(const RTITestSetting *settings) { int begin, end; RTITestFunction *testSelectionBasic = NULL, *testSelectionAdditional = NULL; int testSelectionBasicSize = 0, testSelectionAdditionalSize = 0; RTITestSetting_getRange(settings, &begin, &end, _test_level); if (RTITestSetting_isCoreBasicPubSubValidationArchitecture() && !RTITestSetting_isRangeSpecified(begin, end)) { testSelectionBasic = (RTITestFunction *) get_test_functions_basic_pubsub(); testSelectionBasicSize = get_test_count_basic_pubsub(); testSelectionAdditional = NULL; testSelectionAdditionalSize = 0; } else { testSelectionBasic = (RTITestFunction *) get_test_functions(); testSelectionBasicSize = get_test_count(); testSelectionAdditional = (RTITestFunction *) get_test_functions_additional(); testSelectionAdditionalSize = get_test_count_additional(); } return RTITestSetting_runTestsExt( settings, _name.c_str(), testSelectionBasic, testSelectionBasicSize, testSelectionAdditional, testSelectionAdditionalSize, begin, end); } private: // Adds the test functions from src_tester into dest_tester. // Useful to combine smaller Testers into a larger one void merge_tester(Tester& src_tester) { const RTITestFunction *src_functions = src_tester.get_test_functions(); const int test_count = src_tester.get_test_count(); const RTITestFunction *src_functions_additional = src_tester.get_test_functions_additional(); const int test_count_additional = src_tester.get_test_count_additional(); const RTITestFunction *src_functions_basic_pubsub = src_tester.get_test_functions_basic_pubsub(); const int test_count_basicpubsub = src_tester.get_test_count_basic_pubsub(); if (src_functions != NULL) { for (int i = 0; i < test_count; i++) { add_test_function(src_functions[i]); } } if (src_functions_additional != NULL) { for (int i = 0; i < test_count_additional; i++) { add_test_function_additional(src_functions_additional[i]); } } if (src_functions_basic_pubsub != NULL) { for (int i = 0; i < test_count_basicpubsub; i++) { add_test_function_basic_pubsub(src_functions_basic_pubsub[i]); } } } const RTITestSetting *_settings; enum { MAX_TEST_COUNT = 64 }; std::vector _test_functions; std::vector _test_functions_additional; std::vector _test_functions_basic_pubsub; const std::string _name; RTITestLevel _test_level; bool _tests_require_file_system; }; // Generic function to run all the test functions in a Tester. // When instantiated for a concrete Tester it has the // signature that the C test framework expects. template int run_tester(const struct RTITestSetting *setting) { return SingletonTester::get_instance().run_tests(setting); // does not // throw } /** * @ingroup RTITestCPPClasses * @brief A container of Testers. This is the top level tester, so tester * classes should inherits from this class. */ class TesterContainer { public: TesterContainer(const std::string& name) : tester_(name, RTI_TEST_LEVEL_SUBMODULE) { } virtual ~TesterContainer() { // Empty virtual destructor added because this class includes virtual // functions } template void add_tester() { tester_.add_test_function(run_tester); } template void add_tester_basic_pubsub() { tester_.add_test_function_basic_pubsub(run_tester); } /** * @brief Runs the tests. * * @param setting \b In. The RTITestSetting object that contains the test configuration. * @return int The exit code of the test run. 0 if successful, 1 otherwise. */ int run_tests(const RTITestSetting& setting) { if (!on_tests_begin(setting)) { return 1; } int ok = tester_.run_tests(&setting); if (!on_tests_end(setting)) { ok = 0; } RTITestSetting_printComplete(tester_.get_name().c_str()); return ok ? 0 : 1; // return 0 on success as expected by main() } /** * @brief Parses the command-line arguments and runs the tests. * * @param argc \b In. The number of command-line arguments. * @param argv \b In. The command-line arguments. * @return The exit code of the test execution. 0 if successful, 1 otherwise. */ int run_tests(int argc, char **argv) { RTITestSetting setting; RTITestSetting_init(&setting); if (!RTITestSetting_parseArgument(&setting, argc, argv)) { return RTI_FALSE; } return run_tests(setting); } protected: // These function can be overridden to add additional behavior before and // after running all tests. virtual bool on_tests_begin(const RTITestSetting&) { return true; } virtual bool on_tests_end(const RTITestSetting&) { return true; } private: Tester tester_; // does most of the work }; }} // namespace rti::test #endif // RTI_TEST_TESTER_HPP_