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 <madelyneolson@gmail.com>
This commit is contained in:
Madelyn Olson 2024-05-02 20:00:04 -07:00 committed by GitHub
parent 443d80f168
commit 5b1fd222ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 779 additions and 457 deletions

View File

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

View File

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

2
.gitignore vendored
View File

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

View File

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

View File

@ -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 <stdio.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);
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#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 <bytes> 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

View File

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

View File

@ -341,220 +341,3 @@ int intsetValidateIntegrity(const unsigned char *p, size_t size, int deep) {
return 1;
}
#ifdef SERVER_TEST
#include <sys/time.h>
#include <time.h>
#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<<bits)-1;
uint64_t value;
intset *is = intsetNew();
for (int i = 0; i < size; i++) {
if (bits > 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<<bits)-1),NULL);
printf("%ld lookups, %ld element set, %lldusec\n",
num,size,usec()-start);
zfree(is);
}
printf("Stress add+delete: "); {
int i, v1, v2;
is = intsetNew();
for (i = 0; i < 0xffff; i++) {
v1 = rand() % 0xfff;
is = intsetAdd(is,v1,NULL);
assert(intsetFind(is,v1));
v2 = rand() % 0xfff;
is = intsetRemove(is,v2,NULL);
assert(!intsetFind(is,v2));
}
checkConsistency(is);
ok();
zfree(is);
}
return 0;
}
#endif

View File

@ -6894,12 +6894,10 @@ struct serverTest {
} serverTests[] = {
{"ziplist", ziplistTest},
{"quicklist", quicklistTest},
{"intset", intsetTest},
{"zipmap", zipmapTest},
{"sha1test", sha1Test},
{"util", utilTest},
{"endianconv", endianconvTest},
{"crc64", crc64Test},
{"zmalloc", zmalloc_test},
{"sds", sdsTest},
{"dict", dictTest},

54
src/unit/README.md Normal file
View File

@ -0,0 +1,54 @@
## Introduction
Valkey uses a very simple C testing framework, built up over time but now based loosely off of [Unity](https://www.throwtheswitch.org/unity).
All test files being test_ in the unit directory.
A single test file can have multiple individual tests, and they must be of the form `int test_<test_name>(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.

30
src/unit/test_crc64.c Normal file
View File

@ -0,0 +1,30 @@
#include <stdio.h>
#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;
}

View File

@ -0,0 +1,193 @@
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#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 <bytes> 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;
}

31
src/unit/test_files.h Normal file
View File

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

48
src/unit/test_help.h Normal file
View File

@ -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 <stdlib.h>
#include <stdio.h>
/* 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

229
src/unit/test_intset.c Normal file
View File

@ -0,0 +1,229 @@
#include <sys/time.h>
#include <time.h>
#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<<bits)-1;
uint64_t value;
intset *is = intsetNew();
for (int i = 0; i < size; i++) {
if (bits > 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<<bits)-1),NULL);
TEST_PRINT_INFO("%ld lookups, %ld element set, %lldusec\n",
num,size,usec()-start);
zfree(is);
return 0;
}
int test_intsetStressAddDelete(int argc, char **argv, int flags) {
UNUSED(argc);
UNUSED(argv);
UNUSED(flags);
int i, v1, v2;
intset *is = intsetNew();
for (i = 0; i < 0xffff; i++) {
v1 = rand() % 0xfff;
is = intsetAdd(is,v1,NULL);
TEST_ASSERT(intsetFind(is,v1));
v2 = rand() % 0xfff;
is = intsetRemove(is,v2,NULL);
TEST_ASSERT(!intsetFind(is,v2));
}
TEST_ASSERT(checkConsistency(is) == 1);
zfree(is);
return 0;
}

67
src/unit/test_main.c Normal file
View File

@ -0,0 +1,67 @@
/*
* Copyright Valkey contributors.
* All rights reserved.
* SPDX-License-Identifier: BSD 3-Clause
*/
#include <strings.h>
#include <stdio.h>
#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;
}

View File

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