2019-04-28 12:12:40 -05:00
// 28 april 2019
2019-05-02 01:01:42 -05:00
// TODO pin down minimum POSIX versions (depends on what macOS 10.8 conforms to and what GLib/GTK+ require)
// TODO feature test macros for things like monotonic clocks?
// TODO is this needed in this file specifically, or just in testing_unix.c?
# define _POSIX_C_SOURCE 200112L
2019-04-28 12:12:40 -05:00
# include <errno.h>
# include <inttypes.h>
# include <setjmp.h>
# include <signal.h>
2019-04-28 20:22:11 -05:00
# include <stdlib.h>
2019-04-28 12:12:40 -05:00
# include <string.h>
2019-04-28 20:22:11 -05:00
# include <time.h>
2019-04-28 12:12:40 -05:00
# include <sys/time.h>
2019-04-28 20:22:11 -05:00
# include <pthread.h>
2019-04-30 20:00:52 -05:00
# include <unistd.h>
2019-04-28 12:12:40 -05:00
# include "testing.h"
2019-04-29 22:46:08 -05:00
# include "testingpriv.h"
2019-04-28 12:12:40 -05:00
2019-04-28 18:07:41 -05:00
// TODO don't start the timer on any platform until after we call setjmp(); also decide whether to start the timer before or after resuming the thread on Windows
2019-04-28 13:52:39 -05:00
2019-04-28 12:12:40 -05:00
static jmp_buf timeout_ret ;
static void onTimeout ( int sig )
{
longjmp ( timeout_ret , 1 ) ;
}
2019-04-28 12:26:15 -05:00
void testingprivRunWithTimeout ( testingT * t , const char * file , long line , int64_t timeout , void ( * f ) ( testingT * t , void * data ) , void * data , const char * comment , int failNowOnError )
2019-04-28 12:12:40 -05:00
{
char * timeoutstr ;
2019-04-28 13:52:39 -05:00
void ( * prevsig ) ( int ) ;
2019-04-30 08:32:48 -05:00
struct itimerval duration , prevDuration ;
2019-04-28 12:12:40 -05:00
int setitimerError = 0 ;
2019-04-28 12:19:04 -05:00
timeoutstr = testingNsecString ( timeout ) ;
2019-04-28 12:12:40 -05:00
prevsig = signal ( SIGALRM , onTimeout ) ;
2019-04-30 08:32:48 -05:00
duration . it_interval . tv_sec = 0 ;
duration . it_interval . tv_usec = 0 ;
duration . it_value . tv_sec = timeout / testingNsecPerSec ;
duration . it_value . tv_usec = ( timeout % testingNsecPerSec ) / testingNsecPerUsec ;
if ( setitimer ( ITIMER_REAL , & duration , & prevDuration ) ! = 0 ) {
2019-04-28 12:12:40 -05:00
setitimerError = errno ;
2019-04-28 12:26:15 -05:00
testingprivTLogfFull ( t , file , line , " error applying %s timeout: %s " , comment , strerror ( setitimerError ) ) ;
testingTFail ( t ) ;
2019-04-28 12:12:40 -05:00
goto out ;
}
if ( setjmp ( timeout_ret ) = = 0 ) {
( * f ) ( t , data ) ;
failNowOnError = 0 ; // we succeeded
2019-04-28 12:26:15 -05:00
} else {
testingprivTLogfFull ( t , file , line , " %s timeout passed (%s) " , comment , timeoutstr ) ;
testingTFail ( t ) ;
}
2019-04-28 12:12:40 -05:00
out :
if ( setitimerError = = 0 )
2019-04-30 08:32:48 -05:00
setitimer ( ITIMER_REAL , & prevDuration , NULL ) ;
2019-04-28 12:12:40 -05:00
signal ( SIGALRM , prevsig ) ;
2019-04-28 12:19:04 -05:00
testingFreeNsecString ( timeoutstr ) ;
2019-04-28 12:12:40 -05:00
if ( failNowOnError )
testingTFailNow ( t ) ;
}
2019-04-28 20:22:11 -05:00
void testingSleep ( int64_t nsec )
{
2019-04-30 20:00:52 -05:00
struct timespec duration , remaining ;
void ( * prevsig ) ( int ) ;
sigset_t set , prevSet ;
struct itimerval setiDuration , prevSetiDuration ;
unsigned sec ;
2019-04-28 20:22:11 -05:00
2019-04-30 08:32:48 -05:00
duration . tv_sec = nsec / testingNsecPerSec ;
duration . tv_nsec = nsec % testingNsecPerSec ;
2019-04-30 20:00:52 -05:00
for ( ; ; ) {
errno = 0 ;
if ( nanosleep ( & duration , & remaining ) = = 0 )
return ;
if ( errno ! = EINTR )
break ;
duration = remaining ;
}
// if we got here, nanosleep() failed outright
if ( sigemptyset ( & set ) ! = 0 )
goto fallback ;
if ( sigaddset ( & set , SIGABRT ) ! = 0 )
goto fallback ;
if ( pthread_sigmask ( SIG_BLOCK , & set , & prevSet ) ! = 0 )
goto fallback ;
prevsig = signal ( SIGALRM , SIG_IGN ) ;
setiDuration . it_interval . tv_sec = 0 ;
setiDuration . it_interval . tv_usec = 0 ;
// keep using duration for this in case nanosleep() was interrupted before it failed
setiDuration . it_value . tv_sec = duration . tv_sec ;
setiDuration . it_value . tv_usec = duration . tv_nsec / testingNsecPerUsec ;
if ( setitimer ( ITIMER_REAL , & setiDuration , & prevSetiDuration ) ! = 0 ) {
pthread_sigmask ( SIG_SETMASK , & prevSet , NULL ) ;
signal ( SIGALRM , prevsig ) ;
goto fallback ;
}
// TODO can this return an errno other than EINTR?
sigsuspend ( & prevSet ) ;
setitimer ( ITIMER_REAL , & prevSetiDuration , NULL ) ;
signal ( SIGALRM , prevsig ) ;
return ;
fallback :
// hopefully we never reach this point, because it has the least granularity of all, but there are no errors, so...
sec = duration . tv_sec ;
if ( duration . tv_nsec > 0 )
sec + + ;
while ( sec > 0 )
sec = sleep ( sec ) ;
2019-04-28 20:22:11 -05:00
}
struct testingThread {
pthread_t thread ;
void ( * f ) ( void * data ) ;
void * data ;
} ;
static void * threadThreadProc ( void * data )
{
testingThread * t = ( testingThread * ) data ;
( * ( t - > f ) ) ( t - > data ) ;
return NULL ;
}
testingThread * testingNewThread ( void ( * f ) ( void * data ) , void * data )
{
testingThread * t ;
2019-04-30 20:00:52 -05:00
int err ;
2019-04-28 20:22:11 -05:00
2019-04-29 22:46:08 -05:00
t = testingprivNew ( testingThread ) ;
2019-04-28 20:22:11 -05:00
t - > f = f ;
t - > data = data ;
2019-04-30 20:00:52 -05:00
err = pthread_create ( & ( t - > thread ) , NULL , threadThreadProc , t ) ;
if ( err ! = 0 )
testingprivInternalError ( " error creating thread: %s (%d) " , strerror ( err ) , err ) ;
2019-04-28 20:22:11 -05:00
return t ;
}
void testingThreadWaitAndFree ( testingThread * t )
{
2019-04-30 20:00:52 -05:00
int err ;
err = pthread_join ( t - > thread , NULL ) ;
if ( err ! = 0 )
testingprivInternalError ( " error waiting for thread to finish: %s (%d) " , strerror ( err ) , err ) ;
2019-04-28 20:22:11 -05:00
// TODO do we need to free t->thread somehow?
2019-04-29 22:46:08 -05:00
testingprivFree ( t ) ;
2019-04-28 20:22:11 -05:00
}