mirror of https://github.com/valkey-io/valkey
Merge 14e25f258e into 981b8fe1bd
This commit is contained in:
commit
d29d5d43cb
|
|
@ -14,6 +14,7 @@ dump*.rdb
|
|||
*-sentinel
|
||||
*-server
|
||||
*-unit-tests
|
||||
*-unit-gtests
|
||||
doc-tools
|
||||
release
|
||||
misc/*
|
||||
|
|
@ -54,3 +55,5 @@ build-debug/
|
|||
build-release/
|
||||
cmake-build-debug/
|
||||
cmake-build-release/
|
||||
__pycache__
|
||||
src/gtest/.flags
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ endif ()
|
|||
|
||||
# Options
|
||||
option(BUILD_UNIT_TESTS "Build valkey-unit-tests" OFF)
|
||||
option(BUILD_UNIT_GTESTS "Build valkey-unit-gtests" OFF)
|
||||
option(BUILD_TEST_MODULES "Build all test modules" OFF)
|
||||
option(BUILD_EXAMPLE_MODULES "Build example modules" OFF)
|
||||
|
||||
|
|
@ -39,6 +40,7 @@ unset(CLANG CACHE)
|
|||
unset(BUILD_RDMA_MODULE CACHE)
|
||||
unset(BUILD_TLS_MODULE CACHE)
|
||||
unset(BUILD_UNIT_TESTS CACHE)
|
||||
unset(BUILD_UNIT_GTESTS CACHE)
|
||||
unset(BUILD_TEST_MODULES CACHE)
|
||||
unset(BUILD_EXAMPLE_MODULES CACHE)
|
||||
unset(USE_TLS CACHE)
|
||||
|
|
|
|||
|
|
@ -66,14 +66,14 @@ After building Valkey, it is a good idea to test it using:
|
|||
|
||||
The above runs the main integration tests. Additional tests are started using:
|
||||
|
||||
% make test-unit # Unit tests
|
||||
% make test-unit # Unit tests (both C unit tests and gtest unit tests)
|
||||
% make test-modules # Tests of the module API
|
||||
% make test-sentinel # Valkey Sentinel integration tests
|
||||
% make test-cluster # Valkey Cluster integration tests
|
||||
|
||||
More about running the integration tests can be found in
|
||||
[tests/README.md](tests/README.md) and for unit tests, see
|
||||
[src/unit/README.md](src/unit/README.md).
|
||||
[tests/README.md](tests/README.md), for unit tests, see
|
||||
[src/unit/README.md](src/unit/README.md) and [src/gtest/README.md](src/gtest/README.md).
|
||||
|
||||
## Fixing build problems with dependencies or cached build options
|
||||
|
||||
|
|
@ -315,6 +315,7 @@ Other options supported by Valkey's `CMake` build system:
|
|||
- `-DBUILD_MALLOC=<libc|jemalloc|tcmalloc|tcmalloc_minimal>` choose the allocator to use. Default on Linux: `jemalloc`, for other OS: `libc`
|
||||
- `-DBUILD_SANITIZER=<address|thread|undefined>` build with address sanitizer enabled. Default: disabled (no sanitizer)
|
||||
- `-DBUILD_UNIT_TESTS=[yes|no]` when set, the build will produce the executable `valkey-unit-tests`. Default: `no`
|
||||
- `-DBUILD_UNIT_GTESTS=[yes|no]` when set, the build will produce gtest unit tests executable `valkey-unit-gtests`. Default: `no`
|
||||
- `-DBUILD_TEST_MODULES=[yes|no]` when set, the build will include the modules located under the `tests/modules` folder. Default: `no`
|
||||
- `-DBUILD_EXAMPLE_MODULES=[yes|no]` when set, the build will include the example modules located under the `src/modules` folder. Default: `no`
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ distclean:
|
|||
-(cd hdr_histogram && $(MAKE) clean) > /dev/null || true
|
||||
-(cd fpconv && $(MAKE) clean) > /dev/null || true
|
||||
-(cd fast_float_c_interface && $(MAKE) clean) > /dev/null || true
|
||||
-(cd googletest && rm -rf build) > /dev/null || true
|
||||
-(rm -f .make-*)
|
||||
|
||||
.PHONY: distclean
|
||||
|
|
@ -128,3 +129,20 @@ fast_float_c_interface: .make-prerequisites
|
|||
cd fast_float_c_interface && $(MAKE)
|
||||
|
||||
.PHONY: fast_float_c_interface
|
||||
|
||||
gtest: .make-prerequisites
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||
@if [ ! -f googletest/CMakeLists.txt ]; then \
|
||||
echo "Downloading googletest..."; \
|
||||
rm -rf googletest; \
|
||||
git clone --depth 1 --branch v1.8.x https://github.com/google/googletest.git googletest; \
|
||||
fi
|
||||
@if [ ! -f gtest-parallel/gtest_parallel.py ]; then \
|
||||
echo "Downloading gtest-parallel..."; \
|
||||
rm -rf gtest-parallel; \
|
||||
git clone --depth 1 https://github.com/google/gtest-parallel.git gtest-parallel; \
|
||||
fi
|
||||
cd googletest && cmake3 -B build -DCMAKE_BUILD_TYPE=Release
|
||||
cd googletest && cmake3 --build build
|
||||
|
||||
.PHONY: gtest
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ should be provided by the operating system.
|
|||
* **linenoise** is a readline replacement. It is developed by the same authors of Valkey but is managed as a separated project and updated as needed.
|
||||
* **lua** is Lua 5.1 with minor changes for security and additional libraries.
|
||||
* **hdr_histogram** Used for per-command latency tracking histograms.
|
||||
* **fast_float** is a replacement for strtod to convert strings to floats efficiently.
|
||||
* **fast_float** is a replacement for strtod to convert strings to floats efficiently.
|
||||
* **googletest** is Google's testing framework used for gtest unit tests.
|
||||
* **gtest-parallel** is a script for running googletest tests in parallel.
|
||||
|
||||
How to upgrade the above dependencies
|
||||
===
|
||||
|
|
@ -121,3 +123,22 @@ To upgrade the library,
|
|||
2. cd fast_float
|
||||
3. Invoke "python3 ./script/amalgamate.py --output fast_float.h"
|
||||
4. Copy fast_float.h file to "deps/fast_float/".
|
||||
|
||||
googletest and gtest-parallel
|
||||
---
|
||||
|
||||
To upgrade googletest and gtest-parallel:
|
||||
|
||||
```sh
|
||||
# googletest (v1.8.x)
|
||||
rm -rf googletest
|
||||
git clone --branch v1.8.x --depth 1 https://github.com/google/googletest.git googletest
|
||||
rm -rf googletest/.git
|
||||
|
||||
# gtest-parallel (master)
|
||||
rm -rf gtest-parallel
|
||||
git clone --depth 1 https://github.com/google/gtest-parallel.git gtest-parallel
|
||||
rm -rf gtest-parallel/.git
|
||||
```
|
||||
|
||||
Commit the changes.
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit dea0216d0c6bc5e63cf5f6c8651cd268668032ec
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit cd488bdedc1d2cffb98201a17afc1b298b0b90f1
|
||||
|
|
@ -3,3 +3,4 @@
|
|||
*.gcov
|
||||
valkey.info
|
||||
lcov-html
|
||||
generated_*
|
||||
|
|
|
|||
|
|
@ -93,3 +93,7 @@ endif ()
|
|||
if (BUILD_UNIT_TESTS)
|
||||
add_subdirectory(unit)
|
||||
endif ()
|
||||
|
||||
if (BUILD_UNIT_GTESTS)
|
||||
add_subdirectory(gtest)
|
||||
endif ()
|
||||
|
|
|
|||
23
src/Makefile
23
src/Makefile
|
|
@ -31,7 +31,7 @@ endif
|
|||
ifneq ($(OPTIMIZATION),-O0)
|
||||
OPTIMIZATION+=-fno-omit-frame-pointer
|
||||
endif
|
||||
DEPENDENCY_TARGETS=libvalkey linenoise lua hdr_histogram fpconv
|
||||
DEPENDENCY_TARGETS=libvalkey linenoise lua hdr_histogram fpconv gtest
|
||||
NODEPS:=clean distclean
|
||||
|
||||
# Default settings
|
||||
|
|
@ -435,6 +435,7 @@ 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)
|
||||
ENGINE_UNIT_GTESTS:=$(ENGINE_NAME)-unit-gtests$(PROG_SUFFIX)
|
||||
ALL_SOURCES=$(sort $(patsubst %.o,%.c,$(ENGINE_SERVER_OBJ) $(ENGINE_CLI_OBJ) $(ENGINE_BENCHMARK_OBJ)))
|
||||
|
||||
USE_FAST_FLOAT?=no
|
||||
|
|
@ -462,7 +463,7 @@ endif
|
|||
|
||||
.PHONY: all
|
||||
|
||||
all-with-unit-tests: all $(ENGINE_UNIT_TESTS)
|
||||
all-with-unit-tests: all $(ENGINE_UNIT_TESTS) $(ENGINE_UNIT_GTESTS)
|
||||
.PHONY: all
|
||||
|
||||
persist-settings: distclean
|
||||
|
|
@ -574,7 +575,7 @@ 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) $(ENGINE_UNIT_TESTS) $(ENGINE_LIB_NAME) unit/*.o unit/*.d lua/*.o lua/*.d trace/*.o trace/*.d *.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_UNIT_GTESTS) $(ENGINE_LIB_NAME) unit/*.o unit/*.d lua/*.o lua/*.d trace/*.o trace/*.d *.o *.gcda *.gcno *.gcov valkey.info lcov-html Makefile.dep *.so
|
||||
rm -f $(DEP)
|
||||
|
||||
.PHONY: clean
|
||||
|
|
@ -583,15 +584,27 @@ distclean: clean
|
|||
-(cd ../deps && $(MAKE) distclean)
|
||||
-(cd modules && $(MAKE) clean)
|
||||
-(cd ../tests/modules && $(MAKE) clean)
|
||||
-(cd gtest && $(MAKE) clean)
|
||||
-(rm -f .make-*)
|
||||
-(find gtest -type d -name __pycache__ -exec rm -rf {} +)
|
||||
|
||||
.PHONY: distclean
|
||||
|
||||
test: $(SERVER_NAME) $(ENGINE_CHECK_AOF_NAME) $(ENGINE_CHECK_RDB_NAME) $(ENGINE_CLI_NAME) $(ENGINE_BENCHMARK_NAME)
|
||||
@(cd ..; ./runtest)
|
||||
|
||||
test-unit: $(ENGINE_UNIT_TESTS)
|
||||
./$(ENGINE_UNIT_TESTS)
|
||||
test-unit: $(ENGINE_UNIT_TESTS) $(ENGINE_UNIT_GTESTS)
|
||||
@echo "Running C unit tests..."
|
||||
./$(ENGINE_UNIT_TESTS) || echo "C unit tests failed"
|
||||
@echo "Running gtest unit tests..."
|
||||
./gtest/$(ENGINE_UNIT_GTESTS) || echo "gtest unit tests failed"
|
||||
|
||||
test-gtest:
|
||||
@(cd gtest && $(MAKE) test-gtest)
|
||||
|
||||
# valkey-unit-gtests
|
||||
$(ENGINE_UNIT_GTESTS): $(ENGINE_LIB_NAME)
|
||||
@(cd gtest && $(MAKE) $(ENGINE_UNIT_GTESTS))
|
||||
|
||||
test-modules: $(SERVER_NAME)
|
||||
@(cd ..; ./runtest-moduleapi)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,9 @@
|
|||
#define _BSD_SOURCE
|
||||
|
||||
#if defined(__linux__)
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
/*
|
||||
* This macro might have already been defined by including <features.h>, which
|
||||
* is transitively included by <sys/types.h>. Therefore, to avoid redefinition
|
||||
|
|
@ -62,7 +64,9 @@
|
|||
#define _POSIX_C_SOURCE 199506L
|
||||
#endif
|
||||
|
||||
#ifndef _LARGEFILE_SOURCE
|
||||
#define _LARGEFILE_SOURCE
|
||||
#endif
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
|
||||
/* deprecate unsafe functions
|
||||
|
|
@ -70,12 +74,20 @@
|
|||
* NOTE: We do not use the poison pragma since it
|
||||
* will error on stdlib definitions in files as well*/
|
||||
#if (__GNUC__ && __GNUC__ >= 4) && !defined __APPLE__
|
||||
|
||||
/* These deprecation attributes rely on C-only constructs (e.g. `restrict`)
|
||||
* and redeclare libc symbols. They are disabled for building gtest
|
||||
* to avoid conflicts with C++ standard library declarations.
|
||||
*/
|
||||
#ifndef __cplusplus
|
||||
int sprintf(char *str, const char *format, ...)
|
||||
__attribute__((deprecated("please avoid use of unsafe C functions. prefer use of snprintf instead")));
|
||||
char *strcpy(char *restrict dest, const char *src)
|
||||
__attribute__((deprecated("please avoid use of unsafe C functions. prefer use of valkey_strlcpy instead")));
|
||||
char *strcat(char *restrict dest, const char *restrict src)
|
||||
__attribute__((deprecated("please avoid use of unsafe C functions. prefer use of valkey_strlcat instead")));
|
||||
#endif /* !__cplusplus */
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
|
|
|
|||
|
|
@ -0,0 +1,155 @@
|
|||
cmake_minimum_required(VERSION 3.14)
|
||||
project(valkey_unit_gtests)
|
||||
|
||||
# GoogleTest requires at least C++17
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
get_valkey_server_linker_option(VALKEY_SERVER_LDFLAGS)
|
||||
|
||||
# Build GoogleTest-based tests
|
||||
message(STATUS "Building gtest unit tests")
|
||||
if (USE_TLS)
|
||||
if (BUILD_TLS_MODULE)
|
||||
# TLS as a module
|
||||
list(APPEND COMPILE_DEFINITIONS "USE_OPENSSL=2")
|
||||
else (BUILD_TLS_MODULE)
|
||||
# Built-in TLS support
|
||||
list(APPEND COMPILE_DEFINITIONS "USE_OPENSSL=1")
|
||||
list(APPEND COMPILE_DEFINITIONS "BUILD_TLS_MODULE=0")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Fetch GoogleTest using FetchContent (modern approach)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
|
||||
)
|
||||
# For Windows: Prevent overriding the parent project's compiler/linker settings
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
|
||||
# Build Valkey sources as a static library for the test (C code compiled with C compiler)
|
||||
add_library(valkeylib-gtest STATIC ${VALKEY_SERVER_SRCS})
|
||||
target_compile_options(valkeylib-gtest PRIVATE "${COMPILE_FLAGS}")
|
||||
target_compile_definitions(valkeylib-gtest PRIVATE "${COMPILE_DEFINITIONS}")
|
||||
target_include_directories(valkeylib-gtest PRIVATE ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/deps)
|
||||
|
||||
# Generate wrapper files from wrappers.h
|
||||
set(GENERATED_WRAPPERS_DIR ${CMAKE_BINARY_DIR}/gtest_generated)
|
||||
file(MAKE_DIRECTORY ${GENERATED_WRAPPERS_DIR})
|
||||
add_custom_command(
|
||||
OUTPUT ${GENERATED_WRAPPERS_DIR}/generated_wrappers.cpp ${GENERATED_WRAPPERS_DIR}/generated_wrappers.hpp
|
||||
COMMAND python3 ${CMAKE_CURRENT_LIST_DIR}/generate-wrappers.py ${GENERATED_WRAPPERS_DIR}
|
||||
DEPENDS ${CMAKE_CURRENT_LIST_DIR}/wrappers.h ${CMAKE_CURRENT_LIST_DIR}/generate-wrappers.py
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
||||
COMMENT "Generating wrapper files from wrappers.h"
|
||||
)
|
||||
|
||||
# Create a target for generating wrapper files
|
||||
add_custom_target(generate_wrappers DEPENDS ${GENERATED_WRAPPERS_DIR}/generated_wrappers.cpp ${GENERATED_WRAPPERS_DIR}/generated_wrappers.hpp)
|
||||
|
||||
# Collect C++ test source files (excluding generated ones for now)
|
||||
file(GLOB GTEST_SRCS "${CMAKE_CURRENT_LIST_DIR}/*.cpp")
|
||||
list(REMOVE_ITEM GTEST_SRCS "${GENERATED_WRAPPERS_DIR}/generated_wrappers.cpp")
|
||||
|
||||
# Create GoogleTest executable (C++ code compiled with C++ compiler)
|
||||
add_executable(valkey-unit-gtests ${GTEST_SRCS} ${GENERATED_WRAPPERS_DIR}/generated_wrappers.cpp)
|
||||
|
||||
# Make sure generated files are created before building
|
||||
add_dependencies(valkey-unit-gtests generate_wrappers)
|
||||
|
||||
# Set C++ properties
|
||||
set_target_properties(valkey-unit-gtests PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
)
|
||||
|
||||
# Add C++ compile definitions and flags for C header compatibility
|
||||
target_compile_definitions(valkey-unit-gtests PRIVATE "${COMPILE_DEFINITIONS}")
|
||||
|
||||
# Allow C-style void pointer conversions in C++
|
||||
target_compile_options(valkey-unit-gtests PRIVATE "${COMPILE_FLAGS}")
|
||||
|
||||
# Disable warnings
|
||||
target_compile_options(valkey-unit-gtests PRIVATE
|
||||
-Wno-deprecated-declarations
|
||||
-Wno-write-strings
|
||||
-fno-var-tracking-assignments
|
||||
)
|
||||
|
||||
# Include directories for C++ compilation
|
||||
target_include_directories(valkey-unit-gtests PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
${CMAKE_SOURCE_DIR}/deps
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
${GENERATED_WRAPPERS_DIR}
|
||||
)
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
# Avoid duplicate symbols on non macOS
|
||||
target_link_options(valkey-unit-gtests PRIVATE "-Wl,--allow-multiple-definition")
|
||||
|
||||
# Extract all wrapped function names from wrappers.h and add --wrap linker flags
|
||||
execute_process(
|
||||
COMMAND python3 -c "
|
||||
import re
|
||||
import sys
|
||||
with open('${CMAKE_CURRENT_LIST_DIR}/wrappers.h', 'r') as f:
|
||||
for line in f:
|
||||
m = re.match(r'.*__wrap_(\\w+)\\(.*\\);', line)
|
||||
if m:
|
||||
print(m.group(1))
|
||||
"
|
||||
OUTPUT_VARIABLE WRAPPED_FUNCTIONS
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
# Convert the list of functions to linker flags
|
||||
string(REPLACE "\n" ";" WRAPPED_FUNCTIONS_LIST "${WRAPPED_FUNCTIONS}")
|
||||
foreach(FUNC ${WRAPPED_FUNCTIONS_LIST})
|
||||
target_link_options(valkey-unit-gtests PRIVATE "-Wl,--wrap=${FUNC}")
|
||||
endforeach()
|
||||
endif ()
|
||||
|
||||
if (USE_JEMALLOC)
|
||||
# Using jemalloc - link to both the static library and the executable
|
||||
target_link_libraries(valkeylib-gtest jemalloc)
|
||||
target_link_libraries(valkey-unit-gtests jemalloc)
|
||||
endif ()
|
||||
|
||||
if (IS_FREEBSD)
|
||||
target_link_libraries(valkey-unit-gtests execinfo)
|
||||
endif ()
|
||||
|
||||
# Link with GoogleTest using target names
|
||||
target_link_libraries(
|
||||
valkey-unit-gtests
|
||||
valkeylib-gtest
|
||||
fpconv
|
||||
lualib
|
||||
hdr_histogram
|
||||
valkey::valkey
|
||||
GTest::gtest_main
|
||||
GTest::gmock
|
||||
pthread
|
||||
${VALKEY_SERVER_LDFLAGS})
|
||||
|
||||
if (USE_TLS)
|
||||
# Add required libraries needed for TLS
|
||||
target_link_libraries(valkey-unit-gtests OpenSSL::SSL valkey::valkey_tls)
|
||||
endif ()
|
||||
|
||||
# Enable testing and discover tests
|
||||
enable_testing()
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(valkey-unit-gtests)
|
||||
|
||||
# Add custom test target using gtest-parallel
|
||||
add_custom_target(test-gtest
|
||||
COMMAND python3 ${CMAKE_SOURCE_DIR}/deps/gtest-parallel/gtest_parallel.py $<TARGET_FILE:valkey-unit-gtests>
|
||||
DEPENDS valkey-unit-gtests
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Running tests with gtest-parallel"
|
||||
)
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
CXX ?= g++
|
||||
OPT=-O3 -DNDEBUG
|
||||
|
||||
ifeq ($(DEBUG_BUILD),1)
|
||||
CXX+=-DDEBUG_BUILD=1
|
||||
endif
|
||||
|
||||
.DEFAULT_GOAL := all
|
||||
SOURCES := $(wildcard *.cpp)
|
||||
ALL_OBJECTS := $(patsubst %.cpp,%.o,$(SOURCES))
|
||||
DEPENDS := $(patsubst %.cpp,%.d,$(SOURCES))
|
||||
|
||||
# Hold new gtest test to a high standard
|
||||
MORE_WARNINGS=-Wold-style-cast \
|
||||
-Wno-variadic-macros \
|
||||
-Werror \
|
||||
-Wpedantic \
|
||||
-Wextra \
|
||||
-Wall \
|
||||
-Wcast-align \
|
||||
-Wcast-qual \
|
||||
-Wframe-larger-than=32768 \
|
||||
-Wno-strict-overflow \
|
||||
-Wsync-nand \
|
||||
-Wtrampolines \
|
||||
-Wsign-compare \
|
||||
-Werror=float-equal \
|
||||
-Werror=missing-braces \
|
||||
-Werror=init-self \
|
||||
-Werror=logical-op \
|
||||
-Werror=write-strings \
|
||||
-Werror=address \
|
||||
-Werror=array-bounds \
|
||||
-Werror=char-subscripts \
|
||||
-Werror=enum-compare \
|
||||
-Werror=empty-body \
|
||||
-Werror=main \
|
||||
-Werror=aggressive-loop-optimizations \
|
||||
-Werror=nonnull \
|
||||
-Werror=parentheses \
|
||||
-Werror=return-type \
|
||||
-Werror=sequence-point \
|
||||
-Werror=uninitialized \
|
||||
-Werror=volatile-register-var \
|
||||
-Werror=ignored-qualifiers \
|
||||
-Wno-unused-function \
|
||||
-Wno-missing-field-initializers \
|
||||
-Wdouble-promotion \
|
||||
-Wformat=2
|
||||
|
||||
-include $(DEPENDS)
|
||||
|
||||
ifdef COVERAGE
|
||||
TEST_CFLAGS += -DCOVERAGE_TEST $(COVERAGE_CFLAGS)
|
||||
endif
|
||||
|
||||
ifdef FORCE_RUN_SKIPPED_TESTS
|
||||
TEST_CFLAGS+=-DFORCE_RUN_SKIPPED_TESTS
|
||||
endif
|
||||
|
||||
# Track compilation flags to force rebuild when they change
|
||||
.PHONY: .flags
|
||||
.flags:
|
||||
@echo '$(TEST_CFLAGS)' | cmp -s - $@ || echo '$(TEST_CFLAGS)' > $@
|
||||
|
||||
# OSS Valkey library dependencies
|
||||
VALKEY_LIB := ../libvalkey.a
|
||||
HIREDIS_LIB := ../../deps/libvalkey/lib/libvalkey.a
|
||||
LUA_LIB := ../../deps/lua/src/liblua.a
|
||||
JEMALLOC_LIB := ../../deps/jemalloc/lib/libjemalloc.a
|
||||
HDR_HISTOGRAM_LIB := ../../deps/hdr_histogram/libhdrhistogram.a
|
||||
FPCONV_LIB := ../../deps/fpconv/libfpconv.a
|
||||
GTEST_LIB := ../../deps/googletest/build/googlemock/gtest/libgtest.a
|
||||
GMOCK_LIB := ../../deps/googletest/build/googlemock/libgmock.a
|
||||
|
||||
# Ensure GoogleTest is built
|
||||
../../deps/googletest/build/googlemock/gtest/libgtest.a ../../deps/googletest/build/googlemock/libgmock.a:
|
||||
cd ../../deps && $(MAKE) gtest
|
||||
|
||||
# Ensure Valkey library is built
|
||||
$(VALKEY_LIB):
|
||||
cd .. && $(MAKE) libvalkey.a
|
||||
|
||||
# Ensure parent prerequisites are built
|
||||
.PHONY: make-prerequisites
|
||||
make-prerequisites:
|
||||
cd .. && $(MAKE) .make-prerequisites
|
||||
|
||||
# Default target to compile against Valkey, LUA, jemalloc (as Valkey does) as well as gmock
|
||||
%.o: %.cpp make-prerequisites generated_wrappers.cpp .flags
|
||||
$(CXX) -MD -MP -std=c++14 -faligned-new -Wno-write-strings -fpermissive -fno-var-tracking-assignments $(OPT) $(DEBUG) $(TEST_CFLAGS) -Wall -Wno-deprecated-declarations -c -isystem .. -I ../../deps/lua/src/ -I ../../deps/hdr_histogram/ -I ../../deps/fpconv/ -DUSE_JEMALLOC -isystem../../deps/jemalloc/include -I ../../deps/googletest/googletest/include -I ../../deps/googletest/googlemock/include $<
|
||||
|
||||
$(ALL_OBJECTS): OPT := $(OPT) $(MORE_WARNINGS)
|
||||
|
||||
# Obtain the list of function calls for which we are generating mocks using the gmock library. We assume they are defined in
|
||||
# wrappers.h and begin with the prefix '__wrap_' (which is required by the linker). These command line arguments are then
|
||||
# passed in to g++ during linking.
|
||||
ifneq ($(wildcard wrappers.h),)
|
||||
CMD:=egrep '__wrap_.*\(.*;' wrappers.h | sed 's/__wrap_/@/' | sed 's/[^@]*@\([^(]*\)(.*/\1/' | sort | uniq | xargs -I{} echo "-Wl,--wrap={}" | tr "\n" " "
|
||||
OVERRIDES:=$(shell $(CMD))
|
||||
else
|
||||
OVERRIDES:=
|
||||
endif
|
||||
|
||||
# Generate a new set of wrappers every time our header changes.
|
||||
generated_wrappers.cpp: wrappers.h generate-wrappers.py
|
||||
./generate-wrappers.py
|
||||
|
||||
generated_wrappers.o: generated_wrappers.cpp
|
||||
|
||||
LD_LIBS := $(HIREDIS_LIB) \
|
||||
$(LUA_LIB) \
|
||||
$(JEMALLOC_LIB) \
|
||||
$(GMOCK_LIB) \
|
||||
$(GTEST_LIB) \
|
||||
$(HDR_HISTOGRAM_LIB) \
|
||||
$(FPCONV_LIB)
|
||||
|
||||
# Generate the single test binary in parent directory.
|
||||
valkey-unit-gtests: $(ALL_OBJECTS) generated_wrappers.o $(VALKEY_LIB)
|
||||
$(CXX) -g \
|
||||
-Wl,--allow-multiple-definition \
|
||||
$(OVERRIDES) \
|
||||
$(SYSCALL_WRAP_LINKER_OPTIONS) \
|
||||
$(COVERAGE_LDFLAGS) \
|
||||
-Wall -Wno-deprecated-declarations \
|
||||
-o $@ $^ $(LD_LIBS) \
|
||||
-lrt -lm -pthread -ldl -lgcov
|
||||
|
||||
.PHONY: all
|
||||
all: valkey-unit-gtests
|
||||
|
||||
.PHONY: test-gtest
|
||||
test-gtest: valkey-unit-gtests
|
||||
python3 ../../deps/gtest-parallel/gtest_parallel.py ./valkey-unit-gtests
|
||||
|
||||
.PHONY: valgrind
|
||||
valgrind: valkey-unit-gtests
|
||||
valgrind $(VALGRIND_ARGS) ./valkey-unit-gtests
|
||||
|
||||
clean:
|
||||
rm -f *.o valkey-unit-gtests ../valkey-unit-gtests generated_* $(DEPENDS) .flags
|
||||
|
||||
distclean: clean
|
||||
$(MAKE) -C .. distclean
|
||||
|
||||
.PHONY: clean distclean
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
## Valkey GoogleTest Unit Test Framework
|
||||
|
||||
This directory contains the GoogleTest (gtest) framework integration for Valkey
|
||||
unit testing. GoogleTest provides advanced unit testing capabilities that are
|
||||
not available in Valkey's legacy unit testing framework. These capabilities
|
||||
include:
|
||||
|
||||
- Integrated mocking via GoogleMock, providing expressive, behavior-based mocks
|
||||
- Rich argument matchers and call sequencing / ordering
|
||||
- Advanced test fixtures and lifecycle control
|
||||
- Richful assertions and detailed diagnostics
|
||||
- Parameterized and typed tests
|
||||
|
||||
These features enable more expressive, maintainable, and scalable unit tests,
|
||||
particularly for complex components and edge-case validation.
|
||||
|
||||
For more information on GoogleTest, see: https://google.github.io/googletest/
|
||||
|
||||
To use this framework to write unit tests, we have modified Valkey to build as
|
||||
a library that can link against other test executables. This framework uses the
|
||||
GNU C++ linker, which implements 'wrap' functionality to rename function calls
|
||||
to foo() to a method __wrap_foo() and renames the real foo() method to
|
||||
__real_foo().
|
||||
|
||||
Using this trick, we define the Valkey wrappers we wish to mock in 'wrappers.h'.
|
||||
Note that these functions can only be mocked if they include calls between
|
||||
source files.
|
||||
|
||||
Using this set of functions, we run 'generate-wrappers.py' to generate the glue
|
||||
code needed to mock functions. Specifically, this generates an interface named
|
||||
Valkey containing all the desired methods and two implementations, MockValkey
|
||||
and RealValkey.
|
||||
|
||||
MockValkey uses gtest definitions to define a mock class. RealValkey uses the
|
||||
__real_foo() methods to call the renamed methods. The script also implements
|
||||
every __wrap_foo() command that delegates to the last MockValkey instance
|
||||
initialized.
|
||||
|
||||
To extend the Valkey classes for mocking further methods, simply add your method
|
||||
to 'wrappers.h' and re-run 'make test-gtest' to regenerate the Valkey glue code
|
||||
and run the tests.
|
||||
|
||||
## Tricks in running unit tests
|
||||
|
||||
Sometimes the developer might want to run only one gtest unit test, or only a
|
||||
subset of all unit tests for debugging. We have a few different flavors of
|
||||
gtest unit tests that you can filter/play with:
|
||||
|
||||
1. Running all unit tests (C unit tests and gtest unit tests)
|
||||
|
||||
```bash
|
||||
make test-unit
|
||||
```
|
||||
|
||||
2. Running all gtest unit tests
|
||||
|
||||
```bash
|
||||
make test-gtest
|
||||
```
|
||||
|
||||
3. Running all gtest unit tests in the test class, replace TEST_CLASS_NAME with
|
||||
expected test class name
|
||||
|
||||
```bash
|
||||
make valkey-unit-gtests
|
||||
./src/gtest/valkey-unit-gtests --gtest_filter=<TEST_CLASS_NAME>
|
||||
```
|
||||
|
||||
4. Running a subset of gtest unit tests in the test class, replace
|
||||
TEST_CLASS_NAME with expected test class name, and replace TEST_NAME_PREFIX
|
||||
with test name
|
||||
|
||||
```bash
|
||||
make valkey-unit-gtests
|
||||
./src/gtest/valkey-unit-gtests --gtest_filter=<*TEST_CLASS_NAME.TEST_NAME_PREFIX>
|
||||
```
|
||||
|
||||
5. Building and running with CMake
|
||||
|
||||
```bash
|
||||
mkdir build-release && cd $_
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/opt/valkey -DBUILD_UNIT_GTESTS=yes
|
||||
make valkey-unit-gtests
|
||||
./bin/valkey-unit-gtests
|
||||
```
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef _CUSTOM_MATCHERS_HPP_
|
||||
#define _CUSTOM_MATCHERS_HPP_
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include <string>
|
||||
|
||||
/* Matchers can be used for complex comparisons inside of EXPECT_THAT(val, matcher)
|
||||
* For example, to check if an robj contains a string "abc", a matcher can be used like:
|
||||
* EXPECT_THAT(o, robjEqualsStr("abc"));
|
||||
*/
|
||||
|
||||
// Matches an robj (which MUST contain an sds encoded string) to a char* string.
|
||||
MATCHER_P(robjEqualsStr, str, "robj string matcher") {
|
||||
assert(arg->type == OBJ_STRING);
|
||||
assert(sdsEncodedObject(arg));
|
||||
return strcmp(static_cast<const char*>(arg->ptr), str) == 0;
|
||||
}
|
||||
|
||||
#endif // _CUSTOM_MATCHERS_HPP_
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
#include "generated_wrappers.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include "dict.h"
|
||||
}
|
||||
|
||||
// Use a class name descriptive of your test unit
|
||||
class ExampleTest : public ::testing::Test {
|
||||
// standard boilerplate supporting mocked functions
|
||||
protected:
|
||||
MockValkey mock;
|
||||
RealValkey real;
|
||||
|
||||
// The SetUp() function is called before each test.
|
||||
void SetUp() override {
|
||||
memset(&server, 0, sizeof(valkeyServer));
|
||||
server.hz = CONFIG_DEFAULT_HZ;
|
||||
}
|
||||
|
||||
// The TearDown() function is called after each test.
|
||||
void TearDown() override {}
|
||||
};
|
||||
|
||||
// Include this (should end in "DeathTest") if testing that code asserts/dies.
|
||||
using ExampleDeathTest = ExampleTest;
|
||||
|
||||
// Example of a DeathTest, which passes only if the code crashes.
|
||||
TEST_F(ExampleDeathTest, TestSimpleDeath) {
|
||||
EXPECT_DEATH(
|
||||
{
|
||||
*(static_cast<char*>(0)) = 'x'; // SEGV
|
||||
},
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
// Simple assertions test
|
||||
TEST_F(ExampleTest, TestAssertions) {
|
||||
int a = 5, b = 3;
|
||||
const char *str = "hello";
|
||||
// Use EXPECT_ macros to test a condition. If the value is not as expected, the test will fail.
|
||||
// Use ASSERT_ macros to test a condition AND immediately end the test.
|
||||
// Prefer to use EXPECT_ macros unless the test can't reasonably continue. This allows multiple
|
||||
// conditions to be tested and reported rather than ending at the first unexpected value.
|
||||
EXPECT_EQ(8, a + b);
|
||||
EXPECT_LE(b, a);
|
||||
EXPECT_GT(a, b);
|
||||
EXPECT_STREQ(str, "hello");
|
||||
ASSERT_EQ(2, a - b);
|
||||
}
|
||||
|
||||
// Test matcher works in custom_matchers.hpp
|
||||
TEST_F(ExampleTest, TestMatchers) {
|
||||
robj *robj_str = createStringObject("test", 4);
|
||||
ASSERT_NE(robj_str , nullptr); // "ASSERT" is correct here, because the test can't reasonably continue
|
||||
EXPECT_THAT(robj_str, robjEqualsStr("test"));
|
||||
decrRefCount(robj_str);
|
||||
}
|
||||
|
||||
// Verify mocking works via zfree
|
||||
TEST_F(ExampleTest, TestMocking) {
|
||||
// zfree should be called in dictRelease
|
||||
EXPECT_CALL(mock, zfree(_)).Times(AtLeast(1)); // Verifies that zfree() is called at least once
|
||||
dict *d = dictCreate(&keylistDictType);
|
||||
dictRelease(d);
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Autogenerated wrapper generator for OSS Valkey.
|
||||
|
||||
Parses '__wrap_' C function signatures from 'wrappers.h' and generates
|
||||
'generated_wrappers.hpp' and 'generated_wrappers.cpp', providing
|
||||
classes (RealValkey and MockValkey) for gtest-based testing.
|
||||
The generated files wrap the real functions and provide mockable
|
||||
interfaces using GoogleTest.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
|
||||
from wrapper_util import find_wrapper_functions_in_header, Method, Arg
|
||||
|
||||
|
||||
def generate_header(header_file, methods):
|
||||
# Write the generated_wrappers hpp file
|
||||
f = open(header_file, 'w')
|
||||
|
||||
f.write('''#ifndef __GENERATED_WRAPPERS_HPP
|
||||
#define __GENERATED_WRAPPERS_HPP
|
||||
|
||||
// AUTOGENERATED - DO NOT MODIFY
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "wrappers.h"
|
||||
#include "custom_matchers.hpp"
|
||||
using namespace ::testing;
|
||||
using ::testing::StrictMock;
|
||||
|
||||
extern "C" {
|
||||
''')
|
||||
|
||||
for m in methods:
|
||||
f.write(" extern %s __real_%s(%s);\n" % (m.ret_type, m.name, m.full_args))
|
||||
|
||||
f.write('''
|
||||
}
|
||||
|
||||
class Valkey {
|
||||
public:
|
||||
virtual ~Valkey() {};
|
||||
''')
|
||||
|
||||
for m in methods:
|
||||
f.write(" virtual %s %s(%s) = 0;\n" % (m.ret_type, m.name, m.arg_string))
|
||||
|
||||
f.write('''
|
||||
};
|
||||
|
||||
class RealValkey : public Valkey {
|
||||
public:
|
||||
virtual ~RealValkey() {};
|
||||
''')
|
||||
|
||||
for m in methods:
|
||||
f.write('''
|
||||
virtual %s %s(%s) {
|
||||
%s__real_%s(%s);
|
||||
}''' % (m.ret_type, m.name, m.arg_string, 'return ' if m.ret_type != 'void' else '', m.name, ','.join(x.name for x in m.args)))
|
||||
|
||||
f.write('''
|
||||
};
|
||||
|
||||
class MockValkey : public Valkey {
|
||||
protected:
|
||||
RealValkey _real;
|
||||
|
||||
public:
|
||||
MockValkey();
|
||||
virtual ~MockValkey();
|
||||
''')
|
||||
|
||||
for m in methods:
|
||||
f.write(" MOCK_METHOD%d(%s, %s(%s));\n" % (len(m.args), m.name, m.ret_type, m.arg_string))
|
||||
|
||||
f.write('''};
|
||||
#endif
|
||||
''')
|
||||
|
||||
f.close()
|
||||
|
||||
|
||||
def generate_cpp(cpp_file, methods):
|
||||
f = open(cpp_file, 'w')
|
||||
f.write('''// AUTOGENERATED - DO NOT MODIFY
|
||||
|
||||
#include "generated_wrappers.hpp"
|
||||
|
||||
// A global pointer to our current test's MockValkey
|
||||
// so that the C valkey wrappers can delegate to the
|
||||
// correct mock
|
||||
MockValkey* globalValkey = NULL;
|
||||
|
||||
extern "C" {
|
||||
''')
|
||||
|
||||
for m in methods:
|
||||
prefix = 'return ' if m.ret_type != 'void' else ''
|
||||
names = ','.join(x.name for x in m.args)
|
||||
f.write('''
|
||||
%s __wrap_%s(%s) {
|
||||
// Delegate to the global valkey if it is initialized
|
||||
if (globalValkey != NULL) {
|
||||
%sglobalValkey->%s(%s);
|
||||
} else {
|
||||
%s__real_%s(%s);
|
||||
}
|
||||
}''' % (m.ret_type, m.name, m.full_args, prefix, m.name, names, prefix, m.name, names))
|
||||
|
||||
f.write('''
|
||||
}
|
||||
|
||||
MockValkey::MockValkey() {
|
||||
// Set the global valkey to the current
|
||||
// instance
|
||||
globalValkey = this;
|
||||
''')
|
||||
for m in methods:
|
||||
args = ','.join('_' for x in m.args)
|
||||
f.write(" EXPECT_CALL(*globalValkey, %s(%s)).WillRepeatedly(Invoke(&_real, &RealValkey::%s));\n" % (m.name, args, m.name))
|
||||
|
||||
f.write('''
|
||||
}
|
||||
|
||||
MockValkey::~MockValkey() {
|
||||
// Unset the global valkey
|
||||
globalValkey = NULL;
|
||||
}
|
||||
''')
|
||||
f.close()
|
||||
|
||||
def main():
|
||||
# Determine the output directory
|
||||
output_dir = "" if len(sys.argv) == 1 else f"{sys.argv[1]}/"
|
||||
|
||||
# Parse the source file containing the wrappers
|
||||
methods = find_wrapper_functions_in_header('wrappers.h')
|
||||
|
||||
# Do not generate the file if not needed
|
||||
generated_header = f"{output_dir}generated_wrappers.hpp"
|
||||
generated_cpp = f"{output_dir}generated_wrappers.cpp"
|
||||
|
||||
generated_output_last_modified = 0 if not os.path.exists(generated_header) else os.path.getmtime(generated_header)
|
||||
input_last_modified = os.path.getmtime('wrappers.h')
|
||||
|
||||
if generated_output_last_modified > input_last_modified:
|
||||
# Output files are up-to-date
|
||||
print(f"{generated_header} is up-to-date")
|
||||
print(f"{generated_cpp} is up-to-date")
|
||||
os.sys.exit(0)
|
||||
|
||||
generate_header(generated_header, methods)
|
||||
generate_cpp(generated_cpp, methods)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// The following line must be executed to initialize GoogleTest before running the tests.
|
||||
::testing::InitGoogleMock(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
Utility functions for parsing '__wrap_' C function signatures from header files (e.g. wrappers.h).
|
||||
|
||||
Extracts return types, parameters, and function pointers, producing Method and Arg namedtuples.
|
||||
This structured data is used by generate-wrappers.py to create MockValkey and RealValkey classes
|
||||
for gtest-based tests.
|
||||
"""
|
||||
import re
|
||||
from collections import namedtuple
|
||||
|
||||
Method = namedtuple("Method", "ret_type name full_args arg_string args")
|
||||
Arg = namedtuple("Arg", "type name")
|
||||
|
||||
def split_args(arg_string):
|
||||
"""
|
||||
Split a C-style argument string by commas, but ignore commas inside parentheses.
|
||||
Example:
|
||||
"int x, void (*cb)(int, int), char* buf"
|
||||
becomes:
|
||||
["int x", "void (*cb)(int, int)", "char* buf"]
|
||||
"""
|
||||
args = []
|
||||
current = []
|
||||
depth = 0
|
||||
for char in arg_string:
|
||||
if char == '(':
|
||||
depth += 1
|
||||
current.append(char)
|
||||
elif char == ')':
|
||||
depth -= 1
|
||||
current.append(char)
|
||||
elif char == ',' and depth == 0:
|
||||
# Split only at top-level commas
|
||||
args.append(''.join(current).strip())
|
||||
current = []
|
||||
else:
|
||||
current.append(char)
|
||||
if current:
|
||||
args.append(''.join(current).strip())
|
||||
# Single "void" means no args
|
||||
if len(args) == 1 and args[0] == "void":
|
||||
return []
|
||||
# Remove variadic "..."
|
||||
if args and args[-1] == "...":
|
||||
args = args[:-1]
|
||||
return args
|
||||
|
||||
|
||||
def find_wrapper_functions_in_header(header_file_name):
|
||||
"""
|
||||
Parse a header file and extract all functions starting with '__wrap_'.
|
||||
|
||||
Each function is returned as a Method namedtuple containing:
|
||||
- ret_type: the return type of the function
|
||||
- name: the function name (without __wrap_)
|
||||
- full_args: raw argument string from the header
|
||||
- arg_string: comma-separated type + name strings for declarations
|
||||
- args: list of Arg namedtuples (type, name)
|
||||
|
||||
This parser recognizes two types of arguments:
|
||||
|
||||
1. Normal arguments (e.g., int x, char* buffer)
|
||||
2. Function pointer arguments (e.g., int *(fp)(int, void*))
|
||||
"""
|
||||
methods = []
|
||||
|
||||
with open(header_file_name, 'r') as f:
|
||||
for line in f:
|
||||
# Match a function signature starting with __wrap_
|
||||
m = re.match(r"(.*)__wrap_(\w+)\((.*)\);", line)
|
||||
if not m:
|
||||
continue
|
||||
|
||||
method_ret_type = m.group(1).strip()
|
||||
method_name = m.group(2).strip()
|
||||
full_args = m.group(3)
|
||||
|
||||
args_declaration = []
|
||||
args_definition = []
|
||||
|
||||
for arg in split_args(full_args):
|
||||
|
||||
# Normal argument
|
||||
m_normal = re.match(r"(.+[\s\*])([a-zA-Z0-9_]+)$", arg)
|
||||
if m_normal:
|
||||
arg_type = m_normal.group(1).strip()
|
||||
arg_name = m_normal.group(2).strip()
|
||||
args_definition.append(Arg(arg_type, arg_name))
|
||||
args_declaration.append(Arg(arg_type, arg_name))
|
||||
continue
|
||||
|
||||
# Function pointer argument
|
||||
m_func_ptr = re.match(r"(.+?)\s*\(\s*(?:\*\s*)?([a-zA-Z0-9_]+)\s*\)\s*\((.*)\)", arg)
|
||||
if m_func_ptr:
|
||||
arg_ret_type = m_func_ptr.group(1).strip()
|
||||
arg_name = m_func_ptr.group(2).strip()
|
||||
params = m_func_ptr.group(3).strip()
|
||||
|
||||
arg_type = f"{arg_ret_type} ({arg_name})({params})"
|
||||
args_definition.append(Arg(arg_type, arg_name))
|
||||
args_declaration.append(Arg(arg_type, ""))
|
||||
continue
|
||||
|
||||
# If argument cannot be parsed
|
||||
print(f"WARNING: Could not parse argument: '{arg}', {line}")
|
||||
|
||||
# Build comma-separated argument string for declaration
|
||||
arg_string = ', '.join(f"{x.type} {x.name}".strip() for x in args_declaration)
|
||||
|
||||
# Append the method to the results
|
||||
methods.append(Method(
|
||||
ret_type=method_ret_type,
|
||||
name=method_name,
|
||||
full_args=full_args,
|
||||
arg_string=arg_string,
|
||||
args=args_definition
|
||||
))
|
||||
|
||||
return methods
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* wrappers.h - Function Wrapper Declarations for GoogleTest Unit Tests
|
||||
*
|
||||
* PURPOSE:
|
||||
* This file declares C function wrappers that enable mocking of Valkey C functions
|
||||
* in GoogleTest unit tests. It bridges C code with gtest infrastructure.
|
||||
*
|
||||
* HOW IT WORKS:
|
||||
* 1. Declare wrapper functions with __wrap_ prefix (e.g., __wrap_mstime for mstime())
|
||||
* 2. generate-wrappers.py parses this file and auto-generates TWO files:
|
||||
* - generated_wrappers.hpp (MockValkey class with MOCK_METHOD macros)
|
||||
* - generated_wrappers.cpp (wrapper implementations that delegate to MockValkey)
|
||||
* 3. Build system uses --wrap linker flags to redirect calls: mstime() -> __wrap_mstime()
|
||||
* 4. GoogleTest can mock these wrappers to control behavior and verify calls
|
||||
*
|
||||
* RULES:
|
||||
* - All wrapper functions MUST be prefixed with __wrap_
|
||||
* - Function signatures MUST exactly match the original C function
|
||||
* - DO NOT wrap variadic functions (functions with ...) - GoogleTest doesn't support them
|
||||
* - Each wrapper becomes mockable in gtest via the auto-generated MockValkey class
|
||||
*
|
||||
* WORKFLOW:
|
||||
* wrappers.h -> generate-wrappers.py -> [generated_wrappers.hpp + generated_wrappers.cpp]
|
||||
* -> linked with gtest
|
||||
*
|
||||
* See: wrapper_util.py, generate-wrappers.py
|
||||
*/
|
||||
|
||||
#include <sched.h>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef __WRAPPERS_H
|
||||
#define __WRAPPERS_H
|
||||
#define _Atomic /* nothing */
|
||||
#define _Bool bool
|
||||
#define typename _typename
|
||||
#define protected protected_
|
||||
#include "ae.h"
|
||||
#include "dict.h"
|
||||
#include "server.h"
|
||||
#include "adlist.h"
|
||||
#include "zmalloc.h"
|
||||
|
||||
/**
|
||||
* The list of wrapper methods defined. Each wrapper method must
|
||||
* conform to the same naming conventions (i.e. prefix with a
|
||||
* '__wrap_') and have its method signature match the overridden
|
||||
* method exactly.
|
||||
*
|
||||
* Note: You should NOT wrap variable argument functions (i.e have "...")
|
||||
* See: https://github.com/google/googletest/blob/master/googlemock/docs/gmock_faq.md#can-i-mock-a-variadic-function
|
||||
* Example: serverLog(int level, const char *fmt, ...) should NOT be mocked.
|
||||
*/
|
||||
long long __wrap_aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds, aeTimeProc *proc, void *clientData, aeEventFinalizerProc *finalizerProc);
|
||||
void* __wrap_valkey_malloc(size_t size);
|
||||
void* __wrap_valkey_free(void* ptr);
|
||||
void *__wrap_valkey_calloc(size_t size);
|
||||
void * __wrap_valkey_realloc(void* ptr, size_t size);
|
||||
list* __wrap_listCreate();
|
||||
dict *__wrap_dictCreate(dictType *type);
|
||||
void __wrap_listRelease(struct list *list);
|
||||
|
||||
#undef protected
|
||||
#undef _Bool
|
||||
#undef typename
|
||||
|
||||
#endif
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -5923,8 +5923,8 @@ int getClientTypeByName(char *name) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
char *getClientTypeName(int class) {
|
||||
switch (class) {
|
||||
char *getClientTypeName(int client_class) {
|
||||
switch (client_class) {
|
||||
case CLIENT_TYPE_NORMAL: return "normal";
|
||||
case CLIENT_TYPE_REPLICA: return "slave";
|
||||
case CLIENT_TYPE_PUBSUB: return "pubsub";
|
||||
|
|
|
|||
|
|
@ -1240,9 +1240,9 @@ void sds_free(void *ptr) {
|
|||
* Template variables are specified using curly brackets, e.g. {variable}.
|
||||
* An opening bracket can be quoted by repeating it twice.
|
||||
*/
|
||||
sds sdstemplate(const char *template, sdstemplate_callback_t cb_func, void *cb_arg) {
|
||||
sds sdstemplate(const char *sds_template, sdstemplate_callback_t cb_func, void *cb_arg) {
|
||||
sds res = sdsempty();
|
||||
const char *p = template;
|
||||
const char *p = sds_template;
|
||||
|
||||
while (*p) {
|
||||
/* Find next variable, copy everything until there */
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ struct __attribute__((__packed__)) sdshdr64 {
|
|||
#define SDS_TYPE_64 4
|
||||
#define SDS_TYPE_MASK 7
|
||||
#define SDS_TYPE_BITS 3
|
||||
#define SDS_HDR_VAR(T, s) struct sdshdr##T *sh = (void *)((s) - (sizeof(struct sdshdr##T)));
|
||||
#define SDS_HDR_VAR(T, s) struct sdshdr##T *sh = (struct sdshdr##T *)((s) - (sizeof(struct sdshdr##T)))
|
||||
#define SDS_HDR(T, s) ((struct sdshdr##T *)((s) - (sizeof(struct sdshdr##T))))
|
||||
#define SDS_TYPE_5_LEN(f) ((unsigned char)(f) >> SDS_TYPE_BITS)
|
||||
|
||||
|
|
@ -256,7 +256,7 @@ int sdsneedsrepr(const_sds s);
|
|||
* substitution value. Returning a NULL indicates an error.
|
||||
*/
|
||||
typedef sds (*sdstemplate_callback_t)(const_sds variable, void *arg);
|
||||
sds sdstemplate(const char *template, sdstemplate_callback_t cb_func, void *cb_arg);
|
||||
sds sdstemplate(const char *sds_template, sdstemplate_callback_t cb_func, void *cb_arg);
|
||||
|
||||
/* Low level functions exposed to the user API */
|
||||
int sdsHdrSize(char type);
|
||||
|
|
|
|||
|
|
@ -7014,9 +7014,9 @@ static sds expandProcTitleTemplate(const char *template, const char *title) {
|
|||
return sdstrim(res, " ");
|
||||
}
|
||||
/* Validate the specified template, returns 1 if valid or 0 otherwise. */
|
||||
int validateProcTitleTemplate(const char *template) {
|
||||
int validateProcTitleTemplate(const char *templ) {
|
||||
int ok = 1;
|
||||
sds res = expandProcTitleTemplate(template, "");
|
||||
sds res = expandProcTitleTemplate(templ, "");
|
||||
if (!res) return 0;
|
||||
if (sdslen(res) == 0) ok = 0;
|
||||
sdsfree(res);
|
||||
|
|
|
|||
13
src/server.h
13
src/server.h
|
|
@ -57,7 +57,7 @@
|
|||
#include <systemd/sd-daemon.h>
|
||||
#endif
|
||||
|
||||
#ifndef static_assert
|
||||
#if !defined(static_assert) && !defined(__cplusplus)
|
||||
#define static_assert _Static_assert
|
||||
#endif
|
||||
|
||||
|
|
@ -2737,7 +2737,7 @@ uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
|||
void exitFromChild(int retcode);
|
||||
long long serverPopcount(void *s, long count);
|
||||
int serverSetProcTitle(char *title);
|
||||
int validateProcTitleTemplate(const char *template);
|
||||
int validateProcTitleTemplate(const char *templ);
|
||||
int serverCommunicateSystemd(const char *sd_notify_msg);
|
||||
void serverSetCpuAffinity(const char *cpulist);
|
||||
void dictVanillaFree(void *val);
|
||||
|
|
@ -2879,7 +2879,7 @@ int freeClientsInAsyncFreeQueue(void);
|
|||
int closeClientOnOutputBufferLimitReached(client *c, int async);
|
||||
int getClientType(client *c);
|
||||
int getClientTypeByName(char *name);
|
||||
char *getClientTypeName(int class);
|
||||
char *getClientTypeName(int client_class);
|
||||
void flushReplicasOutputBuffers(void);
|
||||
void disconnectReplicas(void);
|
||||
void evictClients(void);
|
||||
|
|
@ -4066,11 +4066,18 @@ void resetCommand(client *c);
|
|||
void failoverCommand(client *c);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#ifdef __cplusplus
|
||||
void *calloc(size_t count, size_t size) throw() __attribute__((deprecated));
|
||||
void free(void *ptr) throw() __attribute__((deprecated));
|
||||
void *malloc(size_t size) throw() __attribute__((deprecated));
|
||||
void *realloc(void *ptr, size_t size) throw() __attribute__((deprecated));
|
||||
#else
|
||||
void *calloc(size_t count, size_t size) __attribute__((deprecated));
|
||||
void free(void *ptr) __attribute__((deprecated));
|
||||
void *malloc(size_t size) __attribute__((deprecated));
|
||||
void *realloc(void *ptr, size_t size) __attribute__((deprecated));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Debugging stuff */
|
||||
void _serverAssertWithInfo(const client *c, const robj *o, const char *estr, const char *file, int line);
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@ typedef struct streamID {
|
|||
} streamID;
|
||||
|
||||
typedef struct stream {
|
||||
rax *cgroups; /* Consumer groups dictionary: name -> streamCG */
|
||||
rax *rax; /* The radix tree holding the stream. */
|
||||
uint64_t length; /* Current number of elements inside this stream. */
|
||||
streamID last_id; /* Zero if there are yet no items. */
|
||||
streamID first_id; /* The first non-tombstone entry, zero if empty. */
|
||||
streamID max_deleted_entry_id; /* The maximal ID that was deleted. */
|
||||
uint64_t entries_added; /* All time count of elements added. */
|
||||
rax *cgroups; /* Consumer groups dictionary: name -> streamCG */
|
||||
} stream;
|
||||
|
||||
/* We define an iterator to iterate stream items in an abstract way, without
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
## 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).
|
||||
Valkey uses a very simple C testing framework, built up over time but now based loosely off of [Unity](https://www.throwtheswitch.org/unity). Valkey now also supports [gtest unit tests](https://google.github.io/googletest/), see the `src/gtest/README.md` for details.
|
||||
|
||||
All test files are located at `src/unit/test_*`.
|
||||
All C unit test files are located at `src/unit/test_*`.
|
||||
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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue