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/ASWIniTests.exe
gui_exe_path: build/bin/Release/ASWIniTests_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/ASWIniTests.exe
gui_exe_path: build/bin/ASWIniTests_Win32_GUI.exe
sanitize: ""
- name: Linux (GCC)
os: ubuntu-latest
Expand Down Expand Up @@ -183,6 +185,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 "ASWIni-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: Build and run the example
# Builds example/cmake (with the library from the source tree) at the job's C++ standard and with its
# sanitizer, and runs it: it returns 0 only when every step did what it shows, and a sanitizer report fails it
Expand Down
12 changes: 6 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ These instructions apply to the entire repository unless a more specific `AGENTS
ASWIni is a thread-safe, dependency-free C++17 library for reading and writing INI files on Windows and Linux. It keeps comments, blank lines and formatting when a file is loaded and saved, and lets an application declare its settings (section, key, type, default, comment) in one place.

- `ASWIni/` contains the library implementation (namespace `ASWIni`).
- `tests/` contains the unit tests: test modules (`Test_ASWIni_<Unit>`, the tests of `ASWIni_<Unit>`) and shared test helpers (`UT_Helper_<Topic>`) 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/package/` is a project that builds against an installed ASWIni with `find_package` (CI installs the library and runs it).
- `tests/` contains the unit tests: test modules (`Test_ASWIni_<Unit>`, the tests of `ASWIni_<Unit>`) and shared test helpers (`UT_Helper_<Topic>`) 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/package/` is a project that builds against an installed ASWIni with `find_package` (CI installs the library and runs it).
- `example/` contains the example program, one portable file (`example/src/main.cpp`) that returns 0 only when every step did what it shows, its CMake project in `example/cmake/` (also built by the root project with `ASWINI_BUILD_EXAMPLE`), and its RAD Studio project for Win32 and Win64x in `example/rad370/`, which lists the library's sources.
- `docs/` contains the documentation for application developers: `Format.md`, `Options.md`, `Settings.md` and `Guarantees.md`. Keep them in line with the code, and compile each snippet in them (the README's quick start too) when it is written or changed.
- `CMakeLists.txt` (the root) defines the `ASWIni::ASWIni` library target and its install rules; `cmake/ASWIniConfig.cmake.in` is the template of the installed package file. The project version is read from `ASWIni/ASWIni_Version.h`.
- `third_party/asw-unit-tests/` is the ASWUnitTests framework, a git submodule pinned to a release tag. Never edit files in it.
- `.github/workflows/ci.yml` builds and runs the unit tests on Windows (MSVC, MinGW) and Linux (GCC, Clang) at C++20, on Linux GCC at C++17, plus Linux Clang builds with ThreadSanitizer and with AddressSanitizer + UBSan (`ASWINI_SANITIZE`); every job also builds and runs the example, and the non-sanitizer jobs 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) at C++20, on Linux GCC at C++17, plus Linux Clang builds with ThreadSanitizer and with AddressSanitizer + UBSan (`ASWINI_SANITIZE`); every job also builds and runs the example, the non-sanitizer jobs 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.
- `Deploy.bat` copies the library sources, build files, docs and example to a folder; it lists every file explicitly.
- `README.md` contains repo details: features, quick start, project setup, API overview, links to the docs, versions and tests.
- `CHANGELOG.md` records notable changes per release (see Changelog below).
Expand Down Expand Up @@ -78,9 +78,9 @@ The unit tests use the ASWUnitTests framework from the `third_party/asw-unit-tes

