diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 137bd195c7..c94893320b 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 178d9b7ceb..f4726a420e 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 920e32eca7..8ed98aa326 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 833349839f..908aa695de 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 9d4e98ee70..97b28250a5 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 3debc3295a..34015655c1 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 fea4173b43..058d0bfb30 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 0000000000..9489a24625 --- /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 0000000000..ed5c39d97f --- /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 0000000000..ab2f2f6267 --- /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 0000000000..6fc5b4c378 --- /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 0000000000..9e23101dff --- /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 0000000000..00cd852f2d --- /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