Implemented g_test_trap_fork() API.

* gtestframework.h: added g_assert_cmphex(). reworked g_test_trap*() API.

* gtestframework.c: implemented g_test_trap_fork() API.

svn path=/trunk/; revision=5879
This commit is contained in:
Tim Janik 2007-11-20 15:00:25 +00:00
parent ca1c42405e
commit f1d751b76e
2 changed files with 352 additions and 17 deletions

View File

@ -18,8 +18,17 @@
*/ */
#include "config.h" #include "config.h"
#include "gtestframework.h" #include "gtestframework.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif /* HAVE_SYS_SELECT_H */
/* --- structures --- */ /* --- structures --- */
struct GTestCase struct GTestCase
@ -37,7 +46,11 @@ struct GTestSuite
GSList *cases; GSList *cases;
}; };
/* --- prototypes --- */
static void test_trap_clear (void);
/* --- variables --- */ /* --- variables --- */
static int test_stdmsg = 1;
static gboolean test_mode_quick = TRUE; static gboolean test_mode_quick = TRUE;
static gboolean test_mode_perf = FALSE; static gboolean test_mode_perf = FALSE;
static gboolean test_mode_fatal = TRUE; static gboolean test_mode_fatal = TRUE;
@ -51,6 +64,10 @@ static gchar *test_run_name = "";
static GSList *test_paths = NULL; static GSList *test_paths = NULL;
static GTestSuite *test_suite_root = NULL; static GTestSuite *test_suite_root = NULL;
static GSList *test_run_free_queue = NULL; static GSList *test_run_free_queue = NULL;
static int test_trap_last_status = 0;
static int test_trap_last_pid = 0;
static char *test_trap_last_stdout = NULL;
static char *test_trap_last_stderr = NULL;
/* --- functions --- */ /* --- functions --- */
static void static void
@ -270,14 +287,15 @@ test_case_run (GTestCase *tc)
if (tc->fixture_setup) if (tc->fixture_setup)
tc->fixture_setup (fixture); tc->fixture_setup (fixture);
tc->fixture_test (fixture); tc->fixture_test (fixture);
if (tc->fixture_teardown) test_trap_clear();
tc->fixture_teardown (fixture);
while (test_run_free_queue) while (test_run_free_queue)
{ {
gpointer freeme = test_run_free_queue->data; gpointer freeme = test_run_free_queue->data;
test_run_free_queue = g_slist_delete_link (test_run_free_queue, test_run_free_queue); test_run_free_queue = g_slist_delete_link (test_run_free_queue, test_run_free_queue);
g_free (freeme); g_free (freeme);
} }
if (tc->fixture_teardown)
tc->fixture_teardown (fixture);
g_free (fixture); g_free (fixture);
if (!test_run_quiet) if (!test_run_quiet)
g_print ("OK\n"); g_print ("OK\n");
@ -398,6 +416,7 @@ g_assertion_message_cmpnum (const char *domain,
switch (numtype) switch (numtype)
{ {
case 'i': s = g_strdup_printf ("assertion failed (%s): (%.0Lf %s %.0Lf)", expr, arg1, cmp, arg2); break; case 'i': s = g_strdup_printf ("assertion failed (%s): (%.0Lf %s %.0Lf)", expr, arg1, cmp, arg2); break;
case 'x': s = g_strdup_printf ("assertion failed (%s): (0x%08Lx %s 0x%08Lx)", expr, (guint64) arg1, cmp, (guint64) arg2); break;
case 'f': s = g_strdup_printf ("assertion failed (%s): (%.9Lg %s %.9Lg)", expr, arg1, cmp, arg2); break; case 'f': s = g_strdup_printf ("assertion failed (%s): (%.9Lg %s %.9Lg)", expr, arg1, cmp, arg2); break;
/* ideally use: floats=%.7g double=%.17g */ /* ideally use: floats=%.7g double=%.17g */
} }
@ -437,3 +456,300 @@ g_strcmp0 (const char *str1,
return str1 != str2; return str1 != str2;
return strcmp (str1, str2); return strcmp (str1, str2);
} }
static int /* 0 on success */
kill_child (int pid,
int *status,
int patience)
{
int wr;
if (patience >= 3) /* try graceful reap */
{
if (waitpid (pid, status, WNOHANG) > 0)
return 0;
}
if (patience >= 2) /* try SIGHUP */
{
kill (pid, SIGHUP);
if (waitpid (pid, status, WNOHANG) > 0)
return 0;
g_usleep (20 * 1000); /* give it some scheduling/shutdown time */
if (waitpid (pid, status, WNOHANG) > 0)
return 0;
g_usleep (50 * 1000); /* give it some scheduling/shutdown time */
if (waitpid (pid, status, WNOHANG) > 0)
return 0;
g_usleep (100 * 1000); /* give it some scheduling/shutdown time */
if (waitpid (pid, status, WNOHANG) > 0)
return 0;
}
if (patience >= 1) /* try SIGTERM */
{
kill (pid, SIGTERM);
if (waitpid (pid, status, WNOHANG) > 0)
return 0;
g_usleep (200 * 1000); /* give it some scheduling/shutdown time */
if (waitpid (pid, status, WNOHANG) > 0)
return 0;
g_usleep (400 * 1000); /* give it some scheduling/shutdown time */
if (waitpid (pid, status, WNOHANG) > 0)
return 0;
}
/* finish it off */
kill (pid, SIGKILL);
do
wr = waitpid (pid, status, 0);
while (wr < 0 && errno == EINTR);
return wr;
}
static inline int
g_string_must_read (GString *gstring,
int fd)
{
#define STRING_BUFFER_SIZE 4096
char buf[STRING_BUFFER_SIZE];
gssize bytes;
again:
bytes = read (fd, buf, sizeof (buf));
if (bytes == 0)
return 0; /* EOF, calling this function assumes data is available */
else if (bytes > 0)
{
g_string_append_len (gstring, buf, bytes);
return 1;
}
else if (bytes < 0 && errno == EINTR)
goto again;
else /* bytes < 0 */
{
g_warning ("failed to read() from child process (%d): %s", test_trap_last_pid, g_strerror (errno));
return 1; /* ignore error after warning */
}
}
static inline void
g_string_write_out (GString *gstring,
int outfd,
int *stringpos)
{
if (*stringpos < gstring->len)
{
int r;
do
r = write (outfd, gstring->str + *stringpos, gstring->len - *stringpos);
while (r < 0 && errno == EINTR);
*stringpos += MAX (r, 0);
}
}
static int
sane_dup2 (int fd1,
int fd2)
{
int ret;
do
ret = dup2 (fd1, fd2);
while (ret < 0 && errno == EINTR);
return ret;
}
static void
test_trap_clear (void)
{
test_trap_last_status = 0;
test_trap_last_pid = 0;
g_free (test_trap_last_stdout);
test_trap_last_stdout = NULL;
g_free (test_trap_last_stderr);
test_trap_last_stderr = NULL;
}
static guint64
test_time_stamp (void)
{
GTimeVal tv;
guint64 stamp;
g_get_current_time (&tv);
stamp = tv.tv_sec;
stamp = stamp * 1000000 + tv.tv_usec;
return stamp;
}
gboolean
g_test_trap_fork (guint64 usec_timeout,
GTestTrapFlags test_trap_flags)
{
int stdout_pipe[2] = { -1, -1 };
int stderr_pipe[2] = { -1, -1 };
int stdtst_pipe[2] = { -1, -1 };
test_trap_clear();
if (pipe (stdout_pipe) < 0 || pipe (stderr_pipe) < 0 || pipe (stdtst_pipe) < 0)
g_error ("failed to create pipes to fork test program: %s", g_strerror (errno));
signal (SIGCHLD, SIG_DFL);
test_trap_last_pid = fork ();
if (test_trap_last_pid < 0)
g_error ("failed to fork test program: %s", g_strerror (errno));
if (test_trap_last_pid == 0) /* child */
{
int fd0 = -1;
close (stdout_pipe[0]);
close (stderr_pipe[0]);
close (stdtst_pipe[0]);
if (!(test_trap_flags & G_TEST_TRAP_INHERIT_STDIN))
fd0 = open ("/dev/null", O_RDONLY);
if (sane_dup2 (stdout_pipe[1], 1) < 0 || sane_dup2 (stderr_pipe[1], 2) < 0 || (fd0 >= 0 && sane_dup2 (fd0, 0) < 0))
g_error ("failed to dup2() in forked test program: %s", g_strerror (errno));
if (fd0 >= 3)
close (fd0);
if (stdout_pipe[1] >= 3)
close (stdout_pipe[1]);
if (stderr_pipe[1] >= 3)
close (stderr_pipe[1]);
test_stdmsg = stdtst_pipe[1];
return TRUE;
}
else /* parent */
{
GString *sout = g_string_new (NULL);
GString *serr = g_string_new (NULL);
GString *stst = g_string_new (NULL);
guint64 sstamp;
int soutpos = 0, serrpos = 0, ststpos = 0, wr, need_wait = TRUE;
close (stdout_pipe[1]);
close (stderr_pipe[1]);
close (stdtst_pipe[1]);
sstamp = test_time_stamp();
/* read data until we get EOF on all pipes */
while (stdout_pipe[0] >= 0 || stderr_pipe[0] >= 0 || stdtst_pipe[0] > 0)
{
fd_set fds;
struct timeval tv;
FD_ZERO (&fds);
if (stdout_pipe[0] >= 0)
FD_SET (stdout_pipe[0], &fds);
if (stderr_pipe[0] >= 0)
FD_SET (stderr_pipe[0], &fds);
if (stdtst_pipe[0] >= 0)
FD_SET (stdtst_pipe[0], &fds);
tv.tv_sec = 0;
tv.tv_usec = MIN (usec_timeout ? usec_timeout : 1000000, 100 * 1000); // sleep at most 0.5 seconds to catch clock skews, etc.
int ret = select (MAX (MAX (stdout_pipe[0], stderr_pipe[0]), stdtst_pipe[0]) + 1, &fds, NULL, NULL, &tv);
if (ret < 0 && errno != EINTR)
{
g_warning ("Unexpected error in select() while reading from child process (%d): %s", test_trap_last_pid, g_strerror (errno));
break;
}
if (stdout_pipe[0] >= 0 && FD_ISSET (stdout_pipe[0], &fds) &&
g_string_must_read (sout, stdout_pipe[0]) == 0)
{
close (stdout_pipe[0]);
stdout_pipe[0] = -1;
}
if (stderr_pipe[0] >= 0 && FD_ISSET (stderr_pipe[0], &fds) &&
g_string_must_read (serr, stderr_pipe[0]) == 0)
{
close (stderr_pipe[0]);
stderr_pipe[0] = -1;
}
if (stdtst_pipe[0] >= 0 && FD_ISSET (stdtst_pipe[0], &fds) &&
g_string_must_read (stst, stdtst_pipe[0]) == 0)
{
close (stdtst_pipe[0]);
stdtst_pipe[0] = -1;
}
if (!(test_trap_flags & G_TEST_TRAP_SILENCE_STDOUT))
g_string_write_out (sout, 1, &soutpos);
if (!(test_trap_flags & G_TEST_TRAP_SILENCE_STDERR))
g_string_write_out (serr, 2, &serrpos);
if (TRUE) // FIXME: needs capturing into log file
g_string_write_out (stst, 1, &ststpos);
if (usec_timeout)
{
guint64 nstamp = test_time_stamp();
int status = 0;
sstamp = MIN (sstamp, nstamp); // guard against backwards clock skews
if (usec_timeout < nstamp - sstamp)
{
/* timeout reached, need to abort the child now */
kill_child (test_trap_last_pid, &status, 3);
test_trap_last_status = 1024; /* timeout */
if (0 && WIFSIGNALED (status))
g_printerr ("%s: child timed out and received: %s\n", G_STRFUNC, g_strsignal (WTERMSIG (status)));
need_wait = FALSE;
break;
}
}
}
close (stdout_pipe[0]);
close (stderr_pipe[0]);
close (stdtst_pipe[0]);
if (need_wait)
{
int status = 0;
do
wr = waitpid (test_trap_last_pid, &status, 0);
while (wr < 0 && errno == EINTR);
if (WIFEXITED (status)) /* normal exit */
test_trap_last_status = WEXITSTATUS (status); /* 0..255 */
else if (WIFSIGNALED (status))
test_trap_last_status = (WTERMSIG (status) << 12); /* signalled */
else /* WCOREDUMP (status) */
test_trap_last_status = 512; /* coredump */
}
test_trap_last_stdout = g_string_free (sout, FALSE);
test_trap_last_stderr = g_string_free (serr, FALSE);
g_string_free (stst, TRUE);
return FALSE;
}
}
gboolean
g_test_trap_has_passed (void)
{
return test_trap_last_status == 0; /* exit_status == 0 && !signal && !coredump */
}
gboolean
g_test_trap_reached_timeout (void)
{
return 0 != (test_trap_last_status & 1024); /* timeout flag */
}
void
g_test_trap_assertions (const char *domain,
const char *file,
int line,
const char *func,
gboolean must_pass,
gboolean must_fail,
const char *stdout_pattern,
const char *stderr_pattern)
{
if (test_trap_last_pid == 0)
g_error ("child process failed to exit after g_test_trap_fork() and before g_test_trap_assert*()");
if (must_pass && !g_test_trap_has_passed())
{
char *msg = g_strdup_printf ("child process (%d) of test trap failed unexpectedly", test_trap_last_pid);
g_assertion_message (domain, file, line, func, msg);
g_free (msg);
}
if (must_fail && g_test_trap_has_passed())
{
char *msg = g_strdup_printf ("child process (%d) did not fail as expected", test_trap_last_pid);
g_assertion_message (domain, file, line, func, msg);
g_free (msg);
}
if (stdout_pattern && !g_pattern_match_simple (stdout_pattern, test_trap_last_stdout))
{
char *msg = g_strdup_printf ("stdout of child process (%d) failed to match: %s", test_trap_last_pid, stdout_pattern);
g_assertion_message (domain, file, line, func, msg);
g_free (msg);
}
if (stderr_pattern && !g_pattern_match_simple (stderr_pattern, test_trap_last_stderr))
{
char *msg = g_strdup_printf ("stderr of child process (%d) failed to match: %s", test_trap_last_pid, stderr_pattern);
g_assertion_message (domain, file, line, func, msg);
g_free (msg);
}
}

