diff --git a/.gitignore b/.gitignore index bee8170..159eeef 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ example_no_runner example_random example_shuffle example_trunc +example_cpp *.o diff --git a/CHANGELOG.md b/CHANGELOG.md index b57ec85..47f37ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,50 @@ # greatest Changes By Release +## v1.5.0 - 2021-02-15 + +### API Changes + +Changed default: `GREATEST_USE_LONGJMP` now defaults to 0. This eliminates +a warning about variables that can possibly become stale/corrupt in the +presence of `longjmp`. Since `GREATEST_FAIL_WITH_LONGJMP` isn't frequently +used, it should probably be opt-in. + +Added `greatest_set_exact_name_match()` / `-e` flag, which changes the +name-based filtering from substring to exact match. Note that filtering +on an exact suite name will not skip tests run outside of any suite. + +Added `GREATEST_ASSERT_NEQ` and `GREATEST_ASSERT_NEQm`. (Thanks @tekknolagi.) + +Added `GREATEST_ASSERT_GT`, `GREATEST_ASSERT_GTE`, `GREATEST_ASSERT_LT`, +and `GREATEST_ASSERT_LTE`, along with their custom message (`m`) +variants. + + +### Bug Fixes + +Makefile: Fix targets so all files are rebuilt when `greatest.h` or the +`Makefile` are modified, but without potentially breaking the build due +to including `greatest.h` as a linker argument to `example_trunc` (which +could happen with clang). (Thanks @vemakereporter, @theosotr.) + +Calls to `GREATEST_RUN_TEST` from inside another test are now ignored. + +Other flags starting with `--` besides `--help` (print help) and `--` +(ignore rest of ARGV) now produce an "Unknown argument" message; +previously they were unintentionally handled like `--`. + + +### Other Improvements + +Added built `example_cpp` executable to `.gitignore`. + +Expanded on the role of `RUN_TEST`, `RUN_TEST1`, `RUN_TESTp`, +`PASS`, `SKIP`, and `FAIL` in the README. + +Addressed a `-Wimplicit-fallthrough` warning when building with clang +using `-Weverything`. + + ## v1.4.2 - 2019-03-24 ### API Changes diff --git a/Makefile b/Makefile index 5416740..24c1881 100644 --- a/Makefile +++ b/Makefile @@ -17,8 +17,8 @@ PROGRAMS_CPP= example_cpp # Uncomment to demo c99 parametric testing. #CFLAGS += -std=c99 -# Uncomment to disable setjmp()/longjmp(). -#CFLAGS += -DGREATEST_USE_LONGJMP=0 +# Uncomment to enable setjmp()/longjmp(). +#CFLAGS += -DGREATEST_USE_LONGJMP=1 # Uncomment to disable clock() / time.h. #CFLAGS += -DGREATEST_USE_TIME=0 @@ -32,19 +32,21 @@ example: example.o example_suite.o example_no_suite: example_no_suite.o example_no_runner: example_no_runner.o example_shuffle: example_shuffle.o +example_trunc: example_trunc.o -example_cpp: example_cpp.cpp +*.o: greatest.h Makefile + +example_cpp: example_cpp.cpp greatest.h Makefile ${CXX} -o $@ example_cpp.cpp ${CPPFLAGS} ${LDFLAGS} %.o: %.c ${CC} -c -o $@ ${CFLAGS} $< +%.o: %.cpp + ${CXX} -c -o $@ ${CPPFLAGS} $< + %: %.o ${CC} -o $@ ${LDFLAGS} $^ -*.o: Makefile -*.o: greatest.h - clean: rm -f ${PROGRAMS_C} ${PROGRAMS_CPP} *.o *.core - diff --git a/README.md b/README.md index 7329c0b..f1ba127 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A testing system for C, contained in 1 header file. -## Key Features +## Key Features and Project Goals - **Small, Portable, Lightweight** @@ -114,22 +114,42 @@ Total: 1 test (47 ticks, 0.000 sec), 3 assertions Pass: 1, fail: 0, skip: 0. ``` +Tests are run with `RUN_TEST(test_name)`, which can be called directly +from the test runner's `main` function or grouped into suites (which are +run with `RUN_SUITE(suite_name)`). (Calls to `RUN_TEST` from inside +another test are ignored.) + +Test cases can be run with arguments: `RUN_TEST1(test_name, arg)` passes +a single argument, and if C99 features are supported, then +`RUN_TESTp(test_name, ...)` uses `__VA_ARGS__` to run a test case with +one or mare arguments. `greatest_set_test_suffix` sets a name suffix, so +output from the test runner can include info about arguments. + Test cases should call assertions and then end with `PASS()`, `SKIP()`, `FAIL()`, or their custom message variants (e.g. `SKIPm("TODO");`). If there are any test failures, the test runner will return 1, otherwise it will return 0. (Skips do not cause the test runner to report failure.) +`PASS()`, `SKIP()`, `FAIL()`, and their custom message variants are +macros that updating internal bookkeeping and then returning and enum +value, such as `GREATEST_TEST_RES_FAIL`. They all `return` from the +current test case function. + `PASS()`/`PASSm("msg")` prints as a dot when verbosity is zero, or the test name and custom message (if any) with verbosity >= 1. `FAIL()`/`FAILm("msg")` always prints "FAIL test_name: msg file:line". -`SKIP()`/`SKIPm("msg")` prints as an 's' when verbosity is zero, or -the test name and custom message (if any) with verbosity >= 1. +`SKIP()`/`SKIPm("msg")` prints as an 's' when verbosity is zero, or the +test name and custom message (if any) with verbosity >= 1. Because skips +are not treated as a failure by the test runner, they can be used to +skip test cases that aren't relevant in a particular build or +environment, a way to temporarily disable broken tests, or as a sort of +todo list for tests and functionality under active development. Tests and suites are just functions, so normal C scoping rules apply. -For example, a test or suite named "main" will have a name collision. +For example, a test or suite named `main` will have a name collision. (For more examples, look at `example.c` and `example_suite.c`.) @@ -159,6 +179,11 @@ also contain "slow": The string matching includes optional test name suffixes. +The `greatest_set_exact_name_match()` function and corresponding `-e` +command line runner flag can be used to only run tests and/or suites +whose names exactly match the name filter(s). Note: exact-match suite +filtering by name will not skip tests that are run outside of any suite. + ## Available Assertions @@ -187,6 +212,31 @@ differ, use `ASSERT_EQ_FMT`. To compare with custom equality test and print functions, use `ASSERT_EQUAL_T` instead. +### `ASSERT_NEQ(EXPECTED, ACTUAL)` + +Assert that `EXPECTED != ACTUAL`. + + +### `ASSERT_GT(EXPECTED, ACTUAL)` + +Assert that `EXPECTED > ACTUAL`. + + +### `ASSERT_GTE(EXPECTED, ACTUAL)` + +Assert that `EXPECTED >= ACTUAL`. + + +### `ASSERT_LT(EXPECTED, ACTUAL)` + +Assert that `EXPECTED < ACTUAL`. + + +### `ASSERT_LTE(EXPECTED, ACTUAL)` + +Assert that `EXPECTED <= ACTUAL`. + + ### `ASSERT_EQ_FMT(EXPECTED, ACTUAL, FORMAT)` Assert that `EXPECTED == ACTUAL`. If they are not equal, print their @@ -350,7 +400,7 @@ The function should have a return type of `enum greatest_test_res`. Test runners build with the following command line options: - Usage: (test_runner) [-hlfav] [-s SUITE] [-t TEST] [-x EXCLUDE] + Usage: (test_runner) [-hlfave] [-s SUITE] [-t TEST] [-x EXCLUDE] -h, --help print this Help -l List suites and tests, then exit (dry run) -f Stop runner after first failure @@ -358,6 +408,7 @@ Test runners build with the following command line options: -v Verbose output -s SUITE only run suite w/ name containing substring SUITE -t TEST only run test w/ name containing substring TEST + -e only run exact name match for -s or -t -x EXCLUDE exclude tests containing string substring EXCLUDE Any arguments after `--` will be ignored. @@ -383,9 +434,11 @@ The command line flags above have corresponding functions: - `greatest_stop_at_first_fail()` - `greatest_abort_on_fail()` - `greatest_list_only()` +- `greatest_set_exact_name_match()` - `greatest_set_suite_filter(const char *filter)` - `greatest_set_test_filter(const char *filter)` - `greatest_set_test_exclude(const char *filter)` +- `greatest_get_verbosity()` - `greatest_set_verbosity(unsigned int verbosity)` diff --git a/example.c b/example.c index 13af484..f6f4d2b 100644 --- a/example.c +++ b/example.c @@ -30,6 +30,12 @@ TEST expect_equal(void) { PASS(); } +TEST expect_not_equal(void) { + int i = 9; + ASSERT_NEQ(10, i); + PASS(); +} + TEST expect_str_equal(void) { const char *foo1 = "foo1"; ASSERT_STR_EQ("foo2", foo1); @@ -255,6 +261,60 @@ TEST extra_slow_test(void) { PASS(); } +TEST nested_RUN_TEST(void) { + printf("This nested RUN_TEST call should not trigger an infinite loop...\n"); + RUN_TEST(nested_RUN_TEST); + PASS(); +} + +TEST eq_pass_and_fail(void) { + const int x = 1, y = 2; + ASSERT_EQ(x, x); + ASSERT_EQm("y == y", y, y); + ASSERT_EQ(x, y); + PASS(); +} + +TEST neq_pass_and_fail(void) { + const int x = 1, y = 2; + ASSERT_NEQm("x != y", x, y); + ASSERT_NEQ(x, x); + PASS(); +} + +TEST gt_pass_and_fail(void) { + const int x = 1, y = 2; + ASSERT_GTm("y > x", y, x); + ASSERT_GT(x, x); + PASS(); +} + +TEST gte_pass_and_fail(void) { + const int x = 1, y = 2, z = 3;; + ASSERT_GTE(z, y); + ASSERT_GTE(y, x); + ASSERT_GTE(z, x); + ASSERT_GTEm("y >= y", y, y); + ASSERT_GTE(y, z); + PASS(); +} + +TEST lt_pass_and_fail(void) { + const int x = 1, y = 2; + ASSERT_LTm("x < y", x, y); + ASSERT_LT(x, x); + PASS(); +} + +TEST lte_pass_and_fail(void) { + const int x = 1, y = 2, z = 3;; + ASSERT_LTE(y, z); + ASSERT_LTEm("x <= y", x, y); + ASSERT_LTE(x, x); + ASSERT_LTE(z, x); + PASS(); +} + static void trace_setup(void *arg) { printf("-- in setup callback\n"); teardown_was_called = 0; @@ -279,6 +339,7 @@ SUITE(suite) { printf("\nThis should fail:\n"); RUN_TEST(expect_str_equal); printf("\nThis should pass:\n"); + RUN_TEST(expect_not_equal); RUN_TEST(expect_strn_equal); printf("\nThis should fail:\n"); RUN_TEST(expect_boxed_int_equal); @@ -359,6 +420,15 @@ SUITE(suite) { RUN_TEST(expect_enum_equal_only_evaluates_args_once); RUN_TEST(extra_slow_test); + RUN_TEST(nested_RUN_TEST); + + printf("\nThese next several tests should also fail:\n"); + RUN_TEST(eq_pass_and_fail); + RUN_TEST(neq_pass_and_fail); + RUN_TEST(gt_pass_and_fail); + RUN_TEST(gte_pass_and_fail); + RUN_TEST(lt_pass_and_fail); + RUN_TEST(lte_pass_and_fail); } TEST standalone_test(void) { diff --git a/example_trunc.c b/example_trunc.c index 40de137..a081694 100644 --- a/example_trunc.c +++ b/example_trunc.c @@ -1,9 +1,6 @@ /* Make the buffer size VERY small, to check truncation */ #define GREATEST_TESTNAME_BUF_SIZE 8 -/* Disable longjmp, since it isn't used here. */ -#define GREATEST_USE_LONGJMP 0 - #include "greatest.h" TEST t(void) { diff --git a/greatest.h b/greatest.h index fd4fe24..af0c053 100644 --- a/greatest.h +++ b/greatest.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Scott Vokes + * Copyright (c) 2011-2021 Scott Vokes * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -21,10 +21,10 @@ extern "C" { #endif -/* 1.4.2 */ +/* 1.5.0 */ #define GREATEST_VERSION_MAJOR 1 -#define GREATEST_VERSION_MINOR 4 -#define GREATEST_VERSION_PATCH 2 +#define GREATEST_VERSION_MINOR 5 +#define GREATEST_VERSION_PATCH 0 /* A unit testing system for C, contained in 1 file. * It doesn't use dynamic allocation or depend on anything @@ -119,7 +119,7 @@ int main(int argc, char **argv) { /* Set to 0 to disable all use of setjmp/longjmp. */ #ifndef GREATEST_USE_LONGJMP -#define GREATEST_USE_LONGJMP 1 +#define GREATEST_USE_LONGJMP 0 #endif /* Make it possible to replace fprintf with another @@ -232,7 +232,8 @@ struct greatest_prng { typedef struct greatest_run_info { unsigned char flags; unsigned char verbosity; - unsigned char pad_0[2]; + unsigned char running_test; /* guard for nested RUN_TEST calls */ + unsigned char exact_name_match; unsigned int tests_run; /* total test count */ @@ -321,6 +322,7 @@ int greatest_all_passed(void); void greatest_set_suite_filter(const char *filter); void greatest_set_test_filter(const char *filter); void greatest_set_test_exclude(const char *filter); +void greatest_set_exact_name_match(void); void greatest_stop_at_first_fail(void); void greatest_abort_on_fail(void); void greatest_list_only(void); @@ -434,6 +436,16 @@ typedef enum greatest_test_res { GREATEST_ASSERT_FALSEm(#COND, COND) #define GREATEST_ASSERT_EQ(EXP, GOT) \ GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT) +#define GREATEST_ASSERT_NEQ(EXP, GOT) \ + GREATEST_ASSERT_NEQm(#EXP " == " #GOT, EXP, GOT) +#define GREATEST_ASSERT_GT(EXP, GOT) \ + GREATEST_ASSERT_GTm(#EXP " <= " #GOT, EXP, GOT) +#define GREATEST_ASSERT_GTE(EXP, GOT) \ + GREATEST_ASSERT_GTEm(#EXP " < " #GOT, EXP, GOT) +#define GREATEST_ASSERT_LT(EXP, GOT) \ + GREATEST_ASSERT_LTm(#EXP " >= " #GOT, EXP, GOT) +#define GREATEST_ASSERT_LTE(EXP, GOT) \ + GREATEST_ASSERT_LTEm(#EXP " > " #GOT, EXP, GOT) #define GREATEST_ASSERT_EQ_FMT(EXP, GOT, FMT) \ GREATEST_ASSERT_EQ_FMTm(#EXP " != " #GOT, EXP, GOT, FMT) #define GREATEST_ASSERT_IN_RANGE(EXP, GOT, TOL) \ @@ -473,13 +485,21 @@ typedef enum greatest_test_res { if ((COND)) { GREATEST_FAILm(MSG); } \ } while (0) -/* Fail if EXP != GOT (equality comparison by ==). */ -#define GREATEST_ASSERT_EQm(MSG, EXP, GOT) \ +/* Internal macro for relational assertions */ +#define GREATEST__REL(REL, MSG, EXP, GOT) \ do { \ greatest_info.assertions++; \ - if ((EXP) != (GOT)) { GREATEST_FAILm(MSG); } \ + if (!((EXP) REL (GOT))) { GREATEST_FAILm(MSG); } \ } while (0) +/* Fail if EXP is not ==, !=, >, <, >=, or <= to GOT. */ +#define GREATEST_ASSERT_EQm(MSG,E,G) GREATEST__REL(==, MSG,E,G) +#define GREATEST_ASSERT_NEQm(MSG,E,G) GREATEST__REL(!=, MSG,E,G) +#define GREATEST_ASSERT_GTm(MSG,E,G) GREATEST__REL(>, MSG,E,G) +#define GREATEST_ASSERT_GTEm(MSG,E,G) GREATEST__REL(>=, MSG,E,G) +#define GREATEST_ASSERT_LTm(MSG,E,G) GREATEST__REL(<, MSG,E,G) +#define GREATEST_ASSERT_LTEm(MSG,E,G) GREATEST__REL(<=, MSG,E,G) + /* Fail if EXP != GOT (equality comparison by ==). * Warning: FMT, EXP, and GOT will be evaluated more * than once on failure. */ @@ -689,6 +709,9 @@ static int greatest_name_match(const char *name, const char *filter, \ size_t offset = 0; \ size_t filter_len = filter ? strlen(filter) : 0; \ if (filter_len == 0) { return res_if_none; } /* no filter */ \ + if (greatest_info.exact_name_match && strlen(name) != filter_len) { \ + return 0; /* ignore substring matches */ \ + } \ while (name[offset] != '\0') { \ if (name[offset] == filter[0]) { \ if (0 == strncmp(&name[offset], filter, filter_len)) { \ @@ -734,9 +757,14 @@ int greatest_test_pre(const char *name) { \ goto clear; /* don't run this test yet */ \ } \ } \ + if (g->running_test) { \ + fprintf(stderr, "Error: Test run inside another test.\n"); \ + return 0; \ + } \ GREATEST_SET_TIME(g->suite.pre_test); \ if (g->setup) { g->setup(g->setup_udata); } \ p->count_run++; \ + g->running_test = 1; \ return 1; /* test should be run */ \ } else { \ goto clear; /* skipped */ \ @@ -795,6 +823,7 @@ void greatest_test_post(int res) { \ greatest_info.teardown(udata); \ } \ \ + greatest_info.running_test = 0; \ if (res <= GREATEST_TEST_RES_FAIL) { \ greatest_do_fail(); \ } else if (res >= GREATEST_TEST_RES_SKIP) { \ @@ -877,9 +906,7 @@ static void greatest_run_suite(greatest_suite_cb *suite_cb, \ int greatest_do_assert_equal_t(const void *expd, const void *got, \ greatest_type_info *type_info, void *udata) { \ int eq = 0; \ - if (type_info == NULL || type_info->equal == NULL) { \ - return 0; \ - } \ + if (type_info == NULL || type_info->equal == NULL) { return 0; } \ eq = type_info->equal(expd, got, udata); \ if (!eq) { \ if (type_info->print != NULL) { \ @@ -895,7 +922,7 @@ int greatest_do_assert_equal_t(const void *expd, const void *got, \ \ static void greatest_usage(const char *name) { \ GREATEST_FPRINTF(GREATEST_STDOUT, \ - "Usage: %s [-hlfav] [-s SUITE] [-t TEST] [-x EXCLUDE]\n" \ + "Usage: %s [-hlfavex] [-s SUITE] [-t TEST] [-x EXCLUDE]\n" \ " -h, --help print this Help\n" \ " -l List suites and tests, then exit (dry run)\n" \ " -f Stop runner after first failure\n" \ @@ -903,6 +930,7 @@ static void greatest_usage(const char *name) { \ " -v Verbose output\n" \ " -s SUITE only run suites containing substring SUITE\n" \ " -t TEST only run tests containing substring TEST\n" \ + " -e only run exact name match for -s or -t\n" \ " -x EXCLUDE exclude tests containing substring EXCLUDE\n", \ name); \ } \ @@ -922,6 +950,8 @@ static void greatest_parse_options(int argc, char **argv) { \ greatest_set_test_filter(argv[i + 1]); i++; break; \ case 'x': /* test name exclusion */ \ greatest_set_test_exclude(argv[i + 1]); i++; break; \ + case 'e': /* exact name match */ \ + greatest_set_exact_name_match(); break; \ case 'f': /* first fail flag */ \ greatest_stop_at_first_fail(); break; \ case 'a': /* abort() on fail flag */ \ @@ -932,13 +962,13 @@ static void greatest_parse_options(int argc, char **argv) { \ greatest_info.verbosity++; break; \ case 'h': /* help */ \ greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ + default: \ case '-': \ if (0 == strncmp("--help", argv[i], 6)) { \ greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ - } else if (0 == strncmp("--", argv[i], 2)) { \ + } else if (0 == strcmp("--", argv[i])) { \ return; /* ignore following arguments */ \ - } /* fall through */ \ - default: \ + } \ GREATEST_FPRINTF(GREATEST_STDOUT, \ "Unknown argument '%s'\n", argv[i]); \ greatest_usage(argv[0]); \ @@ -962,6 +992,10 @@ void greatest_set_suite_filter(const char *filter) { \ greatest_info.suite_filter = filter; \ } \ \ +void greatest_set_exact_name_match(void) { \ + greatest_info.exact_name_match = 1; \ +} \ + \ void greatest_stop_at_first_fail(void) { \ greatest_set_flag(GREATEST_FLAG_FIRST_FAIL); \ } \ @@ -1004,8 +1038,7 @@ void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata) { \ greatest_info.setup_udata = udata; \ } \ \ -void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, \ - void *udata) { \ +void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata) { \ greatest_info.teardown = cb; \ greatest_info.teardown_udata = udata; \ } \ @@ -1024,8 +1057,7 @@ static int greatest_string_printf_cb(const void *t, void *udata) { \ } \ \ greatest_type_info greatest_type_info_string = { \ - greatest_string_equal_cb, \ - greatest_string_printf_cb, \ + greatest_string_equal_cb, greatest_string_printf_cb, \ }; \ \ static int greatest_memory_equal_cb(const void *expd, const void *got, \ @@ -1143,8 +1175,7 @@ void GREATEST_PRINT_REPORT(void) { \ } \ \ greatest_type_info greatest_type_info_memory = { \ - greatest_memory_equal_cb, \ - greatest_memory_printf_cb, \ + greatest_memory_equal_cb, greatest_memory_printf_cb, \ }; \ \ greatest_run_info greatest_info @@ -1177,6 +1208,11 @@ greatest_run_info greatest_info #define ASSERTm GREATEST_ASSERTm #define ASSERT_FALSE GREATEST_ASSERT_FALSE #define ASSERT_EQ GREATEST_ASSERT_EQ +#define ASSERT_NEQ GREATEST_ASSERT_NEQ +#define ASSERT_GT GREATEST_ASSERT_GT +#define ASSERT_GTE GREATEST_ASSERT_GTE +#define ASSERT_LT GREATEST_ASSERT_LT +#define ASSERT_LTE GREATEST_ASSERT_LTE #define ASSERT_EQ_FMT GREATEST_ASSERT_EQ_FMT #define ASSERT_IN_RANGE GREATEST_ASSERT_IN_RANGE #define ASSERT_EQUAL_T GREATEST_ASSERT_EQUAL_T @@ -1186,6 +1222,11 @@ greatest_run_info greatest_info #define ASSERT_ENUM_EQ GREATEST_ASSERT_ENUM_EQ #define ASSERT_FALSEm GREATEST_ASSERT_FALSEm #define ASSERT_EQm GREATEST_ASSERT_EQm +#define ASSERT_NEQm GREATEST_ASSERT_NEQm +#define ASSERT_GTm GREATEST_ASSERT_GTm +#define ASSERT_GTEm GREATEST_ASSERT_GTEm +#define ASSERT_LTm GREATEST_ASSERT_LTm +#define ASSERT_LTEm GREATEST_ASSERT_LTEm #define ASSERT_EQ_FMTm GREATEST_ASSERT_EQ_FMTm #define ASSERT_IN_RANGEm GREATEST_ASSERT_IN_RANGEm #define ASSERT_EQUAL_Tm GREATEST_ASSERT_EQUAL_Tm diff --git a/package.json b/package.json index 9bfa61d..944ba3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "greatest", - "version": "v1.4.2", + "version": "v1.5.0", "repo": "silentbicycle/greatest", "src": ["greatest.h"], "description": "A C testing library in 1 file. No dependencies, no dynamic allocation.",