blob: e291865e86db816085cb45cec2992f1cfa83a91a [file] [log] [blame]
/* libnih
*
* Copyright © 2007 Scott James Remnant <scott@netsplit.com>.
*
* 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 2 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 St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef NIH_TEST_H
#define NIH_TEST_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <assert.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <nih/macros.h>
#include <nih/alloc.h>
#include <nih/list.h>
/**
* assert0:
* @_expr: expression to check.
*
* Wrapper around the usual assert() function that handles the common case
* of asserting that @_expr returns zero, rather than a TRUE value.
**/
#define assert0(_expr) \
assert ((_expr) == 0)
/**
* TEST_FUNCTION:
* @_func: name of function being tested.
*
* Output a message indicating that tests of the function named @_func are
* being performed.
**/
#define TEST_FUNCTION(_func) \
printf ("Testing %s()\n", _func)
/**
* TEST_FEATURE:
* @_feat: name of function feature being tested.
*
* Output a message indicating that a sub-test of a function is being
* performed, specifically the feature named _feat.
**/
#define TEST_FEATURE(_feat) \
printf ("...%s\n", _feat);
/**
* TEST_FAILED:
* @_fmt: format string.
*
* Output a formatted message indicating that a test has failed, including
* the file, line number and function where the failure happened.
**/
#define TEST_FAILED(_fmt, ...) \
do { \
printf ("BAD: " _fmt "\n\tat %s:%d (%s).\n", \
##__VA_ARGS__, __FILE__, __LINE__, __FUNCTION__); \
abort (); \
} while (0)
/**
* TEST_TRUE:
* @_expr: value or expression to test.
*
* Check that the expression @_expr evaluates to TRUE.
**/
#define TEST_TRUE(_expr) \
if (! (_expr)) \
TEST_FAILED ("wrong value for %s, expected TRUE got FALSE", \
#_expr)
/**
* TEST_FALSE:
* @_expr: value or expression to test.
*
* Check that the expression @_expr evaluates to FALSE.
**/
#define TEST_FALSE(_expr) \
if (_expr) \
TEST_FAILED ("wrong value for %s, expected FALSE got TRUE", \
#_expr)
/**
* TEST_EQ:
* @_a: first integer,
* @_b: second integer.
*
* Check that the two numeric values @_a and @_b are equal, they are cast
* to ssize_t for display purposes.
**/
#define TEST_EQ(_a, _b) \
if ((_a) != (_b)) \
TEST_FAILED ("wrong value for %s, expected %zi got %zi", \
#_a, (ssize_t)(_b), (ssize_t)(_a))
/**
* TEST_EQ_U:
* @_a: first unsigned integer,
* @_b: second unsigned integer.
*
* Check that the two numeric values @_a and @_b are equal, they are cast
* to size_t for display purposes.
**/
#define TEST_EQ_U(_a, _b) \
if ((_a) != (_b)) \
TEST_FAILED ("wrong value for %s, expected %zu got %zu", \
#_a, (size_t)(_b), (size_t)(_a))
/**
* TEST_EQ_P:
* @_a: first pointer,
* @_b: second pointer.
*
* Check that the two pointers @_a and @_b are equal.
**/
#define TEST_EQ_P(_a, _b) \
if ((_a) != (_b)) \
TEST_FAILED ("wrong value for %s, expected %p got %p", \
#_a, (_b), (_a))
/**
* TEST_EQ_STR:
* @_a: first string,
* @_b: second string.
*
* Check that the two strings @_a and @_b are equal.
**/
#define TEST_EQ_STR(_a, _b) \
if (strcmp ((_a), (_b))) \
TEST_FAILED ("wrong value for %s, expected '%s' got '%s'", \
#_a, (_b), (_a))
/**
* TEST_EQ_STRN:
* @_a: first string,
* @_b: second string.
*
* Check that the two strings @_a and @_b are equal, up to the length of
* the second string.
**/
#define TEST_EQ_STRN(_a, _b) \
if (strncmp ((_a), (_b), strlen (_b))) \
TEST_FAILED ("wrong value for %s, expected '%.*s' got '%.*s'", \
#_a, (int)strlen (_b), (_b), \
(int)strlen (_b), (_a))
/**
* TEST_EQ_MEM:
* @_a: first memory area,
* @_b: second memory area,
* @_l: length of @_a and @_b.
*
* Check that the two @_l byte long areas of memory at @_a and @_b are
* identical.
**/
#define TEST_EQ_MEM(_a, _b, _l) \
if (memcmp ((_a), (_b), (_l))) \
TEST_FAILED ("wrong %zu bytes at %p (%s), expected %p (%s)", \
(size_t)(_l), (_a), #_a, (_b), #_b)
/**
* TEST_NE:
* @_a: first integer,
* @_b: second integer.
*
* Check that the two numeric values @_a and @_b are not equal, they are
* cast to ssize_t for display purposes.
**/
#define TEST_NE(_a, _b) \
if ((_a) == (_b)) \
TEST_FAILED ("wrong value for %s, got unexpected %zi", \
#_a, (ssize_t)(_b))
/**
* TEST_NE_U:
* @_a: first unsigned integer,
* @_b: second unsigned integer.
*
* Check that the two numeric values @_a and @_b are not equal, they are
* cast to size_t for display purposes.
**/
#define TEST_NE_U(_a, _b) \
if ((_a) == (_b)) \
TEST_FAILED ("wrong value for %s, got unexpected %zu", \
#_a, (size_t)(_b))
/**
* TEST_NE_P:
* @_a: first pointer,
* @_b: second pointer.
*
* Check that the two pointers @_a and @_b are not equal.
**/
#define TEST_NE_P(_a, _b) \
if ((_a) == (_b)) \
TEST_FAILED ("wrong value for %s, got unexpected %p", \
#_a, (_b))
/**
* TEST_NE_STR:
* @_a: first string,
* @_b: second string.
*
* Check that the two strings @_a and @_b are not equal.
**/
#define TEST_NE_STR(_a, _b) \
if (! strcmp ((_a), (_b))) \
TEST_FAILED ("wrong value for %s, got unexpected '%s'", \
#_a, (_b))
/**
* TEST_NE_STRN:
* @_a: first string,
* @_b: second string.
*
* Check that the two strings @_a and @_b are not equal, up to the length
* of the second string.
**/
#define TEST_NE_STRN(_a, _b) \
if (! strncmp ((_a), (_b), strlen (_b))) \
TEST_FAILED ("wrong value for %s, got unexpected '%.*s'", \
#_a, (int)strlen (_b), (_b))
/**
* TEST_NE_MEM:
* @_a: first memory area,
* @_b: second memory area,
* @_l: length of @_a and @_b.
*
* Check that the two @_l byte long areas of memory at @_a and @_b are
* different.
**/
#define TEST_NE_MEM(_a, _b, _l) \
if (! memcmp ((_a), (_b), (_l))) \
TEST_FAILED ("wrong %zu bytes at %p (%s), got unexpected %p (%s)", \
(size_t)(_l), (_a), #_a, (_b), #_b)
/**
* TEST_LT:
* @_a: first integer,
* @_b: second integer.
*
* Check that the numeric value @_a is less than the numeric value @_b,
* they are cast to ssize_t for display purposes.
**/
#define TEST_LT(_a, _b) \
if ((_a) >= (_b)) \
TEST_FAILED ("wrong value for %s, expected less than %zi got %zi", \
#_a, (ssize_t)(_b), (ssize_t)(_a))
/**
* TEST_LE:
* @_a: first integer,
* @_b: second integer.
*
* Check that the numeric value @_a is less than or equal to the numeric
* value @_b, they are cast to ssize_t for display purposes.
**/
#define TEST_LE(_a, _b) \
if ((_a) > (_b)) \
TEST_FAILED ("wrong value for %s, expected %zi or lower got %zi", \
#_a, (ssize_t)(_b), (ssize_t)(_a))
/**
* TEST_GT:
* @_a: first integer,
* @_b: second integer.
*
* Check that the numeric value @_a is greater than the numeric value @_b,
* they are cast to ssize_t for display purposes.
**/
#define TEST_GT(_a, _b) \
if ((_a) <= (_b)) \
TEST_FAILED ("wrong value for %s, expected greater than %zi got %zi", \
#_a, (ssize_t)(_b), (ssize_t)(_a))
/**
* TEST_GE:
* @_a: first integer,
* @_b: second integer.
*
* Check that the numeric value @_a is greater than or equal to the numeric
* value @_b, they are cast to ssize_t for display purposes.
**/
#define TEST_GE(_a, _b) \
if ((_a) < (_b)) \
TEST_FAILED ("wrong value for %s, expected %zi or greater got %zi", \
#_a, (ssize_t)(_b), (ssize_t)(_a))
/**
* TEST_CHILD:
* @_pid: variable to store pid in.
*
* Spawn a child in which a test can be performed without affecting the
* main flow of the process. The pid of the child is stored in @_pid.
*
* This macro ensures that the child has begun exectution before the
* parent is allowed to continue through the usual use of a pipe.
*
* A block of code should follow this macro, which is the code that will
* be run in the child process; if the block ends, the child will abort.
**/
#define TEST_CHILD(_pid) \
do { \
int _test_fds[2]; \
pipe (_test_fds); \
_pid = fork (); \
if (_pid > 0) { \
char _test_buf[1]; \
close (_test_fds[1]); \
read (_test_fds[0], _test_buf, 1); \
close (_test_fds[0]); \
} else if (_pid == 0) { \
close (_test_fds[0]); \
write (_test_fds[1], "\n", 1); \
close (_test_fds[1]); \
} \
} while (0); \
if (_pid == 0) \
for (int _test_child = 0; _test_child < 2; _test_child++) \
if (_test_child) { \
abort (); \
} else
/**
* TEST_CHILD_WAIT:
* @_pid: variable to store pid in,
* @_fd: variable to store lock fd in.
*
* Spawn a child in which a test can be performed while the parent waits
* in this macro for the child to reach the TEST_CHILD_RELEASE macro
* or die.
*
* The pid of the child is stored in @_pid, a file descriptor is stored
* in the @_fd variable which is used by the TEST_CHILD_RELEASE macro.
*
* A block of code should follow this macro, which is the code that will
* be run in the child process; if the block ends, the child will abort.
**/
#define TEST_CHILD_WAIT(_pid, _fd) \
do { \
int _test_fds[2]; \
pipe (_test_fds); \
_pid = fork (); \
if (_pid > 0) { \
char _test_buf[1]; \
close (_test_fds[1]); \
read (_test_fds[0], _test_buf, 1); \
close (_test_fds[0]); \
} else if (_pid == 0) { \
close (_test_fds[0]); \
_fd = _test_fds[1]; \
} \
} while (0); \
if (_pid == 0) \
for (int _test_child = 0; _test_child < 2; _test_child++) \
if (_test_child) { \
abort (); \
} else
/**
* TEST_CHILD_RELEASE:
* @_fd: variable lock fd stored in.
*
* Release the parent of a child spawned by TEST_CHILD_WAIT now that the
* child has reached the critical point.
**/
#define TEST_CHILD_RELEASE(_fd) \
do { \
write ((_fd), "\n", 1); \
close (_fd); \
} while (0)
/**
* TEST_DIVERT_STDOUT_FD:
* @_fd: fd to send standard output to.
*
* This macro diverts standard output to a different file descriptor
* for the duration of a block of code that should follow it.
**/
#define TEST_DIVERT_STDOUT_FD(_fd) \
for (int _test_stdout = 0, _test_oldstdout = dup (STDOUT_FILENO); \
_test_stdout < 3; _test_stdout++) \
if (_test_stdout < 1) { \
fflush (stdout); \
dup2 ((_fd), STDOUT_FILENO); \
} else if (_test_stdout > 1) { \
fflush (stdout); \
dup2 (_test_oldstdout, STDOUT_FILENO); \
close (_test_oldstdout); \
} else
/**
* TEST_DIVERT_STDOUT:
* @_file: FILE to send standard output to.
*
* This macro diverts standard output to a different file for the duration
* of a block of code that should follow it.
*/
#define TEST_DIVERT_STDOUT(_file) \
TEST_DIVERT_STDOUT_FD (fileno (_file))
/**
* TEST_DIVERT_STDERR_FD:
* @_fd: fd to send standard error to.
*
* This macro diverts standard error to a different file descriptor for the
* duration of a block of code that should follow it.
*/
#define TEST_DIVERT_STDERR_FD(_fd) \
for (int _test_stderr = 0, _test_oldstderr = dup (STDERR_FILENO); \
_test_stderr < 3; _test_stderr++) \
if (_test_stderr < 1) { \
fflush (stderr); \
dup2 ((_fd), STDERR_FILENO); \
} else if (_test_stderr > 1) { \
fflush (stderr); \
dup2 (_test_oldstderr, STDERR_FILENO); \
close (_test_oldstderr); \
} else
/**
* TEST_DIVERT_STDERR:
* @_file: FILE to send standard error to.
*
* This macro diverts standard error to a different file for the duration
* of a block of code that should follow it.
*/
#define TEST_DIVERT_STDERR(_file) \
TEST_DIVERT_STDERR_FD (fileno (_file))
/**
* TEST_FILENAME:
* @_var: variable to store filename in.
*
* Generate a filename that may be used for testing, it's unlinked it if
* exists and it's up to you to unlink it when done. @_var should be at
* least PATH_MAX long.
**/
#define TEST_FILENAME(_var) \
do { \
snprintf ((_var), sizeof (_var), "/tmp/%s-%s-%d-%d", \
strrchr (__FILE__, '/') ? strrchr (__FILE__, '/') + 1 : __FILE__, \
__FUNCTION__, __LINE__, getpid ()); \
unlink (_var); \
} while (0)
/**
* TEST_FILE_EQ:
* @_file: FILE to read from,
* @_line: line to expect.
*
* Check that the next line in the file @_file is @_line, which should
* include the terminating newline if one is expected.
**/
#define TEST_FILE_EQ(_file, _line) \
do { \
char _test_file[512]; \
if (! fgets (_test_file, sizeof (_test_file), (_file))) \
TEST_FAILED ("eof on file %p (%s), expected '%s'", \
(_file), #_file, (_line)); \
if (strcmp (_test_file, (_line))) \
TEST_FAILED ("wrong content in file %p (%s), expected '%s' got '%s'", \
(_file), #_file, (_line), _test_file); \
} while (0)
/**
* TEST_FILE_EQ_N:
* @_file: FILE to read from,
* @_line: line to expect.
*
* Check that the start of the next line in the file @_file is @_line, up to
* the length of that argument.
**/
#define TEST_FILE_EQ_N(_file, _line) \
do { \
char _test_file[512]; \
if (! fgets (_test_file, sizeof (_test_file), (_file))) \
TEST_FAILED ("eof on file %p (%s), expected '%s'", \
(_file), #_file, (_line)); \
if (strncmp (_test_file, (_line), strlen (_line))) \
TEST_FAILED ("wrong content in file %p (%s), expected '%.*s' got '%.*s'", \
(_file), #_file, (int)strlen (_line), (_line), \
(int)strlen (_line), _test_file); \
} while (0)
/**
* TEST_FILE_NE:
* @_file: FILE to read from,
* @_line: line to expect.
*
* Check that the next line in the file @_file is not @_line, but also not
* end of file.
**/
#define TEST_FILE_NE(_file, _line) \
do { \
char _test_file[512]; \
if (! fgets (_test_file, sizeof (_test_file), (_file))) \
TEST_FAILED ("eof on file %p (%s), expected line other than '%s'", \
(_file), #_file, (_line)); \
if (strcmp (_test_file, (_line))) \
TEST_FAILED ("wrong content in file %p (%s), got unexpected '%s'", \
(_file), #_file, (_line)); \
} while (0)
/**
* TEST_FILE_NE_N:
* @_file: FILE to read from,
* @_line: line to expect.
*
* Check that the next line in the file @_file does not start with @_line,
* up to the length of that argument; but also not end of file.
**/
#define TEST_FILE_NE_N(_file, _line) \
do { \
char _test_file[512]; \
if (! fgets (_test_file, sizeof (_test_file), (_file))) \
TEST_FAILED ("eof on file %p (%s), expected line other than '%s'", \
(_file), #_file, (_line)); \
if (strncmp (_test_file, (_line), strlen (_line))) \
TEST_FAILED ("wrong content in file %p (%s), got unexpected '%.*s'", \
(_file), #_file, (int)strlen (_line), (_line)); \
} while (0)
/**
* TEST_FILE_END:
* @_file: FILE to check.
*
* Check that the end of the file @_file has been reached, and that there
* are no more lines to read.
**/
#define TEST_FILE_END(_file) \
do { \
char _test_file[512];\
if (fgets (_test_file, sizeof (_test_file), (_file))) \
TEST_FAILED ("wrong content in file %p (%s), expected eof got '%s'", \
(_file), #_file, _test_file); \
} while (0)
/**
* TEST_FILE_RESET:
* @_file: FILE to reset.
*
* This macro may be used to reset a temporary file such that it can be
* treated as a new one.
**/
#define TEST_FILE_RESET(_file) \
do { \
fflush (_file); \
rewind (_file); \
ftruncate (fileno (_file), 0); \
} while (0)
/**
* TEST_ALLOC_SIZE:
* @_ptr: allocated pointer,
* @_sz: expected size.
*
* Check that the pointer @_ptr was allocated with nih_alloc(), and is @_sz
* bytes in length (which includes the context).
**/
#define TEST_ALLOC_SIZE(_ptr, _sz) \
if (nih_alloc_size (_ptr) != (_sz)) \
TEST_FAILED ("wrong size of block %p (%s), expected %zu got %zu", \
(_ptr), #_ptr, (size_t)(_sz), \
nih_alloc_size (_ptr))
/**
* TEST_ALLOC_PARENT:
* @_ptr: allocated pointer,
* @_parent: expected parent.
*
* Check that the pointer @_ptr was allocated with nih_alloc() and has
* the other block @_parent as a parent.
**/
#define TEST_ALLOC_PARENT(_ptr, _parent) \
if (nih_alloc_parent (_ptr) != (_parent)) \
TEST_FAILED ("wrong parent of block %p (%s), expected %p (%s) got %p", \
(_ptr), #_ptr, (_parent), #_parent, \
nih_alloc_parent (_ptr))
/**
* test_alloc_failed:
*
* Variable used by TEST_ALLOC_FAIL as the loop counter.
**/
static int test_alloc_failed = 0;
/**
* _test_alloc_count:
*
* Number of times malloc is called by the TEST_ALLOC_FAIL macro.
**/
static int _test_alloc_count = 0;
/**
* _test_alloc_call:
*
* Number of times malloc has been called during each cycle.
**/
static int _test_alloc_call = 0;
/**
* _test_allocator:
*
* Allocator used by TEST_ALLOC_FAIL; when test_alloc_failed is zero, it
* increments test_alloc_count and returns whatever realloc does. Otherwise
* it internally counts the number of times it is called, and if that matches
* test_alloc_failed, then it returns NULL.
**/
static inline void *
_test_allocator (void *ptr,
size_t size)
{
if (! size)
return realloc (ptr, size);
if (! test_alloc_failed) {
_test_alloc_count++;
return realloc (ptr, size);
}
_test_alloc_call++;
if (test_alloc_failed == _test_alloc_call) {
errno = ENOMEM;
return NULL;
} else {
return realloc (ptr, size);
}
}
/**
* TEST_ALLOC_FAIL:
*
* This macro expands to code that runs the following block repeatedly; the
* first time (when the special test_alloc_failed variable is zero) is
* used to determine how many allocations are performed by the following block;
* subsequent calls (when test_alloc_failed is a positive integer) mean that
* the test_alloc_failedth call to realloc has failed.
*
* This cannot be nested as it relies on setting an alternate allocator
* and sharing a global state.
**/
#define TEST_ALLOC_FAIL \
for (test_alloc_failed = -1; \
test_alloc_failed <= (_test_alloc_count + 1); \
test_alloc_failed++, _test_alloc_call = 0) \
if (test_alloc_failed < 0) { \
_test_alloc_count = 0; \
nih_alloc_set_allocator (_test_allocator); \
} else if (test_alloc_failed \
&& (test_alloc_failed == \
(_test_alloc_count + 1))) { \
nih_alloc_set_allocator (realloc); \
} else
/**
* TEST_ALLOC_SAFE:
*
* This macro may be used within a TEST_ALLOC_FAIL block to guard the
* following block of code from failing allocation.
**/
#define TEST_ALLOC_SAFE \
for (int _test_alloc_safe = 0; _test_alloc_safe < 3; \
_test_alloc_safe++) \
if (_test_alloc_safe < 1) { \
nih_alloc_set_allocator (realloc); \
} else if (_test_alloc_safe > 1) { \
nih_alloc_set_allocator (_test_allocator); \
} else
/**
* TEST_LIST_EMPTY:
* @_list: entry in list.
*
* Check that the list of which @_list is a member is empty, ie. that
* @_list is the sole member.
**/
#define TEST_LIST_EMPTY(_list) \
if (! NIH_LIST_EMPTY (_list)) \
TEST_FAILED ("list %p (%s) not empty as expected", \
(_list), #_list)
/**
* TEST_LIST_NOT_EMPTY:
* @_list: entry in list.
*
* Check that the list of which @_list is a member is not empty, ie. that
* there are more members than just @_list.
**/
#define TEST_LIST_NOT_EMPTY(_list) \
if (NIH_LIST_EMPTY (_list)) \
TEST_FAILED ("list %p (%s) empty, expected multiple members", \
(_list), #_list)
/**
* TEST_HASH_EMPTY:
* @_hash: hash table.
*
* Check that the hash table @_hash is empty.
**/
#define TEST_HASH_EMPTY(_hash) \
for (size_t _hash_i = 0; _hash_i < (_hash)->size; _hash_i++) \
if (! NIH_LIST_EMPTY (&(_hash)->bins[_hash_i])) \
TEST_FAILED ("hash %p (%s) not empty as expected", \
(_hash), #_hash)
/**
* TEST_HASH_NOT_EMPTY:
* @_list: entry in list.
*
* Check that the hash table @_hash is not empty.
**/
#define TEST_HASH_NOT_EMPTY(_hash) \
do { \
int _hash_empty = 1; \
for (size_t _hash_i = 0; _hash_i < (_hash)->size; _hash_i++) \
if (! NIH_LIST_EMPTY (&(_hash)->bins[_hash_i])) \
_hash_empty = 0; \
if (_hash_empty) \
TEST_FAILED ("hash %p (%s) empty, expected multiple members", \
(_hash), #_hash); \
} while (0)
#endif /* NIH_TEST_H */