- Each test group derives from `TAutoTestGroup<itself>`, is named `"ASWIni_<Unit>_Tests"` (or `"ASWIni_<Group>_Tests"` for one of several groups of a unit, e.g. `"ASWIni_ReadingOptions_Tests"`) in its constructor, and defines each test with `ASW_TEST(ClassName, TestName)` in the group's `.cpp`, declared as a public `void Test_TestName();` in its header (in a `public: // Test methods, each defined with ASW_TEST` section, alphabetical), with no `RegisterTest()` call. Use `Test_ASWIni_Version.cpp/h` as the template.
- Pass `__func__, __LINE__` to every check (e.g. `CheckEquals(expected, actual, __func__, __LINE__, "message")`). bcc32c has no `std::source_location`, so the overloads without them don't exist in its build.
- When a unit's tests are clearer as several groups (e.g. one per struct, as in `Test_ASWIni_Options`), keep them in the unit's one `.cpp`/`.h`, each class with its own banner, and register each with `ASW_REGISTER_TEST_GROUP_IN_SUITE(ClassName, "<Unit>")`, so `--filter "<Unit>.*"` runs them all and the GUI runner shows them under one node. A unit with one group isn't put in a suite.
- When a unit's tests are clearer as several groups (e.g. one per struct, as in `Test_ASWIni_Options`), keep them in the unit's one `.cpp`/`.h`, each class with its own banner, and register each with `ASW_REGISTER_TEST_GROUP_IN_SUITE(ClassName, "<Unit>")`, so `--filter "<Unit>.*"` runs them all and the GUI runners show them under one node. A unit with one group isn't put in a suite.
- Structure a test with `// Arrange`, `// Act` and `// Assert` comments, each on its own line (combined as `// Act & Assert` or `// Arrange & Act & Assert` when the steps are interleaved), with any explanation on the line after the comment, and write each check's message as what should be true.
- When adding a test module or a test helper, add its `.cpp`/`.h` to `tests/cmake/CMakeLists.txt`, `tests/rad370/ASWIniTests.cbproj`, and `tests/vcl/gui/rad370/ASWIniTests_VCL_GUI.cbproj`, and keep their include paths in sync. When adding a library source file, add it to the root `CMakeLists.txt`, both RAD Studio test projects, the RAD Studio example project (`example/rad370/ASWIniExample.cbproj`), and `Deploy.bat`.
- When adding a test module or a test helper, add its `.cpp`/`.h` to `tests/cmake/CMakeLists.txt` (its `ASWINI_TEST_SOURCES` list, compiled once for both CMake runners), `tests/rad370/ASWIniTests.cbproj`, `tests/vcl/gui/rad370/ASWIniTests_VCL_GUI.cbproj`, and `tests/win32/gui/rad370/ASWIniTests_Win32_GUI.cbproj`, and keep their include paths in sync. When adding a library source file, add it to the root `CMakeLists.txt`, the three RAD Studio test projects, the RAD Studio example project (`example/rad370/ASWIniExample.cbproj`), and `Deploy.bat`.
- Never open the GUI's main form (`ASWUnitTests_GUI_MainForm`) in the RAD Studio designer and save it, since that writes into the submodule.

