2019-04-10 12:23:25 -05:00
// 27 february 2018
2019-05-20 19:56:47 -05:00
# include <ctype.h>
2019-04-10 12:23:25 -05:00
# include <stdio.h>
# include <stdlib.h>
# include <setjmp.h>
2019-04-17 21:58:44 -05:00
# include <string.h>
2019-05-02 11:38:59 -05:00
# include "timer.h"
2019-04-10 12:23:25 -05:00
# include "testing.h"
2019-05-19 16:55:31 -05:00
# include "testingpriv.h"
2019-05-19 10:05:20 -05:00
2019-04-10 12:23:25 -05:00
struct defer {
2019-04-10 13:27:21 -05:00
void ( * f ) ( testingT * , void * ) ;
2019-04-10 12:23:25 -05:00
void * data ;
struct defer * next ;
} ;
2019-04-21 16:28:47 -05:00
# ifdef _MSC_VER
// Microsoft defines jmp_buf with a __declspec(align()), and for whatever reason, they have a warning that triggers when you use that for any reason, and that warning is enabled with /W4
// Silence the warning; it's harmless.
# pragma warning(push)
# pragma warning(disable: 4324)
# endif
2019-04-10 12:23:25 -05:00
struct testingT {
2019-05-05 12:35:07 -05:00
// set at test creation time
2019-04-10 12:23:25 -05:00
const char * name ;
2019-05-20 20:10:29 -05:00
void ( * f ) ( testingT * , void * ) ;
void * data ;
2019-05-20 20:22:02 -05:00
// for sorting tests in a set; not used by subtests
2019-04-17 21:12:32 -05:00
const char * file ;
long line ;
2019-05-05 12:35:07 -05:00
// test status
2019-05-30 09:33:39 -05:00
bool failed ;
bool skipped ;
bool returned ;
2019-04-10 12:23:25 -05:00
jmp_buf returnNowBuf ;
2019-05-05 12:35:07 -05:00
// deferred functions
2019-04-10 12:23:25 -05:00
struct defer * defers ;
2019-05-30 09:33:39 -05:00
bool defersRun ;
2019-05-05 12:35:07 -05:00
2019-05-20 20:22:02 -05:00
// execution options
testingOptions opts ;
2019-05-05 12:35:07 -05:00
// output
2019-05-20 09:57:31 -05:00
testingprivOutbuf * outbuf ;
2019-04-10 12:23:25 -05:00
} ;
2019-04-21 16:28:47 -05:00
# ifdef _MSC_VER
# pragma warning(pop)
# endif
2019-05-20 20:10:29 -05:00
static void initTest ( testingT * t , const char * name , void ( * f ) ( testingT * , void * ) , void * data , const char * file , long line )
2019-04-10 12:23:25 -05:00
{
2019-05-05 12:35:07 -05:00
memset ( t , 0 , sizeof ( testingT ) ) ;
2019-04-10 12:23:25 -05:00
t - > name = name ;
t - > f = f ;
2019-05-20 20:10:29 -05:00
t - > data = data ;
2019-04-17 21:12:32 -05:00
t - > file = file ;
t - > line = line ;
2019-04-10 13:19:17 -05:00
}
2019-05-09 21:54:14 -05:00
struct testingSet {
2019-05-19 17:06:58 -05:00
testingprivArray tests ;
2019-04-17 21:12:32 -05:00
} ;
2019-05-19 17:06:58 -05:00
static testingSet mainTests = { testingprivArrayStaticInit ( testingT , 32 , " testingT[] " ) } ;
2019-04-17 21:12:32 -05:00
2019-05-20 20:10:29 -05:00
void testingprivSetRegisterTest ( testingSet * * pset , const char * name , void ( * f ) ( testingT * , void * ) , void * data , const char * file , long line )
2019-04-10 13:19:17 -05:00
{
2019-05-09 21:54:14 -05:00
testingSet * set ;
2019-05-19 17:06:58 -05:00
testingT * t ;
2019-05-09 21:54:14 -05:00
set = & mainTests ;
2019-05-10 20:16:29 -05:00
if ( pset ! = NULL ) {
2019-05-09 21:54:14 -05:00
set = * pset ;
2019-05-10 20:16:29 -05:00
if ( set = = NULL ) {
2019-05-19 16:55:31 -05:00
set = testingprivNew ( testingSet ) ;
2019-05-19 17:06:58 -05:00
testingprivArrayInit ( set - > tests , testingT , 32 , " testingT[] " ) ;
2019-05-10 20:16:29 -05:00
* pset = set ;
}
}
2019-05-19 17:06:58 -05:00
t = ( testingT * ) testingprivArrayAppend ( & ( set - > tests ) , 1 ) ;
2019-05-20 20:10:29 -05:00
initTest ( t , name , f , data , file , line ) ;
2019-04-10 12:23:25 -05:00
}
2019-04-17 21:58:44 -05:00
static int testcmp ( const void * a , const void * b )
{
const testingT * ta = ( const testingT * ) a ;
const testingT * tb = ( const testingT * ) b ;
int ret ;
ret = strcmp ( ta - > file , tb - > file ) ;
if ( ret ! = 0 )
return ret ;
if ( ta - > line < tb - > line )
return - 1 ;
if ( ta - > line > tb - > line )
return 1 ;
return 0 ;
}
2019-04-10 12:23:25 -05:00
static void runDefers ( testingT * t )
{
struct defer * d ;
if ( t - > defersRun )
return ;
2019-05-30 09:33:39 -05:00
t - > defersRun = true ;
2019-04-10 12:23:25 -05:00
for ( d = t - > defers ; d ! = NULL ; d = d - > next )
2019-04-10 13:27:21 -05:00
( * ( d - > f ) ) ( t , d - > data ) ;
2019-06-02 13:05:36 -05:00
// and now free the defers
// we could use t->defers == NULL instead of t->defersRun but then recursive calls to runDefers() (for instance, if a deferred function calls testingTFatalf()) would explode
while ( t - > defers ! = NULL ) {
d = t - > defers ;
t - > defers = t - > defers - > next ;
testingprivFree ( d ) ;
}
2019-04-10 12:23:25 -05:00
}
2019-05-10 20:16:29 -05:00
static const testingOptions defaultOptions = {
2019-05-05 14:59:59 -05:00
. Verbose = 0 ,
} ;
2019-05-30 09:33:39 -05:00
static bool testingprivTRun ( testingT * t , testingprivOutbuf * parentbuf )
2019-04-10 12:23:25 -05:00
{
const char * status ;
2019-05-02 11:38:59 -05:00
timerTime start , end ;
2019-05-02 21:03:57 -05:00
char timerstr [ timerDurationStringLen ] ;
2019-05-30 21:09:45 -05:00
bool printStatus ;
2019-04-10 12:23:25 -05:00
2019-05-20 20:22:02 -05:00
if ( t - > opts . Verbose )
2019-05-20 09:57:31 -05:00
testingprivOutbufPrintf ( parentbuf , " === RUN %s \n " , t - > name ) ;
t - > outbuf = testingprivNewOutbuf ( ) ;
start = timerMonotonicNow ( ) ;
if ( setjmp ( t - > returnNowBuf ) = = 0 )
2019-05-20 20:10:29 -05:00
( * ( t - > f ) ) ( t , t - > data ) ;
2019-05-20 09:57:31 -05:00
end = timerMonotonicNow ( ) ;
2019-05-30 09:33:39 -05:00
t - > returned = true ;
2019-05-20 09:57:31 -05:00
runDefers ( t ) ;
2019-05-20 20:22:02 -05:00
printStatus = t - > opts . Verbose ;
2019-05-20 09:57:31 -05:00
status = " PASS " ;
if ( t - > failed ) {
status = " FAIL " ;
2019-05-30 21:09:45 -05:00
printStatus = true ; // always print status on failure
2019-05-20 09:57:31 -05:00
} else if ( t - > skipped )
// note that failed overrides skipped
status = " SKIP " ;
timerDurationString ( timerTimeSub ( end , start ) , timerstr ) ;
if ( printStatus ) {
testingprivOutbufPrintf ( parentbuf , " --- %s: %s (%s) \n " , status , t - > name , timerstr ) ;
testingprivOutbufAppendOutbuf ( parentbuf , t - > outbuf ) ;
}
testingprivOutbufFree ( t - > outbuf ) ;
t - > outbuf = NULL ;
2019-05-30 09:33:39 -05:00
return ! t - > failed ;
2019-05-20 09:57:31 -05:00
}
2019-06-01 11:17:08 -05:00
// TODO make options and opts consistent throughout? what about format vs fmt in public-facing functions?
2019-05-30 09:33:39 -05:00
static void testingprivSetRun ( testingSet * set , const testingOptions * opts , testingprivOutbuf * outbuf , bool * anyFailed )
2019-05-20 09:57:31 -05:00
{
size_t i ;
testingT * t ;
2019-05-19 17:06:58 -05:00
testingprivArrayQsort ( & ( set - > tests ) , testcmp ) ;
t = ( testingT * ) ( set - > tests . buf ) ;
for ( i = 0 ; i < set - > tests . len ; i + + ) {
2019-05-20 20:22:02 -05:00
t - > opts = * opts ;
2019-05-30 09:33:39 -05:00
if ( ! testingprivTRun ( t , outbuf ) )
* anyFailed = true ;
2019-04-17 21:12:32 -05:00
t + + ;
2019-04-10 12:23:25 -05:00
}
2019-04-10 13:19:17 -05:00
}
2019-05-30 09:33:39 -05:00
void testingSetRun ( testingSet * set , const struct testingOptions * options , bool * anyRun , bool * anyFailed )
2019-04-10 13:19:17 -05:00
{
2019-05-30 09:33:39 -05:00
* anyRun = false ;
* anyFailed = false ;
2019-05-09 21:54:14 -05:00
if ( set = = NULL )
set = & mainTests ;
2019-05-10 20:16:29 -05:00
if ( options = = NULL )
options = & defaultOptions ;
2019-05-19 17:06:58 -05:00
if ( set - > tests . len = = 0 )
2019-05-10 20:16:29 -05:00
return ;
2019-05-20 09:57:31 -05:00
testingprivSetRun ( set , options , NULL , anyFailed ) ;
2019-05-30 09:33:39 -05:00
* anyRun = true ;
2019-04-10 12:23:25 -05:00
}
2019-06-01 21:03:13 -05:00
void testingprivTLogfFullThen ( testingT * t , void ( * then ) ( testingT * t ) , const char * file , long line , const char * format , . . . )
2019-04-10 12:23:25 -05:00
{
va_list ap ;
2019-06-01 11:05:29 -05:00
va_start ( ap , format ) ;
2019-06-01 21:03:13 -05:00
// run va_end() *before* then, just to be safe
testingprivTLogvfFullThen ( t , NULL , file , line , format , ap ) ;
2019-04-10 12:23:25 -05:00
va_end ( ap ) ;
2019-06-01 21:03:13 -05:00
if ( then ! = NULL )
( * then ) ( t ) ;
2019-04-10 12:23:25 -05:00
}
2019-05-26 12:31:55 -05:00
static const char * basename ( const char * file )
{
const char * p ;
for ( ; ; ) {
p = strpbrk ( file , " / \\ " ) ;
if ( p = = NULL )
break ;
file = p + 1 ;
}
return file ;
}
2019-06-01 21:03:13 -05:00
void testingprivTLogvfFullThen ( testingT * t , void ( * then ) ( testingT * t ) , const char * file , long line , const char * format , va_list ap )
2019-04-10 12:23:25 -05:00
{
2019-05-26 12:31:55 -05:00
testingprivOutbufPrintf ( t - > outbuf , " %s:%ld: " , basename ( file ) , line ) ;
2019-05-28 23:35:51 -05:00
testingprivOutbufVprintfIndented ( t - > outbuf , format , ap ) ;
2019-05-20 19:07:59 -05:00
testingprivOutbufPrintf ( t - > outbuf , " \n " ) ;
2019-06-01 21:03:13 -05:00
if ( then ! = NULL )
( * then ) ( t ) ;
2019-04-10 12:23:25 -05:00
}
void testingTFail ( testingT * t )
{
2019-05-30 09:33:39 -05:00
t - > failed = true ;
2019-04-10 12:23:25 -05:00
}
static void returnNow ( testingT * t )
{
2019-04-10 13:27:21 -05:00
if ( ! t - > returned ) {
// set this now so a FailNow inside a Defer doesn't longjmp twice
2019-05-30 09:33:39 -05:00
t - > returned = true ;
2019-04-10 13:27:21 -05:00
// run defers before calling longjmp() just to be safe
runDefers ( t ) ;
longjmp ( t - > returnNowBuf , 1 ) ;
}
2019-04-10 12:23:25 -05:00
}
2019-04-28 12:12:40 -05:00
void testingTFailNow ( testingT * t )
2019-04-10 12:23:25 -05:00
{
testingTFail ( t ) ;
returnNow ( t ) ;
}
2019-04-28 12:12:40 -05:00
void testingTSkipNow ( testingT * t )
2019-04-10 12:23:25 -05:00
{
2019-05-30 09:33:39 -05:00
t - > skipped = true ;
2019-04-10 12:23:25 -05:00
returnNow ( t ) ;
}
2019-04-10 19:11:44 -05:00
void testingTDefer ( testingT * t , void ( * f ) ( testingT * t , void * data ) , void * data )
2019-04-10 12:23:25 -05:00
{
struct defer * d ;
2019-05-19 16:55:31 -05:00
d = testingprivNew ( struct defer ) ;
2019-04-10 12:23:25 -05:00
d - > f = f ;
d - > data = data ;
// add to the head of the list so defers are run in reverse order of how they were added
d - > next = t - > defers ;
t - > defers = d ;
}
2019-05-20 19:56:47 -05:00
void testingTRun ( testingT * t , const char * subname , void ( * subfunc ) ( testingT * t , void * data ) , void * data )
{
testingT * subt ;
testingprivOutbuf * rewrittenName ;
2019-05-20 20:33:55 -05:00
char * fullName ;
2019-05-20 19:56:47 -05:00
rewrittenName = testingprivNewOutbuf ( ) ;
2019-05-20 21:03:56 -05:00
while ( * subname ! = ' \0 ' ) {
2019-05-20 19:56:47 -05:00
const char * replaced ;
replaced = NULL ;
switch ( * subname ) {
case ' ' :
case ' \f ' :
case ' \n ' :
case ' \r ' :
case ' \t ' :
case ' \v ' :
replaced = " _ " ;
break ;
case ' \a ' :
replaced = " \\ a " ;
break ;
case ' \b ' :
replaced = " \\ b " ;
break ;
}
if ( replaced ! = NULL )
testingprivOutbufPrintf ( rewrittenName , " %s " , replaced ) ;
else if ( isprint ( * subname ) )
testingprivOutbufPrintf ( rewrittenName , " %c " , * subname ) ;
else
testingprivOutbufPrintf ( rewrittenName , " \\ x%x " , ( unsigned int ) ( * subname ) ) ;
subname + + ;
}
2019-05-20 21:03:56 -05:00
fullName = testingprivSmprintf ( " %s/%s " , t - > name , testingprivOutbufString ( rewrittenName ) ) ;
2019-05-20 19:56:47 -05:00
testingprivOutbufFree ( rewrittenName ) ;
2019-05-20 20:33:55 -05:00
subt = testingprivNew ( testingT ) ;
initTest ( subt , fullName , subfunc , data , NULL , 0 ) ;
2019-05-20 20:22:02 -05:00
subt - > opts = t - > opts ;
2019-05-30 09:33:39 -05:00
if ( ! testingprivTRun ( subt , t - > outbuf ) )
t - > failed = true ;
2019-05-20 21:03:56 -05:00
testingprivFree ( subt ) ;
2019-05-20 20:33:55 -05:00
2019-05-20 21:03:56 -05:00
testingprivFree ( fullName ) ;
2019-05-20 19:56:47 -05:00
}
2019-06-09 13:03:27 -05:00
// Utility functions, provided here to avoid mucking up the sharedbits functions.
char * testingUtilStrdup ( const char * s )
{
return testingprivStrdup ( s ) ;
}
void testingUtilFreeStrdup ( char * s )
{
testingprivFree ( s ) ;
}