From 83e4d39af1fddade2cd8fb917ccda0c92e5344ff Mon Sep 17 00:00:00 2001 From: "Michael D. Lowis" Date: Thu, 28 Feb 2019 22:46:31 -0500 Subject: [PATCH] fleshed out qcheck more and added property tests for buf.c --- config.mk | 4 +- inc/atf.h | 42 +++++++-- inc/qcheck.h | 234 ++++++++++++++++++++++++++++++++++++++++++++++ tests/lib/buf.c | 38 ++++++-- tests/libedit.c | 6 +- tests/test_tide.c | 168 ++------------------------------- 6 files changed, 316 insertions(+), 176 deletions(-) create mode 100644 inc/qcheck.h diff --git a/config.mk b/config.mk index d7a9e03..0611cdd 100644 --- a/config.mk +++ b/config.mk @@ -41,5 +41,5 @@ ARFLAGS = rcs #LDFLAGS += -pg # GCC/Clang Coverage -#CFLAGS += -g -O0 --coverage -#LDFLAGS += -g -O0 --coverage +CFLAGS += -g -O0 --coverage +LDFLAGS += -g -O0 --coverage diff --git a/inc/atf.h b/inc/atf.h index 465ac44..ca7b30a 100644 --- a/inc/atf.h +++ b/inc/atf.h @@ -1,13 +1,29 @@ /** - @brief A minimalistic unit testing framework for C. - @author Michael D. Lowis - @license BSD 2-clause License + Aardvark Test Framework - A minimalistic unit testing framework for C. + + Copyright 2014 Michael D. Lowis + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA + OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. */ #ifndef ATF_H #define ATF_H #include #include +#include +#include extern char* Curr_Test; void atf_init(int argc, char** argv); @@ -22,6 +38,9 @@ int atf_print_results(void); #define CHECK(expr) \ if(atf_test_assert((expr), #expr, __FILE__, __LINE__)) break +#define CHECK_EXITCODE(code) \ + CHECK(ExitCode == code) + #define TEST_SUITE(name) \ void name(void) @@ -34,8 +53,8 @@ int atf_print_results(void); #define RUN_EXTERN_TEST_SUITE(name) \ do { extern TEST_SUITE(name); RUN_TEST_SUITE(name); } while(0) -#define PRINT_TEST_RESULTS \ - atf_print_results +#define EXPECT_EXIT \ + if ((ExitExpected = true, 0 == setjmp(ExitPad))) /* Function Definitions *****************************************************************************/ @@ -51,6 +70,9 @@ char* Curr_File = NULL; unsigned int Curr_Line = 0; static unsigned int Total = 0; static unsigned int Failed = 0; +bool ExitExpected; +int ExitCode; +jmp_buf ExitPad; #ifndef NO_SIGNALS static void handle_signal(int sig) { @@ -122,7 +144,15 @@ int atf_print_results(void) { return Failed; } -#undef INCLUDE_DEFS +void exit(int code) { + if (ExitExpected) { + ExitCode = code; + ExitExpected = false; + longjmp(ExitPad, 1); + } else { + assert(!"Unexpected exit. Something went wrong"); + } +} #endif #endif /* ATF_H */ diff --git a/inc/qcheck.h b/inc/qcheck.h new file mode 100644 index 0000000..7aec0ce --- /dev/null +++ b/inc/qcheck.h @@ -0,0 +1,234 @@ +/** + QCheck - A minimalistic property based testing framework. + + Copyright 2019 Michael D. Lowis + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA + OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. +*/ +#ifndef QCHECK_H +#define QCHECK_H + +#include +#include +#include + +typedef struct QCValue { + void (*showfn)(struct QCValue* val); + void (*freefn)(struct QCValue* val); + long ndata; + long data[]; +} QCValue; + +typedef struct { + int status; + int nvals; + QCValue** vals; +} QCResult; + +typedef int (*QCProp)(int nvals, QCValue** vals); + +typedef QCValue* (*QCGenFn)(void); + +#define VAL(v, type) ((type)((v)->data[0])) + +void qcinit(int seed); +void qcntrials(int ntrials); +QCValue* qcalloc(size_t sz, void* data); +long qcrandr(long from, long to); +long qcrand(void); +void qcfree(int nvals, QCValue** vals); +void qcshow(int nvals, QCValue** vals); +QCResult vqcheck(QCProp prop, int nvals, va_list vals); +int qcheck(char* desc, QCProp prop, int nvals, ...); + +void ShowLong(QCValue* val); +void ShowBool(QCValue* val); +void ShowChar(QCValue* val); + +QCValue* MkLong(long val, void (*showfn)(QCValue* val)); +QCValue* GenLongR(long from, long to); +QCValue* GenLong(void); +QCValue* GenU8(void); +QCValue* GenU16(void); +QCValue* GenU32(void); +QCValue* GenBool(void); +QCValue* GenByte(void); +QCValue* GenAsciiChar(void); +QCValue* GenAsciiString(void); + +/* Function Definitions + *****************************************************************************/ +#ifdef INCLUDE_DEFS +#include +#include +#include + +static int Seed = 0, NTrials = 1000; + +void qcinit(int seed) { + Seed = (seed ? seed : time(NULL)); + srand(Seed); +} + +void qcntrials(int ntrials) { + NTrials = ntrials; +} + +QCValue* qcalloc(size_t sz, void* data) { + QCValue* value = calloc(1, sizeof(QCValue) + sz); + if (data) memcpy(value->data, data, sz); + value->ndata = 1; + return value; +} + +long qcrandr(long from, long to) { + return ((rand() % (to - from + 1)) + from); +} + +long qcrand(void) { + return qcrandr(0, RAND_MAX); +} + +void qcfree(int nvals, QCValue** vals) { + for (int i = 0; i < nvals; i++) { + if (vals[i]->freefn) vals[i]->freefn(vals[i]); + free(vals[i]); + } + free(vals); +} + +void qcshow(int nvals, QCValue** vals) { + for (int i = 0; i < nvals; i++) { + printf("Argument %d: ", i); + vals[i]->showfn(vals[i]); + } +} + +QCResult vqcheck(QCProp prop, int nvals, va_list vals) { + /* generate the input values */ + QCValue** values = NULL; + if (nvals) { + values = malloc(sizeof(QCValue*) * nvals); + for (int i = 0; i < nvals; i++) + values[i] = (va_arg(vals, QCGenFn))(); + } + /* run the test and get the result */ + QCResult result = { .status = 0 }; + result.status = prop(nvals, values); + result.nvals = nvals; + result.vals = values; + return result; +} + +int qcheck(char* desc, QCProp prop, int nvals, ...) { + int passed = 0; + QCResult result; + va_list vals; + for (int i = 0; i < NTrials; i++) { + va_start(vals, nvals); + result = vqcheck(prop, nvals, vals); + va_end(vals); + if (!result.status) break; + qcfree(result.nvals, result.vals); + passed++; + } + /* show 'em the results */ + if (passed == NTrials) { + printf("%d tests passed for property: %s\n", passed, desc); + } else if (!result.status) { + printf("Property: %s\nFalsifiable after %d tests (seed: %d)\n", desc, passed+1, Seed); + qcshow(result.nvals, result.vals); + qcfree(result.nvals, result.vals); + return 0; + } + return 1; +} + +void ShowLong(QCValue* val) { + printf("%ld\n", (val->data[0])); +} + +void ShowBool(QCValue* val) { + printf("%s\n", (val->data[0] ? "true" : "false")); +} + +void ShowChar(QCValue* val) { + printf("'%c'\n", (char)(val->data[0])); +} + +void ShowByte(QCValue* val) { + printf("0x%02x\n", (char)(val->data[0])); +} + +void ShowString(QCValue* val) { + printf("'%s'\n", (char*)(val->data)); +} + +QCValue* MkLong(long val, void (*showfn)(QCValue* val)) { + QCValue* value = qcalloc(sizeof(long), &val); + value->showfn = showfn; + return value; +} + +QCValue* MkArray(size_t nelems, size_t elemsz, void (*showfn)(QCValue* val)) { + QCValue* value = qcalloc(nelems * elemsz, NULL); + value->showfn = showfn; + return value; +} + +QCValue* GenLongR(long from, long to) { + return MkLong(qcrandr(from, to), ShowLong); +} + +QCValue* GenLong(void) { + return MkLong(qcrand(), ShowLong); +} + +QCValue* GenU8(void) { + return GenLongR(0, UINT8_MAX); +} + +QCValue* GenU16(void) { + return GenLongR(0, UINT16_MAX); +} + +QCValue* GenU32(void) { + return GenLongR(0, UINT32_MAX); +} + +QCValue* GenBool(void) { + return MkLong(qcrandr(0, 1), ShowBool); +} + +QCValue* GenByte(void) { + return MkLong(qcrandr(0, 255), ShowByte); +} + +QCValue* GenAsciiChar(void) { + return MkLong(qcrandr(32, 127), ShowChar); +} + +QCValue* GenAsciiString(void) { + size_t nelem = qcrandr(1, 1024); + QCValue* value = MkArray(nelem+1, 1, ShowString); + char* string = (char*)value->data; + for (size_t i = 0; i < nelem; i++) + *(string++) = qcrandr(32,127); + return value; +} + +#endif + +#endif /* QCHECK_H */ diff --git a/tests/lib/buf.c b/tests/lib/buf.c index 13481e5..6be91e5 100644 --- a/tests/lib/buf.c +++ b/tests/lib/buf.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -11,17 +12,38 @@ static void set_buffer_text(char* str) { buf_puts(&TestBuf, str); } -/* -static bool buf_text_eq(char* str) { - buf_selall(&TestBuf); - char* bstr = buf_gets(&TestBuf); - int ret = strcmp(str, bstr); - free(bstr); - return (ret == 0); +#define QCHECK(desc, prop, ...) \ + CHECK(qcheck(desc, prop, __VA_ARGS__)) + +int getc_returns_putc(int nvals, QCValue** vals) { + (void)nvals; + Buf buf = {0}; + buf_init(&buf); + buf_putc(&buf, vals[0]->data[0]); + buf.selection.end = buf.selection.beg = 0; + return (vals[0]->data[0] == buf_getc(&buf)); +} + +int gets_returns_puts(int nvals, QCValue** vals) { + (void)nvals; + Buf buf = {0}; + buf_init(&buf); + char* input = (char*)(vals[0]->data); + buf_puts(&buf, input); + buf.selection.beg = 0; + buf.selection.end = buf_end(&buf); + char* output = buf_gets(&buf); + return (!strcmp(input,output)); } -*/ TEST_SUITE(BufferTests) { + TEST(buf should adhere to specific properties) { + QCHECK("getc should return the same printable ascii value inserted with putc", + getc_returns_putc, 1, GenAsciiChar); + QCHECK("gets should return the same printable ascii string inserted with puts", + gets_returns_puts, 1, GenAsciiString); + } + /* Initializing *************************************************************************/ TEST(buf_init should initialize an empty buffer) { diff --git a/tests/libedit.c b/tests/libedit.c index 9f71026..ffb783d 100644 --- a/tests/libedit.c +++ b/tests/libedit.c @@ -1,13 +1,15 @@ #include #include #include + #define INCLUDE_DEFS #include -#define INCLUDE_DEFS +#include #include "config.h" int main(int argc, char** argv) { - atf_init(argc,argv); + qcinit(argc >= 2 ? strtol(argv[1], 0, 0) : 0); + atf_init(argc, argv); RUN_EXTERN_TEST_SUITE(BufferTests); RUN_EXTERN_TEST_SUITE(Utf8Tests); return atf_print_results(); diff --git a/tests/test_tide.c b/tests/test_tide.c index e3663ed..513d301 100644 --- a/tests/test_tide.c +++ b/tests/test_tide.c @@ -1,154 +1,8 @@ -#include - #define TEST #include "src/tide.c" -typedef struct QCValue QCValue; -struct QCValue { - void (*showfn)(QCValue* val); - void (*freefn)(QCValue* val); - long ndata; - long data[]; -}; - -typedef struct { - int status; - int nvals; - QCValue** vals; -} QCResult; - -typedef int (*QCProp)(int nvals, QCValue** vals); - -typedef QCValue* (*QCGenFn)(void); - -int Seed = 0, NTrials = 10000; - -void qcinit(int seed) { - Seed = (seed ? seed : time(NULL)); - srandom(Seed); -} - -void qcntrials(int ntrials) { - NTrials = ntrials; -} - -QCValue* qcalloc(size_t sz, void* data) { - QCValue* value = calloc(1, sizeof(QCValue) + sz); - memcpy(value->data, data, sz); - return value; -} - -long qcrandr(long from, long to) { - return ((random() % (to - from + 1)) + from); -} - -long qcrand(void) { - return qcrandr(0, RAND_MAX); -} - -void qcfree(int nvals, QCValue** vals) { - for (int i = 0; i < nvals; i++) { - if (vals[i]->freefn) vals[i]->freefn(vals[i]); - free(vals[i]); - } - free(vals); -} - -void qcshow(int nvals, QCValue** vals) { - for (int i = 0; i < nvals; i++) { - printf("Argument %d: ", i); - vals[i]->showfn(vals[i]); - } -} - -QCResult vqcheck(QCProp prop, int nvals, va_list vals) { - /* generate the input values */ - QCValue** values = NULL; - if (nvals) { - values = malloc(sizeof(QCValue*) * nvals); - for (int i = 0; i < nvals; i++) - values[i] = (va_arg(vals, QCGenFn))(); - } - /* run the test and get the result */ - QCResult result = { .status = 0 }; - result.status = prop(nvals, values); - result.nvals = nvals; - result.vals = values; - return result; -} - -int qcheck(char* desc, QCProp prop, int nvals, ...) { - int passed = 0; - QCResult result; - va_list vals; - for (int i = 0; i < NTrials; i++) { - va_start(vals, nvals); - result = vqcheck(prop, nvals, vals); - va_end(vals); - if (!result.status) break; - qcfree(result.nvals, result.vals); - passed++; - } - /* show 'em the results */ - if (passed == NTrials) { - printf("%d tests passed for property: %s\n", passed, desc); - } else if (!result.status) { - printf("Property: %s\nFalsifiable after %d tests (seed: %d)\n", desc, passed+1, Seed); - qcshow(result.nvals, result.vals); - /* should investigate shrinking input here as well */ - qcfree(result.nvals, result.vals); - return 0; - } - return 1; -} - -/************************************************/ - -void ShowLong(QCValue* val) { - printf("%ld\n", (val->data[0])); -} - -void ShowBool(QCValue* val) { - printf("%s\n", (val->data[0] ? "true" : "false")); -} - -void ShowChar(QCValue* val) { - printf("'%c'\n", (char)(val->data[0])); -} - -QCValue* MkLong(long val, void (*showfn)(QCValue* val)) { - QCValue* value = qcalloc(sizeof(long), &val); - value->showfn = showfn; - return value; -} - -QCValue* GenLongR(long from, long to) { - return MkLong(qcrandr(from, to), ShowLong); -} - -QCValue* GenLong(void) { - return MkLong(qcrand(), ShowLong); -} - -QCValue* GenU8(void) { - return GenLongR(0, UINT8_MAX); -} - -QCValue* GenU16(void) { - return GenLongR(0, UINT16_MAX); -} - -QCValue* GenU32(void) { - return GenLongR(0, UINT32_MAX); -} - -QCValue* GenBool(void) { - return MkLong(qcrandr(0, 1), ShowBool); -} - -QCValue* GenChar(void) { - return MkLong(qcrandr(0, 127), ShowChar); -} +#define INCLUDE_DEFS +#include "qcheck.h" /************************************************/ @@ -176,12 +30,12 @@ int resizing_should_not_crash(int nvals, QCValue** vals) { // int pid = fork(); // /* test the resize event */ // if (pid == 0) { - XEvent e = {0}; - e.xconfigure.width = vals[0]->data[0]; - e.xconfigure.height = vals[1]->data[0]; - tide_init(); - (X.eventfns[ConfigureNotify])(&X, &e); - xupdate(NULL); +// XEvent e = {0}; +// e.xconfigure.width = vals[0]->data[0]; +// e.xconfigure.height = vals[1]->data[0]; +// tide_init(); +// (X.eventfns[ConfigureNotify])(&X, &e); +// xupdate(NULL); // } // switch (fork()) { // case 0: @@ -195,12 +49,10 @@ int resizing_should_not_crash(int nvals, QCValue** vals) { } int main(int argc, char** argv) { - (void)argc, (void)argv, (void)usage; + (void)usage; - qcinit(0); + qcinit(argc >= 2 ? strtol(argv[1], 0, 0) : 0); qcheck("all numbers are divisible by 2", divisible_by_two, 1, GenLong); - qcheck("resizing should not crash the app", - resizing_should_not_crash, 2, GenU16, GenU16); return 0; } -- 2.49.0