Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
generator: ""
build_args: "--config Release"
exe_path: build/bin/Release/ASWLogTests.exe
gui_exe_path: build/bin/Release/ASWLogTests_Win32_GUI.exe
# cl takes -MP as well as /MP; Git Bash would turn "/MP" into a path when it starts CMake.
cxxflags: -MP
# A multi-config generator, so the package check builds and installs both into the same prefix (the Debug
Expand All @@ -48,6 +49,7 @@ jobs:
generator: "MinGW Makefiles"
build_args: ""
exe_path: build/bin/ASWLogTests.exe
gui_exe_path: build/bin/ASWLogTests_Win32_GUI.exe
sanitize: ""
- name: Linux (GCC)
os: ubuntu-latest
Expand Down Expand Up @@ -170,6 +172,54 @@ jobs:
path: junit-results.xml
if-no-files-found: error

- name: Run unit tests in the Win32 GUI
# Windows jobs only: the Win32 GUI runner (tests/cmake builds it on Windows) opens its window, runs every test
# (--run), writes the same JUnit report as the console runner, then closes and exits with the console runner's
# exit code (--exit). Bash waits for a GUI program to end, unlike a Windows command prompt. The step's own
# timeout ends a GUI left waiting on a dialog that nobody can close, rather than leaving it to the job's.
if: matrix.gui_exe_path != ''
id: gui_tests
timeout-minutes: 3
shell: bash
run: |
if [ ! -f "${{ matrix.gui_exe_path }}" ]; then
echo "ERROR: Executable not found at ${{ matrix.gui_exe_path }}"
exit 1
fi
./${{ matrix.gui_exe_path }} --project-name "ASWLog-CI-Win32GUI" --run --exit --layout-ignore \
--report-junit gui-results.xml --test-timeout-seconds 60 --catch-crashes

- name: Check the Win32 GUI's report
# The GUI ran the same tests as the console runner, so both reports list the same number.
if: matrix.gui_exe_path != ''
shell: bash
run: |
console=$(grep -o '<testcase ' junit-results.xml | wc -l)
gui=$(grep -o '<testcase ' gui-results.xml | wc -l)
echo "Console runner: $console tests, Win32 GUI runner: $gui tests"
if [ "$gui" -eq 0 ] || [ "$gui" -ne "$console" ]; then
echo "ERROR: The Win32 GUI's report doesn't list the same tests as the console runner's"
exit 1
fi

- name: Win32 GUI test results summary
# As for the console runner's report above.
if: ${{ matrix.gui_exe_path != '' && !cancelled() && steps.gui_tests.outcome != 'skipped' }}
uses: dorny/test-reporter@v3
with:
name: Win32 GUI Test Results - ${{ matrix.name }}
path: gui-results.xml
reporter: java-junit
use-actions-summary: true

- name: Upload Win32 GUI test results
if: ${{ matrix.gui_exe_path != '' && !cancelled() && steps.gui_tests.outcome != 'skipped' }}
uses: actions/upload-artifact@v7
with:
name: gui-test-results-${{ matrix.name }}
path: gui-results.xml
if-no-files-found: error

- name: Check the installed CMake package
# Builds and installs the library (the root project) to a temporary prefix, then builds tests/package against
# it with find_package() and runs it, which checks the install rules and the package files. Not in the
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
[Ll]ogs/
[Rr]elease/
[Dd]ebug/
[Pp]lans/

