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
This commit is contained in:
Tim Janik 2007-11-20 15:00:23 +00:00
parent edfef23665
commit cc3bf40d26
4 changed files with 622 additions and 0 deletions

View File

@ -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 <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);
}

View File

@ -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 <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__ */

View 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

96
glib/tests/testing.c Normal file
View File

@ -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);
}