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-19 16:55:31 -05:00
# include "testingpriv.h"
2019-05-19 10:05:20 -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 ;
2019-05-19 16:55:31 -05:00
o - > buf = testingprivResizeArray ( o - > buf , char , prevcap , o - > cap ) ;
2019-05-05 13:26:38 -05:00
}
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 ) ;
2019-05-19 16:55:31 -05:00
n = testingprivVsnprintf ( NULL , 0 , format , ap2 ) ;
2019-05-05 13:26:38 -05:00
va_end ( ap2 ) ;
2019-05-19 16:55:31 -05:00
buf = testingprivNewArray ( char , n + 1 ) ;
testingprivVsnprintf ( buf , n + 1 , format , ap ) ;
2019-05-05 13:26:38 -05:00
// 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 ) ;
2019-05-19 16:55:31 -05:00
testingprivFree ( buf ) ;
2019-05-05 13:26:38 -05:00
}
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 ;
2019-05-19 10:05:20 -05:00
char * computedName ;
2019-04-10 12:23:25 -05:00
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 ;
2019-05-19 16:55:31 -05:00
t - > computedName = testingprivStrdup ( name ) ;
2019-04-10 12:23:25 -05:00
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-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-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 ;
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 ) ;
initTest ( t , name , f , 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 ;
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-10 20:16:29 -05:00
static const testingOptions defaultOptions = {
2019-05-05 14:59:59 -05:00
. Verbose = 0 ,
} ;
2019-05-10 20:16:29 -05:00
static void testingprivSetRun ( testingSet * set , const testingOptions * options , 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-05-05 14:59:59 -05:00
int printStatus ;
2019-04-10 12:23:25 -05:00
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-10 20:16:29 -05:00
if ( options - > Verbose )
2019-05-05 14:59:59 -05:00
outbufPrintf ( NULL , indent , " === RUN %s " , t - > name ) ;
2019-05-04 20:13:47 -05:00
t - > indent = indent + 1 ;
2019-05-10 20:16:29 -05:00
t - > verbose = options - > 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-10 20:16:29 -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-04-10 13:19:17 -05:00
}
2019-05-10 20:16:29 -05:00
void testingSetRun ( testingSet * set , const struct testingOptions * options , int * anyRun , int * anyFailed )
2019-04-10 13:19:17 -05:00
{
2019-05-10 20:16:29 -05:00
* anyRun = 0 ;
* anyFailed = 0 ;
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 ;
testingprivSetRun ( set , options , 0 , anyFailed ) ;
* anyRun = 1 ;
2019-04-10 12:23:25 -05:00
}
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-19 16:55:31 -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 ) ;
2019-05-19 16:55:31 -05:00
n2 = testingprivVsnprintf ( NULL , 0 , format , ap2 ) ;
2019-05-04 20:13:47 -05:00
// TODO handle n2 < 0 case
va_end ( ap2 ) ;
2019-05-19 16:55:31 -05:00
buf = testingprivNewArray ( char , n + n2 + 1 ) ;
testingprivSnprintf ( buf , n + 1 , " %s:%ld: " , file , line ) ;
testingprivVsnprintf ( buf + n , n2 + 1 , format , ap ) ;
2019-05-05 13:26:38 -05:00
outbufPrintf ( & ( t - > output ) , t - > indent , " %s " , buf ) ;
2019-05-19 16:55:31 -05:00
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 ;
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 ;
}