# Misc
*.bak
Expand Down
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ These instructions apply to the entire repository unless a more specific `AGENTS
ASWLog is a thread-safe, dependency-free C++20 logging library for Windows and Linux.

- `ASWLog/` contains the framework implementation.
- `tests/` contains the unit tests: test modules (`Test_ASWLog_<Unit>`, the tests of `ASWLog_<Unit>`) and shared test helpers (`UT_Helper_<Topic>`, e.g. `UT_Helper_DateTime`) in `tests/tests/`, their CMake project in `tests/cmake/`, their RAD Studio console project in `tests/rad370/`, and their RAD Studio VCL GUI runner project in `tests/vcl/gui/rad370/` (built from the submodule's `vcl/gui/src`). `tests/compile_checks/` holds "must not compile" checks, which `tests/cmake/CMakeLists.txt` runs with `try_compile` when configuring (a failed check stops the configure). `tests/package/` is a project that builds against an installed ASWLog with `find_package` (CI installs the library and runs it).
- `tests/` contains the unit tests: test modules (`Test_ASWLog_<Unit>`, the tests of `ASWLog_<Unit>`) and shared test helpers (`UT_Helper_<Topic>`, e.g. `UT_Helper_DateTime`) in `tests/tests/`, their CMake project in `tests/cmake/` (the console runner, and on Windows also the Win32 GUI runner), their RAD Studio console project in `tests/rad370/`, their RAD Studio VCL GUI runner project in `tests/vcl/gui/rad370/` (built from the submodule's `gui/src` and `vcl/gui/src`), and their RAD Studio Win32 GUI runner project in `tests/win32/gui/rad370/` (built from the submodule's `gui/src` and `win32/gui/src`). `tests/compile_checks/` holds "must not compile" checks, which `tests/cmake/CMakeLists.txt` runs with `try_compile` when configuring (a failed check stops the configure). `tests/package/` is a project that builds against an installed ASWLog with `find_package` (CI installs the library and runs it).
- `CMakeLists.txt` (the root) defines the `ASWLog::ASWLog` library target and its install rules; `cmake/ASWLogConfig.cmake.in` is the template of the installed package file. The project version is read from `ASWLog/ASWLog_Version.h`.
- `third_party/asw-unit-tests/` is the ASWUnitTests framework, a git submodule pinned to a release tag. Never edit files in it; test modules self-register with `ASW_REGISTER_TEST_GROUP`, so adding a test only touches `tests/` and its build files. Each test group derives from `TAutoTestGroup<itself>`, so a new test is defined with `ASW_TEST(ClassName, TestName)` in the group's `.cpp` and declared as a public `Test_TestName()` method in its header, with no `RegisterTest()` call. The test groups of loggers that write entries somewhere (the sinks, e.g. `TASWFileLog`, `TASWConsoleLog`, `TASWNullLog`) register with `ASW_REGISTER_TEST_GROUP_IN_SUITE(ClassName, "Sinks")`, so `--filter "Sinks.*"` runs them all; loggers that pass entries on to others (`TASWMultiLog`, `TASWCategoryLog`) and the other test groups aren't in a suite.
- `.github/workflows/ci.yml` builds and runs the unit tests on Windows (MSVC, MinGW) and Linux (GCC, Clang), plus Linux Clang builds with ThreadSanitizer and with AddressSanitizer + UBSan (`ASWLOG_SANITIZE`); the non-sanitizer jobs also check the installed CMake package (`tests/package/`). A Linux GCC coverage job (informational, it never fails the run) shows the library's line coverage on its summary page and uploads an HTML report.
- `.github/workflows/ci.yml` builds and runs the unit tests on Windows (MSVC, MinGW) and Linux (GCC, Clang), plus Linux Clang builds with ThreadSanitizer and with AddressSanitizer + UBSan (`ASWLOG_SANITIZE`); the non-sanitizer jobs also check the installed CMake package (`tests/package/`), and the Windows jobs also run the tests in the Win32 GUI runner. A Linux GCC coverage job (informational, it never fails the run) shows the library's line coverage on its summary page and uploads an HTML report.
- `example/` contains example app that uses the logger.
- `example/rad370/` contains the RAD Studio 13.1 project and Windows build scripts.
- `README.md` contains repo details: features, quick start, project setup, API overview, versions and tests.
Expand Down Expand Up @@ -68,9 +68,9 @@ cmake -S tests/cmake -B tests/cmake/build
cmake --build tests/cmake/build --config Release
```

