2019-04-10 12:23:25 -05:00
// 27 february 2018
# include <stdio.h>
# include <stdlib.h>
# include <setjmp.h>
2019-04-17 21:58:44 -05:00
# include <string.h>
2019-04-10 12:23:25 -05:00
# include "testing.h"
# define testingprivNew(T) ((T *) malloc(sizeof (T)))
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 {
const char * name ;
void ( * f ) ( testingT * ) ;
2019-04-17 21:12:32 -05:00
const char * file ;
long line ;
2019-04-10 12:23:25 -05:00
int failed ;
int skipped ;
2019-04-10 13:27:21 -05:00
int returned ;
2019-04-10 12:23:25 -05:00
jmp_buf returnNowBuf ;
struct defer * defers ;
int defersRun ;
} ;
2019-04-21 16:28:47 -05:00
# ifdef _MSC_VER
# pragma warning(pop)
# endif
2019-04-17 21:12:32 -05:00
static void initTest ( testingT * t , const char * name , void ( * f ) ( testingT * ) , const char * file , long line )
2019-04-10 12:23:25 -05:00
{
t - > name = name ;
t - > f = f ;
2019-04-17 21:12:32 -05:00
t - > file = file ;
t - > line = line ;
2019-04-10 12:23:25 -05:00
t - > failed = 0 ;
t - > skipped = 0 ;
2019-04-10 13:27:21 -05:00
t - > returned = 0 ;
2019-04-10 12:23:25 -05:00
t - > defers = NULL ;
t - > defersRun = 0 ;
2019-04-10 13:19:17 -05:00
}
2019-04-17 21:12:32 -05:00
# define nGrow 32
struct testset {
testingT * tests ;
size_t len ;
size_t cap ;
} ;
static struct testset tests = { NULL , 0 , 0 } ;
static struct testset testsBefore = { NULL , 0 , 0 } ;
static struct testset testsAfter = { NULL , 0 , 0 } ;
static void testsetAdd ( struct testset * set , const char * name , void ( * f ) ( testingT * ) , const char * file , long line )
2019-04-10 13:19:17 -05:00
{
2019-04-17 21:12:32 -05:00
if ( set - > len = = set - > cap ) {
testingT * newbuf ;
2019-04-10 13:19:17 -05:00
2019-04-17 21:12:32 -05:00
set - > cap + = nGrow ;
newbuf = ( testingT * ) realloc ( set - > tests , set - > cap * sizeof ( testingT ) ) ;
// TODO abort if newbuf is NULL
set - > tests = newbuf ;
}
initTest ( set - > tests + set - > len , name , f , file , line ) ;
set - > len + + ;
2019-04-10 12:23:25 -05:00
}
2019-04-17 21:12:32 -05:00
void testingprivRegisterTest ( const char * name , void ( * f ) ( testingT * ) , const char * file , long line )
2019-04-10 19:17:40 -05:00
{
2019-04-17 21:12:32 -05:00
testsetAdd ( & tests , name , f , file , line ) ;
}
2019-04-10 19:17:40 -05:00
2019-04-17 21:12:32 -05:00
void testingprivRegisterTestBefore ( const char * name , void ( * f ) ( testingT * ) , const char * file , long line )
{
testsetAdd ( & testsBefore , name , f , file , line ) ;
2019-04-10 19:17:40 -05:00
}
2019-04-17 20:49:47 -05:00
void testingprivRegisterTestAfter ( const char * name , void ( * f ) ( testingT * ) , const char * file , long line )
2019-04-10 19:17:40 -05:00
{
2019-04-17 21:12:32 -05:00
testsetAdd ( & testsAfter , name , f , file , line ) ;
2019-04-10 19:17:40 -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 ;
}
static void testsetSort ( struct testset * set )
{
qsort ( set - > tests , set - > len , sizeof ( testingT ) , testcmp ) ;
}
2019-04-10 12:23:25 -05:00
static void runDefers ( testingT * t )
{
struct defer * d ;
if ( t - > defersRun )
return ;
t - > defersRun = 1 ;
for ( d = t - > defers ; d ! = NULL ; d = d - > next )
2019-04-10 13:27:21 -05:00
( * ( d - > f ) ) ( t , d - > data ) ;
2019-04-10 12:23:25 -05:00
}
2019-04-17 21:12:32 -05:00
static void testsetRun ( struct testset * set , int * anyFailed )
2019-04-10 12:23:25 -05:00
{
2019-04-17 21:12:32 -05:00
size_t i ;
testingT * t ;
2019-04-10 12:23:25 -05:00
const char * status ;
2019-04-22 22:01:55 -05:00
testingTimer * timer ;
char * timerstr ;
2019-04-10 12:23:25 -05:00
2019-04-17 21:12:32 -05:00
t = set - > tests ;
2019-04-22 22:01:55 -05:00
timer = testingNewTimer ( ) ;
2019-04-17 21:12:32 -05:00
for ( i = 0 ; i < set - > len ; i + + ) {
2019-04-10 12:23:25 -05:00
printf ( " === RUN %s \n " , t - > name ) ;
2019-04-22 22:01:55 -05:00
testingTimerStart ( timer ) ;
2019-04-10 12:23:25 -05:00
if ( setjmp ( t - > returnNowBuf ) = = 0 )
( * ( t - > f ) ) ( t ) ;
2019-04-22 22:01:55 -05:00
testingTimerEnd ( timer ) ;
2019-04-10 13:27:21 -05:00
t - > returned = 1 ;
2019-04-10 12:23:25 -05:00
runDefers ( t ) ;
status = " PASS " ;
if ( t - > failed ) {
status = " FAIL " ;
2019-04-10 13:19:17 -05:00
* anyFailed = 1 ;
2019-04-10 12:23:25 -05:00
} else if ( t - > skipped )
// note that failed overrides skipped
status = " SKIP " ;
2019-04-22 22:01:55 -05:00
timerstr = testingTimerString ( timer ) ;
printf ( " --- %s: %s (%s) \n " , status , t - > name , timerstr ) ;
testingFreeTimerString ( timerstr ) ;
2019-04-17 21:12:32 -05:00
t + + ;
2019-04-10 12:23:25 -05:00
}
2019-04-22 22:01:55 -05:00
testingFreeTimer ( timer ) ;
2019-04-10 13:19:17 -05:00
}
int testingMain ( void )
{
int anyFailed ;
// TODO see if this should run if all tests are skipped
2019-04-17 21:12:32 -05:00
if ( ( testsBefore . len + tests . len + testsAfter . len ) = = 0 ) {
2019-04-10 13:19:17 -05:00
fprintf ( stderr , " warning: no tests to run \n " ) ;
// imitate Go here (TODO confirm this)
return 0 ;
}
2019-04-10 12:23:25 -05:00
2019-04-17 21:58:44 -05:00
testsetSort ( & testsBefore ) ;
testsetSort ( & tests ) ;
testsetSort ( & testsAfter ) ;
2019-04-10 13:19:17 -05:00
anyFailed = 0 ;
2019-04-17 21:12:32 -05:00
testsetRun ( & testsBefore , & anyFailed ) ;
2019-04-19 11:12:13 -05:00
// TODO print a warning that we skip the next stages if a prior stage failed?
if ( ! anyFailed )
testsetRun ( & tests , & anyFailed ) ;
2019-04-19 11:13:08 -05:00
// TODO should we unconditionally run these tests if before succeeded but the main tests failed?
2019-04-19 11:12:13 -05:00
if ( ! anyFailed )
testsetRun ( & testsAfter , & anyFailed ) ;
2019-04-10 12:23:25 -05:00
if ( anyFailed ) {
printf ( " FAIL \n " ) ;
return 1 ;
}
printf ( " PASS \n " ) ;
return 0 ;
}
2019-04-10 13:19:17 -05:00
void testingprivTLogfFull ( testingT * t , const char * file , long line , const char * format , . . . )
2019-04-10 12:23:25 -05:00
{
va_list ap ;
va_start ( ap , format ) ;
testingprivTLogvfFull ( t , file , line , format , ap ) ;
va_end ( ap ) ;
}
2019-04-10 13:19:17 -05:00
void testingprivTLogvfFull ( testingT * t , const char * file , long line , const char * format , va_list ap )
2019-04-10 12:23:25 -05:00
{
// TODO extract filename from file
2019-04-10 19:11:44 -05:00
printf ( " \t %s:%ld: " , file , line ) ;
2019-04-10 12:23:25 -05:00
// TODO split into lines separated by \n\t\t and trimming trailing empty lines
vprintf ( format , ap ) ;
printf ( " \n " ) ;
}
void testingTFail ( testingT * t )
{
t - > failed = 1 ;
}
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
t - > returned = 1 ;
// 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-10 13:19:17 -05:00
void testingprivTFailNow ( testingT * t )
2019-04-10 12:23:25 -05:00
{
testingTFail ( t ) ;
returnNow ( t ) ;
}
2019-04-10 13:19:17 -05:00
void testingprivTSkipNow ( testingT * t )
2019-04-10 12:23:25 -05:00
{
t - > skipped = 1 ;
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 ;
d = testingprivNew ( struct defer ) ;
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-04-22 22:01:55 -05:00
// This is based on the algorithm that Go uses for time.Duration.
// Of course, we're not expressing it the same way...
struct timerStringPart {
char suffix ;
char suffix2 ;
int mode ;
uint32_t maxOrMod ;
int precision ;
} ;
enum {
modeMaxAndStop ,
modeFracModContinue ,
} ;
static const struct timerStringPart parts [ ] = {
{ ' n ' , ' s ' , modeMaxAndStop , 1000 , 0 } ,
{ ' u ' , ' s ' , modeMaxAndStop , 1000000 , 3 } ,
{ ' m ' , ' s ' , modeMaxAndStop , 1000000000 , 6 } ,
{ ' s ' , 0 , modeFracModContinue , 60 , 9 } ,
{ ' m ' , 0 , modeFracModContinue , 60 , 0 } ,
{ ' h ' , 0 , modeFracModContinue , 60 , 0 } ,
{ 0 , 0 , 0 , 0 , 0 } ,
} ;
static void fillFracPart ( char * s , int precision , int * start , uint64_t * unsec )
{
int i ;
int print ;
uint64_t digit ;
print = 0 ;
for ( i = 0 ; i < precision ; i + + ) {
digit = * unsec % 10 ;
print = print | | ( digit = = 0 ) ;
if ( print ) {
s [ * start - 1 ] = " 0123456789 " [ digit ] ;
( * start ) - - ;
}
* unsec / = 10 ;
}
if ( print ) {
s [ * start - 1 ] = ' . ' ;
( * start ) - - ;
}
}
static void fillIntPart ( char * s , int * start , uint64_t unsec )
{
if ( unsec = = 0 ) {
s [ * start - 1 ] = ' 0 ' ;
( * start ) - - ;
return ;
}
while ( unsec ! = 0 ) {
s [ * start - 1 ] = " 0123456789 " [ unsec % 10 ] ;
( * start ) - - ;
unsec / = 10 ;
}
}
char * testingTimerString ( testingTimer * t )
{
int64_t nsec ;
uint64_t unsec ;
int neg ;
char * s ;
int start ;
const struct timerStringPart * p ;
// The Go algorithm says 32 should be enough.
s = ( char * ) malloc ( 33 * sizeof ( char ) ) ;
// TODO handle failure
memset ( s , 0 , 33 * sizeof ( char ) ) ;
start = 32 ;
nsec = testingTimerNsec ( t ) ;
if ( nsec = = 0 ) {
s [ 0 ] = ' 0 ' ;
s [ 1 ] = ' s ' ;
return s ;
}
unsec = ( uint64_t ) nsec ;
neg = 0 ;
if ( nsec < 0 ) {
unsec = - unsec ;
neg = 1 ;
}
for ( p = parts ; p - > suffix ! = 0 ; p + + ) {
if ( p - > mode = = modeMaxAndStop & & unsec < p - > maxOrMod ) {
if ( p - > suffix2 ! = 0 ) {
s [ start - 1 ] = p - > suffix2 ;
start - - ;
}
s [ start - 1 ] = p - > suffix ;
start - - ;
fillFracPart ( s , p - > precision , & start , & unsec ) ;
fillIntPart ( s , & start , unsec ) ;
break ;
}
if ( p - > mode = = modeFracModContinue & & unsec ! = 0 ) {
if ( p - > suffix2 ! = 0 ) {
s [ start - 1 ] = p - > suffix2 ;
start - - ;
}
s [ start - 1 ] = p - > suffix ;
start - - ;
fillFracPart ( s , p - > precision , & start , & unsec ) ;
fillIntPart ( s , & start , unsec % p - > maxOrMod ) ;
unsec / = p - > maxOrMod ;
// and move on to the next one
}
}
if ( neg ) {
s [ start - 1 ] = ' - ' ;
start - - ;
}
memmove ( s , s + start , 33 - start ) ;
return s ;
}
void testingFreeTimerString ( char * s )
{
free ( s ) ;
}