Skip to content

Commit ed5d0f1

Browse files
authored
feat: add Catch2 unit test support (#17)
1 parent 7eea425 commit ed5d0f1

7 files changed

Lines changed: 261 additions & 123 deletions

File tree

.github/workflows/ci.yml

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ on:
55
branches: [main]
66
paths:
77
- "src/**"
8+
- "test/**"
89
- ".clang-format"
910
- ".clang-tidy"
1011
- "CMakeLists.txt"
12+
- "CMakePresets.json"
1113
- "cmake/**"
1214
- "vcpkg.json"
1315
- ".github/workflows/ci.yml"
@@ -32,6 +34,52 @@ jobs:
3234
clang-format-version: '22'
3335
check-path: src
3436

37+
- uses: jidicula/clang-format-action@v4.18.0
38+
with:
39+
clang-format-version: '22'
40+
check-path: test
41+
42+
test:
43+
needs: clang-format
44+
runs-on: windows-latest
45+
permissions:
46+
contents: read
47+
actions: write
48+
49+
steps:
50+
- name: Checkout
51+
uses: actions/checkout@v6
52+
with:
53+
submodules: true
54+
55+
- name: Install CMake and Ninja
56+
uses: lukka/get-cmake@latest
57+
58+
- name: Restore vcpkg binary cache
59+
uses: actions/cache@v5
60+
with:
61+
path: C:\vcpkg-cache
62+
key: vcpkg-tests-${{ hashFiles('vcpkg.json', 'cmake/triplets/**', 'cmake/ports/**') }}
63+
restore-keys: vcpkg-tests-
64+
65+
- name: Setup vcpkg
66+
uses: lukka/run-vcpkg@v11
67+
with:
68+
vcpkgDirectory: ${{ github.workspace }}/lib/vcpkg
69+
env:
70+
VCPKG_BINARY_SOURCES: "clear;files,C:\\vcpkg-cache,readwrite"
71+
72+
- name: Configure and Build Tests
73+
uses: lukka/run-cmake@v10
74+
with:
75+
configurePreset: test-windows
76+
buildPreset: test-windows
77+
env:
78+
VCPKG_BINARY_SOURCES: "clear;files,C:\\vcpkg-cache,readwrite"
79+
80+
- name: Run Tests
81+
run: ctest --preset test-windows
82+
3583
build:
3684
needs: clang-format
3785
runs-on: windows-latest
@@ -79,7 +127,7 @@ jobs:
79127
build/msvc/Release/*.pdb
80128
81129
clang-tidy:
82-
needs: build
130+
needs: [build, test]
83131
runs-on: windows-latest
84132
permissions:
85133
contents: read

CMakeLists.txt

Lines changed: 141 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -10,125 +10,147 @@ set(CMAKE_CXX_STANDARD 23)
1010
set(CMAKE_CXX_STANDARD_REQUIRED ON)
1111
set(CMAKE_CXX_EXTENSIONS OFF)
1212

13-
# Auto-init submodules if inside a git repo (self-heals if user skipped init.sh)
14-
find_package(Git QUIET)
15-
if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
16-
execute_process(
17-
COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
18-
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
19-
RESULT_VARIABLE _submod_result)
20-
if(NOT _submod_result EQUAL 0)
21-
message(FATAL_ERROR "git submodule update failed (exit code ${_submod_result})")
22-
endif()
23-
endif()
24-
25-
# Dependencies
26-
27-
find_package(spdlog CONFIG REQUIRED)
28-
# directxtk must be found before add_commonlibsse_plugin (CommonLibSSE-NG links
29-
# Microsoft::DirectXTK transitively).
30-
find_package(directxtk CONFIG REQUIRED)
31-
# rapidcsv must be in vcpkg.json (CommonLibSSE-NG unconditionally adds it as an include dir)
32-
33-
# CommonLibSSE-NG
34-
35-
set(ENABLE_SKYRIM_SE
36-
ON
37-
CACHE BOOL "" FORCE)
38-
set(ENABLE_SKYRIM_AE
39-
ON
40-
CACHE BOOL "" FORCE)
41-
set(ENABLE_SKYRIM_VR
42-
OFF
43-
CACHE BOOL "" FORCE)
44-
set(BUILD_TESTS
45-
OFF
46-
CACHE BOOL "" FORCE)
47-
set(SKSE_SUPPORT_XBYAK
48-
OFF
49-
CACHE BOOL "" FORCE)
50-
51-
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/lib/commonlibsse-ng" "${CMAKE_CURRENT_BINARY_DIR}/commonlibsse-ng"
52-
EXCLUDE_FROM_ALL)
53-
54-
include("${CMAKE_CURRENT_SOURCE_DIR}/lib/commonlibsse-ng/cmake/CommonLibSSE.cmake")
55-
56-
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/src")
57-
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.rc.in" "${CMAKE_CURRENT_BINARY_DIR}/src/version.rc" @ONLY)
58-
59-
# Plugin — auto-generates SKSEPlugin_Version; Plugin.cpp only needs SKSEPlugin_Load
60-
add_commonlibsse_plugin(
61-
${PROJECT_NAME}
62-
NAME
63-
"ExampleMod"
64-
AUTHOR
65-
"Author"
66-
SOURCES
67-
src/Plugin.cpp
68-
"${CMAKE_CURRENT_BINARY_DIR}/src/version.rc"
69-
USE_ADDRESS_LIBRARY)
70-
71-
source_group("Resources" FILES "${CMAKE_CURRENT_BINARY_DIR}/src/version.rc")
72-
73-
# cmake_llvm_rc preprocesses RC files using the target's INCLUDE_DIRECTORIES, not CMAKE_C_FLAGS.
74-
# The xwin SDK paths are in CMAKE_C_FLAGS_INIT as /imsvc flags and are invisible to cmake_llvm_rc,
75-
# so we add them explicitly here for the RC compiler to find winres.h.
76-
if(DEFINED XWIN_DIR)
77-
target_include_directories(
78-
${PROJECT_NAME} SYSTEM PRIVATE "$<$<COMPILE_LANGUAGE:RC>:${XWIN_DIR}/sdk/include/um>"
79-
"$<$<COMPILE_LANGUAGE:RC>:${XWIN_DIR}/sdk/include/shared>")
80-
endif()
81-
82-
target_precompile_headers(${PROJECT_NAME} PRIVATE src/PCH.h)
83-
84-
target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src" "${CMAKE_CURRENT_BINARY_DIR}/src")
85-
86-
set_target_properties(
87-
${PROJECT_NAME}
88-
PROPERTIES OUTPUT_NAME ExampleMod
89-
PREFIX ""
90-
SUFFIX ".dll")
91-
92-
# Compiler/linker flags: /Zi emits PDB in all configs; Release gets full optimization + /DEBUG:FULL
93-
if(MSVC)
94-
target_compile_options(${PROJECT_NAME} PRIVATE /Zi "$<$<CONFIG:Release>:/Zc:inline;/JMC->")
95-
target_link_options(${PROJECT_NAME} PRIVATE "$<$<CONFIG:Debug>:/INCREMENTAL;/OPT:NOREF;/OPT:NOICF>"
96-
"$<$<CONFIG:Release>:/INCREMENTAL:NO;/OPT:REF;/OPT:ICF;/DEBUG:FULL>")
97-
endif()
98-
99-
# LTO/IPO — guarded: unconditionally setting IPO causes a fatal error on toolchains that don't support it
100-
include(CheckIPOSupported)
101-
check_ipo_supported(RESULT _ipo_supported OUTPUT _ipo_output)
102-
if(_ipo_supported)
103-
set_target_properties(${PROJECT_NAME} PROPERTIES INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
104-
else()
105-
message(VERBOSE "IPO not supported: ${_ipo_output}")
106-
endif()
13+
option(PLUGIN_TESTS_ONLY "Build unit tests instead of the plugin" OFF)
14+
15+
if(PLUGIN_TESTS_ONLY)
16+
17+
find_package(Catch2 3 CONFIG REQUIRED)
18+
19+
include(CTest)
20+
include(Catch)
21+
22+
add_executable(${PROJECT_NAME}Tests test/ExampleTests.cpp)
23+
24+
target_include_directories(${PROJECT_NAME}Tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
10725

108-
# Deploy: copies DLL + PDB to SKYRIM_MODS_FOLDER, SKYRIM_FOLDER/Data, or build/deploy/ as fallback
26+
target_link_libraries(${PROJECT_NAME}Tests PRIVATE Catch2::Catch2WithMain)
27+
28+
catch_discover_tests(${PROJECT_NAME}Tests)
10929

110-
if(DEFINED ENV{SKYRIM_MODS_FOLDER} AND IS_DIRECTORY "$ENV{SKYRIM_MODS_FOLDER}")
111-
set(_default_deploy "$ENV{SKYRIM_MODS_FOLDER}/${PROJECT_NAME}")
112-
elseif(DEFINED ENV{SKYRIM_FOLDER} AND IS_DIRECTORY "$ENV{SKYRIM_FOLDER}/Data")
113-
set(_default_deploy "$ENV{SKYRIM_FOLDER}/Data")
11430
else()
115-
set(_default_deploy "${CMAKE_BINARY_DIR}/deploy/${PROJECT_NAME}")
116-
endif()
117-
118-
set(DEPLOY_DIR
119-
"${_default_deploy}"
120-
CACHE PATH "Deploy destination (set SKYRIM_MODS_FOLDER or SKYRIM_FOLDER in .env)")
121-
122-
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION "${DEPLOY_DIR}/SKSE/Plugins")
123-
124-
install(
125-
FILES "$<TARGET_PDB_FILE:${PROJECT_NAME}>"
126-
DESTINATION "${DEPLOY_DIR}/SKSE/Plugins"
127-
OPTIONAL)
128-
129-
# `cmake --build --preset deploy` (or cmake --workflow --preset deploy)
130-
add_custom_target(
131-
deploy
132-
COMMAND "${CMAKE_COMMAND}" --install "${CMAKE_BINARY_DIR}"
133-
DEPENDS ${PROJECT_NAME}
134-
COMMENT "Deploying to ${DEPLOY_DIR}/SKSE/Plugins")
31+
32+
# Auto-init submodules if inside a git repo (self-heals if user skipped init.sh)
33+
find_package(Git QUIET)
34+
if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
35+
execute_process(
36+
COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
37+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
38+
RESULT_VARIABLE _submod_result)
39+
if(NOT _submod_result EQUAL 0)
40+
message(FATAL_ERROR "git submodule update failed (exit code ${_submod_result})")
41+
endif()
42+
endif()
43+
44+
# Dependencies
45+
46+
find_package(spdlog CONFIG REQUIRED)
47+
# directxtk must be found before add_commonlibsse_plugin (CommonLibSSE-NG links
48+
# Microsoft::DirectXTK transitively).
49+
find_package(directxtk CONFIG REQUIRED)
50+
# rapidcsv must be in vcpkg.json (CommonLibSSE-NG unconditionally adds it as an include dir)
51+
52+
# CommonLibSSE-NG
53+
54+
set(ENABLE_SKYRIM_SE
55+
ON
56+
CACHE BOOL "" FORCE)
57+
set(ENABLE_SKYRIM_AE
58+
ON
59+
CACHE BOOL "" FORCE)
60+
set(ENABLE_SKYRIM_VR
61+
OFF
62+
CACHE BOOL "" FORCE)
63+
set(BUILD_TESTS
64+
OFF
65+
CACHE BOOL "" FORCE)
66+
set(SKSE_SUPPORT_XBYAK
67+
OFF
68+
CACHE BOOL "" FORCE)
69+
70+
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/lib/commonlibsse-ng" "${CMAKE_CURRENT_BINARY_DIR}/commonlibsse-ng"
71+
EXCLUDE_FROM_ALL)
72+
73+
include("${CMAKE_CURRENT_SOURCE_DIR}/lib/commonlibsse-ng/cmake/CommonLibSSE.cmake")
74+
75+
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/src")
76+
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.rc.in" "${CMAKE_CURRENT_BINARY_DIR}/src/version.rc" @ONLY)
77+
78+
# Plugin — auto-generates SKSEPlugin_Version; Plugin.cpp only needs SKSEPlugin_Load
79+
add_commonlibsse_plugin(
80+
${PROJECT_NAME}
81+
NAME
82+
"ExampleMod"
83+
AUTHOR
84+
"Author"
85+
SOURCES
86+
src/Plugin.cpp
87+
"${CMAKE_CURRENT_BINARY_DIR}/src/version.rc"
88+
USE_ADDRESS_LIBRARY)
89+
90+
source_group("Resources" FILES "${CMAKE_CURRENT_BINARY_DIR}/src/version.rc")
91+
92+
# cmake_llvm_rc preprocesses RC files using the target's INCLUDE_DIRECTORIES, not CMAKE_C_FLAGS.
93+
# The xwin SDK paths are in CMAKE_C_FLAGS_INIT as /imsvc flags and are invisible to cmake_llvm_rc,
94+
# so we add them explicitly here for the RC compiler to find winres.h.
95+
if(DEFINED XWIN_DIR)
96+
target_include_directories(
97+
${PROJECT_NAME} SYSTEM PRIVATE "$<$<COMPILE_LANGUAGE:RC>:${XWIN_DIR}/sdk/include/um>"
98+
"$<$<COMPILE_LANGUAGE:RC>:${XWIN_DIR}/sdk/include/shared>")
99+
endif()
100+
101+
target_precompile_headers(${PROJECT_NAME} PRIVATE src/PCH.h)
102+
103+
target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src"
104+
"${CMAKE_CURRENT_BINARY_DIR}/src")
105+
106+
set_target_properties(
107+
${PROJECT_NAME}
108+
PROPERTIES OUTPUT_NAME ExampleMod
109+
PREFIX ""
110+
SUFFIX ".dll")
111+
112+
# Compiler/linker flags: /Zi emits PDB in all configs; Release gets full optimization + /DEBUG:FULL
113+
if(MSVC)
114+
target_compile_options(${PROJECT_NAME} PRIVATE /Zi "$<$<CONFIG:Release>:/Zc:inline;/JMC->")
115+
target_link_options(${PROJECT_NAME} PRIVATE "$<$<CONFIG:Debug>:/INCREMENTAL;/OPT:NOREF;/OPT:NOICF>"
116+
"$<$<CONFIG:Release>:/INCREMENTAL:NO;/OPT:REF;/OPT:ICF;/DEBUG:FULL>")
117+
endif()
118+
119+
# LTO/IPO — guarded: unconditionally setting IPO causes a fatal error on toolchains that don't support it
120+
include(CheckIPOSupported)
121+
check_ipo_supported(RESULT _ipo_supported OUTPUT _ipo_output)
122+
if(_ipo_supported)
123+
set_target_properties(${PROJECT_NAME} PROPERTIES INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
124+
else()
125+
message(VERBOSE "IPO not supported: ${_ipo_output}")
126+
endif()
127+
128+
# Deploy: copies DLL + PDB to SKYRIM_MODS_FOLDER, SKYRIM_FOLDER/Data, or build/deploy/ as fallback
129+
130+
if(DEFINED ENV{SKYRIM_MODS_FOLDER} AND IS_DIRECTORY "$ENV{SKYRIM_MODS_FOLDER}")
131+
set(_default_deploy "$ENV{SKYRIM_MODS_FOLDER}/${PROJECT_NAME}")
132+
elseif(DEFINED ENV{SKYRIM_FOLDER} AND IS_DIRECTORY "$ENV{SKYRIM_FOLDER}/Data")
133+
set(_default_deploy "$ENV{SKYRIM_FOLDER}/Data")
134+
else()
135+
set(_default_deploy "${CMAKE_BINARY_DIR}/deploy/${PROJECT_NAME}")
136+
endif()
137+
138+
set(DEPLOY_DIR
139+
"${_default_deploy}"
140+
CACHE PATH "Deploy destination (set SKYRIM_MODS_FOLDER or SKYRIM_FOLDER in .env)")
141+
142+
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION "${DEPLOY_DIR}/SKSE/Plugins")
143+
144+
install(
145+
FILES "$<TARGET_PDB_FILE:${PROJECT_NAME}>"
146+
DESTINATION "${DEPLOY_DIR}/SKSE/Plugins"
147+
OPTIONAL)
148+
149+
# `cmake --build --preset deploy` (or cmake --workflow --preset deploy)
150+
add_custom_target(
151+
deploy
152+
COMMAND "${CMAKE_COMMAND}" --install "${CMAKE_BINARY_DIR}"
153+
DEPENDS ${PROJECT_NAME}
154+
COMMENT "Deploying to ${DEPLOY_DIR}/SKSE/Plugins")
155+
156+
endif() # PLUGIN_TESTS_ONLY

CMakePresets.json

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,18 @@
8181
"CMAKE_BUILD_TYPE": "Release",
8282
"CMAKE_DISABLE_PRECOMPILE_HEADERS": "ON"
8383
}
84+
},
85+
{
86+
"name": "test-windows",
87+
"displayName": "Test (Windows, MSVC + Ninja)",
88+
"description": "Build and run unit tests on Windows with MSVC",
89+
"inherits": ["_vcpkg", "_ninja", "_msvc"],
90+
"binaryDir": "${sourceDir}/build/test-windows",
91+
"cacheVariables": {
92+
"CMAKE_BUILD_TYPE": "Release",
93+
"PLUGIN_TESTS_ONLY": "ON",
94+
"VCPKG_MANIFEST_FEATURES": "tests"
95+
}
8496
}
8597
],
8698
"buildPresets": [
@@ -100,9 +112,23 @@
100112
"displayName": "Deploy to mod manager",
101113
"configurePreset": "release-linux",
102114
"targets": ["deploy"]
115+
},
116+
{
117+
"name": "test-windows",
118+
"displayName": "Test (Windows)",
119+
"configurePreset": "test-windows"
120+
}
121+
],
122+
"testPresets": [
123+
{
124+
"name": "test-windows",
125+
"displayName": "Test (Windows)",
126+
"configurePreset": "test-windows",
127+
"output": {
128+
"outputOnFailure": true
129+
}
103130
}
104131
],
105-
"testPresets": [],
106132
"packagePresets": [],
107133
"workflowPresets": [
108134
{

0 commit comments

Comments
 (0)