2019-04-10 12:23:25 -05:00
// 27 february 2018
2019-05-04 20:47:04 -05:00
// TODO get rid of the need for this (it temporarily silences noise so I can find actual build issues)
# ifdef _MSC_VER
# define _CRT_SECURE_NO_WARNINGS
# endif
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-05 12:35:07 -05:00
static void internalError ( const char * fmt , . . . )
2019-04-29 22:46:08 -05:00
{
va_list ap ;
va_start ( ap , fmt ) ;
fprintf ( stderr , " ** testing internal error: " ) ;
vfprintf ( stderr , fmt , ap ) ;
fprintf ( stderr , " ; aborting \n " ) ;
va_end ( ap ) ;
abort ( ) ;
}
2019-05-05 12:35:07 -05:00
static void * mustmalloc ( size_t n , const char * what )
2019-04-29 22:46:08 -05:00
{
void * x ;
x = malloc ( n ) ;
if ( x = = NULL )
2019-05-05 12:35:07 -05:00
internalError ( " memory exhausted allocating %s " , what ) ;
2019-04-29 22:46:08 -05:00
memset ( x , 0 , n ) ;
return x ;
}
2019-05-05 12:35:07 -05:00
# define new(T) ((T *) mustmalloc(sizeof (T), #T))
# define newArray(T, n) ((T *) mustmalloc(n * sizeof (T), #T "[" #n "]"))
2019-05-04 15:47:56 -05:00
2019-05-05 13:26:38 -05:00
static void * mustrealloc ( void * x , size_t prevN , size_t n , const char * what )
2019-04-29 22:46:08 -05:00
{
void * y ;
2019-05-05 13:26:38 -05:00
// don't use realloc() because we want to clear the new memory
y = malloc ( n ) ;
2019-04-29 22:46:08 -05:00
if ( y = = NULL )
2019-05-05 12:35:07 -05:00
internalError ( " memory exhausted reallocating %s " , what ) ;
2019-05-05 13:26:38 -05:00
memset ( y , 0 , n ) ;
memmove ( y , x , prevN ) ;
free ( x ) ;
2019-04-29 22:46:08 -05:00
return y ;
}
2019-05-05 13:26:38 -05:00
# define resizeArray(x, T, prevN, n) ((T *) mustrealloc(x, prevN * sizeof (T), n * sizeof (T), #T "[" #n "]"))
2019-05-05 12:35:07 -05:00
2019-05-05 13:26:38 -05:00
static int mustvsnprintf ( char * s , size_t n , const char * format , va_list ap )
2019-05-05 12:35:07 -05:00
{
int ret ;
2019-05-05 13:26:38 -05:00
ret = vsnprintf ( s , n , format , ap ) ;
2019-05-05 12:35:07 -05:00
if ( ret < 0 )
internalError ( " encoding error in vsnprintf(); this likely means your call to testingTLogf() and the like is invalid " ) ;
return ret ;
}
2019-05-04 15:47:56 -05:00
2019-05-05 13:26:38 -05:00
static int mustsnprintf ( char * s , size_t n , const char * format , . . . )
2019-04-29 22:46:08 -05:00
{
2019-05-05 12:35:07 -05:00
va_list ap ;
int ret ;
2019-05-05 13:26:38 -05:00
va_start ( ap , format ) ;
ret = mustvsnprintf ( s , n , format , ap ) ;
2019-05-05 12:35:07 -05:00
va_end ( ap ) ;
return ret ;
2019-04-29 22:46:08 -05:00
}
2019-04-10 12:23:25 -05:00
2019-05-05 13:26:38 -05:00
// a struct outbuf of NULL writes directly to stdout
struct outbuf {
char * buf ;
size_t len ;
size_t cap ;
} ;
# define nOutbufGrow 32
static void outbufCopyStr ( struct outbuf * o , const char * str , size_t len )
{
size_t grow ;
if ( len = = 0 )
return ;
if ( o = = NULL ) {
printf ( " %s " , str ) ;
return ;
}
grow = len + 1 ;
if ( grow < nOutbufGrow )
grow = nOutbufGrow ;
if ( ( o - > len + grow ) > = o - > cap ) {
size_t prevcap ;
prevcap = o - > cap ;
o - > cap + = grow ;
o - > buf = resizeArray ( o - > buf , char , prevcap , o - > cap ) ;
}
memmove ( o - > buf + o - > len , str , len * sizeof ( char ) ) ;
o - > len + = len ;
}
# define outbufAddNewline(o) outbufCopyStr(o, "\n", 1)
static void outbufAddIndent ( struct outbuf * o , int n )
{
for ( ; n ! = 0 ; n - - )
outbufCopyStr ( o , " " , 4 ) ;
}
static void outbufVprintf ( struct outbuf * o , int indent , const char * format , va_list ap )
{
va_list ap2 ;
char * buf ;
int n ;
char * lineStart , * lineEnd ;
int firstLine = 1 ;
va_copy ( ap2 , ap ) ;
n = mustvsnprintf ( NULL , 0 , format , ap2 ) ;
// TODO handle n < 0 case
va_end ( ap2 ) ;
buf = newArray ( char , n + 1 ) ;
mustvsnprintf ( buf , n + 1 , format , ap ) ;
// strip trailing blank lines
while ( buf [ n - 1 ] = = ' \n ' )
n - - ;
buf [ n ] = ' \0 ' ;
lineStart = buf ;
for ( ; ; ) {
lineEnd = strchr ( lineStart , ' \n ' ) ;
if ( lineEnd = = NULL ) // last line
break ;
* lineEnd = ' \0 ' ;
outbufAddIndent ( o , indent ) ;
outbufCopyStr ( o , lineStart , lineEnd - lineStart ) ;
outbufAddNewline ( o ) ;
lineStart = lineEnd + 1 ;
if ( firstLine ) {
// subsequent lines are indented twice
indent + + ;
firstLine = 0 ;
}
}
// print the last line
outbufAddIndent ( o , indent ) ;
outbufCopyStr ( o , lineStart , strlen ( lineStart ) ) ;
outbufAddNewline ( o ) ;
free ( buf ) ;
}
static void outbufPrintf ( struct outbuf * o , int indent , const char * format , . . . )
{
va_list ap ;
va_start ( ap , format ) ;
outbufVprintf ( o , indent , format , ap ) ;
va_end ( ap ) ;
}
static void outbufCopy ( struct outbuf * o , const struct outbuf * from )
{
outbufCopyStr ( o , from - > buf , from - > len ) ;
}
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 ;
void ( * f ) ( testingT * ) ;
2019-04-17 21:12:32 -05:00
const char * file ;
long line ;
2019-05-05 12:35:07 -05:00
// test status
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 ;
2019-05-05 12:35:07 -05:00
// deferred functions
2019-04-10 12:23:25 -05:00
struct defer * defers ;
int defersRun ;
2019-05-05 12:35:07 -05:00
// output
2019-05-04 20:13:47 -05:00
int indent ;
2019-05-05 12:35:07 -05:00
int verbose ;
2019-05-05 13:26:38 -05:00
struct outbuf output ;
2019-04-10 12:23:25 -05:00
} ;
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
{
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-04-17 21:12:32 -05:00
t - > file = file ;
t - > line = line ;
2019-04-10 13:19:17 -05:00
}
2019-04-17 21:12:32 -05:00
# define nGrow 32
2019-05-09 21:54:14 -05:00
struct testingSet {
2019-04-17 21:12:32 -05:00
testingT * tests ;
size_t len ;
size_t cap ;
} ;
2019-05-09 21:54:14 -05:00
static testingSet mainTests = { NULL , 0 , 0 } ;
2019-04-17 21:12:32 -05:00
2019-05-09 21:54:14 -05:00
void testingprivSetRegisterTest ( testingSet * * pset , const char * name , void ( * f ) ( testingT * ) , const char * file , long line )
2019-04-10 13:19:17 -05:00
{
2019-05-09 21:54:14 -05:00
testingSet * set ;
set = & mainTests ;
if ( pset ! = NULL )
set = * pset ;
2019-04-17 21:12:32 -05:00
if ( set - > len = = set - > cap ) {
2019-05-05 13:26:38 -05:00
size_t prevcap ;
prevcap = set - > cap ;
2019-04-17 21:12:32 -05:00
set - > cap + = nGrow ;
2019-05-05 13:26:38 -05:00
set - > tests = resizeArray ( set - > tests , testingT , prevcap , set - > cap ) ;
2019-04-17 21:12:32 -05:00
}
initTest ( set - > tests + set - > len , name , f , file , line ) ;
set - > len + + ;
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 ;
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-05-05 14:59:59 -05:00
static testingOptions opts = {
. Verbose = 0 ,
} ;
2019-05-09 21:54:14 -05:00
static int testsetprivRun ( testingSet * set , int indent )
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-05-02 11:38:59 -05:00
timerTime start , end ;
2019-05-02 21:03:57 -05:00
char timerstr [ timerDurationStringLen ] ;
2019-05-05 14:59:59 -05:00
int printStatus ;
2019-05-09 21:54:14 -05:00
int anyFailed = 0 ;
2019-04-10 12:23:25 -05:00
2019-05-09 21:54:14 -05:00
qsort ( set - > tests , set - > len , sizeof ( testingT ) , testcmp ) ;
2019-04-17 21:12:32 -05:00
t = set - > tests ;
for ( i = 0 ; i < set - > len ; i + + ) {
2019-05-05 14:59:59 -05:00
if ( opts . Verbose )
outbufPrintf ( NULL , indent , " === RUN %s " , t - > name ) ;
2019-05-04 20:13:47 -05:00
t - > indent = indent + 1 ;
2019-05-05 14:59:59 -05:00
t - > verbose = opts . Verbose ;
2019-05-02 11:38:59 -05:00
start = timerMonotonicNow ( ) ;
2019-04-10 12:23:25 -05:00
if ( setjmp ( t - > returnNowBuf ) = = 0 )
( * ( t - > f ) ) ( t ) ;
2019-05-02 11:38:59 -05:00
end = timerMonotonicNow ( ) ;
2019-04-10 13:27:21 -05:00
t - > returned = 1 ;
2019-04-10 12:23:25 -05:00
runDefers ( t ) ;
2019-05-05 14:59:59 -05:00
printStatus = t - > verbose ;
2019-04-10 12:23:25 -05:00
status = " PASS " ;
if ( t - > failed ) {
status = " FAIL " ;
2019-05-05 14:59:59 -05:00
printStatus = 1 ; // always print status on failure
2019-05-09 21:54:14 -05:00
anyFailed = 1 ;
2019-04-10 12:23:25 -05:00
} else if ( t - > skipped )
// note that failed overrides skipped
status = " SKIP " ;
2019-05-02 11:38:59 -05:00
timerDurationString ( timerTimeSub ( end , start ) , timerstr ) ;
2019-05-05 14:59:59 -05:00
if ( printStatus ) {
outbufPrintf ( NULL , indent , " --- %s: %s (%s) " , status , t - > name , timerstr ) ;
outbufCopy ( NULL , & ( t - > output ) ) ;
}
2019-04-17 21:12:32 -05:00
t + + ;
2019-04-10 12:23:25 -05:00
}
2019-05-09 21:54:14 -05:00
return anyFailed ;
2019-04-10 13:19:17 -05:00
}
2019-05-09 21:54:14 -05:00
int testingMain ( testingSet * set , const struct testingOptions * options )
2019-04-10 13:19:17 -05:00
{
int anyFailed ;
2019-05-09 21:54:14 -05:00
if ( set = = NULL )
set = & mainTests ;
2019-05-05 14:59:59 -05:00
2019-04-10 13:19:17 -05:00
// TODO see if this should run if all tests are skipped
2019-05-09 21:54:14 -05:00
if ( set - > len = = 0 ) {
$ $ TODOTODO fprintf ( stderr , " warning: no tests to run \n " ) ;
2019-04-10 13:19:17 -05:00
// imitate Go here (TODO confirm this)
return 0 ;
}
2019-04-10 12:23:25 -05:00
2019-05-09 21:54:14 -05:00
return testingprivRun ( set , 0 ) ;
} $ $ TODOTODO {
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 )
2019-05-04 20:13:47 -05:00
testsetRun ( & tests , 0 , & 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 )
2019-05-04 20:13:47 -05:00
testsetRun ( & testsAfter , 0 , & 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
{
2019-05-04 20:13:47 -05:00
va_list ap2 ;
char * buf ;
int n , n2 ;
2019-04-10 12:23:25 -05:00
// TODO extract filename from file
2019-05-05 12:35:07 -05:00
n = mustsnprintf ( NULL , 0 , " %s:%ld: " , file , line ) ;
2019-05-04 20:13:47 -05:00
// TODO handle n < 0 case
va_copy ( ap2 , ap ) ;
2019-05-05 12:35:07 -05:00
n2 = mustvsnprintf ( NULL , 0 , format , ap2 ) ;
2019-05-04 20:13:47 -05:00
// TODO handle n2 < 0 case
va_end ( ap2 ) ;
2019-05-05 12:35:07 -05:00
buf = newArray ( char , n + n2 + 1 ) ;
mustsnprintf ( buf , n + 1 , " %s:%ld: " , file , line ) ;
mustvsnprintf ( buf + n , n2 + 1 , format , ap ) ;
2019-05-05 13:26:38 -05:00
outbufPrintf ( & ( t - > output ) , t - > indent , " %s " , buf ) ;
2019-05-05 12:35:07 -05:00
free ( buf ) ;
2019-04-10 12:23:25 -05:00
}
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-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
{
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 ;
2019-05-05 12:35:07 -05:00
d = new ( 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 ;
}