From 4eeb6e4d543d9ebac2a96738e82a4c5655ca9664 Mon Sep 17 00:00:00 2001 From: "Michael D. Lowis" Date: Wed, 19 Dec 2018 10:40:15 -0500 Subject: [PATCH] added some automated unit tests to help with refactoring without brekaing --- Makefile | 9 +++- atf.h | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests.c | 105 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 atf.h create mode 100644 tests.c diff --git a/Makefile b/Makefile index 93ee4a3..f103f6c 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ HFILES=\ regcomp.h\ utf.h\ -all: $(LIB) +all: tests install: $(LIB) mkdir -p $(PREFIX)/share/man/man3 $(PREFIX)/share/man/man7 @@ -37,6 +37,11 @@ test: test.$O $(LIB) test2: test2.$O $(LIB) $(CC) -o test2 test2.$O $(LIB) +tests: tests.$O $(LIB) + $(CC) -o tests tests.$O $(LIB) + ./tests + + $(LIB): $(OFILES) $(AR) $(ARFLAGS) $(LIB) $(OFILES) $(RANLIB) $(LIB) @@ -47,4 +52,4 @@ $(LIB): $(OFILES) $(OFILES): $(HFILES) clean: - rm -f $(OFILES) $(LIB) + rm -f $(LIB) test test2 tests *.o diff --git a/atf.h b/atf.h new file mode 100644 index 0000000..465ac44 --- /dev/null +++ b/atf.h @@ -0,0 +1,128 @@ +/** + @brief A minimalistic unit testing framework for C. + @author Michael D. Lowis + @license BSD 2-clause License +*/ +#ifndef ATF_H +#define ATF_H + +#include +#include + +extern char* Curr_Test; +void atf_init(int argc, char** argv); +void atf_test_start(char* file, unsigned int line, char* name); +bool atf_test_assert(bool success, char* expr_str, char* file, int line); +void atf_test_fail(char* expr, char* file, int line); +int atf_print_results(void); + +#define IGNORE(msg) \ + printf("%s:%d:%s:IGNORE:\n\t%s\n", __FILE__, __LINE__, Curr_Test, msg); break + +#define CHECK(expr) \ + if(atf_test_assert((expr), #expr, __FILE__, __LINE__)) break + +#define TEST_SUITE(name) \ + void name(void) + +#define TEST(desc) \ + for(atf_test_start(__FILE__,__LINE__,#desc); Curr_Test != NULL; Curr_Test = NULL) + +#define RUN_TEST_SUITE(name) \ + name(); + +#define RUN_EXTERN_TEST_SUITE(name) \ + do { extern TEST_SUITE(name); RUN_TEST_SUITE(name); } while(0) + +#define PRINT_TEST_RESULTS \ + atf_print_results + +/* Function Definitions + *****************************************************************************/ +#ifdef INCLUDE_DEFS +#include +#include +#ifndef NO_SIGNALS +#include +#endif + +char* Curr_Test = NULL; +char* Curr_File = NULL; +unsigned int Curr_Line = 0; +static unsigned int Total = 0; +static unsigned int Failed = 0; + +#ifndef NO_SIGNALS +static void handle_signal(int sig) { + /* Determine the signal name */ + char* sig_name = NULL; + switch(sig) { + case SIGABRT: sig_name = "SIGABRT"; break; + case SIGBUS: sig_name = "SIGBUS"; break; + case SIGFPE: sig_name = "SIGFPE"; break; + case SIGILL: sig_name = "SIGILL"; break; + case SIGSEGV: sig_name = "SIGSEGV"; break; + case SIGSYS: sig_name = "SIGSYS"; break; + /* If we don't recognize it then just return and let the default handler + catch it. */ + default: return; + } + /* Error and exit. No summary will be printed but the user will know which + test has crashed. */ + fprintf(stderr,"%s:%d:0:%s:CRASH (signal: %d - %s)\n", Curr_File, Curr_Line, Curr_Test, sig, sig_name); + Failed++; + (void)atf_print_results(); + _Exit(1); +} +#endif + +void atf_init(int argc, char** argv) { + /* I reserve the right to use these later */ + (void)argc; + (void)argv; + +#ifndef NO_SIGNALS + /* Init signal handler */ + signal(SIGABRT, handle_signal); + signal(SIGBUS, handle_signal); + signal(SIGFPE, handle_signal); + signal(SIGILL, handle_signal); + signal(SIGSEGV, handle_signal); + signal(SIGSYS, handle_signal); +#endif +} + +void atf_test_start(char* file, unsigned int line, char* name) { + Curr_File = file; + Curr_Line = line; + Curr_Test = name; + Total++; +} + +bool atf_test_assert(bool success, char* expr, char* file, int line) { + bool failed = !success; + if (failed) atf_test_fail(expr,file,line); + return failed; +} + +void atf_test_fail(char* expr, char* file, int line) { + Failed++; + printf("%s:%d:0:%s:FAIL:( %s )\n", file, line, Curr_Test, expr); \ +} + +int atf_print_results(void) { + static const char* results_string = + "\nUnit Test Summary" + "\n-----------------" + "\nTotal: %d" + "\nPassed: %d" + "\nFailed: %d" + "\n\n"; + printf(results_string, Total, Total - Failed, Failed); + return Failed; +} + +#undef INCLUDE_DEFS +#endif + +#endif /* ATF_H */ diff --git a/tests.c b/tests.c new file mode 100644 index 0000000..7694714 --- /dev/null +++ b/tests.c @@ -0,0 +1,105 @@ +#define INCLUDE_DEFS +#include "atf.h" +#include "regexp9.h" + +int main(int argc, char** argv) { + atf_init(argc,argv); + RUN_EXTERN_TEST_SUITE(MatchTests); + return atf_print_results(); +} + +int match(char* regex, char* str) { + Reprog* regprog = regcomp(regex); + int result = regexec(regprog, str, 0, 0); + free(regprog); + return (result == 1); +} + +TEST_SUITE(MatchTests) { + TEST(/a/ should match 'a') + CHECK(match("a", "a")); + + TEST(/a/ should not match 'b') + CHECK(!match("a", "b")); + + TEST(/a/ should not match 'b') + CHECK(!match("a", "b")); + + TEST(/^a/ should match 'ab') + CHECK(match("^a", "ab")); + + TEST(/^a/ should not match 'ba') + CHECK(!match("^a", "ba")); + + TEST(/a$/ should match 'ba') + CHECK(match("a$", "ba")); + + TEST(/a$/ should not match 'ab') + CHECK(!match("a$", "ab")); + + TEST(/./ should not match '') + CHECK(!match(".", "")); + + TEST(/./ should match 'a') + CHECK(match(".", "a")); + + TEST(/./ should match 'b') + CHECK(match(".", "b")); + + TEST(/a*/ should match '') + CHECK(match("a*", "")); + + TEST(/a*/ should match 'a') + CHECK(match("a*", "a")); + + TEST(/a*/ should match 'aa') + CHECK(match("a*", "aa")); + + TEST(/a+/ should not match '') + CHECK(!match("a+", "")); + + TEST(/a+/ should match 'a') + CHECK(match("a+", "a")); + + TEST(/a+/ should match 'aa') + CHECK(match("a+", "aa")); + + TEST(/a+/ should match 'aaa') + CHECK(match("a+", "aaa")); + + TEST(/a?/ should match '') + CHECK(match("a?", "")); + + TEST(/a?/ should match 'a') + CHECK(match("a?", "a")); + + TEST(/[b-c]/ should not match 'a') + CHECK(!match("[b-c]", "a")); + + TEST(/[b-c]/ should match 'b') + CHECK(match("[b-c]", "b")); + + TEST(/[b-c]/ should match 'c') + CHECK(match("[b-c]", "c")); + + TEST(/[b-c]/ should not match 'd') + CHECK(!match("[b-c]", "d")); + + TEST(/^a(bc)d$/ should match 'abcd') + CHECK(match("^a(bc)d$", "abcd")); + + TEST(/^a(bc)d$/ should not match 'abd') + CHECK(!match("^a(bc)d$", "abd")); + + TEST(/^a(bc)d$/ should not match 'acd') + CHECK(!match("^a(bc)d$", "acd")); + + TEST(/^a|b/ should not match 'c') + CHECK(!match("a|b", "c")); + + TEST(/^a|b/ should match 'a') + CHECK(match("a|b", "a")); + + TEST(/^a|b/ should match 'b') + CHECK(match("a|b", "b")); +} \ No newline at end of file -- 2.55.0