diff options
Diffstat (limited to 'src/common/libtap')
-rw-r--r-- | src/common/libtap/README | 231 | ||||
-rw-r--r-- | src/common/libtap/tap.c | 313 | ||||
-rw-r--r-- | src/common/libtap/tap.h | 101 | ||||
-rw-r--r-- | src/common/libtap/tap_unit.h | 94 |
4 files changed, 739 insertions, 0 deletions
diff --git a/src/common/libtap/README b/src/common/libtap/README new file mode 100644 index 0000000..d57b81d --- /dev/null +++ b/src/common/libtap/README @@ -0,0 +1,231 @@ +NAME +==== + +libtap - Write tests in C + +SYNOPSIS +======== + + #include <tap.h> + + int foo () {return 3;} + char *bar () {return "fnord";} + + int main () { + plan(5); + ok(foo() == 3); + is(bar(), "eek"); + ok(foo() <= 8732, "foo <= %d", 8732); + like(bar(), "f(yes|no)r*[a-f]$", "is like"); + cmp_ok(foo(), ">=", 10, "foo is greater than ten"); + return exit_status(); + } + +results in: + + 1..5 + ok 1 + not ok 2 + # Failed test at synopsis.c line 9. + # got: 'fnord' + # expected: 'eek' + ok 3 - foo <= 8732 + ok 4 - is like + not ok 5 - foo is greater than ten + # Failed test 'foo is greater than ten' + # at synopsis.c line 12. + # 3 + # >= + # 10 + # Looks like you failed 2 tests of 5 run. + +DESCRIPTION +=========== + +tap is an easy to read and easy to write way of creating tests for your +software. This library creates functions that can be used to generate it for +your C programs. It is mostly based on the Test::More Perl module. + +FUNCTIONS +========= + +- plan(tests) +- plan(NO_PLAN) + + Use this to start a series of tests. When you know how many tests there + will be, you can put a number as a number of tests you expect to run. If + you do not know how many tests there will be, you can use plan(NO_PLAN) + or not call this function. When you pass it a number of tests to run, a + message similar to the following will appear in the output: + + 1..5 + +- ok(test) +- ok(test, fmt, ...) + + Specify a test. the test can be any statement returning a true or false + value. You may optionally pass a format string describing the test. + + ok(r = reader_new("Of Mice and Men"), "create a new reader"); + ok(reader_go_to_page(r, 55), "can turn the page"); + ok(r->page == 55, "page turned to the right one"); + + Should print out: + + ok 1 - create a new reader + ok 2 - can turn the page + ok 3 - page turned to the right one + + On failure, a diagnostic message will be printed out. + + not ok 3 - page turned to the right one + # Failed test 'page turned to the right one' + # at reader.c line 13. + +- is(got, expected) +- is(got, expected, fmt, ...) +- isnt(got, expected) +- isnt(got, expected, fmt, ...) + + Tests that the string you got is what you expected. with isnt, it is the + reverse. + + is("this", "that", "this is that"); + + prints: + + not ok 1 - this is that + # Failed test 'this is that' + # at is.c line 6. + # got: 'this' + # expected: 'that' + +- cmp_ok(a, op, b) +- cmp_ok(a, op, b, fmt, ...) + + Compares two ints with any binary operator that doesn't require an lvalue. + This is nice to use since it provides a better error message than an + equivalent ok. + + cmp_ok(420, ">", 666); + + prints: + + not ok 1 + # Failed test at cmpok.c line 5. + # 420 + # > + # 666 + +- like(got, expected) +- like(got, expected, fmt, ...) +- unlike(got, expected) +- unlike(got, expected, fmt, ...) + + Tests that the string you got matches the expected extended POSIX regex. + unlike is the reverse. These macros are the equivalent of a skip on + Windows. + + like("stranger", "^s.(r).*\\1$", "matches the regex"); + + prints: + + ok 1 - matches the regex + +- pass() +- pass(fmt, ...) +- fail() +- fail(fmt, ...) + + Speciy that a test succeeded or failed. Use these when the statement is + longer than you can fit into the argument given to an ok() test. + +- dies_ok(code) +- dies_ok(code, fmt, ...) +- lives_ok(code) +- lives_ok(code, fmt, ...) + + Tests whether the given code causes your program to exit. The code gets + passed to a macro that will test it in a forked process. If the code + succeeds it will be executed in the parent process. You can test things + like passing a function a null pointer and make sure it doesnt + dereference it and crash. + + dies_ok({abort();}, "abort does close your program"); + dies_ok({int x = 0/0;}, "divide by zero crash"); + lives ok({pow(3.0, 5.0)}, "nothing wrong with taking 3**5"); + + On Windows, these macros are the equivalent of a skip. + +- exit_status() + + Summarizes the tests that occurred. If there was no plan, it will print + out the number of tests as. + + 1..5 + + It will also print a diagnostic message about how many + failures there were. + + # Looks like you failed 2 tests of 3 run. + + If all planned tests were successful, it will return 0. If any test fails, + it will return the number of failed tests (including ones that were + missing). If they all passed, but there were missing tests, it will return + 255. + +- note(fmt, ...) +- diag(fmt, ...) + + print out a message to the tap output. note prints to stdout and diag + prints to stderr. Each line is preceeded by a "# " so that you know its a + diagnostic message. + + note("This is\na note\nto describe\nsomething."); + + prints: + + # This is + # a note + # to describe + # something + + ok() and these functions return ints so you can use them like: + + ok(1) && note("yo!"); + ok(0) || diag("I have no idea what just happened"); + +- skip(test, n) +- skip(test, n, fmt, ...) +- endskip + + Skip a series of n tests if test is true. You may give a reason why you are + skipping them or not. The (possibly) skipped tests must occur between the + skip and endskip macros. + + skip(TRUE, 2); + ok(1); + ok(0); + endskip; + + prints: + + ok 1 # skip + ok 2 # skip + +- todo() +- todo(fmt, ...) +- endtodo + + Specifies a series of tests that you expect to fail because they are not + yet implemented. + + todo() + ok(0); + endtodo; + + prints: + + not ok 1 # TODO + # Failed (TODO) test at todo.c line 7 + diff --git a/src/common/libtap/tap.c b/src/common/libtap/tap.c new file mode 100644 index 0000000..61e0528 --- /dev/null +++ b/src/common/libtap/tap.c @@ -0,0 +1,313 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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, see <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> + +//#include "common.h" +#include "tap.h" + +static int expected_tests = NO_PLAN; +static int failed_tests; +static int current_test; +static char *todo_mesg; + +void +plan (int tests) { + expected_tests = tests; + if (tests != NO_PLAN) + printf("1..%d\n", tests); +} + +static char * +vstrdupf (const char *fmt, va_list args) { + char *str; + int size; + va_list args2; + va_copy(args2, args); + if (!fmt) + fmt = ""; + size = vsnprintf(NULL, 0, fmt, args2) + 2; + str = malloc(size); + vsprintf(str, fmt, args); + va_end(args2); + return str; +} + +int +vok_at_loc (const char *file, int line, int test, const char *fmt, + va_list args) +{ + char *name = vstrdupf(fmt, args); + printf("%sok %d", test ? "" : "not ", ++current_test); + if (*name) + printf(" - %s", name); + if (todo_mesg) { + printf(" # TODO"); + if (*todo_mesg) + printf(" %s", todo_mesg); + } + printf("\n"); + if (!test) { + if (*name) + diag(" Failed%s test '%s'\n at %s line %d.", + todo_mesg ? " (TODO)" : "", name, file, line); + else + diag(" Failed%s test at %s line %d.", + todo_mesg ? " (TODO)" : "", file, line); + if (!todo_mesg) + failed_tests++; + } + free(name); + return test; +} + +int +ok_at_loc (const char *file, int line, int test, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + vok_at_loc(file, line, test, fmt, args); + va_end(args); + return test; +} + +static int +mystrcmp (const char *a, const char *b) { + return a == b ? 0 : !a ? -1 : !b ? 1 : strcmp(a, b); +} + +#define eq(a, b) (!mystrcmp(a, b)) +#define ne(a, b) (mystrcmp(a, b)) + +int +is_at_loc (const char *file, int line, const char *got, const char *expected, + const char *fmt, ...) +{ + int test = eq(got, expected); + va_list args; + va_start(args, fmt); + vok_at_loc(file, line, test, fmt, args); + va_end(args); + if (!test) { + diag(" got: '%s'", got); + diag(" expected: '%s'", expected); + } + return test; +} + +int +isnt_at_loc (const char *file, int line, const char *got, const char *expected, + const char *fmt, ...) +{ + int test = ne(got, expected); + va_list args; + va_start(args, fmt); + vok_at_loc(file, line, test, fmt, args); + va_end(args); + if (!test) { + diag(" got: '%s'", got); + diag(" expected: anything else"); + } + return test; +} + +int +cmp_ok_at_loc (const char *file, int line, int a, const char *op, int b, + const char *fmt, ...) +{ + int test = eq(op, "||") ? a || b + : eq(op, "&&") ? a && b + : eq(op, "|") ? a | b + : eq(op, "^") ? a ^ b + : eq(op, "&") ? a & b + : eq(op, "==") ? a == b + : eq(op, "!=") ? a != b + : eq(op, "<") ? a < b + : eq(op, ">") ? a > b + : eq(op, "<=") ? a <= b + : eq(op, ">=") ? a >= b + : eq(op, "<<") ? a << b + : eq(op, ">>") ? a >> b + : eq(op, "+") ? a + b + : eq(op, "-") ? a - b + : eq(op, "*") ? a * b + : eq(op, "/") ? a / b + : eq(op, "%") ? a % b + : diag("unrecognized operator '%s'", op); + va_list args; + va_start(args, fmt); + vok_at_loc(file, line, test, fmt, args); + va_end(args); + if (!test) { + diag(" %d", a); + diag(" %s", op); + diag(" %d", b); + } + return test; +} + +static void +vdiag_to_fh (FILE *fh, const char *fmt, va_list args) { + char *mesg, *line; + int i; + if (!fmt) + return; + mesg = vstrdupf(fmt, args); + line = mesg; + for (i = 0; *line; i++) { + char c = mesg[i]; + if (!c || c == '\n') { + mesg[i] = '\0'; + fprintf(fh, "# %s\n", line); + if (!c) break; + mesg[i] = c; + line = &mesg[i+1]; + } + } + free(mesg); + return; +} + +int +diag (const char *fmt, ...) { + va_list args; + va_start(args, fmt); + vdiag_to_fh(stderr, fmt, args); + va_end(args); + return 0; +} + +int +note (const char *fmt, ...) { + va_list args; + va_start(args, fmt); + vdiag_to_fh(stdout, fmt, args); + va_end(args); + return 0; +} + +int +exit_status () { + int retval = 0; + if (expected_tests == NO_PLAN) { + printf("1..%d\n", current_test); + } + else if (current_test != expected_tests) { + diag("Looks like you planned %d test%s but ran %d.", + expected_tests, expected_tests > 1 ? "s" : "", current_test); + retval = 255; + } + if (failed_tests) { + diag("Looks like you failed %d test%s of %d run.", + failed_tests, failed_tests > 1 ? "s" : "", current_test); + if (expected_tests == NO_PLAN) + retval = failed_tests; + else + retval = expected_tests - current_test + failed_tests; + } + return retval; +} + +void +skippy (int n, const char *fmt, ...) { + char *why; + va_list args; + va_start(args, fmt); + why = vstrdupf(fmt, args); + va_end(args); + while (n --> 0) { + printf("ok %d ", ++current_test); + note("skip %s\n", why); + } + free(why); +} + +void +ctodo (int ignore, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + todo_mesg = vstrdupf(fmt, args); + va_end(args); +} + +void +cendtodo () { + free(todo_mesg); + todo_mesg = NULL; +} + +#ifndef _WIN32 +#include <sys/mman.h> +#include <regex.h> + +#ifndef MAP_ANONYMOUS +#define MAP_ANONYMOUS MAP_ANON +#endif + +/* Create a shared memory int to keep track of whether a piece of code executed +dies. to be used in the dies_ok and lives_ok macros */ +int +tap_test_died (int status) { + static int *test_died = NULL; + int prev; + if (!test_died) { + test_died = mmap(0, sizeof (int), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + *test_died = 0; + } + prev = *test_died; + *test_died = status; + return prev; +} + +int +like_at_loc (int for_match, const char *file, int line, const char *got, + const char *expected, const char *fmt, ...) +{ + int test; + regex_t re; + int err = regcomp(&re, expected, REG_EXTENDED); + if (err) { + char errbuf[256]; + regerror(err, &re, errbuf, sizeof errbuf); + fprintf(stderr, "Unable to compile regex '%s': %s at %s line %d\n", + expected, errbuf, file, line); + exit(255); + } + err = regexec(&re, got, 0, NULL, 0); + regfree(&re); + test = for_match ? !err : err; + va_list args; + va_start(args, fmt); + vok_at_loc(file, line, test, fmt, args); + va_end(args); + if (!test) { + if (for_match) { + diag(" '%s'", got); + diag(" doesn't match: '%s'", expected); + } + else { + diag(" '%s'", got); + diag(" matches: '%s'", expected); + } + } + return test; +} +#endif + diff --git a/src/common/libtap/tap.h b/src/common/libtap/tap.h new file mode 100644 index 0000000..2e89b90 --- /dev/null +++ b/src/common/libtap/tap.h @@ -0,0 +1,101 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __TAP_H__ +#define __TAP_H__ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> + +#define NO_PLAN -1 +#define ok(...) ok_at_loc(__FILE__, __LINE__, __VA_ARGS__, NULL) +#define pass(...) ok(1, ## __VA_ARGS__) +#define fail(...) ok(0, ## __VA_ARGS__) +#define is(...) is_at_loc(__FILE__, __LINE__, __VA_ARGS__, NULL) +#define isnt(...) isnt_at_loc(__FILE__, __LINE__, __VA_ARGS__, NULL) +#define cmp_ok(...) cmp_ok_at_loc(__FILE__, __LINE__, __VA_ARGS__, NULL) + +int vok_at_loc (const char *file, int line, int test, const char *fmt, + va_list args); +void plan (int tests); +int ok_at_loc (const char *file, int line, int test, const char *fmt, + ...); +int diag (const char *fmt, ...); +int note (const char *fmt, ...); +int exit_status (void); +void skippy (int n, const char *fmt, ...); +void ctodo (int ignore, const char *fmt, ...); +void cendtodo (void); +int is_at_loc (const char *file, int line, const char *got, + const char *expected, const char *fmt, ...); +int isnt_at_loc (const char *file, int line, const char *got, + const char *expected, const char *fmt, ...); +int cmp_ok_at_loc (const char *file, int line, int a, const char *op, + int b, const char *fmt, ...); + +#ifdef _WIN32 +#define like(...) skippy(1, "like is not implemented on MSWin32") +#define unlike(...) like() +#else +#define like(...) like_at_loc(1, __FILE__, __LINE__, __VA_ARGS__, NULL) +#define unlike(...) like_at_loc(0, __FILE__, __LINE__, __VA_ARGS__, NULL) +int like_at_loc (int for_match, const char *file, int line, + const char *got, const char *expected, + const char *fmt, ...); +#endif + +#define skip(test, ...) do {if (test) {skippy(__VA_ARGS__, NULL); break;} +#define endskip } while (0) + +#define todo(...) ctodo(0, ## __VA_ARGS__, NULL) +#define endtodo cendtodo() + +#define dies_ok(code, ...) dies_ok_common(code, 1, ## __VA_ARGS__) +#define lives_ok(code, ...) dies_ok_common(code, 0, ## __VA_ARGS__) + +#ifdef _WIN32 +#define dies_ok_common(...) \ + skippy(1, "Death detection is not supported on MSWin32") +#else +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +int tap_test_died (int status); +#define dies_ok_common(code, for_death, ...) \ + do { \ + tap_test_died(1); \ + int cpid = fork(); \ + switch (cpid) { \ + case -1: \ + perror("fork error"); \ + exit(EXIT_FAILURE); \ + case 0: /* child */ \ + close(1); close(2); \ + code \ + tap_test_died(0); \ + exit(EXIT_SUCCESS); \ + } \ + if (waitpid(cpid, NULL, 0) < 0) { \ + perror("waitpid error"); \ + exit(EXIT_FAILURE); \ + } \ + int it_died = tap_test_died(0); \ + if (!it_died) {code} \ + ok(for_death ? it_died : !it_died, ## __VA_ARGS__); \ + } while (0) +#endif +#endif diff --git a/src/common/libtap/tap_unit.h b/src/common/libtap/tap_unit.h new file mode 100644 index 0000000..c248fde --- /dev/null +++ b/src/common/libtap/tap_unit.h @@ -0,0 +1,94 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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, see <http://www.gnu.org/licenses/>. + */ +/*! + * \file tap_unit.h + * \author Marek Vavrusa <marek.vavusa@nic.cz> + * + * \brief libtap test unit. + * + * Contains description of a single test unit API. + * + * Export unit_api in each module header file, + * and set function pointer to according test routines. + * + * <b>Example code for myunit.h</b> + * \code + * #ifndef MYUNIT_TEST_H + * #define MYUNIT_TEST_H + * + * // Export unittest symbol + * unit_api mymodule; + * + * #endif // MYUNIT_TEST_H + * \endcode + * + * <b>Example code for myunit.c</b> + * \code + * #include "myunit.h" + * + * // Function to return unit test count + * int myunit_count(int argc, char *argv[]) { + * return 1; // Number of tests in this unit + * } + * + * // Function to perform tests + * int myunit_run(int argc, char *argv[]) { + * // 1. test + * ok(1 == 1, "test OK"); + * return 0; + * } + * + * // Declare module API + * unit_api mymodule = { + * "My module", + * &myunit_count, + * &myunit_run + * }; + * \endcode + * + * To incorporate test, add it to unit tests main(). + * + * See https://github.com/zorgnax/libtap for libtap API reference. + * + * \addtogroup tests + * @{ + */ + +#ifndef _TAP_UNIT_H_ +#define _TAP_UNIT_H_ + +#include "common/libtap/tap.h" + +/*! \brief Pointer to function for unit_api. */ +typedef int(unitapi_f)(int, char*[]); + + +/*! + * \brief Basic Unit APIs. + * + * Each unit should have one global variable with + * an initialized instance of unit_api. + */ +typedef struct { + const char *name; /*!< Test unit name. */ + unitapi_f *count; /*!< Function to calculate number of tests. */ + unitapi_f *run; /*!< Function to run unit tests. */ +} unit_api; + +#endif // _TAP_UNIT_H_ + +/*! @} */ + |