## Building and Testing
Expand All @@ -92,9 +92,9 @@ cmake -S tests/cmake -B tests/cmake/build
cmake --build tests/cmake/build --config Release
```

and with `-DASWINI_CXX_STANDARD=17` in a second build folder for a C++17 build. The executable is written to `tests/cmake/build/bin` (in a `Release` sub folder for multi-config generators).
and with `-DASWINI_CXX_STANDARD=17` in a second build folder for a C++17 build. The executable is written to `tests/cmake/build/bin` (in a `Release` sub folder for multi-config generators); on Windows, the Win32 GUI runner, `ASWIniTests_Win32_GUI.exe`, is written next to it.

The RAD Studio scripts need the `Rad370` environment variable to point to the RAD Studio installation: `tests/rad370/Build_Win32_Debug.bat`, `Build_Win32_Release.bat`, `Build_Win64x_Debug.bat` and `Build_Win64x_Release.bat` build the console tests into `tests/build/bin/<Platform>/<Config>/`. The Debug configurations pass `--pause` when run from the IDE. The VCL GUI runner builds with the scripts of the same names in `tests/vcl/gui/rad370/` into the same output folders. To run every test in it without interaction, use `start /wait ASWIniTests_VCL_GUI.exe --run --exit --layout-ignore --report-junit results.xml`; its exit code matches the console runner's.
The RAD Studio scripts need the `Rad370` environment variable to point to the RAD Studio installation: `tests/rad370/Build_Win32_Debug.bat`, `Build_Win32_Release.bat`, `Build_Win64x_Debug.bat` and `Build_Win64x_Release.bat` build the console tests into `tests/build/bin/<Platform>/<Config>/`. The Debug configurations pass `--pause` when run from the IDE. The VCL GUI runner builds with the scripts of the same names in `tests/vcl/gui/rad370/`, and the Win32 GUI runner with those in `tests/win32/gui/rad370/`, into the same output folders. To run every test in a GUI runner without interaction, use e.g. `start /wait ASWIniTests_VCL_GUI.exe --run --exit --layout-ignore --report-junit results.xml`; its exit code matches the console runner's.

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 @@ -7,6 +7,14 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [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 `ASWIniTests_Win32_GUI.exe`, next to `ASWIniTests.exe`,
and `tests/win32/gui/rad370/ASWIniTests_Win32_GUI.cbproj` builds it with RAD Studio for Win32 and
Win64x. 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-21

The first release.
Expand Down
13 changes: 7 additions & 6 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/ASWIniTests.cbproj`, and
`tests/vcl/gui/rad370/ASWIniTests_VCL_GUI.cbproj`. A new library source file goes into the root
`CMakeLists.txt`, the RAD Studio test projects, the RAD Studio example project
test file goes into `tests/cmake/CMakeLists.txt`, `tests/rad370/ASWIniTests.cbproj`,
`tests/vcl/gui/rad370/ASWIniTests_VCL_GUI.cbproj`, and
`tests/win32/gui/rad370/ASWIniTests_Win32_GUI.cbproj`. A new library source file goes into the
root `CMakeLists.txt`, the RAD Studio test projects, the RAD Studio example project
(`example/rad370/ASWIniExample.cbproj`), and `Deploy.bat`.
4. Build the affected configuration and run the unit-test executable, at C++17 and at C++20. See
[README.md](README.md#unit-tests) for the CMake build commands, and the `Build_Win32_*.bat`
Expand All @@ -37,9 +38,9 @@ depends only on the C++ standard library and the operating system's APIs; third-
6. If you touched platform-specific code (`#if defined(_WIN32)` or similar), validate both the
Windows and the portable path if you can. CI builds and runs the tests on Windows (MSVC, MinGW)
and Linux (GCC, Clang, and GCC at C++17), plus Linux builds with ThreadSanitizer and with
AddressSanitizer + UBSan, and runs the example program (`example/`) in each of them; a pull
request should pass all of them. [README.md](README.md#unit-tests) shows how to run a sanitizer
build yourself.
AddressSanitizer + UBSan, and runs the example program (`example/`) in each of them, and the
tests in the Win32 GUI runner on Windows; a pull request should pass all of them.
[README.md](README.md#unit-tests) shows how to run a sanitizer build yourself.
7. Keep `.cpp` and `.h` files ASCII-only. MSVC reads a source file without a BOM in the system's
code page, so other characters can change meaning. In tests, write non-ASCII text with escapes
(e.g. `"\xCE\xBB"` for UTF-8 bytes, or `L"\x03BB"` for a Windows path).
Expand Down
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,13 +340,22 @@ 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/ASWIniTests_VCL_GUI.cbproj`
(or its `Build_*.bat` scripts), which writes `ASWIniTests_VCL_GUI.exe` to
`tests/build/bin/<Platform>/<Config>`. `ASWIniTests_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
`ASWIniTests_Win32_GUI.exe`, next to the console runner, and CI runs it in the Windows jobs. For
RAD Studio, use `tests/win32/gui/rad370/ASWIniTests_Win32_GUI.cbproj` or its `Build_*.bat`
scripts.
- The VCL GUI runner needs RAD Studio: use `tests/vcl/gui/rad370/ASWIniTests_VCL_GUI.cbproj` or its
`Build_*.bat` scripts.

Like the console tests, the RAD Studio builds of both GUI runners are for Win32 (C++17) and Win64x
(C++20), and write `ASWIniTests_Win32_GUI.exe` and `ASWIniTests_VCL_GUI.exe` to
`tests/build/bin/<Platform>/<Config>`. `tests/vcl/gui/rad370/ASWIniTests_Group.groupproj` opens both
GUI projects together with the console test project.

# Coding Standards

Expand Down
Loading
Loading