The VCL GUI runner builds with `tests/vcl/gui/rad370/Build_Win64x_Debug.bat` or `Build_Win64x_Release.bat` into the same output folder. To run every test in it without interaction, use `start /wait ASWLogTests_VCL_GUI.exe --run --exit --layout-ignore --report-junit results.xml`; its exit code matches the console runner's.
The VCL GUI runner builds with `tests/vcl/gui/rad370/Build_Win64x_Debug.bat` or `Build_Win64x_Release.bat`, and the Win32 GUI runner with `tests/win32/gui/rad370/Build_Win64x_Debug.bat` or `Build_Win64x_Release.bat`, into the same output folder. On Windows, the CMake build also makes the Win32 GUI runner, `ASWLogTests_Win32_GUI.exe`, next to `ASWLogTests.exe`. To run every test in a GUI runner without interaction, use e.g. `start /wait ASWLogTests_VCL_GUI.exe --run --exit --layout-ignore --report-junit results.xml`; its exit code matches the console runner's.

When adding a test module or a test helper, add its `.cpp`/`.h` to `tests/cmake/CMakeLists.txt`, `tests/rad370/ASWLogTests.cbproj`, and `tests/vcl/gui/rad370/ASWLogTests_VCL_GUI.cbproj`, and keep their include paths in sync. Never open the GUI's main form (`ASWUnitTests_GUI_MainForm`) in the RAD Studio designer and save it, since that writes into the submodule.
When adding a test module or a test helper, add its `.cpp`/`.h` to `tests/cmake/CMakeLists.txt` (its `ASWLOG_TEST_SOURCES` list, which both CMake runners build), `tests/rad370/ASWLogTests.cbproj`, `tests/vcl/gui/rad370/ASWLogTests_VCL_GUI.cbproj`, and `tests/win32/gui/rad370/ASWLogTests_Win32_GUI.cbproj`, and keep their include paths in sync. Never open the GUI's main form (`ASWUnitTests_GUI_MainForm`) in the RAD Studio designer and save it, since that writes into the submodule.

For other environments, use the repository's CMake configuration when present or the IDE's native project configuration. Tests should be run outside the debugger unless debugging an expected exception is intentional. Before considering a change complete:

Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ versioned baseline.

## [Unreleased]

### Added

- Win32 GUI test runner, the ASWUnitTests 1.5.0 GUI runner built on the Windows API alone: on
Windows, the CMake test build also makes `ASWLogTests_Win32_GUI.exe`, next to `ASWLogTests.exe`,
and `tests/win32/gui/rad370/ASWLogTests_Win32_GUI.cbproj` builds it with RAD Studio. CI's Windows
jobs run the tests in it too. Both GUI runners have ASWUnitTests 1.5.0's light and dark themes,
and every test runner has the ASWUnitTests icon.

## [1.0.0] - 2026-10-11

