gtester.c: adapted to become a rudimentary test binary launcher.
authorTim Janik <timj@src.gnome.org>
Tue, 20 Nov 2007 15:00:41 +0000 (15:00 +0000)
committerTim Janik <timj@src.gnome.org>
Tue, 20 Nov 2007 15:00:41 +0000 (15:00 +0000)
* gtester.c: increased read buffer size to match common unix pipe buffer size.
added argument parsing and usage. changed io handling to capture and replicate
stdout. fixed io handlers to be cleaned up when the child process exits (catch
G_IO_ERR | G_IO_HUP). we now use pending/iteration instead of a main loop
structure, to keep running until the child process exits and all io has been
processed. launch the test binaries given on the command line. don't quit when
a child couldn't be launched but --keep-going was specified.

svn path=/trunk/; revision=5895

glib/gtester.c

index 628036f8fa17271e3e359b3b4bd1df9b15cf2614..778678e8ca87c9ca76705cde9a245e51fbb015ba 100644 (file)
  * Boston, MA 02111-1307, USA.
  */
 #include <glib.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
 
 /* the read buffer size in bytes */
-#define READ_BUFFER_SIZE 1024
+#define READ_BUFFER_SIZE 4096
+
+/* --- prototypes --- */
+static void     parse_args      (gint           *argc_p,
+                                 gchar        ***argv_p);
 
 /* --- variables --- */
-static GIOChannel* out = NULL;
+static GIOChannel  *ioc_report = NULL;
+static gboolean     subtest_running = FALSE;
+static gboolean     subtest_io_pending = FALSE;
+static gboolean     gtester_quiet = FALSE;
+static gboolean     gtester_verbose = FALSE;
+static gboolean     gtester_list_tests = FALSE;
+static gboolean     subtest_mode_fatal = FALSE;
+static gboolean     subtest_mode_perf = FALSE;
+static gboolean     subtest_mode_quick = TRUE;
+static const gchar *subtest_seedstr = NULL;
+static GSList      *subtest_paths = NULL;
+static const gchar *outpu_filename = NULL;
 
 /* --- functions --- */
 static gboolean
-child_out_cb (GIOChannel  *source,
-             GIOCondition condition,
-             gpointer     data)
+child_report_cb (GIOChannel  *source,
+                 GIOCondition condition,
+                 gpointer     data)
 {
-  GError *error = NULL;
-  gsize length = 0;
-  gchar buffer[READ_BUFFER_SIZE];
   GIOStatus status = G_IO_STATUS_NORMAL;
 
   while (status == G_IO_STATUS_NORMAL)
     {
+      gchar buffer[READ_BUFFER_SIZE];
+      gsize length = 0;
+      GError *error = NULL;
       status = g_io_channel_read_chars (source, buffer, sizeof (buffer), &length, &error);
-
       switch (status)
         {
         case G_IO_STATUS_NORMAL:
-          // FIXME: this is where the parsing happens
-          g_print ("read output bytes: %d\n", length);
+          write (2, buffer, length); /* passthrough child's stdout */
           break;
         case G_IO_STATUS_AGAIN:
           /* retry later */
           break;
         case G_IO_STATUS_ERROR:
+          /* ignore, child closed fd or similar g_warning ("Error while reading data: %s", error->message); */
           /* fall through into EOF */
-          g_warning ("Error while reading data: %s",
-                     error->message);
-          g_error_free (error);
         case G_IO_STATUS_EOF:
+          subtest_io_pending = FALSE;
           return FALSE;
         }
+      g_clear_error (&error);
     }
   return TRUE;
 }
@@ -66,57 +82,259 @@ child_watch_cb (GPid     pid,
                gint     status,
                gpointer data)
 {
-  GMainLoop *loop = data;
-
   g_spawn_close_pid (pid);
+  subtest_running = FALSE;
+}
 
-  /* read the remaining data - also stops the io watch from being polled */
-  child_out_cb (out, G_IO_IN, data);
-  g_main_loop_quit (loop);
+static gchar*
+queue_gfree (GSList **slistp,
+             gchar   *string)
+{
+  *slistp = g_slist_prepend (*slistp, string);
+  return string;
 }
 
