mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-25 06:56:14 +01:00
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:
parent
ca1c42405e
commit
f1d751b76e
@ -18,8 +18,17 @@
|
||||
*/
|
||||
#include "config.h"
|
||||
#include "gtestframework.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.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 --- */
|
||||
struct GTestCase
|
||||
@ -37,7 +46,11 @@ struct GTestSuite
|
||||
GSList *cases;
|
||||
};
|
||||
|
||||
/* --- prototypes --- */
|
||||
static void test_trap_clear (void);
|
||||
|
||||
/* --- variables --- */
|
||||
static int test_stdmsg = 1;
|
||||
static gboolean test_mode_quick = TRUE;
|
||||
static gboolean test_mode_perf = FALSE;
|
||||
static gboolean test_mode_fatal = TRUE;
|
||||
@ -51,6 +64,10 @@ static gchar *test_run_name = "";
|
||||
static GSList *test_paths = NULL;
|
||||
static GTestSuite *test_suite_root = 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 --- */
|
||||
static void
|
||||
@ -270,14 +287,15 @@ test_case_run (GTestCase *tc)
|
||||
if (tc->fixture_setup)
|
||||
tc->fixture_setup (fixture);
|
||||
tc->fixture_test (fixture);
|
||||
if (tc->fixture_teardown)
|
||||
tc->fixture_teardown (fixture);
|
||||
test_trap_clear();
|
||||
while (test_run_free_queue)
|
||||
{
|
||||
gpointer freeme = test_run_free_queue->data;
|
||||
test_run_free_queue = g_slist_delete_link (test_run_free_queue, test_run_free_queue);
|
||||
g_free (freeme);
|
||||
}
|
||||
if (tc->fixture_teardown)
|
||||
tc->fixture_teardown (fixture);
|
||||
g_free (fixture);
|
||||
if (!test_run_quiet)
|
||||
g_print ("OK\n");
|
||||
@ -398,6 +416,7 @@ g_assertion_message_cmpnum (const char *domain,
|
||||
switch (numtype)
|
||||
{
|
||||
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;
|
||||
/* ideally use: floats=%.7g double=%.17g */
|
||||
}
|
||||
@ -437,3 +456,300 @@ g_strcmp0 (const char *str1,
|
||||
return 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);
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,9 @@ typedef struct GTestSuite GTestSuite;
|
||||
#define g_assert_cmpint(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, '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 \
|
||||
g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
|
||||
#n1 " " #cmp " " #n2, n1, #cmp, n2, 'f'); } while (0)
|
||||
@ -49,13 +52,6 @@ void g_test_maximized_result (double maximized_quantity,
|
||||
const char *format,
|
||||
...) 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 */
|
||||
void g_test_init (int *argc,
|
||||
char ***argv,
|
||||
@ -83,9 +79,24 @@ double g_test_timer_last (void); // repeat last elapsed() result
|
||||
void g_test_queue_free (gpointer gfree_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 */
|
||||
long double g_test_rand_range (long double range_start,
|
||||
long double range_end);
|
||||
double g_test_rand_range (double range_start,
|
||||
double range_end);
|
||||
|
||||
/* semi-internal API */
|
||||
GTestCase* g_test_create_case (const char *test_name,
|
||||
@ -102,6 +113,14 @@ void g_test_suite_add_suite (GTestSuite *suite,
|
||||
int g_test_run_suite (GTestSuite *suite);
|
||||
|
||||
/* 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,
|
||||
const char *file,
|
||||
int line,
|
||||
|
Loading…
Reference in New Issue
Block a user