Quellcode durchsuchen

Implement assertion checking in unit tests.

See the new test_assert.h header for details.
Jason Valenzuela vor 4 Jahren
Ursprung
Commit
b46f29dbb1

+ 1 - 0
source/CMakeLists.txt

@@ -135,6 +135,7 @@ if( OpENer_TESTS )
   # The used CppUTest framework does not support parallel jobs
   SETUP_TARGET_FOR_COVERAGE_LCOV(NAME ${PROJECT_NAME}_coverage EXECUTABLE OpENer_Tests EXCLUDE "tests/*" "src/ports/*/sample_application/*" "${CPPUTEST_HOME}/*")
   add_test_includes()
+  add_definitions( -DOPENER_UNIT_TEST )
   add_subdirectory( tests )
 endif( OpENer_TESTS )
 

+ 1 - 1
source/buildsupport/OpENer_Tests.cmake

@@ -4,5 +4,5 @@
 macro( add_test_includes )
   set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage -include ${CPPUTEST_HOME}/include/CppUTest/MemoryLeakDetectorNewMacros.h" )
   set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage -include ${CPPUTEST_HOME}/include/CppUTest/MemoryLeakDetectorMallocMacros.h" )
-  include_directories( ${CPPUTEST_HOME}/include )
+  include_directories( ${CPPUTEST_HOME}/include ${OpENer_SOURCE_DIR}/tests)
 endmacro( add_test_includes )

+ 12 - 0
source/src/ports/MINGW/sample_application/opener_user_conf.h

@@ -24,6 +24,10 @@
 
  #include "typedefs.h"
 
+#ifdef OPENER_UNIT_TEST
+  #include "test_assert.h"
+#endif /* OPENER_UNIT_TEST */
+
 typedef unsigned short in_port_t;
 
 /** @brief Set this define if you have a DLR capable device
@@ -159,6 +163,12 @@ typedef unsigned short in_port_t;
  */
 static const MilliSeconds kOpenerTimerTickInMilliSeconds = 10;
 
+/*
+ * Omit assertion definitions when building unit tests. These will
+ * be defined with versions suitable for the unit test environment.
+ */
+#ifndef OPENER_UNIT_TEST
+
 #ifdef OPENER_WITH_TRACES
 /* If we have tracing enabled provide LOG_TRACE macro */
     #include <stdio.h>
@@ -209,6 +219,8 @@ static const MilliSeconds kOpenerTimerTickInMilliSeconds = 10;
 
 #endif  /* ifdef OPENER_WITH_TRACES */
 
+#endif /* ifndef OPENER_UNIT_TEST */
+
 /** @brief The number of bytes used for the Ethernet message buffer on
  * the PC port. For different platforms it may makes sense to
  * have more than one buffer.

+ 12 - 0
source/src/ports/POSIX/sample_application/opener_user_conf.h

@@ -53,6 +53,10 @@
   #define CIP_SECURITY_OBJECTS 0
 #endif
 
+#ifdef OPENER_UNIT_TEST
+  #include "test_assert.h"
+#endif /* OPENER_UNIT_TEST */
+
 /** @brief Set this define if you have a DLR capable device
  *
  * This define changes the OpENer device configuration in a way that
@@ -186,6 +190,12 @@
  */
 static const MilliSeconds kOpenerTimerTickInMilliSeconds = 10;
 
+/*
+ * Omit assertion definitions when building unit tests. These will
+ * be defined with versions suitable for the unit test environment.
+ */
+#ifndef OPENER_UNIT_TEST
+
 #ifdef OPENER_WITH_TRACES
 /* If we have tracing enabled provide LOG_TRACE macro */
     #include <stdio.h>
@@ -236,6 +246,8 @@ static const MilliSeconds kOpenerTimerTickInMilliSeconds = 10;
 
 #endif  /* ifdef OPENER_WITH_TRACES */
 