### Added
Expand Down
7 changes: 4 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ depends only on the C++ standard library and the operating system's APIs; third-
3. Add or update focused tests in `tests/tests/` for the behavior change, including error and
platform-specific cases where applicable. Define a new test with `ASW_TEST(ClassName, TestName)`
and declare its `Test_TestName()` method public in the group's header; it registers itself. A new
test file goes into `tests/cmake/CMakeLists.txt`, `tests/rad370/ASWLogTests.cbproj`, and
`tests/vcl/gui/rad370/ASWLogTests_VCL_GUI.cbproj`. A new library source file goes into the root
`CMakeLists.txt`, the RAD Studio test and example projects, and `Deploy.bat`.
test file goes into `tests/cmake/CMakeLists.txt`, `tests/rad370/ASWLogTests.cbproj`,
`tests/vcl/gui/rad370/ASWLogTests_VCL_GUI.cbproj`, and
`tests/win32/gui/rad370/ASWLogTests_Win32_GUI.cbproj`. A new library source file goes into the
root `CMakeLists.txt`, the RAD Studio test and example projects, and `Deploy.bat`.
4. Build the affected configuration and run the unit-test executable. See
[README.md](README.md#unit-tests) for the CMake build commands, and
`tests/rad370/Build_Win64x_Debug.bat` / `Build_Win64x_Release.bat` for the RAD Studio build. Also
Expand Down
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,13 +350,21 @@ CI also measures which lines of the library the tests run (a Linux GCC build wit
by gcovr): the "Linux (GCC, coverage)" job shows a table on its summary page and keeps an HTML
report as the `coverage-report` artifact. It is informational and never fails the run.

On Windows with RAD Studio, the tests can also run in the ASWUnitTests VCL GUI runner, a window for
choosing tests and reading their results: build `tests/vcl/gui/rad370/ASWLogTests_VCL_GUI.cbproj`
(or its `Build_Win64x_*.bat` scripts), which writes `ASWLogTests_VCL_GUI.exe` to
`tests/build/bin/<Config>`. `ASWLogTests_Group.groupproj` in the same folder opens it together with
the console test project. The GUI takes the console runner's command line options plus a few of its
own, e.g. `--run --exit` to run every test and exit with the console runner's exit code; see the
ASWUnitTests README's "VCL GUI Runner" section.
On Windows, the tests can also run in an ASWUnitTests GUI runner, a window for choosing tests and
reading their results, with a light and a dark theme (**View > Theme**). The GUI runners take the
console runner's command line options plus a few of their own, e.g. `--run --exit` to run every test
and exit with the console runner's exit code; see the ASWUnitTests README's "GUI Runners" section.

- The Win32 GUI runner uses only the Windows API. The CMake build makes it on Windows as
`ASWLogTests_Win32_GUI.exe`, next to the console runner, and CI runs it in the Windows jobs. For
RAD Studio, use `tests/win32/gui/rad370/ASWLogTests_Win32_GUI.cbproj` or its `Build_Win64x_*.bat`
scripts.
- The VCL GUI runner needs RAD Studio: use `tests/vcl/gui/rad370/ASWLogTests_VCL_GUI.cbproj` or its
`Build_Win64x_*.bat` scripts.

The RAD Studio builds write `ASWLogTests_Win32_GUI.exe` and `ASWLogTests_VCL_GUI.exe` to
`tests/build/bin/<Config>`. `tests/vcl/gui/rad370/ASWLogTests_Group.groupproj` opens both GUI
projects together with the console test project.

# Coding Standards

Expand Down
70 changes: 48 additions & 22 deletions tests/cmake/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ if(NOT TARGET ASWLog::ASWLog)
add_subdirectory("${ASWLOG_ROOT}" aswlog)
endif()

add_executable(ASWLogTests
${ASWUNITTESTS_SOURCES}
# The test modules and helpers, which both the console runner and, on Windows, the Win32 GUI runner run.
set(ASWLOG_TEST_SOURCES
"${ASWLOG_TESTS_DIR}/Test_ASWLog_Base.cpp"
"${ASWLOG_TESTS_DIR}/Test_ASWLog_CategoryLog.cpp"
"${ASWLOG_TESTS_DIR}/Test_ASWLog_Config.cpp"
Expand All @@ -58,34 +58,60 @@ add_executable(ASWLogTests
"${ASWLOG_TESTS_DIR}/UT_Helper_StdErr.cpp"
)

target_include_directories(ASWLogTests PRIVATE
"${ASWUNITTESTS_SOURCE_DIR}"
"${ASWLOG_TESTS_DIR}"
)

target_link_libraries(ASWLogTests PRIVATE ASWLog::ASWLog)

# ASWUnitTests' --test-timeout-seconds uses std::thread/std::future, which needs an explicit threading library on
# Linux and some MinGW-w64 distributions.
find_package(Threads REQUIRED)
target_link_libraries(ASWLogTests PRIVATE Threads::Threads)

if(MSVC)
target_compile_options(ASWLogTests PRIVATE /W4 /Zc:__cplusplus)
else()
target_compile_options(ASWLogTests PRIVATE -Wall -Wextra -Wpedantic)
endif()
# The settings both test runners share.
function(aswlog_configure_test_runner target)
target_include_directories(${target} PRIVATE "${ASWLOG_TESTS_DIR}")
target_link_libraries(${target} PRIVATE ASWLog::ASWLog Threads::Threads)

if(MINGW)
# Statically link the MinGW runtime so the executable launches without MinGW's bin directory on PATH (e.g. on a
# CI runner).
target_link_options(ASWLogTests PRIVATE -static-libgcc -static-libstdc++ -static)
endif()
if(MSVC)
target_compile_options(${target} PRIVATE /W4 /Zc:__cplusplus)
else()
target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic)
endif()

if(MINGW)
# Statically link the MinGW runtime so the executable launches without MinGW's bin directory on PATH (e.g. on
# a CI runner), and can't load another program's older libstdc++-6.dll from it.
target_link_options(${target} PRIVATE -static-libgcc -static-libstdc++ -static)
endif()

set_target_properties(${target} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
endfunction()

set_target_properties(ASWLogTests PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
add_executable(ASWLogTests
${ASWUNITTESTS_SOURCES}
${ASWLOG_TEST_SOURCES}
)

target_include_directories(ASWLogTests PRIVATE "${ASWUNITTESTS_SOURCE_DIR}")
aswlog_configure_test_runner(ASWLogTests)

if(WIN32)
# The ASWUnitTests icon, which the resource compiler finds through the include path.
target_sources(ASWLogTests PRIVATE "${ASWUNITTESTS_ROOT}/resource/ASWUnitTests_Icon.rc")
target_include_directories(ASWLogTests PRIVATE "${ASWUNITTESTS_ROOT}/resource")

# The Win32 GUI runner: the same tests in a window. Its WinMain takes the place of the console runner's main.cpp,
# and its sources list has the icon.
include("${ASWUNITTESTS_ROOT}/win32/gui/src/ASWUnitTests_Win32GUI_Sources.cmake")

add_executable(ASWLogTests_Win32_GUI WIN32
${ASWUNITTESTS_WIN32_GUI_SOURCES}
${ASWLOG_TEST_SOURCES}
)

target_include_directories(ASWLogTests_Win32_GUI PRIVATE ${ASWUNITTESTS_WIN32_GUI_INCLUDE_DIRS})
target_link_libraries(ASWLogTests_Win32_GUI PRIVATE ${ASWUNITTESTS_WIN32_GUI_LIBRARIES})
target_link_options(ASWLogTests_Win32_GUI PRIVATE ${ASWUNITTESTS_WIN32_GUI_LINK_OPTIONS})
aswlog_configure_test_runner(ASWLogTests_Win32_GUI)
endif()

# The *Fmt methods check a format string against its arguments at compile time, which a unit test can't show, so
# configuring checks it: tests/compile_checks/Check_FormatString.cpp must compile as case 0 (the control) and fail as
# every other case (a mismatched format string). Compiled only, not linked.
Expand Down
1 change: 1 addition & 0 deletions tests/rad370/ASWLogTests.cbproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
<BCC_ExtendedErrorInfo>true</BCC_ExtendedErrorInfo>
<ILINK_TranslatedLibraryPath>$(BDSLIB)\$(PLATFORM)\release\$(LANGDIR);$(ILINK_TranslatedLibraryPath)</ILINK_TranslatedLibraryPath>
<ProjectType>CppConsoleApplication</ProjectType>
<Icon_MainIcon>..\..\third_party\asw-unit-tests\resource\ASWUnitTests.ico</Icon_MainIcon>
<DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace)</DCC_Namespace>
<Multithreaded>true</Multithreaded>
<SanitizedProjectName>ASWLogTests</SanitizedProjectName>
Expand Down
Loading
Loading