# Unit tests using CTest include_directories(BEFORE ${MBEDTLS_INCLUDE_DIRS}) include_directories(${PROJECT_SOURCE_DIR}/src) include_directories(${PROJECT_BINARY_DIR}/src) include_directories(${SODIUM_INCLUDE_DIRS}) include_directories(${PCRE2_INCLUDE_DIRS}) include_directories(${CARES_INCLUDE_DIRS}) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") find_program(VALGRIND_EXECUTABLE NAMES valgrind) endif() # Some hosts (notably macOS, whose default soft limit is 256) start test # processes with too few file descriptors for descriptor-heavy tests such as # test_event_cpp. Raise the soft limit before exec'ing each test; a failure to # raise it (hard limit too low) is ignored so the test still runs. if(NOT WIN32) set(SS_TEST_LAUNCHER sh -c "ulimit -n 1024 2>/dev/null\; exec \"$0\" \"$@\"") endif() # "crypto.h" and friends come from the C reference implementation. include_directories(${CMAKE_CURRENT_SOURCE_DIR}/reference) # Helper function to add a unit test function(ss_add_test name sources libs) if(WIN32 AND "${sources}" MATCHES "utils.cpp") list(APPEND sources ${PROJECT_SOURCE_DIR}/src/winsock.cpp) endif() add_executable(${name} ${sources}) target_compile_definitions(${name} PRIVATE -DHAVE_CONFIG_H) target_link_libraries(${name} ${libs}) if(WIN32) target_link_libraries(${name} ws2_32) endif() # Keep assertions enabled for tests even in Release builds. ss_project_options(${name}) if(MSVC) target_compile_options(${name} PRIVATE /UNDEBUG) else() target_compile_options(${name} PRIVATE -UNDEBUG) endif() add_test(NAME ${name} COMMAND ${SS_TEST_LAUNCHER} $) set_tests_properties(${name} PROPERTIES LABELS "unit") if(VALGRIND_EXECUTABLE) add_test(NAME memcheck_${name} COMMAND ${SS_TEST_LAUNCHER} ${VALGRIND_EXECUTABLE} --tool=memcheck --leak-check=full --show-leak-kinds=definite,indirect --errors-for-leak-kinds=definite,indirect --error-exitcode=1 --track-origins=yes --quiet $) set_tests_properties(memcheck_${name} PROPERTIES LABELS "memcheck" TIMEOUT 120) endif() endfunction() # C++ core (src/core): header-only ownership and allocation primitives. foreach(core_test result pool buffer arena fixed_map ownership) ss_add_test(test_core_${core_test} "test_core_${core_test}.cpp" "") ss_strict_warnings(test_core_${core_test}.cpp) endforeach() # The embedding API on the static library (POSIX: the test uses BSD sockets). if(TARGET shadowsocks-c-static AND UNIX) add_executable(test_local_api_cpp test_local_api_cpp.cpp) target_link_libraries(test_local_api_cpp PRIVATE shadowsocks-c-static Threads::Threads) target_compile_options(test_local_api_cpp PRIVATE -UNDEBUG) target_include_directories(test_local_api_cpp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) ss_project_options(test_local_api_cpp) ss_strict_warnings(test_local_api_cpp.cpp) add_test(NAME test_local_api_cpp COMMAND test_local_api_cpp) set_tests_properties(test_local_api_cpp PROPERTIES LABELS "unit" TIMEOUT 60) endif() # C++ event layer (src/event) over libuv. ss_add_test(test_event_cpp "test_event_cpp.cpp" "ss_uv") ss_strict_warnings(test_event_cpp.cpp) set_tests_properties(test_event_cpp PROPERTIES TIMEOUT 30) # test_heap_guard_cpp - library heaps fall back instead of failing a realloc ss_add_test(test_heap_guard_cpp "test_heap_guard_cpp.cpp;${PROJECT_SOURCE_DIR}/src/core/heap_guard.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp" "ss_uv;ss_cares;ss_mbedcrypto;ss_sodium") ss_strict_warnings(test_heap_guard_cpp.cpp) # C++ sources under src/ must not allocate on the heap (see the script). find_package(Python3 COMPONENTS Interpreter QUIET) if(Python3_Interpreter_FOUND) add_test(NAME check_no_heap COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/check_no_heap.py --self-test) set_tests_properties(check_no_heap PROPERTIES LABELS "unit") # The programs and the static library must stay usable without a C++ # runtime library (see docs/cpp-core.md). Sanitizer runtimes bring their # own C++ symbols, so skip the check there. set(SS_RUNTIME_CHECKED) foreach(target ss-local ss-server ss-tunnel ss-redir ss-manager shadowsocks-c-static) if(TARGET ${target}) get_target_property(excluded ${target} EXCLUDE_FROM_ALL) if(NOT excluded) list(APPEND SS_RUNTIME_CHECKED $) endif() endif() endforeach() if(SS_RUNTIME_CHECKED AND CMAKE_NM AND NOT ENABLE_SANITIZERS) add_test(NAME check_cxx_runtime COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/check_cxx_runtime.py --nm ${CMAKE_NM} ${SS_RUNTIME_CHECKED}) set_tests_properties(check_cxx_runtime PROPERTIES LABELS "unit") endif() endif() # Shared-reference ownership and consuming pointer moves. ss_add_test(test_ref "test_ref_cpp.cpp" "") # test_base64 - no external deps ss_add_test(test_base64 "test_base64_cpp.cpp;${PROJECT_SOURCE_DIR}/src/base64.cpp" "") # test_utils - needs sodium (for ss_malloc -> uses sodium in utils.cpp) ss_add_test(test_utils "test_utils_cpp.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp" "ss_sodium") # test_json - standalone json parser ss_add_test(test_json "test_json_cpp.cpp;${PROJECT_SOURCE_DIR}/src/json.cpp" "${SS_MATH_LIBRARY}") # test_ssurl - ss:// URI parsing (SIP002 and legacy) ss_add_test(test_ssurl "test_ssurl_cpp.cpp;${PROJECT_SOURCE_DIR}/src/ssurl.cpp;${PROJECT_SOURCE_DIR}/src/base64.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp" "ss_sodium") # test_netutils - address parsing and resolver integration set(TEST_NETUTILS_LIBS ss_sodium ss_ev ss_cares ) ss_add_test(test_netutils "test_netutils_cpp.cpp;${PROJECT_SOURCE_DIR}/src/netutils.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp" "${TEST_NETUTILS_LIBS}") # test_cache - expiration uses a platform monotonic clock ss_add_test(test_cache "test_cache_reference_cpp.cpp;${CMAKE_CURRENT_SOURCE_DIR}/reference/cache.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp" "ss_sodium") # test_cache_cpp - fixed-capacity cache edge cases ss_add_test(test_cache_cpp "test_cache_cpp.cpp;${CMAKE_CURRENT_SOURCE_DIR}/reference/cache.cpp" "ss_sodium") ss_strict_warnings(test_cache_cpp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/reference/cache.cpp) # test_ppbloom - needs bloom set(TEST_PPBLOOM_LIBS "") list(APPEND TEST_PPBLOOM_LIBS bloom) ss_add_test(test_ppbloom "test_ppbloom_cpp.cpp;${PROJECT_SOURCE_DIR}/src/ppbloom.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp" "${TEST_PPBLOOM_LIBS};ss_sodium;${SS_MATH_LIBRARY}") if(SS_ENABLE_REGEX) # test_rule - regex matching set(TEST_RULE_LIBS ss_pcre2 ss_sodium ) ss_add_test(test_rule_cpp "test_rule_cpp.cpp;${PROJECT_SOURCE_DIR}/src/rule.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp" "${TEST_RULE_LIBS}") # test_acl - ACL precedence and prefix matching set(TEST_ACL_LIBS ${TEST_RULE_LIBS}) ss_add_test(test_acl "test_acl_cpp.cpp;${PROJECT_SOURCE_DIR}/src/acl.cpp;${PROJECT_SOURCE_DIR}/src/ipset.cpp;${PROJECT_SOURCE_DIR}/src/rule.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp" "${TEST_ACL_LIBS}") endif() # test_jconf - configuration parsing set(TEST_JCONF_LIBS ss_sodium ss_ev ss_cares ) ss_add_test(test_jconf "test_jconf_cpp.cpp;${PROJECT_SOURCE_DIR}/src/jconf.cpp;${PROJECT_SOURCE_DIR}/src/json.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp;${PROJECT_SOURCE_DIR}/src/netutils.cpp" "${TEST_JCONF_LIBS};${SS_MATH_LIBRARY}") # The original C cipher implementation, kept under tests/reference as an # independent oracle: the C++ codecs are tested against it byte for byte, # and udp_session_eviction_client speaks to ss-server with it. The programs # no longer contain it. set(SS_CRYPTO_TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/reference/crypto.cpp ${CMAKE_CURRENT_SOURCE_DIR}/reference/aead.cpp ${CMAKE_CURRENT_SOURCE_DIR}/reference/aead2022.cpp ${CMAKE_CURRENT_SOURCE_DIR}/reference/aead2022_udp.cpp ${PROJECT_SOURCE_DIR}/src/ppbloom.cpp ${CMAKE_CURRENT_SOURCE_DIR}/reference/cache.cpp ${PROJECT_SOURCE_DIR}/src/base64.cpp ${PROJECT_SOURCE_DIR}/src/utils.cpp ) # test_buffer - needs sodium and mbedtls for crypto.cpp buffer functions ss_add_test(test_buffer "test_buffer_cpp.cpp;${SS_CRYPTO_TEST_SOURCES}" "ss_sodium;;ss_mbedcrypto;ss_ev;${TEST_PPBLOOM_LIBS};blake3;${SS_MATH_LIBRARY}") # test_crypto - full crypto test set(TEST_CRYPTO_LIBS ss_sodium ss_mbedcrypto ss_ev ) list(APPEND TEST_CRYPTO_LIBS bloom) # BLAKE3 backs the 2022 ciphers' key derivation list(APPEND TEST_CRYPTO_LIBS blake3) ss_add_test(test_crypto "test_crypto_cpp.cpp;${SS_CRYPTO_TEST_SOURCES}" "${TEST_CRYPTO_LIBS};${SS_MATH_LIBRARY}") # test_aead2022 - Shadowsocks 2022 Edition (SIP022) ciphers ss_add_test(test_aead2022 "test_aead2022_cpp.cpp;${SS_CRYPTO_TEST_SOURCES}" "${TEST_CRYPTO_LIBS};${SS_MATH_LIBRARY}") # test_crypto_aead_cpp - C++ AEAD codecs, differentially against aead.cpp ss_add_test(test_crypto_aead_cpp "test_crypto_aead_cpp.cpp;${PROJECT_SOURCE_DIR}/src/crypto/aead.cpp;${PROJECT_SOURCE_DIR}/src/crypto/keys.cpp;${SS_CRYPTO_TEST_SOURCES}" "${TEST_CRYPTO_LIBS};${SS_MATH_LIBRARY}") ss_strict_warnings(test_crypto_aead_cpp.cpp ${PROJECT_SOURCE_DIR}/src/crypto/aead.cpp) # test_crypto_aead2022_cpp - C++ SIP022 codecs, differentially against aead2022.cpp ss_add_test(test_crypto_aead2022_cpp "test_crypto_aead2022_cpp.cpp;${PROJECT_SOURCE_DIR}/src/crypto/aead2022.cpp;${PROJECT_SOURCE_DIR}/src/crypto/aead.cpp;${PROJECT_SOURCE_DIR}/src/crypto/keys.cpp;${SS_CRYPTO_TEST_SOURCES}" "${TEST_CRYPTO_LIBS};${SS_MATH_LIBRARY}") ss_strict_warnings(test_crypto_aead2022_cpp.cpp ${PROJECT_SOURCE_DIR}/src/crypto/aead2022.cpp) # test_interop_rust - interoperability against shadowsocks-rust. # Skips itself when shadowsocks-rust or the built binaries are absent. add_test(NAME test_interop_rust COMMAND bash "${PROJECT_SOURCE_DIR}/tests/test_interop_rust.sh" WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}") set_tests_properties(test_interop_rust PROPERTIES LABELS "integration" TIMEOUT 300 SKIP_RETURN_CODE 77 ENVIRONMENT "SS_BIN_DIR=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") # test_ss_setup - bash unit tests for the ss-setup TUI tool add_test(NAME test_ss_setup COMMAND bash "${PROJECT_SOURCE_DIR}/tests/test_ss_setup.sh" WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}") set_tests_properties(test_ss_setup PROPERTIES LABELS "integration") ss_add_test(test_ipset "test_ipset_cpp.cpp;${PROJECT_SOURCE_DIR}/src/ipset.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp" "ss_sodium") ss_add_test(test_process "test_process_cpp.cpp;${PROJECT_SOURCE_DIR}/src/process.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp" "ss_sodium") set_tests_properties(test_process PROPERTIES TIMEOUT 20) if(UNIX) # The resolver against a delayed local DNS server. ss_add_test(test_resolver_cpp "test_resolver_cpp.cpp;${PROJECT_SOURCE_DIR}/src/relay/resolver.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp" "ss_ev;ss_cares;ss_sodium") ss_strict_warnings(test_resolver_cpp.cpp) set_tests_properties(test_resolver_cpp PROPERTIES TIMEOUT 30) endif() set(TEST_LITERAL_LIBS ss_sodium) if(SS_ENABLE_REGEX) list(APPEND TEST_LITERAL_LIBS ss_pcre2) endif() ss_add_test(test_acl_literal "test_acl_literal_cpp.cpp;${PROJECT_SOURCE_DIR}/src/acl.cpp;${PROJECT_SOURCE_DIR}/src/ipset.cpp;${PROJECT_SOURCE_DIR}/src/rule.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp" "${TEST_LITERAL_LIBS}") # Tests ported from C build with the full C++ warning set. test_ipset_reference # is intentionally absent: it needs the pre-migration libipset archives. ss_strict_warnings( test_acl_cpp.cpp test_acl_literal_cpp.cpp test_aead2022_cpp.cpp test_base64_cpp.cpp test_buffer_cpp.cpp test_cache_reference_cpp.cpp test_crypto_cpp.cpp test_ipset_cpp.cpp test_jconf_cpp.cpp test_json_cpp.cpp test_netutils_cpp.cpp test_ppbloom_cpp.cpp test_process_cpp.cpp test_ref_cpp.cpp test_ssurl_cpp.cpp test_utils_cpp.cpp) # Preloadable malloc counter for the allocation audit. It interposes the # system allocator, so it is only built where that is supported (glibc, # macOS) and never together with the sanitizers, which own malloc. include(CheckSymbolExists) check_symbol_exists(__GLIBC__ "stdlib.h" SS_HAVE_GLIBC) if(SS_BUILD_EXECUTABLES AND NOT ENABLE_SANITIZERS AND (APPLE OR SS_HAVE_GLIBC)) add_library(ss_alloc_audit SHARED alloc_audit/alloc_counter.c) endif() if(SS_BUILD_EXECUTABLES AND NOT CMAKE_CROSSCOMPILING) find_package(Python3 COMPONENTS Interpreter QUIET) if(Python3_Interpreter_FOUND) add_test(NAME test_cli COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_cli.py --bin-dir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) set_tests_properties(test_cli PROPERTIES LABELS "integration" TIMEOUT 60) if(UNIX) add_executable(udp_session_eviction_client udp_session_eviction_client.c ${SS_CRYPTO_TEST_SOURCES}) target_compile_definitions(udp_session_eviction_client PRIVATE HAVE_CONFIG_H) target_compile_options(udp_session_eviction_client PRIVATE -UNDEBUG) target_link_libraries(udp_session_eviction_client ${TEST_CRYPTO_LIBS} ${SS_MATH_LIBRARY}) ss_project_options(udp_session_eviction_client) add_test(NAME test_udp_session_eviction COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_udp_session_eviction.py --bin ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) set_tests_properties(test_udp_session_eviction PROPERTIES LABELS "integration" TIMEOUT 300) add_test(NAME test_experiment_supervisor COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/experiments/test_supervisor.py --bin ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) set_tests_properties(test_experiment_supervisor PROPERTIES LABELS "integration" TIMEOUT 30) if(TARGET ss-manager) add_test(NAME test_manager COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_manager.py --bin ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) set_tests_properties(test_manager PROPERTIES LABELS "integration" TIMEOUT 60) endif() endif() add_test(NAME test_experimental_transports COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/experiments/test_transport.py) set_tests_properties(test_experimental_transports PROPERTIES LABELS "integration" TIMEOUT 30) # Steady-state allocation audit (report-only until the C++ relay # lands; see tests/alloc_audit/measure_alloc.py). if(TARGET ss_alloc_audit) # The C++ codecs must not allocate once initialised. add_test(NAME alloc_steady_crypto_aead COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/alloc_audit/check_steady.py --shim $ -- $) set_tests_properties(alloc_steady_crypto_aead PROPERTIES LABELS "unit" TIMEOUT 60) add_test(NAME alloc_steady_crypto_aead2022 COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/alloc_audit/check_steady.py --shim $ -- $) set_tests_properties(alloc_steady_crypto_aead2022 PROPERTIES LABELS "unit" TIMEOUT 60) add_test(NAME alloc_steady_cache COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/alloc_audit/check_steady.py --shim $ -- $) set_tests_properties(alloc_steady_cache PROPERTIES LABELS "unit" TIMEOUT 60) # The programs: no heap allocation per relayed connection once # started, by name (DNS through the bounded c-ares heap) and by # address, for each cipher family. set(SS_ALLOC_CASES "aes-256-gcm@localhost" "2022-blake3-aes-256-gcm@127.0.0.1" "chacha20-ietf-poly1305@127.0.0.1") foreach(case ${SS_ALLOC_CASES}) string(REPLACE "@" ";" parts "${case}") list(GET parts 0 method) list(GET parts 1 target) add_test(NAME alloc_steady_programs_${method} COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/alloc_audit/measure_alloc.py --bin ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} --shim $ --method ${method} --target ${target} --max-per-connection 0) set_tests_properties(alloc_steady_programs_${method} PROPERTIES LABELS "integration" TIMEOUT 120) endforeach() foreach(method aes-256-gcm 2022-blake3-aes-256-gcm) add_test(NAME alloc_steady_programs_udp_${method} COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/alloc_audit/measure_alloc.py --bin ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} --shim $ --method ${method} --udp --warmup 20 --connections 50 --max-per-connection 0) set_tests_properties(alloc_steady_programs_udp_${method} PROPERTIES LABELS "integration" TIMEOUT 120) endforeach() endif() add_test(NAME test_printable_salt_interop COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/interop.py --self --printable-salt --bin ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) set_tests_properties(test_printable_salt_interop PROPERTIES LABELS "integration" TIMEOUT 90) # ss-tunnel through the C relay engine against ss-server (and # shadowsocks-rust when installed), every method. set(SS_TUNNEL_INTEROP_ARGS --bin ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) add_test(NAME test_limits COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_limits.py --bin ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) set_tests_properties(test_limits PROPERTIES LABELS "integration" TIMEOUT 60) add_test(NAME test_tunnel_interop COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tunnel_interop.py ${SS_TUNNEL_INTEROP_ARGS}) set_tests_properties(test_tunnel_interop PROPERTIES LABELS "integration" TIMEOUT 300) endif() endif() # The zero-allocation checks hold for the bundled Mbed TLS, which is built # with MBEDTLS_BLOCK_CIPHER_C; a system Mbed TLS may allocate during GCM key # setup. Keep those tests visible but disabled in system-dependency builds. if(NOT SS_DEPENDENCY_MODE STREQUAL "bundled") get_property(ss_all_tests DIRECTORY PROPERTY TESTS) foreach(ss_test IN LISTS ss_all_tests) if(ss_test MATCHES "^alloc_steady_(crypto|programs)") set_tests_properties(${ss_test} PROPERTIES DISABLED TRUE) endif() endforeach() endif()