/* (c) Copyright, Real-Time Innovations, 2016-. 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 HPP_APPUTILS_FRAMEWORK_HPP_ #define HPP_APPUTILS_FRAMEWORK_HPP_ // IMPORTANT: macros.hpp must be the first RTI header included in every header // file so that symbols are exported correctly on Windows #include #include #include #include #include #include #include #include #include #include "rtitest/test_setting.h" #include "osapi/osapi_heap.h" #include "osapi/osapi_process.h" #ifndef property_propertyNames_h #include "property/property_propertyNames.h" #endif #include "pres/pres_common.h" #include #include "rti/test/generic_tests.hpp" #include "rti/test/AbstractDomainTester.hpp" #include "rtitest/Tester.hpp" #include "rti/test/utilities.hpp" #include "rti/apputils/util/Path.hpp" #include "rti/apputils/util/Utils.hpp" #include "dds/core/Time.hpp" #include "dds/dds.hpp" #include "dds/pub/DataWriter.hpp" #include "dds/sub/DataReader.hpp" #include "dds/pub/Publisher.hpp" #include "rti/core/ListenerBinder.hpp" #include "rti/core/constants.hpp" namespace rti { namespace apputils { namespace test { /** * A set of utility functions to use in the tests. */ class TestUtils { public: static std::string get_executable_path(const RTITestSetting *settings) { char path[RECORDER_FILE_PATH_MAX_LENGTH] = ""; RTI_TEST_ASSERT( RTIOsapiUtility_getExecutablePath( path, RECORDER_FILE_PATH_MAX_LENGTH + 1, settings->execPath)); return std::string(path); } static std::string get_resource_path( const RTITestSetting *settings, const std::string& file) { char resource_path[RECORDER_FILE_PATH_MAX_LENGTH] = ""; RTI_TEST_ASSERT( RTITestSetting_getFullResourcePathFileName( settings, resource_path, file.c_str())); return std::string(resource_path); } /** * Build a multi-file string, prepending the default DDS QoS file to the * file name passed as a parameter. Recall that files are separated by the * ';' character. */ static const std::string build_config_file( const RTITestSetting *settings, const std::string& config_file_name) { std::stringstream config_file_stream; config_file_stream << get_resource_path( settings, "/xml/USER_QOS_PROFILES.xml"); config_file_stream << ";"; config_file_stream << get_resource_path(settings, config_file_name); return config_file_stream.str(); } static int64_t get_cache_sample_count(DDS_DataReader *reader) { DDS_DataReaderCacheStatus cache_status = DDS_DataReaderCacheStatus_INITIALIZER; DDS_ReturnCode_t retcode = DDS_DataReaderCacheStatus_initialize(&cache_status); RTI_TEST_ASSERT(retcode == DDS_RETCODE_OK); std::shared_ptr cache_status_ptr( &cache_status, DDS_DataReaderCacheStatus_finalize); retcode = DDS_DataReader_get_datareader_cache_status( reader, &cache_status); RTI_TEST_ASSERT(retcode == DDS_RETCODE_OK); return cache_status.sample_count; } static int32_t get_matched_participant_count( DDS_DomainParticipant *participant) { DDS_InstanceHandleSeq participant_handles = DDS_SEQUENCE_INITIALIZER; RTI_TEST_ASSERT(DDS_InstanceHandleSeq_initialize(&participant_handles)); /* * Using std::shared_ptr because we just want RAII semantics and the * construction of the smart pointer is easier than that of a * std::unique_ptr. */ std::shared_ptr participant_handles_ptr( &participant_handles, DDS_InstanceHandleSeq_finalize); DDS_ReturnCode_t retcode = DDS_DomainParticipant_get_discovered_participants( participant, &participant_handles); RTI_TEST_ASSERT(retcode == DDS_RETCODE_OK); int32_t num_participants = DDS_InstanceHandleSeq_get_length(&participant_handles); return num_participants; } static int32_t get_matched_publications_count(DDS_DataReader *reader) { DDS_InstanceHandleSeq publication_handles = DDS_SEQUENCE_INITIALIZER; RTI_TEST_ASSERT(DDS_InstanceHandleSeq_initialize(&publication_handles)); /* * Using std::shared_ptr because we just want RAII semantics and the * construction of the smart pointer is easier than that of a * std::unique_ptr. */ std::shared_ptr publication_handles_ptr( &publication_handles, DDS_InstanceHandleSeq_finalize); DDS_ReturnCode_t retcode = DDS_DataReader_get_matched_publications( reader, &publication_handles); RTI_TEST_ASSERT(retcode == DDS_RETCODE_OK); int32_t num_publications = DDS_InstanceHandleSeq_get_length(&publication_handles); return num_publications; } static int32_t get_matched_subscriptions_count(DDS_DataWriter *writer) { DDS_InstanceHandleSeq subscription_handles = DDS_SEQUENCE_INITIALIZER; RTI_TEST_ASSERT(DDS_InstanceHandleSeq_initialize(&subscription_handles)); /* * Using std::shared_ptr because we just want RAII semantics and the * construction of the smart pointer is easier than that of a * std::unique_ptr. */ std::shared_ptr subscription_handles_ptr( &subscription_handles, DDS_InstanceHandleSeq_finalize); DDS_ReturnCode_t retcode = DDS_DataWriter_get_matched_subscriptions( writer, &subscription_handles); RTI_TEST_ASSERT(retcode == DDS_RETCODE_OK); int32_t num_subscriptions = DDS_InstanceHandleSeq_get_length(&subscription_handles); return num_subscriptions; } }; class ParticipantBuiltinTopicListener : public dds::all::NoOpDataReaderListener< dds::topic::ParticipantBuiltinTopicData> { public: ParticipantBuiltinTopicListener() { } void on_data_available( dds::sub::DataReader& reader) { dds::sub::LoanedSamples< dds::topic::ParticipantBuiltinTopicData> samples = reader.read(); dds::domain::DomainParticipant local_participant = reader.subscriber().participant(); char current_process_id_as_string[64] = {0}; RTIOsapiUtility_snprintf( current_process_id_as_string, 64, "%llu", RTIOsapiProcess_getId()); dds::sub::LoanedSamples< dds::topic::ParticipantBuiltinTopicData>::iterator sample_it = samples.begin(); const std::string property_key(PROPERTY_NAME_DDS_SYS_INFO_PROCESS_ID); for (; sample_it != samples.end(); ++sample_it) { if (sample_it->info().valid() && sample_it->info().state().instance_state() == dds::sub::status::InstanceState::alive() && sample_it->info().state().view_state() == dds::sub::status::ViewState::new_view()) { /* * Juanlu: if we don't check the existence of the property first * then the get() method will throw, printing the message: * on_data_available:!property does not exist */ if (sample_it->data()->property().exists(property_key)) { std::string process_property = sample_it->data()->property().get(property_key); if (!process_property.empty() && process_property != current_process_id_as_string) { dds::domain::ignore( local_participant, sample_it->info().instance_handle()); } } else { /* Participants that do not contain this property are not * part of the test */ dds::domain::ignore( local_participant, sample_it->info().instance_handle()); } } } } }; /** * Utility class wrapping random number generation. Currently based on std::rand * and a seed, that is output to a file on error. This allows for repeatability * of the execution. * The seed used to execute will be stored in a file called .seed, in the * current working directory. This file will be looked for upon construction and * if present, the value of the seed will be used as a seed to the std::rand * function. * Manually delete the .seed file if you want to resume non-repeatable * execution. * In the future, when we integrate with newer C++ standards, explore the * possiblity of using std::random_device instead. */ class RandGenerator { public: static const std::string& seed_file_name() { static std::string SEED_FILE_NAME_STR(".seed"); return SEED_FILE_NAME_STR; } RandGenerator(bool print_seed = true) : seed_(0), error_(true) { seed_ = std::chrono::time_point_cast( std::chrono::high_resolution_clock::now()). time_since_epoch().count() % rti::core::nanosec_per_sec; rti::apputils::util::Path seed_file(seed_file_name()); if (seed_file.exists()) { std::ifstream seed_f_stream((const char *) seed_file); seed_f_stream >> seed_; } RTIOsapiUtility_srandWithSeed(seed_); if (print_seed) { RTITestLog_freeForm( RTI_LOG_BIT_EXCEPTION, "Random generator seed for the test: %lu\n", static_cast(seed_)); } } ~RandGenerator() { rti::apputils::util::Path seed_file(seed_file_name()); if (!seed_file.exists()) { if (error_) { std::ofstream seed_f_stream((const char *) seed_file); seed_f_stream << seed_; seed_f_stream.close(); } } } int32_t next(int32_t max) { int32_t random = RTIOsapiUtility_rand(); return random % max; } int32_t next(int32_t min, int32_t max) { int32_t random = RTIOsapiUtility_rand(); return min + random % (max - min + 1); } void set_error(bool error) { error_ = error; } private: uint32_t seed_; bool error_; }; /** * Utility class designed to make sure that we remove test files after a test is * executed. This substitutes the old pattern using explicit calls to * RTIOsapiFile_remove() at the end of the test, that are not reached when the * test exits abnormally (fails). * * This class is supposed to be used with the following pattern: * 1) Create the TestFileSet object at the beginning of the test. * 2) Add the files and directories for which we want to link destruction to the * destruction of the object itself. * 3) Add a call to set_remove(true). If this call is added at the end of the * test code, then if the test fails no files are deleted. This allows for * inspection to determine the cause of the error. If added at any other * point in the code, the files and directories will be deleted even if any * test code after that point fails. * We generally add calls to set_remove(true) when */ class TestFileSet { public: TestFileSet() : remove_(false) { } ~TestFileSet() { if (remove_) { std::list::iterator it = files_.begin(); for (; it != files_.end(); it++) { if (it->exists()) { if (!RTIOsapiFile_remove(*it)) { RTITestLog_freeForm( RTI_LOG_BIT_WARN, "Error removing file %s\n", it->c_str());} } } it = directories_.begin(); for (; it != directories_.end(); it++) { if (it->exists()) { try { rti::apputils::util::Path::remove_directory(*it); } catch (std::exception& ex) { RTITestLog_freeForm( RTI_LOG_BIT_WARN, "Error removing directory %s; exception = %s\n", it->c_str(), ex.what()); } } } } } /** * @brief Add a file path to the list of tracked files to be removed after * completing the test. If the file already existed, it will be removed. * This allows running tests with retries. * * Note: this method should be called before the file is actually created by * the test, otherwise the file will be deleted automatically because of the * desired behaviour above. */ void add_file(const rti::apputils::util::Path& file) { if (file.exists()) { RTI_TEST_ASSERT(RTIOsapiFile_remove(file)); } files_.push_back(file); } /** * @brief Add a directory path to the list of tracked directories to be * removed after completing the test. If the directory already existed, it * will be removed. This allows running the tests with retries. * * Important notes: * - when adding directories and one contains the other or * others, add the deeper directories first. * - this method should be called before the directory is actually created * by the test, otherwise the directory will be deleted automatically * because of the desired behaviour above. */ void add_directory(const rti::apputils::util::Path& directory) { if (directory.exists()) { rti::apputils::util::Path::remove_directory(directory); } directories_.push_back(directory); } /** * @brief Set or unset the boolean flag controlling the deletion of * directories and files associated with this TestFileSet. * * @param remove The value of the remove flag. This flag is false when the * TestFileSet object is created. Setting it to true will * enable automatic deletion of the associated directories and * files. */ void set_remove(bool remove) { remove_ = remove; } private: std::list files_; std::list directories_; bool remove_; }; #define TEST_TYPES_FILE_NAME "RecorderServiceTestTypes.xml" /** * A singleton abstraction that generates a new directory (in most platforms, * inside the OS's temporary directory) for the tests to create by-products * during their execution. * If the tests pass, the directory will be deleted before the instance is * destroyed. */ class TestWorkspaceProvider { public: static TestWorkspaceProvider& init(RTITestSetting& setting) { static TestWorkspaceProvider singleton_(setting); return singleton_; } static const std::string& get() { /* * The recording service tests generate byproducts in the form of * database files. This can be problematic when running, for example, * the tester application from the same tree, at the same time. We're * going to provide a mechanism here where all the by-product files will * be created inside a temporary working directory. We'll modify all the * test configurations so that they define filenames based on an * environment variable defining a directory that will be unique per * execution. */ static std::string WORKSPACE_DIR; if (WORKSPACE_DIR.empty()) { #ifdef RTI_WIN32 std::string placeholder; #include "windows.h" char placeholder_str[MAX_PATH]; RTINtpTime now; RTIOsapiUtility_getTime(&now); DDS_Duration_t duration; DDS_Duration_from_ntp_time(&duration, &now); dds::core::Duration cpp_duration(duration.sec, duration.nanosec); char temp_path[MAX_PATH]; // Get the temporary directory uint32_t size = GetTempPath(MAX_PATH, temp_path); RTI_TEST_ASSERT(size <= MAX_PATH && size != 0); placeholder.append(temp_path); placeholder.append("Recorder4Tester_"); std::ostringstream int_str; int_str << cpp_duration.to_millisecs(); placeholder.append(int_str.str()); RTI_TEST_ASSERT(CreateDirectory(placeholder.c_str(), NULL)); RTI_TEST_ASSERT(RTIOsapiUtility_normalizePath( placeholder_str, MAX_PATH, placeholder.c_str())); WORKSPACE_DIR.append(placeholder_str); #elif defined RTI_QNX #include /* * Because we must use the NFS until we have local memory in place, * we need the full path of the current directory. To get it, we * use the getcwd function. The max_path_size is set to 1024, as * the QNX documentation says: www.qnx.com/developers/docs/7.0.0/#com.qnx.doc.neutrino.user_guide/topic/limits_NFS2.html * The tmpl_size variable sets the maximum size of the temporary * directory string. Given that the template of the path requires * 37 characters, we must save 38 characters from the max size * to append the temporary directory. */ const int tmplt_size = 38; int max_path_size = 1024; char tmpdir[] = "/recorder_tmp/"; char tmplt[] = "Recorder4Tester_XXXXXXX"; char current_dir[max_path_size-tmplt_size]; char tmplt_full_path[max_path_size]=""; char *tmp_dirname; getcwd(current_dir,max_path_size-tmplt_size); RTI_TEST_ASSERT(current_dir != NULL); strcat(tmplt_full_path,current_dir); strcat(tmplt_full_path,tmpdir); mkdir(tmplt_full_path, S_IRWXU | S_IRWXG | S_IRWXO); RTI_TEST_ASSERT(tmplt_full_path != NULL); strcat(tmplt_full_path,tmplt); tmp_dirname = mkdtemp(tmplt_full_path); RTI_TEST_ASSERT(tmp_dirname != NULL); WORKSPACE_DIR.append(tmp_dirname); #else #include char tmplt[] = "/tmp/Recorder4Tester_XXXXXX"; char *tmp_dirname = mkdtemp(tmplt); RTI_TEST_ASSERT(tmp_dirname != NULL); WORKSPACE_DIR.append(tmp_dirname); #endif } return WORKSPACE_DIR; } static const std::string& resource_path_var_name() { static const std::string RESOURCE_PATH_VAR_NAME( "RTI_RECORDING_RESOURCE_DIR"); return RESOURCE_PATH_VAR_NAME; } static const std::string& working_dir_var_name() { static const std::string WORKING_DIR_VAR_NAME( "RTI_RECORDING_WORKING_DIR"); return WORKING_DIR_VAR_NAME; } static const std::string& exec_dir_var_name() { static const std::string EXEC_DIR_VAR_NAME("RTI_RECORDING_EXEC_DIR"); return EXEC_DIR_VAR_NAME; } static const std::string& file_expr_var_name() { static const std::string FILE_EXPR_VAR_NAME("RTI_RECORDING_FILE_EXPR"); return FILE_EXPR_VAR_NAME; } static const std::string& monitoring_enabled_var_name() { static const std::string MONITORING_ENABLED_VAR_NAME( "RTI_RECORDING_MONITORING"); return MONITORING_ENABLED_VAR_NAME; } static const std::string& monitoring_domain_id_var_name() { static const std::string MONITORING_DOMAIN_ID_VAR_NAME( "RTI_RECORDING_MONITORING_DOMAIN_ID"); return MONITORING_DOMAIN_ID_VAR_NAME; } static const std::string& rollover_enabled_var_name() { static const std::string ROLLOVER_VAR_NAME( "RTI_RECORDING_ROLLOVER_ENABLED"); return ROLLOVER_VAR_NAME; } static const std::string& storage_format_var_name() { static const std::string STORAGE_FORMAT_VAR_NAME( "RTI_RECORDING_STORAGE_FORMAT"); return STORAGE_FORMAT_VAR_NAME; } static const std::string& out_storage_format_var_name() { static const std::string OUT_STORAGE_FORMAT_VAR_NAME( "RTI_RECORDING_OUT_STORAGE_FORMAT"); return OUT_STORAGE_FORMAT_VAR_NAME; } static const std::string& cdr_representation_var_name() { static const std::string CDR_REPRESENTATION_VAR_NAME( "RTI_RECORDING_CDR_REPRESENTATION"); return CDR_REPRESENTATION_VAR_NAME; } static const std::string& overwrite_policy_var_name() { static const std::string OVERWRITE_POLICY_VAR_NAME( "RTI_RECORDING_OVERWRITE_POLICY"); return OVERWRITE_POLICY_VAR_NAME; } static const std::string& instance_indexing_kind_var_name() { static const std::string INSTANCE_INDEXING_VAR_NAME( "RTI_RECORDING_INSTANCE_INDEX_TIMESTAMP_KIND"); return INSTANCE_INDEXING_VAR_NAME; } static const std::string& instance_indexing_enabled_var_name() { static const std::string INSTANCE_INDEXING_ENABLED_VAR_NAME( "RTI_RECORDING_INSTANCE_INDEX_ENABLED"); return INSTANCE_INDEXING_ENABLED_VAR_NAME; } static const std::string& range_start_sec_var_name() { static const std::string RANGE_START_SEC_VAR_NAME( "RTI_RECORDING_RANGE_START_SEC"); return RANGE_START_SEC_VAR_NAME; } static const std::string& range_start_nanosec_var_name() { static const std::string RANGE_START_NANOSEC_VAR_NAME( "RTI_RECORDING_RANGE_START_NANOSEC"); return RANGE_START_NANOSEC_VAR_NAME; } static const std::string& range_end_sec_var_name() { static const std::string RANGE_END_SEC_VAR_NAME( "RTI_RECORDING_RANGE_END_SEC"); return RANGE_END_SEC_VAR_NAME; } static const std::string& range_end_nanosec_var_name() { static const std::string RANGE_END_NANOSEC_VAR_NAME( "RTI_RECORDING_RANGE_END_NANOSEC"); return RANGE_END_NANOSEC_VAR_NAME; } static const std::string& playback_sample_order_var_name() { static const std::string PLAYBACK_SAMPLE_ORDER( "RTI_RECORDING_PLAYBACK_SAMPLE_ORDER"); return PLAYBACK_SAMPLE_ORDER; } static const std::string& playback_slice_period_sec_var_name() { static const std::string PLAYBACK_SLICE_PERIOD_SEC( "RTI_RECORDING_PLAYBACK_SLICE_PERIOD_SEC"); return PLAYBACK_SLICE_PERIOD_SEC; } static const std::string& playback_slice_period_nanosec_var_name() { static const std::string PLAYBACK_SLICE_PERIOD_NANOSEC( "RTI_RECORDING_PLAYBACK_SLICE_PERIOD_NANOSEC"); return PLAYBACK_SLICE_PERIOD_NANOSEC; } static const std::string& looping_enabled_var_name() { static const std::string LOOPING("RTI_RECORDING_LOOPING"); return LOOPING; } static const std::string& debug_mode_enabled_var_name() { static const std::string DEBUG_MODE("RTI_RECORDING_DEBUG_MODE"); return DEBUG_MODE; } static const std::string& allow_topic_filter_var_name() { static const std::string ALLOW_TOPIC_FILTER_VAR_NAME( "RTI_RECORDING_ALLOW_TOPIC_FILTER"); return ALLOW_TOPIC_FILTER_VAR_NAME; } static const std::string& deny_topic_filter_var_name() { static const std::string DENY_TOPIC_FILTER_VAR_NAME( "RTI_RECORDING_DENY_TOPIC_FILTER"); return DENY_TOPIC_FILTER_VAR_NAME; } static const std::string& input_sql_init_var_name() { static const std::string INPUT_SQL_INIT_VAR_NAME( "RTI_RECORDING_INPUT_SQL_INIT"); return INPUT_SQL_INIT_VAR_NAME; } static const std::string& output_sql_init_var_name() { static const std::string OUTPUT_SQL_INIT_VAR_NAME( "RTI_RECORDING_OUTPUT_SQL_INIT"); return OUTPUT_SQL_INIT_VAR_NAME; } /** * Copy a file into the current test workspace directory. * * @param origin Path to the original file to be copied. Can be relative or * absolute. * @param destination_relative The destination file, relative to the current * test workspace directory. */ static void copy_file( const std::string& origin, const std::string& destination_relative) { rti::apputils::util::Path origin_path(origin); rti::apputils::util::Path destination_abs_path(get()); destination_abs_path += destination_relative; origin_path.copy_to(destination_abs_path); } /** * Copy a file into the current working directory. * * @param origin Path to the origin file to be copied. * @param destination_filename The name of the origin file in the current * working directory after being copied. */ #if !defined(RTI_INTY) static rti::apputils::util::Path copy_file_to_cwd( const std::string& origin, const std::string& destination_filename) { rti::apputils::util::Path destination( rti::apputils::util::Path::cwd()); destination += destination_filename; rti::apputils::util::Path origin_path(origin); origin_path.copy_to(destination); return destination; } #endif static std::string go_to() { #ifdef RTI_WIN32 char current_wd[MAX_PATH] = ""; RTI_TEST_ASSERT(GetCurrentDirectory(MAX_PATH, current_wd)); RTI_TEST_ASSERT(SetCurrentDirectory(get().c_str())); #else char current_wd[PATH_MAX] = ""; RTI_TEST_ASSERT(getcwd(current_wd, sizeof(current_wd)) != NULL); RTI_TEST_ASSERT(chdir(get().c_str()) == 0); #endif return std::string(current_wd); } static void come_back(const std::string& current_working_dir) { #ifdef RTI_WIN32 RTI_TEST_ASSERT(SetCurrentDirectory(current_working_dir.c_str())); #else RTI_TEST_ASSERT(chdir(current_working_dir.c_str()) == 0); #endif } void remove_dir(bool remove) { remove_dir_ = remove; } private: bool remove_dir_; /** * Initialize the environment variables needed in the XML files we load. * They are necessary even if we're not using them directly in the * configuration we're loading but are defined in the file. * Note for Windows platforms: internally, the OSAPI function to set an * environment variable uses putenv(). On Windows, this function will delete * the environment variable if an empty string is passed as its value (the * right side of the = expression). Thus, we cannot use an * empty string for environment variables that are to be 'empty'. We're * using a whitespace character instead. */ TestWorkspaceProvider(RTITestSetting& setting) : remove_dir_(false) { /* Set resource path environment variable */ char resource_path_c_str[RTI_TEST_MAX_FILE_NAME_LENGTH + 1] = ""; RTI_TEST_ASSERT(RTITestSetting_getFullResourcePathFileName( &setting, resource_path_c_str, "")); rti::apputils::util::Path resource_path(resource_path_c_str); RTITestLog_freeForm( RTI_LOG_BIT_EXCEPTION, "Working directory for the tests: %s\n", get().c_str()); /* Copy the types XML file to the working directory */ rti::apputils::util::Path types_file_path(resource_path); types_file_path += "xml/" TEST_TYPES_FILE_NAME; RTI_TEST_ASSERT(types_file_path.exists()); copy_file(types_file_path, TEST_TYPES_FILE_NAME); } ~TestWorkspaceProvider() { if (remove_dir_) { rti::apputils::util::Path::remove_directory( TestWorkspaceProvider::get()); } } }; /** * This class is used to change the current working directory to the test * workspace. It ensures that when it gets out of scope, the app returns to the * previous working directory. */ class TestDirectoryChangeGuard { public: TestDirectoryChangeGuard() { cwd_ = TestWorkspaceProvider::go_to(); } ~TestDirectoryChangeGuard() { TestWorkspaceProvider::come_back(cwd_); } private: std::string cwd_; }; /** * Utility class designed to make sure that we remove test directory after a test is * executed. This substitutes the old pattern using explicit calls to * RTIOsapiFile_remove() at the end of the test, that are not reached when the * test exits abnormally (fails) */ class TestScopedDirectory { public: TestScopedDirectory() { } void set(const std::string& directory) { directory_ = directory; } ~TestScopedDirectory() { if (directory_.length() != 0) { RTIOsapiUtility_deleteDirectory(directory_.c_str()); } } private: std::string directory_; }; /** * A utility function to be used with shared pointers that hold pointers to * arrays */ template struct ArrayDeleter { void operator ()(T *array) { delete[] array; } }; #define DEFAULT_TEST_TIMEOUT_SEC 60 // Common setup for many tests: participant, topic, reader, writer // Uses QoS from AbstractDomainTester, but does not inherit, since the // AbstractDomainTester doesn't provide virtual methods other than the // destructor. Package class TestSystem { public: typedef std::pair > > Participant; TestSystem() { } // Uses the DomainParticipantQoS from AbstractDomainTester as a starting // point (so we can pick up changes if that changes), and then adds // additional QoS to test types with larger sized type code / type objects static const dds::domain::qos::DomainParticipantQos& test_qos( bool use_domain_tag = true) { using namespace dds::core; /* * [Juanlu]: we were originally providing no value for the boolean flag * in the rti::test::AbstractDomainTester::test_qos() method below. * This was causing us to use the low-memory generic built-in profile. * The loading of a generic QoS profile causes our participants to lack * the system info properties ('dds.sys_info.process_id', etc). * When our extremely isolated participants detect the absence of this * property in the participant properties, they ignore it. This was * causing our tests to fail. */ static dds::domain::qos::DomainParticipantQos qos( rti::test::AbstractDomainTester::test_qos(false)); // override initial peers StringSeq initial_peers_list; initial_peers_list.push_back("8@localhost"); initial_peers_list.push_back("8@udpv4://127.0.0.1"); qos->discovery.initial_peers(initial_peers_list); StringSeq multicast_receive_addr_list; qos->discovery.multicast_receive_addresses(multicast_receive_addr_list); qos->discovery.accept_unknown_peers(true); // Use UDPv4 only qos->transport_builtin.mask( rti::core::policy::TransportBuiltinMask::udpv4()); if (use_domain_tag) { // Use our Domain Tag to ensure isolation of the tests rti::core::policy::Property value; value.set(rti::core::policy::Property::Entry( domain_tag_property(), domain_tag_value())); qos << value; } return qos; } // Returmns default test writer qos static const dds::pub::qos::DataWriterQos writer_qos() { using namespace dds::pub; using namespace dds::core::policy; using namespace rti::core::policy; dds::pub::qos::DataWriterQos qos; make_transient_local(qos); return qos; } // Returns default test reader qos static const dds::sub::qos::DataReaderQos reader_qos() { using namespace dds::sub; using namespace dds::core::policy; using namespace rti::core::policy; dds::sub::qos::DataReaderQos qos; make_transient_local(qos); // set unbounded support rti::core::policy::Property value; value.set(rti::core::policy::Property::Entry( reader_buffer_pool_property(), buffer_pool_max_size())); qos << value; qos << DataReaderResourceLimits().dynamically_allocate_fragmented_samples( true); return qos; } static dds::sub::qos::DataReaderQos& make_xcdr2( dds::sub::qos::DataReaderQos& qos) { using namespace dds::core::policy; DataRepresentationIdSeq data_vector; data_vector.push_back(DataRepresentation::xcdr2()); qos.policy().value( data_vector.begin(), data_vector.end()); return qos; } static dds::pub::qos::DataWriterQos& make_xcdr2( dds::pub::qos::DataWriterQos& qos) { using namespace dds::core::policy; DataRepresentationIdSeq data_vector; data_vector.push_back(DataRepresentation::xcdr2()); qos.policy().value( data_vector.begin(), data_vector.end()); return qos; } // Create a DomainParticipant with QoS specific to Recording Service static std::pair > > create_participant( int32_t domain_id, const dds::domain::qos::DomainParticipantQos& qos = test_qos()) { dds::domain::qos::DomainParticipantFactoryQos factory_qos; factory_qos << dds::core::policy::EntityFactory::ManuallyEnable(); dds::domain::DomainParticipant::participant_factory_qos(factory_qos); dds::domain::DomainParticipant participant = dds::domain::DomainParticipant(domain_id, qos); dds::sub::Subscriber builtin_subscriber = dds::sub::builtin_subscriber( participant); // Access the built-in DataReader for ParticipantBuiltinTopicData std::vector > participant_reader; dds::sub::find< dds::sub::DataReader >( builtin_subscriber, dds::topic::participant_topic_name(), std::back_inserter(participant_reader)); rti::core::ListenerBinder< dds::sub::DataReader > participant_listener = rti::core::bind_and_manage_listener( participant_reader[0], new ParticipantBuiltinTopicListener, dds::core::status::StatusMask::data_available()); participant.enable(); factory_qos << dds::core::policy::EntityFactory::AutoEnable(); dds::domain::DomainParticipant::participant_factory_qos(factory_qos); return std::make_pair(participant, participant_listener); } // Look up whether a Topic exists. If yes, return it. If not, create it template static dds::topic::Topic create_topic( dds::domain::DomainParticipant& participant, const std::string& topic_name = "Default") { dds::topic::Topic topic = dds::topic::find >( participant, topic_name); if (topic != dds::core::null) { return topic; } return dds::topic::Topic(participant, topic_name); } template static dds::topic::Topic create_topic( dds::domain::DomainParticipant& participant, const std::string& topic_name, const std::string& type_name) { dds::topic::Topic topic = dds::topic::find >( participant, topic_name); if (topic != dds::core::null) { return topic; } return dds::topic::Topic(participant, topic_name, type_name); } // Create a DataReader with transient local QoS template static dds::sub::DataReader create_transient_local_reader( dds::domain::DomainParticipant& participant, dds::topic::Topic& topic, const bool unbounded = true, const bool use_xcdr_2 = false) { using namespace dds::sub; using namespace dds::core::policy; using namespace rti::core::policy; qos::DataReaderQos qos; make_transient_local(qos); if (unbounded) { rti::core::policy::Property value; value.set(rti::core::policy::Property::Entry( reader_buffer_pool_property(), buffer_pool_max_size())); qos << value; qos << DataReaderResourceLimits().dynamically_allocate_fragmented_samples( true); } if (use_xcdr_2) { make_xcdr2(qos); } return dds::sub::DataReader(Subscriber(participant), topic, qos); } // Create a DataReader with transient local QoS template static dds::sub::DataReader create_transient_local_reader( dds::sub::Subscriber& subscriber, dds::topic::Topic& topic, const bool unbounded = true, const bool use_xcdr_2 = false) { using namespace dds::sub; using namespace dds::core::policy; using namespace rti::core::policy; qos::DataReaderQos qos; make_transient_local(qos); if (unbounded) { rti::core::policy::Property value; value.set(rti::core::policy::Property::Entry( reader_buffer_pool_property(), buffer_pool_max_size())); qos << value; qos << DataReaderResourceLimits().dynamically_allocate_fragmented_samples( true); } if (use_xcdr_2) { make_xcdr2(qos); } return dds::sub::DataReader(subscriber, topic, qos); } // Create a DataReader with transient local QoS template static dds::sub::DataReader create_transient_local_reader( dds::domain::DomainParticipant& participant, dds::topic::Topic& topic, const dds::sub::qos::DataReaderQos& suggested_qos, const bool unbounded = true, const bool use_xcdr_2 = false) { using namespace dds::sub; using namespace dds::core::policy; using namespace rti::core::policy; qos::DataReaderQos qos(suggested_qos); make_transient_local(qos); if (unbounded) { rti::core::policy::Property value; value.set(rti::core::policy::Property::Entry( reader_buffer_pool_property(), buffer_pool_max_size())); qos << value; qos << DataReaderResourceLimits().dynamically_allocate_fragmented_samples( true); } if (use_xcdr_2) { make_xcdr2(qos); } return dds::sub::DataReader(Subscriber(participant), topic, qos); } // Create a DataReader with specified qos template static dds::sub::DataReader create_reader( dds::domain::DomainParticipant& participant, dds::topic::Topic& topic, dds::sub::qos::DataReaderQos& qos, const std::string& filter_expression = "", const bool use_xcdr_2 = false) { using namespace dds::sub; if (use_xcdr_2) { make_xcdr2(qos); } dds::topic::TopicDescription topic_desc = topic; if (filter_expression.length() != 0) { // Create the ContentFilteredTopic dds::topic::ContentFilteredTopic cft = dds::topic::ContentFilteredTopic( topic, // related topic topic.name() + "CFT" + filter_expression, dds::topic::Filter(filter_expression)); return dds::sub::DataReader(Subscriber(participant), cft, qos); } else { return dds::sub::DataReader(Subscriber(participant), topic, qos); } } // Create a reliable DataReader with Recording-Service friendly buffer pool size template static dds::sub::DataReader create_reader( dds::domain::DomainParticipant& participant, dds::topic::Topic topic, const bool unbounded = true, const std::string& filter_expression = "", const bool use_xcdr_2 = false) { using namespace dds::sub; using namespace dds::core::policy; using namespace rti::core::policy; qos::DataReaderQos qos; if (unbounded) { rti::core::policy::Property value; value.set(rti::core::policy::Property::Entry( reader_buffer_pool_property(), buffer_pool_max_size())); qos << value; qos << DataReaderResourceLimits().dynamically_allocate_fragmented_samples( true); } if (use_xcdr_2) { make_xcdr2(qos); } return create_reader(participant, topic, qos, filter_expression); } // Create a DataWriter with suggested QoS and make it transient local template static dds::pub::DataWriter create_transient_local_writer( dds::domain::DomainParticipant& participant, dds::topic::Topic& topic, const dds::pub::qos::DataWriterQos& suggested_qos, const bool use_xcdr_2 = false) { using namespace dds::pub; qos::DataWriterQos adjusted_qos(suggested_qos); make_transient_local(adjusted_qos); if (use_xcdr_2) { make_xcdr2(adjusted_qos); } return dds::pub::DataWriter( Publisher(participant), topic, adjusted_qos); } // Create a DataWriter with suggested QoS and make it transient local, // also including an already existing publisher template static dds::pub::DataWriter create_transient_local_writer( dds::pub::Publisher& publisher, dds::topic::Topic& topic, dds::pub::qos::DataWriterQos& suggested_qos, const bool use_xcdr_2 = false) { using namespace dds::pub; qos::DataWriterQos adjusted_qos(std::move(suggested_qos)); make_transient_local(adjusted_qos); if (use_xcdr_2) { make_xcdr2(adjusted_qos); } return dds::pub::DataWriter( publisher, topic, adjusted_qos); } // Create a DataWriter with transient local QoS template static dds::pub::DataWriter create_transient_local_writer( dds::domain::DomainParticipant& participant, dds::topic::Topic topic, const bool use_xcdr_2 = false) { dds::pub::qos::DataWriterQos qos; return create_transient_local_writer( participant, topic, qos, use_xcdr_2); } // Create a DataWriter with transient local QoS and external Publisher template static dds::pub::DataWriter create_transient_local_writer( dds::pub::Publisher& publisher, dds::topic::Topic topic, const bool use_xcdr_2 = false) { dds::pub::qos::DataWriterQos qos; return create_transient_local_writer(publisher, topic, qos, use_xcdr_2); } // Create a DataWriter with transient local QoS template static dds::pub::DataWriter create_writer( dds::domain::DomainParticipant& participant, dds::topic::Topic& topic, const bool use_xcdr_2 = false) { using namespace dds::pub; qos::DataWriterQos qos; if (use_xcdr_2) { make_xcdr2(qos); } return dds::pub::DataWriter(Publisher(participant), topic, qos); } template static bool wait_for_readers( dds::pub::DataWriter& writer, int32_t num_readers, int32_t timeout_sec = DEFAULT_TEST_TIMEOUT_SEC) { try { RTI_TEST_WAIT_FOR_EQUALS_INT( num_readers, writer.publication_matched_status().current_count(), timeout_sec); } catch (...) { return false; } return true; } template static bool wait_for_writers( dds::sub::DataReader& reader, int32_t num_writers, int32_t timeout_sec = DEFAULT_TEST_TIMEOUT_SEC) { try { RTI_TEST_WAIT_FOR_EQUALS_INT( num_writers, reader.subscription_matched_status().current_count(), timeout_sec); } catch (...) { return false; } return true; } template static bool check_num_writers_invariant( dds::sub::DataReader& reader, int32_t num_writers, int32_t for_seconds = DEFAULT_TEST_TIMEOUT_SEC) { const struct RTINtpTime sleepTime = {0,429496730}; int sleeps = 0; int32_t writer_current_count = reader.subscription_matched_status().current_count(); bool result = (num_writers == writer_current_count); while(result && sleeps < (static_cast(for_seconds)) * 10) { RTIOsapiThread_sleep(&sleepTime); ++sleeps; writer_current_count = reader.subscription_matched_status().current_count(); result = (num_writers == writer_current_count); } if (!result) { print_assert_equals_failure(num_writers, writer_current_count); } return result; } template static bool wait_for_received_sample_state( dds::sub::DataReader& reader, int32_t received_total_count, int32_t cache_sample_count, int32_t timeout_sec = DEFAULT_TEST_TIMEOUT_SEC) { try { RTI_TEST_WAIT_FOR_EQUALS_INT( received_total_count, reader->datareader_protocol_status().received_sample_count().total(), timeout_sec); RTI_TEST_WAIT_FOR_EQUALS_INT( cache_sample_count, reader->datareader_cache_status().sample_count(), timeout_sec); } catch (...) { return false; } return true; } template static bool wait_for_read_count( dds::sub::DataReader& reader, int32_t read_count, int32_t timeout_sec = DEFAULT_TEST_TIMEOUT_SEC) { try { RTI_TEST_WAIT_FOR_EQUALS_INT( read_count, (int32_t) reader.read().length(), timeout_sec); } catch (...) { return false; } return true; } template static bool wait_for_read_count( typename dds::sub::DataReader::Selector selector, int32_t read_count, int32_t timeout_sec = DEFAULT_TEST_TIMEOUT_SEC) { try { RTI_TEST_WAIT_FOR_EQUALS_INT( read_count, (int32_t) selector.read().length(), timeout_sec); } catch (...) { return false; } return true; } template static void wait_for_dispose( typename dds::sub::DataReader reader, const dds::core::InstanceHandle& handle, int32_t timeout_sec = DEFAULT_TEST_TIMEOUT_SEC) { RTI_TEST_WAIT_FOR( reader.select() .state(dds::sub::status::InstanceState::not_alive_mask()) .instance(handle).read().length() > 0, timeout_sec); } static const std::string domain_tag_property() { return PRES_PARTICIPANT_DOMAIN_TAG_PROPERTY_NAME; } static const std::string domain_tag_value() { return "rti.recording.test.DOMAIN_TAG"; } private: static dds::sub::qos::DataReaderQos& make_transient_local( dds::sub::qos::DataReaderQos& qos) { using namespace dds::core::policy; qos.policy().kind(DurabilityKind::TRANSIENT_LOCAL); qos.policy().kind(ReliabilityKind::RELIABLE); qos.policy().kind(HistoryKind::KEEP_ALL); return qos; } static dds::pub::qos::DataWriterQos& make_transient_local( dds::pub::qos::DataWriterQos& qos) { using namespace dds::core::policy; qos.policy().kind(DurabilityKind::TRANSIENT_LOCAL); qos.policy().kind(ReliabilityKind::RELIABLE); qos.policy().kind(HistoryKind::KEEP_ALL); qos << WriterDataLifecycle::ManuallyDisposeUnregisteredInstances(); return qos; } // Note: The following constants are encapsulated in static methods rather // than private constant static members to avoid creating a .cxx file just // to initialize static constant variables. static const std::string buffer_pool_max_size() { return "16384"; } static const std::string reader_buffer_pool_property() { return PROPERTY_NAME_DDS_DATA_READER_HISTORY_MEMORY_MANAGER_FAST_POOL_POOL_BUFFER_MAX_SIZE; } }; } } } /* namespace rti::apputils::test */ #endif /* HPP_APPUTILS_FRAMEWORK_HPP_ */ // -------------------------------------------------------------------- /* End $Id$ */