diff --git a/src/core/ddsc/tests/CMakeLists.txt b/src/core/ddsc/tests/CMakeLists.txt index 7f246cb03c..81e7fc02d9 100644 --- a/src/core/ddsc/tests/CMakeLists.txt +++ b/src/core/ddsc/tests/CMakeLists.txt @@ -242,6 +242,39 @@ target_include_directories( target_link_libraries(oneliner PRIVATE RoundTrip Space ddsc) +# PSMX dummy implementation for interface testing +set( + psmx_dummy_sources + "psmx_dummy_impl.c" + "psmx_dummy_impl.h" +) +if(BUILD_SHARED_LIBS) + add_library(psmx_dummy SHARED ${psmx_dummy_sources}) +else() + add_library(psmx_dummy OBJECT ${psmx_dummy_sources}) + set_property(GLOBAL APPEND PROPERTY cdds_plugin_list psmx_dummy) + set_property(GLOBAL PROPERTY psmx_dummy_symbols dummy_create_psmx) +endif() +generate_export_header(psmx_dummy BASE_NAME PSMX_DUMMY EXPORT_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/include/dds/psmx_dummy/export.h") +target_include_directories( + psmx_dummy PRIVATE + "$" + "$" + "$" + "$" + "$" + "$" + "$" +) +if(BUILD_SHARED_LIBS) + target_link_libraries(psmx_dummy PRIVATE ddsc) +endif() +install( + TARGETS psmx_dummy + EXPORT "${PROJECT_NAME}" + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) # PSMX implementation with Cyclone as transport, for testing if (BUILD_SHARED_LIBS) diff --git a/src/core/ddsc/tests/psmx.c b/src/core/ddsc/tests/psmx.c index 2926407a7b..81f93906e0 100644 --- a/src/core/ddsc/tests/psmx.c +++ b/src/core/ddsc/tests/psmx.c @@ -30,6 +30,7 @@ #include "config_env.h" #include "test_common.h" +#include "psmx_dummy_public.h" #include "Array100.h" #include "DynamicData.h" #include "PsmxDataModels.h" @@ -1031,6 +1032,80 @@ static bool eq_DynamicData_KMsg (const void *vsent, const void *vrecvd, bool val } } +/** @brief Compare two sets of stats. + * + * @param[in] mockstats1 first stats to compare + * @param[in] mockstats2 second stats to compare + * + * @return A bitmask to indicate which elements are different, zero if all are equal. +*/ +static uint32_t dummy_mockstats_cmp(const dummy_mockstats_t* mockstats1, const dummy_mockstats_t* mockstats2) +{ + uint32_t cmp = 0; + uint32_t shift = 0; + + cmp |= ((uint32_t)(mockstats1->cnt_type_qos_supported != mockstats2->cnt_type_qos_supported) << shift++); + cmp |= ((uint32_t)(mockstats1->cnt_create_topic != mockstats2->cnt_create_topic) << shift++); + cmp |= ((uint32_t)(mockstats1->cnt_delete_topic != mockstats2->cnt_delete_topic) << shift++); + cmp |= ((uint32_t)(mockstats1->cnt_deinit != mockstats2->cnt_deinit) << shift++); + cmp |= ((uint32_t)(mockstats1->cnt_get_node_id != mockstats2->cnt_get_node_id) << shift++); + cmp |= ((uint32_t)(mockstats1->cnt_supported_features != mockstats2->cnt_supported_features) << shift++); + cmp |= ((uint32_t)(mockstats1->cnt_create_endpoint != mockstats2->cnt_create_endpoint) << shift++); + cmp |= ((uint32_t)(mockstats1->cnt_delete_endpoint != mockstats2->cnt_delete_endpoint) << shift++); + cmp |= ((uint32_t)(mockstats1->cnt_request_loan != mockstats2->cnt_request_loan) << shift++); + cmp |= ((uint32_t)(mockstats1->cnt_write != mockstats2->cnt_write) << shift++); + cmp |= ((uint32_t)(mockstats1->cnt_take != mockstats2->cnt_take) << shift++); + cmp |= ((uint32_t)(mockstats1->cnt_on_data_available != mockstats2->cnt_on_data_available) << shift++); + return cmp; +} + +/** @brief Convert a set of stats to a string. + * + * Truncates the output string if the string buffer capacity is too small. + * + * @param[in] dmock stats to convert + * @param[out] str_out string buffer to write the string into + * @param[in] str_capacity number of bytes the string buffer can hold + * + * @return Upon successful return, it returns the number of characters printed + * (excluding the null byte used to end output to strings). + * If an output error is encountered, a negative value is returned. +*/ +static int dummy_mockstats_tostring(const dummy_mockstats_t* dmock, char* str_out, size_t str_capacity) +{ + return snprintf( + str_out, + str_capacity, + "\ + type_qos_supported: %i\n\ + create_topic: %i\n\ + delete_topic: %i\n\ + deinit: %i\n\ + get_node_id: %i\n\ + supported_features: %i\n\ + \n\ + create_endpoint: %i\n\ + delete_endpoint: %i\n\ + \n\ + request_loan: %i\n\ + write: %i\n\ + take: %i\n\ + on_data_available: %i\n", + dmock->cnt_type_qos_supported, + dmock->cnt_create_topic, + dmock->cnt_delete_topic, + dmock->cnt_deinit, + dmock->cnt_get_node_id, + dmock->cnt_supported_features, + dmock->cnt_create_endpoint, + dmock->cnt_delete_endpoint, + dmock->cnt_request_loan, + dmock->cnt_write, + dmock->cnt_take, + dmock->cnt_on_data_available + ); +} + CU_Test(ddsc_psmx, one_writer, .timeout = 240) { failed = false; @@ -1331,6 +1406,9 @@ CU_Test (ddsc_psmx, basic) /// - Create some entities /// - Check if shared memory is enabled. /// - Expectation: Shared memory is enabled iff the psmx interface supports it (use queried value). +/// - Delete the domain +/// - Check the function call counts of the dummy psmx. +/// - Expectation: The counts match expectations. In particular, create counts must match their delete counterpart. /// /// - Create a configuration with a psmx interface capable of shared memory and don't specify a locator. /// - Create a domain using this configuration. @@ -1340,12 +1418,19 @@ CU_Test (ddsc_psmx, basic) /// - Create some entities /// - Check if shared memory is enabled. /// - Expectation: Shared memory is enabled iff the psmx interface supports it (use queried value). +/// - Delete the domain +/// - Check the function call counts of the dummy psmx. +/// - Expectation: The counts match expectations. In particular, create counts must match their delete counterpart. /// -/// - The above two cases (with and without locator) are repeated for a -/// psmx interface that supports shared memory, and a psmx interface that doesn't. -/// -CU_Test(ddsc_psmx, shared_memory) +CU_Test(ddsc_psmxif, shared_memory) { + const char* CDDS_PSMX_NAME = NULL; + ddsrt_getenv("CDDS_PSMX_NAME", &CDDS_PSMX_NAME); + ddsrt_setenv("CDDS_PSMX_NAME", "dummy"); // Make `create_participant()` use the dummy psmx. + dummy_mockstats_t dmock, dmock_expected; + char strbuf1[512]; + char strbuf2[512]; + size_t strbuf_size = sizeof(strbuf1); { // Check that the data types I'm planning to use are actually suitable for use with shared memory. dds_data_type_properties_t props; @@ -1383,10 +1468,12 @@ CU_Test(ddsc_psmx, shared_memory) dds_domain* dom = NULL; CU_ASSERT_FATAL(domain_pin(domain, &dom)); CU_ASSERT_FATAL(dom->psmx_instances.length == 1); // There shall be exactly one psmx instance. - dds_psmx_t* psmx_ptr = dom->psmx_instances.instances[0]; + struct dummy_psmx* dpsmx = (struct dummy_psmx*)dom->psmx_instances.instances[0]; + dpsmx->mockstats_get_ownership(&dmock); + // The psmx must have an instance_name that is not an empty string. - CU_ASSERT_FATAL(psmx_ptr->instance_name != NULL && strcmp(psmx_ptr->instance_name, "") != 0); - supports_shared_memory = ((psmx_ptr->ops.supported_features(psmx_ptr) & DDS_PSMX_FEATURE_SHARED_MEMORY) == DDS_PSMX_FEATURE_SHARED_MEMORY); + CU_ASSERT_FATAL(dpsmx->c.instance_name != NULL && strcmp(dpsmx->c.instance_name, "") != 0); + supports_shared_memory = ((dpsmx->c.ops.supported_features(&dpsmx->c) & DDS_PSMX_FEATURE_SHARED_MEMORY) == DDS_PSMX_FEATURE_SHARED_MEMORY); domain_unpin(dom); } dds_entity_t writer1, reader1, writer2, reader2; @@ -1415,6 +1502,7 @@ CU_Test(ddsc_psmx, shared_memory) CU_ASSERT_FATAL(reader2 > 0); dds_delete_qos(qos); } + { // Check that shared memory is enabled when it should, and not enabled when it shouldn't. bool psmx_enabled; @@ -1435,6 +1523,29 @@ CU_Test(ddsc_psmx, shared_memory) CU_ASSERT_FATAL(dds_is_shared_memory_available(reader2) == supports_shared_memory); } dds_delete(domain); + + // Check number of calls against expected counts. + memset(&dmock_expected, 0, sizeof(dummy_mockstats_t)); + dmock_expected.cnt_type_qos_supported = 10; + dmock_expected.cnt_create_topic = 2; + dmock_expected.cnt_delete_topic = 2; + dmock_expected.cnt_deinit = 1; + dmock_expected.cnt_get_node_id = 1; + dmock_expected.cnt_supported_features = 5; + dmock_expected.cnt_create_endpoint = 4; + dmock_expected.cnt_delete_endpoint = 4; + dmock_expected.cnt_on_data_available = 2; // This is perhaps unexpected, see below. + /* + NOTE: The `on_data_available` is triggered in + `dds_create_reader_int()`, even if no samples have been received at all. + */ + uint32_t cmp = dummy_mockstats_cmp(&dmock, &dmock_expected); + if( cmp != 0 ){ + dummy_mockstats_tostring(&dmock, strbuf1, strbuf_size); + dummy_mockstats_tostring(&dmock_expected, strbuf2, strbuf_size); + printf("actual calls:\n%s\nexpected calls:\n%s\n", strbuf1, strbuf2); + CU_ASSERT_FATAL(cmp == 0); + } } } diff --git a/src/core/ddsc/tests/psmx_dummy_impl.c b/src/core/ddsc/tests/psmx_dummy_impl.c new file mode 100644 index 0000000000..a0c840cdd0 --- /dev/null +++ b/src/core/ddsc/tests/psmx_dummy_impl.c @@ -0,0 +1,274 @@ + +#include +#include "dds/ddsrt/heap.h" +#include "dds/ddsrt/string.h" +#include "dds/ddsrt/strtol.h" +#include "dds/ddsi/ddsi_locator.h" +#include "dds/ddsc/dds_psmx.h" +#include "psmx_dummy_public.h" +#include "psmx_dummy_impl.h" + +//### Helper functions ### + +static char* get_config_option_value (const char* conf, const char* option_name) +{ + char* copy = ddsrt_strdup(conf), *cursor = copy, *tok; + while ((tok = ddsrt_strsep(&cursor, ",/|;")) != NULL) + { + if (strlen(tok) == 0) + continue; + char* name = ddsrt_strsep(&tok, "="); + if (name == NULL || tok == NULL) + { + ddsrt_free(copy); + return NULL; + } + if (strcmp(name, option_name) == 0) + { + char* ret = ddsrt_strdup(tok); + ddsrt_free(copy); + return ret; + } + } + ddsrt_free(copy); + return NULL; +} + +//### Dynamic library functions ### + +static dummy_mockstats_t* g_mockstats; +static bool g_mockstats_owned; + +static bool dummy_psmx_type_qos_supported( + struct dds_psmx* psmx, + dds_psmx_endpoint_type_t forwhat, + dds_data_type_properties_t data_type_props, + const struct dds_qos* qos +); +static struct dds_psmx_topic* dummy_psmx_create_topic( + struct dds_psmx* psmx, + const char* topic_name, + const char* type_name, + dds_data_type_properties_t data_type_props +); +static dds_return_t dummy_psmx_delete_topic(struct dds_psmx_topic* psmx_topic); +static dds_return_t dummy_psmx_deinit(struct dds_psmx* psmx); +static dds_psmx_node_identifier_t dummy_psmx_get_node_id(const struct dds_psmx* psmx); +static dds_psmx_features_t dummy_supported_features(const struct dds_psmx* psmx); + +static struct dds_psmx_endpoint* dummy_psmx_create_endpoint( + struct dds_psmx_topic* psmx_topic, + const struct dds_qos* qos, + dds_psmx_endpoint_type_t endpoint_type +); +static dds_return_t dummy_psmx_delete_endpoint(struct dds_psmx_endpoint* psmx_endpoint); + +static dds_loaned_sample_t* dummy_psmx_ep_request_loan(struct dds_psmx_endpoint* psmx_endpoint, uint32_t size_requested); +static dds_return_t dummy_psmx_ep_write(struct dds_psmx_endpoint* psmx_endpoint, dds_loaned_sample_t* data); +static dds_loaned_sample_t* dummy_psmx_ep_take(struct dds_psmx_endpoint* psmx_endpoint); +static dds_return_t dummy_psmx_ep_on_data_available(struct dds_psmx_endpoint* psmx_endpoint, dds_entity_t reader); + +static const dds_psmx_ops_t psmx_instance_ops = { + .type_qos_supported = dummy_psmx_type_qos_supported, + .create_topic = dummy_psmx_create_topic, + .delete_topic = dummy_psmx_delete_topic, + .deinit = dummy_psmx_deinit, + .get_node_id = dummy_psmx_get_node_id, + .supported_features = dummy_supported_features +}; + +static void dummy_mockstats_get_ownership(dummy_mockstats_t* mockstats) +{ + // Transfer ownership of mockstats to user. + memcpy(mockstats, g_mockstats, sizeof(dummy_mockstats_t)); + if ( g_mockstats_owned ) { + ddsrt_free(g_mockstats); + g_mockstats_owned = false; + } + g_mockstats = mockstats; +} + +static bool dummy_psmx_type_qos_supported( + struct dds_psmx* psmx, + dds_psmx_endpoint_type_t forwhat, + dds_data_type_properties_t data_type_props, + const struct dds_qos* qos +) { + (void)psmx; + (void)forwhat; + (void)data_type_props; + (void)qos; + ++g_mockstats->cnt_type_qos_supported; + return true; +} + +static struct dds_psmx_topic* dummy_psmx_create_topic( + struct dds_psmx* psmx, + const char* topic_name, + const char* type_name, + dds_data_type_properties_t data_type_props +) { + (void)data_type_props; + struct dds_psmx_topic* topic = dds_alloc(sizeof(struct dds_psmx_topic)); + memset(topic, 0, sizeof(struct dds_psmx_topic)); + topic->ops.create_endpoint = dummy_psmx_create_endpoint; + topic->ops.delete_endpoint = dummy_psmx_delete_endpoint; + topic->psmx_instance = psmx; + topic->topic_name = ddsrt_strdup(topic_name); + topic->type_name = ddsrt_strdup(type_name); + dds_add_psmx_topic_to_list(topic, &psmx->psmx_topics); + ++g_mockstats->cnt_create_topic; + return topic; +} + +static dds_return_t dummy_psmx_delete_topic(struct dds_psmx_topic* psmx_topic) +{ + dds_psmx_topic_cleanup_generic(psmx_topic); + dds_free(psmx_topic); + ++g_mockstats->cnt_delete_topic; + return DDS_RETCODE_OK; +} + +static dds_return_t dummy_psmx_deinit(struct dds_psmx* psmx) +{ + dds_psmx_cleanup_generic(psmx); + dds_free(psmx); + ++g_mockstats->cnt_deinit; + if ( g_mockstats_owned ) { + ddsrt_free(g_mockstats); + g_mockstats = NULL; + g_mockstats_owned = false; + } + return DDS_RETCODE_OK; +} + +static dds_psmx_node_identifier_t dummy_psmx_get_node_id(const struct dds_psmx* psmx) +{ + (void)psmx; + dds_psmx_node_identifier_t node_id; + memset(&node_id, 0, sizeof(dds_psmx_node_identifier_t)); + ++g_mockstats->cnt_get_node_id; + return node_id; +} + +static dds_psmx_features_t dummy_supported_features(const struct dds_psmx* psmx) +{ + (void)psmx; + ++g_mockstats->cnt_supported_features; + return DDS_PSMX_FEATURE_SHARED_MEMORY | DDS_PSMX_FEATURE_ZERO_COPY; +} + +dds_return_t dummy_create_psmx(dds_psmx_t** psmx_out, dds_psmx_instance_id_t instance_id, const char* config) +{ + assert(psmx_out); + + g_mockstats = ddsrt_malloc(sizeof(dummy_mockstats_t)); + memset(g_mockstats, 0, sizeof(dummy_mockstats_t)); + g_mockstats_owned = true; // I own the mockstats until transferred to the user. + + struct dummy_psmx* psmx = dds_alloc(sizeof(struct dummy_psmx)); + memset(psmx, 0, sizeof(struct dummy_psmx)); + psmx->c.instance_name = dds_string_dup("dummy_psmx"); + psmx->c.instance_id = instance_id; + psmx->c.ops = psmx_instance_ops; + dds_psmx_init_generic(&psmx->c); + + if (config != NULL && strlen (config) > 0) + { + char* lstr = get_config_option_value (config, "LOCATOR"); + if (lstr != NULL) + { + if (strlen (lstr) != 32) + { + dds_free (lstr); + goto err_locator; + } + uint8_t* const dst = (uint8_t*) psmx->c.locator->address; + for (uint32_t n = 0; n < 32 && lstr[n]; n++) + { + int32_t num; + if ((num = ddsrt_todigit (lstr[n])) < 0 || num >= 16) + { + dds_free (lstr); + goto err_locator; + } + if ((n % 2) == 0) + dst[n / 2] = (uint8_t) (num << 4); + else + dst[n / 2] |= (uint8_t) num; + } + dds_free (lstr); + } + } + + psmx->mockstats_get_ownership = dummy_mockstats_get_ownership; + *psmx_out = (dds_psmx_t*)psmx; + return DDS_RETCODE_OK; + +err_locator: + dds_psmx_cleanup_generic (&psmx->c); + dds_free (psmx); + return DDS_RETCODE_BAD_PARAMETER; +} + +static struct dds_psmx_endpoint* dummy_psmx_create_endpoint( + struct dds_psmx_topic* psmx_topic, + const struct dds_qos* qos, + dds_psmx_endpoint_type_t endpoint_type +) { + (void)qos; + struct dds_psmx_endpoint* endp = dds_alloc(sizeof(struct dds_psmx_endpoint)); + memset(endp, 0, sizeof(struct dds_psmx_endpoint)); + endp->ops.request_loan = dummy_psmx_ep_request_loan; + endp->ops.write = dummy_psmx_ep_write; + endp->ops.take = dummy_psmx_ep_take; + endp->ops.on_data_available = dummy_psmx_ep_on_data_available; + + endp->psmx_topic = psmx_topic; + endp->endpoint_type = endpoint_type; + dds_add_psmx_endpoint_to_list(endp, &psmx_topic->psmx_endpoints); + ++g_mockstats->cnt_create_endpoint; + return endp; +} + +static dds_return_t dummy_psmx_delete_endpoint(struct dds_psmx_endpoint* psmx_endpoint) +{ + dds_free(psmx_endpoint); + ++g_mockstats->cnt_delete_endpoint; + return DDS_RETCODE_OK; +} + +static dds_loaned_sample_t* dummy_psmx_ep_request_loan(struct dds_psmx_endpoint* psmx_endpoint, uint32_t size_requested) +{ + (void)psmx_endpoint; + (void)size_requested; + // Details yet to be implemented + ++g_mockstats->cnt_request_loan; + return NULL; +} + +static dds_return_t dummy_psmx_ep_write(struct dds_psmx_endpoint* psmx_endpoint, dds_loaned_sample_t* data) +{ + (void)psmx_endpoint; + (void)data; + // Details yet to be implemented + ++g_mockstats->cnt_write; + return DDS_RETCODE_OK; +} + +static dds_loaned_sample_t* dummy_psmx_ep_take(struct dds_psmx_endpoint* psmx_endpoint) +{ + (void)psmx_endpoint; + // Details yet to be implemented + ++g_mockstats->cnt_take; + return NULL; +} + +static dds_return_t dummy_psmx_ep_on_data_available(struct dds_psmx_endpoint* psmx_endpoint, dds_entity_t reader) +{ + (void)psmx_endpoint; + (void)reader; + // Details yet to be implemented + ++g_mockstats->cnt_on_data_available; + return DDS_RETCODE_OK; +} diff --git a/src/core/ddsc/tests/psmx_dummy_impl.h b/src/core/ddsc/tests/psmx_dummy_impl.h new file mode 100644 index 0000000000..1e03474f4c --- /dev/null +++ b/src/core/ddsc/tests/psmx_dummy_impl.h @@ -0,0 +1,26 @@ +// Copyright(c) 2023 ZettaScale Technology and others +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +// v. 1.0 which is available at +// http://www.eclipse.org/org/documents/edl-v10.php. +// +// SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + +#ifndef PSMX_DUMMY_IMPL_H +#define PSMX_DUMMY_IMPL_H + +#include "dds/psmx_dummy/export.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +PSMX_DUMMY_EXPORT dds_return_t dummy_create_psmx(dds_psmx_t **psmx, dds_psmx_instance_id_t instance_id, const char *config); + +#if defined (__cplusplus) +} +#endif + +#endif /* PSMX_DUMMY_IMPL_H */ diff --git a/src/core/ddsc/tests/psmx_dummy_public.h b/src/core/ddsc/tests/psmx_dummy_public.h new file mode 100644 index 0000000000..336a5032ea --- /dev/null +++ b/src/core/ddsc/tests/psmx_dummy_public.h @@ -0,0 +1,48 @@ + +// Copyright(c) 2023 ZettaScale Technology and others +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +// v. 1.0 which is available at +// http://www.eclipse.org/org/documents/edl-v10.php. +// +// SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + +#ifndef PSMX_DUMMY_PUBLIC_H +#define PSMX_DUMMY_PUBLIC_H + +#if defined (__cplusplus) +extern "C" { +#endif + +typedef struct dummy_mockstats_s{ + // dds_psmx_ops + int cnt_type_qos_supported; + int cnt_create_topic; + int cnt_delete_topic; + int cnt_deinit; + int cnt_get_node_id; + int cnt_supported_features; + + // dds_psmx_topic_ops + int cnt_create_endpoint; + int cnt_delete_endpoint; + + // dds_psmx_endpoint_ops + int cnt_request_loan; + int cnt_write; + int cnt_take; + int cnt_on_data_available; +}dummy_mockstats_t; + +struct dummy_psmx { + struct dds_psmx c; + void (*mockstats_get_ownership)(dummy_mockstats_t*); +}; + +#if defined (__cplusplus) +} +#endif + +#endif /* PSMX_DUMMY_PUBLIC_H */