Browse Source

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
pull/3773/head
Gleb Mazovetskiy 4 years ago
parent
commit
076b0c0c05
  1. 8
      .circleci/config.yml
  2. 2
      .github/workflows/Linux_x86.yml
  3. 2
      .github/workflows/Linux_x86_64_SDL1.yml
  4. 2
      .github/workflows/MacOSX.yml
  5. 2
      .github/workflows/Windows_x64.yml
  6. 2
      .github/workflows/Windows_x86.yml
  7. 14
      3rdParty/googletest/CMakeLists.txt
  8. 10
      CMake/Assets.cmake
  9. 9
      CMake/Dependencies.cmake
  10. 2
      CMake/VcPkgManifestFeatures.cmake
  11. 1
      CMake/platforms/amiga.cmake
  12. 1
      CMake/platforms/android.cmake
  13. 53
      CMake/platforms/cpigamesh.cmake
  14. 1
      CMake/platforms/ios.cmake
  15. 1
      CMake/platforms/n3ds.cmake
  16. 1
      CMake/platforms/switch.cmake
  17. 1
      CMake/platforms/vita.cmake
  18. 94
      CMakeLists.txt
  19. 260
      CMakeSettings.json
  20. 1
      Packaging/OpenDingux/build.sh
  21. 17
      Source/automap.h
  22. 3
      Source/control.h
  23. 1
      Source/cursor.cpp
  24. 9
      Source/cursor.h
  25. 10
      Source/diablo.h
  26. 4
      Source/engine/point.hpp
  27. 13
      Source/gendung.h
  28. 7
      Source/init.h
  29. 7
      Source/lighting.h
  30. 5
      Source/loadsave.h
  31. 3
      Source/multi.h
  32. 3
      Source/nthread.h
  33. 3
      Source/objects.h
  34. 2
      Source/path.cpp
  35. 2
      Source/player.cpp
  36. 7
      Source/player.h
  37. 3
      Source/quests.h
  38. 5
      Source/stores.h
  39. 11
      Source/utils/attributes.h
  40. 8
      Source/utils/ui_fwd.h
  41. 2
      test/.clang-format
  42. 44
      test/CMakeLists.txt
  43. 2
      test/animationinfo_test.cpp
  44. 2
      test/file_util_test.cpp
  45. 6
      test/lighting_test.cpp
  46. 7
      test/main.cpp
  47. 41
      test/pack_test.cpp
  48. 550
      test/path_test.cpp
  49. 2
      test/player_test.h
  50. 2
      test/quests_test.cpp
  51. 8
      test/writehero_test.cpp
  52. 2
      tools/make_src_dist.py

8
.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:

2
.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}}

2
.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}}

2
.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}}

2
.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}}

2
.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}}

14
3rdParty/googletest/CMakeLists.txt vendored

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

10
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()

9
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()

2
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()

1
CMake/platforms/amiga.cmake

@ -1,3 +1,4 @@
set(BUILD_TESTING OFF)
set(ASAN OFF)
set(UBSAN OFF)
set(NONET ON)

1
CMake/platforms/android.cmake

@ -1,4 +1,5 @@
# General build options.
set(BUILD_TESTING OFF)
set(VIRTUAL_GAMEPAD ON)
# Disable all system dependencies.

53
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")

1
CMake/platforms/ios.cmake

@ -1,4 +1,5 @@
# General build options.
set(BUILD_TESTING OFF)
set(VIRTUAL_GAMEPAD ON)
# Disable all system dependencies.

1
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)

1
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)

1
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:/\"")

94
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}$<$<CONFIG:Debug>:-${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}$<$<CONFIG:Debug>:-${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

260
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"
}
]
}
]
}

1
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 \

17
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.

3
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;

