From cc3bf40d26ad41d2266edbfc5c78ff01d46ad170 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Tue, 20 Nov 2007 15:00:23 +0000 Subject: [PATCH] GTest framework started. * 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 | 383 +++++++++++++++++++++++++++++++++++++++++++++++++ glib/gtestframework.h | 123 ++++++++++++++++ glib/tests/Makefile.am | 20 +++ glib/tests/testing.c | 96 +++++++++++++ 4 files changed, 622 insertions(+) create mode 100644 glib/tests/testing.c diff --git a/glib/gtestframework.c b/glib/gtestframework.c index 33e31f8..888a5a1 100644 --- a/glib/gtestframework.c +++ b/glib/gtestframework.c @@ -16,3 +16,386 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ +#include "config.h" +#include "gtestframework.h" +#include +#include + +/* --- 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); +} diff --git a/glib/gtestframework.h b/glib/gtestframework.h index 33e31f8..12fb4d3 100644 --- a/glib/gtestframework.h +++ b/glib/gtestframework.h @@ -16,3 +16,126 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ +#ifndef __G_TESTFRAMEWORK_H__ +#define __G_TESTFRAMEWORK_H__ + +#include + +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__ */ diff --git a/glib/tests/Makefile.am b/glib/tests/Makefile.am index e69de29..31b02a0 100644 --- a/glib/tests/Makefile.am +++ b/glib/tests/Makefile.am @@ -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 index 0000000..cde815d --- /dev/null +++ b/glib/tests/testing.c @@ -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 + +/* 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); +} -- 2.7.4