+#endif /* ifndef OPENER_UNIT_TEST */
+
 /** @brief The number of bytes used for the Ethernet message buffer on
  * the PC port. For different platforms it may makes sense to
  * have more than one buffer.

+ 11 - 0
source/src/ports/WIN32/sample_application/opener_user_conf.h

@@ -28,6 +28,9 @@ typedef unsigned short in_port_t;
 
 #include "typedefs.h"
 
+#ifdef OPENER_UNIT_TEST
+  #include "test_assert.h"
+#endif /* OPENER_UNIT_TEST */
 
 /** @brief Set this define if you have a DLR capable device
  *
@@ -162,6 +165,12 @@ typedef unsigned short in_port_t;
  */
 static const MilliSeconds kOpenerTimerTickInMilliSeconds = 10;
 
+/*
+ * Omit assertion definitions when building unit tests. These will
+ * be defined with versions suitable for the unit test environment.
+ */
+#ifndef OPENER_UNIT_TEST
+
 #ifdef OPENER_WITH_TRACES
 /* If we have tracing enabled provide LOG_TRACE macro */
     #include <stdio.h>
@@ -212,6 +221,8 @@ static const MilliSeconds kOpenerTimerTickInMilliSeconds = 10;
 
 #endif  /* ifdef OPENER_WITH_TRACES */
 
