diff --git a/test/Makefile b/test/Makefile index fc663c2..1c4208d 100644 --- a/test/Makefile +++ b/test/Makefile @@ -6,7 +6,7 @@ LDFLAGS = -lrt -lpthread SRC = timetest.c OBJ = ${SRC:.c=.o} -TESTFUNCS = $(notdir $(basename $(wildcard snippets/*.c))) +TEST_SNIPPETS = $(notdir $(basename $(wildcard snippets/*.c))) EXPECTATIONS= $(notdir $(basename $(wildcard snippets/*.variable))) all: timetest test @@ -31,32 +31,35 @@ functest: randomtest: repeat_random ./randomtest.sh -## test variables +# run snippet tests +snippets: test_variable_data test_library_constructors + +## test snippet behavior across env var setting over time: test_variable_data: test_variable_data.sh $(foreach f,${EXPECTATIONS},run_${f}) ./test_variable_data.sh ${EXPECTATIONS} run_%: _run_test.c snippets/%.c - sed s/FUNC_NAME/$*/g < _run_test.c | ${CC} -o $@ ${CFLAGS} -x c - + sed s/SNIPPET_NAME/$*/g < _run_test.c | ${CC} -o $@ ${CFLAGS} -x c - -## testing when interception points get called in library constructors: - -test_library_constructors: test_constructors.sh $(foreach f,${TESTFUNCS},use_lib_${f} lib${f}.so) - true $(foreach f,${TESTFUNCS},&& ./test_constructors.sh ${f}) +## test snippets in other library constructors: +test_library_constructors: $(foreach f,${TEST_SNIPPETS},test_lib_${f}) +test_lib_%: test_constructors.sh use_lib_% lib%.so + ./test_constructors.sh $* lib%.so: _libtest.c snippets/%.c - sed s/FUNC_NAME/$*/g < _libtest.c | ${CC} -shared -o $@ -fpic ${CFLAGS} -x c - + sed s/SNIPPET_NAME/$*/g < _libtest.c | ${CC} -shared -o $@ -fpic ${CFLAGS} -x c - use_lib_%: _use_lib_test.c snippets/%.c lib%.so - sed s/FUNC_NAME/$*/g < _use_lib_test.c | ${CC} -L. -o $@ ${CFLAGS} -x c - -l$* + sed s/SNIPPET_NAME/$*/g < _use_lib_test.c | ${CC} -L. -o $@ ${CFLAGS} -x c - -l$* ## cleanup and metainformation clean: - @rm -f ${OBJ} timetest getrandom_test syscall_test $(foreach f,${TESTFUNCS},use_lib_${f} lib${f}.so run_${f}) + @rm -f ${OBJ} timetest getrandom_test syscall_test $(foreach f,${TEST_SNIPPETS},use_lib_${f} lib${f}.so run_${f}) distclean: clean @echo -.PHONY: all test clean distclean randomtest +.PHONY: all test clean distclean randomtest snippets test_variable_data test_library_constructors diff --git a/test/_libtest.c b/test/_libtest.c index 2fa0bc9..6346e78 100644 --- a/test/_libtest.c +++ b/test/_libtest.c @@ -1,8 +1,8 @@ #include "snippets/include_headers.h" -#define where "library" -void FUNC_NAME_as_needed() { - printf(" called FUNC_NAME_as_needed() \n"); +#define where "libSNIPPET_NAME" +void SNIPPET_NAME_as_needed() { + printf(" called SNIPPET_NAME_as_needed() \n"); } -static __attribute__((constructor)) void init_FUNC_NAME() { -#include "snippets/FUNC_NAME.c" +static __attribute__((constructor)) void init_SNIPPET_NAME() { +#include "snippets/SNIPPET_NAME.c" } diff --git a/test/_run_test.c b/test/_run_test.c index 63672f9..41dda81 100644 --- a/test/_run_test.c +++ b/test/_run_test.c @@ -1,5 +1,5 @@ #include "snippets/include_headers.h" -#define where "direct" +#define where "run_SNIPPET_NAME" int main() { -#include "snippets/FUNC_NAME.c" +#include "snippets/SNIPPET_NAME.c" } diff --git a/test/_use_lib_test.c b/test/_use_lib_test.c index 24d8cc7..1c392fe 100644 --- a/test/_use_lib_test.c +++ b/test/_use_lib_test.c @@ -1,7 +1,7 @@ #include "snippets/include_headers.h" -extern void FUNC_NAME_as_needed(); -#define where "program" +extern void SNIPPET_NAME_as_needed(); +#define where "use_lib_SNIPPET_NAME" int main() { - FUNC_NAME_as_needed(); -#include "snippets/FUNC_NAME.c" + SNIPPET_NAME_as_needed(); +#include "snippets/SNIPPET_NAME.c" } diff --git a/test/snippets/README b/test/snippets/README index 446f080..f5de0cc 100644 --- a/test/snippets/README +++ b/test/snippets/README @@ -1,35 +1,72 @@ -Bulk testing of function interception -===================================== +Testing Interception with Snippets +================================== -Faketime intercepts some C library functions. We want to make it -easier to apply certain generic tests across many different functions. +Faketime intercepts some C library functions and system calls. We +want to make it easier to apply certain generic tests across many +different functions. We do that with snippets of C, which each can be +applied in multiple testing frameworks. -Including a new function ------------------------- +Most snippets are just a minimalist invocation of a single function or +syscall that is likely to be intercepted by libfaketime, though it's +possible to test more complex snippets too. -To test a function FOO, supply a C snippet in this directory named -. This should be a small bit of code that exercises the -function FOO. It should be self-contained, and it should produce some -kind of description of what happened to stdout. Take a look at -getpid.c for a simple example. +Including a New Snippet +----------------------- -The data sent to stdout should include the const char* "where" value -(which is an indication of which test framework is using the snippet). +To cover a new bit of intercepted functionality, supply a C snippet in +this directory named `FOO.c` (the name FOO should conform to C +function names -- letters, numbers, and underscores; if you're testing +interception of a single function, the simplest thing is to name +snippet file after the intercepted function name). -If the output of the snippet is expected to be stable when the -associated variable (e.g. FAKETIME, FAKETIME_FAKEPID, or -FAKERANDOM_SEED) is set, and is likely to vary otherwise, especially -across a delay of a second or more, add a single-line file -FOO.variable that contains the name of the variable separated by a -space and then the value of the variable. +This file should contain a small bit of code that exercises the +functionality in question. It should be self-contained, and it should +produce some kind of description of what happened to stdout. The data +sent to stdout should include the const char* "where" value (which is +an indication of which test framework is using the snippet). Take a +look at getpid.c for a simple example. -If the snippet needs to additional #include headers, please add them -in include_headers.h. These #includes will be used by every snippet, +If the snippet needs additional #include headers, please add them +in `include_headers.h`. These #includes will be used by every snippet, so try to keep it minimal. -Testing across functions ------------------------- +Snippet Testing Frameworks +-------------------------- -If you want to test something systemically, autogenerate code that -uses each snippet. See test_variable_data or -test_library_constructors in ../Makefile for an example. +We have the following frameworks that use the snippets: + +### Variable Data + +Most functionality intercepted by libfaketime will normally produce +variant output when invoked multiple times across the span of a few +seconds by different processes. But if `FAKETIME` (or an analogous +variable like `FAKERANDOM_SEED`) is set, the output data should remain +constant. + +If this describes the functionality in a new snippet `FOO.c`, please +also drop a single-line file `FOO.variable` in this directory, where +the first word of the line is the variable that should hold the output +constant, and the rest of the line is the value of that variable. + +See the `test_variable_data` target in `test/Makefile` for how this is +implemented. + +### Library Constructors + +Library constructor routines are run by ld.so before the main process +is launched. If the LD_PRELOADed libfaketime is masking a symbol from +libc, and another library has a constructor routine that invokes that +symbol, it might get called before libfaketime has had a chance to +initialize its followup pointers to the actual libc functionality. + +This framework is applied automatically to all snippets. + +See the `test_library_constructors` target in `test/Makefile` for how +this is implemented. + +Adding a New Framework +---------------------- + +If you want to add a new framework that tests across all snippets, +implement it in a few lines of `test/Makefile` and document it in the +section above. diff --git a/test/snippets/clock_gettime.c b/test/snippets/clock_gettime.c new file mode 100644 index 0000000..94f3629 --- /dev/null +++ b/test/snippets/clock_gettime.c @@ -0,0 +1,8 @@ +struct timespec ts; +clockid_t ckid = CLOCK_REALTIME; +int ret = clock_gettime(ckid, &ts); +if (ret == 0) { + printf("[%s] clock_gettime(CLOCK_REALTIME[%d], &ts) -> {%lld, %ld}\n", where, ckid, (long long)ts.tv_sec, ts.tv_nsec); + } else { + printf("[%s] clock_gettime(CLOCK_REALTIME[%d], &ts) returned non-zero (%d), errno = %d (%s)\n", where, ckid, ret, errno, strerror(errno)); + } diff --git a/test/snippets/clock_gettime.variable b/test/snippets/clock_gettime.variable new file mode 100644 index 0000000..3f75b5f --- /dev/null +++ b/test/snippets/clock_gettime.variable @@ -0,0 +1 @@ +FAKETIME 2020-02-02 02:02:02+00:00 diff --git a/test/snippets/clock_gettime_heap.c b/test/snippets/clock_gettime_heap.c new file mode 100644 index 0000000..11ae827 --- /dev/null +++ b/test/snippets/clock_gettime_heap.c @@ -0,0 +1,8 @@ +struct timespec *ts = malloc(sizeof(struct timespec)); +clockid_t ckid = CLOCK_REALTIME; +int ret = clock_gettime(ckid, ts); +if (ret == 0) { + printf("[%s] clock_gettime_heap(CLOCK_REALTIME[%d], ts) -> {%lld, %ld}\n", where, ckid, (long long)ts->tv_sec, ts->tv_nsec); + } else { + printf("[%s] clock_gettime_heap(CLOCK_REALTIME[%d], ts) returned non-zero (%d), errno = %d (%s)\n", where, ckid, ret, errno, strerror(errno)); + } diff --git a/test/snippets/clock_gettime_heap.variable b/test/snippets/clock_gettime_heap.variable new file mode 100644 index 0000000..3f75b5f --- /dev/null +++ b/test/snippets/clock_gettime_heap.variable @@ -0,0 +1 @@ +FAKETIME 2020-02-02 02:02:02+00:00 diff --git a/test/snippets/include_headers.h b/test/snippets/include_headers.h index 55cd48f..1c329bc 100644 --- a/test/snippets/include_headers.h +++ b/test/snippets/include_headers.h @@ -6,3 +6,4 @@ #include #include #include +#include diff --git a/test/snippets/syscall_clock_gettime_heap.c b/test/snippets/syscall_clock_gettime_heap.c new file mode 100644 index 0000000..06ebb4f --- /dev/null +++ b/test/snippets/syscall_clock_gettime_heap.c @@ -0,0 +1,8 @@ +struct timespec *ts = malloc(sizeof(struct timespec)); +clockid_t ckid = CLOCK_REALTIME; +long ret = syscall(__NR_clock_gettime, ckid, ts); +if (ret == 0) + printf("[%s] syscall(__NR_gettime, CLOCK_REALTIME[%d], ts) -> {%lld, %ld}\n", where, ckid, (long long)ts->tv_sec, ts->tv_nsec); +else + printf("[%s] syscall(__NR_gettime, CLOCK_REALTIME[%d], ts) returned non-zero (%ld)\n", where, ckid, ret); + diff --git a/test/snippets/syscall_clock_gettime_heap.variable b/test/snippets/syscall_clock_gettime_heap.variable new file mode 100644 index 0000000..3f75b5f --- /dev/null +++ b/test/snippets/syscall_clock_gettime_heap.variable @@ -0,0 +1 @@ +FAKETIME 2020-02-02 02:02:02+00:00 diff --git a/test/snippets/time.c b/test/snippets/time.c new file mode 100644 index 0000000..7153588 --- /dev/null +++ b/test/snippets/time.c @@ -0,0 +1,2 @@ +time_t t = time(NULL); +printf("[%s] time() yielded %zd\n", where, t); diff --git a/test/snippets/time.variable b/test/snippets/time.variable new file mode 100644 index 0000000..3f75b5f --- /dev/null +++ b/test/snippets/time.variable @@ -0,0 +1 @@ +FAKETIME 2020-02-02 02:02:02+00:00