From 597ca2528734b682ee8c80aa4d842cfdb1d46e7d Mon Sep 17 00:00:00 2001 From: Martin Bremmer Date: Wed, 10 Apr 2019 17:57:29 +0200 Subject: [PATCH] Multi Process Testing framework Signed-off-by: Martin Bremmer --- docs/dev/mpt_req.md | 86 +++ docs/dev/multi_process_testing.md | 86 +++ docs/dev/pictures/mpt_flow.png | Bin 0 -> 74078 bytes docs/dev/pictures/mpt_tree.png | Bin 0 -> 19753 bytes src/CMakeLists.txt | 6 + src/ddsrt/include/dds/ddsrt/log.h | 2 +- src/mpt/CMakeLists.txt | 22 + src/mpt/mpt/CMakeLists.txt | 17 + src/mpt/mpt/cmake/MPT.cmake | 184 +++++ src/mpt/mpt/include/mpt/mpt.h | 158 +++++ src/mpt/mpt/include/mpt/private/mpt.h | 153 ++++ src/mpt/mpt/include/mpt/resource.h.in | 6 + src/mpt/mpt/src/main.c.in | 551 ++++++++++++++ src/mpt/tests/CMakeLists.txt | 19 + src/mpt/tests/basic/CMakeLists.txt | 23 + src/mpt/tests/basic/etc/config_any.xml | 30 + src/mpt/tests/basic/etc/config_specific.xml | 30 + src/mpt/tests/basic/helloworld.c | 63 ++ src/mpt/tests/basic/multi.c | 50 ++ src/mpt/tests/basic/procs/hello.c | 239 +++++++ src/mpt/tests/basic/procs/hello.h | 52 ++ src/mpt/tests/basic/procs/helloworlddata.idl | 9 + src/mpt/tests/self/CMakeLists.txt | 25 + src/mpt/tests/self/asserts.c | 711 +++++++++++++++++++ src/mpt/tests/self/environments.c | 96 +++ src/mpt/tests/self/etc/file | 0 src/mpt/tests/self/fixtures.c | 72 ++ src/mpt/tests/self/ipc.c | 29 + src/mpt/tests/self/resources.c | 30 + src/mpt/tests/self/usage.c | 186 +++++ 30 files changed, 2934 insertions(+), 1 deletion(-) create mode 100644 docs/dev/mpt_req.md create mode 100644 docs/dev/multi_process_testing.md create mode 100644 docs/dev/pictures/mpt_flow.png create mode 100644 docs/dev/pictures/mpt_tree.png create mode 100644 src/mpt/CMakeLists.txt create mode 100644 src/mpt/mpt/CMakeLists.txt create mode 100644 src/mpt/mpt/cmake/MPT.cmake create mode 100644 src/mpt/mpt/include/mpt/mpt.h create mode 100644 src/mpt/mpt/include/mpt/private/mpt.h create mode 100644 src/mpt/mpt/include/mpt/resource.h.in create mode 100644 src/mpt/mpt/src/main.c.in create mode 100644 src/mpt/tests/CMakeLists.txt create mode 100644 src/mpt/tests/basic/CMakeLists.txt create mode 100644 src/mpt/tests/basic/etc/config_any.xml create mode 100644 src/mpt/tests/basic/etc/config_specific.xml create mode 100644 src/mpt/tests/basic/helloworld.c create mode 100644 src/mpt/tests/basic/multi.c create mode 100644 src/mpt/tests/basic/procs/hello.c create mode 100644 src/mpt/tests/basic/procs/hello.h create mode 100644 src/mpt/tests/basic/procs/helloworlddata.idl create mode 100644 src/mpt/tests/self/CMakeLists.txt create mode 100644 src/mpt/tests/self/asserts.c create mode 100644 src/mpt/tests/self/environments.c create mode 100644 src/mpt/tests/self/etc/file create mode 100644 src/mpt/tests/self/fixtures.c create mode 100644 src/mpt/tests/self/ipc.c create mode 100644 src/mpt/tests/self/resources.c create mode 100644 src/mpt/tests/self/usage.c diff --git a/docs/dev/mpt_req.md b/docs/dev/mpt_req.md new file mode 100644 index 0000000..bcd2a90 --- /dev/null +++ b/docs/dev/mpt_req.md @@ -0,0 +1,86 @@ +# Eclipse Cyclone DDS Multi Process Testing Requirements + +This document present some requirements and considerations regarding the +[Multi Process Test Framework](multi_process_testing.md). + +## Requirements +1.1) To test certain Cyclone DDS features, multiple processes running Cyclone + DDS are needed to force communication through the whole stack. + +1.2) Should be buildable and runnable on platforms that support multiprocess + and filesystems including the ones used in the continues integration + context. + +1.3) Results should be easily analyzable within the continues integration + context and when running locally. This can be done by reporting the + results in a standard format like xunit or cunit. + +1.4) No processes must be left behind (f.i. deadlock in child process) when the + test finished (or timed out). + +1.5) When running tests parallel, they should not interfere with each other. + +1.6) Processes of the same test should be able to communicate (for settings, + syncing, etc). + +1.7) It should be possible to analyze output/messages/tracing of the parent + and child processes to be able to draw a proper test conclusion. + + +## Considerations +2.1) +The files that actually contain the tests, should be focused on those tests. +This means that the process management and setting up (and usage of) IPC +between test processes should be handled by a test framework so that the +test files can remain as clean as possible. + +2.2) +If possible, there shouldn't be a need for writing log files to a file system +when running the tests normally. It could be helpful, however, that these log +files are written when debugging related tests. + +2.3) +Preferably, the DDS communication between the processes should not leave +localhost. + + +## Intentions +There doesn't seem to be a 3rd party test framework that addresses our +requirements in a satisfactory manner. + +After some discussions with a few people (different people in different +meetings), it was decided to create our own framework and to go in the +following direction: + +- Process creation/destruction/etc is (re)introduced in the ddsrt.
+ [1.1/1.2] + +- The files that contain the tests, should be easy to understand and focus on + the tests themselves.
+ [2.1] + +- Other files (generated or in the framework) should take care of the + intricacies of starting/monitoring the proper processes with the proper + settings.
+ [1.4/1.6/2.1] + +- To do this, a similar approach of the current CUnit build will be used; + CMake will scan the test files and create runners according to macros within + the test files.
+ [2.1] + +- The tests should be executed by CTest. For now this means that a proper + runner exit code for pass/fail is enough. We would like to add CUnit like + output in the future.
+ [1.2/1.3] + +- The Cyclone DDS API contains the possibility to monitor generated log traces. + This means we won't be needing to monitor actual log files. Just register a + log callback and go from there.
+ [1.7/2.2] + +- The framework should be able to generate unique domain ids and unique topic + names when necessary. That way, tests won't interfere with each other when + running in parallel.
+ [1.5] + diff --git a/docs/dev/multi_process_testing.md b/docs/dev/multi_process_testing.md new file mode 100644 index 0000000..aa33f9d --- /dev/null +++ b/docs/dev/multi_process_testing.md @@ -0,0 +1,86 @@ +# Eclipse Cyclone DDS Multi Process Testing + +Some features and functionalities of Cyclone DDS can only be tested when +there's communication between processes. Examples are durability, security, +etc. To really make sure that these kind of features work, extended tests +with multiple processes are needed. + +This results in a number of [requirements](mpt_req.md). + +There doesn't seem to be a 3rd party test framework that addresses our +requirements in a satisfactory manner. Therefore, it was decided to create +our own Multi Process Testing (MPT) framework. + +This document will provide an overview of the MPT framework. + + +## Overview + +An MPT application is basically divided into two components +1. Tests +2. Runner + +The Tests are created by the developer. They don't need to worry about the +process management. They only have to provide process entry point(s), tests +and test processes that use these entry points. E.g. +```cpp +MPT_ProcessEntry(publisher, MPT_Args(int domain)) +{ + /* Publish a sample on the given domain. */ + MPT_ASSERT(success, "publisher failed"); +} +MPT_ProcessEntry(subscriber, MPT_Args(int domain)) +{ + /* Subscribe to a sample from the given domain. */ + MPT_ASSERT(success, "subscriber failed"); +} + +MPT_TestProcess(helloworld, domain0, pub, publisher, MPT_ArgValues(0)); +MPT_TestProcess(helloworld, domain0, sub, subscriber, MPT_ArgValues(0)); +MPT_Test(helloworld, domain0); + +MPT_TestProcess(helloworld, domain42, pub, publisher, MPT_ArgValues(42)); +MPT_TestProcess(helloworld, domain42, sub, subscriber, MPT_ArgValues(42)); +MPT_Test(helloworld, domain42); +``` + +There are more options, but see the +[usage test](../../src/mpt/tests/self/usage.c) for more elaborate examples. + +CMake will identify suites, tests and processes depending on those MPT +macros.
+It'll use that to generate part of the MPT Runner. The Runner takes care +of starting test(s) and handling the process management. + +The runner also takes care of preparing IPC between test processes so that +these processes can sync if they need to (NB, this will be a future extension). + + +#### Suite-Test-Process tree + +A process is related to a test and that test is related to a suite.
+A suite can have multiple tests and tests can have multiple processes.
+ +This results in the following tree quite naturally. + +Suite-Test-Process tree + + +#### Test execution + +There are 3 main ways to start an MPT application. +1. Without argument.
+ All tests will be run. +2. With suite and/or test name patterns as arguments.
+ A subset of tests will be run depending on the provided patterns.
+ This allows ctest to execute single tests. +3. With a specific suite/test/process combination as arguments.
+ An user will normally not use this. + +The third option is used by the MPT application itself to start a specific +test related process. It does so by restarting itself with the proper +suite/test/process combination as indicated by the test. This results +in the following flow. + +MPT application flow + diff --git a/docs/dev/pictures/mpt_flow.png b/docs/dev/pictures/mpt_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..271e74bd4b2256079fb2525f72757e9237ad5aab GIT binary patch literal 74078 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 literal 0 HcmV?d00001 diff --git a/docs/dev/pictures/mpt_tree.png b/docs/dev/pictures/mpt_tree.png new file mode 100644 index 0000000000000000000000000000000000000000..eef76442e23f6ecbc77a082060f4e3257ed2b73e GIT binary patch literal 19753 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 G$^QrWss&&G literal 0 HcmV?d00001 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cc63136..e56d494 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -186,5 +186,11 @@ endif() add_subdirectory(examples) +if (BUILD_TESTING) + # Multi Process Tests + add_subdirectory(mpt) +endif() + + # Pull-in CPack and support for generating Config.cmake and packages. include(Packaging) diff --git a/src/ddsrt/include/dds/ddsrt/log.h b/src/ddsrt/include/dds/ddsrt/log.h index 4a08a46..360f9c6 100644 --- a/src/ddsrt/include/dds/ddsrt/log.h +++ b/src/ddsrt/include/dds/ddsrt/log.h @@ -107,7 +107,7 @@ typedef struct { /** Function signature that log and trace callbacks must adhere too. */ typedef void(*dds_log_write_fn_t)(void *, const dds_log_data_t *); -extern uint32_t *const dds_log_mask; +DDS_EXPORT extern uint32_t *const dds_log_mask; /** * @brief Get currently enabled log and trace categories. diff --git a/src/mpt/CMakeLists.txt b/src/mpt/CMakeLists.txt new file mode 100644 index 0000000..4b759c7 --- /dev/null +++ b/src/mpt/CMakeLists.txt @@ -0,0 +1,22 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited 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 +# +cmake_minimum_required(VERSION 3.7) + +option(MPT_ENABLE_SELFTEST "Enable multi-process test-framework self test" OFF) + +set(MPT_CMAKE "${CMAKE_CURRENT_SOURCE_DIR}/mpt/cmake/MPT.cmake") +set(MPT_SOURCE_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +set(MPT_BINARY_ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}") + +add_subdirectory(mpt) +add_subdirectory(tests) + diff --git a/src/mpt/mpt/CMakeLists.txt b/src/mpt/mpt/CMakeLists.txt new file mode 100644 index 0000000..a81fbe5 --- /dev/null +++ b/src/mpt/mpt/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited 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 +# + + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/include/mpt/resource.h.in" "${CMAKE_CURRENT_BINARY_DIR}/include/mpt/resource.h" @ONLY) + + diff --git a/src/mpt/mpt/cmake/MPT.cmake b/src/mpt/mpt/cmake/MPT.cmake new file mode 100644 index 0000000..fd50b5b --- /dev/null +++ b/src/mpt/mpt/cmake/MPT.cmake @@ -0,0 +1,184 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited 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 +# +set(MPT_DIR "${CMAKE_CURRENT_LIST_DIR}/..") + + + +function(parse_mpt_fixtures INPUT TEST_DISABLED TEST_TIMEOUT TEST_XFAIL) + set(s "[ \t\r\n]") + if(INPUT MATCHES ".disabled${s}*=${s}*([tT][rR][uU][eE]|[0-9]+)") + set(${TEST_DISABLED} "TRUE" PARENT_SCOPE) + else() + set(${TEST_DISABLED} "FALSE" PARENT_SCOPE) + endif() + + if(INPUT MATCHES ".timeout${s}*=${s}*([0-9]+)") + set(${TEST_TIMEOUT} "${CMAKE_MATCH_1}" PARENT_SCOPE) + else() + set(${TEST_TIMEOUT} "0" PARENT_SCOPE) + endif() + + if(INPUT MATCHES ".xfail${s}*=${s}*([tT][rR][uU][eE]|[0-9]+)") + set(${TEST_XFAIL} "true" PARENT_SCOPE) + else() + set(${TEST_XFAIL} "false" PARENT_SCOPE) + endif() +endfunction() + + + +function(process_mpt_source_file SOURCE_FILE SUITES TESTS PROCESSES) + unset(tests) + unset(processes) + set(x "\\*") + set(s "[ \t\r\n]") + set(s_or_x "[ \t\r\n\\*]") + set(w "[_a-zA-Z0-9]") + set(ident_expr "(${s}*${w}+${s}*)") + # Very basic type recognition, only things that contain word characters and + # pointers are handled. And since this script does not actually have to + # compile any code, type checking is left to the compiler. An error (or + # warning) will be thrown if something is off. + # + # The "type" regular expression below will match things like: + # - + # - * + # - * * + set(type_expr "(${s}*${w}+${x}*${s}+${s_or_x}*)+") + set(param_expr "${type_expr}${ident_expr}") + # Test fixture support (based on test fixtures as implemented in Criterion), + # to enable per-test (de)initializers, which is very different from + # per-suite (de)initializers, and timeouts. + # + # The following fixtures are supported: + # - init + # - fini + # - disabled + # - timeout + set(data_expr "(${s}*,${s}*\\.${w}+${s}*=[^,\\)]+)*") + + file(READ "${SOURCE_FILE}" content) + + # MPT_Test + set(test_expr "MPT_Test${s}*\\(${ident_expr},${ident_expr}${data_expr}\\)") + string(REGEX MATCHALL "${test_expr}" matches "${content}") + foreach(match ${matches}) + string(REGEX REPLACE "${test_expr}" "\\1" suite "${match}") + string(REGEX REPLACE "${test_expr}" "\\2" test "${match}") + # Remove leading and trailing whitespace + string(STRIP "${suite}" suite) + string(STRIP "${test}" test) + + # Extract fixtures that must be handled by CMake (.disabled and .timeout). + parse_mpt_fixtures("${match}" disabled timeout xfail) + list(APPEND tests "${suite}:${test}:${disabled}:${timeout}:${xfail}") + list(APPEND suites "${suite}") + endforeach() + + # MPT_TestProcess + set(process_expr "MPT_TestProcess${s}*\\(${ident_expr},${ident_expr},${ident_expr}") + string(REGEX MATCHALL "${process_expr}" matches "${content}") + foreach(match ${matches}) + string(REGEX REPLACE "${process_expr}" "\\1" suite "${match}") + string(REGEX REPLACE "${process_expr}" "\\2" test "${match}") + string(REGEX REPLACE "${process_expr}" "\\3" id "${match}") + # Remove leading and trailing whitespace + string(STRIP "${suite}" suite) + string(STRIP "${test}" test) + string(STRIP "${id}" id) + + list(APPEND processes "${suite}:${test}:${id}") + endforeach() + + set(${PROCESSES} ${processes} PARENT_SCOPE) + set(${TESTS} ${tests} PARENT_SCOPE) + set(${SUITES} ${suites} PARENT_SCOPE) +endfunction() + + + +function(add_mpt_executable TARGET) + set(sources) + set(processes) + + foreach(source ${ARGN}) + if((EXISTS "${source}" OR EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${source}")) + unset(processes) + unset(tests) + unset(suites) + + # Disable missing-field-initializers warnings as not having to specify + # every member, aka fixture, is intended behavior. + if(${CMAKE_C_COMPILER_ID} STREQUAL "Clang" OR + ${CMAKE_C_COMPILER_ID} STREQUAL "AppleClang" OR + ${CMAKE_C_COMPILER_ID} STREQUAL "GNU") + set_property( + SOURCE "${source}" + PROPERTY COMPILE_FLAGS -Wno-missing-field-initializers) + endif() + + process_mpt_source_file("${source}" suites tests processes) + + foreach(suite ${suites}) + set(addsuites "${addsuites}\n mpt_add_suite(mpt_suite_new(\"${suite}\"));") + endforeach() + + foreach(testcase ${tests}) + string(REPLACE ":" ";" testcase ${testcase}) + list(GET testcase 0 suite) + list(GET testcase 1 test) + list(GET testcase 2 disabled) + list(GET testcase 3 timeout) + list(GET testcase 4 xfail) + set(addtests "${addtests}\n mpt_add_test(\"${suite}\", mpt_test_new(\"${test}\", ${timeout}, ${xfail}));") + + # Add this test to ctest. + set(ctest "${TARGET}_${suite}_${test}") + add_test( + NAME ${ctest} + COMMAND ${TARGET} -s ${suite} -t ${test}) + set_property(TEST ${ctest} PROPERTY TIMEOUT ${timeout}) + set_property(TEST ${ctest} PROPERTY DISABLED ${disabled}) + endforeach() + + foreach(process ${processes}) + string(REPLACE ":" ";" process ${process}) + list(GET process 0 suite) + list(GET process 1 test) + list(GET process 2 id) + set(addprocs "${addprocs}\n mpt_add_process(\"${suite}\", \"${test}\", mpt_process_new(\"${id}\", MPT_TestProcessName(${suite}, ${test}, ${id})));") + set(procdecls "${procdecls}\nextern MPT_TestProcessDeclaration(${suite}, ${test}, ${id});") + endforeach() + + list(APPEND sources "${source}") + endif() + endforeach() + + configure_file( + "${MPT_DIR}/src/main.c.in" "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.c" @ONLY) + + add_executable( + ${TARGET} "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}.c" ${sources}) + + target_include_directories(${TARGET} PRIVATE "${MPT_DIR}/include" "${MPT_BINARY_ROOT_DIR}/mpt/include") + target_link_libraries(${TARGET} PRIVATE ddsc) + + # We need the 'expand environment variables' feature that is present in the + # 'util' module. However, it is currently not possible to properly link to + # that module on Windows. In the near future, the utils will be migrated to + # ddsrt, after which we automatically have access to expand_envvars. + # But until then, use this very ugly (but quick) hack to solve our immediate + # build issues. + target_include_directories(${TARGET} PRIVATE "${CMAKE_SOURCE_DIR}/util/include") + target_sources(${TARGET} PRIVATE "${CMAKE_SOURCE_DIR}/util/src/ut_expand_envvars.c") +endfunction() + diff --git a/src/mpt/mpt/include/mpt/mpt.h b/src/mpt/mpt/include/mpt/mpt.h new file mode 100644 index 0000000..8d0522a --- /dev/null +++ b/src/mpt/mpt/include/mpt/mpt.h @@ -0,0 +1,158 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited 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 MPT_H_INCLUDED +#define MPT_H_INCLUDED + +#include +#include +#include "mpt/resource.h" +#include "mpt/private/mpt.h" +#include "dds/ddsrt/environ.h" + + +/* Environment name/value pair. */ +typedef struct mpt_env_ { + const char *name; + const char *value; +} mpt_env_t; + + +/* Process entry argument definitions. */ +#define MPT_Args(...) MPT_ProcessArgsSyntax, __VA_ARGS__ +#define MPT_NoArgs() MPT_ProcessArgsSyntax + +#define MPT_ArgValues(...) MPT_ProcessArgs, __VA_ARGS__ +#define MPT_NoArgValues() MPT_ProcessArgs + + +/* Process entry definition. */ +#define MPT_ProcessEntry(process, args)\ +void MPT_ProcessEntryName(process)(args) + + +/* IPC functions. */ +#define MPT_Send(str) mpt_ipc_send(MPT_ProcessArgs, str); +#define MPT_Wait(str) mpt_ipc_wait(MPT_ProcessArgs, str); + + +/* + * MPT_TestProcess generates a wrapper function that takes care of + * per-process initialization, environment settings, + * deinitialization and the actual process entry call. + */ +#define MPT_TestProcess(suite, test, name, process, args, ...) \ +MPT_TestInitDeclaration(suite, test); \ +MPT_TestFiniDeclaration(suite, test); \ +MPT_TestProcessDeclaration(suite, test, name) \ +{ \ + mpt_data_t data = MPT_Fixture(__VA_ARGS__); \ + \ + /* Always export the process name. */ \ + /* This can be used to generate unique log files fi. */ \ + ddsrt_setenv("MPT_PROCESS_NAME", \ + MPT_XSTR(MPT_TestProcessName(suite, test, name))); \ + \ + /* Initialize test related stuff first. */ \ + MPT_TestInitName(suite, test)(); \ + \ + /* Pre-process initialization. */ \ + mpt_export_env(data.environment); \ + if (data.init != NULL) { \ + data.init(); \ + } \ + \ + /* Execute the actual process entry function. */ \ + MPT_ProcessEntryName(process)(args); \ + \ + /* Teardown process and test. */ \ + if (data.fini != NULL) { \ + data.fini(); \ + } \ + MPT_TestFiniName(suite, test)(); \ +} + + +/* + * MPT_Test generates wrapper functions that take care of + * per-test initialization, environment settings and + * deinitialization. + * This is also used by CMake to determine the ctest timeout + * and disabled settings. + */ +#define MPT_Test(suite, test, ...) \ +MPT_TestInitDeclaration(suite, test) \ +{ \ + mpt_data_t data = MPT_Fixture(__VA_ARGS__); \ + mpt_export_env(data.environment); \ + if (data.init != NULL) { \ + data.init(); \ + } \ +} \ +MPT_TestFiniDeclaration(suite, test) \ +{ \ + mpt_data_t data = MPT_Fixture(__VA_ARGS__); \ + if (data.fini != NULL) { \ + data.fini(); \ + } \ +} + + +/* + * Test asserts. + * Printing is supported eg MPT_ASSERT_EQ(a, b, "foo: %s", bar") + */ +#define MPT_ASSERT(cond, ...) MPT__ASSERT__(cond, MPT_FATAL_NO, __VA_ARGS__) + +#define MPT_ASSERT_FAIL(...) MPT_ASSERT(0, __VA_ARGS__) + +#define MPT_ASSERT_EQ(value, expected, ...) MPT_ASSERT((value == expected), __VA_ARGS__) +#define MPT_ASSERT_NEQ(value, expected, ...) MPT_ASSERT((value != expected), __VA_ARGS__) +#define MPT_ASSERT_LEQ(value, expected, ...) MPT_ASSERT((value <= expected), __VA_ARGS__) +#define MPT_ASSERT_GEQ(value, expected, ...) MPT_ASSERT((value >= expected), __VA_ARGS__) +#define MPT_ASSERT_LT(value, expected, ...) MPT_ASSERT((value < expected), __VA_ARGS__) +#define MPT_ASSERT_GT(value, expected, ...) MPT_ASSERT((value > expected), __VA_ARGS__) + +#define MPT_ASSERT_NULL(value, ...) MPT_ASSERT((value == NULL), __VA_ARGS__) +#define MPT_ASSERT_NOT_NULL(value, ...) MPT_ASSERT((value != NULL), __VA_ARGS__) + +#define MPT_ASSERT_STR_EQ(value, expected, ...) MPT_ASSERT((MPT_STRCMP(value, expected, 1) == 0), __VA_ARGS__) +#define MPT_ASSERT_STR_NEQ(value, expected, ...) MPT_ASSERT((MPT_STRCMP(value, expected, 0) != 0), __VA_ARGS__) +#define MPT_ASSERT_STR_EMPTY(value, ...) MPT_ASSERT((MPT_STRLEN(value, 1) == 0), __VA_ARGS__) +#define MPT_ASSERT_STR_NOT_EMPTY(value, ...) MPT_ASSERT((MPT_STRLEN(value, 0) > 0), __VA_ARGS__) + + +/* Fatal just means that control is returned to the parent function. */ +#define MPT_ASSERT_FATAL(cond, ...) MPT__ASSERT__(cond, MPT_FATAL_YES, __VA_ARGS__) + +#define MPT_ASSERT_FATAL_FAIL(...) MPT_ASSERT_FATAL(0, __VA_ARGS__) + +#define MPT_ASSERT_FATAL_EQ(value, expected, ...) MPT_ASSERT_FATAL((value == expected), __VA_ARGS__) +#define MPT_ASSERT_FATAL_NEQ(value, expected, ...) MPT_ASSERT_FATAL((value != expected), __VA_ARGS__) +#define MPT_ASSERT_FATAL_LEQ(value, expected, ...) MPT_ASSERT_FATAL((value <= expected), __VA_ARGS__) +#define MPT_ASSERT_FATAL_GEQ(value, expected, ...) MPT_ASSERT_FATAL((value >= expected), __VA_ARGS__) +#define MPT_ASSERT_FATAL_LT(value, expected, ...) MPT_ASSERT_FATAL((value < expected), __VA_ARGS__) +#define MPT_ASSERT_FATAL_GT(value, expected, ...) MPT_ASSERT_FATAL((value > expected), __VA_ARGS__) + +#define MPT_ASSERT_FATAL_NULL(value, ...) MPT_ASSERT_FATAL((value == NULL), __VA_ARGS__) +#define MPT_ASSERT_FATAL_NOT_NULL(value, ...) MPT_ASSERT_FATAL((value != NULL), __VA_ARGS__) + +#define MPT_ASSERT_FATAL_STR_EQ(value, expected, ...) MPT_ASSERT_FATAL((MPT_STRCMP(value, expected, 1) == 0), __VA_ARGS__) +#define MPT_ASSERT_FATAL_STR_NEQ(value, expected, ...) MPT_ASSERT_FATAL((MPT_STRCMP(value, expected, 0) != 0), __VA_ARGS__) +#define MPT_ASSERT_FATAL_STR_EMPTY(value, ...) MPT_ASSERT_FATAL((MPT_STRLEN(value, 1) == 0), __VA_ARGS__) +#define MPT_ASSERT_FATAL_STR_NOT_EMPTY(value, ...) MPT_ASSERT_FATAL((MPT_STRLEN(value, 0) > 0), __VA_ARGS__) + + +/* Helpful function to check for patterns in log callbacks. */ +int mpt_patmatch(const char *pat, const char *str); + + +#endif /* MPT_H_INCLUDED */ diff --git a/src/mpt/mpt/include/mpt/private/mpt.h b/src/mpt/mpt/include/mpt/private/mpt.h new file mode 100644 index 0000000..e167e1d --- /dev/null +++ b/src/mpt/mpt/include/mpt/private/mpt.h @@ -0,0 +1,153 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited 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 MPT_PRIVATE_H_INCLUDED +#define MPT_PRIVATE_H_INCLUDED + +#include +#include + + +/* + * Just some helpful macros. + */ +#define MPT_XSTR(s) MPT_STR(s) +#define MPT_STR(s) #s + +#define MPT_STRCMP(value, expected, err) \ + (((value != NULL) && (expected != NULL)) ? strcmp(value, expected) : err) + +#define MPT_STRLEN(value, err) \ + ((value != NULL) ? strlen(value) : err) + +#define MPT_ProcessArgs mpt__args__, mpt__retval__ +#define MPT_ProcessArgsSyntax \ + const mpt_data_t *mpt__args__, mpt_retval_t *mpt__retval__ + + + +/* + * Name and declaration macros. + */ +#define MPT_ProcessEntryName(process) \ + MPT_ProcessEntry__ ## process + +#define MPT_TestInitName(suite, test) \ + MPT_TestInit__##suite##_##test + +#define MPT_TestInitDeclaration(suite, test) \ +void MPT_TestInitName(suite, test)(void) + +#define MPT_TestFiniName(suite, test) \ + MPT_TestFini__##suite##_##test + +#define MPT_TestFiniDeclaration(suite, test) \ +void MPT_TestFiniName(suite, test)(void) + +#define MPT_TestProcessName(suite, test, name) \ + MPT_TestProcess__##suite##_##test##_##name + +#define MPT_TestProcessDeclaration(suite, test, name) \ +void MPT_TestProcessName(suite, test, name) (MPT_ProcessArgsSyntax) + + + +/* + * MPT Assert impl. + */ +typedef enum { + MPT_SUCCESS = 0, + MPT_FAILURE +} mpt_retval_t; + +#define MPT_FATAL_YES 1 +#define MPT_FATAL_NO 0 + +#ifdef _WIN32 +/* Microsoft Visual Studio does not expand __VA_ARGS__ correctly. */ +#define MPT__ASSERT__(...) MPT__ASSERT____((__VA_ARGS__)) +#define MPT__ASSERT____(tuple) MPT__ASSERT___ tuple +#else +#define MPT__ASSERT__(...) MPT__ASSERT___(__VA_ARGS__) +#endif /* _WIN32 */ + +#define MPT__ASSERT___(cond, fatal, ...) \ + do { \ + (void)mpt__args__; /* Satisfy compiler. */ \ + if (!(cond)) { \ + if (*mpt__retval__ == MPT_SUCCESS) { \ + *mpt__retval__ = MPT_FAILURE; \ + } \ + printf("MPT_FAIL(%s, %d):\n", __FILE__, __LINE__); \ + printf(__VA_ARGS__); \ + printf("\n"); \ + if (fatal == MPT_FATAL_YES) { \ + return; \ + } \ + } \ + } while(0) + + + +/* + * MPT Fixture impl. + */ +struct mpt_env_; + +typedef void(*mpt_init_func_t)(void); +typedef void(*mpt_fini_func_t)(void); + +typedef struct { + /* Test and process fixtures. */ + mpt_init_func_t init; + mpt_fini_func_t fini; + struct mpt_env_ *environment; + /* Test fixtures. */ + bool disabled; + int timeout; + bool xfail; + /* IPC information. */ + int todo; +} mpt_data_t; + +/* Microsoft Visual Studio does not like empty struct initializers, i.e. + no fixtures are specified. To work around that issue MPT_Fixture inserts a + NULL initializer as fall back. */ +#define MPT_Comma() , +#define MPT_Reduce(one, ...) one + +#ifdef _WIN32 +/* Microsoft Visual Studio does not expand __VA_ARGS__ correctly. */ +#define MPT_Fixture__(...) MPT_Fixture____((__VA_ARGS__)) +#define MPT_Fixture____(tuple) MPT_Fixture___ tuple +#else +#define MPT_Fixture__(...) MPT_Fixture___(__VA_ARGS__) +#endif /* _WIN32 */ + +#define MPT_Fixture___(throw, away, value, ...) value + +#define MPT_Fixture_(throwaway, ...) \ + MPT_Fixture__(throwaway, ((mpt_data_t){ 0 }), ((mpt_data_t){ __VA_ARGS__ })) + +#define MPT_Fixture(...) \ + MPT_Fixture_( MPT_Comma MPT_Reduce(__VA_ARGS__,) (), __VA_ARGS__ ) + + + +/* + * MPT Support functions. + */ +void mpt_export_env(const struct mpt_env_ *env); +void mpt_ipc_send(MPT_ProcessArgsSyntax, const char *str); +void mpt_ipc_wait(MPT_ProcessArgsSyntax, const char *str); + + +#endif /* MPT_PRIVATE_H_INCLUDED */ diff --git a/src/mpt/mpt/include/mpt/resource.h.in b/src/mpt/mpt/include/mpt/resource.h.in new file mode 100644 index 0000000..13c5711 --- /dev/null +++ b/src/mpt/mpt/include/mpt/resource.h.in @@ -0,0 +1,6 @@ +#ifndef MPT_RESOURCE_H_INCLUDED +#define MPT_RESOURCE_H_INCLUDED + +#define MPT_SOURCE_ROOT_DIR "@MPT_SOURCE_ROOT_DIR@" + +#endif /* MPT_RESOURCE_H_INCLUDED */ diff --git a/src/mpt/mpt/src/main.c.in b/src/mpt/mpt/src/main.c.in new file mode 100644 index 0000000..31f0689 --- /dev/null +++ b/src/mpt/mpt/src/main.c.in @@ -0,0 +1,551 @@ +#include +#include +#include +#include +#include "mpt/mpt.h" +#include "dds/ddsrt/heap.h" +#include "dds/ddsrt/process.h" +#include "dds/ddsrt/environ.h" +#include "dds/util/ut_expand_envvars.h" + +#ifndef _WIN32 +#include +#else +#define EX_USAGE (64) +#define EX_SOFTWARE (70) +#endif /* _WIN32 */ + + + +/************************************************ + * Support functions. + ************************************************/ +int +mpt_patmatch(const char *pat, const char *str) +{ + while (*pat) { + if (*pat == '?') { + /* any character will do */ + if (*str++ == 0) { + return 0; + } + pat++; + } else if (*pat == '*') { + /* collapse a sequence of wildcards, requiring as many + characters in str as there are ?s in the sequence */ + while (*pat == '*' || *pat == '?') { + if (*pat == '?' && *str++ == 0) { + return 0; + } + pat++; + } + /* try matching on all positions where str matches pat */ + while (*str) { + if (*str == *pat && mpt_patmatch(pat+1, str+1)) { + return 1; + } + str++; + } + return *pat == 0; + } else { + /* only an exact match */ + if (*str++ != *pat++) { + return 0; + } + } + } + + return *str == 0; +} + +void +mpt_export_env(const mpt_env_t *env) +{ + if (env) { + while ((env->name != NULL) && (env->value != NULL)) { + char *expanded = ut_expand_envvars(env->value); + ddsrt_setenv(env->name, expanded); + ddsrt_free(expanded); + env++; + } + } +} + +void +mpt_ipc_send(MPT_ProcessArgsSyntax, const char *str) +{ + (void)str; + (void)mpt__args__; + /* TODO: implement. */ + MPT_ASSERT(0, "mpt_ipc_send not implemented"); +} + +void +mpt_ipc_wait(MPT_ProcessArgsSyntax, const char *str) +{ + (void)str; + (void)mpt__args__; + /* TODO: implement. */ + MPT_ASSERT(0, "mpt_ipc_wait not implemented"); +} + + + +/************************************************ + * Processes. + ************************************************/ +@procdecls@ + +typedef void(*mpt_func_proc_t)( + const mpt_data_t *mpt__args__, mpt_retval_t *mpt__retval__); + +typedef struct mpt_process_ { + const char* name; + ddsrt_pid_t pid; + mpt_func_proc_t process; + struct mpt_process_ *next; +} mpt_process_t; + +static mpt_process_t* +mpt_process_new(const char* name, mpt_func_proc_t process) +{ + mpt_process_t *proc; + proc = ddsrt_malloc(sizeof(mpt_process_t)); + proc->pid = 0; + proc->name = name; + proc->process = process; + proc->next = NULL; + return proc; +} + +static void +mpt_process_freelist(mpt_process_t *proc) +{ + if (proc) { + mpt_process_freelist(proc->next); + if (proc->pid != 0) { + printf("Process %s kill(%d)\n", proc->name, (int)proc->pid); + ddsrt_proc_kill(proc->pid); + } + ddsrt_free(proc); + } +} + + + +/************************************************ + * Tests. + ************************************************/ +typedef struct mpt_test_ { + const char* name; + bool xfail; + dds_duration_t timeout; + mpt_process_t *procs; + struct mpt_test_ *next; +} mpt_test_t; + +static mpt_test_t* +mpt_test_new(const char* name, int secs, bool xf) +{ + mpt_test_t *test; + test = ddsrt_malloc(sizeof(mpt_test_t)); + test->procs = NULL; + test->name = name; + test->xfail = xf; + + /* This test will stop after a given timeout. However, when running in + * ctest, we'd like to use the ctest provided timeout functionality. + * So, make sure that the 'manual' timeout takes longer than the ctest + * timeout. */ + if (secs == 0) { + test->timeout = DDS_SECS(1600); + } else { + test->timeout = DDS_SECS(secs * 2); + } + + return test; +} + +static void +mpt_test_freelist(mpt_test_t *test) +{ + if (test) { + mpt_process_freelist(test->procs); + mpt_test_freelist(test->next); + } + ddsrt_free(test); +} + +static void +mpt_test_add_process(mpt_test_t *test, mpt_process_t *proc) +{ + /* Prepend */ + proc->next = test->procs; + test->procs = proc; +} + + + +/************************************************ + * Suites. + ************************************************/ +typedef struct mpt_suite_ { + const char* name; + mpt_test_t *tests; + struct mpt_suite_ *next; +} mpt_suite_t; + +static mpt_suite_t* +mpt_suite_new(const char* name) +{ + mpt_suite_t *suite; + suite = ddsrt_malloc(sizeof(mpt_suite_t)); + suite->tests = NULL; + suite->name = name; + return suite; +} + +static void +mpt_suite_freelist(mpt_suite_t *suite) +{ + if (suite) { + mpt_test_freelist(suite->tests); + mpt_suite_freelist(suite->next); + } + ddsrt_free(suite); +} + +static void +mpt_suite_add_test(mpt_suite_t *suite, mpt_test_t *test) +{ + /* Prepend */ + test->next = suite->tests; + suite->tests = test; +} + +static mpt_test_t* +mpt_suite_find_test( + mpt_suite_t *suite, const char *tname) +{ + mpt_test_t *found = suite->tests; + while (found) { + if (mpt_patmatch(tname, found->name)) { + break; + } + found = found->next; + } + return found; +} + + + +/************************************************ + * Root. + ************************************************/ +mpt_suite_t *root = NULL; + +static void +mpt_add_suite(mpt_suite_t *suite) +{ + /* Prepend */ + suite->next = root; + root = suite; +} + +static mpt_suite_t* +mpt_find_suite(const char *sname) +{ + mpt_suite_t *found = root; + while (found) { + if (mpt_patmatch(sname, found->name)) { + break; + } + found = found->next; + } + return found; +} + +static void +mpt_add_test(const char *sname, mpt_test_t *test) +{ + mpt_suite_t *suite = mpt_find_suite(sname); + assert(suite); + mpt_suite_add_test(suite, test); +} + +static void +mpt_add_process(const char *sname, const char *tname, mpt_process_t *proc) +{ + mpt_suite_t *suite = mpt_find_suite(sname); + mpt_test_t *test = mpt_suite_find_test(suite, tname); + assert(suite); + assert(test); + mpt_test_add_process(test, proc); +} + +static void +mpt_free(void) +{ + mpt_suite_freelist(root); + root = NULL; +} + + + +/************************************************ + * Runner. + ************************************************/ +static int +mpt_run_test(const char *exe, mpt_suite_t *suite, mpt_test_t *test) +{ + int result = EXIT_SUCCESS; + mpt_process_t *proc; + dds_retcode_t retcode; + char *argv[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + + argv[0] = "-s"; + argv[1] = (char*)suite->name; + argv[2] = "-t"; + argv[3] = (char*)test->name; + + /* Start the processes. */ + proc = test->procs; + while ((proc) && (result == EXIT_SUCCESS)) { + if (proc == test->procs) { + printf("\n\n"); + printf("=====================================================\n"); + printf("Suite: %s\n", suite->name); + printf("Test: %s\n", test->name); + printf("=====================================================\n"); + } + argv[4] = "-p"; + argv[5] = (char*)proc->name; + retcode = ddsrt_proc_create(exe, argv, &proc->pid); + if (retcode != DDS_RETCODE_OK) { + printf("Start %s::%s::%s failed\n", suite->name, test->name, proc->name); + proc->pid = 0; + result = EXIT_FAILURE; + } + proc = proc->next; + } + + /* Wait for the processes. */ + retcode = DDS_RETCODE_OK; + while ((result == EXIT_SUCCESS) && (retcode == DDS_RETCODE_OK)) { + int32_t status; + ddsrt_pid_t pid; + /* A second/third/etc wait will restart the timeout. + * This means that the end timeout can take longer than the requested + * test timeout. However, that's good enough for our purpose. */ + retcode = ddsrt_proc_waitpids(test->timeout, &pid, &status); + if (retcode == DDS_RETCODE_OK) { + proc = test->procs; + while (proc) { + if (proc->pid == pid) { + break; + } + proc = proc->next; + } + if (proc) { + proc->pid = 0; + if (status != 0) { + printf("Process %s::%s::%s failed (%d)\n", suite->name, test->name, proc->name, status); + result = EXIT_FAILURE; + } + } else { + printf("Wait for processes of %s::%s return unknown pid %d\n", suite->name, test->name, (int)pid); + result = EXIT_FAILURE; + } + } else if (retcode != DDS_RETCODE_NOT_FOUND) { + printf("Wait for processes of %s::%s failed (%d)\n", suite->name, test->name, (int)retcode); + result = EXIT_FAILURE; + } + } + + /* Be sure to kill all remaining processes when needed. */ + if (result != EXIT_SUCCESS) { + proc = test->procs; + while (proc) { + if (proc->pid != 0) { + printf("Process %s::%s::%s kill(%d)\n", suite->name, test->name, proc->name, (int)proc->pid); + ddsrt_proc_kill(proc->pid); + ddsrt_proc_waitpid(proc->pid, DDS_SECS(10), NULL); + proc->pid = 0; + } + proc = proc->next; + } + } + + /* Revert result when we expect the test to have failed. */ + if (test->xfail) { + result = ((result == EXIT_SUCCESS) ? EXIT_FAILURE : EXIT_SUCCESS); + } + + return result; +} + +static int +mpt_run_tests(const char *exe, const char *spattern, const char *tpattern) +{ + int result = EXIT_SUCCESS; + mpt_suite_t *suite = root; + while (suite) { + if (mpt_patmatch(spattern, suite->name)) { + mpt_test_t *test = suite->tests; + while (test) { + if (mpt_patmatch(tpattern, test->name)) { + int run = mpt_run_test(exe, suite, test); + if (run != EXIT_SUCCESS) { + result = run; + } + } + test = test->next; + } + } + suite = suite->next; + } + return result; +} + +static int +mpt_run_proc(mpt_process_t *proc) +{ + mpt_retval_t retval = MPT_SUCCESS; + mpt_data_t args; + proc->process(&args, &retval); + return (retval == MPT_SUCCESS) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +static int +mpt_run_procs(const char *spattern, const char *tpattern, const char *ppattern) +{ + int result = EXIT_SUCCESS; + mpt_suite_t *suite = root; + while (suite) { + if (mpt_patmatch(spattern, suite->name)) { + mpt_test_t *test = suite->tests; + while (test) { + if (mpt_patmatch(tpattern, test->name)) { + mpt_process_t *proc = test->procs; + while (proc) { + if (mpt_patmatch(ppattern, proc->name)) { + int run = mpt_run_proc(proc); + if (run != EXIT_SUCCESS) { + result = run; + } + } + proc = proc->next; + } + } + test = test->next; + } + } + suite = suite->next; + } + return result; +} + + + +/************************************************ + * Main functionality. + ************************************************/ +static struct { + bool print_help; + const char *suite; + const char *test; + const char *process; +} opts = { + false, + "*", + "*", + NULL +}; + +static int parse_options(int argc, char *argv[]) +{ + int err = 0; + + for (int i = 1; err == 0 && i < argc; i++) { + switch ((argv[i][0] == '-') ? argv[i][1] : 0) { + case 'h': + opts.print_help = true; + break; + case 's': + if ((i+1) < argc) { + opts.suite = argv[++i]; + break; + } + /* FALLS THROUGH */ + case 't': + if ((i+1) < argc) { + opts.test = argv[++i]; + break; + } + /* FALLS THROUGH */ + case 'p': + if ((i+1) < argc) { + opts.process = argv[++i]; + break; + } + /* FALLS THROUGH */ + default: + err = 1; + break; + } + } + + return err; +} + +static void usage(const char *name) +{ + fprintf(stderr, "Usage: %s OPTIONS\n", name); + fprintf(stderr, "Try '%s -h' for more information\n", name); +} + +static void help(const char *name) +{ + printf("Usage: %s [OPTIONS]\n", name); + printf("\n"); + printf("Possible options:\n"); + printf(" -h display this help and exit\n"); + printf(" -s PATTERN run only tests in suites matching pattern\n"); + printf(" -t PATTERN run only test matching pattern\n"); + printf(" -p PROCESS run only process matching pattern\n"); + printf("\n"); + printf("Exit codes:\n"); + printf(" %-2d Successful termination\n", EXIT_SUCCESS); + printf(" %-2d One or more failing test cases\n", EXIT_FAILURE); + printf(" %-2d Command line usage error\n", EX_USAGE); + printf(" %-2d Internal software error\n", EX_SOFTWARE); +} + +int main(int argc, char *argv[]) +{ + int result = EXIT_SUCCESS; + + if (parse_options(argc, argv) != 0) { + usage(argv[0]); + return EX_USAGE; + } else if (opts.print_help) { + help(argv[0]); + return result; + } + + atexit(mpt_free); + @addsuites@ + @addtests@ + @addprocs@ + + if (opts.process == NULL) { + /* Run test(s). */ + result = mpt_run_tests(argv[0], opts.suite, opts.test); + } else { + /* Run process(es). */ + result = mpt_run_procs(opts.suite, opts.test, opts.process); + } + + return result; +} + diff --git a/src/mpt/tests/CMakeLists.txt b/src/mpt/tests/CMakeLists.txt new file mode 100644 index 0000000..91a9183 --- /dev/null +++ b/src/mpt/tests/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited 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 +# +cmake_minimum_required(VERSION 3.7) + +if(MPT_ENABLE_SELFTEST) + add_subdirectory(self) +endif() + +add_subdirectory(basic) + diff --git a/src/mpt/tests/basic/CMakeLists.txt b/src/mpt/tests/basic/CMakeLists.txt new file mode 100644 index 0000000..cb2efba --- /dev/null +++ b/src/mpt/tests/basic/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited 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 +# +include(${MPT_CMAKE}) + +set(sources + "procs/hello.c" + "helloworld.c" + "multi.c") + +add_mpt_executable(mpt_basic ${sources}) + +idlc_generate(mpt_basic_helloworlddata_lib "procs/helloworlddata.idl") +target_link_libraries(mpt_basic PRIVATE mpt_basic_helloworlddata_lib) + diff --git a/src/mpt/tests/basic/etc/config_any.xml b/src/mpt/tests/basic/etc/config_any.xml new file mode 100644 index 0000000..68e0e0a --- /dev/null +++ b/src/mpt/tests/basic/etc/config_any.xml @@ -0,0 +1,30 @@ + + + + any + + + + auto + true + true + + + lax + + + + diff --git a/src/mpt/tests/basic/etc/config_specific.xml b/src/mpt/tests/basic/etc/config_specific.xml new file mode 100644 index 0000000..2ae3286 --- /dev/null +++ b/src/mpt/tests/basic/etc/config_specific.xml @@ -0,0 +1,30 @@ + + + + ${DOMAIN_ID} + + + + auto + true + true + + + lax + + + + diff --git a/src/mpt/tests/basic/helloworld.c b/src/mpt/tests/basic/helloworld.c new file mode 100644 index 0000000..c959edb --- /dev/null +++ b/src/mpt/tests/basic/helloworld.c @@ -0,0 +1,63 @@ +#include "dds/dds.h" +#include "mpt/mpt.h" +#include "mpt/resource.h" /* MPT_SOURCE_ROOT_DIR */ +#include "procs/hello.h" /* publisher and subscriber entry points. */ + + +/* + * Tests to check simple communication between a publisher and subscriber. + * The published text should be received by the subscriber. + */ + + +/* Environments */ +static mpt_env_t environment_any[] = { + { "ETC_DIR", MPT_SOURCE_ROOT_DIR"/tests/basic/etc" }, + { "CYCLONEDDS_URI", "file://${ETC_DIR}/config_any.xml" }, + { NULL, NULL } +}; +static mpt_env_t environment_42[] = { + { "ETC_DIR", MPT_SOURCE_ROOT_DIR"/tests/basic/etc" }, + { "DOMAIN_ID", "42" }, + { "CYCLONEDDS_URI", "file://${ETC_DIR}/config_specific.xml" }, + { NULL, NULL } +}; + + + +/********************************************************************** + * No CYCLONEDDS_URI set. + **********************************************************************/ +#define TEST_ARGS MPT_ArgValues(DDS_DOMAIN_DEFAULT, "hello_def", 1, "No environment set") +MPT_TestProcess(helloworld, default, pub, hello_publisher, TEST_ARGS); +MPT_TestProcess(helloworld, default, sub, hello_subscriber, TEST_ARGS); +MPT_Test(helloworld, default, .init=hello_init, .fini=hello_fini); +#undef TEST_ARGS + + + +/********************************************************************** + * Config domain is any. Test domain is default. + **********************************************************************/ +#define TEST_ARGS MPT_ArgValues(DDS_DOMAIN_DEFAULT, "hello_any", 1, "Some nice text over any domain") +MPT_TestProcess(helloworld, domain_any, pub, hello_publisher, TEST_ARGS); +MPT_TestProcess(helloworld, domain_any, sub, hello_subscriber, TEST_ARGS); +MPT_Test(helloworld, domain_any, .init=hello_init, .fini=hello_fini, .environment=environment_any); +#undef TEST_ARGS + + + +/********************************************************************** + * Pub: Config domain is any. Test domain is 42. + * Sub: Config domain is 42 (through DOMAIN_ID env). Test domain is default. + **********************************************************************/ +#define TEST_PUB_ARGS MPT_ArgValues(42, "hello_42", 1, "Now domain 42 is used") +#define TEST_SUB_ARGS MPT_ArgValues(DDS_DOMAIN_DEFAULT, "hello_42", 1, "Now domain 42 is used") +MPT_TestProcess(helloworld, domain_42, pub, hello_publisher, TEST_PUB_ARGS, .environment=environment_any); +MPT_TestProcess(helloworld, domain_42, sub, hello_subscriber, TEST_SUB_ARGS, .environment=environment_42); +MPT_Test(helloworld, domain_42, .init=hello_init, .fini=hello_fini); +#undef TEST_SUB_ARGS +#undef TEST_PUB_ARGS + + + diff --git a/src/mpt/tests/basic/multi.c b/src/mpt/tests/basic/multi.c new file mode 100644 index 0000000..4ed5743 --- /dev/null +++ b/src/mpt/tests/basic/multi.c @@ -0,0 +1,50 @@ +#include "mpt/mpt.h" +#include "procs/hello.h" + + +/* + * Tests to check communication between multiple publisher(s) and subscriber(s). + */ + + +/* + * The publisher expects 2 publication matched. + * The subscribers expect 1 sample each. + */ +#define TEST_PUB_ARGS MPT_ArgValues(DDS_DOMAIN_DEFAULT, "multi_pubsubsub", 2, "pubsubsub") +#define TEST_SUB_ARGS MPT_ArgValues(DDS_DOMAIN_DEFAULT, "multi_pubsubsub", 1, "pubsubsub") +MPT_TestProcess(multi, pubsubsub, pub, hello_publisher, TEST_PUB_ARGS); +MPT_TestProcess(multi, pubsubsub, sub1, hello_subscriber, TEST_SUB_ARGS); +MPT_TestProcess(multi, pubsubsub, sub2, hello_subscriber, TEST_SUB_ARGS); +MPT_Test(multi, pubsubsub, .init=hello_init, .fini=hello_fini); +#undef TEST_SUB_ARGS +#undef TEST_PUB_ARGS + + +/* + * The publishers expect 1 publication matched each. + * The subscriber expects 2 samples. + */ +#define TEST_PUB_ARGS MPT_ArgValues(DDS_DOMAIN_DEFAULT, "multi_pubpubsub", 1, "pubpubsub") +#define TEST_SUB_ARGS MPT_ArgValues(DDS_DOMAIN_DEFAULT, "multi_pubpubsub", 2, "pubpubsub") +MPT_TestProcess(multi, pubpubsub, pub1, hello_publisher, TEST_PUB_ARGS); +MPT_TestProcess(multi, pubpubsub, pub2, hello_publisher, TEST_PUB_ARGS); +MPT_TestProcess(multi, pubpubsub, sub, hello_subscriber, TEST_SUB_ARGS); +MPT_Test(multi, pubpubsub, .init=hello_init, .fini=hello_fini); +#undef TEST_SUB_ARGS +#undef TEST_PUB_ARGS + + +/* + * The publishers expect 2 publication matched each. + * The subscribers expect 2 samples each. + */ +#define TEST_PUB_ARGS MPT_ArgValues(DDS_DOMAIN_DEFAULT, "multi_pubpubsubsub", 2, "pubpubsubsub") +#define TEST_SUB_ARGS MPT_ArgValues(DDS_DOMAIN_DEFAULT, "multi_pubpubsubsub", 2, "pubpubsubsub") +MPT_TestProcess(multi, pubpubsubsub, pub1, hello_publisher, TEST_PUB_ARGS); +MPT_TestProcess(multi, pubpubsubsub, pub2, hello_publisher, TEST_PUB_ARGS); +MPT_TestProcess(multi, pubpubsubsub, sub1, hello_subscriber, TEST_SUB_ARGS); +MPT_TestProcess(multi, pubpubsubsub, sub2, hello_subscriber, TEST_SUB_ARGS); +MPT_Test(multi, pubpubsubsub, .init=hello_init, .fini=hello_fini); +#undef TEST_SUB_ARGS +#undef TEST_PUB_ARGS diff --git a/src/mpt/tests/basic/procs/hello.c b/src/mpt/tests/basic/procs/hello.c new file mode 100644 index 0000000..2c68a84 --- /dev/null +++ b/src/mpt/tests/basic/procs/hello.c @@ -0,0 +1,239 @@ +#include +#include +#include + +#include "mpt/mpt.h" + +#include "dds/dds.h" +#include "helloworlddata.h" + +#include "dds/ddsrt/time.h" +#include "dds/ddsrt/strtol.h" +#include "dds/ddsrt/process.h" +#include "dds/ddsrt/environ.h" +#include "dds/ddsrt/cdtors.h" +#include "dds/ddsrt/sync.h" + + +/* An array of one message (aka sample in dds terms) will be used. */ +#define MAX_SAMPLES 1 + +static int g_publication_matched_count = 0; +static ddsrt_mutex_t g_mutex; +static ddsrt_cond_t g_cond; + +static void +publication_matched_cb( + dds_entity_t writer, + const dds_publication_matched_status_t status, + void* arg) +{ + (void)arg; + (void)writer; + ddsrt_mutex_lock(&g_mutex); + g_publication_matched_count = (int)status.current_count; + ddsrt_cond_broadcast(&g_cond); + ddsrt_mutex_unlock(&g_mutex); +} + +static void +data_available_cb( + dds_entity_t reader, + void* arg) +{ + (void)arg; + (void)reader; + ddsrt_mutex_lock(&g_mutex); + ddsrt_cond_broadcast(&g_cond); + ddsrt_mutex_unlock(&g_mutex); +} + +void +hello_init(void) +{ + ddsrt_init(); + ddsrt_mutex_init(&g_mutex); + ddsrt_cond_init(&g_cond); +} + +void +hello_fini(void) +{ + ddsrt_cond_destroy(&g_cond); + ddsrt_mutex_destroy(&g_mutex); + ddsrt_fini(); +} + + +/* + * The HelloWorld publisher. + * It waits for a publication matched, and then writes a sample. + * It quits when the publication matched has been reset again. + */ +MPT_ProcessEntry(hello_publisher, + MPT_Args(dds_domainid_t domainid, + const char *topic_name, + int sub_cnt, + const char *text)) +{ + HelloWorldData_Msg msg; + dds_listener_t *listener; + dds_entity_t participant; + dds_entity_t topic; + dds_entity_t writer; + dds_return_t rc; + dds_qos_t *qos; + int id = (int)ddsrt_getpid(); + + assert(topic_name); + assert(text); + + printf("=== [Publisher(%d)] Start(%d) ...\n", id, domainid); + + /* + * A reliable volatile sample, written after publication matched, can still + * be lost when the subscriber wasn't able to match its subscription yet. + * Use transient_local reliable to make sure the sample is received. + */ + qos = dds_create_qos(); + dds_qset_durability(qos, DDS_DURABILITY_TRANSIENT_LOCAL); + dds_qset_reliability(qos, DDS_RELIABILITY_RELIABLE, DDS_SECS(10)); + + /* Use listener to get number of publications matched. */ + listener = dds_create_listener(NULL); + MPT_ASSERT_FATAL_NOT_NULL(listener, "Could not create listener"); + dds_lset_publication_matched(listener, publication_matched_cb); + + /* Create a Writer. */ + participant = dds_create_participant (domainid, NULL, NULL); + MPT_ASSERT_FATAL_GT(participant, 0, "Could not create participant: %s\n", dds_strretcode(-participant)); + + topic = dds_create_topic ( + participant, &HelloWorldData_Msg_desc, topic_name, qos, NULL); + MPT_ASSERT_FATAL_GT(topic, 0, "Could not create topic: %s\n", dds_strretcode(-topic)); + + writer = dds_create_writer (participant, topic, qos, listener); + MPT_ASSERT_FATAL_GT(writer, 0, "Could not create writer: %s\n", dds_strretcode(-writer)); + + /* Wait for expected nr of subscriber(s). */ + ddsrt_mutex_lock(&g_mutex); + while (g_publication_matched_count != sub_cnt) { + ddsrt_cond_waitfor(&g_cond, &g_mutex, DDS_INFINITY); + } + ddsrt_mutex_unlock(&g_mutex); + + /* Write sample. */ + msg.userID = (int32_t)id; + msg.message = (char*)text; + printf("=== [Publisher(%d)] Send: { %d, %s }\n", id, msg.userID, msg.message); + rc = dds_write (writer, &msg); + MPT_ASSERT_EQ(rc, DDS_RETCODE_OK, "Could not write sample\n"); + + /* Wait for subscriber(s) to have finished. */ + ddsrt_mutex_lock(&g_mutex); + while (g_publication_matched_count != 0) { + ddsrt_cond_waitfor(&g_cond, &g_mutex, DDS_INFINITY); + } + ddsrt_mutex_unlock(&g_mutex); + + rc = dds_delete (participant); + MPT_ASSERT_EQ(rc, DDS_RETCODE_OK, "Teardown failed\n"); + + dds_delete_listener(listener); + dds_delete_qos(qos); + + printf("=== [Publisher(%d)] Done\n", id); +} + + +/* + * The HelloWorld subscriber. + * It waits for sample(s) and checks the content. + */ +MPT_ProcessEntry(hello_subscriber, + MPT_Args(dds_domainid_t domainid, + const char *topic_name, + int sample_cnt, + const char *text)) +{ + HelloWorldData_Msg *msg; + void *samples[MAX_SAMPLES]; + dds_sample_info_t infos[MAX_SAMPLES]; + dds_listener_t *listener; + dds_entity_t participant; + dds_entity_t topic; + dds_entity_t reader; + dds_return_t rc; + dds_qos_t *qos; + int recv_cnt; + int id = (int)ddsrt_getpid(); + + assert(topic_name); + assert(text); + + printf("--- [Subscriber(%d)] Start(%d) ...\n", id, domainid); + + /* + * A reliable volatile sample, written after publication matched, can still + * be lost when the subscriber wasn't able to match its subscription yet. + * Use transient_local reliable to make sure the sample is received. + */ + qos = dds_create_qos(); + dds_qset_durability(qos, DDS_DURABILITY_TRANSIENT_LOCAL); + dds_qset_reliability(qos, DDS_RELIABILITY_RELIABLE, DDS_SECS(10)); + + /* Use listener to get data available trigger. */ + listener = dds_create_listener(NULL); + MPT_ASSERT_FATAL_NOT_NULL(listener, "Could not create listener"); + dds_lset_data_available(listener, data_available_cb); + + /* Create a Reader. */ + participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + MPT_ASSERT_FATAL_GT(participant, 0, "Could not create participant: %s\n", dds_strretcode(-participant)); + topic = dds_create_topic ( + participant, &HelloWorldData_Msg_desc, topic_name, qos, NULL); + MPT_ASSERT_FATAL_GT(topic, 0, "Could not create topic: %s\n", dds_strretcode(-topic)); + reader = dds_create_reader (participant, topic, qos, listener); + MPT_ASSERT_FATAL_GT(reader, 0, "Could not create reader: %s\n", dds_strretcode(-reader)); + + printf("--- [Subscriber(%d)] Waiting for %d sample(s) ...\n", id, sample_cnt); + + /* Initialize sample buffer, by pointing the void pointer within + * the buffer array to a valid sample memory location. */ + samples[0] = HelloWorldData_Msg__alloc (); + + /* Wait until expected nr of samples have been taken. */ + ddsrt_mutex_lock(&g_mutex); + recv_cnt = 0; + while (recv_cnt < sample_cnt) { + /* Use a take with mask to work around the #146 issue. */ + rc = dds_take_mask(reader, samples, infos, MAX_SAMPLES, MAX_SAMPLES, DDS_NEW_VIEW_STATE); + MPT_ASSERT_GEQ(rc, 0, "Could not read: %s\n", dds_strretcode(-rc)); + + /* Check if we read some data and it is valid. */ + if ((rc > 0) && (infos[0].valid_data)) { + /* Print Message. */ + msg = (HelloWorldData_Msg*)samples[0]; + printf("--- [Subscriber(%d)] Received: { %d, %s }\n", id, + msg->userID, msg->message); + MPT_ASSERT_STR_EQ(msg->message, text, + "Messages do not match: \"%s\" vs \"%s\"\n", + msg->message, text); + recv_cnt++; + } else { + ddsrt_cond_waitfor(&g_cond, &g_mutex, DDS_INFINITY); + } + } + ddsrt_mutex_unlock(&g_mutex); + + /* Free the data location. */ + HelloWorldData_Msg_free (samples[0], DDS_FREE_ALL); + + rc = dds_delete (participant); + MPT_ASSERT_EQ(rc, DDS_RETCODE_OK, "Teardown failed\n"); + + dds_delete_listener(listener); + dds_delete_qos(qos); + + printf("--- [Subscriber(%d)] Done\n", id); +} diff --git a/src/mpt/tests/basic/procs/hello.h b/src/mpt/tests/basic/procs/hello.h new file mode 100644 index 0000000..b1d1772 --- /dev/null +++ b/src/mpt/tests/basic/procs/hello.h @@ -0,0 +1,52 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited 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 + */ + +/** @file + * + * @brief DDS C Communication Status API + * + * This header file defines the public API of the Communication Status in the + * Eclipse Cyclone DDS C language binding. + */ +#ifndef MPT_BASIC_PROCS_HELLO_H +#define MPT_BASIC_PROCS_HELLO_H + +#include +#include + +#include "dds/dds.h" +#include "mpt/mpt.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +void hello_init(void); +void hello_fini(void); + +MPT_ProcessEntry(hello_publisher, + MPT_Args(dds_domainid_t domainid, + const char *topic_name, + int sub_cnt, + const char *text)); + +MPT_ProcessEntry(hello_subscriber, + MPT_Args(dds_domainid_t domainid, + const char *topic_name, + int sample_cnt, + const char *text)); + +#if defined (__cplusplus) +} +#endif + +#endif /* MPT_BASIC_PROCS_HELLO_H */ diff --git a/src/mpt/tests/basic/procs/helloworlddata.idl b/src/mpt/tests/basic/procs/helloworlddata.idl new file mode 100644 index 0000000..9561d75 --- /dev/null +++ b/src/mpt/tests/basic/procs/helloworlddata.idl @@ -0,0 +1,9 @@ +module HelloWorldData +{ + struct Msg + { + long userID; + string message; + }; + #pragma keylist Msg userID +}; diff --git a/src/mpt/tests/self/CMakeLists.txt b/src/mpt/tests/self/CMakeLists.txt new file mode 100644 index 0000000..8203b5d --- /dev/null +++ b/src/mpt/tests/self/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited 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 +# +include(${MPT_CMAKE}) + +message(STATUS "MPT selftest enabled: some test will fail deliberately") + +set(sources + "asserts.c" + "environments.c" + "fixtures.c" + "ipc.c" + "resources.c" + "usage.c") + +add_mpt_executable(mpt_self ${sources}) + diff --git a/src/mpt/tests/self/asserts.c b/src/mpt/tests/self/asserts.c new file mode 100644 index 0000000..72495a1 --- /dev/null +++ b/src/mpt/tests/self/asserts.c @@ -0,0 +1,711 @@ +#include +#include +#include "mpt/mpt.h" +#include "dds/ddsrt/time.h" + +static int dummy; + + + +/************************************************************ + * Test MPT_ASSERT + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT, MPT_Args(const char *exp, int cond)) +{ + assert(exp); + MPT_ASSERT(cond, "MPT_ASSERT(%d), 1st, expect: %s", cond, exp); + MPT_ASSERT(cond, "MPT_ASSERT(%d), 2nd, expect: %s", cond, exp); +} +MPT_TestProcess(MPT_ASSERT, pass, id, proc_MPT_ASSERT, MPT_ArgValues("PASS", 1)); +MPT_TestProcess(MPT_ASSERT, fail, id, proc_MPT_ASSERT, MPT_ArgValues("FAIL", 0)); +MPT_Test(MPT_ASSERT, pass, .xfail=false); +MPT_Test(MPT_ASSERT, fail, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_FAIL + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_FAIL, MPT_NoArgs()) +{ + MPT_ASSERT_FAIL("MPT_ASSERT_FAIL(), 1st, expect a fail, always"); + MPT_ASSERT_FAIL("MPT_ASSERT_FAIL(), 2nd, expect a fail, always"); +} +MPT_TestProcess(MPT_ASSERT_FAIL, call, id, proc_MPT_ASSERT_FAIL, MPT_NoArgValues()); +MPT_Test(MPT_ASSERT_FAIL, call, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_EQ + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_EQ_int, MPT_Args(const char *exp, int val1, int val2)) +{ + assert(exp); + MPT_ASSERT_EQ(val1, val2, "MPT_ASSERT_EQ(%d, %d), 1st expect: %s", val1, val2, exp); + MPT_ASSERT_EQ(val1, val2, "MPT_ASSERT_EQ(%d, %d), 2nd expect: %s", val1, val2, exp); +} +MPT_TestProcess(MPT_ASSERT_EQ_int, eq, id, proc_MPT_ASSERT_EQ_int, MPT_ArgValues("PASS", 1, 1)); +MPT_TestProcess(MPT_ASSERT_EQ_int, lt, id, proc_MPT_ASSERT_EQ_int, MPT_ArgValues("FAIL", 1, 2)); +MPT_TestProcess(MPT_ASSERT_EQ_int, gt, id, proc_MPT_ASSERT_EQ_int, MPT_ArgValues("FAIL", 3, 2)); +MPT_Test(MPT_ASSERT_EQ_int, eq, .xfail=false); +MPT_Test(MPT_ASSERT_EQ_int, lt, .xfail=true); +MPT_Test(MPT_ASSERT_EQ_int, gt, .xfail=true); + + +MPT_ProcessEntry(proc_MPT_ASSERT_EQ_double, MPT_Args(const char *exp, double val1, double val2)) +{ + assert(exp); + MPT_ASSERT_EQ(val1, val2, "MPT_ASSERT_EQ(%f, %f), 1st expect: %s", val1, val2, exp); + MPT_ASSERT_EQ(val1, val2, "MPT_ASSERT_EQ(%f, %f), 2nd expect: %s", val1, val2, exp); +} +MPT_TestProcess(MPT_ASSERT_EQ_double, eq, id, proc_MPT_ASSERT_EQ_double, MPT_ArgValues("PASS", 1.1, 1.1)); +MPT_TestProcess(MPT_ASSERT_EQ_double, lt, id, proc_MPT_ASSERT_EQ_double, MPT_ArgValues("FAIL", 1.1, 1.2)); +MPT_TestProcess(MPT_ASSERT_EQ_double, gt, id, proc_MPT_ASSERT_EQ_double, MPT_ArgValues("FAIL", 1.3, 1.2)); +MPT_Test(MPT_ASSERT_EQ_double, eq, .xfail=false); +MPT_Test(MPT_ASSERT_EQ_double, lt, .xfail=true); +MPT_Test(MPT_ASSERT_EQ_double, gt, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_NEQ + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_NEQ_int, MPT_Args(const char *exp, int val1, int val2)) +{ + assert(exp); + MPT_ASSERT_NEQ(val1, val2, "MPT_ASSERT_NEQ(%d, %d), 1st expect: %s", val1, val2, exp); + MPT_ASSERT_NEQ(val1, val2, "MPT_ASSERT_NEQ(%d, %d), 2nd expect: %s", val1, val2, exp); +} +MPT_TestProcess(MPT_ASSERT_NEQ_int, eq, id, proc_MPT_ASSERT_NEQ_int, MPT_ArgValues("FAIL", 1, 1)); +MPT_TestProcess(MPT_ASSERT_NEQ_int, lt, id, proc_MPT_ASSERT_NEQ_int, MPT_ArgValues("PASS", 1, 2)); +MPT_TestProcess(MPT_ASSERT_NEQ_int, gt, id, proc_MPT_ASSERT_NEQ_int, MPT_ArgValues("PASS", 3, 2)); +MPT_Test(MPT_ASSERT_NEQ_int, eq, .xfail=true); +MPT_Test(MPT_ASSERT_NEQ_int, lt, .xfail=false); +MPT_Test(MPT_ASSERT_NEQ_int, gt, .xfail=false); + + +MPT_ProcessEntry(proc_MPT_ASSERT_NEQ_double, MPT_Args(const char *exp, double val1, double val2)) +{ + assert(exp); + MPT_ASSERT_NEQ(val1, val2, "MPT_ASSERT_NEQ(%f, %f), 1st expect: %s", val1, val2, exp); + MPT_ASSERT_NEQ(val1, val2, "MPT_ASSERT_NEQ(%f, %f), 2nd expect: %s", val1, val2, exp); +} +MPT_TestProcess(MPT_ASSERT_NEQ_double, eq, id, proc_MPT_ASSERT_NEQ_double, MPT_ArgValues("FAIL", 1.1, 1.1)); +MPT_TestProcess(MPT_ASSERT_NEQ_double, lt, id, proc_MPT_ASSERT_NEQ_double, MPT_ArgValues("PASS", 1.1, 1.2)); +MPT_TestProcess(MPT_ASSERT_NEQ_double, gt, id, proc_MPT_ASSERT_NEQ_double, MPT_ArgValues("PASS", 1.3, 1.2)); +MPT_Test(MPT_ASSERT_NEQ_double, eq, .xfail=true); +MPT_Test(MPT_ASSERT_NEQ_double, lt, .xfail=false); +MPT_Test(MPT_ASSERT_NEQ_double, gt, .xfail=false); + + + +/************************************************************ + * Test MPT_ASSERT_LEQ + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_LEQ_int, MPT_Args(const char *exp, int val1, int val2)) +{ + assert(exp); + MPT_ASSERT_LEQ(val1, val2, "MPT_ASSERT_LEQ(%d, %d), 1st expect: %s", val1, val2, exp); + MPT_ASSERT_LEQ(val1, val2, "MPT_ASSERT_LEQ(%d, %d), 2nd expect: %s", val1, val2, exp); +} +MPT_TestProcess(MPT_ASSERT_LEQ_int, eq, id, proc_MPT_ASSERT_LEQ_int, MPT_ArgValues("PASS", 1, 1)); +MPT_TestProcess(MPT_ASSERT_LEQ_int, lt, id, proc_MPT_ASSERT_LEQ_int, MPT_ArgValues("PASS", 1, 2)); +MPT_TestProcess(MPT_ASSERT_LEQ_int, gt, id, proc_MPT_ASSERT_LEQ_int, MPT_ArgValues("FAIL", 3, 2)); +MPT_Test(MPT_ASSERT_LEQ_int, eq, .xfail=false); +MPT_Test(MPT_ASSERT_LEQ_int, lt, .xfail=false); +MPT_Test(MPT_ASSERT_LEQ_int, gt, .xfail=true); + + +MPT_ProcessEntry(proc_MPT_ASSERT_LEQ_double, MPT_Args(const char *exp, double val1, double val2)) +{ + assert(exp); + MPT_ASSERT_LEQ(val1, val2, "MPT_ASSERT_LEQ(%f, %f), 1st expect: %s", val1, val2, exp); + MPT_ASSERT_LEQ(val1, val2, "MPT_ASSERT_LEQ(%f, %f), 2nd expect: %s", val1, val2, exp); +} +MPT_TestProcess(MPT_ASSERT_LEQ_double, eq, id, proc_MPT_ASSERT_LEQ_double, MPT_ArgValues("PASS", 1.1, 1.1)); +MPT_TestProcess(MPT_ASSERT_LEQ_double, lt, id, proc_MPT_ASSERT_LEQ_double, MPT_ArgValues("PASS", 1.1, 1.2)); +MPT_TestProcess(MPT_ASSERT_LEQ_double, gt, id, proc_MPT_ASSERT_LEQ_double, MPT_ArgValues("FAIL", 1.3, 1.2)); +MPT_Test(MPT_ASSERT_LEQ_double, eq, .xfail=false); +MPT_Test(MPT_ASSERT_LEQ_double, lt, .xfail=false); +MPT_Test(MPT_ASSERT_LEQ_double, gt, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_GEQ + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_GEQ_int, MPT_Args(const char *exp, int val1, int val2)) +{ + assert(exp); + MPT_ASSERT_GEQ(val1, val2, "MPT_ASSERT_GEQ(%d, %d), 1st expect: %s", val1, val2, exp); + MPT_ASSERT_GEQ(val1, val2, "MPT_ASSERT_GEQ(%d, %d), 2nd expect: %s", val1, val2, exp); +} +MPT_TestProcess(MPT_ASSERT_GEQ_int, eq, id, proc_MPT_ASSERT_GEQ_int, MPT_ArgValues("PASS", 1, 1)); +MPT_TestProcess(MPT_ASSERT_GEQ_int, lt, id, proc_MPT_ASSERT_GEQ_int, MPT_ArgValues("FAIL", 1, 2)); +MPT_TestProcess(MPT_ASSERT_GEQ_int, gt, id, proc_MPT_ASSERT_GEQ_int, MPT_ArgValues("PASS", 3, 2)); +MPT_Test(MPT_ASSERT_GEQ_int, eq, .xfail=false); +MPT_Test(MPT_ASSERT_GEQ_int, lt, .xfail=true); +MPT_Test(MPT_ASSERT_GEQ_int, gt, .xfail=false); + + +MPT_ProcessEntry(proc_MPT_ASSERT_GEQ_double, MPT_Args(const char *exp, double val1, double val2)) +{ + assert(exp); + MPT_ASSERT_GEQ(val1, val2, "MPT_ASSERT_GEQ(%f, %f), 1st expect: %s", val1, val2, exp); + MPT_ASSERT_GEQ(val1, val2, "MPT_ASSERT_GEQ(%f, %f), 2nd expect: %s", val1, val2, exp); +} +MPT_TestProcess(MPT_ASSERT_GEQ_double, eq, id, proc_MPT_ASSERT_GEQ_double, MPT_ArgValues("PASS", 1.1, 1.1)); +MPT_TestProcess(MPT_ASSERT_GEQ_double, lt, id, proc_MPT_ASSERT_GEQ_double, MPT_ArgValues("FAIL", 1.1, 1.2)); +MPT_TestProcess(MPT_ASSERT_GEQ_double, gt, id, proc_MPT_ASSERT_GEQ_double, MPT_ArgValues("PASS", 1.3, 1.2)); +MPT_Test(MPT_ASSERT_GEQ_double, eq, .xfail=false); +MPT_Test(MPT_ASSERT_GEQ_double, lt, .xfail=true); +MPT_Test(MPT_ASSERT_GEQ_double, gt, .xfail=false); + + + +/************************************************************ + * Test MPT_ASSERT_LT + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_LT_int, MPT_Args(const char *exp, int val1, int val2)) +{ + assert(exp); + MPT_ASSERT_LT(val1, val2, "MPT_ASSERT_LT(%d, %d), 1st expect: %s", val1, val2, exp); + MPT_ASSERT_LT(val1, val2, "MPT_ASSERT_LT(%d, %d), 2nd expect: %s", val1, val2, exp); +} +MPT_TestProcess(MPT_ASSERT_LT_int, eq, id, proc_MPT_ASSERT_LT_int, MPT_ArgValues("FAIL", 1, 1)); +MPT_TestProcess(MPT_ASSERT_LT_int, lt, id, proc_MPT_ASSERT_LT_int, MPT_ArgValues("PASS", 1, 2)); +MPT_TestProcess(MPT_ASSERT_LT_int, gt, id, proc_MPT_ASSERT_LT_int, MPT_ArgValues("FAIL", 3, 2)); +MPT_Test(MPT_ASSERT_LT_int, eq, .xfail=true); +MPT_Test(MPT_ASSERT_LT_int, lt, .xfail=false); +MPT_Test(MPT_ASSERT_LT_int, gt, .xfail=true); + + +MPT_ProcessEntry(proc_MPT_ASSERT_LT_double, MPT_Args(const char *exp, double val1, double val2)) +{ + assert(exp); + MPT_ASSERT_LT(val1, val2, "MPT_ASSERT_LT(%f, %f), 1st expect: %s", val1, val2, exp); + MPT_ASSERT_LT(val1, val2, "MPT_ASSERT_LT(%f, %f), 2nd expect: %s", val1, val2, exp); +} +MPT_TestProcess(MPT_ASSERT_LT_double, eq, id, proc_MPT_ASSERT_LT_double, MPT_ArgValues("FAIL", 1.1, 1.1)); +MPT_TestProcess(MPT_ASSERT_LT_double, lt, id, proc_MPT_ASSERT_LT_double, MPT_ArgValues("PASS", 1.1, 1.2)); +MPT_TestProcess(MPT_ASSERT_LT_double, gt, id, proc_MPT_ASSERT_LT_double, MPT_ArgValues("FAIL", 1.3, 1.2)); +MPT_Test(MPT_ASSERT_LT_double, eq, .xfail=true); +MPT_Test(MPT_ASSERT_LT_double, lt, .xfail=false); +MPT_Test(MPT_ASSERT_LT_double, gt, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_GT + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_GT_int, MPT_Args(const char *exp, int val1, int val2)) +{ + assert(exp); + MPT_ASSERT_GT(val1, val2, "MPT_ASSERT_GT(%d, %d), 1st expect: %s", val1, val2, exp); + MPT_ASSERT_GT(val1, val2, "MPT_ASSERT_GT(%d, %d), 2nd expect: %s", val1, val2, exp); +} +MPT_TestProcess(MPT_ASSERT_GT_int, eq, id, proc_MPT_ASSERT_GT_int, MPT_ArgValues("FAIL", 1, 1)); +MPT_TestProcess(MPT_ASSERT_GT_int, lt, id, proc_MPT_ASSERT_GT_int, MPT_ArgValues("FAIL", 1, 2)); +MPT_TestProcess(MPT_ASSERT_GT_int, gt, id, proc_MPT_ASSERT_GT_int, MPT_ArgValues("PASS", 3, 2)); +MPT_Test(MPT_ASSERT_GT_int, eq, .xfail=true); +MPT_Test(MPT_ASSERT_GT_int, lt, .xfail=true); +MPT_Test(MPT_ASSERT_GT_int, gt, .xfail=false); + + +MPT_ProcessEntry(proc_MPT_ASSERT_GT_double, MPT_Args(const char *exp, double val1, double val2)) +{ + assert(exp); + MPT_ASSERT_GT(val1, val2, "MPT_ASSERT_GT(%f, %f), 1st expect: %s", val1, val2, exp); + MPT_ASSERT_GT(val1, val2, "MPT_ASSERT_GT(%f, %f), 2nd expect: %s", val1, val2, exp); +} +MPT_TestProcess(MPT_ASSERT_GT_double, eq, id, proc_MPT_ASSERT_GT_double, MPT_ArgValues("FAIL", 1.1, 1.1)); +MPT_TestProcess(MPT_ASSERT_GT_double, lt, id, proc_MPT_ASSERT_GT_double, MPT_ArgValues("FAIL", 1.1, 1.2)); +MPT_TestProcess(MPT_ASSERT_GT_double, gt, id, proc_MPT_ASSERT_GT_double, MPT_ArgValues("PASS", 1.3, 1.2)); +MPT_Test(MPT_ASSERT_GT_double, eq, .xfail=true); +MPT_Test(MPT_ASSERT_GT_double, lt, .xfail=true); +MPT_Test(MPT_ASSERT_GT_double, gt, .xfail=false); + + + +/************************************************************ + * Test MPT_ASSERT_NULL + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_NULL, MPT_Args(const char *exp, const void* ptr)) +{ + assert(exp); + MPT_ASSERT_NULL(ptr, "MPT_ASSERT_NULL(%p), expect: %s", ptr, exp); +} +MPT_TestProcess(MPT_ASSERT_NULL, addr, id, proc_MPT_ASSERT_NULL, MPT_ArgValues("FAIL", &dummy)); +MPT_TestProcess(MPT_ASSERT_NULL, null, id, proc_MPT_ASSERT_NULL, MPT_ArgValues("PASS", NULL)); +MPT_Test(MPT_ASSERT_NULL, addr, .xfail=true); +MPT_Test(MPT_ASSERT_NULL, null, .xfail=false); + + + +/************************************************************ + * Test MPT_ASSERT_NOT_NULL + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_NOT_NULL, MPT_Args(const char *exp, const void* ptr)) +{ + assert(exp); + MPT_ASSERT_NOT_NULL(ptr, "MPT_ASSERT_NOT_NULL(%p), expect: %s", ptr, exp); +} +MPT_TestProcess(MPT_ASSERT_NOT_NULL, addr, id, proc_MPT_ASSERT_NOT_NULL, MPT_ArgValues("PASS", &dummy)); +MPT_TestProcess(MPT_ASSERT_NOT_NULL, null, id, proc_MPT_ASSERT_NOT_NULL, MPT_ArgValues("FAIL", NULL)); +MPT_Test(MPT_ASSERT_NOT_NULL, addr, .xfail=false); +MPT_Test(MPT_ASSERT_NOT_NULL, null, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_STR_EQ + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_STR_EQ, MPT_Args(const char *exp, const char* val1, const char* val2)) +{ + assert(exp); + MPT_ASSERT_STR_EQ(val1, val2, "MPT_ASSERT_STR_EQ(%s, %s), expect: %s", + val1 ? val1 : "", + val2 ? val2 : "", + exp); +} +MPT_TestProcess(MPT_ASSERT_STR_EQ, eq, id, proc_MPT_ASSERT_STR_EQ, MPT_ArgValues("PASS", "foo", "foo")); +MPT_TestProcess(MPT_ASSERT_STR_EQ, neq, id, proc_MPT_ASSERT_STR_EQ, MPT_ArgValues("FAIL", "foo", "bar")); +MPT_TestProcess(MPT_ASSERT_STR_EQ, null1, id, proc_MPT_ASSERT_STR_EQ, MPT_ArgValues("FAIL", NULL, "foo")); +MPT_TestProcess(MPT_ASSERT_STR_EQ, null2, id, proc_MPT_ASSERT_STR_EQ, MPT_ArgValues("FAIL", "foo", NULL)); +MPT_Test(MPT_ASSERT_STR_EQ, eq, .xfail=false); +MPT_Test(MPT_ASSERT_STR_EQ, neq, .xfail=true); +MPT_Test(MPT_ASSERT_STR_EQ, null1, .xfail=true); +MPT_Test(MPT_ASSERT_STR_EQ, null2, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_STR_NEQ + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_STR_NEQ, MPT_Args(const char *exp, const char* val1, const char* val2)) +{ + assert(exp); + MPT_ASSERT_STR_NEQ(val1, val2, "MPT_ASSERT_STR_NEQ(%s, %s), expect: %s", + val1 ? val1 : "", + val2 ? val2 : "", + exp); +} +MPT_TestProcess(MPT_ASSERT_STR_NEQ, eq, id, proc_MPT_ASSERT_STR_NEQ, MPT_ArgValues("FAIL", "foo", "foo")); +MPT_TestProcess(MPT_ASSERT_STR_NEQ, neq, id, proc_MPT_ASSERT_STR_NEQ, MPT_ArgValues("PASS", "foo", "bar")); +MPT_TestProcess(MPT_ASSERT_STR_NEQ, null1, id, proc_MPT_ASSERT_STR_NEQ, MPT_ArgValues("FAIL", NULL, "foo")); +MPT_TestProcess(MPT_ASSERT_STR_NEQ, null2, id, proc_MPT_ASSERT_STR_NEQ, MPT_ArgValues("FAIL", "foo", NULL)); +MPT_Test(MPT_ASSERT_STR_NEQ, eq, .xfail=true); +MPT_Test(MPT_ASSERT_STR_NEQ, neq, .xfail=false); +MPT_Test(MPT_ASSERT_STR_NEQ, null1, .xfail=true); +MPT_Test(MPT_ASSERT_STR_NEQ, null2, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_STR_EMPTY + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_STR_EMPTY, MPT_Args(const char *exp, const char* val)) +{ + assert(exp); + MPT_ASSERT_STR_EMPTY(val, "MPT_ASSERT_STR_EMPTY(%s), expect: %s", + val ? val : "", + exp); +} +MPT_TestProcess(MPT_ASSERT_STR_EMPTY, nempty, id, proc_MPT_ASSERT_STR_EMPTY, MPT_ArgValues("FAIL", "foo")); +MPT_TestProcess(MPT_ASSERT_STR_EMPTY, empty, id, proc_MPT_ASSERT_STR_EMPTY, MPT_ArgValues("PASS", "")); +MPT_TestProcess(MPT_ASSERT_STR_EMPTY, null, id, proc_MPT_ASSERT_STR_EMPTY, MPT_ArgValues("FAIL", NULL)); +MPT_Test(MPT_ASSERT_STR_EMPTY, nempty, .xfail=true); +MPT_Test(MPT_ASSERT_STR_EMPTY, empty, .xfail=false); +MPT_Test(MPT_ASSERT_STR_EMPTY, null, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_STR_NOT_EMPTY + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_STR_NOT_EMPTY, MPT_Args(const char *exp, const char* val)) +{ + assert(exp); + MPT_ASSERT_STR_NOT_EMPTY(val, "MPT_ASSERT_STR_NOT_EMPTY(%s), expect: %s", + val ? val : "", + exp); +} +MPT_TestProcess(MPT_ASSERT_STR_NOT_EMPTY, nempty, id, proc_MPT_ASSERT_STR_NOT_EMPTY, MPT_ArgValues("PASS", "foo")); +MPT_TestProcess(MPT_ASSERT_STR_NOT_EMPTY, empty, id, proc_MPT_ASSERT_STR_NOT_EMPTY, MPT_ArgValues("FAIL", "")); +MPT_TestProcess(MPT_ASSERT_STR_NOT_EMPTY, null, id, proc_MPT_ASSERT_STR_NOT_EMPTY, MPT_ArgValues("FAIL", NULL)); +MPT_Test(MPT_ASSERT_STR_NOT_EMPTY, nempty, .xfail=false); +MPT_Test(MPT_ASSERT_STR_NOT_EMPTY, empty, .xfail=true); +MPT_Test(MPT_ASSERT_STR_NOT_EMPTY, null, .xfail=true); + + + +/*****************************************************************************/ + + + +/************************************************************ + * Test MPT_ASSERT_FATAL + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL, MPT_Args(const char *exp, int cond)) +{ + assert(exp); + MPT_ASSERT_FATAL(cond, "MPT_ASSERT_FATAL(%d), expect: %s", cond, exp); + MPT_ASSERT(cond, "MPT_ASSERT(%d) after a fatal", cond); +} + +MPT_TestProcess(MPT_ASSERT_FATAL, pass, id, proc_MPT_ASSERT_FATAL, MPT_ArgValues("PASS", 1)); +MPT_Test(MPT_ASSERT_FATAL, pass, .xfail=false); + +MPT_TestProcess(MPT_ASSERT_FATAL, fail, id, proc_MPT_ASSERT_FATAL, MPT_ArgValues("FAIL", 0)); +MPT_Test(MPT_ASSERT_FATAL, fail, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_FATAL_FAIL + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_FAIL, MPT_NoArgs()) +{ + MPT_ASSERT_FATAL_FAIL("MPT_ASSERT_FATAL_FAIL(), expect a fail, always"); + MPT_ASSERT_FAIL("MPT_ASSERT_FAIL() after a fatal"); +} + +MPT_TestProcess(MPT_ASSERT_FATAL_FAIL, fail, id, proc_MPT_ASSERT_FATAL_FAIL, MPT_NoArgValues()); +MPT_Test(MPT_ASSERT_FATAL_FAIL, fail, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_FATAL_EQ + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_EQ_int, MPT_Args(const char *exp, int val1, int val2)) +{ + assert(exp); + MPT_ASSERT_FATAL_EQ(val1, val2, "MPT_ASSERT_FATAL_EQ(%d, %d), expect: %s", val1, val2, exp); + MPT_ASSERT_EQ(val1, val2, "MPT_ASSERT_EQ(%d, %d) after a fatal", val1, val2); +} +MPT_TestProcess(MPT_ASSERT_FATAL_EQ_int, eq, id, proc_MPT_ASSERT_FATAL_EQ_int, MPT_ArgValues("PASS", 1, 1)); +MPT_TestProcess(MPT_ASSERT_FATAL_EQ_int, lt, id, proc_MPT_ASSERT_FATAL_EQ_int, MPT_ArgValues("FAIL", 1, 2)); +MPT_TestProcess(MPT_ASSERT_FATAL_EQ_int, gt, id, proc_MPT_ASSERT_FATAL_EQ_int, MPT_ArgValues("FAIL", 3, 2)); +MPT_Test(MPT_ASSERT_FATAL_EQ_int, eq, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_EQ_int, lt, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_EQ_int, gt, .xfail=true); + + +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_EQ_double, MPT_Args(const char *exp, double val1, double val2)) +{ + assert(exp); + MPT_ASSERT_FATAL_EQ(val1, val2, "MPT_ASSERT_FATAL_EQ(%f, %f), expect: %s", val1, val2, exp); + MPT_ASSERT_EQ(val1, val2, "MPT_ASSERT_EQ(%f, %f) after a fatal", val1, val2); +} +MPT_TestProcess(MPT_ASSERT_FATAL_EQ_double, eq, id, proc_MPT_ASSERT_FATAL_EQ_double, MPT_ArgValues("PASS", 1.1, 1.1)); +MPT_TestProcess(MPT_ASSERT_FATAL_EQ_double, lt, id, proc_MPT_ASSERT_FATAL_EQ_double, MPT_ArgValues("FAIL", 1.1, 1.2)); +MPT_TestProcess(MPT_ASSERT_FATAL_EQ_double, gt, id, proc_MPT_ASSERT_FATAL_EQ_double, MPT_ArgValues("FAIL", 1.3, 1.2)); +MPT_Test(MPT_ASSERT_FATAL_EQ_double, eq, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_EQ_double, lt, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_EQ_double, gt, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_FATAL_NEQ + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_NEQ_int, MPT_Args(const char *exp, int val1, int val2)) +{ + assert(exp); + MPT_ASSERT_FATAL_NEQ(val1, val2, "MPT_ASSERT_FATAL_NEQ(%d, %d), expect: %s", val1, val2, exp); + MPT_ASSERT_NEQ(val1, val2, "MPT_ASSERT_NEQ(%d, %d) after a fatal", val1, val2); +} +MPT_TestProcess(MPT_ASSERT_FATAL_NEQ_int, eq, id, proc_MPT_ASSERT_FATAL_NEQ_int, MPT_ArgValues("FAIL", 1, 1)); +MPT_TestProcess(MPT_ASSERT_FATAL_NEQ_int, lt, id, proc_MPT_ASSERT_FATAL_NEQ_int, MPT_ArgValues("PASS", 1, 2)); +MPT_TestProcess(MPT_ASSERT_FATAL_NEQ_int, gt, id, proc_MPT_ASSERT_FATAL_NEQ_int, MPT_ArgValues("PASS", 3, 2)); +MPT_Test(MPT_ASSERT_FATAL_NEQ_int, eq, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_NEQ_int, lt, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_NEQ_int, gt, .xfail=false); + + +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_NEQ_double, MPT_Args(const char *exp, double val1, double val2)) +{ + assert(exp); + MPT_ASSERT_FATAL_NEQ(val1, val2, "MPT_ASSERT_FATAL_NEQ(%f, %f), expect: %s", val1, val2, exp); + MPT_ASSERT_NEQ(val1, val2, "MPT_ASSERT_NEQ(%f, %f) after a fatal", val1, val2); +} +MPT_TestProcess(MPT_ASSERT_FATAL_NEQ_double, eq, id, proc_MPT_ASSERT_FATAL_NEQ_double, MPT_ArgValues("FAIL", 1.1, 1.1)); +MPT_TestProcess(MPT_ASSERT_FATAL_NEQ_double, lt, id, proc_MPT_ASSERT_FATAL_NEQ_double, MPT_ArgValues("PASS", 1.1, 1.2)); +MPT_TestProcess(MPT_ASSERT_FATAL_NEQ_double, gt, id, proc_MPT_ASSERT_FATAL_NEQ_double, MPT_ArgValues("PASS", 1.3, 1.2)); +MPT_Test(MPT_ASSERT_FATAL_NEQ_double, eq, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_NEQ_double, lt, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_NEQ_double, gt, .xfail=false); + + + +/************************************************************ + * Test MPT_ASSERT_FATAL_LEQ + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_LEQ_int, MPT_Args(const char *exp, int val1, int val2)) +{ + assert(exp); + MPT_ASSERT_FATAL_LEQ(val1, val2, "MPT_ASSERT_FATAL_LEQ(%d, %d), expect: %s", val1, val2, exp); + MPT_ASSERT_LEQ(val1, val2, "MPT_ASSERT_LEQ(%d, %d) after a fatal", val1, val2); +} +MPT_TestProcess(MPT_ASSERT_FATAL_LEQ_int, eq, id, proc_MPT_ASSERT_FATAL_LEQ_int, MPT_ArgValues("PASS", 1, 1)); +MPT_TestProcess(MPT_ASSERT_FATAL_LEQ_int, lt, id, proc_MPT_ASSERT_FATAL_LEQ_int, MPT_ArgValues("PASS", 1, 2)); +MPT_TestProcess(MPT_ASSERT_FATAL_LEQ_int, gt, id, proc_MPT_ASSERT_FATAL_LEQ_int, MPT_ArgValues("FAIL", 3, 2)); +MPT_Test(MPT_ASSERT_FATAL_LEQ_int, eq, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_LEQ_int, lt, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_LEQ_int, gt, .xfail=true); + + +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_LEQ_double, MPT_Args(const char *exp, double val1, double val2)) +{ + assert(exp); + MPT_ASSERT_FATAL_LEQ(val1, val2, "MPT_ASSERT_FATAL_LEQ(%f, %f), expect: %s", val1, val2, exp); + MPT_ASSERT_LEQ(val1, val2, "MPT_ASSERT_LEQ(%f, %f) after a fatal", val1, val2); +} +MPT_TestProcess(MPT_ASSERT_FATAL_LEQ_double, eq, id, proc_MPT_ASSERT_FATAL_LEQ_double, MPT_ArgValues("PASS", 1.1, 1.1)); +MPT_TestProcess(MPT_ASSERT_FATAL_LEQ_double, lt, id, proc_MPT_ASSERT_FATAL_LEQ_double, MPT_ArgValues("PASS", 1.1, 1.2)); +MPT_TestProcess(MPT_ASSERT_FATAL_LEQ_double, gt, id, proc_MPT_ASSERT_FATAL_LEQ_double, MPT_ArgValues("FAIL", 1.3, 1.2)); +MPT_Test(MPT_ASSERT_FATAL_LEQ_double, eq, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_LEQ_double, lt, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_LEQ_double, gt, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_FATAL_GEQ + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_GEQ_int, MPT_Args(const char *exp, int val1, int val2)) +{ + assert(exp); + MPT_ASSERT_FATAL_GEQ(val1, val2, "MPT_ASSERT_FATAL_GEQ(%d, %d), expect: %s", val1, val2, exp); + MPT_ASSERT_GEQ(val1, val2, "MPT_ASSERT_GEQ(%d, %d) after a fatal", val1, val2); +} +MPT_TestProcess(MPT_ASSERT_FATAL_GEQ_int, eq, id, proc_MPT_ASSERT_FATAL_GEQ_int, MPT_ArgValues("PASS", 1, 1)); +MPT_TestProcess(MPT_ASSERT_FATAL_GEQ_int, lt, id, proc_MPT_ASSERT_FATAL_GEQ_int, MPT_ArgValues("FAIL", 1, 2)); +MPT_TestProcess(MPT_ASSERT_FATAL_GEQ_int, gt, id, proc_MPT_ASSERT_FATAL_GEQ_int, MPT_ArgValues("PASS", 3, 2)); +MPT_Test(MPT_ASSERT_FATAL_GEQ_int, eq, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_GEQ_int, lt, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_GEQ_int, gt, .xfail=false); + + +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_GEQ_double, MPT_Args(const char *exp, double val1, double val2)) +{ + assert(exp); + MPT_ASSERT_FATAL_GEQ(val1, val2, "MPT_ASSERT_FATAL_GEQ(%f, %f), expect: %s", val1, val2, exp); + MPT_ASSERT_GEQ(val1, val2, "MPT_ASSERT_GEQ(%f, %f) after a fatal", val1, val2); +} +MPT_TestProcess(MPT_ASSERT_FATAL_GEQ_double, eq, id, proc_MPT_ASSERT_FATAL_GEQ_double, MPT_ArgValues("PASS", 1.1, 1.1)); +MPT_TestProcess(MPT_ASSERT_FATAL_GEQ_double, lt, id, proc_MPT_ASSERT_FATAL_GEQ_double, MPT_ArgValues("FAIL", 1.1, 1.2)); +MPT_TestProcess(MPT_ASSERT_FATAL_GEQ_double, gt, id, proc_MPT_ASSERT_FATAL_GEQ_double, MPT_ArgValues("PASS", 1.3, 1.2)); +MPT_Test(MPT_ASSERT_FATAL_GEQ_double, eq, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_GEQ_double, lt, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_GEQ_double, gt, .xfail=false); + + + +/************************************************************ + * Test MPT_ASSERT_FATAL_LT + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_LT_int, MPT_Args(const char *exp, int val1, int val2)) +{ + assert(exp); + MPT_ASSERT_FATAL_LT(val1, val2, "MPT_ASSERT_FATAL_LT(%d, %d), expect: %s", val1, val2, exp); + MPT_ASSERT_LT(val1, val2, "MPT_ASSERT_LT(%d, %d) after a fatal", val1, val2); +} +MPT_TestProcess(MPT_ASSERT_FATAL_LT_int, eq, id, proc_MPT_ASSERT_FATAL_LT_int, MPT_ArgValues("FAIL", 1, 1)); +MPT_TestProcess(MPT_ASSERT_FATAL_LT_int, lt, id, proc_MPT_ASSERT_FATAL_LT_int, MPT_ArgValues("PASS", 1, 2)); +MPT_TestProcess(MPT_ASSERT_FATAL_LT_int, gt, id, proc_MPT_ASSERT_FATAL_LT_int, MPT_ArgValues("FAIL", 3, 2)); +MPT_Test(MPT_ASSERT_FATAL_LT_int, eq, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_LT_int, lt, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_LT_int, gt, .xfail=true); + + +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_LT_double, MPT_Args(const char *exp, double val1, double val2)) +{ + assert(exp); + MPT_ASSERT_FATAL_LT(val1, val2, "MPT_ASSERT_FATAL_LT(%f, %f), expect: %s", val1, val2, exp); + MPT_ASSERT_LT(val1, val2, "MPT_ASSERT_LT(%f, %f) after a fatal", val1, val2); +} +MPT_TestProcess(MPT_ASSERT_FATAL_LT_double, eq, id, proc_MPT_ASSERT_FATAL_LT_double, MPT_ArgValues("FAIL", 1.1, 1.1)); +MPT_TestProcess(MPT_ASSERT_FATAL_LT_double, lt, id, proc_MPT_ASSERT_FATAL_LT_double, MPT_ArgValues("PASS", 1.1, 1.2)); +MPT_TestProcess(MPT_ASSERT_FATAL_LT_double, gt, id, proc_MPT_ASSERT_FATAL_LT_double, MPT_ArgValues("FAIL", 1.3, 1.2)); +MPT_Test(MPT_ASSERT_FATAL_LT_double, eq, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_LT_double, lt, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_LT_double, gt, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_FATAL_GT + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_GT_int, MPT_Args(const char *exp, int val1, int val2)) +{ + assert(exp); + MPT_ASSERT_FATAL_GT(val1, val2, "MPT_ASSERT_FATAL_GT(%d, %d), expect: %s", val1, val2, exp); + MPT_ASSERT_GT(val1, val2, "MPT_ASSERT_GT(%d, %d) after a fatal", val1, val2); +} +MPT_TestProcess(MPT_ASSERT_FATAL_GT_int, eq, id, proc_MPT_ASSERT_FATAL_GT_int, MPT_ArgValues("FAIL", 1, 1)); +MPT_TestProcess(MPT_ASSERT_FATAL_GT_int, lt, id, proc_MPT_ASSERT_FATAL_GT_int, MPT_ArgValues("FAIL", 1, 2)); +MPT_TestProcess(MPT_ASSERT_FATAL_GT_int, gt, id, proc_MPT_ASSERT_FATAL_GT_int, MPT_ArgValues("PASS", 3, 2)); +MPT_Test(MPT_ASSERT_FATAL_GT_int, eq, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_GT_int, lt, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_GT_int, gt, .xfail=false); + + +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_GT_double, MPT_Args(const char *exp, double val1, double val2)) +{ + assert(exp); + MPT_ASSERT_FATAL_GT(val1, val2, "MPT_ASSERT_FATAL_GT(%f, %f), expect: %s", val1, val2, exp); + MPT_ASSERT_GT(val1, val2, "MPT_ASSERT_GT(%f, %f) after a fatal", val1, val2); +} +MPT_TestProcess(MPT_ASSERT_FATAL_GT_double, eq, id, proc_MPT_ASSERT_FATAL_GT_double, MPT_ArgValues("FAIL", 1.1, 1.1)); +MPT_TestProcess(MPT_ASSERT_FATAL_GT_double, lt, id, proc_MPT_ASSERT_FATAL_GT_double, MPT_ArgValues("FAIL", 1.1, 1.2)); +MPT_TestProcess(MPT_ASSERT_FATAL_GT_double, gt, id, proc_MPT_ASSERT_FATAL_GT_double, MPT_ArgValues("PASS", 1.3, 1.2)); +MPT_Test(MPT_ASSERT_FATAL_GT_double, eq, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_GT_double, lt, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_GT_double, gt, .xfail=false); + + + +/************************************************************ + * Test MPT_ASSERT_FATAL_NULL + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_NULL, MPT_Args(const char *exp, const void* ptr)) +{ + assert(exp); + MPT_ASSERT_FATAL_NULL(ptr, "MPT_ASSERT_FATAL_NULL(%p), expect: %s", ptr, exp); +} +MPT_TestProcess(MPT_ASSERT_FATAL_NULL, addr, id, proc_MPT_ASSERT_FATAL_NULL, MPT_ArgValues("FAIL", &dummy)); +MPT_TestProcess(MPT_ASSERT_FATAL_NULL, null, id, proc_MPT_ASSERT_FATAL_NULL, MPT_ArgValues("PASS", NULL)); +MPT_Test(MPT_ASSERT_FATAL_NULL, addr, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_NULL, null, .xfail=false); + + + +/************************************************************ + * Test MPT_ASSERT_FATAL_NOT_NULL + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_NOT_NULL, MPT_Args(const char *exp, const void* ptr)) +{ + assert(exp); + MPT_ASSERT_FATAL_NOT_NULL(ptr, "MPT_ASSERT_FATAL_NOT_NULL(%p), expect: %s", ptr, exp); +} +MPT_TestProcess(MPT_ASSERT_FATAL_NOT_NULL, addr, id, proc_MPT_ASSERT_FATAL_NOT_NULL, MPT_ArgValues("PASS", &dummy)); +MPT_TestProcess(MPT_ASSERT_FATAL_NOT_NULL, null, id, proc_MPT_ASSERT_FATAL_NOT_NULL, MPT_ArgValues("FAIL", NULL)); +MPT_Test(MPT_ASSERT_FATAL_NOT_NULL, addr, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_NOT_NULL, null, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_FATAL_STR_EQ + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_STR_EQ, MPT_Args(const char *exp, const char* val1, const char* val2)) +{ + assert(exp); + MPT_ASSERT_FATAL_STR_EQ(val1, val2, "MPT_ASSERT_FATAL_STR_EQ(%s, %s), expect: %s", + val1 ? val1 : "", + val2 ? val2 : "", + exp); +} +MPT_TestProcess(MPT_ASSERT_FATAL_STR_EQ, eq, id, proc_MPT_ASSERT_FATAL_STR_EQ, MPT_ArgValues("PASS", "foo", "foo")); +MPT_TestProcess(MPT_ASSERT_FATAL_STR_EQ, neq, id, proc_MPT_ASSERT_FATAL_STR_EQ, MPT_ArgValues("FAIL", "foo", "bar")); +MPT_TestProcess(MPT_ASSERT_FATAL_STR_EQ, null1, id, proc_MPT_ASSERT_FATAL_STR_EQ, MPT_ArgValues("FAIL", NULL, "foo")); +MPT_TestProcess(MPT_ASSERT_FATAL_STR_EQ, null2, id, proc_MPT_ASSERT_FATAL_STR_EQ, MPT_ArgValues("FAIL", "foo", NULL)); +MPT_Test(MPT_ASSERT_FATAL_STR_EQ, eq, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_STR_EQ, neq, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_STR_EQ, null1, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_STR_EQ, null2, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_FATAL_STR_NEQ + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_STR_NEQ, MPT_Args(const char *exp, const char* val1, const char* val2)) +{ + assert(exp); + MPT_ASSERT_FATAL_STR_NEQ(val1, val2, "MPT_ASSERT_FATAL_STR_NEQ(%s, %s), expect: %s", + val1 ? val1 : "", + val2 ? val2 : "", + exp); +} +MPT_TestProcess(MPT_ASSERT_FATAL_STR_NEQ, eq, id, proc_MPT_ASSERT_FATAL_STR_NEQ, MPT_ArgValues("FAIL", "foo", "foo")); +MPT_TestProcess(MPT_ASSERT_FATAL_STR_NEQ, neq, id, proc_MPT_ASSERT_FATAL_STR_NEQ, MPT_ArgValues("PASS", "foo", "bar")); +MPT_TestProcess(MPT_ASSERT_FATAL_STR_NEQ, null1, id, proc_MPT_ASSERT_FATAL_STR_NEQ, MPT_ArgValues("FAIL", NULL, "foo")); +MPT_TestProcess(MPT_ASSERT_FATAL_STR_NEQ, null2, id, proc_MPT_ASSERT_FATAL_STR_NEQ, MPT_ArgValues("FAIL", "foo", NULL)); +MPT_Test(MPT_ASSERT_FATAL_STR_NEQ, eq, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_STR_NEQ, neq, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_STR_NEQ, null1, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_STR_NEQ, null2, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_FATAL_STR_EMPTY + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_STR_EMPTY, MPT_Args(const char *exp, const char* val)) +{ + assert(exp); + MPT_ASSERT_FATAL_STR_EMPTY(val, "MPT_ASSERT_FATAL_STR_EMPTY(%s), expect: %s", + val ? val : "", + exp); +} +MPT_TestProcess(MPT_ASSERT_FATAL_STR_EMPTY, nempty, id, proc_MPT_ASSERT_FATAL_STR_EMPTY, MPT_ArgValues("FAIL", "foo")); +MPT_TestProcess(MPT_ASSERT_FATAL_STR_EMPTY, empty, id, proc_MPT_ASSERT_FATAL_STR_EMPTY, MPT_ArgValues("PASS", "")); +MPT_TestProcess(MPT_ASSERT_FATAL_STR_EMPTY, null, id, proc_MPT_ASSERT_FATAL_STR_EMPTY, MPT_ArgValues("FAIL", NULL)); +MPT_Test(MPT_ASSERT_FATAL_STR_EMPTY, nempty, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_STR_EMPTY, empty, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_STR_EMPTY, null, .xfail=true); + + + +/************************************************************ + * Test MPT_ASSERT_FATAL_STR_NOT_EMPTY + ************************************************************/ +MPT_ProcessEntry(proc_MPT_ASSERT_FATAL_STR_NOT_EMPTY, MPT_Args(const char *exp, const char* val)) +{ + assert(exp); + MPT_ASSERT_FATAL_STR_NOT_EMPTY(val, "MPT_ASSERT_FATAL_STR_NOT_EMPTY(%s), expect: %s", + val ? val : "", + exp); +} +MPT_TestProcess(MPT_ASSERT_FATAL_STR_NOT_EMPTY, nempty, id, proc_MPT_ASSERT_FATAL_STR_NOT_EMPTY, MPT_ArgValues("PASS", "foo")); +MPT_TestProcess(MPT_ASSERT_FATAL_STR_NOT_EMPTY, empty, id, proc_MPT_ASSERT_FATAL_STR_NOT_EMPTY, MPT_ArgValues("FAIL", "")); +MPT_TestProcess(MPT_ASSERT_FATAL_STR_NOT_EMPTY, null, id, proc_MPT_ASSERT_FATAL_STR_NOT_EMPTY, MPT_ArgValues("FAIL", NULL)); +MPT_Test(MPT_ASSERT_FATAL_STR_NOT_EMPTY, nempty, .xfail=false); +MPT_Test(MPT_ASSERT_FATAL_STR_NOT_EMPTY, empty, .xfail=true); +MPT_Test(MPT_ASSERT_FATAL_STR_NOT_EMPTY, null, .xfail=true); + + + +/*****************************************************************************/ + + + +/************************************************************ + * Test propagation, + * Check if failure/success is actually propagated to ctest. + ************************************************************/ +MPT_ProcessEntry(proc_propagation, MPT_Args(const char *exp, int cond, dds_duration_t delay)) +{ + assert(exp); + if (delay > 0) { + dds_sleepfor(delay); + } + MPT_ASSERT(cond, "MPT_ASSERT(%d), expect: %s", cond, exp); +} +/* This should pass in the ctest results. */ +MPT_TestProcess(propagation, pass, id1, proc_propagation, MPT_ArgValues("PASS", 1, 0)); +MPT_TestProcess(propagation, pass, id2, proc_propagation, MPT_ArgValues("PASS", 1, 0)); +MPT_Test(propagation, pass); +/* This should fail in the ctest results. */ +MPT_TestProcess(propagation, fail_1st, id1, proc_propagation, MPT_ArgValues("FAIL", 0, 0)); +MPT_TestProcess(propagation, fail_1st, id2, proc_propagation, MPT_ArgValues("PASS", 1, DDS_SECS(1))); +MPT_Test(propagation, fail_1st); +/* This should fail in the ctest results. */ +MPT_TestProcess(propagation, fail_2nd, id1, proc_propagation, MPT_ArgValues("PASS", 1, 0)); +MPT_TestProcess(propagation, fail_2nd, id2, proc_propagation, MPT_ArgValues("FAIL", 0, DDS_SECS(1))); +MPT_Test(propagation, fail_2nd); + diff --git a/src/mpt/tests/self/environments.c b/src/mpt/tests/self/environments.c new file mode 100644 index 0000000..9b6dc97 --- /dev/null +++ b/src/mpt/tests/self/environments.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include "mpt/mpt.h" +#include "mpt/resource.h" /* MPT_SOURCE_ROOT_DIR */ +#include "dds/ddsrt/environ.h" + + + +/************************************************************ + * Process + ************************************************************/ +MPT_ProcessEntry(proc_environments, MPT_Args(mpt_env_t *exp)) +{ + assert(exp); + while ((exp->name != NULL) && (exp->value != NULL)) { + /* The environment variable value should match the expected value. */ + char *ptr = NULL; + ddsrt_getenv(exp->name, &ptr); + if (ptr) { + MPT_ASSERT((strcmp(exp->value, ptr) == 0), "%s: found \"%s\", expected \"%s\"", exp->name, ptr, exp->value); + } else { + MPT_ASSERT(0, "Expected \"%s\" not found in environment", exp->name); + } + exp++; + } +} + + + +/************************************************************ + * Basic environment tests + ************************************************************/ +static mpt_env_t environ_basic[] = { + { "MY_ENV_VAR1", "1" }, + { "MY_ENV_VAR2", "2" }, + { NULL, NULL } +}; + +MPT_TestProcess(environment, proc, id, proc_environments, MPT_ArgValues(environ_basic), .environment=environ_basic); +MPT_Test(environment, proc); + +MPT_TestProcess(environment, test, id, proc_environments, MPT_ArgValues(environ_basic)); +MPT_Test(environment, test, .environment=environ_basic); + + + +/************************************************************ + * Expanding variables environment tests + ************************************************************/ +static mpt_env_t environ_expand[] = { + { "1", "b" }, + { "2", "${1}l" }, + { "3", "${2}aat" }, + { "4", "bla${1}${2}a${3}" }, + { NULL, NULL } +}; +static mpt_env_t expect_expand[] = { + { "1", "b" }, + { "2", "bl" }, + { "3", "blaat" }, + { "4", "blabblablaat" }, + { NULL, NULL } +}; + +MPT_TestProcess(environment, expand_proc, id, proc_environments, MPT_ArgValues(expect_expand), .environment=environ_expand); +MPT_Test(environment, expand_proc); + +MPT_TestProcess(environment, expand_test, id, proc_environments, MPT_ArgValues(expect_expand)); +MPT_Test(environment, expand_test, .environment=environ_expand); + + + +/************************************************************ + * Environment inheritance test + ************************************************************/ +static mpt_env_t environ_test[] = { + { "ETC_DIR", MPT_SOURCE_ROOT_DIR"/tests/self/etc" }, + { "OVERRULE", "NO" }, + { NULL, NULL } +}; +static mpt_env_t environ_proc[] = { + { "CYCLONE_URI", "file://${ETC_DIR}/ospl.xml" }, + { "OVERRULE", "YES" }, + { "EXTRA", "proc" }, + { NULL, NULL } +}; +static mpt_env_t environ_test_proc[] = { + { "ETC_DIR", MPT_SOURCE_ROOT_DIR"/tests/self/etc" }, + { "CYCLONE_URI", "file://"MPT_SOURCE_ROOT_DIR"/tests/self/etc/ospl.xml" }, + { "OVERRULE", "YES" }, + { "EXTRA", "proc" }, + { NULL, NULL } +}; +MPT_TestProcess(environment, inheritance, id, proc_environments, MPT_ArgValues(environ_test_proc), .environment=environ_proc); +MPT_Test(environment, inheritance, .environment=environ_test); diff --git a/src/mpt/tests/self/etc/file b/src/mpt/tests/self/etc/file new file mode 100644 index 0000000..e69de29 diff --git a/src/mpt/tests/self/fixtures.c b/src/mpt/tests/self/fixtures.c new file mode 100644 index 0000000..25b5782 --- /dev/null +++ b/src/mpt/tests/self/fixtures.c @@ -0,0 +1,72 @@ +#include +#include "mpt/mpt.h" +#include "mpt/resource.h" /* MPT_SOURCE_ROOT_DIR */ +#include "dds/ddsrt/time.h" + + + +/************************************************************ + * Support functions + ************************************************************/ +static int g_initcnt = 0; +static void init_inc(void) +{ + g_initcnt++; +}; + + + +/************************************************************ + * Processes + ************************************************************/ +MPT_ProcessEntry(proc_initcnt, MPT_Args(int init)) +{ + MPT_ASSERT((g_initcnt == init), "init count: %d vs %d", g_initcnt, init); +} + +MPT_ProcessEntry(proc_sleep, MPT_Args(dds_duration_t delay)) +{ + MPT_ASSERT((delay > 0), "basically just to satisfy the compiler"); + dds_sleepfor(delay); +} + + + +/************************************************************ + * Init fixture tests + ************************************************************/ +MPT_TestProcess(init, none, id, proc_initcnt, MPT_ArgValues(0)); +MPT_Test(init, none); + +MPT_TestProcess(init, null, id, proc_initcnt, MPT_ArgValues(0), .init=NULL); +MPT_Test(init, null, .init=NULL); + +MPT_TestProcess(init, proc, id, proc_initcnt, MPT_ArgValues(1), .init=init_inc); +MPT_Test(init, proc); + +MPT_TestProcess(init, test, id, proc_initcnt, MPT_ArgValues(1)); +MPT_Test(init, test, .init=init_inc); + +MPT_TestProcess(init, test_proc, id, proc_initcnt, MPT_ArgValues(2), .init=init_inc); +MPT_Test(init, test_proc, .init=init_inc); + + + +/************************************************************ + * Disable fixture tests + ************************************************************/ +MPT_TestProcess(disabled, _true, id, proc_initcnt, MPT_ArgValues(0)); +MPT_Test(disabled, _true, .disabled=true); + +MPT_TestProcess(disabled, _false, id, proc_initcnt, MPT_ArgValues(0)); +MPT_Test(disabled, _false, .disabled=false); + + + +/************************************************************ + * Timeout fixture tests + ************************************************************/ +/* See if a child process is killed when the parent is killed. + * This can only really be done manually, unfortunately. */ +MPT_TestProcess(timeout, child_culling, id, proc_sleep, MPT_ArgValues(DDS_SECS(120))); +MPT_Test(timeout, child_culling, .timeout=1); diff --git a/src/mpt/tests/self/ipc.c b/src/mpt/tests/self/ipc.c new file mode 100644 index 0000000..932c6e6 --- /dev/null +++ b/src/mpt/tests/self/ipc.c @@ -0,0 +1,29 @@ +#include +#include "mpt/mpt.h" + + + +/************************************************************ + * Processes + ************************************************************/ +MPT_ProcessEntry(proc_ipc_send, MPT_NoArgs()) +{ + /* This will fail and cause an internal MPT assert as long + * as IPC is not yet implemented. */ + MPT_Send("todo: implement"); +} +MPT_ProcessEntry(proc_ipc_wait, MPT_NoArgs()) +{ + /* This will fail and cause an internal MPT assert as long + * as IPC is not yet implemented. */ + MPT_Wait("todo: implement"); +} + + + +/************************************************************ + * Tests + ************************************************************/ +MPT_TestProcess(ipc, TODO, id1, proc_ipc_send, MPT_NoArgValues()); +MPT_TestProcess(ipc, TODO, id2, proc_ipc_wait, MPT_NoArgValues()); +MPT_Test(ipc, TODO); diff --git a/src/mpt/tests/self/resources.c b/src/mpt/tests/self/resources.c new file mode 100644 index 0000000..9cba731 --- /dev/null +++ b/src/mpt/tests/self/resources.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include "mpt/mpt.h" +#include "mpt/resource.h" /* MPT_SOURCE_ROOT_DIR */ + +/************************************************************ + * Processes + ************************************************************/ +MPT_ProcessEntry(proc_file, MPT_NoArgs()) +{ + const char *test_file = MPT_SOURCE_ROOT_DIR"/tests/self/etc/file"; +#if _WIN32 + struct _stat buffer; + int ret = _stat(test_file,&buffer); +#else + struct stat buffer; + int ret = stat(test_file,&buffer); +#endif + MPT_ASSERT((ret == 0), "%s", test_file); +} + + + +/************************************************************ + * Test if MPT_SOURCE_ROOT_DIR is a valid location. + ************************************************************/ +MPT_TestProcess(resources, root_dir, id, proc_file, MPT_NoArgValues()); +MPT_Test(resources, root_dir); + diff --git a/src/mpt/tests/self/usage.c b/src/mpt/tests/self/usage.c new file mode 100644 index 0000000..1621810 --- /dev/null +++ b/src/mpt/tests/self/usage.c @@ -0,0 +1,186 @@ +#include +#include "mpt/mpt.h" +#include "mpt/resource.h" /* MPT_SOURCE_ROOT_DIR */ + + +/****************************************************************************** + * First, we need a process entry-point that can be used in tests. + *****************************************************************************/ +/* | name | arguments | */ +MPT_ProcessEntry(proc_noargs, MPT_NoArgs()) +{ + // Do stuff + + // The test processes will use asserts to indicate success/failures. + MPT_ASSERT(1, "The problem is: %s", "existential crisis"); + + // No need to return anything, that's handled by the assert calls. +} + +/****************************************************************************** + * A process entry-point can have arguments. + *****************************************************************************/ +/* | name | arguments | */ +MPT_ProcessEntry(proc_args, MPT_Args(int domain, const char* text)) +{ + int expected = 1; + MPT_ASSERT(expected == domain, "proc_args(%d, %s)", domain, text); +} + +/****************************************************************************** + * Process entry-points can communicate to be able to sync fi. + *****************************************************************************/ +MPT_ProcessEntry(proc_recv, MPT_NoArgs()) +{ + /* This will wait until another process sends the same string. */ + MPT_Wait("some state reached"); +} +MPT_ProcessEntry(proc_send, MPT_NoArgs()) +{ + /* If this fails, an internal MPT_ASSERT will be triggered. + * The same is true for MPT_Wait(). */ + MPT_Send("some state reached"); +} + + + +/****************************************************************************** + * Test: suitename_testA + ****************************************************************************** + * A simple test that starts two processes. Because a test can use the same + * process entry-point to start multiple processes, each process has to have + * its own unique id within the test. + */ +/* | process identification | entry-point | arguments | */ +MPT_TestProcess(suitename, testA, id1, proc_noargs, MPT_NoArgValues()); +MPT_TestProcess(suitename, testA, id2, proc_noargs, MPT_NoArgValues()); +MPT_Test(suitename, testA); + + + + +/****************************************************************************** + * Test: suitename_testB + ****************************************************************************** + * Of course, different processes can be started as well. + * Argument values are provided per test process. + */ +MPT_TestProcess(suitename, testB, id1, proc_noargs, MPT_NoArgValues( )); +MPT_TestProcess(suitename, testB, id2, proc_args, MPT_ArgValues(1, "2")); +MPT_TestProcess(suitename, testB, id3, proc_args, MPT_ArgValues(1, "3")); +MPT_Test(suitename, testB); + + + + +/****************************************************************************** + * Test: suitename_testC + ****************************************************************************** + * The processes can have different or equal 'system environments'. + */ +mpt_env_t environment_C1[] = { + { "CYCLONEDDS_URI", "file://config1.xml" }, + { "PERMISSIONS", "file://permissions.p7s" }, + { "GOVERNANCE", "file://governance.p7s" }, + { NULL, NULL } +}; +mpt_env_t environment_C2[] = { + { "CYCLONEDDS_URI", "file://config2.xml" }, + { "PERMISSIONS", "file://permissions.p7s" }, + { "GOVERNANCE", "file://governance.p7s" }, + { NULL, NULL } +}; +MPT_TestProcess(suitename, testC, id1, proc_noargs, MPT_NoArgValues(), .environment=environment_C1); +MPT_TestProcess(suitename, testC, id2, proc_noargs, MPT_NoArgValues(), .environment=environment_C1); +MPT_TestProcess(suitename, testC, id3, proc_noargs, MPT_NoArgValues(), .environment=environment_C2); +MPT_Test(suitename, testC); + + + + +/****************************************************************************** + * Test: suitename_testD + ****************************************************************************** + * The two environments in the previous example are partly the same. + * It's possible set the environment on test level. The environment variables + * related to the test are set before the ones related to a process. This + * means that a process can overrule variables. + * + * The following test is the same as the previous one. + */ +mpt_env_t environment_D1[] = { + { "CYCLONEDDS_URI", "file://config1.xml" }, + { "PERMISSIONS", "file://permissions.p7s" }, + { "GOVERNANCE", "file://governance.p7s" }, + { NULL, NULL } +}; +mpt_env_t environment_D2[] = { + { "CYCLONEDDS_URI", "file://config2.xml" }, + { NULL, NULL } +}; +MPT_TestProcess(suitename, testD, id1, proc_noargs, MPT_NoArgValues()); +MPT_TestProcess(suitename, testD, id2, proc_noargs, MPT_NoArgValues()); +MPT_TestProcess(suitename, testD, id3, proc_noargs, MPT_NoArgValues(), .environment=environment_D2); +MPT_Test(suitename, testD, .environment=environment_D1); + + + + +/****************************************************************************** + * Test: suitename_testE + ****************************************************************************** + * Environment variables will be expanded. + * Also, the MPT_SOURCE_ROOT_DIR define contains a string to that particular + * directory. + * This can be combined to easily point to files. + */ +mpt_env_t environment_E[] = { + { "ETC_DIR", MPT_SOURCE_ROOT_DIR"/tests/self/etc" }, + { "CYCLONEDDS_URI", "file://${ETC_DIR}/config.xml" }, + { NULL, NULL } +}; +MPT_TestProcess(suitename, testE, id, proc_noargs, MPT_NoArgValues(), .environment=environment_E); +MPT_Test(suitename, testE); + + + + +/****************************************************************************** + * Test: suitename_testF + ****************************************************************************** + * The processes and tests can use init/fini fixtures. + * The test init is executed before the process init. + * The process fini is executed before the test fini. + */ +void proc_setup(void) { /* do stuff */ } +void proc_teardown(void) { /* do stuff */ } +void test_setup(void) { /* do stuff */ } +void test_teardown(void) { /* do stuff */ } +MPT_TestProcess(suitename, testF, id1, proc_noargs, MPT_NoArgValues(), .init=proc_setup); +MPT_TestProcess(suitename, testF, id2, proc_noargs, MPT_NoArgValues(), .fini=proc_teardown); +MPT_TestProcess(suitename, testF, id3, proc_noargs, MPT_NoArgValues(), .init=proc_setup, .fini=proc_teardown); +MPT_Test(suitename, testF, .init=test_setup, .fini=test_teardown); + + + + +/****************************************************************************** + * Test: suitename_testG + ****************************************************************************** + * The timeout and disable options are handled by test fixtures. + */ +MPT_TestProcess(suitename, testG, id1, proc_noargs, MPT_NoArgValues()); +MPT_TestProcess(suitename, testG, id2, proc_noargs, MPT_NoArgValues()); +MPT_Test(suitename, testG, .timeout=10, .disabled=true); + + + + +/****************************************************************************** + * Test: suitename_testH + ****************************************************************************** + * See the process entries to notice the MPT Send/Wait IPC. + */ +MPT_TestProcess(suitename, testH, id1, proc_recv, MPT_NoArgValues()); +MPT_TestProcess(suitename, testH, id2, proc_send, MPT_NoArgValues()); +MPT_Test(suitename, testH);