* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
+#include "config.h"
+
#include <glib.h>
+#include <glib-unix.h>
#include <gstdio.h>
#include <string.h>
#include <stdlib.h>
static gboolean subtest_mode_fatal = TRUE;
static gboolean subtest_mode_perf = FALSE;
static gboolean subtest_mode_quick = TRUE;
+static gboolean subtest_mode_undefined = TRUE;
static const gchar *subtest_seedstr = NULL;
+static gchar *subtest_last_seed = NULL;
static GSList *subtest_paths = NULL;
+static GSList *skipped_paths = NULL;
static GSList *subtest_args = NULL;
static gboolean testcase_open = FALSE;
static guint testcase_count = 0;
sindent (guint n)
{
static const char spaces[] = " ";
- int l = sizeof (spaces);
- n = MIN (n, l - 1);
+ int l = sizeof (spaces) - 1;
+ n = MIN (n, l);
return spaces + l - n;
}
static void
testcase_close (long double duration,
- guint exit_status,
+ gint exit_status,
guint n_forks)
{
g_return_if_fail (testcase_open > 0);
test_log_printfe ("%s<duration>%.6Lf</duration>\n", sindent (log_indent), duration);
- test_log_printfe ("%s<status exit-status=\"%d\" n-forks=\"%d\"/>\n",
- sindent (log_indent), exit_status, n_forks);
+ test_log_printfe ("%s<status exit-status=\"%d\" n-forks=\"%d\" result=\"%s\"/>\n",
+ sindent (log_indent), exit_status, n_forks,
+ exit_status ? "failed" : "success");
log_indent -= 2;
test_log_printfe ("%s</testcase>\n", sindent (log_indent));
testcase_open--;
if (gtester_verbose)
g_print ("%s\n", exit_status ? "FAIL" : "OK");
+ if (exit_status && subtest_last_seed)
+ g_print ("GTester: last random seed: %s\n", subtest_last_seed);
if (exit_status)
testcase_fail_count += 1;
- if (subtest_mode_fatal && testcase_fail_count)
+ if (subtest_mode_fatal && exit_status)
terminate();
}
{
switch (msg->log_type)
{
+ guint i;
+ gchar **strv;
case G_TEST_LOG_NONE:
+ case G_TEST_LOG_START_SUITE:
+ case G_TEST_LOG_STOP_SUITE:
break;
case G_TEST_LOG_ERROR:
- g_printerr ("%s\n", msg->strings[0]);
+ strv = g_strsplit (msg->strings[0], "\n", -1);
+ for (i = 0; strv[i]; i++)
+ test_log_printfe ("%s<error>%s</error>\n", sindent (log_indent), strv[i]);
+ g_strfreev (strv);
break;
case G_TEST_LOG_START_BINARY:
test_log_printfe ("%s<binary file=\"%s\"/>\n", sindent (log_indent), msg->strings[0]);
- test_log_printfe ("%s<random-seed>%s</random-seed>\n", sindent (log_indent), msg->strings[1]);
+ subtest_last_seed = g_strdup (msg->strings[1]);
+ test_log_printfe ("%s<random-seed>%s</random-seed>\n", sindent (log_indent), subtest_last_seed);
break;
case G_TEST_LOG_LIST_CASE:
g_print ("%s\n", msg->strings[0]);
log_indent += 2;
break;
case G_TEST_LOG_SKIP_CASE:
- if (TRUE && gtester_verbose) // enable to debug test case skipping logic
+ if (FALSE && gtester_verbose) /* enable to debug test case skipping logic */
{
gchar *sc = g_strconcat (msg->strings[0], ":", NULL);
gchar *sleft = g_strdup_printf ("%-68s", sc);
{
GTestLogBuffer *tlb = data;
GIOStatus status = G_IO_STATUS_NORMAL;
+ gboolean first_read_eof = FALSE, first_read = TRUE;
gsize length = 0;
do
{
guint8 buffer[READ_BUFFER_SIZE];
GError *error = NULL;
status = g_io_channel_read_chars (source, (gchar*) buffer, sizeof (buffer), &length, &error);
+ if (first_read && (condition & G_IO_IN))
+ {
+ /* on some unixes (MacOS) we need to detect non-blocking fd EOF
+ * by an IO_IN select/poll followed by read()==0.
+ */
+ first_read_eof = length == 0;
+ }
+ first_read = FALSE;
if (length)
{
GTestLogMsg *msg;
while (msg);
}
g_clear_error (&error);
- /* ignore the io channel status, which seems to be bogus especially for non blocking fds */
+ /* ignore the io channel status, which will report intermediate EOFs for non blocking fds */
(void) status;
}
while (length > 0);
- if (condition & (G_IO_ERR | G_IO_HUP))
+ /* g_print ("LASTIOSTATE: first_read_eof=%d condition=%d\n", first_read_eof, condition); */
+ if (first_read_eof || (condition & (G_IO_ERR | G_IO_HUP)))
{
/* if there's no data to read and select() reports an error or hangup,
* the fd must have been closed remotely
GTestLogBuffer *tlb;
GSList *slist, *free_list = NULL;
GError *error = NULL;
- const gchar *argv[99 + g_slist_length (subtest_args) + g_slist_length (subtest_paths)];
+ int argc = 0;
+ const gchar **argv;
GPid pid = 0;
gint report_pipe[2] = { -1, -1 };
+ guint child_report_cb_id = 0;
+ gboolean loop_pending;
gint i = 0;
- if (pipe (report_pipe) < 0)
+ if (!g_unix_open_pipe (report_pipe, FD_CLOEXEC, &error))
{
if (subtest_mode_fatal)
- g_error ("Failed to open pipe for test binary: %s: %s", binary, g_strerror (errno));
+ g_error ("Failed to open pipe for test binary: %s: %s", binary, error->message);
else
- g_warning ("Failed to open pipe for test binary: %s: %s", binary, g_strerror (errno));
+ g_warning ("Failed to open pipe for test binary: %s: %s", binary, error->message);
+ g_clear_error (&error);
return FALSE;
}
+ /* setup argc */
+ for (slist = subtest_args; slist; slist = slist->next)
+ argc++;
+ /* argc++; */
+ if (subtest_quiet)
+ argc++;
+ if (subtest_verbose)
+ argc++;
+ if (!subtest_mode_fatal)
+ argc++;
+ if (subtest_mode_quick)
+ argc++;
+ else
+ argc++;
+ if (subtest_mode_perf)
+ argc++;
+ if (!subtest_mode_undefined)
+ argc++;
+ if (gtester_list_tests)
+ argc++;
+ if (subtest_seedstr)
+ argc++;
+ argc++;
+ if (skip_tests)
+ argc++;
+ for (slist = subtest_paths; slist; slist = slist->next)
+ argc++;
+ for (slist = skipped_paths; slist; slist = slist->next)
+ argc++;
+
/* setup argv */
+ argv = g_malloc ((argc + 2) * sizeof(gchar *));
argv[i++] = binary;
for (slist = subtest_args; slist; slist = slist->next)
argv[i++] = (gchar*) slist->data;
- // argv[i++] = "--debug-log";
+ /* argv[i++] = "--debug-log"; */
if (subtest_quiet)
argv[i++] = "--quiet";
if (subtest_verbose)
argv[i++] = "-m=slow";
if (subtest_mode_perf)
argv[i++] = "-m=perf";
+ if (!subtest_mode_undefined)
+ argv[i++] = "-m=no-undefined";
if (gtester_list_tests)
argv[i++] = "-l";
if (subtest_seedstr)
argv[i++] = queue_gfree (&free_list, g_strdup_printf ("--GTestSkipCount=%u", skip_tests));
for (slist = subtest_paths; slist; slist = slist->next)
argv[i++] = queue_gfree (&free_list, g_strdup_printf ("-p=%s", (gchar*) slist->data));
+ for (slist = skipped_paths; slist; slist = slist->next)
+ argv[i++] = queue_gfree (&free_list, g_strdup_printf ("-s=%s", (gchar*) slist->data));
argv[i++] = NULL;
g_spawn_async_with_pipes (NULL, /* g_get_current_dir() */
else
g_warning ("Failed to execute test binary: %s: %s", argv[0], error->message);
g_clear_error (&error);
+ g_free (argv);
return FALSE;
}
+ g_free (argv);
subtest_running = TRUE;
subtest_io_pending = TRUE;
g_io_channel_set_flags (ioc_report, G_IO_FLAG_NONBLOCK, NULL);
g_io_channel_set_encoding (ioc_report, NULL, NULL);
g_io_channel_set_buffered (ioc_report, FALSE);
- g_io_add_watch_full (ioc_report, G_PRIORITY_DEFAULT - 1, G_IO_IN | G_IO_ERR | G_IO_HUP, child_report_cb, tlb, NULL);
+ child_report_cb_id = g_io_add_watch_full (ioc_report, G_PRIORITY_DEFAULT - 1, G_IO_IN | G_IO_ERR | G_IO_HUP, child_report_cb, tlb, NULL);
g_io_channel_unref (ioc_report);
}
g_child_watch_add_full (G_PRIORITY_DEFAULT + 1, pid, child_watch_cb, NULL, NULL);
- while (subtest_running || /* FALSE once child exits */
- subtest_io_pending || /* FALSE once ioc_report closes */
- g_main_context_pending (NULL)) /* TRUE while idler, etc are running */
- g_main_context_iteration (NULL, TRUE);
+ loop_pending = g_main_context_pending (NULL);
+ while (subtest_running || /* FALSE once child exits */
+ subtest_io_pending || /* FALSE once ioc_report closes */
+ loop_pending) /* TRUE while idler, etc are running */
+ {
+ /* g_print ("LOOPSTATE: subtest_running=%d subtest_io_pending=%d\n", subtest_running, subtest_io_pending); */
+ /* check for unexpected hangs that are not signalled on report_pipe */
+ if (!subtest_running && /* child exited */
+ subtest_io_pending && /* no EOF detected on report_pipe */
+ !loop_pending) /* no IO events pending however */
+ break;
+ g_main_context_iteration (NULL, TRUE);
+ loop_pending = g_main_context_pending (NULL);
+ }
+
+ if (subtest_io_pending)
+ g_source_remove (child_report_cb_id);
close (report_pipe[0]);
g_test_log_buffer_free (tlb);
gboolean success = TRUE;
GTimer *btimer = g_timer_new();
gboolean need_restart;
+
testcase_count = 0;
- testcase_fail_count = 0;
if (!gtester_quiet)
g_print ("TEST: %s... ", binary);
success &= subtest_exitstatus == 0;
need_restart = testcase_open != 0;
if (testcase_open)
- testcase_close (0, -999, 0);
+ testcase_close (0, -256, 0);
g_timer_stop (btimer);
test_log_printfe ("%s<duration>%.6f</duration>\n", sindent (log_indent), g_timer_elapsed (btimer, NULL));
log_indent -= 2;
test_log_printfe ("%s</testbinary>\n", sindent (log_indent));
+ g_free (subtest_last_seed);
+ subtest_last_seed = NULL;
if (need_restart)
{
/* restart test binary, skipping processed test cases */
goto retry;
}
+ /* count the inability to run a test as a failure */
+ if (!success && testcase_count == 0)
+ testcase_fail_count++;
+
if (!gtester_quiet)
- g_print ("%s: %s\n", testcase_fail_count || !success ? "FAIL" : "PASS", binary);
+ g_print ("%s: %s\n", !success ? "FAIL" : "PASS", binary);
g_timer_destroy (btimer);
if (subtest_mode_fatal && !success)
terminate();
g_print ("gtester version %d.%d.%d\n", GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, GLIB_MICRO_VERSION);
return;
}
- g_print ("Usage: gtester [OPTIONS] testprogram...\n");
+ g_print ("Usage:\n");
+ g_print ("gtester [OPTIONS] testprogram...\n\n");
/* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 */
- g_print ("Options:\n");
- g_print (" -h, --help show this help message\n");
- g_print (" -v, --version print version informations\n");
- g_print (" --g-fatal-warnings make warnings fatal (abort)\n");
- g_print (" -k, --keep-going continue running after tests failed\n");
- g_print (" -l list paths of available test cases\n");
- g_print (" -m=perf, -m=slow, -m=quick run test cases in mode perf, slow or quick (default)\n");
- g_print (" -p=TESTPATH only start test cases matching TESTPATH\n");
- g_print (" --seed=SEEDSTRING start all tests with random number seed SEEDSTRING\n");
- g_print (" -o=LOGFILE write the test log to LOGFILE\n");
- g_print (" -q, --quiet suppress per test binary output\n");
- g_print (" --verbose report success per testcase\n");
+ g_print ("Help Options:\n");
+ g_print (" -h, --help Show this help message\n\n");
+ g_print ("Utility Options:\n");
+ g_print (" -v, --version Print version informations\n");
+ g_print (" --g-fatal-warnings Make warnings fatal (abort)\n");
+ g_print (" -k, --keep-going Continue running after tests failed\n");
+ g_print (" -l List paths of available test cases\n");
+ g_print (" -m {perf|slow|thorough|quick} Run test cases according to mode\n");
+ g_print (" -m {undefined|no-undefined} Run test cases according to mode\n");
+ g_print (" -p=TESTPATH Only start test cases matching TESTPATH\n");
+ g_print (" -s=TESTPATH Skip test cases matching TESTPATH\n");
+ g_print (" --seed=SEEDSTRING Start tests with random seed SEEDSTRING\n");
+ g_print (" -o=LOGFILE Write the test log to LOGFILE\n");
+ g_print (" -q, --quiet Suppress per test binary output\n");
+ g_print (" --verbose Report success per testcase\n");
}
static void
{
gtester_selftest = TRUE;
argv[i] = NULL;
- break; // stop parsing regular gtester arguments
+ break; /* stop parsing regular gtester arguments */
}
else if (strcmp (argv[i], "-h") == 0 || strcmp (argv[i], "--help") == 0)
{
}
argv[i] = NULL;
}
+ else if (strcmp ("-s", argv[i]) == 0 || strncmp ("-s=", argv[i], 3) == 0)
+ {
+ gchar *equal = argv[i] + 2;
+ if (*equal == '=')
+ skipped_paths = g_slist_prepend (skipped_paths, equal + 1);
+ else if (i + 1 < argc)
+ {
+ argv[i++] = NULL;
+ skipped_paths = g_slist_prepend (skipped_paths, argv[i]);
+ }
+ argv[i] = NULL;
+ }
else if (strcmp ("--test-arg", argv[i]) == 0 || strncmp ("--test-arg=", argv[i], 11) == 0)
{
gchar *equal = argv[i] + 10;
}
if (strcmp (mode, "perf") == 0)
subtest_mode_perf = TRUE;
- else if (strcmp (mode, "slow") == 0)
+ else if (strcmp (mode, "slow") == 0 || strcmp (mode, "thorough") == 0)
subtest_mode_quick = FALSE;
else if (strcmp (mode, "quick") == 0)
{
subtest_mode_quick = TRUE;
subtest_mode_perf = FALSE;
}
+ else if (strcmp (mode, "undefined") == 0)
+ subtest_mode_undefined = TRUE;
+ else if (strcmp (mode, "no-undefined") == 0)
+ subtest_mode_undefined = FALSE;
else
g_error ("unknown test mode: -m %s", mode);
argv[i] = NULL;
{
guint ui;
- /* some unices need SA_RESTART for SIGCHLD to return -EAGAIN for io.
- * we must fiddle with sigaction() *before* glib is used, otherwise
- * we could revoke signal hanmdler setups from glib initialization code.
- */
- if (TRUE)
- {
- struct sigaction sa;
- struct sigaction osa;
- sa.sa_handler = SIG_DFL;
- sigfillset (&sa.sa_mask);
- sa.sa_flags = SA_RESTART;
- sigaction (SIGCHLD, &sa, &osa);
- }
-
g_set_prgname (argv[0]);
parse_args (&argc, &argv);
if (gtester_selftest)
close (log_fd);
- return 0;
+ return testcase_fail_count == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
static void
-fixture_setup (guint *fix)
+fixture_setup (guint *fix,
+ gconstpointer test_data)
{
g_assert_cmphex (*fix, ==, 0);
*fix = 0xdeadbeef;
}
static void
-fixture_test (guint *fix)
+fixture_test (guint *fix,
+ gconstpointer test_data)
{
g_assert_cmphex (*fix, ==, 0xdeadbeef);
g_test_message ("This is a test message API test message.");
g_test_bug ("456");
}
static void
-fixture_teardown (guint *fix)
+fixture_teardown (guint *fix,
+ gconstpointer test_data)
{
g_assert_cmphex (*fix, ==, 0xdeadbeef);
}
main_selftest (int argc,
char **argv)
{
- /* gtester main() for --gtester-selftest invokations */
+ /* gtester main() for --gtester-selftest invocations */
g_test_init (&argc, &argv, NULL);
- g_test_add ("/gtester/fixture-test", guint, fixture_setup, fixture_test, fixture_teardown);
+ g_test_add ("/gtester/fixture-test", guint, NULL, fixture_setup, fixture_test, fixture_teardown);
return g_test_run();
}