From 076b0c0c058d8dd9c8a079b31c68f13dccc4b79a Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sun, 12 Dec 2021 13:43:42 +0000 Subject: [PATCH] Overhaul tests 1. Adds a `libdevilution_so` target when tests are enabled. 2. Each test file is now a separate binary target linked against `libdevilutionx_so` (can now run tests in parallel). 3. Tests are now defined in a separate `test/CMakeLists.txt` file. 4. Building the tests is now controlled by the standard `BUILD_TESTING` option (defined by CTest). 5. Tests are now built by default. 6. On CI, test errors are now reported. Also: * `.clang-format`: Enable SortIncludes in tests * `path_test.cpp`: Fix -Wsign-compare --- .circleci/config.yml | 8 +- .github/workflows/Linux_x86.yml | 2 +- .github/workflows/Linux_x86_64_SDL1.yml | 2 +- .github/workflows/MacOSX.yml | 2 +- .github/workflows/Windows_x64.yml | 2 +- .github/workflows/Windows_x86.yml | 2 +- 3rdParty/googletest/CMakeLists.txt | 14 + CMake/Assets.cmake | 10 +- CMake/Dependencies.cmake | 9 + CMake/VcPkgManifestFeatures.cmake | 2 +- CMake/platforms/amiga.cmake | 1 + CMake/platforms/android.cmake | 1 + CMake/platforms/cpigamesh.cmake | 53 +-- CMake/platforms/ios.cmake | 1 + CMake/platforms/n3ds.cmake | 1 + CMake/platforms/switch.cmake | 1 + CMake/platforms/vita.cmake | 1 + CMakeLists.txt | 94 ++-- CMakeSettings.json | 260 +++++------ Packaging/OpenDingux/build.sh | 1 + Source/automap.h | 17 +- Source/control.h | 3 +- Source/cursor.cpp | 1 + Source/cursor.h | 9 +- Source/diablo.h | 10 +- Source/engine/point.hpp | 4 +- Source/gendung.h | 13 +- Source/init.h | 7 +- Source/lighting.h | 7 +- Source/loadsave.h | 5 +- Source/multi.h | 3 +- Source/nthread.h | 3 +- Source/objects.h | 3 +- Source/path.cpp | 2 +- Source/player.cpp | 2 +- Source/player.h | 7 +- Source/quests.h | 3 +- Source/stores.h | 5 +- Source/utils/attributes.h | 11 + Source/utils/ui_fwd.h | 8 +- test/.clang-format | 2 +- test/CMakeLists.txt | 44 ++ test/animationinfo_test.cpp | 2 +- test/file_util_test.cpp | 2 +- test/lighting_test.cpp | 6 +- test/main.cpp | 7 + test/pack_test.cpp | 41 +- test/path_test.cpp | 550 ++++++++++++------------ test/player_test.h | 2 +- test/quests_test.cpp | 2 +- test/writehero_test.cpp | 8 +- tools/make_src_dist.py | 2 +- 52 files changed, 657 insertions(+), 601 deletions(-) create mode 100644 3rdParty/googletest/CMakeLists.txt create mode 100644 test/CMakeLists.txt diff --git a/.circleci/config.yml b/.circleci/config.yml index 9eea170fb..5ca6bde06 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ jobs: - run: apt update -y - run: apt install -y g++ libsdl2-dev libbz2-dev git rpm wget smpq - run: apt install -y -t 'stretch-backports*' cmake libsodium-dev libpng-dev - - run: cmake -S. -Bbuild .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCPACK=ON -DCMAKE_INSTALL_PREFIX=/usr + - run: cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DCPACK=ON -DCMAKE_INSTALL_PREFIX=/usr - run: cmake --build build -j 2 --target package - store_artifacts: {path: ./build/devilutionx, destination: devilutionx_linux_x86_64} - run: Packaging/nix/LinuxReleasePackaging.sh @@ -25,9 +25,9 @@ jobs: - checkout - run: apt-get update -y - run: apt-get install -y cmake curl g++ git lcov libgtest-dev libgmock-dev libfmt-dev libsdl2-dev libsodium-dev libpng-dev libbz2-dev - - run: cmake -S. -Bbuild -DRUN_TESTS=ON -DENABLE_CODECOVERAGE=ON + - run: cmake -S. -Bbuild -DENABLE_CODECOVERAGE=ON - run: cmake --build build -j 2 - - run: cmake --build build -j 2 --target test + - run: cd build && ctest --output-on-failure - run: bash <(curl -s https://codecov.io/bash) environment: CTEST_OUTPUT_ON_FAILURE: 1 @@ -37,7 +37,7 @@ jobs: working_directory: ~/repo steps: - checkout - - run: /opt/devkitpro/portlibs/switch/bin/aarch64-none-elf-cmake -S. -Bbuild .. -DCMAKE_BUILD_TYPE=RelWithDebInfo + - run: /opt/devkitpro/portlibs/switch/bin/aarch64-none-elf-cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo - run: cmake --build build -j 2 - store_artifacts: {path: ./build/devilutionx.nro, destination: devilutionx.nro} 3ds: diff --git a/.github/workflows/Linux_x86.yml b/.github/workflows/Linux_x86.yml index 609ece9c9..4aa579e8c 100644 --- a/.github/workflows/Linux_x86.yml +++ b/.github/workflows/Linux_x86.yml @@ -32,7 +32,7 @@ jobs: - name: Configure CMake shell: bash working-directory: ${{github.workspace}} - run: cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCPACK=ON -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_TOOLCHAIN_FILE=../CMake/platforms/linux_i386.toolchain.cmake + run: cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DCPACK=ON -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_TOOLCHAIN_FILE=../CMake/platforms/linux_i386.toolchain.cmake - name: Build working-directory: ${{github.workspace}} diff --git a/.github/workflows/Linux_x86_64_SDL1.yml b/.github/workflows/Linux_x86_64_SDL1.yml index bf56d1966..57cf4c362 100644 --- a/.github/workflows/Linux_x86_64_SDL1.yml +++ b/.github/workflows/Linux_x86_64_SDL1.yml @@ -31,7 +31,7 @@ jobs: - name: Configure CMake shell: bash working-directory: ${{github.workspace}} - run: cmake -S. -Bbuild .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCPACK=ON -DUSE_SDL1=ON -DDISCORD_INTEGRATION=ON + run: cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DCPACK=ON -DUSE_SDL1=ON -DDISCORD_INTEGRATION=ON - name: Build working-directory: ${{github.workspace}} diff --git a/.github/workflows/MacOSX.yml b/.github/workflows/MacOSX.yml index 42525d8f0..2c6929aa2 100644 --- a/.github/workflows/MacOSX.yml +++ b/.github/workflows/MacOSX.yml @@ -39,7 +39,7 @@ jobs: # access regardless of the host operating system shell: bash working-directory: ${{github.workspace}} - run: cmake -S. -Bbuild -DMACOSX_STANDALONE_APP_BUNDLE=ON -DDISCORD_INTEGRATION=ON + run: cmake -S. -Bbuild -DBUILD_TESTING=OFF -DMACOSX_STANDALONE_APP_BUNDLE=ON -DDISCORD_INTEGRATION=ON - name: Build working-directory: ${{github.workspace}} diff --git a/.github/workflows/Windows_x64.yml b/.github/workflows/Windows_x64.yml index 72fb3a7b3..c6b9bd926 100644 --- a/.github/workflows/Windows_x64.yml +++ b/.github/workflows/Windows_x64.yml @@ -26,7 +26,7 @@ jobs: - name: Configure CMake shell: bash working-directory: ${{github.workspace}} - run: cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCPACK=ON -DCMAKE_TOOLCHAIN_FILE=../CMake/platforms/mingwcc64.toolchain.cmake -DDEVILUTIONX_SYSTEM_BZIP2=OFF -DDEVILUTIONX_STATIC_LIBSODIUM=ON -DDISCORD_INTEGRATION=ON + run: cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DCPACK=ON -DCMAKE_TOOLCHAIN_FILE=../CMake/platforms/mingwcc64.toolchain.cmake -DDEVILUTIONX_SYSTEM_BZIP2=OFF -DDEVILUTIONX_STATIC_LIBSODIUM=ON -DDISCORD_INTEGRATION=ON - name: Build working-directory: ${{github.workspace}} diff --git a/.github/workflows/Windows_x86.yml b/.github/workflows/Windows_x86.yml index fe9891657..348e83740 100644 --- a/.github/workflows/Windows_x86.yml +++ b/.github/workflows/Windows_x86.yml @@ -26,7 +26,7 @@ jobs: - name: Configure CMake shell: bash working-directory: ${{github.workspace}} - run: cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCPACK=ON -DCMAKE_TOOLCHAIN_FILE=../CMake/platforms/mingwcc.toolchain.cmake -DDEVILUTIONX_SYSTEM_BZIP2=OFF -DDEVILUTIONX_STATIC_LIBSODIUM=ON -DDISCORD_INTEGRATION=ON + run: cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DCPACK=ON -DCMAKE_TOOLCHAIN_FILE=../CMake/platforms/mingwcc.toolchain.cmake -DDEVILUTIONX_SYSTEM_BZIP2=OFF -DDEVILUTIONX_STATIC_LIBSODIUM=ON -DDISCORD_INTEGRATION=ON - name: Build working-directory: ${{github.workspace}} diff --git a/3rdParty/googletest/CMakeLists.txt b/3rdParty/googletest/CMakeLists.txt new file mode 100644 index 000000000..1716e0cea --- /dev/null +++ b/3rdParty/googletest/CMakeLists.txt @@ -0,0 +1,14 @@ +include(functions/FetchContent_MakeAvailableExcludeFromAll) + +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip + URL_HASH MD5=0a912f72cbe9d4e2c976e49219433cb1 +) + +set(INSTALL_GTEST OFF) + +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +FetchContent_MakeAvailable(googletest) diff --git a/CMake/Assets.cmake b/CMake/Assets.cmake index d4a5c94c4..68d4d4dc0 100644 --- a/CMake/Assets.cmake +++ b/CMake/Assets.cmake @@ -29,8 +29,8 @@ if (Gettext_FOUND) set_source_files_properties("${_gmo_file}" PROPERTIES MACOSX_PACKAGE_LOCATION Resources XCODE_EXPLICIT_FILE_TYPE compiled) - add_dependencies(${BIN_TARGET} "${_lang_target}") - target_sources(${BIN_TARGET} PRIVATE "${_gmo_file}") + add_dependencies(libdevilutionx "${_lang_target}") + target_sources(libdevilutionx PRIVATE "${_gmo_file}") endif() if(VITA) @@ -160,7 +160,7 @@ if(APPLE) set_source_files_properties("${src}" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/${_asset_dir}" XCODE_EXPLICIT_FILE_TYPE compiled) - target_sources(${BIN_TARGET} PRIVATE "${src}") + target_sources(libdevilutionx PRIVATE "${src}") endforeach() else() # Copy assets to the build assets subdirectory. This serves two purposes: @@ -195,9 +195,9 @@ else() DEPENDS ${DEVILUTIONX_OUTPUT_ASSETS_FILES} ${devilutionx_lang_targets} ${devilutionx_lang_files} VERBATIM) add_custom_target(devilutionx_mpq DEPENDS "${DEVILUTIONX_MPQ}") - add_dependencies(${BIN_TARGET} devilutionx_mpq) + add_dependencies(libdevilutionx devilutionx_mpq) else() add_custom_target(devilutionx_copied_assets DEPENDS ${DEVILUTIONX_OUTPUT_ASSETS_FILES} ${devilutionx_lang_targets}) - add_dependencies(${BIN_TARGET} devilutionx_copied_assets) + add_dependencies(libdevilutionx devilutionx_copied_assets) endif() endif() diff --git a/CMake/Dependencies.cmake b/CMake/Dependencies.cmake index 0a0273b4b..aa1e1c1b7 100644 --- a/CMake/Dependencies.cmake +++ b/CMake/Dependencies.cmake @@ -135,3 +135,12 @@ endif() if(DISCORD_INTEGRATION) add_subdirectory(3rdParty/discord) endif() + +if(BUILD_TESTING) + dependency_options("googletest" DEVILUTIONX_SYSTEM_GOOGLETEST ON DEVILUTIONX_STATIC_GOOGLETEST) + if(DEVILUTIONX_SYSTEM_GOOGLETEST) + find_package(GTest REQUIRED) + else() + add_subdirectory(3rdParty/googletest) + endif() +endif() diff --git a/CMake/VcPkgManifestFeatures.cmake b/CMake/VcPkgManifestFeatures.cmake index bc78e039a..de7359fa8 100644 --- a/CMake/VcPkgManifestFeatures.cmake +++ b/CMake/VcPkgManifestFeatures.cmake @@ -10,6 +10,6 @@ endif() if(USE_GETTEXT_FROM_VCPKG) list(APPEND VCPKG_MANIFEST_FEATURES "translations") endif() -if(RUN_TESTS) +if(BUILD_TESTING) list(APPEND VCPKG_MANIFEST_FEATURES "tests") endif() diff --git a/CMake/platforms/amiga.cmake b/CMake/platforms/amiga.cmake index eb41f8eab..07e68991a 100644 --- a/CMake/platforms/amiga.cmake +++ b/CMake/platforms/amiga.cmake @@ -1,3 +1,4 @@ +set(BUILD_TESTING OFF) set(ASAN OFF) set(UBSAN OFF) set(NONET ON) diff --git a/CMake/platforms/android.cmake b/CMake/platforms/android.cmake index 3b3038234..d99d4a353 100644 --- a/CMake/platforms/android.cmake +++ b/CMake/platforms/android.cmake @@ -1,4 +1,5 @@ # General build options. +set(BUILD_TESTING OFF) set(VIRTUAL_GAMEPAD ON) # Disable all system dependencies. diff --git a/CMake/platforms/cpigamesh.cmake b/CMake/platforms/cpigamesh.cmake index 11fe7fcbb..46ffb9f89 100644 --- a/CMake/platforms/cpigamesh.cmake +++ b/CMake/platforms/cpigamesh.cmake @@ -1,26 +1,27 @@ -set(NONET ON) -set(PREFILL_PLAYER_NAME ON) -set(HAS_KBCTRL 1) -set(LTO ON) -set(DIST ON) -set(DEBUG OFF) -set(ASAN OFF) -set(UBSAN OFF) -set(KBCTRL_BUTTON_DPAD_LEFT SDLK_LEFT) -set(KBCTRL_BUTTON_DPAD_RIGHT SDLK_RIGHT) -set(KBCTRL_BUTTON_DPAD_UP SDLK_UP) -set(KBCTRL_BUTTON_DPAD_DOWN SDLK_DOWN) -set(KBCTRL_BUTTON_X SDLK_i) -set(KBCTRL_BUTTON_Y SDLK_u) -set(KBCTRL_BUTTON_B SDLK_k) -set(KBCTRL_BUTTON_A SDLK_j) -set(KBCTRL_BUTTON_RIGHTSHOULDER SDLK_l) -set(KBCTRL_BUTTON_LEFTSHOULDER SDLK_h) -set(KBCTRL_BUTTON_AXIS_TRIGGERLEFT SDLK_y) -set(KBCTRL_BUTTON_AXIS_TRIGGERRIGHT SDLK_o) -set(KBCTRL_BUTTON_LEFTSTICK SDLK_PAGEUP) -set(KBCTRL_BUTTON_RIGHTSTICK SDLK_PAGEDOWN) -set(KBCTRL_BUTTON_START SDLK_RETURN) -set(KBCTRL_BUTTON_BACK SDLK_SPACE) - -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") +set(BUILD_TESTING OFF) +set(NONET ON) +set(PREFILL_PLAYER_NAME ON) +set(HAS_KBCTRL 1) +set(LTO ON) +set(DIST ON) +set(DEBUG OFF) +set(ASAN OFF) +set(UBSAN OFF) +set(KBCTRL_BUTTON_DPAD_LEFT SDLK_LEFT) +set(KBCTRL_BUTTON_DPAD_RIGHT SDLK_RIGHT) +set(KBCTRL_BUTTON_DPAD_UP SDLK_UP) +set(KBCTRL_BUTTON_DPAD_DOWN SDLK_DOWN) +set(KBCTRL_BUTTON_X SDLK_i) +set(KBCTRL_BUTTON_Y SDLK_u) +set(KBCTRL_BUTTON_B SDLK_k) +set(KBCTRL_BUTTON_A SDLK_j) +set(KBCTRL_BUTTON_RIGHTSHOULDER SDLK_l) +set(KBCTRL_BUTTON_LEFTSHOULDER SDLK_h) +set(KBCTRL_BUTTON_AXIS_TRIGGERLEFT SDLK_y) +set(KBCTRL_BUTTON_AXIS_TRIGGERRIGHT SDLK_o) +set(KBCTRL_BUTTON_LEFTSTICK SDLK_PAGEUP) +set(KBCTRL_BUTTON_RIGHTSTICK SDLK_PAGEDOWN) +set(KBCTRL_BUTTON_START SDLK_RETURN) +set(KBCTRL_BUTTON_BACK SDLK_SPACE) + +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") diff --git a/CMake/platforms/ios.cmake b/CMake/platforms/ios.cmake index 558661583..c9875b2af 100644 --- a/CMake/platforms/ios.cmake +++ b/CMake/platforms/ios.cmake @@ -1,4 +1,5 @@ # General build options. +set(BUILD_TESTING OFF) set(VIRTUAL_GAMEPAD ON) # Disable all system dependencies. diff --git a/CMake/platforms/n3ds.cmake b/CMake/platforms/n3ds.cmake index 78c14610c..fbe9f9e52 100644 --- a/CMake/platforms/n3ds.cmake +++ b/CMake/platforms/n3ds.cmake @@ -1,6 +1,7 @@ #General compilation options set(ASAN OFF) set(UBSAN OFF) +set(BUILD_TESTING OFF) set(DEVILUTIONX_SYSTEM_LIBSODIUM OFF) set(DEVILUTIONX_SYSTEM_LIBFMT OFF) set(DEVILUTIONX_STATIC_LIBSODIUM ON) diff --git a/CMake/platforms/switch.cmake b/CMake/platforms/switch.cmake index e151e9059..6919f3734 100644 --- a/CMake/platforms/switch.cmake +++ b/CMake/platforms/switch.cmake @@ -2,6 +2,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/switch") set(ASAN OFF) set(UBSAN OFF) +set(BUILD_TESTING OFF) set(DEVILUTIONX_SYSTEM_LIBSODIUM OFF) set(DISABLE_ZERO_TIER ON) diff --git a/CMake/platforms/vita.cmake b/CMake/platforms/vita.cmake index 30ce014a8..0b690f783 100644 --- a/CMake/platforms/vita.cmake +++ b/CMake/platforms/vita.cmake @@ -1,5 +1,6 @@ set(ASAN OFF) set(UBSAN OFF) +set(BUILD_TESTING OFF) set(DISABLE_ZERO_TIER ON) set(PREFILL_PLAYER_NAME ON) add_definitions("-DMO_LANG_DIR=\"app0:/\"") diff --git a/CMakeLists.txt b/CMakeLists.txt index ca9f480b5..7d785ffc6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,8 +38,9 @@ option(TSAN "Enable thread sanitizer (not compatible with ASAN=ON)" OFF) DEBUG_OPTION(DEBUG "Enable debug mode in engine") option(GPERF "Build with GPerfTools profiler" OFF) cmake_dependent_option(GPERF_HEAP_FIRST_GAME_ITERATION "Save heap profile of the first game iteration" OFF "GPERF" OFF) +option(BUILD_TESTING "Build tests." ON) option(DISABLE_LTO "Disable link-time optimization (by default enabled in release mode)" OFF) -option(PIE "Generate position-independent code" OFF) +cmake_dependent_option(PIE "Generate position-independent code" OFF "BUILD_TESTING" ON) option(MACOSX_STANDALONE_APP_BUNDLE "Generate a portable app bundle to use on other devices (requires sudo)" OFF) option(USE_SDL1 "Use SDL1.2 instead of SDL2" OFF) option(NONET "Disable network support" OFF) @@ -47,8 +48,7 @@ cmake_dependent_option(DISABLE_TCP "Disable TCP multiplayer option" OFF "NOT NON cmake_dependent_option(DISABLE_ZERO_TIER "Disable ZeroTier multiplayer option" OFF "NOT NONET" ON) cmake_dependent_option(PACKET_ENCRYPTION "Encrypt network packets" ON "NOT NONET" OFF) option(NOSOUND "Disable sound support" OFF) -option(RUN_TESTS "Build and run tests" OFF) -option(ENABLE_CODECOVERAGE "Instrument code for code coverage (only enabled with RUN_TESTS)" OFF) +option(ENABLE_CODECOVERAGE "Instrument code for code coverage (only enabled with BUILD_TESTING)" OFF) option(DISCORD_INTEGRATION "Build with Discord SDK for rich presence support" OFF) option(DISABLE_STREAMING_MUSIC "Disable streaming music (to work around broken platform implementations)" OFF) @@ -58,10 +58,6 @@ mark_as_advanced(DISABLE_STREAMING_SOUNDS) option(STREAM_ALL_AUDIO "Stream all the audio. For extremely RAM-constrained platforms.") mark_as_advanced(STREAM_ALL_AUDIO) -if(PIE) - set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) -endif() - if(TSAN) set(ASAN OFF) endif() @@ -117,6 +113,12 @@ else() endif() set(PROJECT_VERSION_WITH_SUFFIX "${PROJECT_VERSION}$<$:-${VERSION_SUFFIX}>") +# This built-in CMake module adds a BUILD_TESTING option (ON by default). +# Must be included in the top-level `CMakeLists.txt` after calling `project`. +# Because we must include `VcPkgManifestFeatures` before the `project` call, +# we add a BUILD_TESTING option ourselves above as well. +include(CTest) + # Platform definitions can override options and we want `cmake_dependent_option` to see the effects, # so ideally we would include Platforms.cmake before definining the options. # @@ -126,6 +128,17 @@ set(PROJECT_VERSION_WITH_SUFFIX "${PROJECT_VERSION}$<$:-${VERSION_ include(Platforms) # Recalculate the dependent options after including the Platforms: +if(BUILD_TESTING) + # For tests, we build a libdevilutionx.so shared library. + # When this libdevilutionx.so is linked against certain static libraries, + # they must be compiled with `-fPIC`. + set(PIE ON) +endif() + +if(PIE) + set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) +endif() + if(NONET) set(DISABLE_TCP ON) set(DISABLE_ZERO_TIER ON) @@ -415,33 +428,6 @@ if(DISCORD_INTEGRATION) ) endif() -if(RUN_TESTS) - set(devilutionxtest_SRCS - test/appfat_test.cpp - test/automap_test.cpp - test/control_test.cpp - test/cursor_test.cpp - test/codec_test.cpp - test/dead_test.cpp - test/diablo_test.cpp - test/drlg_l1_test.cpp - test/effects_test.cpp - test/file_util_test.cpp - test/inv_test.cpp - test/lighting_test.cpp - test/main.cpp - test/missiles_test.cpp - test/pack_test.cpp - test/path_test.cpp - test/player_test.cpp - test/quests_test.cpp - test/random_test.cpp - test/scrollrt_test.cpp - test/stores_test.cpp - test/writehero_test.cpp - test/animationinfo_test.cpp) -endif() - add_library(libdevilutionx OBJECT ${libdevilutionx_SRCS}) if(ANDROID) add_library(${BIN_TARGET} SHARED Source/main.cpp) @@ -457,6 +443,20 @@ else() endif() target_link_libraries(${BIN_TARGET} PRIVATE libdevilutionx) +if(BUILD_TESTING) + if(ENABLE_CODECOVERAGE) + if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + message(WARNING "Codecoverage not supported with MSVC") + else() + target_compile_options(libdevilutionx PUBLIC --coverage) + target_link_options(libdevilutionx PUBLIC --coverage) + endif() + endif() + + target_compile_definitions(libdevilutionx PRIVATE _DVL_EXPORTING) + add_subdirectory(test) +endif() + # Use file GENERATE instead of configure_file because configure_file # does not support generator expressions. get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) @@ -475,31 +475,6 @@ file(GENERATE OUTPUT ${CONFIG_PATH} CONTENT #define PROJECT_VERSION_PATCH ${PROJECT_VERSION_PATCH} ") -if(RUN_TESTS) - add_executable(devilutionx-tests WIN32 MACOSX_BUNDLE ${devilutionxtest_SRCS}) - include(CTest) - include(GoogleTest) - find_package(GTest REQUIRED) - add_definitions(-DRUN_TESTS) - target_include_directories(devilutionx-tests PRIVATE ${GTEST_INCLUDE_DIRS}) - target_link_libraries(devilutionx-tests PRIVATE libdevilutionx) - target_link_libraries(devilutionx-tests PRIVATE ${GTEST_LIBRARIES}) - target_include_directories(devilutionx-tests PRIVATE 3rdParty/PicoSHA2) - if(ENABLE_CODECOVERAGE) - if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - message(WARNING "Codecoverage not supported with MSVC") - else() - target_compile_options(devilutionx-tests PRIVATE -fprofile-arcs -ftest-coverage) - target_compile_options(libdevilutionx PRIVATE -fprofile-arcs -ftest-coverage) - target_compile_options(${BIN_TARGET} PRIVATE -fprofile-arcs -ftest-coverage) - target_link_options(devilutionx-tests PRIVATE -fprofile-arcs) - target_link_options(libdevilutionx PRIVATE -fprofile-arcs) - target_link_options(${BIN_TARGET} PRIVATE -fprofile-arcs) - endif() - endif() - gtest_add_tests(devilutionx-tests "" AUTO) -endif() - if(DISCORD_INTEGRATION) target_compile_definitions(libdevilutionx PRIVATE DISCORD) target_link_libraries(libdevilutionx PRIVATE discord discord_game_sdk) @@ -559,6 +534,7 @@ foreach( DISABLE_ZERO_TIER DISABLE_STREAMING_MUSIC DISABLE_STREAMING_SOUNDS + BUILD_TESTING GPERF GPERF_HEAP_MAIN GPERF_HEAP_FIRST_GAME_ITERATION diff --git a/CMakeSettings.json b/CMakeSettings.json index 4df4c2be5..1f8447ce5 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -1,148 +1,112 @@ -{ - "configurations": [ - { - "name": "x64-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "buildRoot": "${workspaceRoot}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "inheritEnvironments": [ "msvc_x64" ], - "intelliSenseMode": "windows-msvc-x64", - "enableClangTidyCodeAnalysis": true, - "variables": [ - { - "name": "DISCORD_INTEGRATION", - "value": "True", - "type": "BOOL" - } - ] - }, - { - "name": "x64-Debug-UnitTests", - "generator": "Ninja", - "configurationType": "Debug", - "buildRoot": "${workspaceRoot}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "inheritEnvironments": [ "msvc_x64" ], - "intelliSenseMode": "windows-msvc-x64", - "cmakeCommandArgs": "-DRUN_TESTS=ON", - "enableClangTidyCodeAnalysis": true, - "variables": [ - { - "name": "DISCORD_INTEGRATION", - "value": "True", - "type": "BOOL" - } - ] - }, - { - "name": "x64-Debug-SDL1", - "generator": "Ninja", - "configurationType": "Debug", - "buildRoot": "${workspaceRoot}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "inheritEnvironments": [ "msvc_x64" ], - "intelliSenseMode": "windows-msvc-x64", - "cmakeCommandArgs": "-DUSE_SDL1=ON", - "enableClangTidyCodeAnalysis": true, - "variables": [ - { - "name": "DISCORD_INTEGRATION", - "value": "True", - "type": "BOOL" - } - ] - }, - { - "name": "x64-Release", - "generator": "Ninja", - "configurationType": "Release", - "buildRoot": "${workspaceRoot}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "cmakeCommandArgs": "-DCPACK=ON", - "inheritEnvironments": [ "msvc_x64" ], - "intelliSenseMode": "windows-msvc-x64", - "enableClangTidyCodeAnalysis": true, - "variables": [ - { - "name": "DISCORD_INTEGRATION", - "value": "True", - "type": "BOOL" - } - ] - }, - { - "name": "x86-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "buildRoot": "${workspaceRoot}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "inheritEnvironments": [ "msvc_x86" ], - "intelliSenseMode": "windows-msvc-x86", - "enableClangTidyCodeAnalysis": true, - "variables": [ - { - "name": "DISCORD_INTEGRATION", - "value": "True", - "type": "BOOL" - } - ] - }, - { - "name": "x86-Debug-UnitTests", - "generator": "Ninja", - "configurationType": "Debug", - "buildRoot": "${workspaceRoot}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "inheritEnvironments": [ "msvc_x86" ], - "intelliSenseMode": "windows-msvc-x86", - "cmakeCommandArgs": "-DRUN_TESTS=ON", - "enableClangTidyCodeAnalysis": true, - "variables": [ - { - "name": "DISCORD_INTEGRATION", - "value": "True", - "type": "BOOL" - } - ] - }, - { - "name": "x86-Release", - "generator": "Ninja", - "configurationType": "Release", - "buildRoot": "${workspaceRoot}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "cmakeCommandArgs": "-DCPACK=ON", - "inheritEnvironments": [ "msvc_x86" ], - "intelliSenseMode": "windows-msvc-x86", - "enableClangTidyCodeAnalysis": true, - "variables": [ - { - "name": "DISCORD_INTEGRATION", - "value": "True", - "type": "BOOL" - } - ] - }, - { - "name": "x64-Debug-WSL-GCC", - "generator": "Ninja", - "configurationType": "Debug", - "buildRoot": "${workspaceRoot}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "cmakeExecutable": "cmake", - "cmakeCommandArgs": "", - "buildCommandArgs": "", - "ctestCommandArgs": "", - "inheritEnvironments": [ "linux_x64" ], - "wslPath": "${defaultWSLPath}", - "variables": [ - { - "name": "DISCORD_INTEGRATION", - "value": "True", - "type": "BOOL" - } - ] - } - ] -} +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${workspaceRoot}\\build\\${name}", + "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", + "inheritEnvironments": [ "msvc_x64" ], + "intelliSenseMode": "windows-msvc-x64", + "enableClangTidyCodeAnalysis": true, + "variables": [ + { + "name": "DISCORD_INTEGRATION", + "value": "True", + "type": "BOOL" + } + ] + }, + { + "name": "x64-Debug-SDL1", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${workspaceRoot}\\build\\${name}", + "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", + "inheritEnvironments": [ "msvc_x64" ], + "intelliSenseMode": "windows-msvc-x64", + "cmakeCommandArgs": "-DUSE_SDL1=ON", + "enableClangTidyCodeAnalysis": true, + "variables": [ + { + "name": "DISCORD_INTEGRATION", + "value": "True", + "type": "BOOL" + } + ] + }, + { + "name": "x64-Release", + "generator": "Ninja", + "configurationType": "Release", + "buildRoot": "${workspaceRoot}\\build\\${name}", + "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", + "cmakeCommandArgs": "-DCPACK=ON", + "inheritEnvironments": [ "msvc_x64" ], + "intelliSenseMode": "windows-msvc-x64", + "enableClangTidyCodeAnalysis": true, + "variables": [ + { + "name": "DISCORD_INTEGRATION", + "value": "True", + "type": "BOOL" + } + ] + }, + { + "name": "x86-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${workspaceRoot}\\build\\${name}", + "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", + "inheritEnvironments": [ "msvc_x86" ], + "intelliSenseMode": "windows-msvc-x86", + "enableClangTidyCodeAnalysis": true, + "variables": [ + { + "name": "DISCORD_INTEGRATION", + "value": "True", + "type": "BOOL" + } + ] + }, + { + "name": "x86-Release", + "generator": "Ninja", + "configurationType": "Release", + "buildRoot": "${workspaceRoot}\\build\\${name}", + "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", + "cmakeCommandArgs": "-DCPACK=ON", + "inheritEnvironments": [ "msvc_x86" ], + "intelliSenseMode": "windows-msvc-x86", + "enableClangTidyCodeAnalysis": true, + "variables": [ + { + "name": "DISCORD_INTEGRATION", + "value": "True", + "type": "BOOL" + } + ] + }, + { + "name": "x64-Debug-WSL-GCC", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${workspaceRoot}\\build\\${name}", + "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", + "cmakeExecutable": "cmake", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "linux_x64" ], + "wslPath": "${defaultWSLPath}", + "variables": [ + { + "name": "DISCORD_INTEGRATION", + "value": "True", + "type": "BOOL" + } + ] + } + ] +} diff --git a/Packaging/OpenDingux/build.sh b/Packaging/OpenDingux/build.sh index 235da6c40..32c6955dd 100755 --- a/Packaging/OpenDingux/build.sh +++ b/Packaging/OpenDingux/build.sh @@ -79,6 +79,7 @@ cmake_configure() { cmake -S. -B"$BUILD_DIR" \ "-DTARGET_PLATFORM=$TARGET" \ -DCMAKE_TOOLCHAIN_FILE="${TOOLCHAIN}/usr/share/buildroot/toolchainfile.cmake" \ + -DBUILD_TESTING=OFF \ -DDEVILUTIONX_SYSTEM_LIBSODIUM=OFF \ -DDEVILUTIONX_SYSTEM_BZIP2=OFF \ -DSTACK_PROTECTOR=OFF \ diff --git a/Source/automap.h b/Source/automap.h index 2587987ae..3b4d490e8 100644 --- a/Source/automap.h +++ b/Source/automap.h @@ -11,6 +11,7 @@ #include "engine/displacement.hpp" #include "engine/point.hpp" #include "gendung.h" +#include "utils/attributes.h" namespace devilution { @@ -28,17 +29,17 @@ enum MapExplorationType : uint8_t { }; /** Specifies whether the automap is enabled. */ -extern bool AutomapActive; +extern DVL_API_FOR_TEST bool AutomapActive; /** Tracks the explored areas of the map. */ extern uint8_t AutomapView[DMAXX][DMAXY]; /** Specifies the scale of the automap. */ -extern int AutoMapScale; -extern Displacement AutomapOffset; -extern int AmLine64; -extern int AmLine32; -extern int AmLine16; -extern int AmLine8; -extern int AmLine4; +extern DVL_API_FOR_TEST int AutoMapScale; +extern DVL_API_FOR_TEST Displacement AutomapOffset; +extern DVL_API_FOR_TEST int AmLine64; +extern DVL_API_FOR_TEST int AmLine32; +extern DVL_API_FOR_TEST int AmLine16; +extern DVL_API_FOR_TEST int AmLine8; +extern DVL_API_FOR_TEST int AmLine4; /** * @brief Initializes the automap. diff --git a/Source/control.h b/Source/control.h index d7fce07fb..ecdf78ef8 100644 --- a/Source/control.h +++ b/Source/control.h @@ -15,6 +15,7 @@ #include "panels/ui_panels.hpp" #include "spelldat.h" #include "spells.h" +#include "utils/attributes.h" #include "utils/stdcompat/optional.hpp" #include "utils/stdcompat/string_view.hpp" #include "utils/ui_fwd.h" @@ -38,7 +39,7 @@ extern bool lvlbtndown; extern int dropGoldValue; extern bool drawmanaflag; extern bool chrbtnactive; -extern int pnumlines; +extern DVL_API_FOR_TEST int pnumlines; extern UiFlags InfoColor; extern char tempstr[256]; extern int sbooktab; diff --git a/Source/cursor.cpp b/Source/cursor.cpp index 81e7e74e1..39cfbc55c 100644 --- a/Source/cursor.cpp +++ b/Source/cursor.cpp @@ -20,6 +20,7 @@ #include "towners.h" #include "track.h" #include "trigs.h" +#include "utils/attributes.h" #include "utils/language.h" namespace devilution { diff --git a/Source/cursor.h b/Source/cursor.h index 4b040125a..065107050 100644 --- a/Source/cursor.h +++ b/Source/cursor.h @@ -11,6 +11,7 @@ #include "engine.h" #include "engine/cel_sprite.hpp" #include "miniwin/miniwin.h" +#include "utils/attributes.h" #include "utils/stdcompat/optional.hpp" namespace devilution { @@ -31,16 +32,16 @@ enum cursor_id : uint8_t { CURSOR_FIRSTITEM, }; -extern Size cursSize; +extern DVL_API_FOR_TEST Size cursSize; extern int pcursmonst; -extern Size icursSize28; -extern Size icursSize; +extern DVL_API_FOR_TEST Size icursSize28; +extern DVL_API_FOR_TEST Size icursSize; extern int8_t pcursinvitem; extern int8_t pcursitem; extern int8_t pcursobj; extern int8_t pcursplr; extern Point cursPosition; -extern int pcurs; +extern DVL_API_FOR_TEST int pcurs; void InitCursor(); void FreeCursor(); diff --git a/Source/diablo.h b/Source/diablo.h index 11f1babc5..62061457e 100644 --- a/Source/diablo.h +++ b/Source/diablo.h @@ -7,14 +7,14 @@ #include -#include "utils/endian.hpp" - #include "controls/keymapper.hpp" #ifdef _DEBUG #include "monstdat.h" #endif #include "gendung.h" #include "init.h" +#include "utils/attributes.h" +#include "utils/endian.hpp" namespace devilution { @@ -61,21 +61,21 @@ extern dungeon_type gnLevelTypeTbl[NUMLEVELS]; extern Point MousePosition; extern bool gbRunGame; extern bool gbRunGameResult; -extern bool zoomflag; +extern DVL_API_FOR_TEST bool zoomflag; extern bool gbProcessPlayers; extern bool gbLoadGame; extern bool cineflag; extern int force_redraw; /* These are defined in fonts.h */ extern void FontsCleanup(); -extern int PauseMode; +extern DVL_API_FOR_TEST int PauseMode; extern bool gbNestArt; extern bool gbBard; extern bool gbBarbarian; /** * @brief Don't show Messageboxes or other user-interaction. Needed for UnitTests. */ -extern bool gbQuietMode; +extern DVL_API_FOR_TEST bool gbQuietMode; extern clicktype sgbMouseDown; extern uint16_t gnTickDelay; extern char gszProductName[64]; diff --git a/Source/engine/point.hpp b/Source/engine/point.hpp index 47facd2cc..8afcb5632 100644 --- a/Source/engine/point.hpp +++ b/Source/engine/point.hpp @@ -1,7 +1,7 @@ #pragma once #include -#ifdef RUN_TESTS +#ifdef BUILD_TESTING #include #endif @@ -153,7 +153,7 @@ struct Point { return std::max(offset.deltaX, offset.deltaY); } -#ifdef RUN_TESTS +#ifdef BUILD_TESTING /** * @brief Format points nicely in test failure messages * @param stream output stream, expected to have overloads for int and char* diff --git a/Source/gendung.h b/Source/gendung.h index 82143732b..14b67f283 100644 --- a/Source/gendung.h +++ b/Source/gendung.h @@ -12,6 +12,7 @@ #include "engine/cel_sprite.hpp" #include "engine/point.hpp" #include "scrollrt.h" +#include "utils/attributes.h" #include "utils/enum_traits.h" #include "utils/stdcompat/optional.hpp" @@ -165,7 +166,7 @@ extern std::array nBlockTable; /** * List of path blocking dPieces */ -extern std::array nSolidTable; +extern DVL_API_FOR_TEST std::array nSolidTable; /** * List of transparent dPieces */ @@ -180,7 +181,7 @@ extern Point dminPosition; /** Specifies the maximum X,Y-coordinates of the map. */ extern Point dmaxPosition; /** Specifies the active dungeon type of the current game. */ -extern dungeon_type leveltype; +extern DVL_API_FOR_TEST dungeon_type leveltype; /** Specifies the active dungeon level of the current game. */ extern uint8_t currlevel; extern bool setlevel; @@ -196,12 +197,12 @@ extern char TransVal; /** Specifies the active transparency indices. */ extern bool TransList[256]; /** Contains the piece IDs of each tile on the map. */ -extern int dPiece[MAXDUNX][MAXDUNY]; +extern DVL_API_FOR_TEST int dPiece[MAXDUNX][MAXDUNY]; /** Specifies the dungeon piece information for a given coordinate and block number. */ extern MICROS dpiece_defs_map_2[MAXDUNX][MAXDUNY]; /** Specifies the transparency at each coordinate of the map. */ extern int8_t dTransVal[MAXDUNX][MAXDUNY]; -extern char dLight[MAXDUNX][MAXDUNY]; +extern DVL_API_FOR_TEST char dLight[MAXDUNX][MAXDUNY]; extern char dPreLight[MAXDUNX][MAXDUNY]; /** Holds various information about dungeon tiles, @see DungeonFlag */ extern DungeonFlag dFlags[MAXDUNX][MAXDUNY]; @@ -220,9 +221,9 @@ extern int16_t dMonster[MAXDUNX][MAXDUNY]; * dDead[x][y] & 0x1F - index of dead * dDead[x][y] >> 0x5 - direction */ -extern int8_t dCorpse[MAXDUNX][MAXDUNY]; +extern DVL_API_FOR_TEST int8_t dCorpse[MAXDUNX][MAXDUNY]; /** Contains the object numbers (objects array indices) of the map. */ -extern char dObject[MAXDUNX][MAXDUNY]; +extern DVL_API_FOR_TEST char dObject[MAXDUNX][MAXDUNY]; /** Contains the item numbers (items array indices) of the map. */ extern int8_t dItem[MAXDUNX][MAXDUNY]; /** diff --git a/Source/init.h b/Source/init.h index 8d6ebe9ff..8a5fb660f 100644 --- a/Source/init.h +++ b/Source/init.h @@ -7,6 +7,7 @@ #include "miniwin/miniwin.h" #include "mpq/mpq_reader.hpp" +#include "utils/attributes.h" namespace devilution { @@ -15,9 +16,9 @@ extern std::optional hellfire_mpq; extern WNDPROC CurrentProc; extern std::optional spawn_mpq; extern std::optional diabdat_mpq; -extern bool gbIsSpawn; -extern bool gbIsHellfire; -extern bool gbVanilla; +extern DVL_API_FOR_TEST bool gbIsSpawn; +extern DVL_API_FOR_TEST bool gbIsHellfire; +extern DVL_API_FOR_TEST bool gbVanilla; extern bool forceHellfire; extern std::optional hfmonk_mpq; extern std::optional hfbard_mpq; diff --git a/Source/lighting.h b/Source/lighting.h index b3ac76d77..71afb467f 100644 --- a/Source/lighting.h +++ b/Source/lighting.h @@ -11,6 +11,7 @@ #include "engine.h" #include "engine/point.hpp" #include "miniwin/miniwin.h" +#include "utils/attributes.h" namespace devilution { @@ -45,7 +46,7 @@ extern uint8_t ActiveLights[MAXLIGHTS]; extern int ActiveLightCount; extern char LightsMax; extern std::array LightTables; -extern bool DisableLighting; +extern DVL_API_FOR_TEST bool DisableLighting; extern bool UpdateLighting; void DoLighting(Point position, int nRadius, int Lnum); @@ -74,8 +75,8 @@ void lighting_color_cycling(); /* rdata */ -extern const int8_t CrawlTable[2749]; -extern const int CrawlNum[19]; +extern DVL_API_FOR_TEST const int8_t CrawlTable[2749]; +extern DVL_API_FOR_TEST const int CrawlNum[19]; extern const uint8_t VisionCrawlTable[23][30]; } // namespace devilution diff --git a/Source/loadsave.h b/Source/loadsave.h index 3a5eeb999..183bb705d 100644 --- a/Source/loadsave.h +++ b/Source/loadsave.h @@ -6,11 +6,12 @@ #pragma once #include "player.h" +#include "utils/attributes.h" namespace devilution { -extern bool gbIsHellfireSaveGame; -extern uint8_t giNumberOfLevels; +extern DVL_API_FOR_TEST bool gbIsHellfireSaveGame; +extern DVL_API_FOR_TEST uint8_t giNumberOfLevels; void RemoveInvalidItem(Item &pItem); _item_indexes RemapItemIdxFromDiablo(_item_indexes i); diff --git a/Source/multi.h b/Source/multi.h index 89906b9f7..9333a9025 100644 --- a/Source/multi.h +++ b/Source/multi.h @@ -8,6 +8,7 @@ #include #include "msg.h" +#include "utils/attributes.h" namespace devilution { @@ -37,7 +38,7 @@ extern BYTE gbActivePlayers; extern bool gbGameDestroyed; extern GameData sgGameInitInfo; extern bool gbSelectProvider; -extern bool gbIsMultiplayer; +extern DVL_API_FOR_TEST bool gbIsMultiplayer; extern char szPlayerName[128]; extern bool PublicGame; extern BYTE gbDeltaSender; diff --git a/Source/nthread.h b/Source/nthread.h index 7291d9aa1..e225bd0e1 100644 --- a/Source/nthread.h +++ b/Source/nthread.h @@ -6,6 +6,7 @@ #pragma once #include "player.h" +#include "utils/attributes.h" namespace devilution { @@ -15,7 +16,7 @@ extern uint32_t gdwTurnsInTransit; extern uintptr_t glpMsgTbl[MAX_PLRS]; extern uint32_t gdwLargestMsgSize; extern uint32_t gdwNormalMsgSize; -extern float gfProgressToNextGameTick; // the progress as a fraction (0.0f to 1.0f) in time to the next game tick +extern DVL_API_FOR_TEST float gfProgressToNextGameTick; // the progress as a fraction (0.0f to 1.0f) in time to the next game tick extern int last_tick; void nthread_terminate_game(const char *pszFcn); diff --git a/Source/objects.h b/Source/objects.h index d7f73632a..7bb65db8f 100644 --- a/Source/objects.h +++ b/Source/objects.h @@ -13,6 +13,7 @@ #include "monster.h" #include "objdat.h" #include "textdat.h" +#include "utils/attributes.h" namespace devilution { @@ -230,7 +231,7 @@ struct Object { } }; -extern Object Objects[MAXOBJECTS]; +extern DVL_API_FOR_TEST Object Objects[MAXOBJECTS]; extern int AvailableObjects[MAXOBJECTS]; extern int ActiveObjects[MAXOBJECTS]; extern int ActiveObjectCount; diff --git a/Source/path.cpp b/Source/path.cpp index 37abffe09..d7b2509ae 100644 --- a/Source/path.cpp +++ b/Source/path.cpp @@ -545,7 +545,7 @@ std::optional FindClosestValidPosition(const std::function & return {}; } -#ifdef RUN_TESTS +#ifdef BUILD_TESTING int TestPathGetHeuristicCost(Point startPosition, Point destinationPosition) { return GetHeuristicCost(startPosition, destinationPosition); diff --git a/Source/player.cpp b/Source/player.cpp index 94dd41605..5e76ac27c 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -3813,7 +3813,7 @@ void PlayDungMsgs() } } -#ifdef RUN_TESTS +#ifdef BUILD_TESTING bool TestPlayerDoGotHit(int pnum) { return DoGotHit(pnum); diff --git a/Source/player.h b/Source/player.h index 93e98f23a..19ec72183 100644 --- a/Source/player.h +++ b/Source/player.h @@ -20,6 +20,7 @@ #include "multi.h" #include "path.h" #include "spelldat.h" +#include "utils/attributes.h" #include "utils/enum_traits.h" namespace devilution { @@ -614,9 +615,9 @@ struct Player { } }; -extern int MyPlayerId; -extern Player *MyPlayer; -extern Player Players[MAX_PLRS]; +extern DVL_API_FOR_TEST int MyPlayerId; +extern DVL_API_FOR_TEST Player *MyPlayer; +extern DVL_API_FOR_TEST Player Players[MAX_PLRS]; extern bool MyPlayerIsDead; extern int BlockBonuses[enum_size::value]; diff --git a/Source/quests.h b/Source/quests.h index 1e8548445..477779528 100644 --- a/Source/quests.h +++ b/Source/quests.h @@ -14,6 +14,7 @@ #include "monster.h" #include "objdat.h" #include "textdat.h" +#include "utils/attributes.h" #include "utils/stdcompat/optional.hpp" namespace devilution { @@ -73,7 +74,7 @@ struct QuestData { extern bool QuestLogIsOpen; extern std::optional pQLogCel; -extern Quest Quests[MAXQUESTS]; +extern DVL_API_FOR_TEST Quest Quests[MAXQUESTS]; extern Point ReturnLvlPosition; extern dungeon_type ReturnLevelType; extern int ReturnLevel; diff --git a/Source/stores.h b/Source/stores.h index a1166224d..3612388cb 100644 --- a/Source/stores.h +++ b/Source/stores.h @@ -9,6 +9,7 @@ #include "control.h" #include "engine.h" #include "engine/cel_sprite.hpp" +#include "utils/attributes.h" #include "utils/stdcompat/optional.hpp" namespace devilution { @@ -77,11 +78,11 @@ extern std::optional pSTextSlidCels; extern talk_id stextflag; /** Current index into storehidx/storehold */ -extern int storenumh; +extern DVL_API_FOR_TEST int storenumh; /** Map of inventory items being presented in the store */ extern char storehidx[48]; /** Copies of the players items as presented in the store */ -extern Item storehold[48]; +extern DVL_API_FOR_TEST Item storehold[48]; /** Temporary item used to generate gold piles by various function */ extern Item golditem; diff --git a/Source/utils/attributes.h b/Source/utils/attributes.h index 367fb5891..163545791 100644 --- a/Source/utils/attributes.h +++ b/Source/utils/attributes.h @@ -31,3 +31,14 @@ #else #define DVL_ATTRIBUTE_HOT #endif + +// Any global data used by tests must be marked with `DVL_API_FOR_TEST`. +#if defined(_MSC_VER) && defined(BUILD_TESTING) +#ifdef _DVL_EXPORTING +#define DVL_API_FOR_TEST __declspec(dllexport) +#else +#define DVL_API_FOR_TEST __declspec(dllimport) +#endif +#else +#define DVL_API_FOR_TEST +#endif diff --git a/Source/utils/ui_fwd.h b/Source/utils/ui_fwd.h index d9ed997f9..5f37814c8 100644 --- a/Source/utils/ui_fwd.h +++ b/Source/utils/ui_fwd.h @@ -2,11 +2,13 @@ #include +#include "utils/attributes.h" + namespace devilution { -extern Uint16 gnScreenWidth; -extern Uint16 gnScreenHeight; -extern Uint16 gnViewportHeight; +extern DVL_API_FOR_TEST Uint16 gnScreenWidth; +extern DVL_API_FOR_TEST Uint16 gnScreenHeight; +extern DVL_API_FOR_TEST Uint16 gnViewportHeight; Uint16 GetScreenWidth(); Uint16 GetScreenHeight(); diff --git a/test/.clang-format b/test/.clang-format index 6af8a9627..2403752f3 100644 --- a/test/.clang-format +++ b/test/.clang-format @@ -5,6 +5,6 @@ AllowShortFunctionsOnASingleLine: None PointerAlignment: Right TabWidth: 4 UseTab: ForIndentation -SortIncludes: false +SortIncludes: true NamespaceIndentation: None FixNamespaceComments: true diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 000000000..db095de0f --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,44 @@ +include(GoogleTest) + +add_library(libdevilutionx_so SHARED) +target_link_libraries(libdevilutionx_so PUBLIC libdevilutionx) +target_include_directories(libdevilutionx_so INTERFACE "${DevilutionX_SOURCE_DIR}/Source") +set_target_properties(libdevilutionx_so PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) + +add_library(test_main STATIC main.cpp) +target_link_libraries(test_main PRIVATE libdevilutionx_so ${GTEST_LIBRARIES}) +target_include_directories(test_main PRIVATE ${GTEST_INCLUDE_DIRS}) + +set(tests + animationinfo_test + appfat_test + automap_test + codec_test + control_test + cursor_test + dead_test + diablo_test + drlg_l1_test + effects_test + file_util_test + inv_test + lighting_test + missiles_test + pack_test + path_test + player_test + quests_test + random_test + scrollrt_test + stores_test + writehero_test +) + +foreach(test_target ${tests}) + add_executable(${test_target} "${test_target}.cpp") + gtest_discover_tests(${test_target}) + target_link_libraries(${test_target} PRIVATE libdevilutionx_so ${GTEST_LIBRARIES} test_main) + target_include_directories(${test_target} PRIVATE ${GTEST_INCLUDE_DIRS}) +endforeach() + +target_include_directories(writehero_test PRIVATE ../3rdParty/PicoSHA2) diff --git a/test/animationinfo_test.cpp b/test/animationinfo_test.cpp index 6c59264b6..5662e113e 100644 --- a/test/animationinfo_test.cpp +++ b/test/animationinfo_test.cpp @@ -1,7 +1,7 @@ #include -#include "nthread.h" #include "engine/animationinfo.h" +#include "nthread.h" using namespace devilution; diff --git a/test/file_util_test.cpp b/test/file_util_test.cpp index f71f169a1..381657367 100644 --- a/test/file_util_test.cpp +++ b/test/file_util_test.cpp @@ -1,7 +1,7 @@ #include -#include #include +#include #include "utils/file_util.h" diff --git a/test/lighting_test.cpp b/test/lighting_test.cpp index ed412bf50..51781472b 100644 --- a/test/lighting_test.cpp +++ b/test/lighting_test.cpp @@ -17,8 +17,7 @@ TEST(Lighting, CrawlTables) for (unsigned i = (uint8_t)CrawlTable[cr - 1]; i > 0; i--, cr += 2) { int dx = x + CrawlTable[cr]; int dy = y + CrawlTable[cr + 1]; - sprintf(tempstr, "location %i:%i added twice.", dx - 20, dy - 20); - EXPECT_EQ(added[dx][dy], false) << tempstr; + EXPECT_EQ(added[dx][dy], false) << "location " << i << ":" << j << " added twice"; added[dx][dy] = true; } } @@ -29,8 +28,7 @@ TEST(Lighting, CrawlTables) continue; if ((i == -18 && j == -18) || (i == -18 && j == 18) || (i == 18 && j == -18) || (i == 18 && j == 18)) continue; // Limit of the crawl table rage - sprintf(tempstr, "while checking location %i:%i.", i, j); - EXPECT_EQ(false, true) << tempstr; + EXPECT_EQ(false, true) << "while checking location " << i << ":" << j; } } } diff --git a/test/main.cpp b/test/main.cpp index e3b60271d..ed9ebdcbf 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -1,10 +1,17 @@ #include #include "diablo.h" +#include "utils/paths.h" int main(int argc, char **argv) { + // Disable error dialogs. devilution::gbQuietMode = true; + + // Let the tests find `devilutionx.mpq` or `assets/`. + devilution::paths::SetAssetsPath(devilution::paths::BasePath() + "../assets"); + devilution::paths::SetBasePath(devilution::paths::BasePath() + ".."); + testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/test/pack_test.cpp b/test/pack_test.cpp index d92ab96c9..2ad16b73b 100644 --- a/test/pack_test.cpp +++ b/test/pack_test.cpp @@ -1,10 +1,12 @@ -#include #include +#include + #include "pack.h" #include "utils/paths.h" -using namespace devilution; +namespace devilution { +namespace { static void ComparePackedItems(const ItemPack &item1, const ItemPack &item2) { @@ -327,7 +329,7 @@ const TestItemStruct DiabloItems[] = { // clang-format on }; -TEST(pack, UnPackItem_diablo) +TEST(PackTest, UnPackItem_diablo) { Item id; ItemPack is; @@ -348,7 +350,7 @@ TEST(pack, UnPackItem_diablo) } } -TEST(pack, UnPackItem_diablo_unique_bug) +TEST(PackTest, UnPackItem_diablo_unique_bug) { ItemPack pkItemBug = { 6, 911, 14, 5, 60, 60, 0, 0, 0, 0 }; // Veil of Steel - with morph bug ItemPack pkItem = { 6, 655, 14, 5, 60, 60, 0, 0, 0, 0 }; // Veil of Steel - fixed @@ -398,7 +400,7 @@ const TestItemStruct SpawnItems[] = { // clang-format on }; -TEST(pack, UnPackItem_spawn) +TEST(PackTest, UnPackItem_spawn) { Item id; ItemPack is; @@ -442,7 +444,7 @@ const TestItemStruct DiabloMPItems[] = { // clang-format on }; -TEST(pack, UnPackItem_diablo_multiplayer) +TEST(PackTest, UnPackItem_diablo_multiplayer) { Item id; ItemPack is; @@ -651,7 +653,7 @@ const TestItemStruct HellfireItems[] = { // clang-format on }; -TEST(pack, UnPackItem_hellfire) +TEST(PackTest, UnPackItem_hellfire) { Item id; ItemPack is; @@ -673,7 +675,7 @@ TEST(pack, UnPackItem_hellfire) } } -TEST(pack, UnPackItem_diablo_strip_hellfire_items) +TEST(PackTest, UnPackItem_diablo_strip_hellfire_items) { ItemPack is = { 1478792102, 259, 92, 0, 0, 0, 0, 0, 0, 0 }; // Scroll of Search Item id; @@ -687,7 +689,7 @@ TEST(pack, UnPackItem_diablo_strip_hellfire_items) ASSERT_EQ(id._itype, ItemType::None); } -TEST(pack, UnPackItem_empty) +TEST(PackTest, UnPackItem_empty) { ItemPack is = { 0, 0, 0xFFFF, 0, 0, 0, 0, 0, 0, 0 }; Item id; @@ -697,7 +699,7 @@ TEST(pack, UnPackItem_empty) ASSERT_EQ(id._itype, ItemType::None); } -TEST(pack, PackItem_empty) +TEST(PackTest, PackItem_empty) { ItemPack is; Item id = {}; @@ -706,7 +708,9 @@ TEST(pack, PackItem_empty) PackItem(is, id); - ASSERT_EQ(is.idx, 0xFFFF); + // Copy the value out before comparing to avoid loading a misaligned address. + const auto idx = is.idx; + ASSERT_EQ(idx, 0xFFFF); } static void compareGold(const ItemPack &is, int iCurs) @@ -715,7 +719,9 @@ static void compareGold(const ItemPack &is, int iCurs) UnPackItem(is, id, false); ASSERT_EQ(id._iCurs, iCurs); ASSERT_EQ(id.IDidx, IDI_GOLD); - ASSERT_EQ(id._ivalue, is.wValue); + // Copy the value out before comparing to avoid loading a misaligned address. + const auto wvalue = is.wValue; + ASSERT_EQ(id._ivalue, wvalue); ASSERT_EQ(id._itype, ItemType::Gold); ASSERT_EQ(id._iClass, ICLASS_GOLD); @@ -724,25 +730,25 @@ static void compareGold(const ItemPack &is, int iCurs) ComparePackedItems(is, is2); } -TEST(pack, UnPackItem_gold_small) +TEST(PackTest, UnPackItem_gold_small) { ItemPack is = { 0, 0, IDI_GOLD, 0, 0, 0, 0, 0, 1000, 0 }; compareGold(is, ICURS_GOLD_SMALL); } -TEST(pack, UnPackItem_gold_medium) +TEST(PackTest, UnPackItem_gold_medium) { ItemPack is = { 0, 0, IDI_GOLD, 0, 0, 0, 0, 0, 1001, 0 }; compareGold(is, ICURS_GOLD_MEDIUM); } -TEST(pack, UnPackItem_gold_large) +TEST(PackTest, UnPackItem_gold_large) { ItemPack is = { 0, 0, IDI_GOLD, 0, 0, 0, 0, 0, 2500, 0 }; compareGold(is, ICURS_GOLD_LARGE); } -TEST(pack, UnPackItem_ear) +TEST(PackTest, UnPackItem_ear) { ItemPack is = { 1633955154, 17509, 23, 111, 103, 117, 101, 68, 19843, 0 }; Item id; @@ -755,3 +761,6 @@ TEST(pack, UnPackItem_ear) PackItem(is2, id); ComparePackedItems(is, is2); } + +} // namespace +} // namespace devilution diff --git a/test/path_test.cpp b/test/path_test.cpp index 7901d9009..728c30880 100644 --- a/test/path_test.cpp +++ b/test/path_test.cpp @@ -1,275 +1,275 @@ -#include - -#include "path.h" - -// The following headers are included to access globals used in functions that have not been isolated yet. -#include "gendung.h" -#include "objects.h" - -namespace devilution { - -extern int TestPathGetHeuristicCost(Point startPosition, Point destinationPosition); - -TEST(PathTest, Heuristics) -{ - constexpr Point source { 25, 32 }; - Point destination = source; - EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 0) << "Wrong cost for travelling to the same tile"; - - destination = source + Direction::NorthEast; - EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 2) << "Wrong cost for travelling to horizontal/vertical adjacent tile"; - destination = source + Direction::SouthEast; - EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 2) << "Wrong cost for travelling to horizontal/vertical adjacent tile"; - destination = source + Direction::SouthWest; - EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 2) << "Wrong cost for travelling to horizontal/vertical adjacent tile"; - destination = source + Direction::NorthWest; - EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 2) << "Wrong cost for travelling to horizontal/vertical adjacent tile"; - - destination = source + Direction::North; - EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 4) << "Wrong cost for travelling to diagonally adjacent tile"; - destination = source + Direction::East; - EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 4) << "Wrong cost for travelling to diagonally adjacent tile"; - destination = source + Direction::South; - EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 4) << "Wrong cost for travelling to diagonally adjacent tile"; - destination = source + Direction::West; - EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 4) << "Wrong cost for travelling to diagonally adjacent tile"; - destination = source + Direction::SouthWest + Direction::SouthEast; // Effectively the same as Direction::South - EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 4) << "Wrong cost for travelling to diagonally adjacent tile"; - - destination = source + Direction::NorthEast + Direction::North; - EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 6) << "Wrong cost for travelling to a { 2, 1 } offset"; - destination = source + Direction::SouthEast + Direction::SouthEast; - EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 4) << "Wrong cost for travelling to a { 2, 0 } offset"; -} - -TEST(PathTest, Solid) -{ - dPiece[5][5] = 0; - nSolidTable[0] = true; - EXPECT_TRUE(IsTileSolid({ 5, 5 })) << "Solid in-bounds tiles are solid"; - EXPECT_FALSE(IsTileNotSolid({ 5, 5 })) << "IsTileNotSolid returns the inverse of IsTileSolid for in-bounds tiles"; - - dPiece[6][6] = 1; - nSolidTable[1] = false; - EXPECT_FALSE(IsTileSolid({ 6, 6 })) << "Non-solid in-bounds tiles are not solid"; - EXPECT_TRUE(IsTileNotSolid({ 6, 6 })) << "IsTileNotSolid returns the inverse of IsTileSolid for in-bounds tiles"; - - EXPECT_FALSE(IsTileSolid({ -1, 1 })) << "Out of bounds tiles are not solid"; // this reads out of bounds in the current code and may fail unexpectedly - EXPECT_FALSE(IsTileNotSolid({ -1, 1 })) << "Out of bounds tiles are also not not solid"; -} - -TEST(PathTest, SolidPieces) -{ - dPiece[0][0] = 0; - dPiece[0][1] = 0; - dPiece[1][0] = 0; - dPiece[1][1] = 0; - nSolidTable[0] = false; - EXPECT_TRUE(path_solid_pieces({ 0, 0 }, { 1, 1 })) << "A step in open space is free of solid pieces"; - EXPECT_TRUE(path_solid_pieces({ 1, 1 }, { 0, 0 })) << "A step in open space is free of solid pieces"; - EXPECT_TRUE(path_solid_pieces({ 1, 0 }, { 0, 1 })) << "A step in open space is free of solid pieces"; - EXPECT_TRUE(path_solid_pieces({ 0, 1 }, { 1, 0 })) << "A step in open space is free of solid pieces"; - - nSolidTable[1] = true; - dPiece[1][0] = 1; - EXPECT_TRUE(path_solid_pieces({ 0, 1 }, { 1, 0 })) << "Can path to a destination which is solid"; - EXPECT_TRUE(path_solid_pieces({ 1, 0 }, { 0, 1 })) << "Can path from a starting position which is solid"; - EXPECT_TRUE(path_solid_pieces({ 0, 1 }, { 1, 1 })) << "Stepping in a cardinal direction ignores solid pieces"; - EXPECT_TRUE(path_solid_pieces({ 1, 0 }, { 1, 1 })) << "Stepping in a cardinal direction ignores solid pieces"; - EXPECT_TRUE(path_solid_pieces({ 0, 0 }, { 1, 0 })) << "Stepping in a cardinal direction ignores solid pieces"; - EXPECT_TRUE(path_solid_pieces({ 1, 1 }, { 1, 0 })) << "Stepping in a cardinal direction ignores solid pieces"; - - EXPECT_FALSE(path_solid_pieces({ 0, 0 }, { 1, 1 })) << "Can't cut a solid corner"; - EXPECT_FALSE(path_solid_pieces({ 1, 1 }, { 0, 0 })) << "Can't cut a solid corner"; - dPiece[0][1] = 1; - EXPECT_FALSE(path_solid_pieces({ 0, 0 }, { 1, 1 })) << "Can't walk through the boundary between two corners"; - EXPECT_FALSE(path_solid_pieces({ 1, 1 }, { 0, 0 })) << "Can't walk through the boundary between two corners"; - dPiece[1][0] = 0; - EXPECT_FALSE(path_solid_pieces({ 0, 0 }, { 1, 1 })) << "Can't cut a solid corner"; - EXPECT_FALSE(path_solid_pieces({ 1, 1 }, { 0, 0 })) << "Can't cut a solid corner"; - dPiece[0][1] = 0; - - dPiece[0][0] = 1; - EXPECT_FALSE(path_solid_pieces({ 1, 0 }, { 0, 1 })) << "Can't cut a solid corner"; - EXPECT_FALSE(path_solid_pieces({ 0, 1 }, { 1, 0 })) << "Can't cut a solid corner"; - dPiece[1][1] = 1; - EXPECT_FALSE(path_solid_pieces({ 1, 0 }, { 0, 1 })) << "Can't walk through the boundary between two corners"; - EXPECT_FALSE(path_solid_pieces({ 0, 1 }, { 1, 0 })) << "Can't walk through the boundary between two corners"; - dPiece[0][0] = 0; - EXPECT_FALSE(path_solid_pieces({ 1, 0 }, { 0, 1 })) << "Can't cut a solid corner"; - EXPECT_FALSE(path_solid_pieces({ 0, 1 }, { 1, 0 })) << "Can't cut a solid corner"; - dPiece[1][1] = 0; -} - -void CheckPath(Point startPosition, Point destinationPosition, std::vector expectedSteps) -{ - static int8_t pathSteps[MAX_PATH_LENGTH]; - auto pathLength = FindPath([](Point) { return true; }, startPosition, destinationPosition, pathSteps); - - EXPECT_EQ(pathLength, expectedSteps.size()) << "Wrong path length for a path from " << startPosition << " to " << destinationPosition; - // Die early if the wrong path length is returned as we don't want to read oob in expectedSteps - ASSERT_LE(pathLength, expectedSteps.size()) << "Path is longer than expected."; - - for (auto i = 0; i < pathLength; i++) { - EXPECT_EQ(pathSteps[i], expectedSteps[i]) << "Path step " << i << " differs from expectation for a path from " - << startPosition << " to " << destinationPosition; // this shouldn't be a requirement but... - - // Path directions are all jacked up compared to the Direction enum. Most consumers have their own mapping definition - // startPosition += Direction { path[i] - 1 }; - } - // Given that we can't really make any assumptions about how the path is actually used. - // EXPECT_EQ(startPosition, destinationPosition) << "Path doesn't lead to destination"; -} - -TEST(PathTest, FindPath) -{ - CheckPath({ 8, 8 }, { 8, 8 }, {}); - - // Traveling in cardinal directions is the only way to get a first step in a cardinal direction - CheckPath({ 8, 8 }, { 8, 6 }, { 1, 1 }); - CheckPath({ 8, 8 }, { 6, 8 }, { 2, 2 }); - CheckPath({ 8, 8 }, { 10, 8 }, { 3, 3 }); - CheckPath({ 8, 8 }, { 8, 10 }, { 4, 4 }); - - // Otherwise pathing biases along diagonals and the diagonal steps will always be first - CheckPath({ 8, 8 }, { 5, 6 }, { 5, 5, 2 }); - CheckPath({ 8, 8 }, { 4, 4 }, { 5, 5, 5, 5 }); - CheckPath({ 8, 8 }, { 12, 20 }, { 7, 7, 7, 7, 4, 4, 4, 4, 4, 4, 4, 4 }); -} - -TEST(PathTest, Walkable) -{ - dPiece[5][5] = 0; - nSolidTable[0] = true; // Doing this manually to save running through the code in gendung.cpp - EXPECT_FALSE(IsTileWalkable({ 5, 5 })) << "Tile which is marked as solid should be considered blocked"; - EXPECT_FALSE(IsTileWalkable({ 5, 5 }, true)) << "Solid non-door tiles remain unwalkable when ignoring doors"; - - nSolidTable[0] = false; - EXPECT_TRUE(IsTileWalkable({ 5, 5 })) << "Non-solid tiles are walkable"; - EXPECT_TRUE(IsTileWalkable({ 5, 5 }, true)) << "Non-solid tiles remain walkable when ignoring doors"; - - dObject[5][5] = 1; - Objects[0]._oSolidFlag = true; - EXPECT_FALSE(IsTileWalkable({ 5, 5 })) << "Tile occupied by a solid object is unwalkable"; - EXPECT_FALSE(IsTileWalkable({ 5, 5 }, true)) << "Tile occupied by a solid non-door object are unwalkable when ignoring doors"; - - Objects[0]._otype = _object_id::OBJ_L1LDOOR; - EXPECT_FALSE(IsTileWalkable({ 5, 5 })) << "Tile occupied by a door which is marked as solid should be considered blocked"; - EXPECT_TRUE(IsTileWalkable({ 5, 5 }, true)) << "Tile occupied by a door is considered walkable when ignoring doors"; - - Objects[0]._oSolidFlag = false; - EXPECT_TRUE(IsTileWalkable({ 5, 5 })) << "Tile occupied by an open door is walkable"; - EXPECT_TRUE(IsTileWalkable({ 5, 5 }, true)) << "Tile occupied by a door is considered walkable when ignoring doors"; - - nSolidTable[0] = true; - EXPECT_FALSE(IsTileWalkable({ 5, 5 })) << "Solid tiles occupied by an open door remain unwalkable"; - EXPECT_TRUE(IsTileWalkable({ 5, 5 }, true)) << "Solid tiles occupied by an open door become walkable when ignoring doors"; -} - -TEST(PathTest, FindClosest) -{ - { - std::array, 101> searchedTiles {}; - - std::optional nearPosition = FindClosestValidPosition( - [&searchedTiles](Point testPosition) { - searchedTiles[testPosition.x][testPosition.y]++; - return false; - }, - { 50, 50 }, 0, 50); - - EXPECT_FALSE(nearPosition) << "Searching with no valid tiles should return an empty optional"; - - for (int x = 0; x < searchedTiles.size(); x++) { - for (int y = 0; y < searchedTiles[x].size(); y++) { - if (IsAnyOf(x, 0, 100) && IsAnyOf(y, 0, 100)) { - EXPECT_EQ(searchedTiles[x][y], 0) << "Extreme corners should be skipped due to the inset/rounded search space"; - } else { - EXPECT_EQ(searchedTiles[x][y], 1) << "Position " << Point { x, y } << " should have been searched exactly once"; - } - } - } - } - { - std::array, 5> searchedTiles {}; - - std::optional nearPosition = FindClosestValidPosition( - [&searchedTiles](Point testPosition) { - searchedTiles[testPosition.x][testPosition.y]++; - return false; - }, - { 2, 2 }, 1, 2); - - EXPECT_FALSE(nearPosition) << "Still shouldn't find a valid position with no valid tiles"; - - for (int x = 0; x < searchedTiles.size(); x++) { - for (int y = 0; y < searchedTiles[x].size(); y++) { - if (Point { x, y } == Point { 2, 2 }) { - EXPECT_EQ(searchedTiles[x][y], 0) << "The starting tile should be skipped with a min radius of 1"; - } else if (IsAnyOf(x, 0, 4) && IsAnyOf(y, 0, 4)) { - EXPECT_EQ(searchedTiles[x][y], 0) << "Corners should be skipped"; - } else { - EXPECT_EQ(searchedTiles[x][y], 1) << "All tiles in range should be searched exactly once"; - } - } - } - } - { - std::array, 3> searchedTiles {}; - - std::optional nearPosition = FindClosestValidPosition( - [&searchedTiles](Point testPosition) { - searchedTiles[testPosition.x][testPosition.y]++; - return false; - }, - { 1, 1 }, 0, 0); - - EXPECT_FALSE(nearPosition) << "Searching with no valid tiles should return an empty optional"; - - for (int x = 0; x < searchedTiles.size(); x++) { - for (int y = 0; y < searchedTiles[x].size(); y++) { - if (Point { x, y } == Point { 1, 1 }) { - EXPECT_EQ(searchedTiles[x][y], 1) << "Only the starting tile should be searched with max radius 0"; - } else { - EXPECT_EQ(searchedTiles[x][y], 0) << "Position " << Point { x, y } << " should not have been searched"; - } - } - } - } - - { - std::array, 7> searchedTiles {}; - - std::optional nearPosition = FindClosestValidPosition( - [&searchedTiles](Point testPosition) { - searchedTiles[testPosition.x][testPosition.y]++; - return false; - }, - { 3, 3 }, 3, 3); - - EXPECT_FALSE(nearPosition) << "Searching with no valid tiles should return an empty optional"; - - for (int x = 0; x < searchedTiles.size(); x++) { - for (int y = 0; y < searchedTiles[x].size(); y++) { - if ((IsAnyOf(x, 1, 5) && IsAnyOf(y, 1, 5)) // inset corners - || (IsAnyOf(x, 0, 6) && IsNoneOf(y, 0, 6)) // left/right sides - || (IsNoneOf(x, 0, 6) && IsAnyOf(y, 0, 6)) // top/bottom sides - ) { - EXPECT_EQ(searchedTiles[x][y], 1) << "Searching with a fixed radius should make a square with inset corners"; - } else { - EXPECT_EQ(searchedTiles[x][y], 0) << "Position " << Point { x, y } << " should not have been searched"; - } - } - } - } - { - std::optional nearPosition = FindClosestValidPosition( - [](Point testPosition) { - return true; - }, - { 50, 50 }, 21, 50); - - EXPECT_EQ(*nearPosition, (Point { 50, 50 } + Displacement { 0, 21 })) << "First candidate position with a minimum radius should be at {0, +y}"; - } -} -} // namespace devilution +#include + +#include "path.h" + +// The following headers are included to access globals used in functions that have not been isolated yet. +#include "gendung.h" +#include "objects.h" + +namespace devilution { + +extern int TestPathGetHeuristicCost(Point startPosition, Point destinationPosition); + +TEST(PathTest, Heuristics) +{ + constexpr Point source { 25, 32 }; + Point destination = source; + EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 0) << "Wrong cost for travelling to the same tile"; + + destination = source + Direction::NorthEast; + EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 2) << "Wrong cost for travelling to horizontal/vertical adjacent tile"; + destination = source + Direction::SouthEast; + EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 2) << "Wrong cost for travelling to horizontal/vertical adjacent tile"; + destination = source + Direction::SouthWest; + EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 2) << "Wrong cost for travelling to horizontal/vertical adjacent tile"; + destination = source + Direction::NorthWest; + EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 2) << "Wrong cost for travelling to horizontal/vertical adjacent tile"; + + destination = source + Direction::North; + EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 4) << "Wrong cost for travelling to diagonally adjacent tile"; + destination = source + Direction::East; + EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 4) << "Wrong cost for travelling to diagonally adjacent tile"; + destination = source + Direction::South; + EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 4) << "Wrong cost for travelling to diagonally adjacent tile"; + destination = source + Direction::West; + EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 4) << "Wrong cost for travelling to diagonally adjacent tile"; + destination = source + Direction::SouthWest + Direction::SouthEast; // Effectively the same as Direction::South + EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 4) << "Wrong cost for travelling to diagonally adjacent tile"; + + destination = source + Direction::NorthEast + Direction::North; + EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 6) << "Wrong cost for travelling to a { 2, 1 } offset"; + destination = source + Direction::SouthEast + Direction::SouthEast; + EXPECT_EQ(TestPathGetHeuristicCost(source, destination), 4) << "Wrong cost for travelling to a { 2, 0 } offset"; +} + +TEST(PathTest, Solid) +{ + dPiece[5][5] = 0; + nSolidTable[0] = true; + EXPECT_TRUE(IsTileSolid({ 5, 5 })) << "Solid in-bounds tiles are solid"; + EXPECT_FALSE(IsTileNotSolid({ 5, 5 })) << "IsTileNotSolid returns the inverse of IsTileSolid for in-bounds tiles"; + + dPiece[6][6] = 1; + nSolidTable[1] = false; + EXPECT_FALSE(IsTileSolid({ 6, 6 })) << "Non-solid in-bounds tiles are not solid"; + EXPECT_TRUE(IsTileNotSolid({ 6, 6 })) << "IsTileNotSolid returns the inverse of IsTileSolid for in-bounds tiles"; + + EXPECT_FALSE(IsTileSolid({ -1, 1 })) << "Out of bounds tiles are not solid"; // this reads out of bounds in the current code and may fail unexpectedly + EXPECT_FALSE(IsTileNotSolid({ -1, 1 })) << "Out of bounds tiles are also not not solid"; +} + +TEST(PathTest, SolidPieces) +{ + dPiece[0][0] = 0; + dPiece[0][1] = 0; + dPiece[1][0] = 0; + dPiece[1][1] = 0; + nSolidTable[0] = false; + EXPECT_TRUE(path_solid_pieces({ 0, 0 }, { 1, 1 })) << "A step in open space is free of solid pieces"; + EXPECT_TRUE(path_solid_pieces({ 1, 1 }, { 0, 0 })) << "A step in open space is free of solid pieces"; + EXPECT_TRUE(path_solid_pieces({ 1, 0 }, { 0, 1 })) << "A step in open space is free of solid pieces"; + EXPECT_TRUE(path_solid_pieces({ 0, 1 }, { 1, 0 })) << "A step in open space is free of solid pieces"; + + nSolidTable[1] = true; + dPiece[1][0] = 1; + EXPECT_TRUE(path_solid_pieces({ 0, 1 }, { 1, 0 })) << "Can path to a destination which is solid"; + EXPECT_TRUE(path_solid_pieces({ 1, 0 }, { 0, 1 })) << "Can path from a starting position which is solid"; + EXPECT_TRUE(path_solid_pieces({ 0, 1 }, { 1, 1 })) << "Stepping in a cardinal direction ignores solid pieces"; + EXPECT_TRUE(path_solid_pieces({ 1, 0 }, { 1, 1 })) << "Stepping in a cardinal direction ignores solid pieces"; + EXPECT_TRUE(path_solid_pieces({ 0, 0 }, { 1, 0 })) << "Stepping in a cardinal direction ignores solid pieces"; + EXPECT_TRUE(path_solid_pieces({ 1, 1 }, { 1, 0 })) << "Stepping in a cardinal direction ignores solid pieces"; + + EXPECT_FALSE(path_solid_pieces({ 0, 0 }, { 1, 1 })) << "Can't cut a solid corner"; + EXPECT_FALSE(path_solid_pieces({ 1, 1 }, { 0, 0 })) << "Can't cut a solid corner"; + dPiece[0][1] = 1; + EXPECT_FALSE(path_solid_pieces({ 0, 0 }, { 1, 1 })) << "Can't walk through the boundary between two corners"; + EXPECT_FALSE(path_solid_pieces({ 1, 1 }, { 0, 0 })) << "Can't walk through the boundary between two corners"; + dPiece[1][0] = 0; + EXPECT_FALSE(path_solid_pieces({ 0, 0 }, { 1, 1 })) << "Can't cut a solid corner"; + EXPECT_FALSE(path_solid_pieces({ 1, 1 }, { 0, 0 })) << "Can't cut a solid corner"; + dPiece[0][1] = 0; + + dPiece[0][0] = 1; + EXPECT_FALSE(path_solid_pieces({ 1, 0 }, { 0, 1 })) << "Can't cut a solid corner"; + EXPECT_FALSE(path_solid_pieces({ 0, 1 }, { 1, 0 })) << "Can't cut a solid corner"; + dPiece[1][1] = 1; + EXPECT_FALSE(path_solid_pieces({ 1, 0 }, { 0, 1 })) << "Can't walk through the boundary between two corners"; + EXPECT_FALSE(path_solid_pieces({ 0, 1 }, { 1, 0 })) << "Can't walk through the boundary between two corners"; + dPiece[0][0] = 0; + EXPECT_FALSE(path_solid_pieces({ 1, 0 }, { 0, 1 })) << "Can't cut a solid corner"; + EXPECT_FALSE(path_solid_pieces({ 0, 1 }, { 1, 0 })) << "Can't cut a solid corner"; + dPiece[1][1] = 0; +} + +void CheckPath(Point startPosition, Point destinationPosition, std::vector expectedSteps) +{ + static int8_t pathSteps[MAX_PATH_LENGTH]; + auto pathLength = FindPath([](Point) { return true; }, startPosition, destinationPosition, pathSteps); + + EXPECT_EQ(pathLength, expectedSteps.size()) << "Wrong path length for a path from " << startPosition << " to " << destinationPosition; + // Die early if the wrong path length is returned as we don't want to read oob in expectedSteps + ASSERT_LE(pathLength, expectedSteps.size()) << "Path is longer than expected."; + + for (auto i = 0; i < pathLength; i++) { + EXPECT_EQ(pathSteps[i], expectedSteps[i]) << "Path step " << i << " differs from expectation for a path from " + << startPosition << " to " << destinationPosition; // this shouldn't be a requirement but... + + // Path directions are all jacked up compared to the Direction enum. Most consumers have their own mapping definition + // startPosition += Direction { path[i] - 1 }; + } + // Given that we can't really make any assumptions about how the path is actually used. + // EXPECT_EQ(startPosition, destinationPosition) << "Path doesn't lead to destination"; +} + +TEST(PathTest, FindPath) +{ + CheckPath({ 8, 8 }, { 8, 8 }, {}); + + // Traveling in cardinal directions is the only way to get a first step in a cardinal direction + CheckPath({ 8, 8 }, { 8, 6 }, { 1, 1 }); + CheckPath({ 8, 8 }, { 6, 8 }, { 2, 2 }); + CheckPath({ 8, 8 }, { 10, 8 }, { 3, 3 }); + CheckPath({ 8, 8 }, { 8, 10 }, { 4, 4 }); + + // Otherwise pathing biases along diagonals and the diagonal steps will always be first + CheckPath({ 8, 8 }, { 5, 6 }, { 5, 5, 2 }); + CheckPath({ 8, 8 }, { 4, 4 }, { 5, 5, 5, 5 }); + CheckPath({ 8, 8 }, { 12, 20 }, { 7, 7, 7, 7, 4, 4, 4, 4, 4, 4, 4, 4 }); +} + +TEST(PathTest, Walkable) +{ + dPiece[5][5] = 0; + nSolidTable[0] = true; // Doing this manually to save running through the code in gendung.cpp + EXPECT_FALSE(IsTileWalkable({ 5, 5 })) << "Tile which is marked as solid should be considered blocked"; + EXPECT_FALSE(IsTileWalkable({ 5, 5 }, true)) << "Solid non-door tiles remain unwalkable when ignoring doors"; + + nSolidTable[0] = false; + EXPECT_TRUE(IsTileWalkable({ 5, 5 })) << "Non-solid tiles are walkable"; + EXPECT_TRUE(IsTileWalkable({ 5, 5 }, true)) << "Non-solid tiles remain walkable when ignoring doors"; + + dObject[5][5] = 1; + Objects[0]._oSolidFlag = true; + EXPECT_FALSE(IsTileWalkable({ 5, 5 })) << "Tile occupied by a solid object is unwalkable"; + EXPECT_FALSE(IsTileWalkable({ 5, 5 }, true)) << "Tile occupied by a solid non-door object are unwalkable when ignoring doors"; + + Objects[0]._otype = _object_id::OBJ_L1LDOOR; + EXPECT_FALSE(IsTileWalkable({ 5, 5 })) << "Tile occupied by a door which is marked as solid should be considered blocked"; + EXPECT_TRUE(IsTileWalkable({ 5, 5 }, true)) << "Tile occupied by a door is considered walkable when ignoring doors"; + + Objects[0]._oSolidFlag = false; + EXPECT_TRUE(IsTileWalkable({ 5, 5 })) << "Tile occupied by an open door is walkable"; + EXPECT_TRUE(IsTileWalkable({ 5, 5 }, true)) << "Tile occupied by a door is considered walkable when ignoring doors"; + + nSolidTable[0] = true; + EXPECT_FALSE(IsTileWalkable({ 5, 5 })) << "Solid tiles occupied by an open door remain unwalkable"; + EXPECT_TRUE(IsTileWalkable({ 5, 5 }, true)) << "Solid tiles occupied by an open door become walkable when ignoring doors"; +} + +TEST(PathTest, FindClosest) +{ + { + std::array, 101> searchedTiles {}; + + std::optional nearPosition = FindClosestValidPosition( + [&searchedTiles](Point testPosition) { + searchedTiles[testPosition.x][testPosition.y]++; + return false; + }, + { 50, 50 }, 0, 50); + + EXPECT_FALSE(nearPosition) << "Searching with no valid tiles should return an empty optional"; + + for (size_t x = 0; x < searchedTiles.size(); x++) { + for (size_t y = 0; y < searchedTiles[x].size(); y++) { + if ((x == 0 || x == 100) && (y == 0 || y == 100)) { + EXPECT_EQ(searchedTiles[x][y], 0) << "Extreme corners should be skipped due to the inset/rounded search space"; + } else { + EXPECT_EQ(searchedTiles[x][y], 1) << "Position " << x << " " << y << " should have been searched exactly once"; + } + } + } + } + { + std::array, 5> searchedTiles {}; + + std::optional nearPosition = FindClosestValidPosition( + [&searchedTiles](Point testPosition) { + searchedTiles[testPosition.x][testPosition.y]++; + return false; + }, + { 2, 2 }, 1, 2); + + EXPECT_FALSE(nearPosition) << "Still shouldn't find a valid position with no valid tiles"; + + for (size_t x = 0; x < searchedTiles.size(); x++) { + for (size_t y = 0; y < searchedTiles[x].size(); y++) { + if (x == 2 && y == 2) { + EXPECT_EQ(searchedTiles[x][y], 0) << "The starting tile should be skipped with a min radius of 1"; + } else if ((x == 0 || x == 4) && (y == 0 || y == 4)) { + EXPECT_EQ(searchedTiles[x][y], 0) << "Corners should be skipped"; + } else { + EXPECT_EQ(searchedTiles[x][y], 1) << "All tiles in range should be searched exactly once"; + } + } + } + } + { + std::array, 3> searchedTiles {}; + + std::optional nearPosition = FindClosestValidPosition( + [&searchedTiles](Point testPosition) { + searchedTiles[testPosition.x][testPosition.y]++; + return false; + }, + { 1, 1 }, 0, 0); + + EXPECT_FALSE(nearPosition) << "Searching with no valid tiles should return an empty optional"; + + for (size_t x = 0; x < searchedTiles.size(); x++) { + for (size_t y = 0; y < searchedTiles[x].size(); y++) { + if (x == 1 && y == 1) { + EXPECT_EQ(searchedTiles[x][y], 1) << "Only the starting tile should be searched with max radius 0"; + } else { + EXPECT_EQ(searchedTiles[x][y], 0) << "Position " << x << " " << y << " should not have been searched"; + } + } + } + } + + { + std::array, 7> searchedTiles {}; + + std::optional nearPosition = FindClosestValidPosition( + [&searchedTiles](Point testPosition) { + searchedTiles[testPosition.x][testPosition.y]++; + return false; + }, + { 3, 3 }, 3, 3); + + EXPECT_FALSE(nearPosition) << "Searching with no valid tiles should return an empty optional"; + + for (size_t x = 0; x < searchedTiles.size(); x++) { + for (size_t y = 0; y < searchedTiles[x].size(); y++) { + if (((x == 1 || x == 5) && (y == 1 || y == 5)) // inset corners + || ((x == 0 || x == 6) && y != 0 && y != 6) // left/right sides + || (x != 0 && x != 6 && (y == 0 || y == 6)) // top/bottom sides + ) { + EXPECT_EQ(searchedTiles[x][y], 1) << "Searching with a fixed radius should make a square with inset corners"; + } else { + EXPECT_EQ(searchedTiles[x][y], 0) << "Position " << x << " " << y << " should not have been searched"; + } + } + } + } + { + std::optional nearPosition = FindClosestValidPosition( + [](Point testPosition) { + return true; + }, + { 50, 50 }, 21, 50); + + EXPECT_EQ(*nearPosition, (Point { 50, 50 } + Displacement { 0, 21 })) << "First candidate position with a minimum radius should be at {0, +y}"; + } +} +} // namespace devilution diff --git a/test/player_test.h b/test/player_test.h index ff548204e..03240fbe9 100644 --- a/test/player_test.h +++ b/test/player_test.h @@ -5,8 +5,8 @@ */ #pragma once -#include "player.h" #include "items.h" +#include "player.h" using namespace devilution; diff --git a/test/quests_test.cpp b/test/quests_test.cpp index 1b54f0a8c..1aef016b8 100644 --- a/test/quests_test.cpp +++ b/test/quests_test.cpp @@ -1,5 +1,5 @@ -#include #include +#include #include "quests.h" diff --git a/test/writehero_test.cpp b/test/writehero_test.cpp index 072ba2ac0..caeaef70e 100644 --- a/test/writehero_test.cpp +++ b/test/writehero_test.cpp @@ -1,18 +1,18 @@ #include "player_test.h" +#include #include #include -#include #include -#include + +#include +#include #include "loadsave.h" #include "pack.h" #include "pfile.h" #include "utils/paths.h" -#include "picosha2.h" - using namespace devilution; int spelldat_vanilla[] = { diff --git a/tools/make_src_dist.py b/tools/make_src_dist.py index 0d8ea54a9..67e0afaa1 100755 --- a/tools/make_src_dist.py +++ b/tools/make_src_dist.py @@ -40,7 +40,7 @@ _DEPS = ['asio', 'libmpq', 'libsmackerdec', # These dependencies are not vendored by default. # Run with `--fully_vendored` to include them. -_DEPS_NOT_VENDORED_BY_DEFAULT = ['sdl2', 'sdl_image', +_DEPS_NOT_VENDORED_BY_DEFAULT = ['googletest', 'sdl2', 'sdl_image', 'libpng', 'libfmt', 'bzip2', 'libsodium'] _ROOT_DIR = pathlib.Path(__file__).resolve().parent.parent