GTest framework started.
authorTim Janik <timj@src.gnome.org>
Tue, 20 Nov 2007 15:00:23 +0000 (15:00 +0000)
committerTim Janik <timj@src.gnome.org>
Tue, 20 Nov 2007 15:00:23 +0000 (15:00 +0000)
* glib/gtestframework.h: testing framework API as proposed on gtk-devel-list.
includes elaborate assertions, performance report functions, test traps,
test timer, test random numbers, teardoiwn garbage collection functions
and general test case / test suite management APIs.

* glib/gtestframework.c: first test framework implementation. already covers
some test suite management APIs and assertion message implementations.

* glib/tests/testing.c: test program for the testing framework.

* glib/tests/Makefile.am: complie testing.c as test. run all tests as part of
make test:.

svn path=/trunk/; revision=5877

glib/gtestframework.c
glib/gtestframework.h
glib/tests/Makefile.am
glib/tests/testing.c [new file with mode: 0644]

index 33e31f8..888a5a1 100644 (file)
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 02111-1307, USA.
  */
+#include "config.h"
+#include "gtestframework.h"
+#include <string.h>
+#include <stdlib.h>
+
+/* --- structures --- */
+struct GTestCase
+{
+  gchar  *name;
+  guint   fixture_size;
+  void (*fixture_setup) (void*);
+  void (*fixture_test) (void*);
+  void (*fixture_teardown) (void*);
+};
+struct GTestSuite
+{
+  gchar  *name;
+  GSList *suites;
+  GSList *cases;
+};
+
+/* --- variables --- */
+static gboolean    test_mode_quick = TRUE;
+static gboolean    test_mode_perf = FALSE;
+static gboolean    test_mode_fatal = TRUE;
+static gboolean    g_test_initialized = FALSE;
+static gboolean    g_test_run_once = TRUE;
+static gboolean    test_run_quiet = FALSE;
+static gboolean    test_run_list = FALSE;
+static gchar      *test_run_output = NULL;
+static gchar      *test_run_seed = NULL;
+static gchar      *test_run_name = "";
+static GSList     *test_paths = NULL;
+static GTestSuite *test_suite_root = NULL;
+static GSList     *test_run_free_queue = NULL;
+
+/* --- functions --- */
+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], "--keep-going") == 0 ||
+               strcmp (argv[i], "-k") == 0)
+        {
+          test_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 == '=')
+            test_paths = g_slist_prepend (test_paths, equal + 1);
+          else if (i + 1 < argc)
+            {
+              argv[i++] = NULL;
+              test_paths = g_slist_prepend (test_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 == '=')
+            test_run_output = equal + 1;
+          else if (i + 1 < argc)
+            {
+              argv[i++] = NULL;
+              test_run_output = 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)
+            test_mode_perf = TRUE;
+          else if (strcmp (mode, "slow") == 0)
+            test_mode_quick = FALSE;
+          else if (strcmp (mode, "quick") == 0)
+            {
+              test_mode_quick = TRUE;
+              test_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)
+        {
+          test_run_quiet = TRUE;
+          argv[i] = NULL;
+        }
+      else if (strcmp ("-l", argv[i]) == 0)
+        {
+          test_run_list = TRUE;
+          argv[i] = NULL;
+        }
+      else if (strcmp ("-seed", argv[i]) == 0 || strncmp ("-seed=", argv[i], 6) == 0)
+        {
+          gchar *equal = argv[i] + 5;
+          if (*equal == '=')
+            test_run_seed = equal + 1;
+          else if (i + 1 < argc)
+            {
+              argv[i++] = NULL;
+              test_run_seed = 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;
+}
+
+void
+g_test_init (int            *argc,
+             char         ***argv,
+             ...)
+{
+  va_list args;
+  gpointer vararg1;
+  g_return_if_fail (argc != NULL);
+  g_return_if_fail (argv != NULL);
+  g_return_if_fail (g_test_initialized == FALSE);
+  g_test_initialized = TRUE;
+
+  va_start (args, argv);
+  vararg1 = va_arg (args, gpointer); /* reserved for future extensions */
+  va_end (args);
+  g_return_if_fail (vararg1 == NULL);
+
+  parse_args (argc, argv);
+
+
+}
+
+GTestSuite*
+g_test_get_root (void)
+{
+  if (!test_suite_root)
+    {
+      test_suite_root = g_test_create_suite ("root");
+      g_free (test_suite_root->name);
+      test_suite_root->name = g_strdup ("");
+    }
+  return test_suite_root;
+}
+
+int
+g_test_run (void)
+{
+  return g_test_run_suite (g_test_get_root());
+}
+
+GTestCase*
+g_test_create_case (const char     *test_name,
+                    gsize           data_size,
+                    void          (*data_setup) (void),
+                    void          (*data_test) (void),
+                    void          (*data_teardown) (void))
+{
+  g_return_val_if_fail (test_name != NULL, NULL);
+  g_return_val_if_fail (strchr (test_name, '/') == NULL, NULL);
+  g_return_val_if_fail (test_name[0] != 0, NULL);
+  g_return_val_if_fail (data_test != NULL, NULL);
+  GTestCase *tc = g_slice_new0 (GTestCase);
+  tc->name = g_strdup (test_name);
+  tc->fixture_size = data_size;
+  tc->fixture_setup = (void*) data_setup;
+  tc->fixture_test = (void*) data_test;
+  tc->fixture_teardown = (void*) data_teardown;
+  return tc;
+}
+
+GTestSuite*
+g_test_create_suite (const char *suite_name)
+{
+  g_return_val_if_fail (suite_name != NULL, NULL);
+  g_return_val_if_fail (strchr (suite_name, '/') == NULL, NULL);
+  g_return_val_if_fail (suite_name[0] != 0, NULL);
+  GTestSuite *ts = g_slice_new0 (GTestSuite);
+  ts->name = g_strdup (suite_name);
+  return ts;
+}
+
+void
+g_test_suite_add (GTestSuite     *suite,
+                  GTestCase      *test_case)
+{
+  g_return_if_fail (suite != NULL);
+  g_return_if_fail (test_case != NULL);
+  suite->cases = g_slist_prepend (suite->cases, test_case);
+}
+
+void
+g_test_suite_add_suite (GTestSuite     *suite,
+                        GTestSuite     *nestedsuite)
+{
+  g_return_if_fail (suite != NULL);
+  g_return_if_fail (nestedsuite != NULL);
+  suite->suites = g_slist_prepend (suite->suites, nestedsuite);
+}
+
+void
+g_test_queue_free (gpointer gfree_pointer)
+{
+  if (gfree_pointer)
+    test_run_free_queue = g_slist_prepend (test_run_free_queue, gfree_pointer);
+}
+
+static int
+test_case_run (GTestCase *tc)
+{
+  gchar *old_name = test_run_name;
+  test_run_name = g_strconcat (old_name, "/", tc->name, NULL);
+  void *fixture = g_malloc0 (tc->fixture_size);
+  if (tc->fixture_setup)
+    tc->fixture_setup (fixture);
+  tc->fixture_test (fixture);
+  if (tc->fixture_teardown)
+    tc->fixture_teardown (fixture);
+  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);
+    }
+  g_free (fixture);
+  g_free (test_run_name);
+  test_run_name = old_name;
+  return 0;
+}
+
+static int
+g_test_run_suite_internal (GTestSuite *suite)
+{
+  guint n_bad = 0, n_good = 0, bad_suite = 0;
+  gchar *old_name = test_run_name;
+  GSList *slist, *reversed;
+  g_return_val_if_fail (suite != NULL, -1);
+  test_run_name = suite->name[0] == 0 ? g_strdup (test_run_name) : g_strconcat (old_name, "/", suite->name, NULL);
+  reversed = g_slist_reverse (g_slist_copy (suite->cases));
+  for (slist = reversed; slist; slist = slist->next)
+    {
+      GTestCase *tc = slist->data;
+      n_good++;
+      n_bad += test_case_run (tc) != 0;
+    }
+  g_slist_free (reversed);
+  reversed = g_slist_reverse (g_slist_copy (suite->suites));
+  for (slist = reversed; slist; slist = slist->next)
+    {
+      GTestSuite *ts = slist->data;
+      bad_suite += g_test_run_suite_internal (ts) != 0;
+    }
+  g_slist_free (reversed);
+  g_free (test_run_name);
+  test_run_name = old_name;
+  return n_bad || bad_suite;
+}
+
+int
+g_test_run_suite (GTestSuite *suite)
+{
+  g_return_val_if_fail (g_test_initialized == TRUE, -1);
+  g_return_val_if_fail (g_test_run_once == TRUE, -1);
+  g_test_run_once = FALSE;
+  return g_test_run_suite_internal (suite);
+}
+
+void
+g_assertion_message (const char     *domain,
+                     const char     *file,
+                     int             line,
+                     const char     *func,
+                     const char     *message)
+{
+  char lstr[32];
+  g_snprintf (lstr, 32, "%d", line);
+  char *s = g_strconcat (domain ? domain : "", domain && domain[0] ? ":" : "",
+                         file, ":", lstr, ":",
+                         func, func[0] ? ":" : "",
+                         " ", message, NULL);
+  g_printerr ("**\n** %s\n", s);
+  g_free (s);
+  abort();
+}
+
+void
+g_assertion_message_expr (const char     *domain,
+                          const char     *file,
+                          int             line,
+                          const char     *func,
+                          const char     *expr)
+{
+  char *s = g_strconcat ("assertion failed: (", expr, ")", NULL);
+  g_assertion_message (domain, file, line, func, s);
+  g_free (s);
+}
+
+void
+g_assertion_message_cmpnum (const char     *domain,
+                            const char     *file,
+                            int             line,
+                            const char     *func,
+                            const char     *expr,
+                            long double     arg1,
+                            const char     *cmp,
+                            long double     arg2,
+                            char            numtype)
+{
+  char *s = NULL;
+  switch (numtype)
+    {
+    case 'i':   s = g_strdup_printf ("assertion failed (%s): (%.0Lf %s %.0Lf)", 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 */
+    }
+  g_assertion_message (domain, file, line, func, s);
+  g_free (s);
+}
+
+void
+g_assertion_message_cmpstr (const char     *domain,
+                            const char     *file,
+                            int             line,
+                            const char     *func,
+                            const char     *expr,
+                            const char     *arg1,
+                            const char     *cmp,
+                            const char     *arg2)
+{
+  char *a1, *a2, *s, *t1 = NULL, *t2 = NULL;
+  a1 = arg1 ? g_strconcat ("\"", t1 = g_strescape (arg1, NULL), "\"", NULL) : g_strdup ("NULL");
+  a2 = arg2 ? g_strconcat ("\"", t2 = g_strescape (arg2, NULL), "\"", NULL) : g_strdup ("NULL");
+  g_free (t1);
+  g_free (t2);
+  s = g_strdup_printf ("assertion failed (%s): (%s %s %s)", expr, a1, cmp, a2);
+  g_free (a1);
+  g_free (a2);
+  g_assertion_message (domain, file, line, func, s);
+  g_free (s);
+}
+
+int
+g_strcmp0 (const char     *str1,
+           const char     *str2)
+{
+  if (!str1)
+    return -(str1 != str2);
+  if (!str2)
+    return str1 != str2;
+  return strcmp (str1, str2);
+}
index 33e31f8..12fb4d3 100644 (file)
  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  * Boston, MA 02111-1307, USA.
  */
+#ifndef __G_TESTFRAMEWORK_H__
+#define __G_TESTFRAMEWORK_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS;
+
+typedef struct GTestCase  GTestCase;
+typedef struct GTestSuite GTestSuite;
+
+/* assertion API */
+#define g_assert_cmpstr(s1, cmp, s2)    do { if (g_strcmp0 (s1, s2) cmp 0) ; else \
+                                          g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+                                            #s1 " " #cmp " " #s2, s1, #cmp, s2); } while (0)
+#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_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)
+int     g_strcmp0                       (const char     *str1,
+                                         const char     *str2);
+//      g_assert(condition)             /*...*/
+//      g_assert_not_reached()          /*...*/
+
+/* report performance results */
+void    g_test_minimized_result         (double          minimized_quantity,
+                                         const char     *format,
+                                         ...) G_GNUC_PRINTF (2, 3);
+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,
+                                         ...);
+/* run all tests under toplevel suite (path: /) */
+int     g_test_run                      (void);
+/* hook up a simple test function under test path */
+void    g_test_add_func                 (const char     *testpath,
+                                         void          (*test_func) (void));
+/* hook up a test with fixture under test path */
+#define g_test_add(testpath, Fixture, fsetup, ftest, fteardown) \
+                                        ((void (*) (const char*,        \
+                                                    gsize,              \
+                                                    void (*) (Fixture*),   \
+                                                    void (*) (Fixture*),   \
+                                                    void (*) (Fixture*)))  \
+                                         (void*) g_test_add_vtable) \
+                                          (testpath, sizeof (Fixture), fsetup, ftest, fteardown)
+/* measure test timings */
+void    g_test_timer_start              (void);
+double  g_test_timer_elapsed            (void); // elapsed seconds
+double  g_test_timer_last               (void); // repeat last elapsed() result
+
+/* automatically g_free or g_object_unref upon teardown */
+void    g_test_queue_free               (gpointer gfree_pointer);
+void    g_test_queue_unref              (gpointer gobjectunref_pointer);
+
+/* provide seed-able random numbers for tests */
+long double     g_test_rand_range       (long double range_start,
+                                         long double range_end);
+
+/* semi-internal API */
+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,
+                                         GTestCase      *test_case);
+void            g_test_suite_add_suite  (GTestSuite     *suite,
+                                         GTestSuite     *nestedsuite);
+int             g_test_run_suite        (GTestSuite     *suite);
+
+/* internal ABI */
+void    g_assertion_message             (const char     *domain,
+                                         const char     *file,
+                                         int             line,
+                                         const char     *func,
+                                         const char     *message);
+void    g_assertion_message_expr        (const char     *domain,
+                                         const char     *file,
+                                         int             line,
+                                         const char     *func,
+                                         const char     *expr);
+void    g_assertion_message_cmpstr      (const char     *domain,
+                                         const char     *file,
+                                         int             line,
+                                         const char     *func,
+                                         const char     *expr,
+                                         const char     *arg1,
+                                         const char     *cmp,
+                                         const char     *arg2);
+void    g_assertion_message_cmpnum      (const char     *domain,
+                                         const char     *file,
+                                         int             line,
+                                         const char     *func,
+                                         const char     *expr,
+                                         long double     arg1,
+                                         const char     *cmp,
+                                         long double     arg2,
+                                         char            numtype);
+void    g_test_add_vtable               (const char     *testpath,
+                                         gsize           data_size,
+                                         void          (*data_setup)    (void),
+                                         void          (*data_test)     (void),
+                                         void          (*data_teardown) (void));
+
+
+G_END_DECLS;
+
+#endif /* __G_TESTFRAMEWORK_H__ */
index e69de29..31b02a0 100644 (file)
@@ -0,0 +1,20 @@
+INCLUDES = -g -I$(top_srcdir) -I$(top_srcdir)/glib $(GLIB_DEBUG_FLAGS)
+
+TESTS  =
+noinst_PROGRAMS = $(TESTS)
+progs_ldadd     = $(top_builddir)/glib/libglib-2.0.la
+
+
+TESTS            += testing
+testing_SOURCES          = testing.c
+testing_LDADD    = $(progs_ldadd)
+
+
+test:
+       @set -e \
+       && for tst in ${TESTS} ; do \
+         echo -n "TEST: ./$$tst...  " ; \
+         ./$$tst ; \
+         echo "OK" ; \
+       done
+.PHONY: test
diff --git a/glib/tests/testing.c b/glib/tests/testing.c
new file mode 100644 (file)
index 0000000..cde815d
--- /dev/null
@@ -0,0 +1,96 @@
+/* GLib testing framework examples and tests
+ * Copyright (C) 2007 Tim Janik
+ *
+ * This work is provided "as is"; redistribution and modification
+ * in whole or in part, in any medium, physical or electronic is
+ * permitted without restriction.
+ *
+ * This work is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * In no event shall the authors or contributors be liable for any
+ * direct, indirect, incidental, special, exemplary, or consequential
+ * damages (including, but not limited to, procurement of substitute
+ * goods or services; loss of use, data, or profits; or business
+ * interruption) however caused and on any theory of liability, whether
+ * in contract, strict liability, or tort (including negligence or
+ * otherwise) arising in any way out of the use of this software, even
+ * if advised of the possibility of such damage.
+ */
+#include <glib/gtestframework.h>
+
+/* test assertion variants */
+static void
+test_assertions (void)
+{
+  g_assert_cmpint (1, >, 0);
+  g_assert_cmpint (2, ==, 2);
+  g_assert_cmpfloat (3.3, !=, 7);
+  g_assert_cmpfloat (7, <=, 3 + 4);
+  g_assert (TRUE);
+  g_assert_cmpstr ("foo", !=, "faa");
+  gchar *fuu = g_strdup_printf ("f%s", "uu");
+  g_test_queue_free (fuu);
+  g_assert_cmpstr ("foo", !=, fuu);
+  g_assert_cmpstr ("fuu", ==, fuu);
+  g_assert_cmpstr (NULL, <, "");
+  g_assert_cmpstr (NULL, ==, NULL);
+  g_assert_cmpstr ("", >, NULL);
+  g_assert_cmpstr ("foo", <, "fzz");
+  g_assert_cmpstr ("fzz", >, "faa");
+  g_assert_cmpstr ("fzz", ==, "fzz");
+}
+
+/* run a test with fixture setup and teardown */
+typedef struct {
+  guint  seed;
+  guint  prime;
+  gchar *msg;
+} Fixturetest;
+
+static void
+fixturetest_setup (Fixturetest *fix)
+{
+  fix->seed = 18;
+  fix->prime = 19;
+  fix->msg = g_strdup_printf ("%d", fix->prime);
+}
+
+static void
+fixturetest_test (Fixturetest *fix)
+{
+  guint prime = g_spaced_primes_closest (fix->seed);
+  g_assert_cmpint (prime, ==, fix->prime);
+  prime = g_ascii_strtoull (fix->msg, NULL, 0);
+  g_assert_cmpint (prime, ==, fix->prime);
+}
+
+static void
+fixturetest_teardown (Fixturetest *fix)
+{
+  g_free (fix->msg);
+}
+
+int
+main (int   argc,
+      char *argv[])
+{
+  GTestCase *tc;
+  g_test_init (&argc, &argv, NULL);
+  GTestSuite *rootsuite = g_test_create_suite ("root");
+  GTestSuite *miscsuite = g_test_create_suite ("misc");
+  g_test_suite_add_suite (rootsuite, miscsuite);
+  GTestSuite *forksuite = g_test_create_suite ("fork");
+  g_test_suite_add_suite (rootsuite, forksuite);
+
+  tc = g_test_create_case ("assertions", 0, NULL, test_assertions, NULL);
+  g_test_suite_add (miscsuite, tc);
+  tc = g_test_create_case ("primetoul", sizeof (Fixturetest),
+                           (void(*)(void)) fixturetest_setup,
+                           (void(*)(void)) fixturetest_test,
+                           (void(*)(void)) fixturetest_teardown);
+  g_test_suite_add (miscsuite, tc);
+
+  return g_test_run_suite (rootsuite);
+}