-int
-main (int    argc,
-      char **argv)
+static void
+launch_test (const char *binary)
 {
-  GMainLoop *loop;
-  GError    *error = NULL;
-  GPid       pid = 0;
-  gchar     *working_folder;
-  gchar     *child_argv[] = {
-    "/bin/ls",
-    NULL
-  };
-  gint        child_out;
-
-  working_folder = g_strdup ("."); // g_get_current_dir ();
-  g_spawn_async_with_pipes (working_folder,
-                            child_argv, NULL /* envp */,
-                            G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
-                            NULL, NULL,
+  GSList *slist, *free_list = NULL;
+  GError *error = NULL;
+  const gchar *argv[20 + g_slist_length (subtest_paths)];
+  GPid pid = 0;
+  gint i = 0, child_report = -1;
+
+  /* setup argv */
+  argv[i++] = binary;
+  // argv[i++] = "--quiet";
+  if (!subtest_mode_fatal)
+    argv[i++] = "--keep-going";
+  if (subtest_mode_quick)
+    argv[i++] = "-m=quick";
+  else
+    argv[i++] = "-m=slow";
+  if (subtest_mode_perf)
+    argv[i++] = "-m=perf";
+  if (subtest_seedstr)
+    argv[i++] = queue_gfree (&free_list, g_strdup_printf ("--seed=%s", subtest_seedstr));
+  for (slist = subtest_paths; slist; slist = slist->next)
+    argv[i++] = queue_gfree (&free_list, g_strdup_printf ("-p=%s", (gchar*) slist->data));
+  argv[i++] = NULL;
+
+  /* child_report will be used to capture logging information from the
+   * child binary. for the moment, we just use it to replicate stdout.
+   */
+
+  g_spawn_async_with_pipes (NULL, /* g_get_current_dir() */
+                            (gchar**) argv,
+                            NULL, /* envp */
+                            G_SPAWN_DO_NOT_REAP_CHILD, /* G_SPAWN_SEARCH_PATH */
+                            NULL, NULL, /* child_setup, user_data */
                             &pid,
-                            NULL,
-                            &child_out,
-                            NULL,
+                            NULL,       /* standard_input */
+                            &child_report, /* standard_output */
+                            NULL,       /* standard_error */
                             &error);
-  g_free (working_folder);
+  g_slist_foreach (free_list, (void(*)(void*,void*)) g_free, NULL);
+  g_slist_free (free_list);
+  free_list = NULL;
 
   if (error)
     {
-      g_error ("Couldn't execute child: %s", error->message);
-      /* doesn't return */
+      if (subtest_mode_fatal)
+        g_error ("Failed to execute test binary: %s: %s", argv[0], error->message);
+      else
+        g_warning ("Failed to execute test binary: %s: %s", argv[0], error->message);
+      g_clear_error (&error);
+      return;
     }
+  subtest_running = TRUE;
+  subtest_io_pending = TRUE;
 
-  loop = g_main_loop_new (NULL, FALSE);
+  if (child_report >= 0)
+    {
+      ioc_report = g_io_channel_unix_new (child_report);
+      g_io_channel_set_flags (ioc_report, G_IO_FLAG_NONBLOCK, NULL);
+      g_io_add_watch_full (ioc_report, G_PRIORITY_DEFAULT - 1, G_IO_IN | G_IO_ERR | G_IO_HUP, child_report_cb, NULL, NULL);
+      g_io_channel_unref (ioc_report);
+    }
+  g_child_watch_add_full (G_PRIORITY_DEFAULT + 1, pid, child_watch_cb, NULL, NULL);
 
-  g_child_watch_add (pid, child_watch_cb, loop);
+  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);
+}
 
