From 5b1fd222ed97430dfeef27a95aeb7e1fb8fd1352 Mon Sep 17 00:00:00 2001 From: Madelyn Olson Date: Thu, 2 May 2024 20:00:04 -0700 Subject: [PATCH] An initial simple unit test framework (#344) The core idea was to take a lot of the stuff from the C unity framework and adapt it a bit here. Each file in the `unit` directory that starts with `test_` is automatically assumed to be a test suite. Within each file, all functions that start with `test_` are assumed to be a test. See unit/README.md for details about the implementation. Instead of compiling basically a net new binary, the way the tests are compiled is that the main valkey server is compiled as a static archive, which we then compile the individual test files against to create a new test executable. This is not all that important now, other than it makes the compilation simpler, but what it will allow us to do is overwrite functions in the archive to enable mocking for cross compilation unit functions. There are also ways to enable mocking from within the same compilation unit, but I don't know how important this is. Tests are also written in one of two styles: 1. Including the header file and directly calling functions from the archive. 2. Importing the original file, and then calling the functions. This second approach is cool because we can call static functions. It won't mess up the archive either. --------- Signed-off-by: Madelyn Olson --- .github/workflows/ci.yml | 5 +- .github/workflows/daily.yml | 31 +++- .gitignore | 2 + src/Makefile | 41 +++++- src/crc64.c | 223 ---------------------------- src/crc64.h | 4 - src/intset.c | 217 --------------------------- src/server.c | 2 - src/unit/README.md | 54 +++++++ src/unit/test_crc64.c | 30 ++++ src/unit/test_crc64combine.c | 193 ++++++++++++++++++++++++ src/unit/test_files.h | 31 ++++ src/unit/test_help.h | 48 ++++++ src/unit/test_intset.c | 229 +++++++++++++++++++++++++++++ src/unit/test_main.c | 67 +++++++++ utils/generate-unit-test-header.py | 59 ++++++++ 16 files changed, 779 insertions(+), 457 deletions(-) create mode 100644 src/unit/README.md create mode 100644 src/unit/test_crc64.c create mode 100644 src/unit/test_crc64combine.c create mode 100644 src/unit/test_files.h create mode 100644 src/unit/test_help.h create mode 100644 src/unit/test_intset.c create mode 100644 src/unit/test_main.c create mode 100755 utils/generate-unit-test-header.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 137bd195c..c94893320 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: - name: make # Fail build if there are warnings # build with TLS just for compilation coverage - run: make SERVER_CFLAGS='-Werror' BUILD_TLS=yes + run: make all-with-unit-tests SERVER_CFLAGS='-Werror' BUILD_TLS=yes - name: test run: | sudo apt-get install tcl8.6 tclx @@ -27,6 +27,9 @@ jobs: make commands.def dirty=$(git diff) if [[ ! -z $dirty ]]; then echo $dirty; exit 1; fi + - name: unit tests + run: | + ./src/valkey-unit-tests test-sanitizer-address: runs-on: ubuntu-latest diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml index 178d9b7ce..f4726a420 100644 --- a/.github/workflows/daily.yml +++ b/.github/workflows/daily.yml @@ -54,7 +54,7 @@ jobs: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make - run: make SERVER_CFLAGS='-Werror -DSERVER_TEST' + run: make all-with-unit-tests SERVER_CFLAGS='-Werror -DSERVER_TEST' - name: testprep run: sudo apt-get install tcl8.6 tclx - name: test @@ -69,9 +69,12 @@ jobs: - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} - - name: unittest + - name: legacy unit tests if: true && !contains(github.event.inputs.skiptests, 'unittest') run: ./src/valkey-server test all --accurate + - name: new unit tests + if: true && !contains(github.event.inputs.skiptests, 'unittest') + run: ./src/valkey-unit-tests --accurate test-ubuntu-jemalloc-fortify: runs-on: ubuntu-latest @@ -113,9 +116,12 @@ jobs: - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} - - name: unittest + - name: legacy unit tests if: true && !contains(github.event.inputs.skiptests, 'unittest') run: ./src/valkey-server test all --accurate + - name: new unit tests + if: true && !contains(github.event.inputs.skiptests, 'unittest') + run: make valkey-unit-tests && ./src/valkey-unit-tests --accurate test-ubuntu-libc-malloc: runs-on: ubuntu-latest @@ -231,9 +237,12 @@ jobs: - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} - - name: unittest + - name: legacy unit tests if: true && !contains(github.event.inputs.skiptests, 'unittest') run: ./src/valkey-server test all --accurate + - name: new unit tests + if: true && !contains(github.event.inputs.skiptests, 'unittest') + run: ./src/valkey-unit-tests --accurate test-ubuntu-tls: runs-on: ubuntu-latest @@ -589,7 +598,7 @@ jobs: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make - run: make SANITIZER=address SERVER_CFLAGS='-DSERVER_TEST -Werror -DDEBUG_ASSERTIONS' + run: make all-with-unit-tests SANITIZER=address SERVER_CFLAGS='-DSERVER_TEST -Werror -DDEBUG_ASSERTIONS' - name: testprep # Work around ASAN issue, see https://github.com/google/sanitizers/issues/1716 run: | @@ -608,9 +617,12 @@ jobs: - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} - - name: unittest + - name: legacy unit tests if: true && !contains(github.event.inputs.skiptests, 'unittest') run: ./src/valkey-server test all + - name: new unit tests + if: true && !contains(github.event.inputs.skiptests, 'unittest') + run: ./src/valkey-unit-tests test-sanitizer-undefined: runs-on: ubuntu-latest @@ -638,7 +650,7 @@ jobs: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make - run: make SANITIZER=undefined SERVER_CFLAGS='-DSERVER_TEST -Werror' LUA_DEBUG=yes # we (ab)use this flow to also check Lua C API violations + run: make all-with-unit-tests SANITIZER=undefined SERVER_CFLAGS='-DSERVER_TEST -Werror' LUA_DEBUG=yes # we (ab)use this flow to also check Lua C API violations - name: testprep run: | sudo apt-get update @@ -655,9 +667,12 @@ jobs: - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} - - name: unittest + - name: legacy unit tests if: true && !contains(github.event.inputs.skiptests, 'unittest') run: ./src/valkey-server test all --accurate + - name: new unit tests + if: true && !contains(github.event.inputs.skiptests, 'unittest') + run: ./src/valkey-unit-tests --accurate test-centos7-jemalloc: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 920e32eca..8ed98aa32 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .*.swp *.o +*.a *.xo *.so *.d @@ -12,6 +13,7 @@ dump.rdb *-cli *-sentinel *-server +*-unit-tests doc-tools release misc/* diff --git a/src/Makefile b/src/Makefile index 833349839..908aa695d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -98,6 +98,15 @@ ifeq ($(USE_JEMALLOC),no) MALLOC=libc endif +# Some unit tests compile files a second time to get access to static functions, the "--allow-multiple-definition" flag +# allows us to do that without an error, by using the first instance of function. This behavior can also be used +# to tweak behavior of code just for unit tests. The version of ld on MacOS apparently always does this. +ifneq ($(uname_S),Darwin) + ALLOW_DUPLICATE_FLAG=-Wl,--allow-multiple-definition +else + ALLOW_DUPLICATE_FLAG= +endif + ifdef SANITIZER ifeq ($(SANITIZER),address) MALLOC=libc @@ -357,6 +366,7 @@ else endif SERVER_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS) +SERVER_AR=$(QUIET_AR)$(AR) SERVER_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS) ENGINE_INSTALL=$(QUIET_INSTALL)$(INSTALL) @@ -372,6 +382,7 @@ QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR QUIET_GEN = @printf ' %b %b\n' $(CCCOLOR)GEN$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR) 1>&2; QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2; QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2; +QUIET_AR = @printf ' %b %b\n' $(CCCOLOR)ARCHIVE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2; endif ifneq (, $(findstring LOG_REQ_RES, $(SERVER_CFLAGS))) @@ -392,6 +403,10 @@ ENGINE_BENCHMARK_NAME=$(ENGINE_NAME)-benchmark$(PROG_SUFFIX) ENGINE_BENCHMARK_OBJ=ae.o anet.o valkey-benchmark.o adlist.o dict.o zmalloc.o serverassert.o release.o crcspeed.o crccombine.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o strl.o ENGINE_CHECK_RDB_NAME=$(ENGINE_NAME)-check-rdb$(PROG_SUFFIX) ENGINE_CHECK_AOF_NAME=$(ENGINE_NAME)-check-aof$(PROG_SUFFIX) +ENGINE_LIB_NAME=lib$(ENGINE_NAME).a +ENGINE_TEST_FILES:=$(wildcard unit/*.c) +ENGINE_TEST_OBJ:=$(sort $(patsubst unit/%.c,unit/%.o,$(ENGINE_TEST_FILES))) +ENGINE_UNIT_TESTS:=$(ENGINE_NAME)-unit-tests$(PROG_SUFFIX) ALL_SOURCES=$(sort $(patsubst %.o,%.c,$(ENGINE_SERVER_OBJ) $(ENGINE_CLI_OBJ) $(ENGINE_BENCHMARK_OBJ))) all: $(SERVER_NAME) $(ENGINE_SENTINEL_NAME) $(ENGINE_CLI_NAME) $(ENGINE_BENCHMARK_NAME) $(ENGINE_CHECK_RDB_NAME) $(ENGINE_CHECK_AOF_NAME) $(TLS_MODULE) @@ -408,6 +423,9 @@ endif .PHONY: all +all-with-unit-tests: all $(ENGINE_UNIT_TESTS) +.PHONY: all + persist-settings: distclean echo STD=$(STD) >> .make-settings echo WARN=$(WARN) >> .make-settings @@ -442,6 +460,14 @@ endif $(SERVER_NAME): $(ENGINE_SERVER_OBJ) $(SERVER_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a $(FINAL_LIBS) +# Valkey static library, used to compile against for unit testing +$(ENGINE_LIB_NAME): $(ENGINE_SERVER_OBJ) + $(SERVER_AR) rcs $@ $^ + +# valkey-unit-tests +$(ENGINE_UNIT_TESTS): $(ENGINE_TEST_OBJ) $(ENGINE_LIB_NAME) + $(SERVER_LD) $(ALLOW_DUPLICATE_FLAG) -o $@ $^ ../deps/fpconv/libfpconv.a $(FINAL_LIBS) + # valkey-sentinel $(ENGINE_SENTINEL_NAME): $(SERVER_NAME) $(ENGINE_INSTALL) $(SERVER_NAME) $(ENGINE_SENTINEL_NAME) @@ -475,6 +501,9 @@ DEP = $(ENGINE_SERVER_OBJ:%.o=%.d) $(ENGINE_CLI_OBJ:%.o=%.d) $(ENGINE_BENCHMARK_ %.o: %.c .make-prerequisites $(SERVER_CC) -MMD -o $@ -c $< +unit/%.o: unit/%.c .make-prerequisites + $(SERVER_CC) -MMD -o $@ -c $< + # The following files are checked in and don't normally need to be rebuilt. They # are built only if python is available and their prereqs are modified. ifneq (,$(PYTHON)) @@ -485,12 +514,17 @@ fmtargs.h: ../utils/generate-fmtargs.py $(QUITE_GEN)sed '/Everything below this line/,$$d' $@ > $@.tmp $(QUITE_GEN)$(PYTHON) ../utils/generate-fmtargs.py >> $@.tmp $(QUITE_GEN)mv $@.tmp $@ + +unit/test_files.h: unit/*.c ../utils/generate-unit-test-header.py + $(QUIET_GEN)$(PYTHON) ../utils/generate-unit-test-header.py + +unit/test_main.o: unit/test_files.h endif commands.c: $(COMMANDS_DEF_FILENAME).def clean: - rm -rf $(SERVER_NAME) $(ENGINE_SENTINEL_NAME) $(ENGINE_CLI_NAME) $(ENGINE_BENCHMARK_NAME) $(ENGINE_CHECK_RDB_NAME) $(ENGINE_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov valkey.info lcov-html Makefile.dep *.so + rm -rf $(SERVER_NAME) $(ENGINE_SENTINEL_NAME) $(ENGINE_CLI_NAME) $(ENGINE_BENCHMARK_NAME) $(ENGINE_CHECK_RDB_NAME) $(ENGINE_CHECK_AOF_NAME) $(ENGINE_UNIT_TESTS) $(ENGINE_LIB_NAME) unit/*.o unit/*.d *.o *.gcda *.gcno *.gcov valkey.info lcov-html Makefile.dep *.so rm -f $(DEP) .PHONY: clean @@ -506,6 +540,9 @@ distclean: clean test: $(SERVER_NAME) $(ENGINE_CHECK_AOF_NAME) $(ENGINE_CLI_NAME) $(ENGINE_BENCHMARK_NAME) @(cd ..; ./runtest) +test-unit: $(ENGINE_UNIT_TESTS) + ./$(ENGINE_UNIT_TESTS) + test-modules: $(SERVER_NAME) @(cd ..; ./runtest-moduleapi) @@ -533,7 +570,7 @@ bench: $(ENGINE_BENCHMARK_NAME) @echo "" @echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386" @echo "" - $(MAKE) CFLAGS="-m32" LDFLAGS="-m32" + $(MAKE) all-with-unit-tests CFLAGS="-m32" LDFLAGS="-m32" gcov: $(MAKE) SERVER_CFLAGS="-fprofile-arcs -ftest-coverage -DCOVERAGE_TEST" SERVER_LDFLAGS="-fprofile-arcs -ftest-coverage" diff --git a/src/crc64.c b/src/crc64.c index 9d4e98ee7..97b28250a 100644 --- a/src/crc64.c +++ b/src/crc64.c @@ -141,226 +141,3 @@ void crc64_init(void) { uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) { return crcspeed64native(crc64_table, crc, (void *) s, l); } - -/* Test main */ -#ifdef SERVER_TEST -#include - -static void genBenchmarkRandomData(char *data, int count); -static int bench_crc64(unsigned char *data, uint64_t size, long long passes, uint64_t check, char *name, int csv); -static void bench_combine(char *label, uint64_t size, uint64_t expect, int csv); -long long _ustime(void); - -#include -#include -#include -#include -#include -#include - -#include "zmalloc.h" -#include "crccombine.h" - -long long _ustime(void) { - struct timeval tv; - long long ust; - - gettimeofday(&tv, NULL); - ust = ((long long)tv.tv_sec)*1000000; - ust += tv.tv_usec; - return ust; -} - -static int bench_crc64(unsigned char *data, uint64_t size, long long passes, uint64_t check, char *name, int csv) { - uint64_t min = size, hash; - long long original_start = _ustime(), original_end; - for (long long i=passes; i > 0; i--) { - hash = crc64(0, data, size); - } - original_end = _ustime(); - min = (original_end - original_start) * 1000 / passes; - /* approximate nanoseconds without nstime */ - if (csv) { - printf("%s,%" PRIu64 ",%" PRIu64 ",%d\n", - name, size, (1000 * size) / min, hash == check); - } else { - printf("test size=%" PRIu64 " algorithm=%s %" PRIu64 " M/sec matches=%d\n", - size, name, (1000 * size) / min, hash == check); - } - return hash != check; -} - -const uint64_t BENCH_RPOLY = UINT64_C(0x95ac9329ac4bc9b5); - -static void bench_combine(char *label, uint64_t size, uint64_t expect, int csv) { - uint64_t min = size, start = expect, thash = expect ^ (expect >> 17); - long long original_start = _ustime(), original_end; - for (int i=0; i < 1000; i++) { - crc64_combine(thash, start, size, BENCH_RPOLY, 64); - } - original_end = _ustime(); - /* ran 1000 times, want ns per, counted us per 1000 ... */ - min = original_end - original_start; - if (csv) { - printf("%s,%" PRIu64 ",%" PRIu64 "\n", label, size, min); - } else { - printf("%s size=%" PRIu64 " in %" PRIu64 " nsec\n", label, size, min); - } -} - -static void genBenchmarkRandomData(char *data, int count) { - static uint32_t state = 1234; - int i = 0; - - while (count--) { - state = (state*1103515245+12345); - data[i++] = '0'+((state>>16)&63); - } -} - -#define UNUSED(x) (void)(x) -int crc64Test(int argc, char *argv[], int flags) { - UNUSED(flags); - - uint64_t crc64_test_size = 0; - int i, lastarg, csv = 0, loop = 0, combine = 0; -again: - for (i = 3; i < argc; i++) { - lastarg = (i == (argc-1)); - if (!strcmp(argv[i],"--help")) { - goto usage; - } else if (!strcmp(argv[i],"--csv")) { - csv = 1; - } else if (!strcmp(argv[i],"-l")) { - loop = 1; - } else if (!strcmp(argv[i],"--crc")) { - if (lastarg) goto invalid; - crc64_test_size = atoll(argv[++i]); - } else if (!strcmp(argv[i],"--combine")) { - combine = 1; - } else { -invalid: - printf("Invalid option \"%s\" or option argument missing\n\n",argv[i]); -usage: - printf( -"Usage: crc64 [OPTIONS]\n\n" -" --csv Output in CSV format\n" -" -l Loop. Run the tests forever\n" -" --crc Benchmark crc64 faster options, using a buffer this big, and quit when done.\n" -" --combine Benchmark crc64 combine value ranges and timings.\n" - ); - return 1; - } - } - - if (crc64_test_size == 0 && combine == 0) { - crc64_init(); - printf("[calcula]: e9c6d914c4b8d9ca == %016" PRIx64 "\n", - (uint64_t)_crc64(0, "123456789", 9)); - printf("[64speed]: e9c6d914c4b8d9ca == %016" PRIx64 "\n", - (uint64_t)crc64(0, (unsigned char*)"123456789", 9)); - char li[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed " - "do eiusmod tempor incididunt ut labore et dolore magna " - "aliqua. Ut enim ad minim veniam, quis nostrud exercitation " - "ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis " - "aute irure dolor in reprehenderit in voluptate velit esse " - "cillum dolore eu fugiat nulla pariatur. Excepteur sint " - "occaecat cupidatat non proident, sunt in culpa qui officia " - "deserunt mollit anim id est laborum."; - printf("[calcula]: c7794709e69683b3 == %016" PRIx64 "\n", - (uint64_t)_crc64(0, li, sizeof(li))); - printf("[64speed]: c7794709e69683b3 == %016" PRIx64 "\n", - (uint64_t)crc64(0, (unsigned char*)li, sizeof(li))); - return 0; - - } - - int init_this_loop = 1; - long long init_start, init_end; - - do { - unsigned char* data = NULL; - uint64_t passes = 0; - if (crc64_test_size) { - data = zmalloc(crc64_test_size); - genBenchmarkRandomData((char*)data, crc64_test_size); - /* We want to hash about 1 gig of data in total, looped, to get a good - * idea of our performance. - */ - passes = (UINT64_C(0x100000000) / crc64_test_size); - passes = passes >= 2 ? passes : 2; - passes = passes <= 1000 ? passes : 1000; - } - - crc64_init(); - /* warm up the cache */ - set_crc64_cutoffs(crc64_test_size+1, crc64_test_size+1); - uint64_t expect = crc64(0, data, crc64_test_size); - - if (!combine && crc64_test_size) { - if (csv && init_this_loop) printf("algorithm,buffer,performance,crc64_matches\n"); - - /* get the single-character version for single-byte Redis behavior */ - set_crc64_cutoffs(0, crc64_test_size+1); - if (bench_crc64(data, crc64_test_size, passes, expect, "crc_1byte", csv)) return 1; - - set_crc64_cutoffs(crc64_test_size+1, crc64_test_size+1); - /* run with 8-byte "single" path, crcfaster */ - if (bench_crc64(data, crc64_test_size, passes, expect, "crcspeed", csv)) return 1; - - /* run with dual 8-byte paths */ - set_crc64_cutoffs(1, crc64_test_size+1); - if (bench_crc64(data, crc64_test_size, passes, expect, "crcdual", csv)) return 1; - - /* run with tri 8-byte paths */ - set_crc64_cutoffs(1, 1); - if (bench_crc64(data, crc64_test_size, passes, expect, "crctri", csv)) return 1; - - /* Be free memory region, be free. */ - zfree(data); - data = NULL; - } - - uint64_t INIT_SIZE = UINT64_C(0xffffffffffffffff); - if (combine) { - if (init_this_loop) { - init_start = _ustime(); - crc64_combine( - UINT64_C(0xdeadbeefdeadbeef), - UINT64_C(0xfeebdaedfeebdaed), - INIT_SIZE, - BENCH_RPOLY, 64); - init_end = _ustime(); - - init_end -= init_start; - init_end *= 1000; - if (csv) { - printf("operation,size,nanoseconds\n"); - printf("init_64,%" PRIu64 ",%" PRIu64 "\n", INIT_SIZE, (uint64_t)init_end); - } else { - printf("init_64 size=%" PRIu64 " in %" PRIu64 " nsec\n", INIT_SIZE, (uint64_t)init_end); - } - /* use the hash itself as the size (unpredictable) */ - bench_combine("hash_as_size_combine", crc64_test_size, expect, csv); - - /* let's do something big (predictable, so fast) */ - bench_combine("largest_combine", INIT_SIZE, expect, csv); - } - bench_combine("combine", crc64_test_size, expect, csv); - } - init_this_loop = 0; - /* step down by ~1.641 for a range of test sizes */ - crc64_test_size -= (crc64_test_size >> 2) + (crc64_test_size >> 3) + (crc64_test_size >> 6); - } while (crc64_test_size > 3); - if (loop) goto again; - return 0; -} -# endif - - -#ifdef SERVER_TEST_MAIN -int main(int argc, char *argv[]) { - return crc64Test(argc, argv); -} - -#endif diff --git a/src/crc64.h b/src/crc64.h index 3debc3295..34015655c 100644 --- a/src/crc64.h +++ b/src/crc64.h @@ -6,8 +6,4 @@ void crc64_init(void); uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l); -#ifdef SERVER_TEST -int crc64Test(int argc, char *argv[], int flags); -#endif - #endif diff --git a/src/intset.c b/src/intset.c index fea4173b4..058d0bfb3 100644 --- a/src/intset.c +++ b/src/intset.c @@ -341,220 +341,3 @@ int intsetValidateIntegrity(const unsigned char *p, size_t size, int deep) { return 1; } - -#ifdef SERVER_TEST -#include -#include - -#if 0 -static void intsetRepr(intset *is) { - for (uint32_t i = 0; i < intrev32ifbe(is->length); i++) { - printf("%lld\n", (uint64_t)_intsetGet(is,i)); - } - printf("\n"); -} - -static void error(char *err) { - printf("%s\n", err); - exit(1); -} -#endif - -static void ok(void) { - printf("OK\n"); -} - -static long long usec(void) { - struct timeval tv; - gettimeofday(&tv,NULL); - return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; -} - -static intset *createSet(int bits, int size) { - uint64_t mask = (1< 32) { - value = (rand()*rand()) & mask; - } else { - value = rand() & mask; - } - is = intsetAdd(is,value,NULL); - } - return is; -} - -static void checkConsistency(intset *is) { - for (uint32_t i = 0; i < (intrev32ifbe(is->length)-1); i++) { - uint32_t encoding = intrev32ifbe(is->encoding); - - if (encoding == INTSET_ENC_INT16) { - int16_t *i16 = (int16_t*)is->contents; - assert(i16[i] < i16[i+1]); - } else if (encoding == INTSET_ENC_INT32) { - int32_t *i32 = (int32_t*)is->contents; - assert(i32[i] < i32[i+1]); - } else { - int64_t *i64 = (int64_t*)is->contents; - assert(i64[i] < i64[i+1]); - } - } -} - -#define UNUSED(x) (void)(x) -int intsetTest(int argc, char **argv, int flags) { - uint8_t success; - int i; - intset *is; - srand(time(NULL)); - - UNUSED(argc); - UNUSED(argv); - UNUSED(flags); - - printf("Value encodings: "); { - assert(_intsetValueEncoding(-32768) == INTSET_ENC_INT16); - assert(_intsetValueEncoding(+32767) == INTSET_ENC_INT16); - assert(_intsetValueEncoding(-32769) == INTSET_ENC_INT32); - assert(_intsetValueEncoding(+32768) == INTSET_ENC_INT32); - assert(_intsetValueEncoding(-2147483648) == INTSET_ENC_INT32); - assert(_intsetValueEncoding(+2147483647) == INTSET_ENC_INT32); - assert(_intsetValueEncoding(-2147483649) == INTSET_ENC_INT64); - assert(_intsetValueEncoding(+2147483648) == INTSET_ENC_INT64); - assert(_intsetValueEncoding(-9223372036854775808ull) == - INTSET_ENC_INT64); - assert(_intsetValueEncoding(+9223372036854775807ull) == - INTSET_ENC_INT64); - ok(); - } - - printf("Basic adding: "); { - is = intsetNew(); - is = intsetAdd(is,5,&success); assert(success); - is = intsetAdd(is,6,&success); assert(success); - is = intsetAdd(is,4,&success); assert(success); - is = intsetAdd(is,4,&success); assert(!success); - assert(6 == intsetMax(is)); - assert(4 == intsetMin(is)); - ok(); - zfree(is); - } - - printf("Large number of random adds: "); { - uint32_t inserts = 0; - is = intsetNew(); - for (i = 0; i < 1024; i++) { - is = intsetAdd(is,rand()%0x800,&success); - if (success) inserts++; - } - assert(intrev32ifbe(is->length) == inserts); - checkConsistency(is); - ok(); - zfree(is); - } - - printf("Upgrade from int16 to int32: "); { - is = intsetNew(); - is = intsetAdd(is,32,NULL); - assert(intrev32ifbe(is->encoding) == INTSET_ENC_INT16); - is = intsetAdd(is,65535,NULL); - assert(intrev32ifbe(is->encoding) == INTSET_ENC_INT32); - assert(intsetFind(is,32)); - assert(intsetFind(is,65535)); - checkConsistency(is); - zfree(is); - - is = intsetNew(); - is = intsetAdd(is,32,NULL); - assert(intrev32ifbe(is->encoding) == INTSET_ENC_INT16); - is = intsetAdd(is,-65535,NULL); - assert(intrev32ifbe(is->encoding) == INTSET_ENC_INT32); - assert(intsetFind(is,32)); - assert(intsetFind(is,-65535)); - checkConsistency(is); - ok(); - zfree(is); - } - - printf("Upgrade from int16 to int64: "); { - is = intsetNew(); - is = intsetAdd(is,32,NULL); - assert(intrev32ifbe(is->encoding) == INTSET_ENC_INT16); - is = intsetAdd(is,4294967295,NULL); - assert(intrev32ifbe(is->encoding) == INTSET_ENC_INT64); - assert(intsetFind(is,32)); - assert(intsetFind(is,4294967295)); - checkConsistency(is); - zfree(is); - - is = intsetNew(); - is = intsetAdd(is,32,NULL); - assert(intrev32ifbe(is->encoding) == INTSET_ENC_INT16); - is = intsetAdd(is,-4294967295,NULL); - assert(intrev32ifbe(is->encoding) == INTSET_ENC_INT64); - assert(intsetFind(is,32)); - assert(intsetFind(is,-4294967295)); - checkConsistency(is); - ok(); - zfree(is); - } - - printf("Upgrade from int32 to int64: "); { - is = intsetNew(); - is = intsetAdd(is,65535,NULL); - assert(intrev32ifbe(is->encoding) == INTSET_ENC_INT32); - is = intsetAdd(is,4294967295,NULL); - assert(intrev32ifbe(is->encoding) == INTSET_ENC_INT64); - assert(intsetFind(is,65535)); - assert(intsetFind(is,4294967295)); - checkConsistency(is); - zfree(is); - - is = intsetNew(); - is = intsetAdd(is,65535,NULL); - assert(intrev32ifbe(is->encoding) == INTSET_ENC_INT32); - is = intsetAdd(is,-4294967295,NULL); - assert(intrev32ifbe(is->encoding) == INTSET_ENC_INT64); - assert(intsetFind(is,65535)); - assert(intsetFind(is,-4294967295)); - checkConsistency(is); - ok(); - zfree(is); - } - - printf("Stress lookups: "); { - long num = 100000, size = 10000; - int i, bits = 20; - long long start; - is = createSet(bits,size); - checkConsistency(is); - - start = usec(); - for (i = 0; i < num; i++) intsetSearch(is,rand() % ((1<(int argc, char *argv[], int flags) {`, where test_name is the name of the test. +The test name must be globally unique. +A test will be marked as successful if returns 0, and will be marked failed in all other cases. + +The test framework also parses several flags passed in, and sets them based on the arguments to the tests. + +Tests flags: +* UNIT_TEST_ACCURATE: Corresponds to the --accurate flag. This flag indicates the test should use extra computation to more accurately validate the tests. +* UNIT_TEST_LARGE_MEMORY: Corresponds to the --large-memory flag. This flag indicates whether or not tests should use more than 100mb of memory. +* UNIT_TEST_SINGLE: Corresponds to the --single flag. This flag indicates that a single test is being executed. + +Tests are allowed to be passed in additional arbitrary argv/argc, which they can access from the argc and argv arguments of the test. + +## Assertions + +There are a few built in assertions that can be used, that will automatically return from the current function and return the correct error code. +Assertions are also useful as they will print out the line number that they failed on. + +* `TEST_ASSERT(condition)`: Will evaluate the condition, and if it fails it will return 1 and print out the condition that failed. +* `TEST_ASSERT_MESSAGE(message, condition`): Will evaluate the condition, and if it fails it will return 1 and print out the provided message. + +## Other utilities + +If you would like to print out additional data, use the `TEST_PRINT_INFO(info, ...)` option, which has arguments similar to printf. +This macro will also print out the function the code was executed from in addition to the line it was printed from. + +## Example test + +``` +int test_example(int argc, char *argv[], int flags) { + TEST_ASSERT(5 == 5); + TEST_ASSERT_MESSAGE("This should pass", 6 == 6); + return 0; +} +``` + +## Running tests +Tests can be run by executing: + +``` +make valkey-unit-tests +./valkey-unit-tests +``` + +Running a single unit test file +``` +./valkey-unit-tests --single crc64.c +``` + +Will just run the crc64.c file. \ No newline at end of file diff --git a/src/unit/test_crc64.c b/src/unit/test_crc64.c new file mode 100644 index 000000000..9489a2462 --- /dev/null +++ b/src/unit/test_crc64.c @@ -0,0 +1,30 @@ +#include +#include "../crc64.h" + +#include "test_help.h" + +extern uint64_t _crc64(uint_fast64_t crc, const void *in_data, const uint64_t len); + +int test_crc64(int argc, char **argv, int flags) { + UNUSED(argc); + UNUSED(argv); + UNUSED(flags); + crc64_init(); + + unsigned char numbers[] = "123456789"; + TEST_ASSERT_MESSAGE("[calcula]: CRC64 '123456789'", (uint64_t)_crc64(0, numbers, 9) == 16845390139448941002ull); + TEST_ASSERT_MESSAGE("[calcula]: CRC64 '123456789'", (uint64_t)crc64(0, numbers, 9) == 16845390139448941002ull); + + unsigned char li[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed " + "do eiusmod tempor incididunt ut labore et dolore magna " + "aliqua. Ut enim ad minim veniam, quis nostrud exercitation " + "ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis " + "aute irure dolor in reprehenderit in voluptate velit esse " + "cillum dolore eu fugiat nulla pariatur. Excepteur sint " + "occaecat cupidatat non proident, sunt in culpa qui officia " + "deserunt mollit anim id est laborum."; + + TEST_ASSERT_MESSAGE("[calcula]: CRC64 TEXT'", (uint64_t)_crc64(0, li, sizeof(li)) == 14373597793578550195ull); + TEST_ASSERT_MESSAGE("[calcula]: CRC64 TEXT", (uint64_t)crc64(0, li, sizeof(li)) == 14373597793578550195ull); + return 0; +} diff --git a/src/unit/test_crc64combine.c b/src/unit/test_crc64combine.c new file mode 100644 index 000000000..ed5c39d97 --- /dev/null +++ b/src/unit/test_crc64combine.c @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include +#include + +#include "test_help.h" +#include "../zmalloc.h" +#include "../crc64.h" +#include "../crcspeed.h" +#include "../crccombine.h" + +static void genBenchmarkRandomData(char *data, int count); +static int bench_crc64(unsigned char *data, uint64_t size, long long passes, uint64_t check, char *name, int csv); +static void bench_combine(char *label, uint64_t size, uint64_t expect, int csv); + +long long _ustime(void) { + struct timeval tv; + long long ust; + + gettimeofday(&tv, NULL); + ust = ((long long)tv.tv_sec)*1000000; + ust += tv.tv_usec; + return ust; +} + +static int bench_crc64(unsigned char *data, uint64_t size, long long passes, uint64_t check, char *name, int csv) { + uint64_t min = size, hash; + long long original_start = _ustime(), original_end; + for (long long i=passes; i > 0; i--) { + hash = crc64(0, data, size); + } + original_end = _ustime(); + min = (original_end - original_start) * 1000 / passes; + /* approximate nanoseconds without nstime */ + if (csv) { + printf("%s,%" PRIu64 ",%" PRIu64 ",%d\n", + name, size, (1000 * size) / min, hash == check); + } else { + TEST_PRINT_INFO("test size=%" PRIu64 " algorithm=%s %" PRIu64 " M/sec matches=%d", + size, name, (1000 * size) / min, hash == check); + } + return hash != check; +} + +const uint64_t BENCH_RPOLY = UINT64_C(0x95ac9329ac4bc9b5); + +static void bench_combine(char *label, uint64_t size, uint64_t expect, int csv) { + uint64_t min = size, start = expect, thash = expect ^ (expect >> 17); + long long original_start = _ustime(), original_end; + for (int i=0; i < 1000; i++) { + crc64_combine(thash, start, size, BENCH_RPOLY, 64); + } + original_end = _ustime(); + /* ran 1000 times, want ns per, counted us per 1000 ... */ + min = original_end - original_start; + if (csv) { + printf("%s,%" PRIu64 ",%" PRIu64 "\n", label, size, min); + } else { + printf("%s size=%" PRIu64 " in %" PRIu64 " nsec\n", label, size, min); + } +} + +static void genBenchmarkRandomData(char *data, int count) { + static uint32_t state = 1234; + int i = 0; + + while (count--) { + state = (state*1103515245+12345); + data[i++] = '0'+((state>>16)&63); + } +} + +/* This is a special unit test useful for benchmarking crc64combine performance. The + * benchmarking is only done when the tests are invoked with a single test target, + * like 'valkey-unit-tests --single test_crc64combine.c --crc 16384'. */ +int test_crc64combine(int argc, char **argv, int flags) { + if (!(flags & UNIT_TEST_SINGLE)) { + return 0; + } + + uint64_t crc64_test_size = 0; + int i, lastarg, csv = 0, loop = 0, combine = 0; +again: + for (i = 3; i < argc; i++) { + lastarg = (i == (argc-1)); + if (!strcmp(argv[i],"--help")) { + goto usage; + } else if (!strcmp(argv[i],"--csv")) { + csv = 1; + } else if (!strcmp(argv[i],"-l")) { + loop = 1; + } else if (!strcmp(argv[i],"--crc")) { + if (lastarg) goto invalid; + crc64_test_size = atoll(argv[++i]); + } else if (!strcmp(argv[i],"--combine")) { + combine = 1; + } else { +invalid: + printf("Invalid option \"%s\" or option argument missing\n\n",argv[i]); +usage: + printf( +"Usage: --single test_crc64combine.c [OPTIONS]\n\n" +" --csv Output in CSV format\n" +" -l Loop. Run the tests forever\n" +" --crc Benchmark crc64 faster options, using a buffer this big, and quit when done.\n" +" --combine Benchmark crc64 combine value ranges and timings.\n" + ); + return 1; + } + } + + int init_this_loop = 1; + long long init_start, init_end; + + do { + unsigned char* data = NULL; + uint64_t passes = 0; + if (crc64_test_size) { + data = zmalloc(crc64_test_size); + genBenchmarkRandomData((char*)data, crc64_test_size); + /* We want to hash about 1 gig of data in total, looped, to get a good + * idea of our performance. + */ + passes = (UINT64_C(0x100000000) / crc64_test_size); + passes = passes >= 2 ? passes : 2; + passes = passes <= 1000 ? passes : 1000; + } + + crc64_init(); + /* warm up the cache */ + set_crc64_cutoffs(crc64_test_size+1, crc64_test_size+1); + uint64_t expect = crc64(0, data, crc64_test_size); + + if (!combine && crc64_test_size) { + if (csv && init_this_loop) printf("algorithm,buffer,performance,crc64_matches\n"); + + /* get the single-character version for single-byte Redis behavior */ + set_crc64_cutoffs(0, crc64_test_size+1); + if (bench_crc64(data, crc64_test_size, passes, expect, "crc_1byte", csv)) return 1; + + set_crc64_cutoffs(crc64_test_size+1, crc64_test_size+1); + /* run with 8-byte "single" path, crcfaster */ + if (bench_crc64(data, crc64_test_size, passes, expect, "crcspeed", csv)) return 1; + + /* run with dual 8-byte paths */ + set_crc64_cutoffs(1, crc64_test_size+1); + if (bench_crc64(data, crc64_test_size, passes, expect, "crcdual", csv)) return 1; + + /* run with tri 8-byte paths */ + set_crc64_cutoffs(1, 1); + if (bench_crc64(data, crc64_test_size, passes, expect, "crctri", csv)) return 1; + + /* Be free memory region, be free. */ + zfree(data); + data = NULL; + } + + uint64_t INIT_SIZE = UINT64_C(0xffffffffffffffff); + if (combine) { + if (init_this_loop) { + init_start = _ustime(); + crc64_combine( + UINT64_C(0xdeadbeefdeadbeef), + UINT64_C(0xfeebdaedfeebdaed), + INIT_SIZE, + BENCH_RPOLY, 64); + init_end = _ustime(); + + init_end -= init_start; + init_end *= 1000; + if (csv) { + printf("operation,size,nanoseconds\n"); + printf("init_64,%" PRIu64 ",%" PRIu64 "\n", INIT_SIZE, (uint64_t)init_end); + } else { + TEST_PRINT_INFO("init_64 size=%" PRIu64 " in %" PRIu64 " nsec", INIT_SIZE, (uint64_t)init_end); + } + /* use the hash itself as the size (unpredictable) */ + bench_combine("hash_as_size_combine", crc64_test_size, expect, csv); + + /* let's do something big (predictable, so fast) */ + bench_combine("largest_combine", INIT_SIZE, expect, csv); + } + bench_combine("combine", crc64_test_size, expect, csv); + } + init_this_loop = 0; + /* step down by ~1.641 for a range of test sizes */ + crc64_test_size -= (crc64_test_size >> 2) + (crc64_test_size >> 3) + (crc64_test_size >> 6); + } while (crc64_test_size > 3); + if (loop) goto again; + return 0; +} diff --git a/src/unit/test_files.h b/src/unit/test_files.h new file mode 100644 index 000000000..ab2f2f626 --- /dev/null +++ b/src/unit/test_files.h @@ -0,0 +1,31 @@ +/* Do not modify this file, it's automatically generated from utils/generate-unit-test-header.py */ +typedef int unitTestProc(int argc, char **argv, int flags); + +typedef struct unitTest { + char *name; + unitTestProc *proc; +} unitTest; + +int test_crc64(int argc, char **argv, int flags); +int test_crc64combine(int argc, char **argv, int flags); +int test_intsetValueEncodings(int argc, char **argv, int flags); +int test_intsetBasicAdding(int argc, char **argv, int flags); +int test_intsetLargeNumberRandomAdd(int argc, char **argv, int flags); +int test_intsetUpgradeFromint16Toint32(int argc, char **argv, int flags); +int test_intsetUpgradeFromint16Toint64(int argc, char **argv, int flags); +int test_intsetUpgradeFromint32Toint64(int argc, char **argv, int flags); +int test_intsetStressLookups(int argc, char **argv, int flags); +int test_intsetStressAddDelete(int argc, char **argv, int flags); + +unitTest __test_crc64_c[] = {{"test_crc64", test_crc64}, {NULL, NULL}}; +unitTest __test_crc64combine_c[] = {{"test_crc64combine", test_crc64combine}, {NULL, NULL}}; +unitTest __test_intset_c[] = {{"test_intsetValueEncodings", test_intsetValueEncodings}, {"test_intsetBasicAdding", test_intsetBasicAdding}, {"test_intsetLargeNumberRandomAdd", test_intsetLargeNumberRandomAdd}, {"test_intsetUpgradeFromint16Toint32", test_intsetUpgradeFromint16Toint32}, {"test_intsetUpgradeFromint16Toint64", test_intsetUpgradeFromint16Toint64}, {"test_intsetUpgradeFromint32Toint64", test_intsetUpgradeFromint32Toint64}, {"test_intsetStressLookups", test_intsetStressLookups}, {"test_intsetStressAddDelete", test_intsetStressAddDelete}, {NULL, NULL}}; + +struct unitTestSuite { + char *filename; + unitTest *tests; +} unitTestSuite[] = { + {"test_crc64.c", __test_crc64_c}, + {"test_crc64combine.c", __test_crc64combine_c}, + {"test_intset.c", __test_intset_c}, +}; diff --git a/src/unit/test_help.h b/src/unit/test_help.h new file mode 100644 index 000000000..6fc5b4c37 --- /dev/null +++ b/src/unit/test_help.h @@ -0,0 +1,48 @@ +/* A very simple test framework for valkey. See unit/README.me for more information on usage. + * + * Example: + * + * int test_example(int argc, char *argv[], int flags) { + * TEST_ASSERT_MESSAGE("Check if 1 == 1", 1==1); + * TEST_ASSERT(5 == 5); + * return 0; + * } + */ + +#ifndef __TESTHELP_H +#define __TESTHELP_H + +#include +#include + +/* The flags are the following: +* --accurate: Runs tests with more iterations. +* --large-memory: Enables tests that consume more than 100mb. +* --single: A flag to indicate a specific test file was executed. */ +#define UNIT_TEST_ACCURATE (1<<0) +#define UNIT_TEST_LARGE_MEMORY (1<<1) +#define UNIT_TEST_SINGLE (1<<2) + +#define KRED "\33[31m" +#define KGRN "\33[32m" +#define KBLUE "\33[34m" +#define KRESET "\33[0m" + +#define TEST_PRINT_ERROR(descr) \ + printf("[" KRED "%s - %s:%d" KRESET "] %s\n", __func__, __FILE__, __LINE__, descr) + +#define TEST_PRINT_INFO(descr, ...) \ + printf("[" KBLUE "%s - %s:%d" KRESET "] " descr "\n", __func__, __FILE__, __LINE__, __VA_ARGS__) + +#define TEST_ASSERT_MESSAGE(descr, _c) do { \ + if (!(_c)) { \ + TEST_PRINT_ERROR(descr); \ + return 1; \ + } \ +} while(0) + +#define TEST_ASSERT(_c) TEST_ASSERT_MESSAGE("Failed assertion: " #_c, _c) + +#define UNUSED(x) (void)(x) + +#endif diff --git a/src/unit/test_intset.c b/src/unit/test_intset.c new file mode 100644 index 000000000..9e23101df --- /dev/null +++ b/src/unit/test_intset.c @@ -0,0 +1,229 @@ +#include +#include + +#include "../intset.c" +#include "test_help.h" + +static long long usec(void) { + struct timeval tv; + gettimeofday(&tv,NULL); + return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; +} + +static intset *createSet(int bits, int size) { + uint64_t mask = (1< 32) { + value = (rand()*rand()) & mask; + } else { + value = rand() & mask; + } + is = intsetAdd(is,value,NULL); + } + return is; +} + +static int checkConsistency(intset *is) { + for (uint32_t i = 0; i < (intrev32ifbe(is->length)-1); i++) { + uint32_t encoding = intrev32ifbe(is->encoding); + + if (encoding == INTSET_ENC_INT16) { + int16_t *i16 = (int16_t*)is->contents; + TEST_ASSERT(i16[i] < i16[i+1]); + } else if (encoding == INTSET_ENC_INT32) { + int32_t *i32 = (int32_t*)is->contents; + TEST_ASSERT(i32[i] < i32[i+1]); + } else { + int64_t *i64 = (int64_t*)is->contents; + TEST_ASSERT(i64[i] < i64[i+1]); + } + } + return 1; +} + +int test_intsetValueEncodings(int argc, char **argv, int flags) { + UNUSED(argc); + UNUSED(argv); + UNUSED(flags); + + TEST_ASSERT(_intsetValueEncoding(-32768) == INTSET_ENC_INT16); + TEST_ASSERT(_intsetValueEncoding(+32767) == INTSET_ENC_INT16); + TEST_ASSERT(_intsetValueEncoding(-32769) == INTSET_ENC_INT32); + TEST_ASSERT(_intsetValueEncoding(+32768) == INTSET_ENC_INT32); + TEST_ASSERT(_intsetValueEncoding(-2147483648) == INTSET_ENC_INT32); + TEST_ASSERT(_intsetValueEncoding(+2147483647) == INTSET_ENC_INT32); + TEST_ASSERT(_intsetValueEncoding(-2147483649) == INTSET_ENC_INT64); + TEST_ASSERT(_intsetValueEncoding(+2147483648) == INTSET_ENC_INT64); + TEST_ASSERT(_intsetValueEncoding(-9223372036854775808ull) == + INTSET_ENC_INT64); + TEST_ASSERT(_intsetValueEncoding(+9223372036854775807ull) == + INTSET_ENC_INT64); + + return 0; +} + +int test_intsetBasicAdding(int argc, char **argv, int flags) { + UNUSED(argc); + UNUSED(argv); + UNUSED(flags); + + intset *is = intsetNew(); + uint8_t success; + is = intsetAdd(is,5,&success); TEST_ASSERT(success); + is = intsetAdd(is,6,&success); TEST_ASSERT(success); + is = intsetAdd(is,4,&success); TEST_ASSERT(success); + is = intsetAdd(is,4,&success); TEST_ASSERT(!success); + TEST_ASSERT(6 == intsetMax(is)); + TEST_ASSERT(4 == intsetMin(is)); + zfree(is); + + return 0; +} + +int test_intsetLargeNumberRandomAdd(int argc, char **argv, int flags) { + UNUSED(argc); + UNUSED(argv); + UNUSED(flags); + + uint32_t inserts = 0; + uint8_t success; + intset *is = intsetNew(); + for (int i = 0; i < 1024; i++) { + is = intsetAdd(is,rand()%0x800,&success); + if (success) inserts++; + } + TEST_ASSERT(intrev32ifbe(is->length) == inserts); + TEST_ASSERT(checkConsistency(is) == 1); + zfree(is); + return 0; +} + +int test_intsetUpgradeFromint16Toint32(int argc, char **argv, int flags) { + UNUSED(argc); + UNUSED(argv); + UNUSED(flags); + + intset *is = intsetNew(); + is = intsetAdd(is,32,NULL); + TEST_ASSERT(intrev32ifbe(is->encoding) == INTSET_ENC_INT16); + is = intsetAdd(is,65535,NULL); + TEST_ASSERT(intrev32ifbe(is->encoding) == INTSET_ENC_INT32); + TEST_ASSERT(intsetFind(is,32)); + TEST_ASSERT(intsetFind(is,65535)); + TEST_ASSERT(checkConsistency(is) == 1); + zfree(is); + + is = intsetNew(); + is = intsetAdd(is,32,NULL); + TEST_ASSERT(intrev32ifbe(is->encoding) == INTSET_ENC_INT16); + is = intsetAdd(is,-65535,NULL); + TEST_ASSERT(intrev32ifbe(is->encoding) == INTSET_ENC_INT32); + TEST_ASSERT(intsetFind(is,32)); + TEST_ASSERT(intsetFind(is,-65535)); + TEST_ASSERT(checkConsistency(is) == 1); + zfree(is); + + return 0; +} + +int test_intsetUpgradeFromint16Toint64(int argc, char **argv, int flags) { + UNUSED(argc); + UNUSED(argv); + UNUSED(flags); + + intset *is = intsetNew(); + is = intsetNew(); + is = intsetAdd(is,32,NULL); + TEST_ASSERT(intrev32ifbe(is->encoding) == INTSET_ENC_INT16); + is = intsetAdd(is,4294967295,NULL); + TEST_ASSERT(intrev32ifbe(is->encoding) == INTSET_ENC_INT64); + TEST_ASSERT(intsetFind(is,32)); + TEST_ASSERT(intsetFind(is,4294967295)); + TEST_ASSERT(checkConsistency(is) == 1); + zfree(is); + + is = intsetNew(); + is = intsetAdd(is,32,NULL); + TEST_ASSERT(intrev32ifbe(is->encoding) == INTSET_ENC_INT16); + is = intsetAdd(is,-4294967295,NULL); + TEST_ASSERT(intrev32ifbe(is->encoding) == INTSET_ENC_INT64); + TEST_ASSERT(intsetFind(is,32)); + TEST_ASSERT(intsetFind(is,-4294967295)); + TEST_ASSERT(checkConsistency(is) == 1); + zfree(is); + + return 0; +} + +int test_intsetUpgradeFromint32Toint64(int argc, char **argv, int flags) { + UNUSED(argc); + UNUSED(argv); + UNUSED(flags); + + intset *is = intsetNew(); + is = intsetAdd(is,65535,NULL); + TEST_ASSERT(intrev32ifbe(is->encoding) == INTSET_ENC_INT32); + is = intsetAdd(is,4294967295,NULL); + TEST_ASSERT(intrev32ifbe(is->encoding) == INTSET_ENC_INT64); + TEST_ASSERT(intsetFind(is,65535)); + TEST_ASSERT(intsetFind(is,4294967295)); + TEST_ASSERT(checkConsistency(is) == 1); + zfree(is); + + is = intsetNew(); + is = intsetAdd(is,65535,NULL); + TEST_ASSERT(intrev32ifbe(is->encoding) == INTSET_ENC_INT32); + is = intsetAdd(is,-4294967295,NULL); + TEST_ASSERT(intrev32ifbe(is->encoding) == INTSET_ENC_INT64); + TEST_ASSERT(intsetFind(is,65535)); + TEST_ASSERT(intsetFind(is,-4294967295)); + TEST_ASSERT(checkConsistency(is) == 1); + zfree(is); + + return 0; +} + +int test_intsetStressLookups(int argc, char **argv, int flags) { + UNUSED(argc); + UNUSED(argv); + UNUSED(flags); + + long num = 100000, size = 10000; + int i, bits = 20; + long long start; + intset *is = createSet(bits,size); + TEST_ASSERT(checkConsistency(is) == 1); + + start = usec(); + for (i = 0; i < num; i++) intsetSearch(is,rand() % ((1< +#include +#include "test_files.h" +#include "test_help.h" + +/* We override the default assertion mechanism, so that it prints out info and then dies. */ +void _serverAssert(const char *estr, const char *file, int line) { + printf("[" KRED "serverAssert - %s:%d" KRESET "] - %s\n", file, line, estr); + exit(1); +} + +/* Run the tests defined by the test suite. */ +int runTestSuite(struct unitTestSuite *test, int argc, char **argv, int flags) { + int test_num = 0; + int failed_tests = 0; + printf("[" KBLUE "START" KRESET "] - %s\n", test->filename); + + for (int id = 0; test->tests[id].proc != NULL; id++) { + test_num++; + int test_result = (test->tests[id].proc(argc, argv, flags) != 0); + if (!test_result) { + printf("[" KGRN "ok" KRESET "] - %s:%s\n", test->filename, test->tests[id].name); + } else { + printf("[" KRED "fail" KRESET "] - %s:%s\n", test->filename, test->tests[id].name); + failed_tests++; + } + } + + printf("[" KBLUE "END" KRESET "] - %s: ", test->filename); + printf("%d tests, %d passed, %d failed\n", test_num, + test_num - failed_tests, failed_tests); + return !failed_tests; +} + +int main(int argc, char **argv) { + int flags = 0; + char *file = NULL; + for (int j = 1; j < argc; j++) { + char *arg = argv[j]; + if (!strcasecmp(arg, "--accurate")) flags |= UNIT_TEST_ACCURATE; + else if (!strcasecmp(arg, "--large-memory")) flags |= UNIT_TEST_LARGE_MEMORY; + else if (!strcasecmp(arg, "--single") && (j + 1 < argc)) { + flags |= UNIT_TEST_SINGLE; + file = argv[j + 1]; + } + } + + int numtests = sizeof(unitTestSuite)/sizeof(struct unitTest); + int failed_num = 0, suites_executed = 0; + for (int j = 0; j < numtests; j++) { + if (file && strcasecmp(file, unitTestSuite[j].filename)) continue; + if (!runTestSuite(&unitTestSuite[j], argc, argv, flags)) { + failed_num++; + } + suites_executed++; + } + printf("%d test suites executed, %d passed, %d failed\n", suites_executed, + suites_executed-failed_num, failed_num); + + return failed_num == 0 ? 0 : 1; +} diff --git a/utils/generate-unit-test-header.py b/utils/generate-unit-test-header.py new file mode 100755 index 000000000..00cd852f2 --- /dev/null +++ b/utils/generate-unit-test-header.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +import os +import re + +UNIT_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__)) + '/../src/unit') +TEST_FILE = UNIT_DIR + '/test_files.h' +TEST_PROTOTYPE = '(int (test_[a-zA-Z0-9_]*)\(.*\)).*{' + +if __name__ == '__main__': + with open(TEST_FILE, 'w') as output: + # Find each test file and collect the test names. + test_suites = [] + for root, dirs, files in os.walk(UNIT_DIR): + for file in files: + file_path = UNIT_DIR + '/' + file + if not file.endswith('.c') or file == 'test_main.c': + continue + tests = [] + with open(file_path, 'r') as f: + for line in f: + match = re.match(TEST_PROTOTYPE, line) + if match: + function = match.group(1) + test_name = match.group(2) + tests.append((test_name, function)) + test_suites.append({'file': file, 'tests': tests}) + test_suites.sort(key=lambda test_suite: test_suite['file']) + output.write("""/* Do not modify this file, it's automatically generated from utils/generate-unit-test-header.py */ +typedef int unitTestProc(int argc, char **argv, int flags); + +typedef struct unitTest { + char *name; + unitTestProc *proc; +} unitTest; + +""") + + # Write the headers for the functions + for test_suite in test_suites: + for test in test_suite['tests']: + output.write('{};\n'.format(test[1])) + output.write("\n") + + # Create test suite lists + for test_suite in test_suites: + output.write('unitTest __{}[] = {{'.format(test_suite['file'].replace('.c', '_c'))) + for test in test_suite['tests']: + output.write('{{"{}", {}}}, '.format(test[0], test[0])) + output.write('{NULL, NULL}};\n') + + output.write(""" +struct unitTestSuite { + char *filename; + unitTest *tests; +} unitTestSuite[] = { +""") + for test_suite in test_suites: + output.write(' {{"{0}", __{1}}},\n'.format(test_suite['file'], test_suite['file'].replace('.c', '_c'))) + output.write('};\n') \ No newline at end of file