+#endif /* ifndef OPENER_UNIT_TEST */
+
 /** @brief The number of bytes used for the Ethernet message buffer on
  * the PC port. For different platforms it may makes sense to
  * have more than one buffer.

+ 87 - 0
source/tests/OpENerTests.cpp

@@ -1,9 +1,33 @@
+#include <setjmp.h>
+#include <stdexcept>
+#include <stdio.h>
+
 #include "OpENerTests.h"
+#include "CppUTest/TestRegistry.h"
 
 extern "C" {
 #include "endianconv.h"
 }
 
+
+/*
+ * Stores the location in the unit test function where execution should jump
+ * to upon a failed assertion.
+ */
+jmp_buf assert_jump;
+
+
+/*
+ * This pointer is used to indicate if an assertion is expected in the code
+ * being tested. A non-NULL value means an assertion is expected, and the
+ * resulting longjmp() target has been stored in the assert_jmp variable.
+ * The actual address stored here is meaningless, aside from being NULL or not;
+ * this pointer is never dereferenced. A pointer is used instead of a boolean
+ * so the SetPointerPlugin can automatically reset it after every test.
+ */
+jmp_buf *assert_jump_enabled;
+
+
 int main(int argc,
          char **argv) {
   /* These checks are here to make sure assertions outside test runs don't crash */
@@ -12,5 +36,68 @@ int main(int argc,
 
   DetermineEndianess();
 
+  /*
+   * Enable the Cpputest SetPointerPlugin to automatically reset the
+   * assert_jump_enabled pointer after each test.
+   */
+  SetPointerPlugin assert_restore("AssertJumpRestore");
+  TestRegistry::getCurrentRegistry()->installPlugin(&assert_restore);
+
   return CommandLineTestRunner::RunAllTests(argc, argv);
 }
+
+
+/*
+ * This is the function called by the OPENER_ASSERT macro if the assertion
+ * condition fails. It will interrupt the code under test in one of two ways
+ * depending on if an assertion is expected from the CHECK_ASSERT test macro.
+ *
+ * Arguments:
+ *
+ * file - Path to the source file where the assertion failed.
+ * line - Line number identifying the failed assertion.
+ */
+extern "C" void test_assert_fail(const char *const file,
+                                 const unsigned int line) {
+  /*
+   * Throw an exception with the assertion location if an assertion is not
+   * expected. Unfortunately, this will stop all further tests and does not
+   * identify the test that failed.
+   */
+  if (assert_jump_enabled == NULL) {
+    const char format[] = "Assertion failure: %s:%d";
+    char dummy;
+
+    /* Determine how long the exception message would be. */
+    int len_no_null = snprintf(&dummy, 1, format, file, line);
+
+    if (len_no_null > 0) {
+      /*
+       * Allocate memory for the exception message, including the NULL
+       * terminator. This memory is not freed because the forthcoming
+       * exception terminates everything anyway.
+       */
+      const size_t len_with_null = len_no_null + 1;
+      char *msg = (char *)malloc(len_with_null);
+
+      if (msg != NULL) {
+        len_no_null = snprintf(msg, len_with_null, format, file, line);
+
+        if (len_no_null > 0) {
+          throw std::runtime_error(msg);
+        }
+      }
+    }
+
+    /* Throw a generic exception if string generation fails. */
+    throw std::runtime_error("Assertion failure.");
+  }
+
+  /*
+   * Execute the jump back to the unit test function if an assertion was
+   * expected.
+   */
+  else {
+    longjmp(assert_jump, 0);
+  }
+}

+ 43 - 0
source/tests/check_assert.h

@@ -0,0 +1,43 @@
+/*
+ * This header contains definitions implementing a method for writing test
+ * cases to confirm an OPENER_ASSERTION failure using the CHECK_ASSERT macro.
+ * Only code implementing Cpputest test cases should include this header; it
+ * should not be included by application code.
+ */
+#include <setjmp.h>
+
+
+/* See OpENerTests.cpp for descriptions. */
+extern jmp_buf assert_jump;
+extern jmp_buf *assert_jump_enabled;
+
+
+/*
+ * This macro is intended to be used in the unit test code, not the
+ * application code, to verify a given expression, typically a call to a
+ * function being tested, generates an assertion via a failed OPENER_ASSERT
+ * condition. For example:
+ *
+ * CHECK_ASSERT(func());
+ *
+ * The above statement will pass if an OPENER_ASSERT fails during func(),
+ * or cause the test to fail if func() returns normally.
+ *
+ * These statements are enclosed within a do/while block to keep the if
+ * statement isolated from surrounding if statements.
+ */
+#define CHECK_ASSERT(exp)                                               \
+  do {                                                                  \
+    /* Enable an expected assertion by storing a non-NULL pointer. */   \
+    UT_PTR_SET(assert_jump_enabled, &assert_jump);                      \
+                                                                        \
+    /* Store the assertion jump location. */                            \
+    if (setjmp(assert_jump) == 0) {                                     \
+                                                                        \
+      /* Code under test, which should longjmp() instead of return. */  \
+      exp;                                                              \
+                                                                        \
+      /* Fail if the above expression did not generate an assertion. */ \
+      FAIL("Did not assert as expected.");                              \
+    }                                                                   \
+  } while (0)

+ 33 - 0
source/tests/test_assert.h

@@ -0,0 +1,33 @@
+/*
+ * This header defines an implementation of the OPENER_ASSERT macro that
+ * can be used with Cpputest unit tests to confirm an assertion fails
+ * under given conditions. It is conditionally included in the application
+ * code when unit tests are enabled through the Cmake configuration; it should
+ * not be included in the Cpputest unit test code.
+ *
+ * The intent is to create an assertion implementation that both immediately
+ * stops execution after an assertion failure, as should normally be the
+ * case, and is detectable from the unit test code. This is accomplished via
+ * setjmp() and longjmp(), approximating the behavior of a C++ exception,
+ * where an OPENER_ASSERTION failure results in a longjmp() back to the
+ * unit test code, which then verifies that an assertion failure occurred.
+ */
+#ifndef OPENER_TEST_ASSERT_H
+#define OPENER_TEST_ASSERT_H
+
+
+/*
+ * Define the OPENER_ASSERT macro to call the unit test assertion verification
+ * function. The surrounding do/while loop serves to insulate the if statement
+ * from any surrounding if statements.
+ */
+#define OPENER_ASSERT(assertion) \
+  do {if ( !(assertion) ) test_assert_fail(__FILE__, __LINE__);} while (0)
+
+
+/* Function Prototypes */
+extern void test_assert_fail(const char *const file,
+                             const unsigned int line);
+
+
+#endif /* OPENER_TEST_ASSERT_H */