/* $Id: cpl_test.c,v 1.21 2008/02/21 12:41:29 llundin Exp $ * * This file is part of the ESO Common Pipeline Library * Copyright (C) 2001-2004 European Southern Observatory * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * $Author: llundin $ * $Date: 2008/02/21 12:41:29 $ * $Revision: 1.21 $ * $Name: $ */ #ifdef HAVE_CONFIG_H #include #endif /*----------------------------------------------------------------------------- Includes -----------------------------------------------------------------------------*/ #include #include #include #include #include /* Needed for fabs() */ #include /* Needed for CFITSIO_VERSION */ #include #include "cpl_init.h" #include "cpl_errorstate.h" #include "cpl_memory.h" #include "cpl_tools.h" /*----------------------------------------------------------------------------- Private function prototypes -----------------------------------------------------------------------------*/ static void cpl_test_reset(void); static void cpl_errorstate_dump_debug(unsigned, unsigned, unsigned); static const char * cpl_test_get_description(void); static void cpl_test_one(cpl_boolean, const char *, const char *, const char *, unsigned); /*----------------------------------------------------------------------------- Private variables -----------------------------------------------------------------------------*/ static cpl_errorstate cleanstate; static unsigned cpl_test_count = 0; static unsigned cpl_test_failures = 0; static const char * cpl_test_report = NULL; /*----------------------------------------------------------------------------*/ /** * @defgroup cpl_test Unit testing functions * * This module provides various functions for unit testing. * * @par Synopsis: * @code * #include "cpl_test.h" * @endcode */ /*----------------------------------------------------------------------------*/ /**@{*/ /*----------------------------------------------------------------------------- Function codes -----------------------------------------------------------------------------*/ /*----------------------------------------------------------------------------*/ /** @internal @brief Initialize CPL and unit-test environment @param filename __FILE__ of unit-test source code for log-file @param report The email address for the error message @param default_level Default level for messaging system @return void @see cpl_test_init() @note This function should only be called by cpl_test_init() */ /*----------------------------------------------------------------------------*/ void cpl_test_init_macro(const char * filename, const char * report, cpl_msg_severity default_level) { const char * dotpos; int myerrno; assert(report != NULL); assert(filename != NULL); cpl_test_report = report; cpl_init(CPL_INIT_DEFAULT); myerrno = errno; /* Needed on alphaev56 */ if (signal(SIGFPE, SIG_IGN) == SIG_ERR) { cpl_msg_warning(cpl_func, "Could not install new signal handler " "(SIG_IGN) for SIGFPE"); } cleanstate = cpl_errorstate_get(); if (filename != NULL && (dotpos = index(filename, '.')) != NULL && strlen(dotpos) == 2) { /* Create a new string, where the ending .? is replaced with .log */ char * logfile = cpl_sprintf("%sog", filename); logfile[strlen(logfile)-strlen("log")] = 'l'; cpl_msg_set_log_name(logfile); if (cpl_error_get_code()) { /* The log-file name could not be set */ cpl_test_reset(); } /* Drop .log */ logfile[strlen(logfile)-strlen(".log")] = '\0'; cpl_msg_set_domain(logfile); cpl_free(logfile); } cpl_msg_set_log_level(CPL_MSG_DEBUG); cpl_msg_set_level(default_level); cpl_msg_set_level_from_env(); if (cpl_error_get_code() != CPL_ERROR_NONE) { cpl_errorstate_dump_one(1, 1, 1); /* Dump the most recent error */ assert(cpl_error_get_code() == CPL_ERROR_NONE); exit(EXIT_FAILURE); /* exit also on -DNDEBUG */ } cpl_msg_debug(cpl_func, cpl_get_description(CPL_DESCRIPTION_DEFAULT)); if (errno != myerrno) { cpl_msg_error(cpl_func, "Resetting errno=%d: %s", errno, strerror(errno)); errno = 0; } if (myerrno != 0) { /* FIXME: Work-around for DFS04285 */ cpl_msg_debug(cpl_func, "Resetting errno=%d: %s", myerrno, strerror(myerrno)); errno = 0; } cpl_tools_get_cputime(CPL_CLOCK_START); return; } /*----------------------------------------------------------------------------*/ /** @internal @brief Test a given boolean expression @param expr The integer expression to evaluate @param fail_on_zero Fail iff the expression is zero @param expr_txt The integer expression to evaluate as a string @param function cpl_func @param file __FILE__ @param line __LINE__ @return void @see cpl_test() @note This function should only be called via cpl_test() @note CPL_FALSE of the boolean is a failure, CPL_TRUE is not */ /*----------------------------------------------------------------------------*/ void cpl_test_macro(int expression, cpl_boolean fail_on_zero, const char * expr_txt, const char * function, const char * file, unsigned line) { const char * message = cpl_sprintf("(%s) = %d", expr_txt, expression); const cpl_boolean bool = fail_on_zero ? (expression ? CPL_TRUE : CPL_FALSE) : (expression ? CPL_FALSE : CPL_TRUE); cpl_test_one(bool, message, function, file, line); cpl_free((char*)message); return; } /*----------------------------------------------------------------------------*/ /** @internal @brief Test if a pointer is non-NULL @param pointer The pointer to check, side-effects are allowed @param pointer_string The pointer as a string @param function cpl_func @param file __FILE__ @param line __LINE__ @see cpl_test_null @note This function should only be called from the macro cpl_test_null() */ /*----------------------------------------------------------------------------*/ void cpl_test_null_macro(const void * pointer, const char * pointer_string, const char * function, const char * file, unsigned line) { const char * message = cpl_sprintf("(%s) = %p", pointer_string, pointer); cpl_test_one(pointer == NULL ? CPL_TRUE : CPL_FALSE, message, function, file, line); cpl_free((char*)message); return; } /*----------------------------------------------------------------------------*/ /** @internal @brief Test if a pointer is non-NULL @param pointer The pointer to check, side-effects are allowed @param pointer_string The pointer as a string @param function cpl_func @param file __FILE__ @param line __LINE__ @see cpl_test_nonnull @note This function should only be called from the macro cpl_test_nonnull() */ /*----------------------------------------------------------------------------*/ void cpl_test_nonnull_macro(const void * pointer, const char * pointer_string, const char * function, const char * file, unsigned line) { const char * message = cpl_sprintf("(%s) = %p", pointer_string, pointer); cpl_test_one(pointer != NULL ? CPL_TRUE : CPL_FALSE, message, function, file, line); cpl_free((char*)message); return; } /*----------------------------------------------------------------------------*/ /** @internal @brief Test if two integer expressions are equal @param first The first value in the comparison @param first_string The first value as a string @param second The second value in the comparison @param second_string The second value as a string @param function cpl_func @param file __FILE__ @param line __LINE__ @see cpl_test_eq @note This function should only be called from the macro cpl_test_eq() */ /*----------------------------------------------------------------------------*/ void cpl_test_eq_macro(int first, const char * first_string, int second, const char * second_string, const char * function, const char * file, unsigned line) { const char * message = cpl_sprintf("(%s) = %d; (%s) = %d", first_string, first, second_string, second); cpl_test_one(first == second ? CPL_TRUE : CPL_FALSE, message, function, file, line); cpl_free((char*)message); return; } /*----------------------------------------------------------------------------*/ /** @internal @brief Test if two integer expressions are not equal @param first The first value in the comparison @param first_string The first value as a string @param second The second value in the comparison @param second_string The second value as a string @param function cpl_func @param file __FILE__ @param line __LINE__ @see cpl_test_noneq @note This function should only be called from the macro cpl_test_noneq() */ /*----------------------------------------------------------------------------*/ void cpl_test_noneq_macro(int first, const char * first_string, int second, const char * second_string, const char * function, const char * file, unsigned line) { const char * message = cpl_sprintf("(%s) = %d; (%s) = %d", first_string, first, second_string, second); cpl_test_one(first != second ? CPL_TRUE : CPL_FALSE, message, function, file, line); cpl_free((char*)message); return; } /*----------------------------------------------------------------------------*/ /** @internal @brief Test if two strings are equal @param first The first string or NULL of the comparison @param first_string The first value as a string @param second The second string or NULL of the comparison @param second_string The second value as a string @param function function name @param file filename @param line line number @see cpl_test_eq_string() @note This function should only be called from cpl_test_eq_string() */ /*----------------------------------------------------------------------------*/ void cpl_test_eq_string_macro(const char * first, const char * first_string, const char * second, const char * second_string, const char * function, const char * file, unsigned line) { const char * fquote = first != NULL ? cpl_sprintf("'%s'", first) : "NULL"; const char * squote = second != NULL ? cpl_sprintf("'%s'", second) : "NULL"; const char * message = cpl_sprintf("%s = %s; %s = %s", first_string, fquote, second_string, squote); if (first != NULL) cpl_free((char*)fquote); if (second != NULL) cpl_free((char*)squote); cpl_test_one(first != NULL && second != NULL && strcmp(first, second) == 0, message, function, file, line); cpl_free((char *)message); return; } /*----------------------------------------------------------------------------*/ /** @internal @brief Evaluate A <= B and update an internal counter if it is not true @param value The double-precision number to test @param tolerance The double-precision upper limit to compare against @param function cpl_func @param file __FILE__ @param line __LINE__ @return void @see cpl_test_leq() @note This function should only be called via cpl_test_leq_macro() */ /*----------------------------------------------------------------------------*/ void cpl_test_leq_macro(double value, double tolerance, const char * function, const char * file, unsigned line) { const cpl_boolean expression = (value <= tolerance) ? CPL_TRUE : CPL_FALSE; const char * message = cpl_sprintf("%g <= %g", value, tolerance); cpl_test_one(expression, message, function, file, line); cpl_free((char*)message); return; } /*----------------------------------------------------------------------------*/ /** @internal @brief Test if two numerical expressions are within a given (absolute) tolerance @param first The first value in the comparison @param first_string The first value as a string @param second The second value in the comparison @param second_string The second value as a string @param tolerance A non-negative tolerance @param tolerance_string The tolerance as a string @param function function name @param file filename @param line line number @return void @see cpl_test_abs() @note This function should only be called from the macro cpl_test_abs() */ /*----------------------------------------------------------------------------*/ void cpl_test_abs_macro(double first, const char *first_string, double second, const char *second_string, double tolerance, const char *tolerance_string, const char *function, const char *file, unsigned line) { const cpl_boolean expression = (fabs(first - second) <= tolerance) ? CPL_TRUE : CPL_FALSE; const char *message = cpl_sprintf("|%s - %s| = |%g - %g| = |%g| <= %g = %s", first_string, second_string, first, second, first - second, tolerance, tolerance_string); cpl_test_one(expression, message, function, file, line); cpl_free((char *)message); return; } /*----------------------------------------------------------------------------*/ /** @internal @brief Test if two numerical expressions are within a given relative tolerance @param first The first value in the comparison @param first_string The first value as a string @param second The second value in the comparison @param second_string The second value as a string @param tolerance A non-negative tolerance @param tolerance_string The tolerance as a string @param function function name @param file filename @param line line number @see cpl_test_rel() @note This function should only be called from the macro cpl_test_rel() */ /*----------------------------------------------------------------------------*/ void cpl_test_rel_macro(double first, const char *first_string, double second, const char *second_string, double tolerance, const char *tolerance_string, const char *function, const char *file, unsigned line) { const char * message = NULL; /* Avoid (false) uninit warnings */ cpl_boolean expression; if (tolerance < 0.0) { expression = CPL_FALSE; message = cpl_sprintf("%s = %g; %s = %g. Negative tolerance %s = %g", first_string, first, second_string, second, tolerance_string, tolerance); } else if (first == second) { /* Not needed. Used only for prettier messaging */ expression = CPL_TRUE; message = cpl_sprintf("%s = %g = %s. (Tolerance %s = %g)", first_string, first, second_string, tolerance_string, tolerance); } else if (first == 0.0) { /* Not needed. Used only for prettier messaging */ expression = CPL_FALSE; message = cpl_sprintf("%s = zero; %s = non-zero (%g). (Tolerance " "%s = %g)", first_string, second_string, second, tolerance_string, tolerance); } else if (second == 0.0) { /* Not needed. Used only for prettier messaging */ expression = CPL_FALSE; message = cpl_sprintf("%s = non-zero (%g); %s = zero. (Tolerance " "%s = %g)", first_string, first, second_string, tolerance_string, tolerance); } else if (fabs(first) < fabs(second)) { expression = fabs(first - second) <= tolerance * fabs(first) ? CPL_TRUE : CPL_FALSE; message = cpl_sprintf("|%s - %s|/|%s| = |%g - %g|/|%g| = |%g|/|%g|" " <= %g = %s", first_string, second_string, first_string, first, second, first, first - second, first, tolerance, tolerance_string); } else { /* assert(fabs(second) < fabs(first)) */ expression = fabs(first - second) <= tolerance * fabs(second) ? CPL_TRUE : CPL_FALSE; message = cpl_sprintf("|%s - %s|/|%s| = |%g - %g|/|%g| = |%g|/|%g|" " <= %g = %s", first_string, second_string, second_string, first, second, second, first - second, second, tolerance, tolerance_string); } cpl_test_one(expression, message, function, file, line); cpl_free((char *)message); return; } /*----------------------------------------------------------------------------*/ /** @internal @brief Test and reset the CPL error code @param error The expected CPL error code (incl. CPL_ERROR_NONE) @param error_string The CPL error code as a string @param function cpl_func @param file __FILE__ @param line __LINE__ @see cpl_test_error @note This function should only be called from the macro cpl_test_error() */ /*----------------------------------------------------------------------------*/ void cpl_test_error_macro(cpl_error_code error, const char * error_string, const char * function, const char * file, unsigned line) { const char * message = cpl_sprintf("(%s) = %d (%s)", error_string, error, cpl_error_get_message_default(error)); cpl_test_one(cpl_error_get_code() == error ? CPL_TRUE : CPL_FALSE, message, function, file, line); cpl_free((char*)message); cpl_test_reset(); return; } /*----------------------------------------------------------------------------*/ /** @internal @brief Test if the memory system is empty @param function cpl_func @param file __FILE__ @param line __LINE__ @see cpl_test_memory_is_empty @note This function should only be called from the macro cpl_test_memory_is_empty() */ /*----------------------------------------------------------------------------*/ void cpl_test_memory_is_empty_macro(const char * function, const char * file, unsigned line) { const char * message; cpl_boolean ok; if (cpl_memory_is_empty() == -1) { message = "CPL memory system is empty (not testable)"; ok = CPL_TRUE; } else { message = "CPL memory system is empty"; ok = cpl_memory_is_empty() == 0 ? CPL_FALSE : CPL_TRUE; } cpl_test_one(ok, message, function, file, line); if (!ok) { cpl_msg_indent_more(); cpl_memory_dump(); cpl_msg_indent_less(); } return; } /*----------------------------------------------------------------------------*/ /** @brief Finalize CPL and unit-testing environment and report any failures @param nfail The number of failures counted apart from cpl_test() et al. @return @em EXIT_SUCCESS iff the CPL errorstate is clean @note This function should be used for the final return from a unit test @see cpl_test_init() nfail should normally be zero, but may be set to a positive number when it is necessary to ensure a failure. nfail should only be negative in the unit test of the unit-test functions themselves. Example of usage: @code int main (void) { cpl_test_init(CPL_MSG_WARNING); cpl_test(myfunc(&p)); cpl_test(p != NULL); return cpl_test_end(0); } @endcode */ /*----------------------------------------------------------------------------*/ int cpl_test_end(int nfail) { int myerrno = errno; const cpl_flops nflops = cpl_tools_get_flops(); double cputime; cpl_boolean ok = CPL_TRUE; cpl_tools_get_cputime(CPL_CLOCK_STOP); cputime = cpl_tools_get_cputime(CPL_CLOCK_TIME); cpl_msg_info(cpl_func, "Time spent checking [s]: %g", cputime); if (nflops > 0 && cputime > 0.0) { cpl_msg_info(cpl_func, "Computational speed during this test " "[MFLOPS/sec]: %g", 1e-6*(double)nflops/cputime); } /* Make sure that the failure is written */ if (cpl_msg_get_level() == CPL_MSG_OFF) cpl_msg_set_level(CPL_MSG_ERROR); if (cpl_error_get_code() != CPL_ERROR_NONE) { ok = CPL_FALSE; cpl_msg_error(cpl_func, "The CPL errorstate was set by the unit test(s)"); cpl_msg_indent_more(); cpl_errorstate_dump(cleanstate, CPL_FALSE, NULL); cpl_msg_indent_less(); } if (!cpl_memory_is_empty()) { ok = CPL_FALSE; cpl_msg_error(cpl_func, "Memory leak detected:"); cpl_msg_indent_more(); cpl_memory_dump(); cpl_msg_indent_less(); } else if (cpl_msg_get_level() <= CPL_MSG_DEBUG) { cpl_memory_dump(); } nfail += (int)cpl_test_failures; if (nfail) { ok = CPL_FALSE; cpl_msg_error(cpl_func, "%d of %u test(s) failed", nfail, cpl_test_count); } else { cpl_msg_info(cpl_func, "All %u test(s) succeeded", cpl_test_count); } if (!ok) { cpl_msg_error(cpl_func, "This failure may indicate a bug in the tested " "code"); cpl_msg_error(cpl_func, "Please email the logfile %s to %s", cpl_msg_get_log_name(), cpl_test_report ? cpl_test_report : PACKAGE_BUGREPORT); cpl_msg_error(cpl_func, "System specifics:\n%s", cpl_test_get_description()); } if (myerrno != 0) { cpl_msg_info(cpl_func, "errno=%d: %s", myerrno, strerror(myerrno)); } if (errno != myerrno) { myerrno = errno; cpl_msg_error(cpl_func, "errno=%d: %s", myerrno, strerror(myerrno)); } cpl_end(); return ok ? EXIT_SUCCESS : EXIT_FAILURE; } /**@}*/ /*----------------------------------------------------------------------------*/ /** @internal @brief Dump the CPL errorstate and reset it @return void */ /*----------------------------------------------------------------------------*/ static void cpl_test_reset(void) { if (!cpl_errorstate_is_equal(cleanstate)) { cpl_errorstate_dump(cleanstate, CPL_FALSE, cpl_errorstate_dump_debug); cpl_errorstate_set(cleanstate); } } /*----------------------------------------------------------------------------*/ /** @internal @brief Test an expression and update an internal counter if it fails @param expression The expression to test (CPL_FALSE means failure) @param message The text message associated with the expression @param function function name @param file filename @param line line number */ /*----------------------------------------------------------------------------*/ static void cpl_test_one(cpl_boolean expression, const char *message, const char *function, const char *file, unsigned line) { const cpl_boolean has_error = cpl_error_get_code() ? CPL_TRUE : CPL_FALSE; const char * error_msg = has_error ? cpl_sprintf(" (CPL-error state: '%s' at %s)", cpl_error_get_message(), cpl_error_get_where()) : ""; assert(message != NULL); assert(function != NULL); assert(file != NULL); cpl_test_count++; if (expression) { cpl_msg_debug(function, "OK at %s:%u%s: %s", file, line, error_msg, message); } else { cpl_msg_error(function, "Failure at %s:%u%s: %s", file, line, error_msg, message); cpl_errorstate_dump(cleanstate, CPL_FALSE, NULL); cpl_test_failures++; } if (has_error) cpl_free((char *)error_msg); return; } /*----------------------------------------------------------------------------*/ /** @internal @brief Dump a single CPL error at debug messaging level @param self The number of the current error to be dumped @param first The number of the first error to be dumped @param last The number of the last error to be dumped @return void @see cpl_errorstate_dump_one */ /*----------------------------------------------------------------------------*/ static void cpl_errorstate_dump_debug(unsigned self, unsigned first, unsigned last) { const cpl_boolean is_reverse = first > last ? CPL_TRUE : CPL_FALSE; const unsigned newest = is_reverse ? first : last; const unsigned oldest = is_reverse ? last : first; const char * revmsg = is_reverse ? " in reverse order" : ""; assert( oldest <= self ); assert( newest >= self ); if (newest == 0) { cpl_msg_debug(cpl_func, "No error(s) to dump"); assert( oldest == 0); } else { assert( oldest > 0); assert( newest >= oldest); if (self == first) { if (oldest == 1) { cpl_msg_debug(cpl_func, "Dumping all %u error(s)%s:", newest, revmsg); } else { cpl_msg_debug(cpl_func, "Dumping the %u most recent error(s) " "out of a total of %u errors%s:", newest - oldest + 1, newest, revmsg); } cpl_msg_indent_more(); } cpl_msg_debug(cpl_func, "[%u/%u] '%s' (%u) at %s", self, newest, cpl_error_get_message(), cpl_error_get_code(), cpl_error_get_where()); if (self == last) cpl_msg_indent_less(); } } /*----------------------------------------------------------------------------*/ /** @internal @brief A string useful in error-reporting @return Pointer to a string literal useful in error-reporting */ /*----------------------------------------------------------------------------*/ static const char * cpl_test_get_description(void) { return "CPL version: " PACKAGE_VERSION "\n" #ifdef CFITSIO_VERSION "CFITSIO version: " CPL_STRINGIFY(CFITSIO_VERSION) "\n" #elif defined _FITSIO_H "CFITSIO version is less than 3.0\n" #endif #if defined CPL_WCS_INSTALLED && CPL_WCS_INSTALLED == 1 "WCSLIB installation is detected\n" #else "WCSLIB installation is not detected\n" #endif #if defined WORDS_BIGENDIAN && WORDS_BIGENDIAN == 1 "This platform is big-endian\n" #else "This platform is not big-endian\n" #endif #ifdef __DATE__ "Compile date: " __DATE__ "\n" #endif #ifdef __TIME__ "Compile time: " __TIME__ "\n" #endif #ifdef __STDC__ CPL_XSTRINGIFY(__STDC__) ": " CPL_STRINGIFY(__STDC__) "\n" #endif #ifdef __STDC_VERSION__ CPL_XSTRINGIFY(__STDC_VERSION__) ": " CPL_STRINGIFY(__STDC_VERSION__) "\n" #endif #ifdef __STDC_HOSTED__ CPL_XSTRINGIFY(__STDC_HOSTED__) ": " CPL_STRINGIFY(__STDC_HOSTED__) "\n" #endif #ifdef __STDC_IEC_559__ CPL_XSTRINGIFY(__STDC_IEC_559__) ": " CPL_STRINGIFY(__STDC_IEC_559__) "\n" #endif #ifdef __GNUC__ "gcc version (major number): " CPL_STRINGIFY(__GNUC__) "\n" #ifdef __GNUC_MINOR__ "gcc version (minor number): " CPL_STRINGIFY(__GNUC_MINOR__) "\n" #endif #ifdef __GNUC_PATCHLEVEL__ "gcc version (patch level): " CPL_STRINGIFY(__GNUC_PATCHLEVEL__) "\n" #endif #ifdef __VERSION__ "gcc version: " __VERSION__ "\n" #endif #ifdef __LP64__ CPL_XSTRINGIFY(__LP64__) ": " CPL_STRINGIFY(__LP64__) "\n" #endif #ifdef __PIC__ CPL_XSTRINGIFY(__PIC__) ": " CPL_STRINGIFY(__PIC__) "\n" #endif #ifdef __OPTIMIZE__ CPL_XSTRINGIFY(__OPTIMIZE__) ": " CPL_STRINGIFY(__OPTIMIZE__) "\n" #endif #ifdef __TIMESTAMP__ "Last modification of " __FILE__ ": " __TIMESTAMP__ "\n" #endif #endif ; }