View File

@ -33,6 +33,9 @@ typedef struct GTestSuite GTestSuite;
#define g_assert_cmpint(n1, cmp, n2) do { if (n1 cmp n2) ; else \ #define g_assert_cmpint(n1, cmp, n2) do { if (n1 cmp n2) ; else \
g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#n1 " " #cmp " " #n2, n1, #cmp, n2, 'i'); } while (0) #n1 " " #cmp " " #n2, n1, #cmp, n2, 'i'); } while (0)
#define g_assert_cmphex(n1, cmp, n2) do { if (n1 cmp n2) ; else \
g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#n1 " " #cmp " " #n2, n1, #cmp, n2, 'x'); } while (0)
#define g_assert_cmpfloat(n1,cmp,n2) do { if (n1 cmp n2) ; else \ #define g_assert_cmpfloat(n1,cmp,n2) do { if (n1 cmp n2) ; else \
g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
#n1 " " #cmp " " #n2, n1, #cmp, n2, 'f'); } while (0) #n1 " " #cmp " " #n2, n1, #cmp, n2, 'f'); } while (0)
@ -49,13 +52,6 @@ void g_test_maximized_result (double maximized_quantity,
const char *format, const char *format,
...) G_GNUC_PRINTF (2, 3); ...) G_GNUC_PRINTF (2, 3);
/* guards used around forked tests */
guint g_test_trap_fork ();
void g_test_trap_assert_passed (void);
void g_test_trap_assert_failed (void);
void g_test_trap_assert_stdout (const char *stdout_pattern);
void g_test_trap_assert_stderr (const char *stderr_pattern);
/* initialize testing framework */ /* initialize testing framework */
void g_test_init (int *argc, void g_test_init (int *argc,
char ***argv, char ***argv,
@ -83,25 +79,48 @@ double g_test_timer_last (void); // repeat last elapsed() result
void g_test_queue_free (gpointer gfree_pointer); void g_test_queue_free (gpointer gfree_pointer);
void g_test_queue_unref (gpointer gobjectunref_pointer); void g_test_queue_unref (gpointer gobjectunref_pointer);
/* test traps are guards used around forked tests */
typedef enum {
G_TEST_TRAP_SILENCE_STDOUT = 1 << 7,
G_TEST_TRAP_SILENCE_STDERR = 1 << 8,
G_TEST_TRAP_INHERIT_STDIN = 1 << 9,
} GTestTrapFlags;
gboolean g_test_trap_fork (guint64 usec_timeout,
GTestTrapFlags test_trap_flags);
gboolean g_test_trap_has_passed (void);
gboolean g_test_trap_reached_timeout (void);
#define g_test_trap_assert_passed() g_test_trap_assertions (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, 1, 0, 0, 0)
#define g_test_trap_assert_failed() g_test_trap_assertions (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, 0, 1, 0, 0)
#define g_test_trap_assert_stdout(soutpattern) g_test_trap_assertions (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, 0, 0, soutpattern, 0)
#define g_test_trap_assert_stderr(serrpattern) g_test_trap_assertions (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, 0, 0, 0, serrpattern)
/* provide seed-able random numbers for tests */ /* provide seed-able random numbers for tests */
long double g_test_rand_range (long double range_start, double g_test_rand_range (double range_start,
long double range_end); double range_end);
/* semi-internal API */ /* semi-internal API */
GTestCase* g_test_create_case (const char *test_name, GTestCase* g_test_create_case (const char *test_name,
gsize data_size, gsize data_size,
void (*data_setup) (void), void (*data_setup) (void),
void (*data_test) (void), void (*data_test) (void),
void (*data_teardown) (void)); void (*data_teardown) (void));
GTestSuite* g_test_create_suite (const char *suite_name); GTestSuite* g_test_create_suite (const char *suite_name);
GTestSuite* g_test_get_root (void); GTestSuite* g_test_get_root (void);
void g_test_suite_add (GTestSuite *suite, void g_test_suite_add (GTestSuite *suite,
GTestCase *test_case); GTestCase *test_case);
void g_test_suite_add_suite (GTestSuite *suite, void g_test_suite_add_suite (GTestSuite *suite,
GTestSuite *nestedsuite); GTestSuite *nestedsuite);
int g_test_run_suite (GTestSuite *suite); int g_test_run_suite (GTestSuite *suite);
/* internal ABI */ /* internal ABI */
void g_test_trap_assertions (const char *domain,
const char *file,
int line,
const char *func,
gboolean must_pass,
gboolean must_fail,
const char *stdout_pattern,
const char *stderr_pattern);
void g_assertion_message (const char *domain, void g_assertion_message (const char *domain,
const char *file, const char *file,
int line, int line,