From bbdf06a8d4e85abccb361a187fa63711366cf664 Mon Sep 17 00:00:00 2001 From: Joel Rosdahl Date: Thu, 15 Jul 2010 17:16:46 +0200 Subject: [PATCH] Add framework for tests written in C --- .gitignore | 2 + Makefile.in | 35 +++++++---- configure.ac | 3 +- dev.mk.in | 10 +++- test/framework.c | 144 ++++++++++++++++++++++++++++++++++++++++++++ test/framework.h | 152 +++++++++++++++++++++++++++++++++++++++++++++++ test/main.c | 78 ++++++++++++++++++++++++ test/test_util.c | 36 +++++++++++ 8 files changed, 446 insertions(+), 14 deletions(-) create mode 100644 test/framework.c create mode 100644 test/framework.h create mode 100644 test/main.c create mode 100644 test/test_util.c diff --git a/.gitignore b/.gitignore index 839fdf94e..569aef174 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,7 @@ config.status configure dev.mk perfdir.* +test/main +test/suites.h testdir.* version.c diff --git a/Makefile.in b/Makefile.in index 827628b7a..8b7b783a6 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 diff --git a/configure.ac b/configure.ac index 08d0dad14..9f495adfb 100644 --- a/configure.ac +++ b/configure.ac @@ -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) diff --git a/dev.mk.in b/dev.mk.in index 37298e6c9..afe43adfb 100644 --- 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 index 000000000..fa7cbd2a8 --- /dev/null +++ b/test/framework.c @@ -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 +#if defined(HAVE_TERMIOS_H) && defined(HAVE_SYS_IOCTL_H) +#define USE_COLOR +#include +#include +#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 index 000000000..5334d9c8b --- /dev/null +++ b/test/framework.h @@ -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 +#include + +/*****************************************************************************/ + +#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 index 000000000..6f71dbd77 --- /dev/null +++ b/test/main.c @@ -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 +#include + +#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 index 000000000..ad5e77b99 --- /dev/null +++ b/test/test_util.c @@ -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 -- 2.47.3