Entire-Metadata: .entire/metadata/fb87247f-2a75-407e-b336-cbc8c20ba5a6 Entire-Session: fb87247f-2a75-407e-b336-cbc8c20ba5a6 Entire-Strategy: manual-commit
559 lines
18 KiB
C++
559 lines
18 KiB
C++
/*
|
|
* (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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <functional> // for std::mem_fun
|
|
#include <vector>
|
|
|
|
#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
|
|
* <a href="../../../manuals/unit_test_framework_users_manual/index.html">user's
|
|
* manual</a> 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 <typename Tester, typename TestFunc>
|
|
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<BASE_CLASS&>(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<MyClass> {
|
|
private:
|
|
MyClass();
|
|
friend Singleton<MyClass>;
|
|
};
|
|
*/
|
|
template <class T>
|
|
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<int>(_test_functions.size());
|
|
}
|
|
|
|
int get_test_count_additional() const
|
|
{
|
|
return static_cast<int>(_test_functions_additional.size());
|
|
}
|
|
|
|
int get_test_count_basic_pubsub() const
|
|
{
|
|
return static_cast<int>(_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 <typename SingletonTester>
|
|
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<RTITestFunction> _test_functions;
|
|
std::vector<RTITestFunction> _test_functions_additional;
|
|
std::vector<RTITestFunction> _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 <typename SingletonTester>
|
|
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 <typename Tester>
|
|
void add_tester()
|
|
{
|
|
tester_.add_test_function(run_tester<Tester>);
|
|
}
|
|
|
|
template <typename Tester>
|
|
void add_tester_basic_pubsub()
|
|
{
|
|
tester_.add_test_function_basic_pubsub(run_tester<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_
|