diff --git a/.github/actions/cmake/build/action.yml b/.github/actions/cmake/build/action.yml index 4c089a30c..782c04b96 100644 --- a/.github/actions/cmake/build/action.yml +++ b/.github/actions/cmake/build/action.yml @@ -11,7 +11,8 @@ runs: -DCMAKE_INSTALL_PREFIX:PATH=${INSTALL_PREFIX:-/usr} \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=${INSTALL_RUNSTATEDIR:-/run} \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=${INSTALL_SYSCONFDIR:-/etc} \ - -DENABLE_TESTS:BOOL=ON + -DENABLE_TESTS:BOOL=ON \ + ${EXTRA_CMAKE_OPTIONS:-} shell: bash --noprofile --norc -euxo pipefail {0} - name: Build `mod_tile` diff --git a/.github/workflows/sanitizers-and-warnings.yml b/.github/workflows/sanitizers-and-warnings.yml new file mode 100644 index 000000000..eccbd1180 --- /dev/null +++ b/.github/workflows/sanitizers-and-warnings.yml @@ -0,0 +1,113 @@ +--- +name: Sanitizers & Warnings + +on: + pull_request: + push: + +jobs: + Warnings: + name: >- + ${{ matrix.image }} + (-Werror) + (${{ matrix.compiler }}) + runs-on: ubuntu-latest + strategy: + matrix: + build_system: + - CMake + compiler: + - GNU + - LLVM + image: + - "ubuntu:24.04" + fail-fast: false + container: + env: + CC: ${{ matrix.compiler == 'LLVM' && 'clang' || 'gcc' }} + CXX: ${{ matrix.compiler == 'LLVM' && 'clang++' || 'g++' }} + EXTRA_CMAKE_OPTIONS: -DENABLE_WARNINGS:BOOL=ON -DENABLE_WERROR:BOOL=ON + image: ${{ matrix.image }} + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Install dependencies + uses: ./.github/actions/dependencies/install + + - name: Build `mod_tile` + uses: ./.github/actions/cmake/build + + - name: Test `mod_tile` + uses: ./.github/actions/cmake/test + + Sanitizers: + name: >- + ${{ matrix.name }} + (${{ matrix.compiler }}) + # Run on the host rather than in a container so that `vm.mmap_rnd_bits` can be + # lowered, which ThreadSanitizer requires on recent kernels + runs-on: ubuntu-24.04 + strategy: + matrix: + build_system: + - CMake + compiler: + - GNU + sanitizers: + - address,undefined + - thread + include: + - sanitizers: address,undefined + name: ASan + UBSan + # Includes the Apache/renderd integration tests (the ASan runtime is preloaded into httpd) + test_options: --exclude-regex 'clear_dirs_.+|remove_tile_.+' --output-on-failure + - sanitizers: thread + name: TSan + # TSan cannot be preloaded into the system httpd, so only the unit test executables are run + test_options: --tests-regex '_test$|^unit_tests$' --output-on-failure + fail-fast: false + env: + ASAN_OPTIONS: detect_leaks=0 + BUILD_TYPE: Debug + CC: gcc + CXX: g++ + EXTRA_CMAKE_OPTIONS: -DENABLE_SANITIZERS:STRING=${{ matrix.sanitizers }} + TSAN_OPTIONS: halt_on_error=1:second_deadlock_stack=1:suppressions=${{ github.workspace }}/tests/tsan.supp + UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1 + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Lower `vm.mmap_rnd_bits` for ThreadSanitizer + run: sudo sysctl vm.mmap_rnd_bits=28 + if: matrix.sanitizers == 'thread' + + - name: Install dependencies + uses: ./.github/actions/dependencies/install + + # Unlike in containers, installing the packages on the host starts their system services, + # whose default ports (e.g. memcached on 11211) collide with the test services + - name: Stop system `apache2` & `memcached` services + run: sudo systemctl stop apache2 memcached || true + + - name: Build `mod_tile` + uses: ./.github/actions/cmake/build + + - name: Test `mod_tile` + uses: ./.github/actions/cmake/test + with: + options: ${{ matrix.test_options }} + + # Services started by the integration tests (httpd, renderd) report sanitizer errors to + # their own log files and may still exit "successfully", so check those logs too + - name: Check service logs for sanitizer reports + run: | + if [ ! -d build/tests/logs ]; then + exit 0 + fi + if grep -rlaE 'ERROR: AddressSanitizer|runtime error:|WARNING: ThreadSanitizer' build/tests/logs; then + grep -raEA30 'ERROR: AddressSanitizer|runtime error:|WARNING: ThreadSanitizer' build/tests/logs + exit 1 + fi + shell: bash --noprofile --norc -euo pipefail {0} diff --git a/CMakeLists.txt b/CMakeLists.txt index ffbc85e73..8e1a03fec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ # #----------------------------------------------------------------------------- -cmake_minimum_required(VERSION 3.13 FATAL_ERROR) +cmake_minimum_required(VERSION 3.15 FATAL_ERROR) list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") #----------------------------------------------------------------------------- @@ -21,17 +21,20 @@ project(mod_tile set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_CXX_STANDARD 11 CACHE STRING "Sets the C++ standard.") +set(CMAKE_CXX_STANDARD 14 CACHE STRING "Sets the C++ standard (C++14 minimum; Catch2 v3 requires it).") set(CMAKE_CXX_STANDARD_REQUIRED ON) set(THREADS_PREFER_PTHREAD_FLAG ON) set(CMAKE_INSTALL_MODULESDIR CACHE PATH "Apache HTTP Server module installation directory") -set(ENABLE_MAN ON CACHE BOOL "Build man pages") -set(ENABLE_TESTS OFF CACHE BOOL "Build test suite") -set(USE_CAIRO ON CACHE BOOL "Add cairo support if available (for `store_ro_composite.c` backend)") -set(USE_CURL ON CACHE BOOL "Add curl support if available (for `store_ro_http_proxy.c` backend)") -set(USE_MEMCACHED ON CACHE BOOL "Add memcached support if available (for `store_memcached.c` backend)") -set(USE_RADOS ON CACHE BOOL "Add rados support if available (for `store_rados.c` backend)") +option(ENABLE_MAN "Build man pages" ON) +option(ENABLE_TESTS "Build test suite" OFF) +option(ENABLE_WARNINGS "Compile with extra warnings (-Wall -Wextra)" OFF) +option(ENABLE_WERROR "Treat compiler warnings as errors (requires ENABLE_WARNINGS)" OFF) +set(ENABLE_SANITIZERS "" CACHE STRING "Comma-separated sanitizers to build with, e.g. 'address,undefined' or 'thread'") +option(USE_CAIRO "Add cairo support if available (for `store_ro_composite.c` backend)" ON) +option(USE_CURL "Add curl support if available (for `store_ro_http_proxy.c` backend)" ON) +option(USE_MEMCACHED "Add memcached support if available (for `store_memcached.c` backend)" ON) +option(USE_RADOS "Add rados support if available (for `store_rados.c` backend)" ON) set(MALLOC_LIB "libc" CACHE STRING "Memory Management Library for `renderd`") set_property(CACHE MALLOC_LIB PROPERTY STRINGS "libc" "jemalloc" "mimalloc" "tcmalloc") @@ -41,9 +44,11 @@ set_property(CACHE MALLOC_LIB PROPERTY STRINGS "libc" "jemalloc" "mimalloc" "tcm # #----------------------------------------------------------------------------- +include(FeatureSummary) include(GNUInstallDirs) # Packages +find_package(PkgConfig REQUIRED) find_package(ICU REQUIRED uc) find_package(Threads REQUIRED) @@ -55,6 +60,10 @@ find_package(LIBMAPNIK 3 REQUIRED) if(LIBMAPNIK_VERSION VERSION_GREATER_EQUAL 4) set(CMAKE_CXX_STANDARD 17) +elseif(CMAKE_CXX_STANDARD LESS 14) + # e.g. an existing build directory whose cache still holds the old default of 11 + message(WARNING "CMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} is too old (Catch2 v3 needs C++14), using 14") + set(CMAKE_CXX_STANDARD 14) endif() if(USE_CURL) @@ -171,8 +180,12 @@ set(RENDERD_TILE_DIR "${CMAKE_INSTALL_LOCALSTATEDIR}/cache/renderd/tiles") set(RENDERD_PIDFILE "${RENDERD_RUN_DIR}/renderd.pid") set(RENDERD_SOCKET "${RENDERD_RUN_DIR}/renderd.sock") -set(TILE_LOAD_DIRECTORY "${HTTPD_SYSCONFDIR}") -set(TILE_LOAD_FILENAME "tile.load") +if(NOT DEFINED CACHE{TILE_LOAD_DIRECTORY}) + set(TILE_LOAD_DIRECTORY "${HTTPD_SYSCONFDIR}") +endif() +if(NOT DEFINED CACHE{TILE_LOAD_FILENAME}) + set(TILE_LOAD_FILENAME "tile.load") +endif() set(VERSION "${PROJECT_VERSION}") #----------------------------------------------------------------------------- @@ -182,9 +195,10 @@ set(VERSION "${PROJECT_VERSION}") #----------------------------------------------------------------------------- # include/config.h.in +# (generated into the build tree so that multiple build directories do not clobber each other) configure_file( ${PROJECT_SOURCE_DIR}/includes/config.h.in - ${PROJECT_SOURCE_DIR}/includes/config.h + ${PROJECT_BINARY_DIR}/includes/config.h ) # etc/apache2/tile.load.in @@ -199,6 +213,32 @@ configure_file( ${PROJECT_BINARY_DIR}/renderd.conf ) +#----------------------------------------------------------------------------- +# +# Compiler flags +# +#----------------------------------------------------------------------------- + +if(ENABLE_WARNINGS) + add_compile_options(-Wall -Wextra -Wno-unused-parameter) + # Log messages and storage IDs are deliberately truncated to PATH_MAX-sized buffers, + # so GCC's -Wformat-truncation heuristic only produces false positives here. + include(CheckCCompilerFlag) + check_c_compiler_flag(-Wformat-truncation HAVE_WFORMAT_TRUNCATION) + if(HAVE_WFORMAT_TRUNCATION) + add_compile_options(-Wno-format-truncation) + endif() + if(ENABLE_WERROR) + add_compile_options(-Werror) + endif() +endif() + +if(ENABLE_SANITIZERS) + message(STATUS "Building with sanitizers: ${ENABLE_SANITIZERS}") + add_compile_options(-fsanitize=${ENABLE_SANITIZERS} -fno-omit-frame-pointer) + add_link_options(-fsanitize=${ENABLE_SANITIZERS}) +endif() + #----------------------------------------------------------------------------- # # Build @@ -214,7 +254,11 @@ add_subdirectory(src) #----------------------------------------------------------------------------- # Determine install destination for 'etc/apache2/tile.load.in' -if(EXISTS "/etc/os-release") +# (auto-detected from the build host unless TILE_LOAD_DIRECTORY / TILE_LOAD_FILENAME are set explicitly, +# which packagers and cross-compilers should do) +if(DEFINED CACHE{TILE_LOAD_DIRECTORY} OR DEFINED CACHE{TILE_LOAD_FILENAME}) + message(STATUS "Using user-supplied TILE_LOAD_DIRECTORY / TILE_LOAD_FILENAME") +elseif(EXISTS "/etc/os-release") execute_process(COMMAND sh -c ". /etc/os-release && echo $ID" OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE DISTRO_ID @@ -320,3 +364,16 @@ if(ENABLE_TESTS) enable_testing() add_subdirectory(tests) endif() + +#----------------------------------------------------------------------------- +# +# Summary +# +#----------------------------------------------------------------------------- + +add_feature_info(cairo CAIRO_FOUND "store_ro_composite backend") +add_feature_info(curl CURL_FOUND "store_ro_http_proxy backend") +add_feature_info(memcached LIBMEMCACHED_FOUND "store_memcached backend") +add_feature_info(rados LIBRADOS_FOUND "store_rados backend") +add_feature_info(tests ENABLE_TESTS "Catch2 test suite") +feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 000000000..cdd8ea94e --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,84 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "default", + "displayName": "Release", + "description": "Release build installed under /usr with FHS paths", + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", + "CMAKE_INSTALL_LOCALSTATEDIR": "/var", + "CMAKE_INSTALL_PREFIX": "/usr", + "CMAKE_INSTALL_RUNSTATEDIR": "/run", + "CMAKE_INSTALL_SYSCONFDIR": "/etc" + } + }, + { + "name": "dev", + "displayName": "Development", + "description": "Debug build with tests and extra warnings", + "inherits": "default", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "ENABLE_TESTS": "ON", + "ENABLE_WARNINGS": "ON" + } + }, + { + "name": "asan", + "displayName": "AddressSanitizer + UBSan", + "description": "Debug build with tests, instrumented with ASan and UBSan", + "inherits": "dev", + "cacheVariables": { + "ENABLE_SANITIZERS": "address,undefined" + } + }, + { + "name": "tsan", + "displayName": "ThreadSanitizer", + "description": "Debug build with tests, instrumented with TSan (for renderd threading)", + "inherits": "dev", + "cacheVariables": { + "ENABLE_SANITIZERS": "thread" + } + } + ], + "buildPresets": [ + { "name": "default", "configurePreset": "default" }, + { "name": "dev", "configurePreset": "dev" }, + { "name": "asan", "configurePreset": "asan" }, + { "name": "tsan", "configurePreset": "tsan" } + ], + "testPresets": [ + { + "name": "dev", + "configurePreset": "dev", + "output": { "outputOnFailure": true } + }, + { + "name": "asan", + "configurePreset": "asan", + "output": { "outputOnFailure": true }, + "environment": { + "ASAN_OPTIONS": "detect_leaks=0", + "UBSAN_OPTIONS": "halt_on_error=1:print_stacktrace=1" + } + }, + { + "name": "tsan", + "configurePreset": "tsan", + "output": { "outputOnFailure": true }, + "filter": { "include": { "name": "_test$|^unit_tests$" } }, + "environment": { + "TSAN_OPTIONS": "suppressions=${sourceDir}/tests/tsan.supp" + } + } + ] +} diff --git a/Makefile.am b/Makefile.am index aa31f1c09..08483e64d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,7 +2,7 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 -AM_CPPFLAGS = $(PTHREAD_CFLAGS) $(GLIB_CFLAGS) +AM_CPPFLAGS = $(PTHREAD_CFLAGS) $(GLIB_CFLAGS) $(INIPARSER_CFLAGS) STORE_SOURCES = \ src/g_logger.c \ @@ -98,7 +98,9 @@ render_speedtest_LDADD = $(PTHREAD_CFLAGS) $(GLIB_LIBS) $(INIPARSER_LDFLAGS) #convert_meta_SOURCES = src/dir_utils.c src/store.c src/convert_meta.c -noinst_LIBRARIES = catch_main.o catch_test_common.o +noinst_LIBRARIES = catch2.o catch_main.o catch_test_common.o +catch2_o_SOURCES = tests/catch/catch_amalgamated.cpp +catch2_o_CPPFLAGS = -DCATCH_AMALGAMATED_CUSTOM_MAIN catch_main_o_SOURCES = tests/catch_main.cpp catch_test_common_o_SOURCES = tests/catch_test_common.cpp @@ -107,32 +109,32 @@ gen_tile_test_SOURCES = \ $(renderd_SOURCES) gen_tile_test_CFLAGS = -DMAIN_ALREADY_DEFINED gen_tile_test_CXXFLAGS = $(renderd_CXXFLAGS) -gen_tile_test_LDADD = $(renderd_LDADD) catch_test_common.o +gen_tile_test_LDADD = $(renderd_LDADD) catch_test_common.o catch2.o renderd_test_SOURCES = \ tests/renderd_test.cpp -renderd_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o +renderd_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o catch2.o render_expired_test_SOURCES = \ tests/render_expired_test.cpp -render_expired_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o +render_expired_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o catch2.o render_list_test_SOURCES = \ tests/render_list_test.cpp -render_list_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o +render_list_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o catch2.o render_old_test_SOURCES = \ tests/render_old_test.cpp -render_old_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o +render_old_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o catch2.o render_speedtest_test_SOURCES = \ tests/render_speedtest_test.cpp -render_speedtest_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o +render_speedtest_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o catch2.o unit_tests_SOURCES = \ tests/unit_tests.cpp unit_tests_CXXFLAGS = -Isrc -unit_tests_LDADD = $(GLIB_LIBS) $(INIPARSER_LDFLAGS) $(MAPNIK_LDFLAGS) $(PTHREAD_CFLAGS) catch_main.o catch_test_common.o +unit_tests_LDADD = $(GLIB_LIBS) $(INIPARSER_LDFLAGS) $(MAPNIK_LDFLAGS) $(PTHREAD_CFLAGS) catch_main.o catch_test_common.o catch2.o CLEANFILES=*.slo mod_tile.la stderr.out src/*.slo src/*.lo src/.libs/* src/*.la diff --git a/README.rst b/README.rst index 77b27e348..0e5550388 100644 --- a/README.rst +++ b/README.rst @@ -27,7 +27,7 @@ Dependencies * `macOS` * `Supported Build Systems` * `CMake `__ - * `GNU Autotools `__ + * `GNU Autotools `__ (deprecated, use CMake) * `Runtime/Build Dependencies` * `Apache 2 HTTP webserver `__ * `Cairo 2D graphics library (optional) `__ diff --git a/configure.ac b/configure.ac index b024890ba..29664817a 100644 --- a/configure.ac +++ b/configure.ac @@ -1,14 +1,18 @@ # -*- Autoconf -*- # Process this file with autoconf to produce a configure script. -# Define mod_tile version number -m4_define([mod_tile_version], [0.8.1]) +# NOTE: The Autotools build is deprecated; CMake is the supported build system. + +# mod_tile version number, taken from CMakeLists.txt so it is only defined in one place +m4_define([mod_tile_version], + m4_esyscmd_s([sed -n 's/^ VERSION \([0-9][0-9.]*\)$/\1/p' CMakeLists.txt])) +m4_if(mod_tile_version, [], [m4_fatal([Could not read the project version from CMakeLists.txt])]) AC_PREREQ([2.61]) AX_CONFIG_NICE AC_INIT([mod_tile], [mod_tile_version], - [http://trac.openstreetmap.org]) + [https://github.com/openstreetmap/mod_tile/issues]) AM_INIT_AUTOMAKE([subdir-objects]) LT_INIT AC_CONFIG_SRCDIR([src/mod_tile.c]) @@ -64,10 +68,15 @@ AC_CHECK_LIB(rados, rados_version, [ LIBRADOS_LDFLAGS='-lrados' AC_SUBST(LIBRADOS_LDFLAGS) ][]) -AC_CHECK_LIB(iniparser, iniparser_load, [ - INIPARSER_LDFLAGS='-liniparser' +PKG_CHECK_MODULES([INIPARSER], [iniparser], [ + INIPARSER_LDFLAGS="${INIPARSER_LIBS}" AC_SUBST(INIPARSER_LDFLAGS) -], [AC_MSG_ERROR([Unable to find libiniparser])]) +], [ + AC_CHECK_LIB(iniparser, iniparser_load, [ + INIPARSER_LDFLAGS='-liniparser' + AC_SUBST(INIPARSER_LDFLAGS) + ], [AC_MSG_ERROR([Unable to find libiniparser])]) +]) AC_CHECK_FUNCS([bzero gethostbyname gettimeofday inet_ntoa memset mkdir pow select socket strchr strdup strerror strrchr strstr strtol strtoul utime],[],[AC_MSG_ERROR([One of the required functions was not found])]) AC_CHECK_FUNCS([daemon getloadavg],[],[]) @@ -91,3 +100,9 @@ fi AC_CONFIG_FILES(Makefile) AC_OUTPUT + +AC_MSG_WARN([ + The Autotools build of mod_tile is deprecated and may be removed in a future + release. Please use CMake instead, e.g.: + cmake -B build -S . && cmake --build build + or one of the presets listed by: cmake --list-presets]) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c182ac8d7..2cab3a484 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,8 @@ # #----------------------------------------------------------------------------- +# Generated config.h lives in the build tree and must be found first +include_directories(BEFORE ${PROJECT_BINARY_DIR}/includes) include_directories(${PROJECT_SOURCE_DIR}/includes) include_directories(SYSTEM @@ -21,38 +23,15 @@ include_directories(SYSTEM ${LIBRADOS_INCLUDE_DIRS} ) -link_directories(${CMAKE_LIBRARY_PATH}) - -set(COMMON_SRCS - g_logger.c - sys_utils.c -) set(COMMON_LIBRARIES ${GLIB_LIBRARIES} Threads::Threads ) - -set(RENDER_SRCS - ${COMMON_SRCS} - protocol_helper.c - renderd_config.c -) set(RENDER_LIBRARIES ${COMMON_LIBRARIES} ${INIPARSER_LIBRARIES} ${MATH_LIBRARY} ) - -set(STORE_SRCS - store.c - store_file.c - store_file_utils.c - store_memcached.c - store_null.c - store_rados.c - store_ro_composite.c - store_ro_http_proxy.c -) set(STORE_LIBRARIES ${CAIRO_LIBRARIES} ${CURL_LIBRARIES} @@ -65,6 +44,82 @@ if(NOT MALLOC_LIB STREQUAL "libc") list(PREPEND RENDER_LIBRARIES ${MALLOC_LIBRARY}) endif() +#----------------------------------------------------------------------------- +# +# Internal object libraries +# +# Sources shared between several targets are compiled once here rather than +# once per target. They are built position-independent so that they can also +# be linked into the `mod_tile.so` Apache module. +# +#----------------------------------------------------------------------------- + +# Logging & system helpers (used by everything) +add_library(common_objs OBJECT + g_logger.c + sys_utils.c +) + +# renderd.conf parsing (used by everything) +add_library(config_objs OBJECT + renderd_config.c +) + +# renderd socket protocol (used by renderd and the render_* clients) +add_library(protocol_objs OBJECT + protocol_helper.c +) + +# Client-side render request queue (used by the render_* clients) +add_library(submit_queue_objs OBJECT + render_submit_queue.c +) + +# Metatile path helpers (used by render_old without the full storage layer) +add_library(store_file_utils_objs OBJECT + store_file_utils.c +) + +# Pluggable storage backends +add_library(store_objs OBJECT + store.c + store_file.c + store_memcached.c + store_null.c + store_rados.c + store_ro_composite.c + store_ro_http_proxy.c +) + +# Mapnik rendering core (everything in renderd apart from `main()`) +add_library(renderd_core_objs OBJECT + cache_expire.c + daemon_compat.c + gen_tile.cpp + metatile.cpp + parameterize_style.cpp + request_queue.c +) + +set_target_properties( + common_objs + config_objs + protocol_objs + renderd_core_objs + store_file_utils_objs + store_objs + submit_queue_objs + PROPERTIES POSITION_INDEPENDENT_CODE ON +) + +set(COMMON_OBJS $) +set(CONFIG_OBJS $) +set(PROTOCOL_OBJS $) +set(SUBMIT_QUEUE_OBJS $) +set(RENDER_OBJS ${COMMON_OBJS} ${CONFIG_OBJS} ${PROTOCOL_OBJS}) +set(STORE_OBJS $ $) +set(RENDERD_CORE_OBJS $) + #----------------------------------------------------------------------------- # # Installed targets @@ -78,10 +133,10 @@ endif() #----------------------------------------------------------------------------- set(mod_tile_SRCS - ${COMMON_SRCS} - ${STORE_SRCS} + ${COMMON_OBJS} + ${CONFIG_OBJS} + ${STORE_OBJS} mod_tile.c - renderd_config.c ) set(mod_tile_LIBS ${COMMON_LIBRARIES} @@ -104,10 +159,10 @@ set_target_properties(mod_tile PROPERTIES PREFIX "" SUFFIX ".so") #----------------------------------------------------------------------------- set(render_expired_SRCS - ${RENDER_SRCS} - ${STORE_SRCS} + ${RENDER_OBJS} + ${STORE_OBJS} + ${SUBMIT_QUEUE_OBJS} render_expired.c - render_submit_queue.c ) set(render_expired_LIBS ${RENDER_LIBRARIES} @@ -123,10 +178,10 @@ target_link_libraries(render_expired ${render_expired_LIBS}) #----------------------------------------------------------------------------- set(render_list_SRCS - ${RENDER_SRCS} - ${STORE_SRCS} + ${RENDER_OBJS} + ${STORE_OBJS} + ${SUBMIT_QUEUE_OBJS} render_list.c - render_submit_queue.c ) set(render_list_LIBS ${RENDER_LIBRARIES} @@ -142,10 +197,10 @@ target_link_libraries(render_list ${render_list_LIBS}) #----------------------------------------------------------------------------- set(render_old_SRCS - ${RENDER_SRCS} + ${RENDER_OBJS} + ${SUBMIT_QUEUE_OBJS} + $ render_old.c - render_submit_queue.c - store_file_utils.c ) set(render_old_LIBS ${RENDER_LIBRARIES} @@ -160,9 +215,9 @@ target_link_libraries(render_old ${render_old_LIBS}) #----------------------------------------------------------------------------- set(render_speedtest_SRCS - ${RENDER_SRCS} + ${RENDER_OBJS} + ${SUBMIT_QUEUE_OBJS} render_speedtest.cpp - render_submit_queue.c ) set(render_speedtest_LIBS ${RENDER_LIBRARIES} @@ -177,15 +232,10 @@ target_link_libraries(render_speedtest ${render_speedtest_LIBS}) #----------------------------------------------------------------------------- set(renderd_SRCS - ${RENDER_SRCS} - ${STORE_SRCS} - cache_expire.c - daemon_compat.c - gen_tile.cpp - metatile.cpp - parameterize_style.cpp + ${RENDER_OBJS} + ${STORE_OBJS} + ${RENDERD_CORE_OBJS} renderd.c - request_queue.c ) set(renderd_LIBS ${ICU_LIBRARIES} @@ -210,6 +260,7 @@ if(ENABLE_TESTS) #----------------------------------------------------------------------------- set(gen_tile_test_SRCS + $ $ ${renderd_SRCS} ${PROJECT_SOURCE_DIR}/tests/gen_tile_test.cpp @@ -232,11 +283,13 @@ target_link_libraries(gen_tile_test ${gen_tile_test_LIBS}) #----------------------------------------------------------------------------- set(unit_tests_SRCS + $ $ ${PROJECT_SOURCE_DIR}/tests/unit_tests.cpp ) set(unit_tests_LIBS ${COMMON_LIBRARIES} + ${ICU_LIBRARIES} ${INIPARSER_LIBRARIES} ${LIBMAPNIK_LIBRARIES} ) diff --git a/src/render_expired.c b/src/render_expired.c index 1f5a80a21..4bb7d524c 100644 --- a/src/render_expired.c +++ b/src/render_expired.c @@ -38,17 +38,33 @@ // we'd still only use 4^17 bits = 2 GB RAM (plus a little for the lower zoom // levels) - this saves us the hassle of working with a tree structure. -#define TILE_REQUESTED(z, x, y) \ - (tile_requested[z][((x) * twopow[z] + (y)) / (8 * sizeof(int))] >> (((x) * twopow[z] + (y)) % (8 * sizeof(int)))) & 0x01 -#define SET_TILE_REQUESTED(z, x, y) \ - tile_requested[z][((x) * twopow[z] + (y)) / (8 * sizeof(int))] |= (0x01 << (((x) * twopow[z] + (y)) % (8 * sizeof(int)))); +// Set of metatiles already requested, so each is only queued once. +// Keys are (z, x, y) packed into a gint64; this only uses memory for tiles actually +// seen, unlike a per-zoom bitmap (which needed ~2 GiB at --max-zoom 20). +static GHashTable *tile_requested; + +static gint64 tile_key(int z, int x, int y) +{ + return ((gint64)z << 48) | ((gint64)(x & 0xFFFFFF) << 24) | (gint64)(y & 0xFFFFFF); +} + +static int tile_is_requested(int z, int x, int y) +{ + gint64 key = tile_key(z, x, y); + return g_hash_table_contains(tile_requested, &key); +} + +static void set_tile_requested(int z, int x, int y) +{ + gint64 *key = g_new(gint64, 1); + *key = tile_key(z, x, y); + g_hash_table_add(tile_requested, key); +} // tile marking arrays -unsigned int **tile_requested; // "two raised to the power of [...]" - don't trust pow() to be efficient // for base 2 -unsigned long long twopow[MAX_ZOOM]; void display_rate(struct timeval start, struct timeval end, int num) { @@ -298,19 +314,7 @@ int main(int argc, char **argv) // initialise arrays for tile markings - tile_requested = (unsigned int **)malloc((max_zoom - excess_zoomlevels + 1) * sizeof(unsigned int *)); - - for (int i = 0; i <= max_zoom - excess_zoomlevels; i++) { - // initialize twopow array - twopow[i] = (i == 0) ? 1 : twopow[i - 1] * 2; - unsigned long long fourpow = twopow[i] * twopow[i]; - tile_requested[i] = (unsigned int *)calloc((fourpow / METATILE) + 1, 1); - - if (NULL == tile_requested[i]) { - g_logger(G_LOG_LEVEL_CRITICAL, "not enough memory available"); - return 1; - } - } + tile_requested = g_hash_table_new_full(g_int64_hash, g_int64_equal, g_free, NULL); store = init_storage_backend(tile_dir); @@ -410,7 +414,7 @@ int main(int argc, char **argv) // renderd does keep a list internally to avoid enqueuing the same tile // twice but in case it has already rendered the tile we don't want to // cause extra work. - if (TILE_REQUESTED(z - excess_zoomlevels, x >> excess_zoomlevels, y >> excess_zoomlevels)) { + if (tile_is_requested(z - excess_zoomlevels, x >> excess_zoomlevels, y >> excess_zoomlevels)) { if (verbose) { g_logger(G_LOG_LEVEL_MESSAGE, "Already requested metatile containing '%d/%d/%d', moving on to next input line", z, x, y); } @@ -425,7 +429,7 @@ int main(int argc, char **argv) // mark tile as requested. (do this even if, below, the tile is not // actually requested due to not being present on disk, to avoid // unnecessary later stat'ing). - SET_TILE_REQUESTED(z - excess_zoomlevels, x >> excess_zoomlevels, y >> excess_zoomlevels); + set_tile_requested(z - excess_zoomlevels, x >> excess_zoomlevels, y >> excess_zoomlevels); // commented out - seems to cause problems in MT environment, // trying to write to already-closed file @@ -492,11 +496,7 @@ int main(int argc, char **argv) store->close_storage(store); free(store); - for (int i = 0; i <= max_zoom - excess_zoomlevels; i++) { - free(tile_requested[i]); - } - - free(tile_requested); + g_hash_table_destroy(tile_requested); gettimeofday(&end, NULL); g_logger(G_LOG_LEVEL_MESSAGE, "Read and expanded %i tiles from list.", num_read); diff --git a/src/renderd.c b/src/renderd.c index 6ad8e5209..11929fe61 100644 --- a/src/renderd.c +++ b/src/renderd.c @@ -165,14 +165,14 @@ enum protoCmd rx_request(struct protocol *req, int fd) void request_exit(void) { - // Any write to the exit pipe will trigger a graceful exit + // Any write to the exit pipe will trigger a graceful exit. + // This function is called from a signal handler, so only async-signal-safe + // functions (write(2)) may be used here — no g_logger, no strerror. char c = 0; - - g_logger(G_LOG_LEVEL_INFO, "Sending exit request"); - - if (write(exit_pipe_fd, &c, sizeof(c)) < 0) { - g_logger(G_LOG_LEVEL_ERROR, "Failed to write to the exit pipe: %s", strerror(errno)); - } + // Nothing useful can be done on failure here; assign to discard (GCC ignores a (void) cast + // for functions declared warn_unused_result) + ssize_t ignored = write(exit_pipe_fd, &c, sizeof(c)); + (void)ignored; } void process_loop(int listen_fd) @@ -637,7 +637,20 @@ void *slave_thread(void * arg) struct protocol * req_slave; req_slave = (struct protocol *)malloc(sizeof(struct protocol)); + + if (req_slave == NULL) { + g_logger(G_LOG_LEVEL_ERROR, "slave_thread: failed to allocate memory for req_slave"); + return NULL; + } + resp = (struct protocol *)malloc(sizeof(struct protocol)); + + if (resp == NULL) { + g_logger(G_LOG_LEVEL_ERROR, "slave_thread: failed to allocate memory for resp"); + free(req_slave); + return NULL; + } + bzero(req_slave, sizeof(struct protocol)); bzero(resp, sizeof(struct protocol)); @@ -718,10 +731,27 @@ void *slave_thread(void * arg) retry = 10; while ((ret_size < sizeof(struct protocol)) && (retry > 0)) { - ret_size = recv(pfd, resp + ret_size, (sizeof(struct protocol) - - ret_size), 0); + ssize_t n = recv(pfd, (char *)resp + ret_size, + sizeof(struct protocol) - ret_size, 0); + + if (n < 0) { + if (errno == EINTR) { + continue; + } - if ((errno == EPIPE) || ret_size == 0) { + if (errno == EPIPE) { + close(pfd); + pfd = FD_INVALID; + ret_size = 0; + g_logger(G_LOG_LEVEL_ERROR, "Pipe to Renderd slave closed"); + break; + } + + retry--; + continue; + } + + if (n == 0) { close(pfd); pfd = FD_INVALID; ret_size = 0; @@ -729,7 +759,7 @@ void *slave_thread(void * arg) break; } - retry--; + ret_size += n; } if (ret_size < sizeof(struct protocol)) { @@ -921,6 +951,12 @@ int main(int argc, char **argv) render_threads = (pthread_t *) malloc(sizeof(pthread_t) * config->num_threads); + if (render_threads == NULL) { + g_logger(G_LOG_LEVEL_CRITICAL, "Failed to allocate memory for render threads"); + close(fd); + return 7; + } + for (i = 0; i < config->num_threads; i++) { if (pthread_create(&render_threads[i], NULL, render_thread, (void *)maps)) { g_logger(G_LOG_LEVEL_CRITICAL, "Could not spawn rendering thread"); @@ -934,6 +970,12 @@ int main(int argc, char **argv) k = 0; slave_threads = (pthread_t *) malloc(sizeof(pthread_t) * num_slave_threads); + if (slave_threads == NULL) { + g_logger(G_LOG_LEVEL_CRITICAL, "Failed to allocate memory for slave threads"); + close(fd); + return 7; + } + for (i = 1; i < MAX_SLAVES; i++) { for (j = 0; j < config_slaves[i].num_threads; j++) { if (pthread_create(&slave_threads[k++], NULL, slave_thread, (void *) &config_slaves[i])) { diff --git a/src/request_queue.c b/src/request_queue.c index 832d2e788..6bb893993 100644 --- a/src/request_queue.c +++ b/src/request_queue.c @@ -491,6 +491,15 @@ struct request_queue * request_queue_init() queue->renderHead.next = queue->renderHead.prev = &(queue->renderHead); queue->hashidxSize = HASHIDX_SIZE; queue->item_hashidx = (struct item_idx *) malloc(sizeof(struct item_idx) * queue->hashidxSize); + + if (queue->item_hashidx == NULL) { + g_logger(G_LOG_LEVEL_ERROR, "Failed to allocate memory for request queue hash index"); + pthread_cond_destroy(&(queue->qCond)); + pthread_mutex_destroy(&(queue->qLock)); + free(queue); + return NULL; + } + bzero(queue->item_hashidx, sizeof(struct item_idx) * queue->hashidxSize); return queue; diff --git a/src/store_file.c b/src/store_file.c index 5464cf715..07b8f6d26 100644 --- a/src/store_file.c +++ b/src/store_file.c @@ -74,6 +74,11 @@ static int file_tile_read(struct storage_backend * store, const char *xmlconfig, struct meta_layout *m = (struct meta_layout *)malloc(header_len); size_t file_offset, tile_size; + if (m == NULL) { + snprintf(log_msg, PATH_MAX - 1, "Failed to allocate memory for metatile header\n"); + return -1; + } + meta_offset = xyzo_to_meta(path, sizeof(path), (char *)(store->storage_ctx), xmlconfig, options, x, y, z); fd = open(path, O_RDONLY); @@ -219,6 +224,12 @@ static int file_metatile_write(struct storage_backend * store, const char *xmlco g_logger(G_LOG_LEVEL_DEBUG, "Creating and writing a metatile to %s", meta_path); tmp = (char *)malloc(sizeof(char) * strlen(meta_path) + 24); + + if (tmp == NULL) { + g_logger(G_LOG_LEVEL_ERROR, "file_metatile_write: failed to allocate memory for temp path"); + return -1; + } + snprintf(tmp, strlen(meta_path) + 24, "%s.%lu", meta_path, (unsigned long) pthread_self()); if (mkdirp(tmp)) { @@ -246,7 +257,14 @@ static int file_metatile_write(struct storage_backend * store, const char *xmlco } close(fd); - rename(tmp, meta_path); + + if (rename(tmp, meta_path) != 0) { + g_logger(G_LOG_LEVEL_WARNING, "Error renaming temp file %s to %s: %s", tmp, meta_path, strerror(errno)); + unlink(tmp); + free(tmp); + return -1; + } + free(tmp); return sz; diff --git a/src/store_memcached.c b/src/store_memcached.c index 981b8b102..07546f035 100644 --- a/src/store_memcached.c +++ b/src/store_memcached.c @@ -80,6 +80,11 @@ static int memcached_tile_read(struct storage_backend * store, const char *xmlco memcached_return_t rc; char * buf_raw; + if (m == NULL) { + snprintf(log_msg, 1024, "Failed to allocate memory for metatile header\n"); + return -1; + } + mask = METATILE - 1; meta_offset = (x & mask) * METATILE + (y & mask); @@ -235,7 +240,6 @@ static int memcached_metatile_expire(struct storage_backend * store, const char char * buf; size_t len; uint32_t flags; - uint64_t cas; memcached_return_t rc; //TODO: deal with options @@ -246,11 +250,11 @@ static int memcached_metatile_expire(struct storage_backend * store, const char return -1; } - //cas = memcached_result_cas(&rc); - ((struct stat_info *)buf)->expired = 1; - rc = memcached_cas(store->storage_ctx, meta_path, strlen(meta_path), buf, len, 0, flags, cas); + // memcached_get() does not return a CAS value, so a compare-and-swap cannot be used here + // (the previous code passed an uninitialised CAS token). Replace the existing item instead. + rc = memcached_replace(store->storage_ctx, meta_path, strlen(meta_path), buf, len, 0, flags); if (rc != MEMCACHED_SUCCESS) { free(buf); diff --git a/src/store_rados.c b/src/store_rados.c index c057a804e..4b5281143 100644 --- a/src/store_rados.c +++ b/src/store_rados.c @@ -134,6 +134,11 @@ static int rados_tile_read(struct storage_backend * store, const char *xmlconfig int err; char * buf_raw; + if (m == NULL) { + snprintf(log_msg, 1024, "Failed to allocate memory for metatile header\n"); + return -1; + } + mask = METATILE - 1; meta_offset = (x & mask) * METATILE + (y & mask); @@ -234,6 +239,11 @@ static int rados_metatile_write(struct storage_backend * store, const char *xmlc char * buf2 = malloc(sz2); int err; + if (buf2 == NULL) { + g_logger(G_LOG_LEVEL_ERROR, "rados_metatile_write: failed to allocate memory for write buffer"); + return -1; + } + tile_stat.expired = 0; tile_stat.size = sz; tile_stat.mtime = time(NULL); @@ -352,7 +362,7 @@ struct storage_backend * init_storage_rados(const char * connection_string) int err; int i; - if (ctx == NULL) { + if (ctx == NULL || store == NULL) { free(ctx); free(store); return NULL; diff --git a/src/store_ro_composite.c b/src/store_ro_composite.c index 8faf4b4d6..959cf0560 100644 --- a/src/store_ro_composite.c +++ b/src/store_ro_composite.c @@ -202,8 +202,12 @@ static int ro_composite_metatile_expire(struct storage_backend * store, const ch static int ro_composite_close_storage(struct storage_backend * store) { struct ro_composite_ctx * ctx = (struct ro_composite_ctx *)(store->storage_ctx); + // close_storage() no longer frees the backend itself (#524), so free the + // two inner backends here, as the callers do for the outer one ctx->store_primary->close_storage(ctx->store_primary); + free(ctx->store_primary); ctx->store_secondary->close_storage(ctx->store_secondary); + free(ctx->store_secondary); if (ctx->cache.tile) { free(ctx->cache.tile); @@ -226,7 +230,7 @@ struct storage_backend * init_storage_ro_composite(const char * connection_strin return NULL; #else struct storage_backend * store = malloc(sizeof(struct storage_backend)); - struct ro_composite_ctx * ctx = malloc(sizeof(struct ro_composite_ctx)); + struct ro_composite_ctx * ctx = calloc(1, sizeof(struct ro_composite_ctx)); char * connection_string_primary; char * connection_string_secondary; char * tmp; @@ -248,7 +252,23 @@ struct storage_backend * init_storage_ro_composite(const char * connection_strin } connection_string_secondary = strstr(connection_string, "}{"); + + if (connection_string_secondary == NULL) { + g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_composite: malformed connection string, missing '}{' separator: %s", connection_string); + free(ctx); + free(store); + return NULL; + } + connection_string_primary = malloc(strlen(connection_string) - strlen("composite:{") - strlen(connection_string_secondary) + 1); + + if (connection_string_primary == NULL) { + g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_composite: failed to allocate memory for primary connection string"); + free(ctx); + free(store); + return NULL; + } + memcpy(connection_string_primary, connection_string + strlen("composite:{"), strlen(connection_string) - strlen("composite:{") - strlen(connection_string_secondary)); connection_string_primary[strlen(connection_string) - strlen("composite:{") - strlen(connection_string_secondary)] = 0; connection_string_secondary = strdup(connection_string_secondary + 2); @@ -258,30 +278,61 @@ struct storage_backend * init_storage_ro_composite(const char * connection_strin g_logger(G_LOG_LEVEL_DEBUG, "init_storage_ro_composite: Secondary storage backend: %s", connection_string_secondary); tmp = strstr(connection_string_primary, ","); + + if (tmp == NULL) { + g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_composite: malformed primary connection string, missing ',' separator: %s", connection_string_primary); + free(connection_string_primary); + free(connection_string_secondary); + free(ctx); + free(store); + return NULL; + } + memcpy(ctx->xmlconfig_primary, connection_string_primary, tmp - connection_string_primary); ctx->xmlconfig_primary[tmp - connection_string_primary] = 0; ctx->store_primary = init_storage_backend(tmp + 1); if (ctx->store_primary == NULL) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_composite: failed to initialise primary storage backend"); + free(connection_string_primary); + free(connection_string_secondary); free(ctx); free(store); return NULL; } tmp = strstr(connection_string_secondary, ","); + + if (tmp == NULL) { + g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_composite: malformed secondary connection string, missing ',' separator: %s", connection_string_secondary); + free(connection_string_primary); + free(connection_string_secondary); + ctx->store_primary->close_storage(ctx->store_primary); + free(ctx->store_primary); + free(ctx); + free(store); + return NULL; + } + memcpy(ctx->xmlconfig_secondary, connection_string_secondary, tmp - connection_string_secondary); ctx->xmlconfig_secondary[tmp - connection_string_secondary] = 0; ctx->store_secondary = init_storage_backend(tmp + 1); if (ctx->store_secondary == NULL) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_composite: failed to initialise secondary storage backend"); + free(connection_string_primary); + free(connection_string_secondary); ctx->store_primary->close_storage(ctx->store_primary); + free(ctx->store_primary); free(ctx); free(store); return NULL; } + // The backends keep their own copies of what they need from these + free(connection_string_primary); + free(connection_string_secondary); + ctx->render_size = 256; store->storage_ctx = ctx; diff --git a/src/store_ro_http_proxy.c b/src/store_ro_http_proxy.c index 69925fb7e..61b0e098a 100644 --- a/src/store_ro_http_proxy.c +++ b/src/store_ro_http_proxy.c @@ -116,6 +116,7 @@ static int ro_http_proxy_tile_retrieve(struct storage_backend * store, const cha if (res != CURLE_OK) { g_logger(G_LOG_LEVEL_ERROR, "ro_http_proxy_tile_fetch: failed to retrieve file: %s", curl_easy_strerror(res)); + free(chunk.memory); ctx->cache.x = -1; ctx->cache.y = -1; ctx->cache.z = -1; @@ -126,6 +127,7 @@ static int ro_http_proxy_tile_retrieve(struct storage_backend * store, const cha if (res != CURLE_OK) { g_logger(G_LOG_LEVEL_ERROR, "ro_http_proxy_tile_fetch: failed to retrieve HTTP code: %s", curl_easy_strerror(res)); + free(chunk.memory); ctx->cache.x = -1; ctx->cache.y = -1; ctx->cache.z = -1; @@ -150,19 +152,31 @@ static int ro_http_proxy_tile_retrieve(struct storage_backend * store, const cha case 404: { if (ctx->cache.tile != NULL) { free(ctx->cache.tile); + ctx->cache.tile = NULL; } + free(chunk.memory); ctx->cache.st_stat.size = -1; ctx->cache.st_stat.expired = 0; break; } - } + default: { + // Do not cache anything for unexpected responses, otherwise the previously + // cached tile would be served for these coordinates + g_logger(G_LOG_LEVEL_ERROR, "ro_http_proxy_tile_fetch: unexpected HTTP response code %li", httpCode); + free(chunk.memory); + ctx->cache.x = -1; + ctx->cache.y = -1; + ctx->cache.z = -1; + return -1; + } + } ctx->cache.x = x; ctx->cache.y = y; ctx->cache.z = z; - strcpy(ctx->cache.xmlname, xmlconfig); + snprintf(ctx->cache.xmlname, sizeof(ctx->cache.xmlname), "%s", xmlconfig); return 1; } } @@ -172,7 +186,12 @@ static int ro_http_proxy_tile_read(struct storage_backend * store, const char *x struct ro_http_proxy_ctx * ctx = (struct ro_http_proxy_ctx *)(store->storage_ctx); if (ro_http_proxy_tile_retrieve(store, xmlconfig, options, x, y, z) > 0) { - if (ctx->cache.st_stat.size > sz) { + if (ctx->cache.st_stat.size < 0) { + // Cached 404 + return -1; + } + + if ((size_t)ctx->cache.st_stat.size > sz) { g_logger(G_LOG_LEVEL_ERROR, "ro_http_proxy_tile_read: size was too big, overrun %lu %li", sz, ctx->cache.st_stat.size); return -1; } @@ -299,6 +318,7 @@ struct storage_backend * init_storage_ro_http_proxy(const char * connection_stri if (res != CURLE_OK) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_http_proxy: failed to initialise global curl: %s", curl_easy_strerror(res)); + free(ctx->baseurl); free(ctx); free(store); return NULL; @@ -308,6 +328,7 @@ struct storage_backend * init_storage_ro_http_proxy(const char * connection_stri if (!ctx->ctx) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_http_proxy: failed to initialise curl"); + free(ctx->baseurl); free(ctx); free(store); return NULL; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index be26e98ae..b75418648 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1277,6 +1277,20 @@ endforeach() # #----------------------------------------------------------------------------- +#----------------------------------------------------------------------------- +# +# catch2_o (vendored Catch2 v3 implementation, compiled once) +# +#----------------------------------------------------------------------------- + +add_library(catch2_o OBJECT + ${PROJECT_SOURCE_DIR}/tests/catch/catch_amalgamated.cpp +) +# All test binaries provide their own main() (catch_main.cpp or gen_tile_test.cpp) +target_compile_definitions(catch2_o PUBLIC CATCH_AMALGAMATED_CUSTOM_MAIN) +# Third-party code: do not apply the project's warning flags to it +target_compile_options(catch2_o PRIVATE -w) + #----------------------------------------------------------------------------- # # catch_main_o @@ -1319,6 +1333,7 @@ add_compile_definitions( PROJECT_BINARY_DIR="${PROJECT_BINARY_DIR}/src" RENDERD_CONF="${PROJECT_SOURCE_DIR}/etc/renderd/renderd.conf.examples" ) +include_directories(BEFORE ${PROJECT_BINARY_DIR}/includes) include_directories(${PROJECT_SOURCE_DIR}/includes) link_libraries(${GLIB_LIBRARIES}) @@ -1330,6 +1345,7 @@ link_libraries(${GLIB_LIBRARIES}) add_executable(render_expired_test $ + $ render_expired_test.cpp ) @@ -1341,6 +1357,7 @@ add_executable(render_expired_test add_executable(render_list_test $ + $ render_list_test.cpp ) @@ -1352,6 +1369,7 @@ add_executable(render_list_test add_executable(render_old_test $ + $ render_old_test.cpp ) @@ -1363,6 +1381,7 @@ add_executable(render_old_test add_executable(render_speedtest_test $ + $ render_speedtest_test.cpp ) @@ -1374,5 +1393,45 @@ add_executable(render_speedtest_test add_executable(renderd_test $ + $ renderd_test.cpp ) + +#----------------------------------------------------------------------------- +# +# Sanitizer runtime preloading +# +# The integration tests load the instrumented `mod_tile.so` into the system's +# (uninstrumented) `httpd`, which ASan/TSan refuse unless their runtime is the +# first library loaded. Preload it for all tests when building with sanitizers. +# +#----------------------------------------------------------------------------- + +if(ENABLE_SANITIZERS) + set(SANITIZER_PRELOAD "") + # (TSan cannot be preloaded into uninstrumented processes, so only ASan is handled here; + # with ENABLE_SANITIZERS=thread run only the unit test executables: ctest -R "_test$|^unit_tests$") + foreach(SANITIZER_RUNTIME address:asan) + string(REPLACE ":" ";" SANITIZER_RUNTIME "${SANITIZER_RUNTIME}") + list(GET SANITIZER_RUNTIME 0 SANITIZER_NAME) + list(GET SANITIZER_RUNTIME 1 SANITIZER_LIB) + if(ENABLE_SANITIZERS MATCHES "${SANITIZER_NAME}") + execute_process(COMMAND ${CMAKE_C_COMPILER} -print-file-name=lib${SANITIZER_LIB}.so + OUTPUT_VARIABLE SANITIZER_LIB_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(IS_ABSOLUTE "${SANITIZER_LIB_PATH}" AND EXISTS "${SANITIZER_LIB_PATH}") + list(APPEND SANITIZER_PRELOAD "${SANITIZER_LIB_PATH}") + else() + message(WARNING "Could not locate lib${SANITIZER_LIB}.so; httpd-based tests will fail with ${SANITIZER_NAME}") + endif() + endif() + endforeach() + + if(SANITIZER_PRELOAD) + string(REPLACE ";" ":" SANITIZER_PRELOAD "${SANITIZER_PRELOAD}") + message(STATUS "Preloading '${SANITIZER_PRELOAD}' for tests") + get_property(ALL_TESTS DIRECTORY PROPERTY TESTS) + set_property(TEST ${ALL_TESTS} APPEND PROPERTY ENVIRONMENT "LD_PRELOAD=${SANITIZER_PRELOAD}") + endif() +endif() diff --git a/tests/catch/LICENSE.txt b/tests/catch/LICENSE.txt new file mode 100644 index 000000000..36b7cd93c --- /dev/null +++ b/tests/catch/LICENSE.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/tests/catch/README b/tests/catch/README new file mode 100644 index 000000000..12cda4ff2 --- /dev/null +++ b/tests/catch/README @@ -0,0 +1,6 @@ +Catch2 v3.16.0 amalgamated build +Source: https://github.com/catchorg/Catch2/tree/v3.16.0/extras +Commit: 317ac1ed4c0bb6e6b91eafc817e05c488feffcb3 + +To update, replace catch_amalgamated.hpp/.cpp with the files from the +extras/ directory of the desired Catch2 release tag. diff --git a/tests/catch/catch.hpp b/tests/catch/catch.hpp deleted file mode 100644 index 9b309bddc..000000000 --- a/tests/catch/catch.hpp +++ /dev/null @@ -1,17976 +0,0 @@ -/* - * Catch v2.13.10 - * Generated: 2022-10-16 11:01:23.452308 - * ---------------------------------------------------------- - * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved. - * - * Distributed under the Boost Software License, Version 1.0. (See accompanying - * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - */ -#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -// start catch.hpp - - -#define CATCH_VERSION_MAJOR 2 -#define CATCH_VERSION_MINOR 13 -#define CATCH_VERSION_PATCH 10 - -#ifdef __clang__ -# pragma clang system_header -#elif defined __GNUC__ -# pragma GCC system_header -#endif - -// start catch_suppress_warnings.h - -#ifdef __clang__ -# ifdef __ICC // icpc defines the __clang__ macro -# pragma warning(push) -# pragma warning(disable: 161 1682) -# else // __ICC -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wpadded" -# pragma clang diagnostic ignored "-Wswitch-enum" -# pragma clang diagnostic ignored "-Wcovered-switch-default" -# endif -#elif defined __GNUC__ - // Because REQUIREs trigger GCC's -Wparentheses, and because still - // supported version of g++ have only buggy support for _Pragmas, - // Wparentheses have to be suppressed globally. -# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details - -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wunused-variable" -# pragma GCC diagnostic ignored "-Wpadded" -#endif -// end catch_suppress_warnings.h -#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) -# define CATCH_IMPL -# define CATCH_CONFIG_ALL_PARTS -#endif - -// In the impl file, we want to have access to all parts of the headers -// Can also be used to sanely support PCHs -#if defined(CATCH_CONFIG_ALL_PARTS) -# define CATCH_CONFIG_EXTERNAL_INTERFACES -# if defined(CATCH_CONFIG_DISABLE_MATCHERS) -# undef CATCH_CONFIG_DISABLE_MATCHERS -# endif -# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) -# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER -# endif -#endif - -#if !defined(CATCH_CONFIG_IMPL_ONLY) -// start catch_platform.h - -// See e.g.: -// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html -#ifdef __APPLE__ -# include -# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ - (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) -# define CATCH_PLATFORM_MAC -# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) -# define CATCH_PLATFORM_IPHONE -# endif - -#elif defined(linux) || defined(__linux) || defined(__linux__) -# define CATCH_PLATFORM_LINUX - -#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) -# define CATCH_PLATFORM_WINDOWS -#endif - -// end catch_platform.h - -#ifdef CATCH_IMPL -# ifndef CLARA_CONFIG_MAIN -# define CLARA_CONFIG_MAIN_NOT_DEFINED -# define CLARA_CONFIG_MAIN -# endif -#endif - -// start catch_user_interfaces.h - -namespace Catch { - unsigned int rngSeed(); -} - -// end catch_user_interfaces.h -// start catch_tag_alias_autoregistrar.h - -// start catch_common.h - -// start catch_compiler_capabilities.h - -// Detect a number of compiler features - by compiler -// The following features are defined: -// -// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? -// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? -// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? -// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? -// **************** -// Note to maintainers: if new toggles are added please document them -// in configuration.md, too -// **************** - -// In general each macro has a _NO_ form -// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. -// Many features, at point of detection, define an _INTERNAL_ macro, so they -// can be combined, en-mass, with the _NO_ forms later. - -#ifdef __cplusplus - -# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) -# define CATCH_CPP14_OR_GREATER -# endif - -# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -# define CATCH_CPP17_OR_GREATER -# endif - -#endif - -// Only GCC compiler should be used in this block, so other compilers trying to -// mask themselves as GCC should be ignored. -#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) - -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) - -#endif - -#if defined(__clang__) - -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) - -// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug -// which results in calls to destructors being emitted for each temporary, -// without a matching initialization. In practice, this can result in something -// like `std::string::~string` being called on an uninitialized value. -// -// For example, this code will likely segfault under IBM XL: -// ``` -// REQUIRE(std::string("12") + "34" == "1234") -// ``` -// -// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. -# if !defined(__ibmxl__) && !defined(__CUDACC__) -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ -# endif - -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ - _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") - -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) - -# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) - -# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) - -# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) - -#endif // __clang__ - -//////////////////////////////////////////////////////////////////////////////// -// Assume that non-Windows platforms support posix signals by default -#if !defined(CATCH_PLATFORM_WINDOWS) - #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS -#endif - -//////////////////////////////////////////////////////////////////////////////// -// We know some environments not to support full POSIX signals -#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) - #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -#endif - -#ifdef __OS400__ -# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -# define CATCH_CONFIG_COLOUR_NONE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Android somehow still does not support std::to_string -#if defined(__ANDROID__) -# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING -# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Not all Windows environments support SEH properly -#if defined(__MINGW32__) -# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH -#endif - -//////////////////////////////////////////////////////////////////////////////// -// PS4 -#if defined(__ORBIS__) -# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Cygwin -#ifdef __CYGWIN__ - -// Required for some versions of Cygwin to declare gettimeofday -// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin -# define _BSD_SOURCE -// some versions of cygwin (most) do not support std::to_string. Use the libstd check. -// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 -# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ - && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) - -# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING - -# endif -#endif // __CYGWIN__ - -//////////////////////////////////////////////////////////////////////////////// -// Visual C++ -#if defined(_MSC_VER) - -// Universal Windows platform does not support SEH -// Or console colours (or console at all...) -# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) -# define CATCH_CONFIG_COLOUR_NONE -# else -# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH -# endif - -# if !defined(__clang__) // Handle Clang masquerading for msvc - -// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ -// _MSVC_TRADITIONAL == 0 means new conformant preprocessor -// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor -# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) -# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -# endif // MSVC_TRADITIONAL - -// Only do this if we're not using clang on Windows, which uses `diagnostic push` & `diagnostic pop` -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) -# endif // __clang__ - -#endif // _MSC_VER - -#if defined(_REENTRANT) || defined(_MSC_VER) -// Enable async processing, as -pthread is specified or no additional linking is required -# define CATCH_INTERNAL_CONFIG_USE_ASYNC -#endif // _MSC_VER - -//////////////////////////////////////////////////////////////////////////////// -// Check if we are compiled with -fno-exceptions or equivalent -#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) -# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED -#endif - -//////////////////////////////////////////////////////////////////////////////// -// DJGPP -#ifdef __DJGPP__ -# define CATCH_INTERNAL_CONFIG_NO_WCHAR -#endif // __DJGPP__ - -//////////////////////////////////////////////////////////////////////////////// -// Embarcadero C++Build -#if defined(__BORLANDC__) - #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN -#endif - -//////////////////////////////////////////////////////////////////////////////// - -// Use of __COUNTER__ is suppressed during code analysis in -// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly -// handled by it. -// Otherwise all supported compilers support COUNTER macro, -// but user still might want to turn it off -#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) - #define CATCH_INTERNAL_CONFIG_COUNTER -#endif - -//////////////////////////////////////////////////////////////////////////////// - -// RTX is a special version of Windows that is real time. -// This means that it is detected as Windows, but does not provide -// the same set of capabilities as real Windows does. -#if defined(UNDER_RTSS) || defined(RTX64_BUILD) - #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH - #define CATCH_INTERNAL_CONFIG_NO_ASYNC - #define CATCH_CONFIG_COLOUR_NONE -#endif - -#if !defined(_GLIBCXX_USE_C99_MATH_TR1) -#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER -#endif - -// Various stdlib support checks that require __has_include -#if defined(__has_include) - // Check if string_view is available and usable - #if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW - #endif - - // Check if optional is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) - - // Check if byte is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # include - # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) - # define CATCH_INTERNAL_CONFIG_CPP17_BYTE - # endif - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) - - // Check if variant is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # if defined(__clang__) && (__clang_major__ < 8) - // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 - // fix should be in clang 8, workaround in libstdc++ 8.2 - # include - # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) - # define CATCH_CONFIG_NO_CPP17_VARIANT - # else - # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT - # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) - # else - # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT - # endif // defined(__clang__) && (__clang_major__ < 8) - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) -#endif // defined(__has_include) - -#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) -# define CATCH_CONFIG_COUNTER -#endif -#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) -# define CATCH_CONFIG_WINDOWS_SEH -#endif -// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. -#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) -# define CATCH_CONFIG_POSIX_SIGNALS -#endif -// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. -#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) -# define CATCH_CONFIG_WCHAR -#endif - -#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) -# define CATCH_CONFIG_CPP11_TO_STRING -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) -# define CATCH_CONFIG_CPP17_OPTIONAL -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) -# define CATCH_CONFIG_CPP17_STRING_VIEW -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) -# define CATCH_CONFIG_CPP17_VARIANT -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) -# define CATCH_CONFIG_CPP17_BYTE -#endif - -#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) -# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE -#endif - -#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) -# define CATCH_CONFIG_NEW_CAPTURE -#endif - -#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) -# define CATCH_CONFIG_DISABLE_EXCEPTIONS -#endif - -#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) -# define CATCH_CONFIG_POLYFILL_ISNAN -#endif - -#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) -# define CATCH_CONFIG_USE_ASYNC -#endif - -#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) -# define CATCH_CONFIG_ANDROID_LOGWRITE -#endif - -#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) -# define CATCH_CONFIG_GLOBAL_NEXTAFTER -#endif - -// Even if we do not think the compiler has that warning, we still have -// to provide a macro that can be used by the code. -#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION -#endif -#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS -#endif - -// The goal of this macro is to avoid evaluation of the arguments, but -// still have the compiler warn on problems inside... -#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) -#endif - -#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) -# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#elif defined(__clang__) && (__clang_major__ < 5) -# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#endif - -#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#endif - -#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) -#define CATCH_TRY if ((true)) -#define CATCH_CATCH_ALL if ((false)) -#define CATCH_CATCH_ANON(type) if ((false)) -#else -#define CATCH_TRY try -#define CATCH_CATCH_ALL catch (...) -#define CATCH_CATCH_ANON(type) catch (type) -#endif - -#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) -#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#endif - -// end catch_compiler_capabilities.h -#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line -#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) -#ifdef CATCH_CONFIG_COUNTER -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) -#else -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) -#endif - -#include -#include -#include - -// We need a dummy global operator<< so we can bring it into Catch namespace later -struct Catch_global_namespace_dummy {}; -std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); - -namespace Catch { - - struct CaseSensitive { enum Choice { - Yes, - No - }; }; - - class NonCopyable { - NonCopyable( NonCopyable const& ) = delete; - NonCopyable( NonCopyable && ) = delete; - NonCopyable& operator = ( NonCopyable const& ) = delete; - NonCopyable& operator = ( NonCopyable && ) = delete; - - protected: - NonCopyable(); - virtual ~NonCopyable(); - }; - - struct SourceLineInfo { - - SourceLineInfo() = delete; - SourceLineInfo( char const* _file, std::size_t _line ) noexcept - : file( _file ), - line( _line ) - {} - - SourceLineInfo( SourceLineInfo const& other ) = default; - SourceLineInfo& operator = ( SourceLineInfo const& ) = default; - SourceLineInfo( SourceLineInfo&& ) noexcept = default; - SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; - - bool empty() const noexcept { return file[0] == '\0'; } - bool operator == ( SourceLineInfo const& other ) const noexcept; - bool operator < ( SourceLineInfo const& other ) const noexcept; - - char const* file; - std::size_t line; - }; - - std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); - - // Bring in operator<< from global namespace into Catch namespace - // This is necessary because the overload of operator<< above makes - // lookup stop at namespace Catch - using ::operator<<; - - // Use this in variadic streaming macros to allow - // >> +StreamEndStop - // as well as - // >> stuff +StreamEndStop - struct StreamEndStop { - std::string operator+() const; - }; - template - T const& operator + ( T const& value, StreamEndStop ) { - return value; - } -} - -#define CATCH_INTERNAL_LINEINFO \ - ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) - -// end catch_common.h -namespace Catch { - - struct RegistrarForTagAliases { - RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); - }; - -} // end namespace Catch - -#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -// end catch_tag_alias_autoregistrar.h -// start catch_test_registry.h - -// start catch_interfaces_testcase.h - -#include - -namespace Catch { - - class TestSpec; - - struct ITestInvoker { - virtual void invoke () const = 0; - virtual ~ITestInvoker(); - }; - - class TestCase; - struct IConfig; - - struct ITestCaseRegistry { - virtual ~ITestCaseRegistry(); - virtual std::vector const& getAllTests() const = 0; - virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; - }; - - bool isThrowSafe( TestCase const& testCase, IConfig const& config ); - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); - std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); - std::vector const& getAllTestCasesSorted( IConfig const& config ); - -} - -// end catch_interfaces_testcase.h -// start catch_stringref.h - -#include -#include -#include -#include - -namespace Catch { - - /// A non-owning string class (similar to the forthcoming std::string_view) - /// Note that, because a StringRef may be a substring of another string, - /// it may not be null terminated. - class StringRef { - public: - using size_type = std::size_t; - using const_iterator = const char*; - - private: - static constexpr char const* const s_empty = ""; - - char const* m_start = s_empty; - size_type m_size = 0; - - public: // construction - constexpr StringRef() noexcept = default; - - StringRef( char const* rawChars ) noexcept; - - constexpr StringRef( char const* rawChars, size_type size ) noexcept - : m_start( rawChars ), - m_size( size ) - {} - - StringRef( std::string const& stdString ) noexcept - : m_start( stdString.c_str() ), - m_size( stdString.size() ) - {} - - explicit operator std::string() const { - return std::string(m_start, m_size); - } - - public: // operators - auto operator == ( StringRef const& other ) const noexcept -> bool; - auto operator != (StringRef const& other) const noexcept -> bool { - return !(*this == other); - } - - auto operator[] ( size_type index ) const noexcept -> char { - assert(index < m_size); - return m_start[index]; - } - - public: // named queries - constexpr auto empty() const noexcept -> bool { - return m_size == 0; - } - constexpr auto size() const noexcept -> size_type { - return m_size; - } - - // Returns the current start pointer. If the StringRef is not - // null-terminated, throws std::domain_exception - auto c_str() const -> char const*; - - public: // substrings and searches - // Returns a substring of [start, start + length). - // If start + length > size(), then the substring is [start, size()). - // If start > size(), then the substring is empty. - auto substr( size_type start, size_type length ) const noexcept -> StringRef; - - // Returns the current start pointer. May not be null-terminated. - auto data() const noexcept -> char const*; - - constexpr auto isNullTerminated() const noexcept -> bool { - return m_start[m_size] == '\0'; - } - - public: // iterators - constexpr const_iterator begin() const { return m_start; } - constexpr const_iterator end() const { return m_start + m_size; } - }; - - auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; - auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; - - constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { - return StringRef( rawChars, size ); - } -} // namespace Catch - -constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { - return Catch::StringRef( rawChars, size ); -} - -// end catch_stringref.h -// start catch_preprocessor.hpp - - -#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ -#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) - -#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ -// MSVC needs more evaluations -#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) -#else -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) -#endif - -#define CATCH_REC_END(...) -#define CATCH_REC_OUT - -#define CATCH_EMPTY() -#define CATCH_DEFER(id) id CATCH_EMPTY() - -#define CATCH_REC_GET_END2() 0, CATCH_REC_END -#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 -#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 -#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT -#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) -#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) - -#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) - -#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) - -// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, -// and passes userdata as the first parameter to each invocation, -// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) -#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) -#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ -#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ -#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF -#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) -#else -// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF -#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) -#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) -#endif - -#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ -#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) - -#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) -#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) -#else -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) -#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) -#endif - -#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ - CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) - -#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) -#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) -#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) -#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) -#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) -#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) -#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) -#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) -#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) -#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) -#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) - -#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N - -#define INTERNAL_CATCH_TYPE_GEN\ - template struct TypeList {};\ - template\ - constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ - template class...> struct TemplateTypeList{};\ - template class...Cs>\ - constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ - template\ - struct append;\ - template\ - struct rewrap;\ - template class, typename...>\ - struct create;\ - template class, typename>\ - struct convert;\ - \ - template \ - struct append { using type = T; };\ - template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ - struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ - template< template class L1, typename...E1, typename...Rest>\ - struct append, TypeList, Rest...> { using type = L1; };\ - \ - template< template class Container, template class List, typename...elems>\ - struct rewrap, List> { using type = TypeList>; };\ - template< template class Container, template class List, class...Elems, typename...Elements>\ - struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ - \ - template