*/
#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
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;
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
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");
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 */
}
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);
+ }
+}
#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)
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,
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,
+GTestCase* g_test_create_case (const char *test_name,
gsize data_size,
void (*data_setup) (void),
void (*data_test) (void),
void (*data_teardown) (void));
-GTestSuite* g_test_create_suite (const char *suite_name);
-GTestSuite* g_test_get_root (void);
-void g_test_suite_add (GTestSuite *suite,
+GTestSuite* g_test_create_suite (const char *suite_name);
+GTestSuite* g_test_get_root (void);
+void g_test_suite_add (GTestSuite *suite,
GTestCase *test_case);
-void g_test_suite_add_suite (GTestSuite *suite,
+void g_test_suite_add_suite (GTestSuite *suite,
GTestSuite *nestedsuite);
-int g_test_run_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,