Merge pull request #311 from dkg/more-testing

More snippet testing and better documentation
This commit is contained in:
Wolfgang Hommel
2021-03-02 21:44:56 +01:00
committed by GitHub
14 changed files with 119 additions and 48 deletions

View File

@@ -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

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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
<FOO.c>. 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.

View File

@@ -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));
}

View File

@@ -0,0 +1 @@
FAKETIME 2020-02-02 02:02:02+00:00

View File

@@ -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));
}

View File

@@ -0,0 +1 @@
FAKETIME 2020-02-02 02:02:02+00:00

View File

@@ -6,3 +6,4 @@
#include <string.h>
#include <errno.h>
#include <time.h>
#include <stdlib.h>

View File

@@ -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);

View File

@@ -0,0 +1 @@
FAKETIME 2020-02-02 02:02:02+00:00

2
test/snippets/time.c Normal file
View File

@@ -0,0 +1,2 @@
time_t t = time(NULL);
printf("[%s] time() yielded %zd\n", where, t);

View File

@@ -0,0 +1 @@
FAKETIME 2020-02-02 02:02:02+00:00