]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Add framework for tests written in C
authorJoel Rosdahl <joel@rosdahl.net>
Thu, 15 Jul 2010 15:16:46 +0000 (17:16 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Thu, 15 Jul 2010 23:32:06 +0000 (01:32 +0200)
.gitignore
Makefile.in
configure.ac
dev.mk.in
test/framework.c [new file with mode: 0644]
test/framework.h [new file with mode: 0644]
test/main.c [new file with mode: 0644]
test/test_util.c [new file with mode: 0644]

index 839fdf94eefab33c77e05d78bd34eb66434643bf..569aef17442de3a8f9b5cb6845273bf9be83dea7 100644 (file)
@@ -17,5 +17,7 @@ config.status
 configure
 dev.mk
 perfdir.*
+test/main
+test/suites.h
 testdir.*
 version.c
index 827628b7a7f249a4916ed65cad4eaa67b4214367..8b7b783a62bdfd14449da4bc3aaddd6d47f10959 100644 (file)
@@ -16,21 +16,25 @@ EXEEXT = @EXEEXT@
 
 libs = @LIBS@ -lm
 
-sources = \
+base_sources = \
     ccache.c mdfour.c hash.c execute.c util.c args.c stats.c version.c \
     cleanup.c snprintf.c unify.c manifest.c hashtable.c hashtable_itr.c \
-    murmurhashneutral2.c hashutil.c getopt_long.c exitfn.c main.c
-all_sources = $(sources) @extra_sources@
+    murmurhashneutral2.c hashutil.c getopt_long.c exitfn.c
+base_objs = $(base_sources:.c=.o)
 
-headers = \
-    ccache.h hashtable.h hashtable_itr.h hashtable_private.h hashutil.h \
-    manifest.h mdfour.h murmurhashneutral2.h getopt_long.h
+ccache_sources = main.c $(base_sources) @extra_sources@
+ccache_objs = $(ccache_sources:.c=.o)
 
-objs = $(all_sources:.c=.o)
+test_suites = test/test_util.c
+test_sources = test/main.c test/framework.c $(test_suites)
+test_objs = $(test_sources:.c=.o)
+
+all_sources = $(ccache_sources) $(test_sources)
+all_objs = $(ccache_objs) $(test_objs)
 
 generated_docs = ccache.1 INSTALL.html manual.html NEWS.html README.html
 
-files_to_clean = $(objs) ccache$(EXEEXT) *~
+files_to_clean = $(all_objs) ccache$(EXEEXT) test/main$(EXEEXT) *~
 
 .PHONY: all
 all: ccache$(EXEEXT)
@@ -38,8 +42,8 @@ all: ccache$(EXEEXT)
 .PHONY: docs
 docs: $(generated_docs)
 
-ccache$(EXEEXT): $(objs)
-       $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(objs) $(libs)
+ccache$(EXEEXT): $(ccache_objs)
+       $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(libs)
 
 .PHONY: install
 install: all
@@ -57,9 +61,18 @@ perf: ccache$(EXEEXT)
        $(srcdir)/perf.py --ccache ccache$(EXEEXT) $(CC) $(CFLAGS) $(CPPFLAGS) $(srcdir)/ccache.c
 
 .PHONY: test
-test: ccache$(EXEEXT)
+test: ccache$(EXEEXT) test/main$(EXEEXT)
+       test/main$(EXEEXT)
        CC='$(CC)' $(srcdir)/test.sh
 
+test/main$(EXEEXT): $(base_objs) $(test_objs)
+       $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(libs)
+
+test/main.o: test/suites.h
+
+test/suites.h: $(test_suites)
+       sed -n 's/TEST_SUITE(\(.*\))/SUITE(\1)/p' $^ >$@
+
 .PHONY: check
 check: test
 
index 08d0dad147a9efae1aa25e46907b57ff052f33cc..9f495adfb490dec664ed6368fe82196d713bd875 100644 (file)
@@ -172,6 +172,7 @@ AC_HEADER_TIME
 AC_HEADER_SYS_WAIT
 
 AC_CHECK_HEADERS(ctype.h pwd.h stdlib.h string.h strings.h sys/time.h)
+AC_CHECK_HEADERS(sys/ioctl.h termios.h)
 
 AC_CHECK_FUNCS(asprintf)
 AC_CHECK_FUNCS(gethostname)
@@ -298,7 +299,7 @@ cat config.h >>config.h.tmp
 echo '#endif' >>config.h.tmp
 mv config.h.tmp config.h
 
-mkdir -p .deps
+mkdir -p .deps test
 
 if test x$use_bundled_zlib = xyes; then
    AC_MSG_WARN(using bundled zlib)
index 37298e6c947b4286ee447ef51489187e84821df6..afe43adfb6c44e6ff782915031cab7fc31577c18 100644 (file)
--- a/dev.mk.in
+++ b/dev.mk.in
@@ -18,12 +18,17 @@ dist_archive_tar_gz = ccache-$(version).tar.gz
 generated_docs = ccache.1 INSTALL.html manual.html NEWS.html README.html
 built_dist_files = $(generated_docs)
 
+headers = \
+    ccache.h hashtable.h hashtable_itr.h hashtable_private.h hashutil.h \
+    manifest.h mdfour.h murmurhashneutral2.h getopt_long.h \
+    test/framework.h test/suites.h
+
 files_to_clean += $(dist_archive_tar_bz2) $(dist_archive_tar_gz) .deps/*
-files_to_clean += $(built_dist_files) version.c
+files_to_clean += $(built_dist_files) version.c test/suites.h
 files_to_clean += *.xml
 
 source_dist_files = \
-    $(sources) $(headers) zlib/*.c zlib/*.h \
+    main.c $(base_sources) $(test_sources) $(headers) zlib/*.c zlib/*.h \
     config.h.in configure install-sh Makefile.in \
     test.sh COPYING INSTALL.txt NEWS.txt README.txt
 dist_files = \
@@ -64,6 +69,7 @@ distcheck: $(dist_archive_tar_bz2)
         mkdir -p $(dist_dir)/build && \
         cd $(dist_dir)/build && \
         ../configure --prefix=$$tmpdir/root && \
+        $(MAKE) test && \
         $(MAKE) install && \
         $(MAKE) installcheck) && \
        rm -rf $$tmpdir
diff --git a/test/framework.c b/test/framework.c
new file mode 100644 (file)
index 0000000..fa7cbd2
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2010 Joel Rosdahl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "framework.h"
+#include <stdio.h>
+#if defined(HAVE_TERMIOS_H) && defined(HAVE_SYS_IOCTL_H)
+#define USE_COLOR
+#include <termios.h>
+#include <sys/ioctl.h>
+#endif
+
+static unsigned passed_asserts;
+static unsigned failed_asserts;
+static unsigned passed_tests;
+static unsigned failed_tests;
+static unsigned passed_suites;
+static unsigned failed_suites;
+static unsigned failed_asserts_before_suite;
+static unsigned failed_asserts_before_test;
+static const char *current_suite;
+static const char *current_test;
+static int verbose;
+
+static const char COLOR_END[] = "\x1b[m";
+static const char COLOR_GREEN[] = "\x1b[32m";
+static const char COLOR_RED[] = "\x1b[31m";
+
+#define COLOR(tty, color) ((tty) ? COLOR_##color : "")
+
+static int
+is_tty(int fd)
+{
+#ifdef USE_COLOR
+       struct termios t;
+       return ioctl(fd, TCGETS, &t) == 0;
+#else
+       (void)fd;
+       return 0;
+#endif
+}
+
+int
+cct_run(suite_fn *suites, int verbose_output)
+{
+       suite_fn *suite;
+       int tty = is_tty(1);
+
+       verbose = verbose_output;
+
+       for (suite = suites; *suite; suite++) {
+               unsigned test_index = 0;
+               do {
+                       test_index = (*suite)(test_index + 1);
+               } while (test_index != 0);
+       }
+
+       if (failed_asserts == 0) {
+               printf("%sPASSED%s: %u assertions, %u tests, %u suites\n",
+                      COLOR(tty, GREEN), COLOR(tty, END),
+                      passed_asserts, passed_tests, passed_suites);
+       } else {
+               printf("%sFAILED%s: %u assertions, %u tests, %u suites\n",
+                      COLOR(tty, RED), COLOR(tty, END),
+                      failed_asserts, failed_tests, failed_suites);
+       }
+       return failed_asserts > 0 ? 1 : 0;
+}
+
+void cct_suite_begin(const char *name)
+{
+       if (verbose) {
+               printf("Running suite %s...\n", name);
+       }
+       current_suite = name;
+       failed_asserts_before_suite = failed_asserts;
+       failed_asserts_before_test = failed_tests; /* For first cct_test_end(). */
+}
+
+void cct_suite_end()
+{
+       if (failed_asserts > failed_asserts_before_suite) {
+               ++failed_suites;
+       } else {
+               ++passed_suites;
+       }
+}
+
+void cct_test_begin(const char *name)
+{
+       if (verbose) {
+               printf("Running test %s...\n", name);
+       }
+       current_test = name;
+       failed_asserts_before_test = failed_asserts;
+}
+
+void cct_test_end()
+{
+       if (failed_asserts > failed_asserts_before_test) {
+               ++failed_tests;
+       } else {
+               ++passed_tests;
+       }
+}
+
+void
+cct_check_passed(void)
+{
+       ++passed_asserts;
+}
+
+void
+cct_check_failed(const char *file, int line, const char *what,
+                 const char *expected, const char *actual)
+{
+       ++failed_asserts;
+       fprintf(stderr, "%s:%u: Failed assertion:\n", file, line);
+       fprintf(stderr, "  Suite:      %s\n", current_suite);
+       fprintf(stderr, "  Test:       %s\n", current_test);
+       if (expected && actual) {
+               fprintf(stderr, "  Expression: %s\n", what);
+               fprintf(stderr, "  Expected:   %s\n", expected);
+               fprintf(stderr, "  Actual:     %s\n", actual);
+       } else {
+               fprintf(stderr, "  Assertion:  %s\n", what);
+       }
+       fprintf(stderr, "\n");
+}
diff --git a/test/framework.h b/test/framework.h
new file mode 100644 (file)
index 0000000..5334d9c
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2010 Joel Rosdahl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef TEST_FRAMEWORK_H
+#define TEST_FRAMEWORK_H
+
+#include <string.h>
+#include <stdlib.h>
+
+/*****************************************************************************/
+
+#define TEST_SUITE(name) \
+       unsigned suite_##name(unsigned _start_point) \
+       { \
+               unsigned _test_counter = 0; \
+               cct_suite_begin(#name); \
+               { \
+                       /* Empty due to macro trickery. */
+
+#define TEST(name) \
+                       cct_test_end(); \
+               } \
+               ++_test_counter; \
+               if (_test_counter >= _start_point) { \
+                       cct_test_begin(#name);
+
+#define TEST_SUITE_END \
+                       cct_test_end(); \
+               } \
+               cct_suite_end(); \
+               return 0; /* We have reached the end. */ \
+       }
+
+/*****************************************************************************/
+
+#define CHECK(assertion) \
+       do { \
+               int ok = (assertion); \
+               if (ok) { \
+                       cct_check_passed(); \
+               } else { \
+                       cct_check_failed(__FILE__, __LINE__, #assertion, NULL, NULL); \
+                       return _test_counter; \
+               } \
+       } while (0)
+
+#define CHECK_UNS_EQ(expected, actual) \
+       do { \
+               unsigned exp = (expected); \
+               unsigned act = (actual); \
+               if (exp == act) { \
+                       cct_check_passed(); \
+               } else { \
+                       char *exp_str, *act_str; \
+                       x_asprintf(&exp_str, "%u", exp); \
+                       x_asprintf(&act_str, "%u", act); \
+                       cct_check_failed(__FILE__, __LINE__, #actual, exp_str, act_str); \
+                       free(exp_str); \
+                       free(act_str); \
+                       return _test_counter; \
+               } \
+       } while (0)
+
+#define CHECK_INT_EQ(expected, actual) \
+       do { \
+               int exp = (expected); \
+               int act = (actual); \
+               if (exp == act) { \
+                       cct_check_passed(); \
+               } else { \
+                       char *exp_str, *act_str; \
+                       x_asprintf(&exp_str, "%u", exp); \
+                       x_asprintf(&act_str, "%u", act); \
+                       cct_check_failed(__FILE__, __LINE__, #actual, exp_str, act_str); \
+                       free(exp_str); \
+                       free(act_str); \
+                       return _test_counter; \
+               } \
+       } while (0)
+
+#define CHECK_STR_EQ_BASE(expected, actual, free1, free2) \
+       do { \
+               char *exp = (expected); \
+               char *act = (actual); \
+               if (exp && act && strcmp(act, exp) == 0) { \
+                       cct_check_passed(); \
+                       if (free1) { \
+                               free(exp); \
+                       } \
+                       if (free2) { \
+                               free(act); \
+                       } \
+               } else { \
+                       char *exp_str, *act_str; \
+                       if (exp) { \
+                               x_asprintf(&exp_str, "\"%s\"", exp); \
+                       } else { \
+                               exp_str = x_strdup("(null)"); \
+                       } \
+                       if (act) { \
+                               x_asprintf(&act_str, "\"%s\"", act); \
+                       } else { \
+                               act_str = x_strdup("(null)"); \
+                       } \
+                       cct_check_failed(__FILE__, __LINE__, #actual, exp_str, act_str); \
+                       free(exp_str); \
+                       free(act_str); \
+                       return _test_counter; \
+               } \
+       } while (0)
+
+#define CHECK_STR_EQ(expected, actual) \
+       CHECK_STR_EQ_BASE(expected, actual, 0, 0)
+
+#define CHECK_STR_EQ_FREE1(expected, actual) \
+       CHECK_STR_EQ_BASE(expected, actual, 1, 0)
+
+#define CHECK_STR_EQ_FREE2(expected, actual) \
+       CHECK_STR_EQ_BASE(expected, actual, 0, 1)
+
+#define CHECK_STR_EQ_FREE12(expected, actual) \
+       CHECK_STR_EQ_BASE(expected, actual, 1, 1)
+
+/*****************************************************************************/
+
+typedef unsigned (*suite_fn)(unsigned);
+int cct_run(suite_fn *suites, int verbose);
+
+void cct_suite_begin(const char *name);
+void cct_suite_end();
+void cct_test_begin(const char *name);
+void cct_test_end();
+void cct_check_passed(void);
+void cct_check_failed(const char *file, int line, const char *assertion,
+                      const char *expected, const char *actual);
+
+#endif
diff --git a/test/main.c b/test/main.c
new file mode 100644 (file)
index 0000000..6f71dbd
--- /dev/null
@@ -0,0 +1,78 @@
+/* Mode: -*-c-*- */
+/*
+ * Copyright (C) 2010 Joel Rosdahl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "framework.h"
+#include "../getopt_long.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+#define SUITE(name) unsigned suite_##name(unsigned);
+#include "suites.h"
+#undef SUITE
+
+const char USAGE_TEXT[] =
+       "Usage:\n"
+       "    test [options]\n"
+       "\n"
+       "Options:\n"
+       "    -h, --help      print this help text\n"
+       "    -v, --verbose   enable verbose logging of tests\n";
+
+extern char *cache_logfile;
+
+int main(int argc, char **argv)
+{
+       static const struct option options[] = {
+               {"help", no_argument, NULL, 'h'},
+               {"verbose", no_argument, NULL, 'v'},
+               {NULL, 0, NULL, 0}
+       };
+       int verbose = 0;
+       int c;
+
+       while ((c = getopt_long(argc, argv, "hv", options, NULL)) != -1) {
+               switch (c) {
+               case 'h':
+                       fprintf(stdout, USAGE_TEXT);
+                       return 0;
+
+               case 'v':
+                       verbose = 1;
+                       break;
+
+               default:
+                       fprintf(stderr, USAGE_TEXT);
+                       return 1;
+               }
+       }
+
+       suite_fn suites[] = {
+#define SUITE(name) &suite_##name,
+#include "suites.h"
+#undef SUITE
+               NULL
+       };
+
+       if (getenv("RUN_FROM_BUILD_FARM")) {
+               verbose = 1;
+       }
+       cache_logfile = getenv("CCACHE_LOGFILE");
+
+       return cct_run(suites, verbose);
+}
diff --git a/test/test_util.c b/test/test_util.c
new file mode 100644 (file)
index 0000000..ad5e77b
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2010 Joel Rosdahl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * This file contains tests for functions in util.c.
+ */
+
+#include "../ccache.h"
+#include "framework.h"
+
+TEST_SUITE(util)
+
+TEST(basename)
+{
+       CHECK_STR_EQ_FREE2("foo.c", basename("foo.c"));
+       CHECK_STR_EQ_FREE2("foo.c", basename("dir1/dir2/foo.c"));
+       CHECK_STR_EQ_FREE2("foo.c", basename("/dir/foo.c"));
+       CHECK_STR_EQ_FREE2("", basename("dir1/dir2/"));
+}
+
+TEST_SUITE_END