-  out = g_io_channel_unix_new (child_out);
-  g_io_channel_set_flags (out, G_IO_FLAG_NONBLOCK, NULL); // FIXME: GError
-  g_io_add_watch (out, G_IO_IN, child_out_cb, loop);
+static void
+usage (gboolean just_version)
+{
+  if (just_version)
+    {
+      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");
+  /*        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 unnecessary output\n");
+  g_print ("  --verbose                   produce additional output\n");
+}
 
-  g_main_loop_run (loop);
-  g_main_loop_unref (loop);
-  return 0;
+static void
+parse_args (gint    *argc_p,
+            gchar ***argv_p)
+{
+  guint argc = *argc_p;
+  gchar **argv = *argv_p;
+  guint i, e;
+  /* parse known args */
+  for (i = 1; i < argc; i++)
+    {
+      if (strcmp (argv[i], "--g-fatal-warnings") == 0)
+        {
+          GLogLevelFlags fatal_mask = (GLogLevelFlags) g_log_set_always_fatal ((GLogLevelFlags) G_LOG_FATAL_MASK);
+          fatal_mask = (GLogLevelFlags) (fatal_mask | G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL);
+          g_log_set_always_fatal (fatal_mask);
+          argv[i] = NULL;
+        }
+      else if (strcmp (argv[i], "-h") == 0 || strcmp (argv[i], "--help") == 0)
+        {
+          usage (FALSE);
+          exit (0);
+          argv[i] = NULL;
+        }
+      else if (strcmp (argv[i], "-v") == 0 || strcmp (argv[i], "--version") == 0)
+        {
+          usage (TRUE);
+          exit (0);
+          argv[i] = NULL;
+        }
+      else if (strcmp (argv[i], "--keep-going") == 0 ||
+               strcmp (argv[i], "-k") == 0)
+        {
+          subtest_mode_fatal = FALSE;
+          argv[i] = NULL;
+        }
+      else if (strcmp ("-p", argv[i]) == 0 || strncmp ("-p=", argv[i], 3) == 0)
+        {
+          gchar *equal = argv[i] + 2;
+          if (*equal == '=')
+            subtest_paths = g_slist_prepend (subtest_paths, equal + 1);
+          else if (i + 1 < argc)
+            {
+              argv[i++] = NULL;
+              subtest_paths = g_slist_prepend (subtest_paths, argv[i]);
+            }
+          argv[i] = NULL;
+        }
+      else if (strcmp ("-o", argv[i]) == 0 || strncmp ("-o=", argv[i], 3) == 0)
+        {
+          gchar *equal = argv[i] + 2;
+          if (*equal == '=')
+            outpu_filename = equal + 1;
+          else if (i + 1 < argc)
+            {
+              argv[i++] = NULL;
+              outpu_filename = argv[i];
+            }
+          argv[i] = NULL;
+        }
+      else if (strcmp ("-m", argv[i]) == 0 || strncmp ("-m=", argv[i], 3) == 0)
+        {
+          gchar *equal = argv[i] + 2;
+          const gchar *mode = "";
+          if (*equal == '=')
+            mode = equal + 1;
+          else if (i + 1 < argc)
+            {
+              argv[i++] = NULL;
+              mode = argv[i];
+            }
+          if (strcmp (mode, "perf") == 0)
+            subtest_mode_perf = TRUE;
+          else if (strcmp (mode, "slow") == 0)
+            subtest_mode_quick = FALSE;
+          else if (strcmp (mode, "quick") == 0)
+            {
+              subtest_mode_quick = TRUE;
+              subtest_mode_perf = FALSE;
+            }
+          else
+            g_error ("unknown test mode: -m %s", mode);
+          argv[i] = NULL;
+        }
+      else if (strcmp ("-q", argv[i]) == 0 || strcmp ("--quiet", argv[i]) == 0)
+        {
+          gtester_quiet = TRUE;
+          gtester_verbose = FALSE;
+          argv[i] = NULL;
+        }
+      else if (strcmp ("--verbose", argv[i]) == 0)
+        {
+          gtester_quiet = FALSE;
+          gtester_verbose = TRUE;
+          argv[i] = NULL;
+        }
+      else if (strcmp ("-l", argv[i]) == 0)
+        {
+          gtester_list_tests = TRUE;
+          argv[i] = NULL;
+        }
+      else if (strcmp ("--seed", argv[i]) == 0 || strncmp ("--seed=", argv[i], 7) == 0)
+        {
+          gchar *equal = argv[i] + 6;
+          if (*equal == '=')
+            subtest_seedstr = equal + 1;
+          else if (i + 1 < argc)
+            {
+              argv[i++] = NULL;
+              subtest_seedstr = argv[i];
+            }
+          argv[i] = NULL;
+        }
+    }
+  /* collapse argv */
+  e = 1;
+  for (i = 1; i < argc; i++)
+    if (argv[i])
+      {
+        argv[e++] = argv[i];
+        if (i >= e)
+          argv[i] = NULL;
+      }
+  *argc_p = e;
 }
 
+int
+main (int    argc,
+      char **argv)
+{
+  guint ui;
+
+  g_set_prgname (argv[0]);
+  parse_args (&argc, &argv);
+
+  if (argc <= 1)
+    {
+      usage (FALSE);
+      return 1;
+    }
+
+  for (ui = 1; ui < argc; ui++)
+    {
+      const char *binary = argv[ui];
+      launch_test (binary);
+    }
+
+  /* we only get here on success or if !subtest_mode_fatal */
+  return 0;
+}