1
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 {

9
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();

10
Source/diablo.h

@ -7,14 +7,14 @@
#include <cstdint>
#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];

4
Source/engine/point.hpp

@ -1,7 +1,7 @@
#pragma once
#include <cmath>
#ifdef RUN_TESTS
#ifdef BUILD_TESTING
#include <ostream>
#endif
@ -153,7 +153,7 @@ struct Point {
return std::max<int>(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*

13
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<bool, MAXTILES + 1> nBlockTable;
/**
* List of path blocking dPieces
*/
extern std::array<bool, MAXTILES + 1> nSolidTable;
extern DVL_API_FOR_TEST std::array<bool, MAXTILES + 1> 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];
/**

7
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<MpqArchive> hellfire_mpq;
extern WNDPROC CurrentProc;
extern std::optional<MpqArchive> spawn_mpq;
extern std::optional<MpqArchive> 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<MpqArchive> hfmonk_mpq;
extern std::optional<MpqArchive> hfbard_mpq;

7
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<uint8_t, LIGHTSIZE> 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

5
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);

3
Source/multi.h

@ -8,6 +8,7 @@
#include <cstdint>
#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;

3
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);

3
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;

2
Source/path.cpp

@ -545,7 +545,7 @@ std::optional<Point> FindClosestValidPosition(const std::function<bool(Point)> &
return {};
}
#ifdef RUN_TESTS
#ifdef BUILD_TESTING
int TestPathGetHeuristicCost(Point startPosition, Point destinationPosition)
{
return GetHeuristicCost(startPosition, destinationPosition);

2
Source/player.cpp

@ -3813,7 +3813,7 @@ void PlayDungMsgs()
}
}
#ifdef RUN_TESTS
#ifdef BUILD_TESTING
bool TestPlayerDoGotHit(int pnum)
{
return DoGotHit(pnum);

7
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<HeroClass>::value];

3
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<CelSprite> pQLogCel;
extern Quest Quests[MAXQUESTS];
extern DVL_API_FOR_TEST Quest Quests[MAXQUESTS];
extern Point ReturnLvlPosition;
extern dungeon_type ReturnLevelType;
extern int ReturnLevel;

5
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<CelSprite> 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;

11
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

8
Source/utils/ui_fwd.h

@ -2,11 +2,13 @@
#include <SDL.h>
#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();

2
test/.clang-format

@ -5,6 +5,6 @@ AllowShortFunctionsOnASingleLine: None
PointerAlignment: Right
TabWidth: 4
UseTab: ForIndentation
SortIncludes: false
SortIncludes: true
NamespaceIndentation: None
FixNamespaceComments: true

44
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)

2
test/animationinfo_test.cpp

@ -1,7 +1,7 @@
#include <gtest/gtest.h>
#include "nthread.h"
#include "engine/animationinfo.h"
#include "nthread.h"
using namespace devilution;

2
test/file_util_test.cpp

@ -1,7 +1,7 @@
#include <gtest/gtest.h>
#include <iostream>
#include <fstream>
#include <iostream>
#include "utils/file_util.h"

6
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;
}
}
}

7
test/main.cpp

@ -1,10 +1,17 @@
#include <gtest/gtest.h>
#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();
}

41
test/pack_test.cpp

@ -1,10 +1,12 @@
#include <gtest/gtest.h>
#include <cstdint>
#include <gtest/gtest.h>
#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

550
test/path_test.cpp

@ -1,275 +1,275 @@
#include <gtest/gtest.h>
#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<int8_t> 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<std::array<int, 101>, 101> searchedTiles {};
std::optional<Point> 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<std::array<int, 5>, 5> searchedTiles {};
std::optional<Point> 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<std::array<int, 3>, 3> searchedTiles {};
std::optional<Point> 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<std::array<int, 7>, 7> searchedTiles {};
std::optional<Point> 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<Point> 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 <gtest/gtest.h>
#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<int8_t> 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<std::array<int, 101>, 101> searchedTiles {};
std::optional<Point> 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<std::array<int, 5>, 5> searchedTiles {};
std::optional<Point> 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<std::array<int, 3>, 3> searchedTiles {};
std::optional<Point> 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<std::array<int, 7>, 7> searchedTiles {};
std::optional<Point> 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<Point> 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

2
test/player_test.h

@ -5,8 +5,8 @@
*/
#pragma once
#include "player.h"
#include "items.h"
#include "player.h"
using namespace devilution;

2
test/quests_test.cpp

@ -1,5 +1,5 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "quests.h"

8
test/writehero_test.cpp

@ -1,18 +1,18 @@
#include "player_test.h"
#include <cstdint>
#include <cstdio>
#include <fstream>
#include <gtest/gtest.h>
#include <vector>
#include <cstdint>
#include <gtest/gtest.h>
#include <picosha2.h>
#include "loadsave.h"
#include "pack.h"
#include "pfile.h"
#include "utils/paths.h"
#include "picosha2.h"
using namespace devilution;
int spelldat_vanilla[] = {

2
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

Loading…
Cancel
Save