mirror of
https://github.com/yuzu-emu/mbedtls.git
synced 2024-12-23 19:25:28 +00:00
1689 lines
44 KiB
C
1689 lines
44 KiB
C
|
/*
|
||
|
====================================================================
|
||
|
Copyright (c) 2008 Ian Blumel. All rights reserved.
|
||
|
|
||
|
FCT (Fast C Test) Unit Testing Framework
|
||
|
|
||
|
Copyright (c) 2008, Ian Blumel (ian.blumel@gmail.com)
|
||
|
All rights reserved.
|
||
|
|
||
|
This license is based on the BSD License.
|
||
|
|
||
|
Redistribution and use in source and binary forms, with or without
|
||
|
modification, are permitted provided that the following conditions are
|
||
|
met:
|
||
|
|
||
|
* Redistributions of source code must retain the above copyright
|
||
|
notice, this list of conditions and the following disclaimer.
|
||
|
|
||
|
* Redistributions in binary form must reproduce the above copyright
|
||
|
notice, this list of conditions and the following disclaimer in
|
||
|
the documentation and/or other materials provided with the
|
||
|
distribution.
|
||
|
|
||
|
* Neither the name of, Ian Blumel, nor the names of its
|
||
|
contributors may be used to endorse or promote products derived
|
||
|
from this software without specific prior written permission.
|
||
|
|
||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||
|
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||
|
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||
|
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
====================================================================
|
||
|
|
||
|
File: fct.h
|
||
|
*/
|
||
|
|
||
|
#if !defined(FCT_INCLUDED__IMB)
|
||
|
#define FCT_INCLUDED__IMB
|
||
|
|
||
|
#define FCT_VERSION_STR "1.0.2"
|
||
|
#define FCT_VERSION_MAJOR 1
|
||
|
#define FCT_VERSION_MINOR 0
|
||
|
#define FCT_VERSION_MICRO 2
|
||
|
|
||
|
/* Define this to remove unneeded WIN32 warnings. We will undefine this at
|
||
|
the end of the file so as not to interfere with your build. */
|
||
|
#if defined(WIN32) && !defined(_CRT_SECURE_NO_WARNINGS)
|
||
|
# define _CRT_SECURE_NO_WARNINGS
|
||
|
#endif
|
||
|
|
||
|
#include <string.h>
|
||
|
#include <assert.h>
|
||
|
#include <stdarg.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <stdio.h>
|
||
|
#include <time.h>
|
||
|
#include <float.h>
|
||
|
#include <math.h>
|
||
|
|
||
|
#define FCT_MAX_NAME 256
|
||
|
#define FCT_MAX_LOG_LINE 256
|
||
|
|
||
|
#define nbool_t int
|
||
|
#define FCT_TRUE 1
|
||
|
#define FCT_FALSE 0
|
||
|
|
||
|
/* Forward declarations. The following forward declarations are required
|
||
|
because there is a inter-relationship between certain objects that
|
||
|
just can not be untwined. */
|
||
|
typedef struct _fct_logger_i fct_logger_i;
|
||
|
typedef struct _fct_standard_logger_t fct_standard_logger_t;
|
||
|
typedef struct _fct_minimal_logger_t fct_minimal_logger_t;
|
||
|
typedef struct _fctchk_t fctchk_t;
|
||
|
typedef struct _fct_test_t fct_test_t;
|
||
|
typedef struct _fct_ts_t fct_ts_t;
|
||
|
typedef struct _fctkern_t fctkern_t;
|
||
|
|
||
|
/* Forward declare some functions used throughout. */
|
||
|
static fct_standard_logger_t *
|
||
|
fct_standard_logger__new(void);
|
||
|
|
||
|
static void
|
||
|
fct_logger__del(fct_logger_i *logger);
|
||
|
|
||
|
static void
|
||
|
fct_logger__on_cndtn(fct_logger_i *self, fctchk_t const *chk);
|
||
|
|
||
|
static void
|
||
|
fct_logger__on_test_start(fct_logger_i *logger, fct_test_t const *test);
|
||
|
|
||
|
static void
|
||
|
fct_logger__on_test_end(fct_logger_i *logger, fct_test_t const *test);
|
||
|
|
||
|
static void
|
||
|
fct_logger__on_test_suite_start(fct_logger_i *logger, fct_ts_t const *ts);
|
||
|
|
||
|
static void
|
||
|
fct_logger__on_test_suite_end(fct_logger_i *logger, fct_ts_t const *ts);
|
||
|
|
||
|
static void
|
||
|
fct_logger__on_fct_start(fct_logger_i *logger, fctkern_t const *kern);
|
||
|
|
||
|
static void
|
||
|
fct_logger__on_fct_end(fct_logger_i *logger, fctkern_t const *kern);
|
||
|
|
||
|
|
||
|
|
||
|
/* Explicitly indicate a no-op */
|
||
|
#define fct_pass()
|
||
|
|
||
|
#define fct_unused(x) ((void) (x));
|
||
|
|
||
|
/* This is just a little trick to let me put comments inside of macros. I
|
||
|
really only want to bother with this when we are "unwinding" the macros
|
||
|
for debugging purposes. */
|
||
|
#if defined(FCT_CONF_UNWIND)
|
||
|
# define _fct_cmt(string) {char*_=string;}
|
||
|
#else
|
||
|
# define _fct_cmt(string)
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
--------------------------------------------------------
|
||
|
UTILITIES
|
||
|
--------------------------------------------------------
|
||
|
*/
|
||
|
|
||
|
/* Utility for truncated, safe string copies. */
|
||
|
static void
|
||
|
fct_safe_str_cpy(char *dst, char const *src, size_t num)
|
||
|
{
|
||
|
assert( dst != NULL );
|
||
|
assert( src != NULL );
|
||
|
assert( num > 0 );
|
||
|
strncpy(dst, src, num);
|
||
|
dst[num-1] = '\0';
|
||
|
}
|
||
|
|
||
|
/* Isolate the snprintf implemenation. */
|
||
|
int
|
||
|
fct_snprintf(char *buffer, size_t buffer_len, char *format, ...)
|
||
|
{
|
||
|
int count =0;
|
||
|
va_list args;
|
||
|
va_start(args, format);
|
||
|
count =vsnprintf(buffer, buffer_len, format, args);
|
||
|
va_end(args);
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
/* A very, very simple "filter". This just compares the supplied prefix
|
||
|
against the test_str, to see if they both have the same starting
|
||
|
characters. If they do we return true, otherwise we return false. If the
|
||
|
prefix is a blank string or NULL, then it will return FCT_TRUE.*/
|
||
|
static nbool_t
|
||
|
fct_filter_pass(char const *prefix, char const *test_str)
|
||
|
{
|
||
|
nbool_t is_match = FCT_FALSE;
|
||
|
char const *prefix_p;
|
||
|
char const *test_str_p;
|
||
|
|
||
|
/* If you got nothing to test against, why test? */
|
||
|
assert( test_str != NULL );
|
||
|
|
||
|
/* When the prefix is NULL or blank, we always return FCT_TRUE. */
|
||
|
if ( prefix == NULL || prefix[0] == '\0' )
|
||
|
{
|
||
|
return FCT_TRUE;
|
||
|
}
|
||
|
|
||
|
/* Iterate through both character arrays at the same time. We are
|
||
|
going to play a game and see if we can beat the house. */
|
||
|
for ( prefix_p = prefix, test_str_p = test_str;
|
||
|
*prefix_p != '\0' && *test_str_p != '\0';
|
||
|
++prefix_p, ++test_str_p )
|
||
|
{
|
||
|
is_match = *prefix_p == *test_str_p;
|
||
|
if ( !is_match )
|
||
|
{
|
||
|
break; /* Quit the first time we don't match. */
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* If the iterator for the test_str is pointing at the null char, and
|
||
|
the iterator for the prefix string is not, then the prefix string is
|
||
|
larger than the actual test string, and therefore we failed to pass the
|
||
|
filter. */
|
||
|
if ( *test_str_p == '\0' && *prefix_p != '\0' )
|
||
|
{
|
||
|
return FCT_FALSE;
|
||
|
}
|
||
|
|
||
|
/* is_match will be set to the either FCT_TRUE if we kicked of the loop
|
||
|
early because our filter ran out of characters or FCT_FALSE if we
|
||
|
encountered a mismatch before our filter ran out of characters. */
|
||
|
return is_match;
|
||
|
}
|
||
|
|
||
|
/* Returns true if two reals are equal. */
|
||
|
nbool_t
|
||
|
fct_real_eq(double v1, double v2)
|
||
|
{
|
||
|
return (nbool_t)(fabs(v1 - v2) < DBL_EPSILON);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
--------------------------------------------------------
|
||
|
TIMER
|
||
|
--------------------------------------------------------
|
||
|
*/
|
||
|
|
||
|
typedef struct _fct_timer_t fct_timer_t;
|
||
|
struct _fct_timer_t {
|
||
|
clock_t start;
|
||
|
clock_t stop;
|
||
|
double duration;
|
||
|
};
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_timer__init(fct_timer_t *timer) {
|
||
|
assert(timer != NULL);
|
||
|
memset(timer, 0, sizeof(fct_timer_t));
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_timer__start(fct_timer_t *timer) {
|
||
|
assert(timer != NULL);
|
||
|
timer->start = clock();
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_timer__stop(fct_timer_t *timer) {
|
||
|
assert(timer != NULL);
|
||
|
timer->stop = clock();
|
||
|
timer->duration = (double) (timer->stop - timer->start) / CLOCKS_PER_SEC;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Returns the time in seconds. */
|
||
|
static double
|
||
|
fct_timer__duration(fct_timer_t *timer) {
|
||
|
assert( timer != NULL );
|
||
|
return timer->duration;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
--------------------------------------------------------
|
||
|
GENERIC LIST
|
||
|
--------------------------------------------------------
|
||
|
*/
|
||
|
|
||
|
/* For now we will just keep it at a linear growth rate. */
|
||
|
#define FCT_LIST_GROWTH_FACTOR 2
|
||
|
|
||
|
/* Starting size for the list, to keep it simple we will start
|
||
|
at a reasonable size. */
|
||
|
#define FCT_LIST_START_SIZE 2
|
||
|
|
||
|
/* Helper macros for quickly iterating through a list. You should be able
|
||
|
to do something like,
|
||
|
|
||
|
NLIST_FOREACH_BGN(fct_logger_i*, logger, my_list)
|
||
|
{
|
||
|
fct_logger__on_blah(logger);
|
||
|
}
|
||
|
NLIST_FOREACH_END();
|
||
|
|
||
|
*/
|
||
|
#define NLIST_FOREACH_BGN(Type, Var, List)\
|
||
|
{\
|
||
|
if ( List != NULL ) {\
|
||
|
size_t item_i##Var;\
|
||
|
size_t num_items##Var = nlist__size(List);\
|
||
|
for( item_i##Var =0; item_i##Var != num_items##Var; ++item_i##Var )\
|
||
|
{\
|
||
|
Type Var = (Type) nlist__at((List), item_i##Var);
|
||
|
|
||
|
#define NLIST_FOREACH_END() }}}
|
||
|
|
||
|
/* Used to manage a list of loggers. This works mostly like
|
||
|
the STL vector, where the array grows as more items are
|
||
|
appended. */
|
||
|
typedef struct _nlist_t nlist_t;
|
||
|
struct _nlist_t
|
||
|
{
|
||
|
/* Item's are stored as pointers to void. */
|
||
|
void **itm_list;
|
||
|
|
||
|
/* Indicates the number of element's in the array. */
|
||
|
size_t avail_itm_num;
|
||
|
|
||
|
/* Indicates the number of actually elements in the array. */
|
||
|
size_t used_itm_num;
|
||
|
};
|
||
|
|
||
|
static nlist_t *
|
||
|
nlist_new(void)
|
||
|
{
|
||
|
nlist_t *list = (nlist_t*)calloc(1, sizeof(nlist_t));
|
||
|
assert( list != NULL && "memory check");
|
||
|
|
||
|
list->itm_list = (void**)malloc(sizeof(void*)*FCT_LIST_START_SIZE);
|
||
|
assert( list->itm_list != NULL && "memory check");
|
||
|
|
||
|
list->avail_itm_num =FCT_LIST_START_SIZE;
|
||
|
list->used_itm_num =0;
|
||
|
return list;
|
||
|
}
|
||
|
|
||
|
typedef void (*on_del_t)(void*);
|
||
|
|
||
|
/* Cleans up list, and applies `on_del` to each item in the list.
|
||
|
If on_del is NULL, it will not be applied. If `list` is NULL this
|
||
|
function does nothing. */
|
||
|
static void
|
||
|
nlist__del(nlist_t *list, on_del_t on_del)
|
||
|
{
|
||
|
size_t itm_i =0;
|
||
|
|
||
|
if ( list == NULL ) { return; }
|
||
|
|
||
|
/* Walk through the list applying the destroy function, if it was
|
||
|
defined. */
|
||
|
if ( on_del != NULL )
|
||
|
{
|
||
|
for ( itm_i =0; itm_i != list->used_itm_num; ++itm_i )
|
||
|
{
|
||
|
on_del(list->itm_list[itm_i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
free(list->itm_list);
|
||
|
free(list);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Returns the number of elements within the list. */
|
||
|
static size_t
|
||
|
nlist__size(nlist_t const *list)
|
||
|
{
|
||
|
assert( list != NULL );
|
||
|
return list->used_itm_num;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Returns the item at idx, asserts otherwise. */
|
||
|
static void*
|
||
|
nlist__at(nlist_t const *list, size_t idx)
|
||
|
{
|
||
|
assert( list != NULL );
|
||
|
assert( idx < list->used_itm_num );
|
||
|
return list->itm_list[idx];
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
nlist__append(nlist_t *list, void *itm)
|
||
|
{
|
||
|
assert( list != NULL );
|
||
|
assert( list->itm_list != NULL );
|
||
|
assert( list->avail_itm_num != 0 );
|
||
|
|
||
|
/* If we ran out of room, then the last increment should be equal to the
|
||
|
available space, in this case we need to grow a little more. */
|
||
|
if ( list->used_itm_num == list->avail_itm_num )
|
||
|
{
|
||
|
list->avail_itm_num = list->avail_itm_num*FCT_LIST_GROWTH_FACTOR;
|
||
|
list->itm_list = (void**)realloc(
|
||
|
list->itm_list, sizeof(void*)*list->avail_itm_num
|
||
|
);
|
||
|
assert( list->itm_list != NULL && "memory check");
|
||
|
}
|
||
|
|
||
|
list->itm_list[list->used_itm_num] = itm;
|
||
|
++(list->used_itm_num);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
-----------------------------------------------------------
|
||
|
A SINGLE CHECK
|
||
|
-----------------------------------------------------------
|
||
|
This defines a single check. It indicates what the check was,
|
||
|
and where it occurred. A "Test" object will have-a bunch
|
||
|
of "checks".
|
||
|
*/
|
||
|
|
||
|
struct _fctchk_t {
|
||
|
/* This string that represents the condition. */
|
||
|
char cndtn[FCT_MAX_LOG_LINE];
|
||
|
|
||
|
/* These indicate where the condition occurred. */
|
||
|
char file[FCT_MAX_LOG_LINE];
|
||
|
|
||
|
int lineno;
|
||
|
|
||
|
nbool_t is_pass;
|
||
|
};
|
||
|
|
||
|
#define fctchk__is_pass(_CHK_) ((_CHK_)->is_pass)
|
||
|
#define fctchk__file(_CHK_) ((_CHK_)->file)
|
||
|
#define fctchk__lineno(_CHK_) ((_CHK_)->lineno)
|
||
|
#define fctchk__cndtn(_CHK_) ((_CHK_)->cndtn)
|
||
|
|
||
|
|
||
|
static fctchk_t*
|
||
|
fctchk_new(char const *cndtn, char const *file, int lineno, nbool_t is_pass)
|
||
|
{
|
||
|
fctchk_t *chk = NULL;
|
||
|
|
||
|
assert( cndtn != NULL );
|
||
|
assert( file != NULL );
|
||
|
assert( lineno > 0 );
|
||
|
|
||
|
chk = (fctchk_t*)calloc(1, sizeof(fctchk_t));
|
||
|
assert( chk != NULL && "out of memory");
|
||
|
if ( chk == NULL ) { return NULL; }
|
||
|
|
||
|
fct_safe_str_cpy(chk->cndtn, cndtn, FCT_MAX_LOG_LINE);
|
||
|
fct_safe_str_cpy(chk->file, file, FCT_MAX_LOG_LINE);
|
||
|
chk->lineno = lineno;
|
||
|
|
||
|
chk->is_pass =is_pass;
|
||
|
|
||
|
return chk;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Cleans up a "check" object. If the `chk` is NULL, this function does
|
||
|
nothing. */
|
||
|
static void
|
||
|
fctchk__del(fctchk_t *chk)
|
||
|
{
|
||
|
if ( chk == NULL ) { return; }
|
||
|
free( chk );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
-----------------------------------------------------------
|
||
|
A TEST
|
||
|
-----------------------------------------------------------
|
||
|
A suite will have-a list of tests. Where each test will have-a
|
||
|
list of failed and passed checks.
|
||
|
*/
|
||
|
|
||
|
struct _fct_test_t {
|
||
|
/* List of failed and passed "checks" (fctchk_t). Two seperate
|
||
|
lists make it faster to determine how many checks passed and how
|
||
|
many checks failed. */
|
||
|
nlist_t *failed_chks;
|
||
|
nlist_t *passed_chks;
|
||
|
|
||
|
/* The name of the test case. */
|
||
|
char name[FCT_MAX_NAME];
|
||
|
};
|
||
|
|
||
|
#define fct_test__name(_TEST_) ((_TEST_)->name)
|
||
|
|
||
|
static fct_test_t*
|
||
|
fct_test_new(char const *name) {
|
||
|
fct_test_t *test =NULL;
|
||
|
|
||
|
test = (fct_test_t*)malloc(sizeof(fct_test_t));
|
||
|
assert( test != NULL && "out of memory");
|
||
|
|
||
|
fct_safe_str_cpy(test->name, name, FCT_MAX_NAME);
|
||
|
|
||
|
test->failed_chks = nlist_new();
|
||
|
test->passed_chks = nlist_new();
|
||
|
assert( test->failed_chks != NULL && "out of memory");
|
||
|
assert( test->passed_chks != NULL && "out of memory");
|
||
|
|
||
|
return test;
|
||
|
}
|
||
|
|
||
|
|
||
|
static nbool_t
|
||
|
fct_test__is_pass(fct_test_t const *test)
|
||
|
{
|
||
|
assert( test != NULL );
|
||
|
return nlist__size(test->failed_chks) == 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_test__add(fct_test_t *test, fctchk_t *chk)
|
||
|
{
|
||
|
|
||
|
assert( test != NULL );
|
||
|
assert( chk != NULL );
|
||
|
|
||
|
if ( fctchk__is_pass(chk) )
|
||
|
{
|
||
|
nlist__append(test->passed_chks, (void*)chk);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nlist__append(test->failed_chks, (void*)chk);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Returns the number of checks made throughout the test. */
|
||
|
static int
|
||
|
fct_test__chk_cnt(fct_test_t const *test)
|
||
|
{
|
||
|
assert( test != NULL );
|
||
|
return nlist__size(test->failed_chks) + nlist__size(test->passed_chks);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_test__del(fct_test_t *test)
|
||
|
{
|
||
|
if (test == NULL ) { return; }
|
||
|
nlist__del(test->passed_chks, (on_del_t)fctchk__del);
|
||
|
nlist__del(test->failed_chks, (on_del_t)fctchk__del);
|
||
|
free(test);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
-----------------------------------------------------------
|
||
|
TEST SUITE (TS)
|
||
|
-----------------------------------------------------------
|
||
|
*/
|
||
|
|
||
|
|
||
|
/* The different types of 'modes' that a test suite can be in.
|
||
|
|
||
|
While the test suite is iterating through all the tests, its "State"
|
||
|
can change from "setup mode", to "test mode" to "tear down" mode.
|
||
|
These help to indicate what mode are currently in. Think of it as a
|
||
|
basic FSM.
|
||
|
|
||
|
if the count was 0 end
|
||
|
+--------->---------------------> ending_mode-----+
|
||
|
| ^
|
||
|
^ |
|
||
|
start | [if no more tests]
|
||
|
| | |
|
||
|
+-count_mode -> setup_mode -> test_mode -> teardown_mode
|
||
|
^ |
|
||
|
+-----------<---------------+
|
||
|
*/
|
||
|
enum ts_mode {
|
||
|
ts_mode_cnt, /* To setup when done counting. */
|
||
|
ts_mode_setup, /* To test when done setup. */
|
||
|
ts_mode_teardown, /* To ending mode, when no more tests. */
|
||
|
ts_mode_test, /* To tear down mode. */
|
||
|
ts_mode_ending, /* To ... */
|
||
|
ts_mode_end /* .. The End. */
|
||
|
};
|
||
|
|
||
|
/* Types of modes the test could be in. */
|
||
|
typedef enum {
|
||
|
fct_test_status_SUCCESS,
|
||
|
fct_test_status_FAILURE
|
||
|
} fct_test_status;
|
||
|
|
||
|
|
||
|
struct _fct_ts_t {
|
||
|
/* For counting our 'current' test number, and the total number of
|
||
|
tests. */
|
||
|
int curr_test_num;
|
||
|
int total_test_num;
|
||
|
|
||
|
/* Keeps track of the current state of the object while it is walking
|
||
|
through its "FSM" */
|
||
|
enum ts_mode mode;
|
||
|
|
||
|
/* The name of the test suite. */
|
||
|
char name[FCT_MAX_NAME];
|
||
|
|
||
|
/* List of tests that where executed within the test suite. */
|
||
|
nlist_t *test_list;
|
||
|
};
|
||
|
|
||
|
|
||
|
#define fct_ts__is_setup_mode(ts) ((ts)->mode == ts_mode_setup)
|
||
|
#define fct_ts__is_teardown_mode(ts) ((ts)->mode == ts_mode_teardown)
|
||
|
#define fct_ts__is_test_mode(ts) ((ts)->mode == ts_mode_test)
|
||
|
#define fct_ts__is_ending_mode(ts) ((ts)->mode == ts_mode_ending)
|
||
|
#define fct_ts__is_end(ts) ((ts)->mode == ts_mode_end)
|
||
|
#define fct_ts__is_cnt_mode(ts) ((ts)->mode == ts_mode_cnt)
|
||
|
#define fct_ts__name(ts) ((ts)->name)
|
||
|
|
||
|
|
||
|
static fct_ts_t *
|
||
|
fct_ts_new(char const *name) {
|
||
|
fct_ts_t *ts =NULL;
|
||
|
ts = (fct_ts_t*)calloc(1, sizeof(fct_ts_t));
|
||
|
assert( ts != NULL );
|
||
|
|
||
|
fct_safe_str_cpy(ts->name, name, FCT_MAX_NAME);
|
||
|
ts->mode = ts_mode_cnt;
|
||
|
|
||
|
ts->test_list = nlist_new();
|
||
|
assert( ts->test_list != NULL && "no memory");
|
||
|
|
||
|
return ts;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
fct_ts__del(fct_ts_t *ts) {
|
||
|
if ( ts == NULL ) { return; }
|
||
|
free(ts);
|
||
|
}
|
||
|
|
||
|
/* Flag a test suite as complete. It will no longer accept any more tests. */
|
||
|
#define fct_ts__end(_TS_) ((_TS_)->mode == ts_mode_end)
|
||
|
|
||
|
|
||
|
static nbool_t
|
||
|
fct_ts__is_more_tests(fct_ts_t const *ts) {
|
||
|
assert( ts != NULL );
|
||
|
assert( !fct_ts__is_end(ts) );
|
||
|
return ts->curr_test_num < ts->total_test_num;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Indicates that we have started a test case. */
|
||
|
static void
|
||
|
fct_ts__test_begin(fct_ts_t *ts) {
|
||
|
assert( !fct_ts__is_end(ts) );
|
||
|
++(ts->curr_test_num);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Takes OWNERSHIP of a test object, and warehouses it for later stat
|
||
|
generation. */
|
||
|
static void
|
||
|
fct_ts__add_test(fct_ts_t *ts, fct_test_t *test) {
|
||
|
assert( ts != NULL && "invalid arg");
|
||
|
assert( test != NULL && "invalid arg");
|
||
|
assert( !fct_ts__is_end(ts) );
|
||
|
nlist__append(ts->test_list, test);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_ts__test_end(fct_ts_t *ts) {
|
||
|
assert( ts != NULL );
|
||
|
assert( fct_ts__is_test_mode(ts) && "not in test mode, can't end!" );
|
||
|
|
||
|
/* After a test has completed, move to teardown mode. */
|
||
|
ts->mode = ts_mode_teardown;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Increments the internal count by 1. */
|
||
|
static void
|
||
|
fct_ts__inc_total_test_num(fct_ts_t *ts)
|
||
|
{
|
||
|
assert( ts != NULL );
|
||
|
assert( fct_ts__is_cnt_mode(ts) );
|
||
|
assert( !fct_ts__is_end(ts) );
|
||
|
++(ts->total_test_num);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Flags the end of the setup, which implies we are going to move into
|
||
|
setup mode. You must be already in setup mode for this to work! */
|
||
|
static void
|
||
|
fct_ts__setup_end(fct_ts_t *ts)
|
||
|
{
|
||
|
assert( fct_ts__is_setup_mode(ts) );
|
||
|
assert( !fct_ts__is_end(ts) );
|
||
|
ts->mode = ts_mode_test;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* This cndtn is set when we have iterated through all the tests, and
|
||
|
there was nothing more to do. */
|
||
|
static void
|
||
|
fct_ts__ending(fct_ts_t *ts)
|
||
|
{
|
||
|
// We can only go from 'test-mode' to 'end-down' mode.
|
||
|
assert( fct_ts__is_test_mode(ts) );
|
||
|
assert( !fct_ts__is_end(ts) );
|
||
|
ts->mode = ts_mode_ending;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Flags the end of the teardown, which implies we are going to move
|
||
|
into setup mode (for the next 'iteration'). */
|
||
|
static void
|
||
|
fct_ts__teardown_end(fct_ts_t *ts)
|
||
|
{
|
||
|
assert( fct_ts__is_teardown_mode(ts) );
|
||
|
assert( !fct_ts__is_end(ts) );
|
||
|
/* We have to decide if we should keep on testing by moving into tear down
|
||
|
mode or if we have reached the real end and should be moving into the
|
||
|
ending mode. */
|
||
|
if ( fct_ts__is_more_tests(ts) ) {
|
||
|
ts->mode = ts_mode_setup;
|
||
|
}
|
||
|
else {
|
||
|
ts->mode = ts_mode_ending;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Flags the end of the counting, and proceeding to the first setup.
|
||
|
Consider the special case when a test suite has NO tests in it, in
|
||
|
that case we will have a current count that is zero, in which case
|
||
|
we can skip right to 'ending'. */
|
||
|
static void
|
||
|
fct_ts__cnt_end(fct_ts_t *ts)
|
||
|
{
|
||
|
assert( ts != NULL );
|
||
|
assert( fct_ts__is_cnt_mode(ts) );
|
||
|
assert( !fct_ts__is_end(ts) );
|
||
|
if (ts->total_test_num == 0 ) {
|
||
|
ts->mode = ts_mode_ending;
|
||
|
}
|
||
|
else {
|
||
|
ts->mode = ts_mode_setup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static nbool_t
|
||
|
fct_ts__is_test_cnt(fct_ts_t const *ts, int test_num)
|
||
|
{
|
||
|
assert( ts != NULL );
|
||
|
assert( 0 <= test_num );
|
||
|
assert( test_num < ts->total_test_num );
|
||
|
assert( !fct_ts__is_end(ts) );
|
||
|
|
||
|
/* As we roll through the tests we increment the count. With this
|
||
|
count we can decide if we need to execute a test or not. */
|
||
|
return test_num == ts->curr_test_num;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Returns the # of tests on the FCT TS object. This is the actual
|
||
|
# of tests executed. */
|
||
|
static int
|
||
|
fct_ts__tst_cnt(fct_ts_t const *ts)
|
||
|
{
|
||
|
assert( ts != NULL );
|
||
|
assert( !fct_ts__is_end(ts) );
|
||
|
return nlist__size(ts->test_list);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Returns the # of tests in the TS object that passed. */
|
||
|
static int
|
||
|
fct_ts__tst_cnt_passed(fct_ts_t const *ts)
|
||
|
{
|
||
|
int tally =0;
|
||
|
|
||
|
assert( ts != NULL );
|
||
|
assert( !fct_ts__is_end(ts) );
|
||
|
|
||
|
NLIST_FOREACH_BGN(fct_test_t*, test, ts->test_list)
|
||
|
{
|
||
|
if ( fct_test__is_pass(test) )
|
||
|
{
|
||
|
tally += 1;
|
||
|
}
|
||
|
}
|
||
|
NLIST_FOREACH_END();
|
||
|
return tally;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Returns the # of checks made throughout a test suite. */
|
||
|
static int
|
||
|
fct_ts__chk_cnt(fct_ts_t const *ts)
|
||
|
{
|
||
|
int tally =0;
|
||
|
|
||
|
assert( ts != NULL );
|
||
|
|
||
|
NLIST_FOREACH_BGN(fct_test_t *, test, ts->test_list)
|
||
|
{
|
||
|
tally += fct_test__chk_cnt(test);
|
||
|
}
|
||
|
NLIST_FOREACH_END();
|
||
|
return tally;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
--------------------------------------------------------
|
||
|
FCT KERNAL
|
||
|
--------------------------------------------------------
|
||
|
|
||
|
The "fctkern" is a singleton that is defined throughout the
|
||
|
system.
|
||
|
*/
|
||
|
|
||
|
struct _fctkern_t {
|
||
|
/* This is an list of loggers that can be used in the fct system.
|
||
|
You/ can attach _MAX_LOGGERS to any framework. */
|
||
|
nlist_t *logger_list;
|
||
|
|
||
|
/* This is a list of prefix's that can be used to determine if a
|
||
|
test is should be run or not. */
|
||
|
nlist_t *prefix_list;
|
||
|
|
||
|
/* This is a list of test suites that where generated throughout the
|
||
|
testing process. */
|
||
|
nlist_t *ts_list;
|
||
|
};
|
||
|
|
||
|
|
||
|
/* Returns the number of filters defined for the fct kernal. */
|
||
|
#define fctkern__filter_cnt(_NK_) (nlist__size((_NK_)->prefix_list))
|
||
|
|
||
|
|
||
|
static void
|
||
|
fctkern__add_logger(fctkern_t *fct, fct_logger_i *logger_owns)
|
||
|
{
|
||
|
assert(fct != NULL && "invalid arg");
|
||
|
assert(logger_owns != NULL && "invalid arg");
|
||
|
nlist__append(fct->logger_list, logger_owns);
|
||
|
assert( fct->logger_list != NULL && "memory check");
|
||
|
}
|
||
|
|
||
|
/* Appends a prefix filter that is used to determine if a test can
|
||
|
be executed or not. If the test starts with the same characters as
|
||
|
the prefix, then it should be "runnable". The prefix filter must be
|
||
|
a non-NULL, non-Blank string. */
|
||
|
static void
|
||
|
fctkern__add_prefix_filter(fctkern_t const *fct, char const *prefix_filter)
|
||
|
{
|
||
|
char *filter =NULL;
|
||
|
int filter_len =0;
|
||
|
|
||
|
assert( fct != NULL && "invalid arg" );
|
||
|
assert( prefix_filter != NULL && "invalid arg" );
|
||
|
assert( strlen(prefix_filter) > 0 && "invalid arg" );
|
||
|
|
||
|
/* First we make a copy of the prefix, then we store it away
|
||
|
in our little list. */
|
||
|
filter_len = strlen(prefix_filter);
|
||
|
filter = (char*)malloc(sizeof(char)*(filter_len+1));
|
||
|
strncpy(filter, prefix_filter, filter_len);
|
||
|
filter[filter_len] = '\0';
|
||
|
|
||
|
nlist__append(fct->prefix_list, (void*)filter);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Parses the command line and sets up the framework. The argc and argv
|
||
|
should be directly from the program's main. */
|
||
|
static void
|
||
|
fctkern_init(fctkern_t *nk, int argc, char *argv[])
|
||
|
{
|
||
|
fct_logger_i *standard_logger = NULL;
|
||
|
int arg_i =0;
|
||
|
|
||
|
assert( nk != NULL );
|
||
|
|
||
|
memset(nk, 0, sizeof(fctkern_t));
|
||
|
|
||
|
nk->logger_list = nlist_new();
|
||
|
nk->prefix_list = nlist_new();
|
||
|
nk->ts_list = nlist_new();
|
||
|
|
||
|
/* Low-budget memory check for now. */
|
||
|
assert( nk->logger_list != NULL );
|
||
|
assert( nk->prefix_list != NULL );
|
||
|
assert( nk->ts_list != NULL );
|
||
|
|
||
|
standard_logger = (fct_logger_i*) fct_standard_logger__new();
|
||
|
assert( standard_logger != NULL && "no memory!");
|
||
|
|
||
|
fctkern__add_logger(nk, standard_logger);
|
||
|
standard_logger = NULL; /* Owned by the nk list. */
|
||
|
|
||
|
/* Our basic parser. For now we just take each 'argv' and assume
|
||
|
that it is a prefix filter. Notice we start at argument 1, since
|
||
|
we don't care about the *name* of the program. */
|
||
|
for ( arg_i =1; arg_i < argc; ++arg_i )
|
||
|
{
|
||
|
fctkern__add_prefix_filter(nk, argv[arg_i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Takes OWNERSHIP of the test suite after we have finished executing
|
||
|
its contents. This way we can build up all kinds of summaries at the end
|
||
|
of a run. */
|
||
|
static void
|
||
|
fctkern__add_ts(fctkern_t *nk, fct_ts_t *ts) {
|
||
|
assert( nk != NULL );
|
||
|
assert( ts != NULL );
|
||
|
nlist__append(nk->ts_list, ts);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/* Returns FCT_TRUE if the supplied test_name passes the filters set on
|
||
|
this test suite. If there are no filters, we return FCT_TRUE always. */
|
||
|
static nbool_t
|
||
|
fctkern__pass_filter(fctkern_t *nk, char const *test_name) {
|
||
|
int prefix_i =0;
|
||
|
int prefix_list_size =0;
|
||
|
|
||
|
assert( nk != NULL && "invalid arg");
|
||
|
assert( test_name != NULL );
|
||
|
assert( strlen(test_name) > 0 );
|
||
|
|
||
|
prefix_list_size = fctkern__filter_cnt(nk);
|
||
|
|
||
|
/* If there is no filter list, then we return FCT_TRUE always. */
|
||
|
if ( prefix_list_size == 0 ) {
|
||
|
return FCT_TRUE;
|
||
|
}
|
||
|
|
||
|
/* Iterate through the prefix filter list, and see if we have
|
||
|
anything that does not pass. All we require is ONE item that
|
||
|
passes the test in order for us to succeed here. */
|
||
|
for ( prefix_i = 0; prefix_i != prefix_list_size; ++prefix_i ) {
|
||
|
char const *prefix = (char const*)nlist__at(nk->prefix_list, prefix_i);
|
||
|
nbool_t pass = fct_filter_pass(prefix, test_name);
|
||
|
if ( pass ) {
|
||
|
return FCT_TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Otherwise, we never managed to find a prefix that satisfied the
|
||
|
supplied test name. Therefore we have failed to pass to the filter
|
||
|
list test. */
|
||
|
return FCT_FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Returns the number of tests that were performed. */
|
||
|
static int
|
||
|
fctkern__tst_cnt(fctkern_t const *nk)
|
||
|
{
|
||
|
int tally =0;
|
||
|
assert( nk != NULL );
|
||
|
|
||
|
NLIST_FOREACH_BGN(fct_ts_t *, ts, nk->ts_list)
|
||
|
{
|
||
|
tally += fct_ts__tst_cnt(ts);
|
||
|
}
|
||
|
NLIST_FOREACH_END();
|
||
|
return tally;
|
||
|
}
|
||
|
|
||
|
/* Returns the number of tests that passed. */
|
||
|
static int
|
||
|
fctkern__tst_cnt_passed(fctkern_t const *nk)
|
||
|
{
|
||
|
int tally =0;
|
||
|
assert( nk != NULL );
|
||
|
|
||
|
NLIST_FOREACH_BGN(fct_ts_t*, ts, nk->ts_list)
|
||
|
{
|
||
|
tally += fct_ts__tst_cnt_passed(ts);
|
||
|
}
|
||
|
NLIST_FOREACH_END();
|
||
|
|
||
|
return tally;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Returns the number of tests that failed. */
|
||
|
static int
|
||
|
fctkern__tst_cnt_failed(fctkern_t const *nk)
|
||
|
{
|
||
|
/* Keep it simple for now and just do a little math. */
|
||
|
int total =0;
|
||
|
int passed =0;
|
||
|
int failed =0;
|
||
|
|
||
|
assert( nk != NULL );
|
||
|
|
||
|
total = fctkern__tst_cnt(nk);
|
||
|
passed = fctkern__tst_cnt_passed(nk);
|
||
|
|
||
|
failed = total - passed;
|
||
|
|
||
|
return failed;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Returns the number of checks made throughout the entire test. */
|
||
|
static int
|
||
|
fctkern__chk_cnt(fctkern_t const *nk)
|
||
|
{
|
||
|
int tally =0;
|
||
|
assert( nk != NULL );
|
||
|
|
||
|
NLIST_FOREACH_BGN(fct_ts_t *, ts, nk->ts_list)
|
||
|
{
|
||
|
tally += fct_ts__chk_cnt(ts);
|
||
|
}
|
||
|
NLIST_FOREACH_END();
|
||
|
return tally;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Indicates the very end of all the tests. */
|
||
|
static void
|
||
|
fctkern__end(fctkern_t *fct)
|
||
|
{
|
||
|
fct_unused(fct);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Cleans up the contents of a fctkern. NULL does nothing. */
|
||
|
static void
|
||
|
fctkern__final(fctkern_t *fct)
|
||
|
{
|
||
|
if ( fct == NULL ) { return; }
|
||
|
|
||
|
nlist__del(fct->logger_list, (on_del_t)fct_logger__del);
|
||
|
|
||
|
/* The prefix list is a list of malloc'd strings. */
|
||
|
nlist__del(fct->prefix_list, (on_del_t)free);
|
||
|
|
||
|
nlist__del(fct->ts_list, (on_del_t)fct_ts__del);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fctkern__log_suite_start(fctkern_t *kern, fct_ts_t const *ts)
|
||
|
{
|
||
|
assert( kern != NULL );
|
||
|
assert( ts != NULL );
|
||
|
NLIST_FOREACH_BGN(fct_logger_i*, logger, kern->logger_list)
|
||
|
{
|
||
|
fct_logger__on_test_suite_start(logger, ts);
|
||
|
}
|
||
|
NLIST_FOREACH_END();
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fctkern__log_suite_end(fctkern_t *kern, fct_ts_t const *ts)
|
||
|
{
|
||
|
assert( kern != NULL );
|
||
|
assert( ts != NULL );
|
||
|
NLIST_FOREACH_BGN(fct_logger_i*, logger, kern->logger_list)
|
||
|
{
|
||
|
fct_logger__on_test_suite_end(logger, ts);
|
||
|
}
|
||
|
NLIST_FOREACH_END();
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fctkern__log_chk(fctkern_t *kern, fctchk_t const *chk)
|
||
|
{
|
||
|
assert( kern != NULL );
|
||
|
assert( chk != NULL );
|
||
|
|
||
|
NLIST_FOREACH_BGN(fct_logger_i*, logger, kern->logger_list)
|
||
|
{
|
||
|
fct_logger__on_cndtn(logger, chk);
|
||
|
}
|
||
|
NLIST_FOREACH_END();
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Called whenever a test is started. */
|
||
|
static void
|
||
|
fctkern__log_test_start(fctkern_t *kern, fct_test_t const *test)
|
||
|
{
|
||
|
assert( kern != NULL );
|
||
|
assert( test != NULL );
|
||
|
NLIST_FOREACH_BGN(fct_logger_i*, logger, kern->logger_list)
|
||
|
{
|
||
|
fct_logger__on_test_start(logger, test);
|
||
|
}
|
||
|
NLIST_FOREACH_END();
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fctkern__log_test_end(fctkern_t *kern, fct_test_t const *test)
|
||
|
{
|
||
|
assert( kern != NULL );
|
||
|
assert( test != NULL );
|
||
|
NLIST_FOREACH_BGN(fct_logger_i*, logger, kern->logger_list)
|
||
|
{
|
||
|
fct_logger__on_test_end(logger, test);
|
||
|
}
|
||
|
NLIST_FOREACH_END();
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fctkern__log_start(fctkern_t *kern)
|
||
|
{
|
||
|
assert( kern != NULL );
|
||
|
NLIST_FOREACH_BGN(fct_logger_i*, logger, kern->logger_list)
|
||
|
{
|
||
|
fct_logger__on_fct_start(logger, kern);
|
||
|
}
|
||
|
NLIST_FOREACH_END();
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fctkern__log_end(fctkern_t *kern)
|
||
|
{
|
||
|
assert( kern != NULL );
|
||
|
NLIST_FOREACH_BGN(fct_logger_i*, logger, kern->logger_list)
|
||
|
{
|
||
|
fct_logger__on_fct_end(logger, kern);
|
||
|
}
|
||
|
NLIST_FOREACH_END();
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
-----------------------------------------------------------
|
||
|
LOGGER INTERFACE
|
||
|
|
||
|
Defines an interface to a logging system. A logger
|
||
|
must define the following functions in order to hook
|
||
|
into the logging system.
|
||
|
|
||
|
See the "Standard Logger" and "Minimal Logger" as examples
|
||
|
of the implementation.
|
||
|
-----------------------------------------------------------
|
||
|
*/
|
||
|
|
||
|
typedef void (*fct_logger_on_cndtn_fn)(fct_logger_i *self,
|
||
|
fctchk_t const *chk);
|
||
|
#define _fct_logger_head \
|
||
|
fct_logger_on_cndtn_fn on_cndtn;\
|
||
|
void (*on_test_start)(fct_logger_i *logger, fct_test_t const *test);\
|
||
|
void (*on_test_end)(fct_logger_i *logger, fct_test_t const *test);\
|
||
|
void (*on_test_suite_start)(fct_logger_i *logger, fct_ts_t const *ts);\
|
||
|
void (*on_test_suite_end)(fct_logger_i *logger, fct_ts_t const *ts);\
|
||
|
void (*on_fct_start)(fct_logger_i *logger, fctkern_t const *kern);\
|
||
|
void (*on_fct_end)(fct_logger_i *logger, fctkern_t const *kern);\
|
||
|
void (*on_delete)(fct_logger_i *logger)\
|
||
|
|
||
|
struct _fct_logger_i {
|
||
|
_fct_logger_head;
|
||
|
};
|
||
|
|
||
|
|
||
|
/* Initializes the elements of a logger interface so they are at their
|
||
|
standard values. */
|
||
|
static void
|
||
|
fct_logger__init(fct_logger_i *logger)
|
||
|
{
|
||
|
assert( logger != NULL );
|
||
|
logger->on_cndtn =NULL;
|
||
|
logger->on_test_start =NULL;
|
||
|
logger->on_test_end =NULL;
|
||
|
logger->on_test_suite_start =NULL;
|
||
|
logger->on_test_suite_end =NULL;
|
||
|
logger->on_fct_start =NULL;
|
||
|
logger->on_fct_end =NULL;
|
||
|
logger->on_delete =NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_logger__del(fct_logger_i *logger)
|
||
|
{
|
||
|
if ( logger == NULL ) { return; }
|
||
|
if ( logger->on_delete) { logger->on_delete(logger); }
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_logger__on_test_start(fct_logger_i *logger, fct_test_t const *test)
|
||
|
{
|
||
|
assert( logger != NULL && "invalid arg");
|
||
|
assert( test != NULL && "invalid arg");
|
||
|
|
||
|
if ( logger->on_test_start != NULL )
|
||
|
{
|
||
|
logger->on_test_start(logger, test);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_logger__on_test_end(fct_logger_i *logger, fct_test_t const *test)
|
||
|
{
|
||
|
assert( logger != NULL && "invalid arg");
|
||
|
assert( test != NULL && "invalid arg");
|
||
|
|
||
|
if ( logger->on_test_end != NULL )
|
||
|
{
|
||
|
logger->on_test_end(logger, test);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_logger__on_test_suite_start(fct_logger_i *logger, fct_ts_t const *ts)
|
||
|
{
|
||
|
assert( logger != NULL && "invalid arg");
|
||
|
assert( ts != NULL && "invalid arg");
|
||
|
|
||
|
if ( logger->on_test_suite_start != NULL )
|
||
|
{
|
||
|
logger->on_test_suite_start(logger, ts);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_logger__on_test_suite_end(fct_logger_i *logger, fct_ts_t const *ts)
|
||
|
{
|
||
|
assert( logger != NULL && "invalid arg");
|
||
|
assert( ts != NULL && "invalid arg");
|
||
|
|
||
|
if ( logger->on_test_suite_end != NULL )
|
||
|
{
|
||
|
logger->on_test_suite_end(logger, ts);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_logger__on_cndtn(fct_logger_i *logger, fctchk_t const *chk)
|
||
|
{
|
||
|
assert( logger != NULL && "invalid arg");
|
||
|
assert( chk != NULL && "invalid arg");
|
||
|
|
||
|
if ( logger->on_cndtn )
|
||
|
{
|
||
|
logger->on_cndtn(logger, chk);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* When we start all our tests. */
|
||
|
static void
|
||
|
fct_logger__on_fct_start(fct_logger_i *logger, fctkern_t const *kern)
|
||
|
{
|
||
|
assert( logger != NULL );
|
||
|
assert( kern != NULL );
|
||
|
|
||
|
if ( logger->on_fct_start != NULL )
|
||
|
{
|
||
|
logger->on_fct_start(logger, kern);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* When we have reached the end of ALL of our testing. */
|
||
|
static void
|
||
|
fct_logger__on_fct_end(fct_logger_i *logger, fctkern_t const *kern)
|
||
|
{
|
||
|
assert( logger != NULL );
|
||
|
assert( kern != NULL );
|
||
|
|
||
|
if ( logger->on_fct_end )
|
||
|
{
|
||
|
logger->on_fct_end(logger, kern);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
-----------------------------------------------------------
|
||
|
MINIMAL LOGGER
|
||
|
-----------------------------------------------------------
|
||
|
*/
|
||
|
|
||
|
/* Minimal logger, reports the minimum amount of information needed
|
||
|
to determine "something is happening". */
|
||
|
struct _fct_minimal_logger_t {
|
||
|
_fct_logger_head;
|
||
|
};
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_minimal_logger__on_cndtn(fct_logger_i *self, fctchk_t const *chk)
|
||
|
{
|
||
|
fct_unused(self);
|
||
|
printf(fctchk__is_pass(chk) ? "." : "!");
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_minimal_logger__del(fct_logger_i *self)
|
||
|
{
|
||
|
free(self);
|
||
|
}
|
||
|
|
||
|
|
||
|
static fct_minimal_logger_t *
|
||
|
fct_minimal_logger__new(void)
|
||
|
{
|
||
|
fct_minimal_logger_t *self = (fct_minimal_logger_t*)\
|
||
|
calloc(1,sizeof(fct_minimal_logger_t));
|
||
|
if ( self == NULL ) { return NULL; }
|
||
|
|
||
|
fct_logger__init((fct_logger_i*)self);
|
||
|
|
||
|
self->on_cndtn = fct_minimal_logger__on_cndtn;
|
||
|
self->on_delete = fct_minimal_logger__del;
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
-----------------------------------------------------------
|
||
|
STANDARD LOGGER
|
||
|
-----------------------------------------------------------
|
||
|
*/
|
||
|
|
||
|
struct _fct_standard_logger_t {
|
||
|
_fct_logger_head;
|
||
|
|
||
|
/* Start time. For now we use the low-accuracy time_t version. */
|
||
|
fct_timer_t timer;
|
||
|
|
||
|
/* A list of char*'s that needs to be cleaned up. */
|
||
|
nlist_t *failed_cndtns_list;
|
||
|
};
|
||
|
|
||
|
|
||
|
/* When a failure occurrs, we will record the details so we can display
|
||
|
them when the log "finishes" up. */
|
||
|
static void
|
||
|
fct_standard_logger__on_cndtn(fct_logger_i *logger_, fctchk_t const *chk)
|
||
|
{
|
||
|
fct_standard_logger_t *logger = (fct_standard_logger_t*)logger_;
|
||
|
|
||
|
assert( logger != NULL );
|
||
|
assert( chk != NULL );
|
||
|
|
||
|
/* Only record failures. */
|
||
|
if ( !fctchk__is_pass(chk) )
|
||
|
{
|
||
|
/* For now we will truncate the string to some set amount, later
|
||
|
we can work out a dynamic string object. */
|
||
|
char *str = (char*)malloc(sizeof(char)*FCT_MAX_LOG_LINE);
|
||
|
assert( str != NULL );
|
||
|
|
||
|
fct_snprintf(
|
||
|
str,
|
||
|
FCT_MAX_LOG_LINE,
|
||
|
"%s(%d): %s",
|
||
|
fctchk__file(chk),
|
||
|
fctchk__lineno(chk),
|
||
|
fctchk__cndtn(chk)
|
||
|
);
|
||
|
|
||
|
/* Append it to the listing ... */
|
||
|
nlist__append(logger->failed_cndtns_list, (void*)str);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_standard_logger__on_test_start(fct_logger_i *logger_,
|
||
|
fct_test_t const *test)
|
||
|
{
|
||
|
fct_unused(logger_);
|
||
|
printf("%s ... ", fct_test__name(test));
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_standard_logger__on_test_end(fct_logger_i *logger_,
|
||
|
fct_test_t const *test)
|
||
|
{
|
||
|
nbool_t is_pass;
|
||
|
fct_unused(logger_);
|
||
|
|
||
|
is_pass = fct_test__is_pass(test);
|
||
|
printf("%s\n", (is_pass) ? "PASS" : "FAIL" );
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_standard_logger__on_test_suite_start(fct_logger_i *logger_,
|
||
|
fct_ts_t const *ts)
|
||
|
{
|
||
|
fct_unused(logger_);
|
||
|
fct_unused(ts);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_standard_logger__on_test_suite_end(fct_logger_i *logger_,
|
||
|
fct_ts_t const *ts)
|
||
|
{
|
||
|
fct_unused(logger_);
|
||
|
fct_unused(ts);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_standard_logger__on_fct_start(fct_logger_i *logger_,
|
||
|
fctkern_t const *nk)
|
||
|
{
|
||
|
fct_standard_logger_t *logger = (fct_standard_logger_t*)logger_;
|
||
|
fct_unused(nk);
|
||
|
fct_timer__start(&(logger->timer));
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_standard_logger__on_fct_end(fct_logger_i *logger_, fctkern_t const *nk)
|
||
|
{
|
||
|
fct_standard_logger_t *logger = (fct_standard_logger_t*)logger_;
|
||
|
nbool_t is_success =1;
|
||
|
double elasped_time =0;
|
||
|
int num_tests =0;
|
||
|
int num_passed =0;
|
||
|
|
||
|
fct_timer__stop(&(logger->timer));
|
||
|
|
||
|
is_success = nlist__size(logger->failed_cndtns_list) ==0;
|
||
|
|
||
|
if ( !is_success )
|
||
|
{
|
||
|
printf("\n--------------------------------------------------------\n");
|
||
|
printf("FAILED TESTS\n\n");
|
||
|
|
||
|
NLIST_FOREACH_BGN(char *, cndtn_str, logger->failed_cndtns_list)
|
||
|
{
|
||
|
printf("%s\n", cndtn_str);
|
||
|
}
|
||
|
NLIST_FOREACH_END();
|
||
|
|
||
|
printf("\n");
|
||
|
}
|
||
|
|
||
|
printf("\n--------------------------------------------------------\n");
|
||
|
|
||
|
num_tests = fctkern__tst_cnt(nk);
|
||
|
num_passed = fctkern__tst_cnt_passed(nk);
|
||
|
|
||
|
printf(
|
||
|
"%s (%d/%d tests",
|
||
|
(is_success) ? "PASSED" : "FAILED",
|
||
|
num_passed,
|
||
|
num_tests
|
||
|
);
|
||
|
|
||
|
elasped_time = fct_timer__duration(&(logger->timer));
|
||
|
if ( elasped_time > 0.0000001 )
|
||
|
{
|
||
|
printf(" in %.6fs)\n", elasped_time);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Don't bother displaying the time to execute. */
|
||
|
printf(")\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
fct_standard_logger__del(fct_logger_i *logger_)
|
||
|
{
|
||
|
fct_standard_logger_t *logger = (fct_standard_logger_t*)logger_;
|
||
|
|
||
|
NLIST_FOREACH_BGN(char *, cndtn_str, logger->failed_cndtns_list)
|
||
|
{
|
||
|
free(cndtn_str);
|
||
|
}
|
||
|
NLIST_FOREACH_END();
|
||
|
|
||
|
free(logger);
|
||
|
logger_ =NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
fct_standard_logger_t *
|
||
|
fct_standard_logger__new(void)
|
||
|
{
|
||
|
fct_standard_logger_t *logger = (fct_standard_logger_t *)calloc(
|
||
|
1, sizeof(fct_standard_logger_t)
|
||
|
);
|
||
|
if ( logger == NULL )
|
||
|
{
|
||
|
return NULL;
|
||
|
}
|
||
|
fct_logger__init((fct_logger_i*)logger);
|
||
|
logger->on_cndtn = fct_standard_logger__on_cndtn;
|
||
|
logger->on_test_start = fct_standard_logger__on_test_start;
|
||
|
logger->on_test_end = fct_standard_logger__on_test_end;
|
||
|
logger->on_test_suite_start = fct_standard_logger__on_test_suite_start;
|
||
|
logger->on_test_suite_end = fct_standard_logger__on_test_suite_end;
|
||
|
logger->on_fct_start = fct_standard_logger__on_fct_start;
|
||
|
logger->on_fct_end = fct_standard_logger__on_fct_end;
|
||
|
logger->on_delete = fct_standard_logger__del;
|
||
|
|
||
|
logger->failed_cndtns_list = nlist_new();
|
||
|
assert( logger->failed_cndtns_list != NULL );
|
||
|
|
||
|
fct_timer__init(&(logger->timer));
|
||
|
|
||
|
return logger;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
------------------------------------------------------------
|
||
|
MAGIC MACROS
|
||
|
------------------------------------------------------------
|
||
|
*/
|
||
|
|
||
|
#define FCT_BGN() \
|
||
|
int \
|
||
|
main(int argc, char *argv[])\
|
||
|
{\
|
||
|
fctkern_t fctkern__;\
|
||
|
fctkern_init(&fctkern__, argc, argv);\
|
||
|
fctkern__log_start(&fctkern__);
|
||
|
|
||
|
|
||
|
#define FCT_END()\
|
||
|
{\
|
||
|
int num_failed__ =0;\
|
||
|
num_failed__ = fctkern__tst_cnt_failed((&fctkern__));\
|
||
|
fctkern__log_end(&fctkern__);\
|
||
|
fctkern__end(&fctkern__);\
|
||
|
fctkern__final(&fctkern__);\
|
||
|
return num_failed__;\
|
||
|
}\
|
||
|
}
|
||
|
|
||
|
#define FCT_FIXTURE_SUITE_BGN(_NAME_) \
|
||
|
{\
|
||
|
fct_ts_t *ts__ = fct_ts_new( #_NAME_ );\
|
||
|
fctkern__log_suite_start((&fctkern__), ts__);\
|
||
|
for (;;)\
|
||
|
{\
|
||
|
int fct_test_num__ = -1;\
|
||
|
_fct_cmt("Strict compiler warnings will complain in 'blank' suites.")\
|
||
|
_fct_cmt("so we are going to do a 'noop' to trick them.")\
|
||
|
fct_test_num__ = fct_test_num__;\
|
||
|
if ( fct_ts__is_ending_mode(ts__) )\
|
||
|
{\
|
||
|
_fct_cmt("flag the test suite as complete.");\
|
||
|
fct_ts__end(ts__);\
|
||
|
break;\
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Closes off a "Fixture" test suite. */
|
||
|
#define FCT_FIXTURE_SUITE_END() \
|
||
|
if ( fct_ts__is_cnt_mode(ts__) )\
|
||
|
{\
|
||
|
fct_ts__cnt_end(ts__);\
|
||
|
}\
|
||
|
}\
|
||
|
fctkern__add_ts((&fctkern__), ts__);\
|
||
|
fctkern__log_suite_end((&fctkern__), ts__);\
|
||
|
ts__ = NULL;\
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
#define FCT_SETUP_BGN()\
|
||
|
if ( fct_ts__is_setup_mode(ts__) ) {
|
||
|
|
||
|
#define FCT_SETUP_END() \
|
||
|
fct_ts__setup_end(ts__); }
|
||
|
|
||
|
#define FCT_TEARDOWN_BGN() \
|
||
|
if ( fct_ts__is_teardown_mode(ts__) ) {\
|
||
|
|
||
|
#define FCT_TEARDOWN_END() \
|
||
|
fct_ts__teardown_end(ts__); \
|
||
|
continue; \
|
||
|
}
|
||
|
|
||
|
/* Lets you create a test suite, where maybe you don't want a fixture. We
|
||
|
do it by 'stubbing' out the setup/teardown logic. */
|
||
|
#define FCT_SUITE_BGN(Name) \
|
||
|
FCT_FIXTURE_SUITE_BGN(Name) {\
|
||
|
FCT_SETUP_BGN() {_fct_cmt("stubbed"); } FCT_SETUP_END()\
|
||
|
FCT_TEARDOWN_BGN() {_fct_cmt("stubbed");} FCT_TEARDOWN_END()\
|
||
|
|
||
|
#define FCT_SUITE_END() } FCT_FIXTURE_SUITE_END()
|
||
|
|
||
|
/* Depending on whether or not we are counting the tests, we will have to
|
||
|
first determine if the test is the "current" count. Then we have to determine
|
||
|
if we can pass the filter. Finally we will execute everything so that when a
|
||
|
check fails, we can "break" out to the end of the test. */
|
||
|
#define FCT_TEST_BGN(_NAME_) \
|
||
|
{\
|
||
|
char const *test_name__ = #_NAME_;\
|
||
|
++fct_test_num__;\
|
||
|
if ( fct_ts__is_cnt_mode(ts__) )\
|
||
|
{\
|
||
|
fct_ts__inc_total_test_num(ts__);\
|
||
|
}\
|
||
|
else if ( fct_ts__is_test_mode(ts__) \
|
||
|
&& fct_ts__is_test_cnt(ts__, fct_test_num__) )\
|
||
|
{\
|
||
|
int is_pass__;\
|
||
|
is_pass__ = FCT_FALSE;\
|
||
|
fct_ts__test_begin(ts__);\
|
||
|
if ( fctkern__pass_filter(&fctkern__, test_name__ ) )\
|
||
|
{\
|
||
|
fct_test_t *test__ = fct_test_new( test_name__ );\
|
||
|
fctkern__log_test_start(&fctkern__, test__);\
|
||
|
for (;;) \
|
||
|
{
|
||
|
|
||
|
#define FCT_TEST_END() \
|
||
|
break;\
|
||
|
}\
|
||
|
fct_ts__add_test(ts__, test__);\
|
||
|
fctkern__log_test_end(&fctkern__, test__);\
|
||
|
}\
|
||
|
fct_ts__test_end(ts__);\
|
||
|
continue;\
|
||
|
}\
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
---------------------------------------------------------
|
||
|
CHECKING MACROS
|
||
|
----------------------------------------------------------
|
||
|
|
||
|
For now we only have the one "positive" check macro. In the future I plan
|
||
|
to add more macros that check for different types of common conditions.
|
||
|
*/
|
||
|
|
||
|
#define fct_chk(_CNDTN_) \
|
||
|
{\
|
||
|
fctchk_t *chk =NULL;\
|
||
|
is_pass__ = (_CNDTN_);\
|
||
|
chk = fctchk_new(#_CNDTN_, __FILE__, __LINE__, is_pass__);\
|
||
|
fct_test__add(test__, chk);\
|
||
|
fctkern__log_chk(&fctkern__, chk);\
|
||
|
if ( !is_pass__ ) { break; }\
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
---------------------------------------------------------
|
||
|
GUT CHECK MACROS
|
||
|
----------------------------------------------------------
|
||
|
|
||
|
The following macros are used to help check the "guts" of
|
||
|
the FCT, and to confirm that it all works according to spec.
|
||
|
*/
|
||
|
|
||
|
/* Generates a message to STDERR and exits the application with a
|
||
|
non-zero number. */
|
||
|
#define _FCT_GUTCHK(_CNDTN_) \
|
||
|
if ( !(_CNDTN_) ) {\
|
||
|
fprintf(stderr, "gutchk fail: '" #_CNDTN_ "' was not true.\n");\
|
||
|
exit(1);\
|
||
|
}\
|
||
|
else {\
|
||
|
fprintf(stdout, "gutchk pass: '" #_CNDTN_ "'\n");\
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
---------------------------------------------------------
|
||
|
CLOSING STATEMENTS
|
||
|
----------------------------------------------------------
|
||
|
*/
|
||
|
|
||
|
/* This is defined at the start of the file. We are undefining it
|
||
|
here so it doesn't conflict with existing. */
|
||
|
#if defined(WIN32)
|
||
|
# undef _CRT_SECURE_NO_WARNINGS
|
||
|
#endif
|
||
|
|
||
|
#endif /* !FCT_INCLUDED__IMB */
|