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-04 20:47:04 -05:00
// goddamnit VS2013
# ifdef _MSC_VER
# define testingprivSnprintf _snprintf
# else
2019-05-05 01:17:11 -05:00
# define testingprivSnprintf snprintf
2019-05-04 20:47:04 -05:00
# endif
// and yes, vsnprintf() IS properly provided, so wtf
2019-04-29 22:46:08 -05:00
void testingprivInternalError ( const char * fmt , . . . )
{
va_list ap ;
va_start ( ap , fmt ) ;
fprintf ( stderr , " ** testing internal error: " ) ;
vfprintf ( stderr , fmt , ap ) ;
fprintf ( stderr , " ; aborting \n " ) ;
va_end ( ap ) ;
abort ( ) ;
}
void * testingprivMalloc ( size_t n , const char * what )
{
void * x ;
x = malloc ( n ) ;
if ( x = = NULL )
testingprivInternalError ( " memory exhausted allocating %s " , what ) ;
memset ( x , 0 , n ) ;
return x ;
}
2019-05-04 15:47:56 -05:00
# define testingprivNew(T) ((T *) testingprivMalloc(sizeof (T), #T))
# define testingprivNewArray(T, n) ((T *) testingprivMalloc(n * sizeof (T), #T "[" #n "]"))
2019-04-29 22:46:08 -05:00
void * testingprivRealloc ( void * x , size_t n , const char * what )
{
void * y ;
y = realloc ( x , n ) ;
if ( y = = NULL )
testingprivInternalError ( " memory exhausted reallocating %s " , what ) ;
return y ;
}
2019-05-04 15:47:56 -05:00
# define testingprivResizeArray(x, T, n) ((T *) testingprivRealloc(x, n * sizeof (T), #T "[" #n "]"))
2019-04-29 22:46:08 -05:00
void testingprivFree ( void * x )
{
free ( x ) ;
}
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 {
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-05-04 20:13:47 -05:00
int indent ;
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
{
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 ) {
set - > cap + = nGrow ;
2019-04-29 22:46:08 -05:00
set - > tests = testingprivResizeArray ( set - > tests , testingT , 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: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-05-04 20:13:47 -05:00
static void printIndent ( int n )
{
for ( ; n ! = 0 ; n - - )
printf ( " " ) ;
}
static void vprintfIndented ( 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 = vsnprintf ( NULL , 0 , format , ap2 ) ;
// TODO handle n < 0 case
va_end ( ap2 ) ;
buf = testingprivNewArray ( char , n + 1 ) ;
vsnprintf ( 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 ' ;
printIndent ( indent ) ;
printf ( " %s \n " , lineStart ) ;
lineStart = lineEnd + 1 ;
if ( firstLine ) {
// subsequent lines are indented twice
indent + + ;
firstLine = 0 ;
}
}
// print the last line
printIndent ( indent ) ;
printf ( " %s \n " , lineStart ) ;
testingprivFree ( buf ) ;
}
static void printfIndented ( int indent , const char * format , . . . )
{
va_list ap ;
va_start ( ap , format ) ;
vprintfIndented ( indent , format , ap ) ;
va_end ( ap ) ;
}
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-04 20:13:47 -05:00
static void testsetRun ( struct testset * set , int indent , 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-05-02 11:38:59 -05:00
timerTime start , end ;
2019-05-02 21:03:57 -05:00
char timerstr [ timerDurationStringLen ] ;
2019-04-10 12:23:25 -05:00
2019-04-17 21:12:32 -05:00
t = set - > tests ;
for ( i = 0 ; i < set - > len ; i + + ) {
2019-05-04 20:13:47 -05:00
printfIndented ( indent , " === RUN %s \n " , t - > name ) ;
t - > indent = indent + 1 ;
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 ) ;
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-05-02 11:38:59 -05:00
timerDurationString ( timerTimeSub ( end , start ) , timerstr ) ;
2019-05-04 20:13:47 -05:00
printfIndented ( indent , " --- %s: %s (%s) \n " , status , t - > name , timerstr ) ;
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
}
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-05-04 20:13:47 -05:00
testsetRun ( & testsBefore , 0 , & 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 )
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-04 20:47:04 -05:00
n = testingprivSnprintf ( NULL , 0 , " %s:%ld: " , file , line ) ;
2019-05-04 20:13:47 -05:00
// TODO handle n < 0 case
va_copy ( ap2 , ap ) ;
n2 = vsnprintf ( NULL , 0 , format , ap2 ) ;
// TODO handle n2 < 0 case
va_end ( ap2 ) ;
buf = testingprivNewArray ( char , n + n2 + 1 ) ;
2019-05-04 20:47:04 -05:00
testingprivSnprintf ( buf , n + 1 , " %s:%ld: " , file , line ) ;
2019-05-04 20:13:47 -05:00
vsnprintf ( buf + n , n2 + 1 , format , ap ) ;
printfIndented ( t - > indent , " %s " , buf ) ;
testingprivFree ( 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 ;
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 ;
}