├── .clang-format ├── .clang-tidy ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── README.adoc ├── archlinux └── PKGBUILD ├── build ├── cmake │ └── CMakeLists.txt └── vcpkg │ ├── portfile.cmake.in │ ├── test │ ├── CMakeLists.txt │ ├── main.cpp │ ├── vcpkg-configuration.json │ └── vcpkg.json │ ├── usage │ └── vcpkg.json.in ├── conan ├── conanfile.py └── test_package │ ├── CMakeLists.txt │ ├── conanfile.py │ └── example.cpp ├── config ├── asan.mk ├── base │ └── base.mk ├── dbg.mk ├── default.mk ├── no_par_no_install.mk └── rel.mk ├── debian ├── changelog ├── compat ├── control.in ├── libtst-dev.install ├── libtst-doc.install ├── libtst.install.in ├── rules └── source │ └── format ├── doc ├── doxygen.cfg.in └── makefile ├── homebrew └── libtst.rb.in ├── makefile ├── msvs_solution ├── basic_test │ ├── basic_test.vcxproj │ ├── basic_test.vcxproj.filters │ ├── basic_test.vcxproj.user │ └── packages.config ├── libtst │ ├── libtst.vcxproj │ ├── libtst.vcxproj.filters │ ├── libtst.vcxproj.user │ └── packages.config └── msvs_solution.sln ├── msys2 └── PKGBUILD.in ├── nuget ├── build_nuget.ps1 └── nuget.autopkg.in ├── pkg-config ├── makefile └── tst.pc.in ├── src ├── makefile ├── soname.txt └── tst │ ├── application.cpp │ ├── application.hpp │ ├── check.cpp │ ├── check.hpp │ ├── iterator.hxx │ ├── main.cpp │ ├── reporter.cpp │ ├── reporter.hxx │ ├── runner.cpp │ ├── runner.hxx │ ├── runners_pool.cpp │ ├── runners_pool.hxx │ ├── set.cpp │ ├── set.hpp │ ├── settings.cpp │ ├── settings.hxx │ ├── suite.cpp │ ├── suite.hpp │ ├── util.cpp │ └── util.hxx ├── tests ├── .clang-tidy ├── basic │ ├── main.cpp │ ├── makefile │ └── run_list.txt ├── failed │ ├── checks.cpp │ ├── main.cpp │ └── makefile ├── harness │ ├── testees.cpp │ └── testees.hpp └── makefile └── wiki ├── boost-ut_comparison.md ├── main.adoc └── tutorial.adoc /.clang-format: -------------------------------------------------------------------------------- 1 | tool-configs/clang-format/.clang-format -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | tool-configs/clang-tidy/.clang-tidy -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # disable line endings conversion for all files 2 | * -text 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [igagis] 2 | patreon: igagis 3 | custom: ["https://paypal.me/igagis"] 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: {branches-ignore: [latest], tags: ['*']} 4 | pull_request: 5 | env: 6 | PACKAGE_NAME: ${{ github.event.repository.name }} 7 | PACKAGE_VERSION: ${{ github.sha }} 8 | MYCI_NEXUS_USERNAME: cppfw 9 | MYCI_NEXUS_PASSWORD: ${{ secrets.MYCI_NEXUS_PASSWORD }} 10 | MYCI_GIT_USERNAME: igagis 11 | MYCI_GIT_PASSWORD: ${{ secrets.MYCI_GIT_ACCESS_TOKEN }} 12 | MYCI_CONAN_REMOTE: https://gagis.hopto.org/conan 13 | MYCI_CONAN_USER: cppfw 14 | MYCI_CONAN_PASSWORD: ${{ secrets.MYCI_CONAN_PASSWORD }} 15 | jobs: 16 | ##### sanitizer ##### 17 | sanitizer: 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | include: 22 | - {os: debian, codename: bookworm, image_owner: } 23 | - {os: debian, codename: bookworm, image_owner: arm32v7/, labels: [ubuntu-24.04-arm]} 24 | - {os: debian, codename: bookworm, image_owner: arm64v8/, labels: [ubuntu-24.04-arm]} 25 | runs-on: ${{ (matrix.labels == '' && 'ubuntu-latest') || matrix.labels }} 26 | container: ${{ matrix.image_owner }}${{ matrix.os }}:${{ matrix.codename }} 27 | name: sanitizer - ${{ matrix.image_owner }}${{ matrix.os }}:${{ matrix.codename }} 28 | steps: 29 | - name: add cppfw deb repo 30 | uses: myci-actions/add-deb-repo@main 31 | with: 32 | repo: deb https://gagis.hopto.org/repo/cppfw/${{ matrix.os }} ${{ matrix.codename }} main 33 | repo-name: cppfw 34 | keys-asc: https://gagis.hopto.org/repo/cppfw/pubkey.gpg 35 | install: myci git 36 | - name: add llvm repo (for clang-format) 37 | uses: myci-actions/add-deb-repo@main 38 | with: 39 | repo: deb http://apt.llvm.org/${{ matrix.codename }} llvm-toolchain-${{ matrix.codename }} main 40 | repo-name: llvm 41 | keys-asc: https://apt.llvm.org/llvm-snapshot.gpg.key 42 | - name: install ci tools 43 | run: | 44 | apt install --assume-yes devscripts equivs 45 | - name: git clone 46 | uses: myci-actions/checkout@main 47 | - name: prepare debian package 48 | run: myci-deb-prepare.sh 49 | - name: install deps 50 | run: myci-deb-install-build-deps.sh 51 | - name: build 52 | run: make config=asan 53 | - name: test 54 | run: make config=asan test 55 | ##### deb linux ##### 56 | deb_linux: 57 | strategy: 58 | fail-fast: false 59 | matrix: 60 | include: 61 | # - {os: debian, codename: buster, image_owner: } 62 | # - {os: debian, codename: buster, image_owner: i386/, labels: [i386,docker]} 63 | # - {os: debian, codename: buster, image_owner: arm32v7/, labels: [ubuntu-24.04-arm]} 64 | # - {os: debian, codename: bullseye, image_owner: } 65 | # - {os: debian, codename: bullseye, image_owner: i386/, labels: [i386,docker]} 66 | # - {os: debian, codename: bullseye, image_owner: , labels: [ubuntu-24.04-arm]} 67 | # - {os: debian, codename: bullseye, image_owner: , labels: [ubuntu-24.04-arm]} 68 | - {os: debian, codename: bookworm, image_owner: } 69 | # - {os: debian, codename: bookworm, image_owner: i386/, labels: [i386,docker]} 70 | - {os: debian, codename: bookworm, image_owner: arm32v7/, labels: [ubuntu-24.04-arm]} 71 | - {os: debian, codename: bookworm, image_owner: arm64v8/, labels: [ubuntu-24.04-arm]} 72 | # - {os: ubuntu, codename: focal, image_owner: } 73 | # - {os: ubuntu, codename: jammy, image_owner: } 74 | - {os: ubuntu, codename: noble, image_owner: } 75 | # - {os: raspbian, codename: buster, image_owner: igagis/, labels: [ubuntu-24.04-arm]} 76 | # - {os: raspbian, codename: bullseye, image_owner: igagis/, labels: [ubuntu-24.04-arm]} 77 | # - {os: raspbian, codename: bookworm, image_owner: igagis/, labels: [ubuntu-24.04-arm]} 78 | runs-on: ${{ (matrix.labels == '' && 'ubuntu-latest') || matrix.labels }} 79 | container: ${{ matrix.image_owner }}${{ matrix.os }}:${{ matrix.codename }} 80 | name: linux - ${{ matrix.image_owner }}${{ matrix.os }}:${{ matrix.codename }} 81 | steps: 82 | - name: set TZ for ubuntu:focal 83 | run: | 84 | # configure timezone to avoid 'tzdata' package to require user interaction during installation (needed for ubuntu:focal) 85 | TZ=Europe/Helsinki ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 86 | - name: add cppfw deb repo 87 | uses: myci-actions/add-deb-repo@main 88 | with: 89 | repo: deb https://gagis.hopto.org/repo/cppfw/${{ matrix.os }} ${{ matrix.codename }} main 90 | repo-name: cppfw 91 | keys-asc: https://gagis.hopto.org/repo/cppfw/pubkey.gpg 92 | install: myci git devscripts equivs 93 | - name: add llvm repo (for clang-format) 94 | uses: myci-actions/add-deb-repo@main 95 | with: 96 | repo: deb http://apt.llvm.org/${{ matrix.codename }} llvm-toolchain-${{ matrix.codename }} main 97 | repo-name: llvm 98 | keys-asc: https://apt.llvm.org/llvm-snapshot.gpg.key 99 | - name: git clone 100 | uses: myci-actions/checkout@main 101 | - name: set PACKAGE_VERSION 102 | uses: myci-actions/export-env-var@main 103 | with: {name: PACKAGE_VERSION, value: $(myci-deb-version.sh debian/changelog)} 104 | - name: prepare debian package 105 | run: myci-deb-prepare.sh 106 | - name: install deps 107 | run: myci-deb-install-build-deps.sh 108 | - name: build 109 | run: dpkg-buildpackage --unsigned-source --unsigned-changes 110 | - name: publish test report 111 | uses: mikepenz/action-junit-report@v2.4.0 112 | with: 113 | report_paths: 'tests/basic/out/rel/junit.xml' 114 | github_token: ${{ secrets.GITHUB_TOKEN }} 115 | check_name: "test report: ${{ github.job }} - ${{ matrix.image_owner }}${{ matrix.os }}:${{ matrix.codename }}" 116 | suite_regex: '*' 117 | fail_on_failure: true 118 | if: ${{ matrix.labels == '' }} 119 | - name: deploy deb packages 120 | run: | 121 | echo "${{ secrets.MYCI_REPO_SSH_KEY }}" > repo_key_rsa && chmod 600 repo_key_rsa 122 | myci-deploy-apt-ssh.sh --key repo_key_rsa --server gagis.hopto.org --repo cppfw/${{ matrix.os }} --distro ${{ matrix.codename }} --component main ../lib${PACKAGE_NAME}*_${PACKAGE_VERSION}_*.*deb 123 | if: startsWith(github.ref, 'refs/tags/') 124 | ##### archlinux ##### 125 | # archlinux: 126 | # strategy: 127 | # fail-fast: false 128 | # matrix: 129 | # include: 130 | # - {image: "archlinux:latest", arch: amd64} 131 | # # - {image: "lopsided/archlinux-arm32v7:latest", arch: arm32, labels: [ubuntu-24.04-arm]} 132 | # - {image: "lopsided/archlinux-arm64v8:latest", arch: arm64, labels: [ubuntu-24.04-arm]} 133 | # runs-on: ${{ (matrix.labels == '' && 'ubuntu-latest') || matrix.labels }} 134 | # container: ${{ matrix.image }} 135 | # name: linux - archlinux - ${{ matrix.arch }} 136 | # steps: 137 | # - name: install ci tools 138 | # run: | 139 | # pacman --sync --refresh --sysupgrade --noconfirm --noprogressbar base-devel git 140 | # - name: add cppfw pacman repo 141 | # uses: myci-actions/add-pacman-repo@main 142 | # with: 143 | # name: cppfw 144 | # url: https://gagis.hopto.org/repo/cppfw/archlinux/${{ matrix.arch }} 145 | # key-server: https://gagis.hopto.org/repo/cppfw/pubkey.gpg 146 | # install: >- 147 | # myci 148 | # - name: git clone 149 | # uses: myci-actions/checkout@main 150 | # - name: set PACKAGE_VERSION 151 | # uses: myci-actions/export-env-var@main 152 | # with: {name: PACKAGE_VERSION, value: $(myci-deb-version.sh debian/changelog)} 153 | # # makepkg needs to install dependency packages, so nobody user needs sudo rights 154 | # - name: add nobody to sudoers 155 | # run: | 156 | # echo "nobody ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers 157 | # - name: build 158 | # run: | 159 | # # provide write access to user nobody 160 | # chmod --recursive 777 . 161 | # cd archlinux 162 | # sudo --user=nobody --preserve-env=PACKAGE_VERSION makepkg --syncdeps --noconfirm --skipinteg --noprogressbar 163 | # - name: deploy 164 | # run: | 165 | # echo "${{ secrets.MYCI_REPO_SSH_KEY }}" > repo_key_rsa && chmod 600 repo_key_rsa 166 | # myci-deploy-pacman-ssh.sh --server gagis.hopto.org --key repo_key_rsa --repo cppfw/archlinux/${{ matrix.arch }} --database cppfw archlinux/$PACKAGE_NAME-*.pkg.* 167 | # if: startsWith(github.ref, 'refs/tags/') 168 | ##### macosx ##### 169 | macosx: 170 | runs-on: macos-latest 171 | steps: 172 | - name: workaround python2 and python3 issue when upgrading python 173 | run : | 174 | rm -rf /usr/local/bin/2to3* 175 | rm -rf /usr/local/bin/idle3* 176 | rm -rf /usr/local/bin/pydoc3* 177 | rm -rf /usr/local/bin/python3 178 | rm -rf /usr/local/bin/python3-config 179 | rm -rf /usr/local/bin/python3* 180 | rm -rf /usr/local/bin/python3*-config 181 | - name: git clone 182 | uses: myci-actions/checkout@main 183 | - name: install clang-tidy and clang-format 184 | run: | 185 | brew install llvm 186 | ln -s "$(brew --prefix llvm)/bin/clang-format" "/usr/local/bin/clang-format" 187 | ln -s "$(brew --prefix llvm)/bin/clang-tidy" "/usr/local/bin/clang-tidy" 188 | ln -s "$(brew --prefix llvm)/bin/clang-apply-replacements" "/usr/local/bin/clang-apply-replacements" 189 | - name: add cppfw tap 190 | run: | 191 | brew tap cppfw/tap 192 | brew update 193 | - name: install ci tools 194 | run: brew install myci make 195 | - name: set PATH to use latest make 196 | uses: myci-actions/export-env-var@main 197 | with: {name: PATH, value: "$HOMEBREW_PREFIX/opt/make/libexec/gnubin:$PATH"} 198 | - name: set CFLAGS 199 | uses: myci-actions/export-env-var@main 200 | with: {name: CFLAGS, value: "-I $HOMEBREW_PREFIX/include $CFLAGS"} 201 | - name: set CXXFLAGS 202 | uses: myci-actions/export-env-var@main 203 | with: {name: CXXFLAGS, value: "-I $HOMEBREW_PREFIX/include $CXXFLAGS"} 204 | - name: set LDFLAGS 205 | uses: myci-actions/export-env-var@main 206 | with: {name: LDFLAGS, value: "-L $HOMEBREW_PREFIX/lib $LDFLAGS"} 207 | - name: install deps 208 | run: myci-brew-install.sh `myci-list-deps-homebrew.sh` 209 | - name: build 210 | run: make --include-dir=$(brew --prefix)/include 211 | - name: test 212 | run: make --include-dir=$(brew --prefix)/include test 213 | - name: publish test report 214 | uses: mikepenz/action-junit-report@v2.3.0 215 | with: 216 | report_paths: 'tests/basic/junit.xml' 217 | github_token: ${{ secrets.GITHUB_TOKEN }} 218 | check_name: "test report: ${{ github.job }}" 219 | fail_on_failure: true 220 | - name: deploy 221 | run: myci-deploy-homebrew.sh --tap cppfw/tap 222 | if: startsWith(github.ref, 'refs/tags/') 223 | ##### msys2 ##### 224 | msys2: 225 | runs-on: windows-latest 226 | defaults: 227 | run: 228 | shell: msys2 {0} 229 | strategy: 230 | fail-fast: false 231 | matrix: 232 | include: 233 | - {arch: i686, repo: mingw32} 234 | - {arch: x86_64, repo: mingw64} 235 | name: msys2 - ${{ matrix.repo }} 236 | steps: 237 | - name: make msys2 to provide the default shell 238 | uses: msys2/setup-msys2@v2 239 | with: 240 | update: true 241 | msystem: MSYS 242 | install: >- 243 | msys2-devel 244 | mingw-w64-${{ matrix.arch }}-toolchain 245 | - name: add cppfw pacman msys repo 246 | uses: myci-actions/add-pacman-repo@main 247 | with: 248 | name: cppfw_msys 249 | url: https://gagis.hopto.org/repo/cppfw/msys2/msys 250 | shell: 'msys2 {0}' 251 | key-server: https://gagis.hopto.org/repo/cppfw/pubkey.gpg 252 | install: myci 253 | - name: add cppfw pacman msys/mingw repo 254 | uses: myci-actions/add-pacman-repo@main 255 | with: 256 | name: cppfw_${{ matrix.repo }} 257 | url: https://gagis.hopto.org/repo/cppfw/msys2/${{ matrix.repo }} 258 | shell: 'msys2 {0}' 259 | key-server: https://gagis.hopto.org/repo/cppfw/pubkey.gpg 260 | - name: git clone 261 | uses: myci-actions/checkout@main 262 | - name: prepare pacman package 263 | run: myci-apply-version.sh --version $(myci-deb-version.sh debian/changelog) msys2/PKGBUILD.in 264 | - name: build 265 | # to make makepkg-mingw build only one architecture we need to set the MINGW_ARCH 266 | env: {MINGW_ARCH: '${{ matrix.repo }}'} 267 | run: | 268 | cd msys2 269 | PKGEXT='.pkg.tar.xz' makepkg-mingw --syncdeps --noconfirm --skipinteg 270 | - name: publish test report 271 | uses: mikepenz/action-junit-report@v2.3.0 272 | with: 273 | report_paths: 'tests/basic/junit.xml' 274 | github_token: ${{ secrets.GITHUB_TOKEN }} 275 | check_name: "test report: ${{ github.job }} - ${{ matrix.repo }}" 276 | fail_on_failure: true 277 | - name: deploy 278 | run: | 279 | echo "${{ secrets.MYCI_REPO_SSH_KEY }}" > repo_key_rsa && chmod 600 repo_key_rsa 280 | for f in $(find msys2 -name "mingw-w64-${{ matrix.arch }}-$PACKAGE_NAME-*-any.pkg.*"); do 281 | myci-deploy-pacman-ssh.sh --server gagis.hopto.org --key repo_key_rsa --repo cppfw/msys2/${{ matrix.repo }} --database cppfw_${{ matrix.repo }} $f 282 | done 283 | if: startsWith(github.ref, 'refs/tags/') 284 | ##### msvs ##### 285 | msvs: 286 | runs-on: windows-latest 287 | defaults: 288 | run: 289 | shell: powershell 290 | steps: 291 | - name: git clone 292 | uses: actions/checkout@v3 293 | - name: install CoAPP tools 294 | uses: myci-actions/install-coapp-tools@main 295 | - name: nuget update 296 | run: | 297 | nuget restore msvs_solution/msvs_solution.sln 298 | nuget update msvs_solution/msvs_solution.sln 299 | - name: add msbuild to PATH 300 | uses: microsoft/setup-msbuild@v1.1 301 | - name: build 302 | run: .\nuget\build_nuget.ps1 303 | - name: publish test report 304 | uses: mikepenz/action-junit-report@v2.3.0 305 | with: 306 | report_paths: 'nuget/junit_*.xml' 307 | github_token: ${{ secrets.GITHUB_TOKEN }} 308 | check_name: "test report: ${{ github.job }}" 309 | fail_on_failure: true 310 | - name: deploy 311 | uses: myci-actions/publish-nuget@main 312 | with: 313 | filename: '.\nuget\*.nupkg' 314 | api-key: ${{ secrets.NUGET_DOT_ORG_API_KEY }} 315 | if: startsWith(github.ref, 'refs/tags/') 316 | ##### vcpkg ##### 317 | vcpkg: 318 | strategy: 319 | fail-fast: false 320 | matrix: 321 | include: 322 | - {os: debian, codename: bookworm, image_owner: } 323 | # - {os: debian, codename: bookworm, image_owner: i386/, labels: [i386,docker]} 324 | # - {os: debian, codename: bookworm, image_owner: arm32v7/, labels: [ubuntu-24.04-arm]} 325 | # - {os: debian, codename: bookworm, image_owner: arm64v8/, labels: [ubuntu-24.04-arm]} 326 | runs-on: ${{ (matrix.labels == '' && 'ubuntu-latest') || matrix.labels }} 327 | container: ${{ matrix.image_owner }}${{ matrix.os }}:${{ matrix.codename }} 328 | name: vcpkg - linux | ${{ matrix.image_owner }} 329 | steps: 330 | - name: add cppfw deb repo 331 | uses: myci-actions/add-deb-repo@main 332 | with: 333 | repo: deb https://gagis.hopto.org/repo/cppfw/${{ matrix.os }} ${{ matrix.codename }} main 334 | repo-name: cppfw 335 | keys-asc: https://gagis.hopto.org/repo/cppfw/pubkey.gpg 336 | install: myci cmake git curl zip unzip tar nodejs pkg-config 337 | - name: git clone 338 | uses: myci-actions/checkout@main 339 | - name: install vcpkg 340 | run: | 341 | git clone https://github.com/microsoft/vcpkg.git vcpkg-installation 342 | (cd vcpkg-installation; ./bootstrap-vcpkg.sh) 343 | - name: set VCPKG_ROOT 344 | uses: myci-actions/export-env-var@main 345 | with: {name: VCPKG_ROOT, value: "$(pwd)/vcpkg-installation"} 346 | - name: add VCPKG_ROOT to PATH 347 | uses: myci-actions/export-env-var@main 348 | with: {name: PATH, value: "$PATH:$VCPKG_ROOT"} 349 | - name: prepare vcpkg port 350 | run: | 351 | myci-vcpkg-prepare.sh --git-ref ${{ github.sha }} 352 | - name: test vcpkg port 353 | run: | 354 | cd build/vcpkg/test 355 | cmake . 356 | make 357 | ./test 358 | - name: upload vcpkg logs to artifacts 359 | if: always() # even if previous steps fail, this one needs to be run 360 | uses: actions/upload-artifact@v4 361 | with: 362 | name: vcpkg_logs 363 | path: | 364 | vcpkg-installation/buildtrees/${{ env.PACKAGE_NAME }}/ 365 | build/vcpkg/**/*.log 366 | - name: deploy vcpkg port 367 | run: | 368 | myci-deploy-vcpkg.sh --repo cppfw/vcpkg-repo --port-dir build/vcpkg/overlay/${PACKAGE_NAME} 369 | if: startsWith(github.ref, 'refs/tags/') 370 | ##### conan - linux ##### 371 | conan-linux: 372 | strategy: 373 | fail-fast: false 374 | matrix: 375 | include: 376 | # - {os: ubuntu, codename: noble, image_owner: } 377 | - {os: debian, codename: bookworm, image_owner: } 378 | # - {os: debian, codename: bookworm, image_owner: i386/, labels: [i386,docker]} 379 | # - {os: debian, codename: bookworm, image_owner: arm32v7/, labels: [ubuntu-24.04-arm]} 380 | # - {os: debian, codename: bookworm, image_owner: arm64v8/, labels: [ubuntu-24.04-arm]} 381 | runs-on: ${{ (matrix.labels == '' && 'ubuntu-latest') || matrix.labels }} 382 | container: ${{ matrix.image_owner }}${{ matrix.os }}:${{ matrix.codename }} 383 | name: conan - linux | ${{ matrix.labels[0] }} 384 | steps: 385 | - name: add llvm repo (for clang-format) 386 | uses: myci-actions/add-deb-repo@main 387 | with: 388 | repo: deb http://apt.llvm.org/${{ matrix.codename }} llvm-toolchain-${{ matrix.codename }} main 389 | repo-name: llvm 390 | keys-asc: https://apt.llvm.org/llvm-snapshot.gpg.key 391 | install: clang-format clang-tidy 392 | - name: add cppfw deb repo 393 | uses: myci-actions/add-deb-repo@main 394 | with: 395 | repo: deb https://gagis.hopto.org/repo/cppfw/${{ matrix.os }} ${{ matrix.codename }} main 396 | repo-name: cppfw 397 | keys-asc: https://gagis.hopto.org/repo/cppfw/pubkey.gpg 398 | install: devscripts equivs myci pipx cmake git 399 | - name: add ~/.local/bin to PATH 400 | uses: myci-actions/export-env-var@main 401 | with: {name: PATH, value: "$PATH:$HOME/.local/bin"} 402 | - name: install conan 403 | run: pipx install conan 404 | - name: create default conan profile 405 | run: | 406 | conan profile detect --name default 407 | sed -i -E "s/compiler.cppstd=.*$/compiler.cppstd=17/g" ~/.conan2/profiles/default 408 | - name: git clone 409 | uses: myci-actions/checkout@main 410 | - name: set PACKAGE_VERSION 411 | uses: myci-actions/export-env-var@main 412 | with: {name: PACKAGE_VERSION, value: $(myci-deb-version.sh debian/changelog)} 413 | if: startsWith(github.ref, 'refs/tags/') 414 | - name: build 415 | run: | 416 | conan remote add cppfw $MYCI_CONAN_REMOTE 417 | conan cache clean 418 | conan create conan --build=missing --user $MYCI_CONAN_USER --channel main --version $PACKAGE_VERSION --update 419 | - name: deploy conan package 420 | run: | 421 | conan remote login --password $MYCI_CONAN_PASSWORD cppfw $MYCI_CONAN_USER 422 | conan upload --check --remote cppfw $PACKAGE_NAME/$PACKAGE_VERSION@$MYCI_CONAN_USER/main 423 | if: startsWith(github.ref, 'refs/tags/') 424 | ##### conan - macosx ##### 425 | conan-macosx: 426 | strategy: 427 | fail-fast: false 428 | matrix: 429 | os: 430 | # - macos-10.15 431 | # - macos-11 432 | # - macos-12 433 | - macos-latest 434 | name: conan - ${{ matrix.os }} 435 | runs-on: ${{ matrix.os }} 436 | steps: 437 | - name: workaround python2 and python3 issue when upgrading python 438 | run : | 439 | rm -rf /usr/local/bin/2to3* 440 | rm -rf /usr/local/bin/idle3* 441 | rm -rf /usr/local/bin/pydoc3* 442 | rm -rf /usr/local/bin/python3 443 | rm -rf /usr/local/bin/python3-config 444 | rm -rf /usr/local/bin/python3* 445 | rm -rf /usr/local/bin/python3*-config 446 | - name: git clone 447 | uses: myci-actions/checkout@main 448 | - name: install clang-tidy and clang-format 449 | run: | 450 | brew install llvm 451 | ln -s "$(brew --prefix llvm)/bin/clang-format" "/usr/local/bin/clang-format" 452 | ln -s "$(brew --prefix llvm)/bin/clang-tidy" "/usr/local/bin/clang-tidy" 453 | ln -s "$(brew --prefix llvm)/bin/clang-apply-replacements" "/usr/local/bin/clang-apply-replacements" 454 | - name: add cppfw tap 455 | run: | 456 | brew tap cppfw/tap 457 | brew update 458 | - name: install ci tools 459 | run: brew install myci conan 460 | - name: create default conan profile 461 | run: | 462 | conan profile detect --name default 463 | sed -i -E "s/compiler.cppstd=.*$/compiler.cppstd=17/g" ~/.conan2/profiles/default 464 | - name: set PACKAGE_VERSION 465 | uses: myci-actions/export-env-var@main 466 | with: {name: PACKAGE_VERSION, value: $(myci-deb-version.sh debian/changelog)} 467 | if: startsWith(github.ref, 'refs/tags/') 468 | - name: build 469 | run: | 470 | conan remote add cppfw $MYCI_CONAN_REMOTE 471 | conan cache clean 472 | conan create conan --build=missing --user $MYCI_CONAN_USER --channel main --version $PACKAGE_VERSION --update 473 | - name: deploy conan package 474 | run: | 475 | conan remote login --password $MYCI_CONAN_PASSWORD cppfw $MYCI_CONAN_USER 476 | conan upload --check --remote cppfw $PACKAGE_NAME/$PACKAGE_VERSION@$MYCI_CONAN_USER/main 477 | if: startsWith(github.ref, 'refs/tags/') 478 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/out/* 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tool-configs"] 2 | path = tool-configs 3 | url = ../tool-configs 4 | branch = main 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "(gdb) basic", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/tests/basic/out/dbg/tests", 12 | "args": ["<", "run_list.txt"], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}/tests/basic", 15 | "environment": [{"name": "LD_LIBRARY_PATH", "value": "../../src/out/dbg"}], 16 | "externalConsole": false, 17 | "MIMode": "gdb", 18 | "setupCommands": [ 19 | { 20 | "description": "Enable pretty-printing for gdb", 21 | "text": "-enable-pretty-printing", 22 | "ignoreFailures": true 23 | } 24 | ], 25 | "preLaunchTask": "build_dbg" 26 | }, 27 | { 28 | "name": "(gdb) failed", 29 | "type": "cppdbg", 30 | "request": "launch", 31 | "program": "${workspaceFolder}/tests/failed/out/dbg/tests", 32 | "args": [], 33 | "stopAtEntry": false, 34 | "cwd": "${workspaceFolder}/tests/failed", 35 | "environment": [{"name": "LD_LIBRARY_PATH", "value": "../../src/out/dbg"}], 36 | "externalConsole": false, 37 | "MIMode": "gdb", 38 | "setupCommands": [ 39 | { 40 | "description": "Enable pretty-printing for gdb", 41 | "text": "-enable-pretty-printing", 42 | "ignoreFailures": true 43 | } 44 | ], 45 | "preLaunchTask": "build_dbg" 46 | }, 47 | { 48 | "name": "(gdb) uncaught_exception", 49 | "type": "cppdbg", 50 | "request": "launch", 51 | "program": "${workspaceFolder}/tests/failed/out/dbg/tests", 52 | "args": [ "--suite=factorial", "--test=test_which_throws_unknown_exception" ], 53 | "stopAtEntry": false, 54 | "cwd": "${workspaceFolder}/tests/failed", 55 | "environment": [{"name": "LD_LIBRARY_PATH", "value": "../../src/out/dbg"}], 56 | "externalConsole": false, 57 | "MIMode": "gdb", 58 | "setupCommands": [ 59 | { 60 | "description": "Enable pretty-printing for gdb", 61 | "text": "-enable-pretty-printing", 62 | "ignoreFailures": true 63 | } 64 | ], 65 | "preLaunchTask": "build_dbg" 66 | }, 67 | { 68 | "name": "(gdb) failed_check", 69 | "type": "cppdbg", 70 | "request": "launch", 71 | "program": "${workspaceFolder}/tests/failed/out/dbg/tests", 72 | "args": [ "--suite=factorial", "--test=test_which_fails_check_eq_with_custom_message" ], 73 | "stopAtEntry": false, 74 | "cwd": "${workspaceFolder}/tests/failed", 75 | "environment": [{"name": "LD_LIBRARY_PATH", "value": "../../src/out/dbg"}], 76 | "externalConsole": false, 77 | "MIMode": "gdb", 78 | "setupCommands": [ 79 | { 80 | "description": "Enable pretty-printing for gdb", 81 | "text": "-enable-pretty-printing", 82 | "ignoreFailures": true 83 | } 84 | ], 85 | "preLaunchTask": "build_dbg" 86 | } 87 | ] 88 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "type": "shell", 9 | "command": "make", 10 | "group": "build", 11 | "problemMatcher": [ 12 | "$gcc" 13 | ] 14 | }, 15 | { 16 | "label": "build_dbg", 17 | "type": "shell", 18 | "command": "make config=dbg", 19 | "group": "build", 20 | "problemMatcher": [ 21 | "$gcc" 22 | ] 23 | }, 24 | { 25 | "label": "build_no_par", 26 | "type": "shell", 27 | "command": "make config=no_par_no_install", 28 | "group": "build", 29 | "problemMatcher": [ 30 | "$gcc" 31 | ] 32 | }, 33 | { 34 | "label": "clean-all", 35 | "type": "shell", 36 | "command": "make clean-all", 37 | "group": "build", 38 | "problemMatcher": [] 39 | }, 40 | { 41 | "label": "test_", 42 | "type": "shell", 43 | "command": "make test", 44 | "dependsOn": "build", 45 | "group": "build", 46 | "problemMatcher": [] 47 | }, 48 | { 49 | "label": "test_no_par", 50 | "type": "shell", 51 | "command": "make test config=no_par_no_install", 52 | "dependsOn": "build_no_par", 53 | "group": "build", 54 | "problemMatcher": [] 55 | }, 56 | { 57 | "label": "test_dbg", 58 | "type": "shell", 59 | "command": "make test config=dbg", 60 | "dependsOn": "build_dbg", 61 | "group": "build", 62 | "problemMatcher": [] 63 | }, 64 | { 65 | "label": "format", 66 | "type": "shell", 67 | "command": "make apply-format", 68 | "group": "build", 69 | "problemMatcher": [] 70 | } 71 | ] 72 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2023 Ivan Gagis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | :name: tst 2 | 3 | |==== 4 | | link:https://github.com/cppfw/{name}/releases[image:https://img.shields.io/github/tag/cppfw/{name}.svg[releases]] | link:https://github.com/cppfw/{name}/actions[image:https://github.com/cppfw/{name}/workflows/ci/badge.svg[ci status]] 5 | |==== 6 | 7 | = {name} 8 | 9 | C++ testing framework. 10 | 11 | = Installation, documentation, tutorials 12 | 13 | See link:wiki/main.adoc[WiKi]. 14 | 15 | = Features 16 | 17 | - xUnit-like concepts 18 | - minimal use of preprocessor macros 19 | - declarative definition of test cases 20 | - test suites 21 | - parametrized test cases 22 | - disabled test cases 23 | - parallel test execution 24 | - tests discovery (list existing test cases) 25 | - run list (list of test cases to run) 26 | - JUnit XML report generation 27 | - custom command line arguments 28 | - colored console output 29 | 30 | = Why another {cpp} xUnit framework? 31 | 32 | There is already a plenty of {cpp} testing frameworks. The most popular ones are link:https://github.com/google/googletest[GoogleTest], link:https://github.com/catchorg/Catch2[Catch2] and link:https://github.com/boost-ext/ut[Boost.UT]. 33 | 34 | So why another one? 35 | 36 | Well, the good coding exercise is not the last reason, but also the following ones which make `tst` different: 37 | 38 | - `tst` minimizes use of preprocessor macros. It is designed with keeping in mind the future use of link:https://en.cppreference.com/w/cpp/utility/source_location[`std::source_location`] when it becomes widely supported by compilers. Then it will be possible to avoid using macros at all without much rewriting of existing tests. Right now, essentially only one small macro is required, which is `SL`. For the sake of justice, Boost.UT also works without macros. 39 | - `tst` does not require {cpp}'20 yet (unlike Boost.UT). Works with {cpp}'17 which is well supported by existing compilers. 40 | - `tst` takes in use latest {cpp} concepts without being limited by supporting legacy standards. 41 | - Simple and clean procedural approach to testing (no awkward link:https://en.wikipedia.org/wiki/Behavior-driven_development[BDD], link:https://en.wikipedia.org/wiki/Cucumber_(software)#Gherkin_language[Gherkin], etc. stuff). 42 | - Exception-based "assertions" (`check()` functions). 43 | - Built-in parallel test cases execution. 44 | -------------------------------------------------------------------------------- /archlinux/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Ivan Gagis 2 | 3 | pkgname=tst 4 | pkgver=$PACKAGE_VERSION 5 | pkgrel=1 6 | arch=('x86_64' 'armv7h' 'aarch64') 7 | epoch= 8 | pkgdesc="C++ testing framework" 9 | url="http://github.com/tst/${pkgname}" 10 | license=('MIT') 11 | groups=() 12 | 13 | depends=("utki" "clargs" "nitki") 14 | 15 | makedepends=( 16 | 'myci' 17 | 'prorab' 18 | 'prorab-extra' 19 | 'doxygen' 20 | "clang" # for clang-tidy and clang-format 21 | ) 22 | checkdepends=('myci') 23 | optdepends=() 24 | provides=() 25 | conflicts=() 26 | replaces=() 27 | backup=() 28 | options=() 29 | install= 30 | changelog= 31 | source=() # do not download any sources 32 | noextract=() 33 | md5sums=() 34 | validpgpkeys=() 35 | 36 | rootDir=$(pwd)/.. # project root directory 37 | 38 | prepare() { 39 | cd "$rootDir" 40 | } 41 | 42 | build() { 43 | cd "$rootDir" 44 | # TODO: turn on lint when arch adopts more modern clang-tidy 45 | make lint=off 46 | } 47 | 48 | check() { 49 | cd "$rootDir" 50 | make test 51 | } 52 | 53 | package() { 54 | cd "$rootDir" 55 | make DESTDIR="$pkgdir" PREFIX=/usr install 56 | } 57 | -------------------------------------------------------------------------------- /build/cmake/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | set(name tst) 4 | project(${name}) 5 | 6 | # !!! find_package must go after project() declaration !!! 7 | # Otherwise VCPKG does not set the CMAKE_PREFIX_PATH to find packages. 8 | find_package(myci CONFIG REQUIRED) 9 | 10 | set(srcs) 11 | myci_add_source_files(srcs 12 | DIRECTORY 13 | ../../src/${name} 14 | RECURSIVE 15 | ) 16 | 17 | myci_declare_library(${name} 18 | SOURCES 19 | ${srcs} 20 | PUBLIC_INCLUDE_DIRECTORIES 21 | ../../src/ 22 | INSTALL_INCLUDE_DIRECTORIES 23 | ../../src/${name} 24 | DEPENDENCIES 25 | utki 26 | clargs 27 | opros 28 | nitki 29 | ) 30 | -------------------------------------------------------------------------------- /build/vcpkg/portfile.cmake.in: -------------------------------------------------------------------------------- 1 | vcpkg_check_linkage(ONLY_STATIC_LIBRARY) 2 | 3 | vcpkg_from_github( 4 | OUT_SOURCE_PATH SOURCE_PATH 5 | REPO cppfw/${PORT} 6 | REF $(git_ref) 7 | SHA512 $(archive_hash) 8 | HEAD_REF main 9 | ) 10 | 11 | vcpkg_cmake_configure( 12 | SOURCE_PATH "${SOURCE_PATH}/build/cmake" 13 | ) 14 | 15 | vcpkg_cmake_install() 16 | 17 | vcpkg_cmake_config_fixup() 18 | 19 | # Delete the include directory from the debug installation to prevent overlap. 20 | file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") 21 | 22 | # Install the LICENSE file to the package's share directory and rename it to copyright. 23 | file(INSTALL "${SOURCE_PATH}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) 24 | 25 | # Copy the usage instruction file to the package's share directory. 26 | configure_file("${CMAKE_CURRENT_LIST_DIR}/usage" "${CURRENT_PACKAGES_DIR}/share/${PORT}/usage" COPYONLY) 27 | -------------------------------------------------------------------------------- /build/vcpkg/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | set(CMAKE_TOOLCHAIN_FILE $ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake) 4 | 5 | project(test) 6 | 7 | find_package(utki CONFIG REQUIRED) 8 | find_package(clargs CONFIG REQUIRED) 9 | find_package(opros CONFIG REQUIRED) 10 | find_package(nitki CONFIG REQUIRED) 11 | find_package(tst CONFIG REQUIRED) 12 | 13 | add_executable(test main.cpp) 14 | 15 | target_link_libraries(test PRIVATE tst::tst) 16 | -------------------------------------------------------------------------------- /build/vcpkg/test/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace{ 5 | const tst::set set("test_set", [](tst::suite& suite){ 6 | suite.add("sample_test", [](){ 7 | std::string s = "Hi!"; 8 | tst::check_eq(s.size(), size_t(3), SL); 9 | }); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /build/vcpkg/test/vcpkg-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "default-registry": { 3 | "kind": "git", 4 | "baseline": "5e5d0e1cd7785623065e77eff011afdeec1a3574", 5 | "repository": "https://github.com/microsoft/vcpkg" 6 | }, 7 | "registries": [ 8 | { 9 | "kind": "git", 10 | "repository": "https://github.com/cppfw/vcpkg-repo/", 11 | "baseline": "412f8ee0bb8bad56269668ffa00a91a8ddb5e7d3", 12 | "reference": "main", 13 | "packages": [ "myci", "utki", "clargs", "opros", "nitki" ] 14 | } 15 | ], 16 | "overlay-ports": [ 17 | "../overlay" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /build/vcpkg/test/vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | "tst" 4 | ] 5 | } -------------------------------------------------------------------------------- /build/vcpkg/usage: -------------------------------------------------------------------------------- 1 | tst provides CMake targets: 2 | 3 | find_package(utki CONFIG REQUIRED) 4 | find_package(clargs CONFIG REQUIRED) 5 | find_package(opros CONFIG REQUIRED) 6 | find_package(nitki CONFIG REQUIRED) 7 | find_package(tst CONFIG REQUIRED) 8 | 9 | target_link_libraries(main PRIVATE tst::tst) 10 | -------------------------------------------------------------------------------- /build/vcpkg/vcpkg.json.in: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tst", 3 | "version": "$(version)", 4 | "homepage": "https://github.com/cppfw/tst", 5 | "description": "unit tests runner library in C++", 6 | "license": "MIT", 7 | "dependencies": [ 8 | { 9 | "name" : "vcpkg-cmake", 10 | "host" : true 11 | }, 12 | { 13 | "name" : "vcpkg-cmake-config", 14 | "host" : true 15 | }, 16 | { 17 | "name" : "myci", 18 | "host" : true 19 | }, 20 | "utki", 21 | "clargs", 22 | "nitki" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /conan/conanfile.py: -------------------------------------------------------------------------------- 1 | import os 2 | from conan import ConanFile 3 | from conan.tools.scm import Git 4 | from conan.tools.files import load, update_conandata, copy 5 | from conan.tools.layout import basic_layout 6 | 7 | class TstConan(ConanFile): 8 | name = "tst" 9 | license = "MIT" 10 | author = "Ivan Gagis " 11 | url = "http://github.com/cppfw/" + name 12 | description = "xUnit-like testig framework for C++" 13 | topics = ("C++", "cross-platform") 14 | settings = "os", "compiler", "build_type", "arch" 15 | package_type = "library" 16 | options = {"shared": [True, False], "fPIC": [True, False]} 17 | default_options = {"shared": False, "fPIC": True} 18 | generators = "AutotoolsDeps" # this will set CXXFLAGS etc. env vars 19 | 20 | def requirements(self): 21 | self.requires("utki/[>=1.1.202]@cppfw/main", transitive_headers=True, transitive_libs=True) 22 | self.requires("clargs/[>=0.2.47]@cppfw/main", transitive_headers=True, transitive_libs=True) 23 | self.requires("nitki/[>=0.0.0]@cppfw/main", transitive_headers=False, transitive_libs=True) 24 | 25 | def build_requirements(self): 26 | self.tool_requires("prorab/[>=2.0.27]@cppfw/main") 27 | self.tool_requires("prorab-extra/[>=0.2.57]@cppfw/main") 28 | 29 | def config_options(self): 30 | if self.settings.os == "Windows": 31 | del self.options.fPIC 32 | 33 | # save commit and remote URL to conandata.yml for packaging 34 | def export(self): 35 | git = Git(self) 36 | scm_url = git.get_remote_url() 37 | # NOTE: Git.get_commit() doesn't work properly, 38 | # it gets latest commit of the folder in which conanfile.py resides. 39 | # So, we use "git rev-parse HEAD" instead as it gets the actual HEAD 40 | # commit regardless of the current working directory within the repo. 41 | scm_commit = git.run("rev-parse HEAD") # get current commit 42 | update_conandata(self, {"sources": {"commit": scm_commit, "url": scm_url}}) 43 | 44 | def source(self): 45 | git = Git(self) 46 | sources = self.conan_data["sources"] 47 | # shallow fetch commit 48 | git.fetch_commit(url=sources["url"], commit=sources['commit']) 49 | # shallow clone submodules 50 | git.run("submodule update --init --remote --depth 1") 51 | 52 | def build(self): 53 | self.run("make $MAKE_INCLUDE_DIRS_ARG lint=off") 54 | self.run("make $MAKE_INCLUDE_DIRS_ARG lint=off test") 55 | 56 | def package(self): 57 | src_dir = os.path.join(self.build_folder, "src") 58 | src_rel_dir = os.path.join(self.build_folder, "src/out/rel") 59 | dst_include_dir = os.path.join(self.package_folder, "include") 60 | dst_lib_dir = os.path.join(self.package_folder, "lib") 61 | dst_bin_dir = os.path.join(self.package_folder, "bin") 62 | 63 | copy(conanfile=self, pattern="*.h", dst=dst_include_dir, src=src_dir, keep_path=True) 64 | copy(conanfile=self, pattern="*.hpp", dst=dst_include_dir, src=src_dir, keep_path=True) 65 | 66 | if self.options.shared: 67 | copy(conanfile=self, pattern="*" + self.name + ".lib", dst=dst_lib_dir, src="", keep_path=False) 68 | copy(conanfile=self, pattern="*.dll", dst=dst_bin_dir, src=src_rel_dir, keep_path=False) 69 | copy(conanfile=self, pattern="*.so", dst=dst_lib_dir, src=src_rel_dir, keep_path=False) 70 | copy(conanfile=self, pattern="*.so.*", dst=dst_lib_dir, src=src_rel_dir, keep_path=False) 71 | copy(conanfile=self, pattern="*.dylib", dst=dst_lib_dir, src=src_rel_dir, keep_path=False) 72 | else: 73 | copy(conanfile=self, pattern="*" + self.name + ".lib", dst=dst_lib_dir, src="", keep_path=False) 74 | copy(conanfile=self, pattern="*.a", dst=dst_lib_dir, src=src_rel_dir, keep_path=False) 75 | 76 | def package_info(self): 77 | self.cpp_info.libs = [self.name] 78 | 79 | def package_id(self): 80 | 81 | # change package id only when minor or major version changes, i.e. when ABI breaks 82 | self.info.requires.minor_mode() 83 | -------------------------------------------------------------------------------- /conan/test_package/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(PackageTest CXX) 3 | 4 | # set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | set(THREADS_PREFER_PTHREAD_FLAG ON) 7 | find_package(Threads) 8 | 9 | find_package(tst CONFIG REQUIRED) 10 | 11 | add_executable(example example.cpp) 12 | target_link_libraries(example tst::tst Threads::Threads) 13 | -------------------------------------------------------------------------------- /conan/test_package/conanfile.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from conan import ConanFile, tools 4 | from conan.tools.cmake import CMake, cmake_layout 5 | 6 | class TestConan(ConanFile): 7 | settings = "os", "compiler", "build_type", "arch" 8 | generators = "CMakeToolchain", "CMakeDeps" 9 | 10 | def requirements(self): 11 | self.requires(self.tested_reference_str) 12 | 13 | def build(self): 14 | cmake = CMake(self) 15 | cmake.configure() 16 | cmake.build() 17 | 18 | def layout(self): 19 | cmake_layout(self) 20 | 21 | def test(self): 22 | self.run(".%sexample" % os.sep, env="conanrun") # env sets LD_LIBRARY_PATH etc. to find dependency libs 23 | -------------------------------------------------------------------------------- /conan/test_package/example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace{ 5 | tst::set some_set("some_suite",[](tst::suite& s){ 6 | s.add( 7 | "some_test", 8 | [](){ 9 | tst::check(std::max(3, 4) == 4, SL); 10 | } 11 | ); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /config/asan.mk: -------------------------------------------------------------------------------- 1 | include $(config_dir)rel.mk 2 | 3 | this_cxxflags += -fsanitize=address 4 | this_ldflags += -fsanitize=address 5 | -------------------------------------------------------------------------------- /config/base/base.mk: -------------------------------------------------------------------------------- 1 | this_cxxflags += -Wall # enable all warnings 2 | this_cxxflags += -Wnon-virtual-dtor # warn if base class has non-virtual destructor 3 | this_cxxflags += -Werror # treat warnings as errors 4 | this_cxxflags += -Wfatal-errors # stop on first error encountered 5 | this_cxxflags += -fstrict-aliasing # in order to comply with the c++ standard more strictly 6 | this_cxxflags += -g # include debugging symbols 7 | this_cxxflags += -std=c++17 8 | this_cxxflags += -fPIC 9 | 10 | this_ldflags += -fPIC 11 | this_ldlibs += -lstdc++ 12 | -------------------------------------------------------------------------------- /config/dbg.mk: -------------------------------------------------------------------------------- 1 | include $(config_dir)base/base.mk 2 | 3 | this_cxxflags += -DDEBUG 4 | this_cxxflags += -O0 5 | -------------------------------------------------------------------------------- /config/default.mk: -------------------------------------------------------------------------------- 1 | $(eval $(call prorab-config-default, rel)) 2 | -------------------------------------------------------------------------------- /config/no_par_no_install.mk: -------------------------------------------------------------------------------- 1 | include $(config_dir)rel.mk 2 | 3 | this_lint_cmd := 4 | 5 | this_tst_no_par := true 6 | 7 | this_no_install := true 8 | -------------------------------------------------------------------------------- /config/rel.mk: -------------------------------------------------------------------------------- 1 | include $(config_dir)base/base.mk 2 | 3 | this_cxxflags += -O3 4 | 5 | this_lint_cmd = $(prorab_lint_cmd_clang_tidy) 6 | 7 | # WORKAROUND: on ubuntu jammy dpkg-buildpackage passes -ffat-lto-objects compilation flag 8 | # which is not supported by clang and clang-tidy complains about it: 9 | # error: optimization flag '-ffat-lto-objects' is not supported [clang-diagnostic-ignored-optimization-argument] 10 | # Thus, suppress this warning. 11 | this_cxxflags += -Wno-ignored-optimization-argument 12 | 13 | ifeq ($(os),macosx) 14 | # WORKAROUND: 15 | # clang-tidy on macos doesn't use /usr/local/include as default place to 16 | # search for header files, so we add it explicitly 17 | this_cxxflags += -I /usr/local/include 18 | endif 19 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | libtst (0.3.46) unstable; urgency=medium 2 | 3 | * cmake: myci 4 | 5 | -- Ivan Gagis Sun, 18 May 2025 13:15:37 +0300 6 | 7 | libtst (0.3.45) unstable; urgency=medium 8 | 9 | * conan: macos 10 | 11 | -- Ivan Gagis Tue, 03 Dec 2024 13:41:25 +0200 12 | 13 | libtst (0.3.44) unstable; urgency=medium 14 | 15 | * vcpkgize 16 | 17 | -- Ivan Gagis Mon, 25 Nov 2024 22:54:22 +0200 18 | 19 | libtst (0.3.43) unstable; urgency=medium 20 | 21 | * conan: rebuild 22 | 23 | -- Ivan Gagis Sun, 17 Nov 2024 18:52:04 +0200 24 | 25 | libtst (0.3.42) unstable; urgency=medium 26 | 27 | * update 28 | 29 | -- Ivan Gagis Sun, 07 Jul 2024 00:53:12 +0300 30 | 31 | libtst (0.3.41) unstable; urgency=medium 32 | 33 | * update homebrew packaging 34 | 35 | -- Ivan Gagis Mon, 29 Apr 2024 22:25:55 +0300 36 | 37 | libtst (0.3.40) unstable; urgency=medium 38 | 39 | * fix ubuntu focal build 40 | 41 | -- Ivan Gagis Tue, 26 Mar 2024 22:23:33 +0200 42 | 43 | libtst (0.3.39) unstable; urgency=medium 44 | 45 | * update 46 | 47 | -- Ivan Gagis Tue, 26 Mar 2024 21:07:40 +0200 48 | 49 | libtst (0.3.38) unstable; urgency=medium 50 | 51 | * aptian 52 | 53 | -- Ivan Gagis Wed, 28 Feb 2024 22:37:25 +0200 54 | 55 | libtst (0.3.37) unstable; urgency=medium 56 | 57 | * bookworm 58 | 59 | -- Ivan Gagis Sat, 24 Feb 2024 11:42:23 +0200 60 | 61 | libtst (0.3.36) unstable; urgency=medium 62 | 63 | * format 64 | 65 | -- Ivan Gagis Thu, 08 Feb 2024 14:08:32 +0200 66 | 67 | libtst (0.3.35) unstable; urgency=medium 68 | 69 | * relink to nitki 70 | 71 | -- Ivan Gagis Thu, 08 Feb 2024 13:48:23 +0200 72 | 73 | libtst (0.3.34) unstable; urgency=medium 74 | 75 | * archlinux: deploy 76 | 77 | -- Ivan Gagis Sat, 26 Aug 2023 21:23:37 +0300 78 | 79 | libtst (0.3.33) unstable; urgency=medium 80 | 81 | * relink 82 | 83 | -- Ivan Gagis Tue, 04 Jul 2023 15:49:42 +0300 84 | 85 | libtst (0.3.32) unstable; urgency=medium 86 | 87 | * fix lint issues 88 | 89 | -- Ivan Gagis Sat, 17 Jun 2023 12:37:23 +0300 90 | 91 | libtst (0.3.31) unstable; urgency=medium 92 | 93 | * conan: no transitive headers of nitki 94 | 95 | -- Ivan Gagis Tue, 16 May 2023 22:32:08 +0300 96 | 97 | libtst (0.3.30) unstable; urgency=medium 98 | 99 | * conan: fetch latest commit 100 | 101 | -- Ivan Gagis Mon, 15 May 2023 21:19:24 +0300 102 | 103 | libtst (0.3.29) unstable; urgency=medium 104 | 105 | * conan: transitive_libs 106 | 107 | -- Ivan Gagis Fri, 12 May 2023 22:01:51 +0300 108 | 109 | libtst (0.3.28) unstable; urgency=medium 110 | 111 | * conan: fix linux armhf build 112 | 113 | -- Ivan Gagis Fri, 12 May 2023 18:19:32 +0300 114 | 115 | libtst (0.3.27) unstable; urgency=medium 116 | 117 | * conan: migrate to conan 2 118 | 119 | -- Ivan Gagis Fri, 12 May 2023 13:22:57 +0300 120 | 121 | libtst (0.3.26) unstable; urgency=medium 122 | 123 | * update to latest nitki 124 | 125 | -- Ivan Gagis Sun, 26 Feb 2023 19:37:44 +0200 126 | 127 | libtst (0.3.25) unstable; urgency=medium 128 | 129 | * update to latest nitki 130 | 131 | -- Ivan Gagis Fri, 24 Feb 2023 19:59:29 +0200 132 | 133 | libtst (0.3.24) unstable; urgency=medium 134 | 135 | * fix lint issues 136 | 137 | -- Ivan Gagis Thu, 16 Feb 2023 16:33:11 +0200 138 | 139 | libtst (0.3.23) unstable; urgency=medium 140 | 141 | * relink 142 | 143 | -- Ivan Gagis Tue, 14 Feb 2023 09:59:35 +0200 144 | 145 | libtst (0.3.22) unstable; urgency=medium 146 | 147 | * relink to latest libs 148 | 149 | -- Ivan Gagis Sun, 12 Feb 2023 22:08:59 +0200 150 | 151 | libtst (0.3.21) unstable; urgency=medium 152 | 153 | * msvs: build v143 configs 154 | 155 | -- Ivan Gagis Wed, 28 Dec 2022 14:28:03 +0200 156 | 157 | libtst (0.3.20) unstable; urgency=medium 158 | 159 | * update to latest libutki 160 | 161 | -- Ivan Gagis Fri, 11 Nov 2022 14:11:56 +0200 162 | 163 | libtst (0.3.19) unstable; urgency=medium 164 | 165 | * build for ubuntu jammy 166 | 167 | -- Ivan Gagis Mon, 24 Oct 2022 20:37:27 +0300 168 | 169 | libtst (0.3.18) unstable; urgency=medium 170 | 171 | * test release 172 | 173 | -- Ivan Gagis Wed, 27 Apr 2022 23:56:55 +0300 174 | 175 | libtst (0.3.17) unstable; urgency=medium 176 | 177 | * build for debian arm64 178 | 179 | -- Ivan Gagis Sun, 06 Feb 2022 19:50:19 +0200 180 | 181 | libtst (0.3.16) unstable; urgency=medium 182 | 183 | * refactor tst::check() functions 184 | 185 | -- Ivan Gagis Sat, 08 Jan 2022 13:28:35 +0200 186 | 187 | libtst (0.3.15) unstable; urgency=medium 188 | 189 | * build conan for different versions of macos 190 | 191 | -- Ivan Gagis Fri, 17 Dec 2021 00:06:34 +0200 192 | 193 | libtst (0.3.14) unstable; urgency=medium 194 | 195 | * update to latest clargs 196 | 197 | -- Ivan Gagis Mon, 13 Dec 2021 01:18:10 +0200 198 | 199 | libtst (0.3.13) unstable; urgency=medium 200 | 201 | * rebuild for conan 202 | 203 | -- Ivan Gagis Sun, 12 Dec 2021 21:13:36 +0200 204 | 205 | libtst (0.3.12) unstable; urgency=medium 206 | 207 | * build for raspbian bullseye 208 | 209 | -- Ivan Gagis Mon, 08 Nov 2021 15:59:39 +0200 210 | 211 | libtst (0.3.11) unstable; urgency=medium 212 | 213 | * build for debian bullseye 214 | 215 | -- Ivan Gagis Sat, 30 Oct 2021 01:35:22 +0300 216 | 217 | libtst (0.3.10) unstable; urgency=medium 218 | 219 | * do not use deprecated stuff 220 | 221 | -- Ivan Gagis Wed, 20 Oct 2021 14:08:59 +0300 222 | 223 | libtst (0.3.9) unstable; urgency=medium 224 | 225 | * build for bullseye 226 | 227 | -- Ivan Gagis Sun, 29 Aug 2021 22:56:55 +0300 228 | 229 | libtst (0.3.8) unstable; urgency=medium 230 | 231 | * share test procedure between all parametrized tests 232 | 233 | -- Ivan Gagis Thu, 26 Aug 2021 18:11:03 +0300 234 | 235 | libtst (0.3.7) unstable; urgency=medium 236 | 237 | * msvs: build for v141 tools 238 | 239 | -- Ivan Gagis Sun, 15 Aug 2021 16:19:58 +0300 240 | 241 | libtst (0.3.6) unstable; urgency=medium 242 | 243 | * use latest make in homebrew formulae 244 | 245 | -- Ivan Gagis Wed, 11 Aug 2021 13:30:38 +0300 246 | 247 | libtst (0.3.5) unstable; urgency=medium 248 | 249 | * do not catch exceptions if running just one test 250 | 251 | -- Ivan Gagis Wed, 28 Jul 2021 21:23:19 +0300 252 | 253 | libtst (0.3.4) unstable; urgency=medium 254 | 255 | * deploy to own repo 256 | 257 | -- Ivan Gagis Mon, 19 Jul 2021 19:33:51 +0300 258 | 259 | libtst (0.3.3) unstable; urgency=medium 260 | 261 | * relink to latest libs 262 | 263 | -- Ivan Gagis Wed, 09 Jun 2021 23:01:42 +0300 264 | 265 | libtst (0.3.2) unstable; urgency=medium 266 | 267 | * add --suite and --test command line arguments 268 | 269 | -- Ivan Gagis Wed, 02 Jun 2021 16:26:09 +0300 270 | 271 | libtst (0.3.1) unstable; urgency=medium 272 | 273 | * add possibility to mark test cases as no_parallel 274 | * print warnings on empty suites and empty sets 275 | * relink to new utki 276 | 277 | -- Ivan Gagis Wed, 26 May 2021 22:46:39 +0300 278 | 279 | libtst (0.2.1) unstable; urgency=medium 280 | 281 | * add --run-disabled command line argument 282 | * do not install test apps 283 | 284 | -- Ivan Gagis Tue, 11 May 2021 16:08:38 +0300 285 | 286 | libtst (0.1.13) unstable; urgency=medium 287 | 288 | * prevent runner stop on empty suite 289 | 290 | -- Ivan Gagis Wed, 28 Apr 2021 01:03:39 +0300 291 | 292 | libtst (0.1.12) unstable; urgency=medium 293 | 294 | * fix bugs, add minor logging improvements 295 | 296 | -- Ivan Gagis Tue, 27 Apr 2021 19:47:34 +0300 297 | 298 | libtst (0.1.11) unstable; urgency=medium 299 | 300 | * add application_factory 301 | 302 | -- Ivan Gagis Wed, 21 Apr 2021 16:41:58 +0300 303 | 304 | libtst (0.1.10) unstable; urgency=medium 305 | 306 | * check_result 307 | 308 | -- Ivan Gagis Tue, 20 Apr 2021 23:45:09 +0300 309 | 310 | libtst (0.1.9) unstable; urgency=medium 311 | 312 | * msys2: add nitki dependency 313 | 314 | -- Ivan Gagis Mon, 19 Apr 2021 21:55:56 +0300 315 | 316 | libtst (0.1.8) unstable; urgency=medium 317 | 318 | * deploy to conan 319 | 320 | -- Ivan Gagis Mon, 19 Apr 2021 03:45:20 +0300 321 | 322 | libtst (0.1.7) unstable; urgency=medium 323 | 324 | * remove special support for fixtured tests 325 | 326 | -- Ivan Gagis Sat, 17 Apr 2021 00:52:11 +0300 327 | 328 | libtst (0.1.6) unstable; urgency=medium 329 | 330 | * add doxygen comments 331 | 332 | -- Ivan Gagis Thu, 15 Apr 2021 15:02:12 +0300 333 | 334 | libtst (0.1.5) unstable; urgency=medium 335 | 336 | * do not print outcome by default 337 | 338 | -- Ivan Gagis Wed, 14 Apr 2021 03:58:56 +0300 339 | 340 | libtst (0.1.4) unstable; urgency=medium 341 | 342 | * allow empty lines in run lists 343 | 344 | -- Ivan Gagis Tue, 13 Apr 2021 15:07:31 +0300 345 | 346 | libtst (0.1.3) unstable; urgency=medium 347 | 348 | * test sets 349 | 350 | -- Ivan Gagis Tue, 13 Apr 2021 13:46:55 +0300 351 | 352 | libtst (0.1.2) unstable; urgency=medium 353 | 354 | * test release 355 | 356 | -- Ivan Gagis Tue, 13 Apr 2021 00:08:09 +0300 357 | 358 | libtst (0.1.1) unstable; urgency=medium 359 | 360 | * initial release 361 | 362 | -- Ivan Gagis Mon, 12 Apr 2021 01:09:30 +0300 363 | 364 | libtst (0.1.0) unstable; urgency=low 365 | 366 | * Initial release 367 | 368 | -- Ivan Gagis Fri, 9 Apr 2021 23:05:00 +0200 369 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /debian/control.in: -------------------------------------------------------------------------------- 1 | Source: libtst 2 | Section: libs 3 | Priority: extra 4 | Maintainer: Ivan Gagis 5 | Build-Depends: 6 | debhelper (>= 9), 7 | prorab, 8 | prorab-extra, 9 | myci, 10 | clang-tidy, 11 | clang-format, 12 | libc6-dev, 13 | libutki-dev (>= 1.1.128), 14 | libclargs-dev, 15 | libnitki-dev 16 | Build-Depends-Indep: doxygen 17 | Standards-Version: 3.9.2 18 | 19 | Package: libtst$(soname) 20 | Section: libs 21 | Architecture: any 22 | Depends: ${shlibs:Depends}, ${misc:Depends} 23 | Description: C++ test framework. 24 | C++ test framework for unit and other tests. 25 | 26 | Package: libtst$(soname)-dbg 27 | Section: debug 28 | Architecture: any 29 | Depends: libtst$(soname) (= ${binary:Version}), ${misc:Depends} 30 | Description: debugging symbols for libtst$(soname) package. 31 | 32 | Package: libtst-dev 33 | Section: libdevel 34 | Architecture: any 35 | Depends: libtst$(soname) (= ${binary:Version}), libtst$(soname)-dbg (= ${binary:Version}), ${misc:Depends}, 36 | libutki-dev (>= 1.1.128), 37 | libclargs-dev 38 | Suggests: libtst-doc 39 | Description: C++ test framework. 40 | C++ test framework for unit and other tests. 41 | 42 | Package: libtst-doc 43 | Section: doc 44 | Architecture: all 45 | Depends: ${misc:Depends} 46 | Description: documentation for libtst library. 47 | For more details see description to libtst-dev package. 48 | -------------------------------------------------------------------------------- /debian/libtst-dev.install: -------------------------------------------------------------------------------- 1 | usr/include 2 | usr/lib/pkgconfig 3 | usr/lib/lib*.so 4 | usr/lib/lib*.a 5 | 6 | -------------------------------------------------------------------------------- /debian/libtst-doc.install: -------------------------------------------------------------------------------- 1 | usr/share/doc 2 | -------------------------------------------------------------------------------- /debian/libtst.install.in: -------------------------------------------------------------------------------- 1 | usr/lib/lib*.so.* 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | %: 3 | dh $@ 4 | 5 | .PHONY: override_dh_strip 6 | override_dh_strip: 7 | dh_strip --dbg-package=$(filter %-dbg, $(shell awk '/^Package: /{print $2}' debian/control)) 8 | 9 | .PHONY: override_dh_auto_install 10 | override_dh_auto_install: 11 | PREFIX=/usr dh_auto_install 12 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /doc/makefile: -------------------------------------------------------------------------------- 1 | include prorab.mk 2 | include prorab-doxygen.mk 3 | 4 | this_out_dir := out 5 | this_name := tst 6 | 7 | $(eval $(prorab-build-doxygen)) 8 | -------------------------------------------------------------------------------- /homebrew/libtst.rb.in: -------------------------------------------------------------------------------- 1 | class Libtst < Formula 2 | desc "C++ testing framework." 3 | homepage "https://github.com/cppfw/tst" 4 | url "https://github.com/cppfw/tst/archive/$(version).tar.gz" 5 | sha256 "$(sha256)" 6 | 7 | depends_on "prorab" => :build 8 | depends_on "prorab-extra" => :build 9 | depends_on "myci" => :build 10 | depends_on "pkgconf" => :build # NOTE: 'pkg-config' formula is deprecated 11 | depends_on "libnitki" => :build 12 | depends_on "libutki" 13 | depends_on "libclargs" 14 | 15 | # use gmake here because otherwise homebrew uses default Mac's make which is of too old version 3.81 16 | def install 17 | ENV['PATH'] += ":#{ENV['HOMEBREW_PREFIX']}/bin" 18 | system "#{ENV['HOMEBREW_PREFIX']}/opt/make/libexec/gnubin/make", "--include-dir=#{ENV['HOMEBREW_PREFIX']}/include", "install", "PREFIX=#{prefix}", "lint=off" 19 | end 20 | 21 | test do 22 | system "#{ENV['HOMEBREW_PREFIX']}/opt/make/libexec/gnubin/make", "--include-dir=#{ENV['HOMEBREW_PREFIX']}/include", "test" 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | include prorab.mk 2 | 3 | $(eval $(prorab-include-subdirs)) 4 | -------------------------------------------------------------------------------- /msvs_solution/basic_test/basic_test.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /msvs_solution/basic_test/basic_test.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /msvs_solution/basic_test/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /msvs_solution/libtst/libtst.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | Source Files 38 | 39 | 40 | Source Files 41 | 42 | 43 | Source Files 44 | 45 | 46 | Source Files 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /msvs_solution/libtst/libtst.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /msvs_solution/libtst/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /msvs_solution/msvs_solution.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33205.214 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libtst", "libtst\libtst.vcxproj", "{309B0482-B092-4FDC-BEB8-008989753887}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "basic_test", "basic_test\basic_test.vcxproj", "{F9529845-4306-46E7-BD01-4C5F757D9BF8}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | v141_Debug|x64 = v141_Debug|x64 13 | v141_Debug|x86 = v141_Debug|x86 14 | v141_Release|x64 = v141_Release|x64 15 | v141_Release|x86 = v141_Release|x86 16 | v142_Debug_MD|x64 = v142_Debug_MD|x64 17 | v142_Debug_MD|x86 = v142_Debug_MD|x86 18 | v142_Debug_MT|x64 = v142_Debug_MT|x64 19 | v142_Debug_MT|x86 = v142_Debug_MT|x86 20 | v142_Release_MD|x64 = v142_Release_MD|x64 21 | v142_Release_MD|x86 = v142_Release_MD|x86 22 | v142_Release_MT|x64 = v142_Release_MT|x64 23 | v142_Release_MT|x86 = v142_Release_MT|x86 24 | v143_Debug_MD|x64 = v143_Debug_MD|x64 25 | v143_Debug_MD|x86 = v143_Debug_MD|x86 26 | v143_Debug_MT|x64 = v143_Debug_MT|x64 27 | v143_Debug_MT|x86 = v143_Debug_MT|x86 28 | v143_Release_MD|x64 = v143_Release_MD|x64 29 | v143_Release_MD|x86 = v143_Release_MD|x86 30 | v143_Release_MT|x64 = v143_Release_MT|x64 31 | v143_Release_MT|x86 = v143_Release_MT|x86 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {309B0482-B092-4FDC-BEB8-008989753887}.v141_Debug|x64.ActiveCfg = v141_Debug|x64 35 | {309B0482-B092-4FDC-BEB8-008989753887}.v141_Debug|x64.Build.0 = v141_Debug|x64 36 | {309B0482-B092-4FDC-BEB8-008989753887}.v141_Debug|x86.ActiveCfg = v141_Debug|Win32 37 | {309B0482-B092-4FDC-BEB8-008989753887}.v141_Debug|x86.Build.0 = v141_Debug|Win32 38 | {309B0482-B092-4FDC-BEB8-008989753887}.v141_Release|x64.ActiveCfg = v141_Release|x64 39 | {309B0482-B092-4FDC-BEB8-008989753887}.v141_Release|x64.Build.0 = v141_Release|x64 40 | {309B0482-B092-4FDC-BEB8-008989753887}.v141_Release|x86.ActiveCfg = v141_Release|Win32 41 | {309B0482-B092-4FDC-BEB8-008989753887}.v141_Release|x86.Build.0 = v141_Release|Win32 42 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Debug_MD|x64.ActiveCfg = v142_Debug_MD|x64 43 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Debug_MD|x64.Build.0 = v142_Debug_MD|x64 44 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Debug_MD|x86.ActiveCfg = v142_Debug_MD|Win32 45 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Debug_MD|x86.Build.0 = v142_Debug_MD|Win32 46 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Debug_MT|x64.ActiveCfg = v142_Debug_MT|x64 47 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Debug_MT|x64.Build.0 = v142_Debug_MT|x64 48 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Debug_MT|x86.ActiveCfg = v142_Debug_MT|Win32 49 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Debug_MT|x86.Build.0 = v142_Debug_MT|Win32 50 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Release_MD|x64.ActiveCfg = v142_Release_MD|x64 51 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Release_MD|x64.Build.0 = v142_Release_MD|x64 52 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Release_MD|x86.ActiveCfg = v142_Release_MD|Win32 53 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Release_MD|x86.Build.0 = v142_Release_MD|Win32 54 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Release_MT|x64.ActiveCfg = v142_Release_MT|x64 55 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Release_MT|x64.Build.0 = v142_Release_MT|x64 56 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Release_MT|x86.ActiveCfg = v142_Release_MT|Win32 57 | {309B0482-B092-4FDC-BEB8-008989753887}.v142_Release_MT|x86.Build.0 = v142_Release_MT|Win32 58 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Debug_MD|x64.ActiveCfg = v143_Debug_MD|x64 59 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Debug_MD|x64.Build.0 = v143_Debug_MD|x64 60 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Debug_MD|x86.ActiveCfg = v143_Debug_MD|Win32 61 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Debug_MD|x86.Build.0 = v143_Debug_MD|Win32 62 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Debug_MT|x64.ActiveCfg = v143_Debug_MT|x64 63 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Debug_MT|x64.Build.0 = v143_Debug_MT|x64 64 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Debug_MT|x86.ActiveCfg = v143_Debug_MT|Win32 65 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Debug_MT|x86.Build.0 = v143_Debug_MT|Win32 66 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Release_MD|x64.ActiveCfg = v143_Release_MD|x64 67 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Release_MD|x64.Build.0 = v143_Release_MD|x64 68 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Release_MD|x86.ActiveCfg = v143_Release_MD|Win32 69 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Release_MD|x86.Build.0 = v143_Release_MD|Win32 70 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Release_MT|x64.ActiveCfg = v143_Release_MT|x64 71 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Release_MT|x64.Build.0 = v143_Release_MT|x64 72 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Release_MT|x86.ActiveCfg = v143_Release_MT|Win32 73 | {309B0482-B092-4FDC-BEB8-008989753887}.v143_Release_MT|x86.Build.0 = v143_Release_MT|Win32 74 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v141_Debug|x64.ActiveCfg = v141_Debug|x64 75 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v141_Debug|x64.Build.0 = v141_Debug|x64 76 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v141_Debug|x86.ActiveCfg = v141_Debug|Win32 77 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v141_Debug|x86.Build.0 = v141_Debug|Win32 78 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v141_Release|x64.ActiveCfg = v141_Release|x64 79 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v141_Release|x64.Build.0 = v141_Release|x64 80 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v141_Release|x86.ActiveCfg = v141_Release|Win32 81 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v141_Release|x86.Build.0 = v141_Release|Win32 82 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Debug_MD|x64.ActiveCfg = v142_Debug_MD|x64 83 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Debug_MD|x64.Build.0 = v142_Debug_MD|x64 84 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Debug_MD|x86.ActiveCfg = v142_Debug_MD|Win32 85 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Debug_MD|x86.Build.0 = v142_Debug_MD|Win32 86 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Debug_MT|x64.ActiveCfg = v142_Debug_MT|x64 87 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Debug_MT|x64.Build.0 = v142_Debug_MT|x64 88 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Debug_MT|x86.ActiveCfg = v142_Debug_MT|Win32 89 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Debug_MT|x86.Build.0 = v142_Debug_MT|Win32 90 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Release_MD|x64.ActiveCfg = v142_Release_MD|x64 91 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Release_MD|x64.Build.0 = v142_Release_MD|x64 92 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Release_MD|x86.ActiveCfg = v142_Release_MD|Win32 93 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Release_MD|x86.Build.0 = v142_Release_MD|Win32 94 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Release_MT|x64.ActiveCfg = v142_Release_MT|x64 95 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Release_MT|x64.Build.0 = v142_Release_MT|x64 96 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Release_MT|x86.ActiveCfg = v142_Release_MT|Win32 97 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v142_Release_MT|x86.Build.0 = v142_Release_MT|Win32 98 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Debug_MD|x64.ActiveCfg = v143_Debug_MD|x64 99 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Debug_MD|x64.Build.0 = v143_Debug_MD|x64 100 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Debug_MD|x86.ActiveCfg = v143_Debug_MD|Win32 101 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Debug_MD|x86.Build.0 = v143_Debug_MD|Win32 102 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Debug_MT|x64.ActiveCfg = v143_Debug_MT|x64 103 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Debug_MT|x64.Build.0 = v143_Debug_MT|x64 104 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Debug_MT|x86.ActiveCfg = v143_Debug_MT|Win32 105 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Debug_MT|x86.Build.0 = v143_Debug_MT|Win32 106 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Release_MD|x64.ActiveCfg = v143_Release_MD|x64 107 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Release_MD|x64.Build.0 = v143_Release_MD|x64 108 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Release_MD|x86.ActiveCfg = v143_Release_MD|Win32 109 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Release_MD|x86.Build.0 = v143_Release_MD|Win32 110 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Release_MT|x64.ActiveCfg = v143_Release_MT|x64 111 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Release_MT|x64.Build.0 = v143_Release_MT|x64 112 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Release_MT|x86.ActiveCfg = v143_Release_MT|Win32 113 | {F9529845-4306-46E7-BD01-4C5F757D9BF8}.v143_Release_MT|x86.Build.0 = v143_Release_MT|Win32 114 | EndGlobalSection 115 | GlobalSection(SolutionProperties) = preSolution 116 | HideSolutionNode = FALSE 117 | EndGlobalSection 118 | GlobalSection(ExtensibilityGlobals) = postSolution 119 | SolutionGuid = {867D7FEE-0481-40EA-8E50-8AA4C7643364} 120 | EndGlobalSection 121 | EndGlobal 122 | -------------------------------------------------------------------------------- /msys2/PKGBUILD.in: -------------------------------------------------------------------------------- 1 | # Maintainer: Ivan Gagis 2 | 3 | if [ "$MSYSTEM" == "MSYS" ]; then 4 | pkgPrefix= 5 | dirPrefix=/usr 6 | arch=('x86_64' 'i686') 7 | elif [ "$MSYSTEM" == "MINGW32" ]; then 8 | pkgPrefix=mingw-w64-i686- 9 | dirPrefix=/mingw32 10 | arch=('any') 11 | elif [ "$MSYSTEM" == "MINGW64" ]; then 12 | pkgPrefix=mingw-w64-x86_64- 13 | dirPrefix=/mingw64 14 | arch=('any') 15 | else 16 | echo "ERROR: unknown MSYS shell: $MSYSTEM" 17 | exit 1 18 | fi 19 | 20 | packageName=tst 21 | 22 | pkgname="${pkgPrefix}${packageName}" 23 | pkgver=$(version) 24 | pkgrel=1 25 | epoch= 26 | pkgdesc="C++ testing framework" 27 | #arch=('any') #defined above 28 | url="http://github.com/tst/${packageName}" 29 | license=('MIT') 30 | groups=() 31 | 32 | depends=("${pkgPrefix}utki" "${pkgPrefix}clargs" "${pkgPrefix}nitki") 33 | 34 | makedepends=( 35 | 'myci' 36 | 'prorab' 37 | 'prorab-extra' 38 | 'doxygen' 39 | "${pkgPrefix}clang-tools-extra" 40 | ) 41 | checkdepends=('myci') 42 | optdepends=() 43 | provides=() 44 | conflicts=() 45 | replaces=() 46 | backup=() 47 | options=() 48 | install= 49 | changelog= 50 | source=() # Do not download any sources 51 | noextract=() 52 | md5sums=() 53 | validpgpkeys=() 54 | 55 | rootDir=$(pwd)/.. # project root directory 56 | 57 | prepare() { 58 | cd "$rootDir" 59 | } 60 | 61 | build() { 62 | cd "$rootDir" 63 | make 64 | } 65 | 66 | check() { 67 | cd "$rootDir" 68 | make test 69 | } 70 | 71 | package() { 72 | cd "$rootDir" 73 | make DESTDIR="$pkgdir" PREFIX="$dirPrefix" install 74 | } 75 | -------------------------------------------------------------------------------- /nuget/build_nuget.ps1: -------------------------------------------------------------------------------- 1 | Push-Location 2 | $scriptdir = Split-Path $MyInvocation.MyCommand.Path 3 | cd $scriptdir 4 | 5 | #extract version from debian/changelog 6 | $ver = (Get-Content ..\debian\changelog -Head 1) -replace ".*\((\d*\.\d*\.\d*)(\-\d+){0,1}\).*",'$1' 7 | #Write-Host $ver 8 | 9 | #insert version into all *.in files 10 | Get-ChildItem "." -Filter *.in | Foreach-Object{ 11 | $content = Get-Content $_.FullName 12 | 13 | #filter and save content to the original file 14 | #$content | Where-Object {$_ -match 'step[49]'} | Set-Content $_.FullName 15 | 16 | #filter and save content to a new file 17 | ($content -replace '\$\(version\)',"$ver") | Set-Content ($_.BaseName) 18 | } 19 | 20 | 21 | "%VS142COMNTOOLS%\VsMSBuildCmd.bat" 22 | 23 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v140_Debug /p:Platform=x86 /v:minimal /nologo 24 | # If(!$?){exit 1} 25 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v140_Release /p:Platform=x86 /v:minimal /nologo 26 | # If(!$?){exit 1} 27 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v140_Debug /p:Platform=x64 /v:minimal /nologo 28 | # If(!$?){exit 1} 29 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v140_Release /p:Platform=x64 /v:minimal /nologo 30 | # If(!$?){exit 1} 31 | 32 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v141_Debug /p:Platform=x86 /v:minimal /nologo; If(!$?){exit 1} 33 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v141_Release /p:Platform=x86 /v:minimal /nologo; If(!$?){exit 1} 34 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v141_Debug /p:Platform=x64 /v:minimal /nologo; If(!$?){exit 1} 35 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v141_Release /p:Platform=x64 /v:minimal /nologo; If(!$?){exit 1} 36 | 37 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v142_Debug_MD /p:Platform=x86 /v:minimal /nologo; If(!$?){exit 1} 38 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v142_Release_MD /p:Platform=x86 /v:minimal /nologo; If(!$?){exit 1} 39 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v142_Debug_MD /p:Platform=x64 /v:minimal /nologo; If(!$?){exit 1} 40 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v142_Release_MD /p:Platform=x64 /v:minimal /nologo; If(!$?){exit 1} 41 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v142_Debug_MT /p:Platform=x86 /v:minimal /nologo; If(!$?){exit 1} 42 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v142_Release_MT /p:Platform=x86 /v:minimal /nologo; If(!$?){exit 1} 43 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v142_Debug_MT /p:Platform=x64 /v:minimal /nologo; If(!$?){exit 1} 44 | # msbuild ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v142_Release_MT /p:Platform=x64 /v:minimal /nologo; If(!$?){exit 1} 45 | 46 | msbuild /m ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v143_Debug_MD /p:Platform=x86 /v:minimal /nologo; If(!$?){exit 1} 47 | msbuild /m ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v143_Release_MD /p:Platform=x86 /v:minimal /nologo; If(!$?){exit 1} 48 | msbuild /m ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v143_Debug_MD /p:Platform=x64 /v:minimal /nologo; If(!$?){exit 1} 49 | msbuild /m ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v143_Release_MD /p:Platform=x64 /v:minimal /nologo; If(!$?){exit 1} 50 | msbuild /m ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v143_Debug_MT /p:Platform=x86 /v:minimal /nologo; If(!$?){exit 1} 51 | msbuild /m ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v143_Release_MT /p:Platform=x86 /v:minimal /nologo; If(!$?){exit 1} 52 | msbuild /m ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v143_Debug_MT /p:Platform=x64 /v:minimal /nologo; If(!$?){exit 1} 53 | msbuild /m ../msvs_solution/msvs_solution.sln /t:Rebuild /p:Configuration=v143_Release_MT /p:Platform=x64 /v:minimal /nologo; If(!$?){exit 1} 54 | 55 | Write-Host "running tests..." 56 | # ../msvs_solution/v141_Debug/basic_test.exe --jobs=2 --junit-out=junit_x86_v141_debug.xml; If(!$?){exit 1} 57 | # ../msvs_solution/v141_Release/basic_test.exe --jobs=2 --junit-out=junit_x86_v141_release.xml; If(!$?){exit 1} 58 | # ../msvs_solution/x64/v141_Debug/basic_test.exe --jobs=2 --junit-out=junit_x64_v141_debug.xml; If(!$?){exit 1} 59 | # ../msvs_solution/x64/v141_Release/basic_test.exe --jobs=2 --junit-out=junit_x64_v141_release.xml; If(!$?){exit 1} 60 | 61 | # ../msvs_solution/v142_Debug_MD/basic_test.exe --jobs=2 --junit-out=junit_x86_v142_debug_md.xml; If(!$?){exit 1} 62 | # ../msvs_solution/v142_Debug_MT/basic_test.exe --jobs=2 --junit-out=junit_x86_v142_debug_mt.xml; If(!$?){exit 1} 63 | # ../msvs_solution/v142_Release_MD/basic_test.exe --jobs=2 --junit-out=junit_x86_v142_release_md.xml; If(!$?){exit 1} 64 | # ../msvs_solution/v142_Release_MT/basic_test.exe --jobs=2 --junit-out=junit_x86_v142_release_mt.xml; If(!$?){exit 1} 65 | # ../msvs_solution/x64/v142_Debug_MD/basic_test.exe --jobs=2 --junit-out=junit_x64_v142_debug_md.xml; If(!$?){exit 1} 66 | # ../msvs_solution/x64/v142_Debug_MT/basic_test.exe --jobs=2 --junit-out=junit_x64_v142_debug_mt.xml; If(!$?){exit 1} 67 | # ../msvs_solution/x64/v142_Release_MD/basic_test.exe --jobs=2 --junit-out=junit_x64_v142_release_md.xml; If(!$?){exit 1} 68 | # ../msvs_solution/x64/v142_Release_MT/basic_test.exe --jobs=2 --junit-out=junit_x64_v142_release_mt.xml; If(!$?){exit 1} 69 | 70 | ../msvs_solution/v143_Debug_MD/basic_test.exe --jobs=2 --junit-out=junit_x86_v143_debug_md.xml; If(!$?){exit 1} 71 | ../msvs_solution/v143_Debug_MT/basic_test.exe --jobs=2 --junit-out=junit_x86_v143_debug_mt.xml; If(!$?){exit 1} 72 | ../msvs_solution/v143_Release_MD/basic_test.exe --jobs=2 --junit-out=junit_x86_v143_release_md.xml; If(!$?){exit 1} 73 | ../msvs_solution/v143_Release_MT/basic_test.exe --jobs=2 --junit-out=junit_x86_v143_release_mt.xml; If(!$?){exit 1} 74 | ../msvs_solution/x64/v143_Debug_MD/basic_test.exe --jobs=2 --junit-out=junit_x64_v143_debug_md.xml; If(!$?){exit 1} 75 | ../msvs_solution/x64/v143_Debug_MT/basic_test.exe --jobs=2 --junit-out=junit_x64_v143_debug_mt.xml; If(!$?){exit 1} 76 | ../msvs_solution/x64/v143_Release_MD/basic_test.exe --jobs=2 --junit-out=junit_x64_v143_release_md.xml; If(!$?){exit 1} 77 | ../msvs_solution/x64/v143_Release_MT/basic_test.exe --jobs=2 --junit-out=junit_x64_v143_release_mt.xml; If(!$?){exit 1} 78 | 79 | Write-NuGetPackage nuget.autopkg 80 | If(!$?){exit 1} 81 | Pop-Location 82 | -------------------------------------------------------------------------------- /nuget/nuget.autopkg.in: -------------------------------------------------------------------------------- 1 | configurations { 2 | UserPlatformToolset { 3 | // Needed because autopackage lacks VS2015+ support 4 | key = "PlatformToolset"; 5 | choices: "v140,v141,v142,v143"; 6 | }; 7 | 8 | RuntimeLibrary { 9 | key = "RuntimeLibrary"; // This is the key you can find in .vcxproj file 10 | choices: "MultiThreaded,MultiThreadedDebug,MultiThreadedDLL,MultiThreadedDebugDLL"; // these choices must be valid values for .vcxproj file 11 | }; 12 | } 13 | 14 | nuget{ 15 | nuspec{ 16 | id = libtst; 17 | version : $(version); 18 | title: some C++ library; 19 | authors: {Ivan Gagis}; 20 | owners: {Ivan Gagis}; 21 | licenseUrl: "https://raw.githubusercontent.com/cppfw/tst/main/LICENSE"; 22 | projectUrl: "https://github.com/cppfw/tst"; 23 | iconUrl: "https://github.com/cppfw/tst/blob/main/logo.svg"; 24 | requireLicenseAcceptance:false; 25 | summary: C++ testing framework; 26 | 27 | description: @"C++ testing framework"; 28 | releaseNotes: "Initial release"; 29 | copyright: Copyright 2021 Ivan Gagis; 30 | tags: { native }; 31 | } 32 | dependencies { 33 | packages : { 34 | libutki/1.0.128; 35 | libclargs/0.2.21; 36 | libnitki/1.0.42; 37 | }; 38 | } 39 | files { 40 | // this is needed to put headers in the base folder 41 | nestedInclude: { 42 | #destination = ${d_include}tst; 43 | "..\src\tst\**\*.hpp" 44 | }; 45 | 46 | //==== v140 tools ==== 47 | /* 48 | [x86,v140,release] { 49 | lib: ..\msvs_solution\v140_Release\libtst.lib; 50 | } 51 | [x86,v140,debug] { 52 | lib: ..\msvs_solution\v140_Debug\libtst.lib; 53 | } 54 | [x64,v140,release] { 55 | lib: ..\msvs_solution\x64\v140_Release\libtst.lib; 56 | } 57 | [x64,v140,debug] { 58 | lib: ..\msvs_solution\x64\v140_Debug\libtst.lib; 59 | } 60 | */ 61 | //==== v141 tools ==== 62 | /* 63 | [x86,v141,release] { 64 | lib: ..\msvs_solution\v141_Release\libtst.lib; 65 | } 66 | [x86,v141,debug] { 67 | lib: ..\msvs_solution\v141_Debug\libtst.lib; 68 | } 69 | [x64,v141,release] { 70 | lib: ..\msvs_solution\x64\v141_Release\libtst.lib; 71 | } 72 | [x64,v141,debug] { 73 | lib: ..\msvs_solution\x64\v141_Debug\libtst.lib; 74 | } 75 | */ 76 | //==== v142 tools ==== 77 | /* 78 | [x86,v142,release,MultiThreaded] { 79 | lib: ..\msvs_solution\v142_Release_MT\libtst.lib; 80 | } 81 | [x86,v142,debug,MultiThreadedDebug] { 82 | lib: ..\msvs_solution\v142_Debug_MT\libtst.lib; 83 | } 84 | [x64,v142,release,MultiThreaded] { 85 | lib: ..\msvs_solution\x64\v142_Release_MT\libtst.lib; 86 | } 87 | [x64,v142,debug,MultiThreadedDebug] { 88 | lib: ..\msvs_solution\x64\v142_Debug_MT\libtst.lib; 89 | } 90 | [x86,v142,release,MultiThreadedDLL] { 91 | lib: ..\msvs_solution\v142_Release_MD\libtst.lib; 92 | } 93 | [x86,v142,debug,MultiThreadedDebugDLL] { 94 | lib: ..\msvs_solution\v142_Debug_MD\libtst.lib; 95 | } 96 | [x64,v142,release,MultiThreadedDLL] { 97 | lib: ..\msvs_solution\x64\v142_Release_MD\libtst.lib; 98 | } 99 | [x64,v142,debug,MultiThreadedDebugDLL] { 100 | lib: ..\msvs_solution\x64\v142_Debug_MD\libtst.lib; 101 | } 102 | */ 103 | //==== v143 tools ==== 104 | 105 | [x86,v143,release,MultiThreaded] { 106 | lib: ..\msvs_solution\v143_Release_MT\libtst.lib; 107 | } 108 | [x86,v143,debug,MultiThreadedDebug] { 109 | lib: ..\msvs_solution\v143_Debug_MT\libtst.lib; 110 | } 111 | [x64,v143,release,MultiThreaded] { 112 | lib: ..\msvs_solution\x64\v143_Release_MT\libtst.lib; 113 | } 114 | [x64,v143,debug,MultiThreadedDebug] { 115 | lib: ..\msvs_solution\x64\v143_Debug_MT\libtst.lib; 116 | } 117 | [x86,v143,release,MultiThreadedDLL] { 118 | lib: ..\msvs_solution\v143_Release_MD\libtst.lib; 119 | } 120 | [x86,v143,debug,MultiThreadedDebugDLL] { 121 | lib: ..\msvs_solution\v143_Debug_MD\libtst.lib; 122 | } 123 | [x64,v143,release,MultiThreadedDLL] { 124 | lib: ..\msvs_solution\x64\v143_Release_MD\libtst.lib; 125 | } 126 | [x64,v143,debug,MultiThreadedDebugDLL] { 127 | lib: ..\msvs_solution\x64\v143_Debug_MD\libtst.lib; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /pkg-config/makefile: -------------------------------------------------------------------------------- 1 | include prorab.mk 2 | include prorab-pkg-config.mk 3 | 4 | $(eval $(prorab-pkg-config)) 5 | -------------------------------------------------------------------------------- /pkg-config/tst.pc.in: -------------------------------------------------------------------------------- 1 | Name: tst # human-readable name 2 | Description: C++ testing framework # human-readable description 3 | Version: $(version) 4 | URL: https://github.com/cppfw/tst 5 | Requires: 6 | Conflicts: 7 | Libs: -ltst -rdynamic 8 | Libs.private: 9 | Cflags: 10 | -------------------------------------------------------------------------------- /src/makefile: -------------------------------------------------------------------------------- 1 | include prorab.mk 2 | include prorab-license.mk 3 | include prorab-clang-format.mk 4 | 5 | $(eval $(call prorab-config, ../config)) 6 | 7 | this_name := tst 8 | 9 | this_soname := $(shell cat $(d)soname.txt) 10 | 11 | this_srcs := $(call prorab-src-dir, .) 12 | 13 | this_ldlibs += -lclargs -lutki 14 | 15 | ifeq ($(this_tst_no_par),true) 16 | this_cxxflags += -D TST_NO_PAR 17 | this_srcs := $(filter-out tst/runner.cpp tst/runners_pool.cpp,$(this_srcs)) 18 | else 19 | this_ldlibs += -lnitki -lopros 20 | endif 21 | 22 | $(eval $(prorab-build-lib)) 23 | 24 | $(eval $(prorab-clang-format)) 25 | 26 | this_src_dir := 27 | this_license_file := ../LICENSE 28 | $(eval $(prorab-license)) 29 | -------------------------------------------------------------------------------- /src/soname.txt: -------------------------------------------------------------------------------- 1 | 0 -------------------------------------------------------------------------------- /src/tst/application.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #include "application.hpp" 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #ifndef TST_NO_PAR 35 | # include 36 | #endif 37 | 38 | #include "iterator.hxx" 39 | #include "reporter.hxx" 40 | #include "set.hpp" 41 | #include "settings.hxx" 42 | #include "util.hxx" 43 | 44 | #ifndef TST_NO_PAR 45 | # include "runners_pool.hxx" 46 | #endif 47 | 48 | using namespace std::string_literals; 49 | using namespace std::string_view_literals; 50 | 51 | using namespace tst; 52 | 53 | application_factory::factory_type& application_factory::get_factory() 54 | { 55 | static application_factory::factory_type f; 56 | return f; 57 | } 58 | 59 | application_factory::application_factory(factory_type factory) 60 | { 61 | auto& f = this->get_factory(); 62 | if (f) { 63 | throw std::logic_error("application factory is already registered"); 64 | } 65 | f = std::move(factory); 66 | } 67 | 68 | application::application(std::string name, std::string description) : 69 | name(std::move(name)), 70 | description(std::move(description)) 71 | { 72 | this->cli.add("help", "display help information", []() { 73 | tst::settings::inst().show_help = true; 74 | }); 75 | this->cli.add( 76 | 'j', 77 | "jobs", 78 | "Number of parallel jobs. Possible values:" 79 | "\n" 80 | " positive non-zero number = number of concurrent jobs." 81 | "\n" 82 | " max = unlimited number of concurrent jobs." 83 | "\n" 84 | " auto = number of physical threads supported by the system." 85 | "\n" 86 | "Default value is 1.", 87 | [](std::string_view v) { 88 | #ifndef TST_NO_PAR 89 | auto& s = tst::settings::inst(); 90 | if (v == "auto") { 91 | s.num_threads = std::thread::hardware_concurrency(); 92 | } else if (v == "max") { 93 | s.num_threads = std::numeric_limits::max(); 94 | } else { 95 | s.num_threads = utki::string_parser(v).read_number(); 96 | if (s.num_threads == 0) { 97 | throw std::invalid_argument("--jobs argument value must not be 0"); 98 | } 99 | } 100 | #endif 101 | } 102 | ); 103 | this->cli.add("junit-out", "Output filename of the test report in JUnit format.", [](std::string_view v) { 104 | tst::settings::inst().junit_report_out_file = v; 105 | }); 106 | this->cli.add('l', "list-tests", "List all tests without running them.", []() { 107 | tst::settings::inst().list_tests = true; 108 | }); 109 | this->cli.add( 110 | "about-to-run", 111 | "Print name of the test about to run. By default, before the " 112 | "test is run its name is not printed.", 113 | []() { 114 | settings::inst().print_about_to_run = true; 115 | } 116 | ); 117 | this->cli.add( 118 | "passed", 119 | "Print passed test name to stdout. By default, when test has " 120 | "passed, nothing is printed to stdout.", 121 | []() { 122 | settings::inst().print_passed = true; 123 | } 124 | ); 125 | this->cli.add( 126 | "skipped", 127 | "Print skipped test name to stdout. By default, when test has " 128 | "been skipped, nothing is printed to stdout.", 129 | []() { 130 | settings::inst().print_skipped = true; 131 | } 132 | ); 133 | this->cli.add( 134 | "outcome", 135 | "Print overall testing outcome. By default, overall testing " 136 | "outcome is not printed.", 137 | []() { 138 | settings::inst().print_outcome = true; 139 | } 140 | ); 141 | this->cli.add("no-color", "Do not use output coloring even if running from terminal.", []() { 142 | settings::inst().colored_output = false; 143 | }); 144 | this->cli.add("run-disabled", "Run disabled tests as well.", []() { 145 | settings::inst().run_disabled = true; 146 | }); 147 | this->cli.add("run-list-stdin", "Get list of tests to run from stdin.", []() { 148 | settings::inst().run_list_stdin = true; 149 | }); 150 | this->cli.add("suite", "Run only specified test suite", [](std::string_view s) { 151 | settings::inst().suite_name = s; 152 | }); 153 | this->cli 154 | .add("test", "Run only specified test case from the test suite specified via --suite.", [](std::string_view s) { 155 | settings::inst().test_name = s; 156 | }); 157 | } 158 | 159 | void application::print_help() const 160 | { 161 | if (name.empty()) { 162 | std::cout << "Unit tests implemented using 'tst' testing framework." << std::endl; 163 | } else { 164 | std::cout << name << std::endl; 165 | } 166 | if (!description.empty()) { 167 | std::cout << description << std::endl; 168 | } 169 | std::cout << std::endl; 170 | std::cout << "options:" << std::endl; 171 | std::cout << cli.description(); 172 | } 173 | 174 | size_t application::num_tests() const noexcept 175 | { 176 | size_t ret = 0; 177 | 178 | for (const auto& s : this->suites) { 179 | ret += s.second.size(); 180 | } 181 | 182 | return ret; 183 | } 184 | 185 | size_t application::run_list_size() const noexcept 186 | { 187 | size_t ret = 0; 188 | 189 | for (const auto& s : this->run_list) { 190 | ret += s.second.size(); 191 | } 192 | 193 | return ret; 194 | } 195 | 196 | suite& application::get_suite(std::string_view id) 197 | { 198 | validate_id(id); 199 | 200 | auto i = this->suites.emplace(id, suite()); 201 | return i.first->second; 202 | } 203 | 204 | void application::list_tests(std::ostream& o) const 205 | { 206 | for (const auto& s : this->suites) { 207 | o << s.first << '\n'; 208 | for (const auto& t : s.second.tests) { 209 | o << '\t' << t.first << '\n'; 210 | } 211 | } 212 | } 213 | 214 | namespace { 215 | void print_test_name(std::ostream& o, const full_id& id) 216 | { 217 | if (settings::inst().colored_output) { 218 | o << "\033[2;36m" << id.suite << "\033[0m \033[0;36m" << id.test << "\033[0m"; 219 | } else { 220 | o << id.suite << " " << id.test; 221 | } 222 | o << '\n'; 223 | } 224 | } // namespace 225 | 226 | namespace { 227 | void print_test_name_about_to_run(std::ostream& o, const full_id& id) 228 | { 229 | if (!settings::inst().print_about_to_run) { 230 | return; 231 | } 232 | std::stringstream ss; 233 | if (settings::inst().colored_output) { 234 | ss << "\033[1;33mrun\033[0m: "; 235 | } else { 236 | ss << "run: "; 237 | } 238 | print_test_name(ss, id); 239 | o << ss.str(); 240 | } 241 | } // namespace 242 | 243 | namespace { 244 | void print_disabled_test_name(std::ostream& o, const full_id& id) 245 | { 246 | std::stringstream ss; 247 | if (settings::inst().colored_output) { 248 | ss << "\033[0;33mdisabled\033[0m: "; 249 | } else { 250 | ss << "disabled: "; 251 | } 252 | print_test_name(ss, id); 253 | o << ss.str(); 254 | } 255 | } // namespace 256 | 257 | namespace { 258 | void print_skipped_test_name(std::ostream& o, const full_id& id) 259 | { 260 | if (!settings::inst().print_skipped) { 261 | return; 262 | } 263 | std::stringstream ss; 264 | if (settings::inst().colored_output) { 265 | ss << "\033[1;90mskipped\033[0m: "; 266 | } else { 267 | ss << "skipped: "; 268 | } 269 | print_test_name(ss, id); 270 | o << ss.str(); 271 | } 272 | } // namespace 273 | 274 | namespace { 275 | void print_failed_test_name(std::ostream& o, const full_id& id) 276 | { 277 | if (settings::inst().colored_output) { 278 | o << "\033[1;31mfailed\033[0m: "; 279 | } else { 280 | o << "failed: "; 281 | } 282 | print_test_name(o, id); 283 | } 284 | } // namespace 285 | 286 | namespace { 287 | void print_passed_test_name(std::ostream& o, const full_id& id) 288 | { 289 | if (!settings::inst().print_passed) { 290 | return; 291 | } 292 | std::stringstream ss; 293 | if (settings::inst().colored_output) { 294 | ss << "\033[1;32mpassed\033[0m: "; 295 | } else { 296 | ss << "passed: "; 297 | } 298 | print_test_name(ss, id); 299 | o << ss.str(); 300 | } 301 | } // namespace 302 | 303 | namespace { 304 | void print_error_info(std::ostream& o, const tst::check_failed& e, bool color = settings::inst().colored_output) 305 | { 306 | o << e.source_location.file_name() << ":" << e.source_location.line(); 307 | if (color) { 308 | o << ": \033[1;31merror\033[0m: "; 309 | } else { 310 | o << ": error: "; 311 | } 312 | o << e.message; 313 | } 314 | } // namespace 315 | 316 | namespace { 317 | void run_test(const full_id& id, const std::function& proc, reporter& rep, bool no_catch = false) 318 | { 319 | print_test_name_about_to_run(std::cout, id); 320 | 321 | std::string console_error_message; 322 | 323 | ASSERT(proc) 324 | uint32_t start_ticks = utki::get_ticks_ms(); 325 | 326 | auto run_proc = [&]() -> bool { 327 | try { 328 | proc(); 329 | uint32_t dt = utki::get_ticks_ms() - start_ticks; 330 | rep.report_pass(id, dt); 331 | 332 | print_passed_test_name(std::cout, id); 333 | return true; 334 | } catch (tst::check_failed& e) { 335 | uint32_t dt = utki::get_ticks_ms() - start_ticks; 336 | { 337 | std::stringstream ss; 338 | print_error_info(ss, e); 339 | console_error_message = ss.str(); 340 | } 341 | { 342 | std::stringstream ss; 343 | print_error_info(ss, e, false); 344 | rep.report_failure(id, dt, ss.str()); 345 | } 346 | } 347 | return false; 348 | }; 349 | 350 | if (no_catch) { 351 | if (run_proc()) { 352 | return; 353 | } 354 | } else { 355 | try { 356 | if (run_proc()) { 357 | return; 358 | } 359 | } catch (std::exception& e) { 360 | uint32_t dt = utki::get_ticks_ms() - start_ticks; 361 | std::stringstream ss; 362 | ss << " uncaught exception:\n"sv << utki::to_string(e, " "sv); 363 | console_error_message = ss.str(); 364 | rep.report_error(id, dt, std::string(console_error_message)); 365 | } catch (...) { 366 | uint32_t dt = utki::get_ticks_ms() - start_ticks; 367 | std::stringstream ss; 368 | ss << " uncaught exception:\n"sv << utki::current_exception_to_string(" "sv); 369 | console_error_message = ss.str(); 370 | rep.report_error(id, dt, std::string(console_error_message)); 371 | } 372 | } 373 | 374 | print_failed_test_name(std::cout, id); 375 | std::cout << console_error_message << '\n'; 376 | } 377 | } // namespace 378 | 379 | #ifndef TST_NO_PAR 380 | namespace { 381 | const auto main_thread_id = std::this_thread::get_id(); 382 | } // namespace 383 | #endif 384 | 385 | int application::run() 386 | { 387 | if (this->num_tests() == 0) { 388 | std::cout << "no tests to run" << std::endl; 389 | return 0; 390 | } 391 | 392 | #ifndef TST_NO_PAR 393 | // set up queue for the main thread 394 | opros::wait_set wait_set(1); 395 | nitki::queue queue; 396 | wait_set.add(queue, {opros::ready::read}, &queue); 397 | utki::scope_exit queue_scope_exit([&wait_set, &queue]() { 398 | wait_set.remove(queue); 399 | }); 400 | 401 | runners_pool pool; 402 | #endif 403 | 404 | reporter rep(*this); 405 | 406 | rep.print_num_tests_about_to_run(std::cout); 407 | 408 | bool is_single_test = !settings::inst().test_name.empty(); 409 | 410 | ASSERT(!is_single_test || (this->run_list.size() == 1 && this->run_list.begin()->second.size() == 1)) 411 | 412 | // TODO: add timeout 413 | 414 | uint32_t start_ticks = utki::get_ticks_ms(); 415 | 416 | std::vector no_parallel_tests; 417 | 418 | for (iterator i(this->suites); true;) { 419 | if (i.is_valid()) { 420 | utki::scope_exit iter_next_scope_exit([&i]() { 421 | i.next(); 422 | }); 423 | 424 | auto id = i.id(); 425 | if (!this->is_in_run_list(id.suite, id.test)) { 426 | print_skipped_test_name(std::cout, id); 427 | rep.report_skipped(id, "not in run list"); 428 | continue; 429 | } 430 | 431 | if (i.info().flags.get(flag::disabled)) { 432 | print_disabled_test_name(std::cout, id); 433 | rep.report_disabled_test(id); 434 | continue; 435 | } 436 | 437 | if (settings::inst().num_threads > 1) { 438 | if (i.info().flags.get(flag::no_parallel)) { 439 | no_parallel_tests.push_back(i); 440 | continue; 441 | } 442 | } 443 | 444 | auto& proc = i.info().proc; 445 | ASSERT(proc) 446 | 447 | if (is_single_test) { 448 | // when running a single test indicated by --test command line option we 449 | // don't want to catch exceptions to allow debugger show the correct 450 | // stack trace 451 | run_test( 452 | id, 453 | proc, 454 | rep, 455 | true // no exception catching 456 | ); 457 | } else { 458 | #ifndef TST_NO_PAR 459 | auto r = pool.occupy_runner(); 460 | if (r) { 461 | auto reply = [&pool, r]() { 462 | ASSERT(std::this_thread::get_id() == main_thread_id) 463 | pool.free_runner(r); 464 | }; 465 | 466 | r->push_back([id, &proc, &rep, &queue, reply = std::move(reply)]() { 467 | run_test(id, proc, rep); 468 | queue.push_back(std::move(reply)); 469 | }); 470 | continue; 471 | } 472 | iter_next_scope_exit.release(); 473 | #else 474 | run_test(id, proc, rep); 475 | #endif 476 | } 477 | } else 478 | #ifndef TST_NO_PAR 479 | if (pool.no_active_runners()) 480 | #endif 481 | { 482 | // no tests left and no active runners 483 | break; 484 | } 485 | 486 | #ifndef TST_NO_PAR 487 | if (!is_single_test) { 488 | // no free runners, or no tests left, wait on the queue 489 | wait_set.wait(); 490 | ASSERT(wait_set.get_triggered().size() == 1) 491 | auto f = queue.pop_front(); 492 | ASSERT(f) 493 | f(); 494 | } 495 | #endif 496 | } // ~main loop 497 | 498 | // non-parallel run loop 499 | for (const auto& i : no_parallel_tests) { 500 | run_test(i.id(), i.info().proc, rep); 501 | } 502 | 503 | rep.time_ms = utki::get_ticks_ms() - start_ticks; 504 | 505 | rep.print_num_tests_run(std::cout); 506 | rep.print_num_tests_passed(std::cout); 507 | rep.print_num_tests_disabled(std::cout); 508 | rep.print_num_tests_skipped(std::cout); 509 | rep.print_num_tests_failed(std::cout); 510 | rep.print_num_warnings(std::cout); 511 | rep.print_outcome(std::cout); 512 | 513 | #ifndef TST_NO_PAR 514 | pool.stop_all_runners(); 515 | #endif 516 | 517 | { 518 | auto& junit_file = settings::inst().junit_report_out_file; 519 | if (!junit_file.empty()) { 520 | rep.write_junit_report(junit_file); 521 | } 522 | } 523 | 524 | return rep.is_failed() ? 1 : 0; 525 | } 526 | 527 | bool application::is_in_run_list(const std::string& suite, const std::string& test) const 528 | { 529 | if (this->run_list.empty()) { 530 | return true; 531 | } 532 | auto si = this->run_list.find(suite); 533 | if (si == this->run_list.end()) { 534 | return false; 535 | } 536 | const auto& set = si->second; 537 | if (!set.empty()) { 538 | auto ti = set.find(test); 539 | if (ti == set.end()) { 540 | return false; 541 | } 542 | } 543 | return true; 544 | } 545 | 546 | namespace { 547 | void skip_indentation(std::istream& is) 548 | { 549 | if (is.eof()) { 550 | return; 551 | } 552 | auto c = is.peek(); 553 | while (!is.eof() && (c == ' ' || c == '\t')) { 554 | is.get(); 555 | c = is.peek(); 556 | } 557 | } 558 | } // namespace 559 | 560 | namespace { 561 | void skip_to_new_line(std::istream& is) 562 | { 563 | if (is.eof()) { 564 | return; 565 | } 566 | auto c = is.get(); 567 | while (!is.eof() && c != '\n') { 568 | c = is.get(); 569 | } 570 | } 571 | } // namespace 572 | 573 | namespace { 574 | std::string read_in_name(std::istream& is) 575 | { 576 | if (is.eof()) { 577 | return {}; 578 | } 579 | std::stringstream ss; 580 | auto c = char(is.peek()); 581 | while (!is.eof() && is_valid_id_char(c)) { 582 | ss << char(is.get()); 583 | c = char(is.peek()); 584 | } 585 | return ss.str(); 586 | } 587 | } // namespace 588 | 589 | namespace { 590 | void throw_syntax_error_invalid_char(size_t line, char c) 591 | { 592 | std::stringstream ss; 593 | ss << "error in run list syntax at line: " << line << ": invalid character 0x" << std::hex << unsigned(c); 594 | throw std::invalid_argument(ss.str()); 595 | } 596 | } // namespace 597 | 598 | void application::read_run_list_from_stdin() 599 | { 600 | if (utki::is_terminal_cin()) { 601 | return; 602 | } 603 | 604 | bool expect_test_name = false; 605 | 606 | std::string_view cur_suite_name; 607 | const suite* cur_suite = nullptr; 608 | decltype(this->run_list)::value_type::second_type* cur_run_list_suite = nullptr; 609 | 610 | std::istream& is = std::cin; 611 | 612 | size_t line = 0; 613 | 614 | for (auto c = char(is.peek()); !is.eof(); c = char(is.peek())) { 615 | // LOG([&](auto&o){o << "line = " << line << " c = " << c << '\n';}) 616 | ASSERT(!is.eof()) 617 | ASSERT(!is.fail()) 618 | 619 | if (expect_test_name) { 620 | switch (c) { 621 | case '\r': 622 | case '\n': 623 | case '#': 624 | break; 625 | default: 626 | skip_indentation(is); 627 | if (is_valid_id_char(c)) { 628 | auto tn = read_in_name(is); 629 | // LOG([&](auto&o){o << "test parsed: " << tn << '\n';}) 630 | if (!cur_suite) { 631 | throw std::invalid_argument( 632 | "encountered test name while no test " 633 | "suite name supplied before" 634 | ); 635 | } 636 | 637 | auto i = cur_suite->tests.find(tn); 638 | if (i == cur_suite->tests.end()) { 639 | std::stringstream ss; 640 | ss << "test '" << tn << "' not found in suite '" << cur_suite_name << '\''; 641 | throw std::invalid_argument(ss.str()); 642 | } 643 | ASSERT(cur_run_list_suite) 644 | cur_run_list_suite->insert(std::string_view(i->first)); 645 | } else { 646 | throw_syntax_error_invalid_char(line, c); 647 | } 648 | break; 649 | } 650 | skip_to_new_line(is); 651 | ++line; 652 | expect_test_name = false; 653 | } else { 654 | switch (c) { 655 | case '\n': 656 | case '\r': 657 | case '#': 658 | skip_to_new_line(is); 659 | ++line; 660 | break; 661 | default: 662 | if (is_valid_id_char(c)) { 663 | auto sn = read_in_name(is); 664 | // LOG([&](auto&o){o << "suite parsed: " << sn << '\n';}) 665 | 666 | auto i = this->suites.find(sn); 667 | if (i == this->suites.end()) { 668 | std::stringstream ss; 669 | ss << "suite '" << sn << "' not found"; 670 | throw std::invalid_argument(ss.str()); 671 | } 672 | cur_suite_name = std::string_view(i->first); 673 | cur_suite = &i->second; 674 | cur_run_list_suite = &this->run_list[cur_suite_name]; 675 | } else { 676 | throw_syntax_error_invalid_char(line, c); 677 | } 678 | // fall-through 679 | case ' ': 680 | case '\t': 681 | skip_indentation(is); 682 | expect_test_name = true; 683 | break; 684 | } 685 | } 686 | } 687 | } 688 | 689 | void application::set_run_list_from_suite_and_test_name() 690 | { 691 | const auto& suite_name = settings::inst().suite_name; 692 | auto i = this->suites.find(suite_name); 693 | if (i == this->suites.end()) { 694 | std::stringstream ss; 695 | ss << "Test suite --suite=" << suite_name << " not found"; 696 | throw std::invalid_argument(ss.str()); 697 | } 698 | auto& set = this->run_list[i->first]; // this will add the test suite to the run list 699 | 700 | const auto& test_name = settings::inst().test_name; 701 | if (!test_name.empty()) { 702 | const auto& tests = i->second.tests; 703 | auto j = tests.find(test_name); 704 | if (j == tests.end()) { 705 | std::stringstream ss; 706 | ss << "Test case --suite=" << suite_name << " --test=" << test_name << " not found"; 707 | throw std::invalid_argument(ss.str()); 708 | } 709 | 710 | set.insert(j->first); 711 | } 712 | } 713 | 714 | void application::init() 715 | { 716 | for (const auto& i : set::get_inits()) { 717 | auto& s = this->get_suite(i.first); 718 | auto old_size = s.size(); 719 | for (const auto& p : i.second) { 720 | ASSERT(p) 721 | p(s); 722 | } 723 | if (old_size == s.size()) { 724 | std::stringstream ss; 725 | ss << "some test set for suite '" << i.first << "' is empty"; 726 | ++this->num_warnings; 727 | print_warning(std::cout, ss.str()); 728 | } 729 | } 730 | 731 | set::get_inits().clear(); // we don't need that anymore 732 | 733 | // check for empty suites 734 | for (const auto& s : this->suites) { 735 | if (s.second.size() == 0) { 736 | ++this->num_warnings; 737 | print_warning(std::cout, std::string("suite '") + s.first + "' is empty"); 738 | } 739 | } 740 | } 741 | -------------------------------------------------------------------------------- /src/tst/application.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | 38 | #include "suite.hpp" 39 | 40 | namespace tst { 41 | 42 | /** 43 | * @brief Test runner application. 44 | * The application class provides an entry point for the test program. 45 | * In case one needs more flexibility on how the test program is being 46 | * started/finalized then one can subclass the application class and override 47 | * some of it's behaviour. In that case, one also has to provide the 48 | * tst::create_application() factory function which will construct the 49 | * application instance. 50 | */ 51 | class application 52 | { 53 | // unexpected exceptions are not caught 54 | // NOLINTNEXTLINE(bugprone-exception-escape) 55 | friend int main(utki::span args); 56 | 57 | friend class reporter; 58 | friend class iterator; 59 | 60 | const std::string name; 61 | const std::string description; 62 | 63 | std::unordered_map suites; 64 | 65 | std::unordered_map> run_list; 66 | 67 | bool is_in_run_list(const std::string& suite, const std::string& test) const; 68 | 69 | void print_help() const; 70 | 71 | int run(); 72 | 73 | size_t num_tests() const noexcept; 74 | 75 | size_t run_list_size() const noexcept; 76 | 77 | void list_tests(std::ostream& o) const; 78 | 79 | void read_run_list_from_stdin(); 80 | void set_run_list_from_suite_and_test_name(); 81 | 82 | size_t num_warnings = 0; 83 | 84 | public: 85 | /** 86 | * @brief Constructor. 87 | * @param name - the test application name. 88 | * This name will be shown in the test application's help 89 | * output. 90 | * @param description - description of the test application. 91 | * This description will be shown in the test 92 | * application's help output. 93 | */ 94 | application(std::string name = std::string(), std::string description = std::string()); 95 | 96 | application(const application&) = delete; 97 | application& operator=(const application&) = delete; 98 | 99 | application(application&&) = delete; 100 | application& operator=(application&&) = delete; 101 | 102 | virtual ~application() = default; 103 | 104 | /** 105 | * @brief Initialize test cases. 106 | * This function is called by tst right after command line arguments have been 107 | * parsed. The default implementation of the function adds test cases to the 108 | * application from all tst::set instances. 109 | */ 110 | virtual void init(); 111 | 112 | /** 113 | * @brief Command line arguments parser. 114 | * This is the instance of the command line arguments parser. 115 | * Command line arguments parsing is invoked by tst right after the 116 | * application instance is created. So, in case one needs to add some custom 117 | * command line arguments to the parser, then one can do it from within the 118 | * constructor of the application subclass. 119 | */ 120 | clargs::parser cli; 121 | 122 | /** 123 | * @brief Get test suite. 124 | * This function tries to search for the test suite with the given id. 125 | * In case the suite is not found, it will be created. 126 | * @param id - the id of the test suite to get. 127 | * @return reference to the test suite with the requested id. 128 | */ 129 | suite& get_suite(std::string_view id); 130 | }; 131 | 132 | /** 133 | * @brief Application factory registerer. 134 | * The object of this class registers the application factory function. 135 | * The application object will be constructed using the provided factory 136 | * function at program start. 137 | */ 138 | class application_factory 139 | { 140 | // unexpected exceptions are not caught 141 | // NOLINTNEXTLINE(bugprone-exception-escape) 142 | friend int main(utki::span args); 143 | 144 | public: 145 | using factory_type = std::function()>; 146 | 147 | /** 148 | * @brief Constructor. 149 | * Registers the application object factory function. 150 | * Only one application factory can be registered. 151 | * @param factory - application factory function. 152 | * @throw std::logic_error - in case a factory is already registered. 153 | */ 154 | application_factory(factory_type factory); 155 | 156 | private: 157 | static factory_type& get_factory(); 158 | }; 159 | 160 | } // namespace tst 161 | -------------------------------------------------------------------------------- /src/tst/check.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #include "check.hpp" 28 | 29 | #include "util.hxx" 30 | 31 | using namespace tst; 32 | 33 | namespace { 34 | const char* const default_fail_message = "check(false)"; 35 | } // namespace 36 | 37 | void tst::check(bool c, const std::function& print, utki::source_location&& source_location) 38 | { 39 | if (c) { 40 | return; 41 | } 42 | 43 | std::stringstream ss; 44 | 45 | if (print) { 46 | print(ss); 47 | } else { 48 | ss << default_fail_message; 49 | } 50 | 51 | throw check_failed(ss.str(), std::move(source_location)); 52 | } 53 | 54 | // TODO: why lint complains about it on macos? 55 | // "error: an exception may be thrown" 56 | // NOLINTNEXTLINE(bugprone-exception-escape) 57 | check_result::check_result(check_result&& cr) noexcept : 58 | failed(cr.failed), 59 | source_location(std::move(cr.source_location)), 60 | ss(std::move(cr.ss)) 61 | { 62 | cr.failed = false; 63 | } 64 | 65 | // TODO: remove lint suppression when 66 | // https://github.com/llvm/llvm-project/issues/55143 is resolved 67 | // NOLINTNEXTLINE(bugprone-exception-escape) 68 | check_result::~check_result() noexcept(false) 69 | { 70 | if (!this->failed) { 71 | return; 72 | } 73 | 74 | std::string message; 75 | try { 76 | message = this->ss.str(); 77 | if (message.empty()) { 78 | message = default_fail_message; 79 | } 80 | // ignore possible exceptions when building message string 81 | // NOLINTNEXTLINE(bugprone-empty-catch) 82 | } catch (...) { 83 | } 84 | throw check_failed(std::move(message), std::move(this->source_location)); 85 | } 86 | 87 | check_result tst::check(bool c, utki::source_location&& source_location) 88 | { 89 | #ifdef DEBUG 90 | // This piece of code is just to test move constructor of check_result, 91 | // we do it here instead of writing normal tst tests because check_result 92 | // has private constructors in order to prevent misuse from user side, 93 | // because check_result throws from destructor and should not be normally 94 | // constructed by users explicitly to prevent unexpected behavior. 95 | { 96 | check_result cr(std::move(source_location)); 97 | ASSERT(cr.failed) 98 | 99 | check_result mcr{std::move(cr)}; 100 | ASSERT(mcr.failed) 101 | ASSERT(!cr.failed) 102 | 103 | mcr.failed = false; 104 | } 105 | #endif 106 | 107 | if (c) { 108 | return {}; 109 | } 110 | 111 | return std::move(source_location); 112 | } 113 | -------------------------------------------------------------------------------- /src/tst/check.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | namespace tst { 37 | 38 | /** 39 | * @brief Check for condition with additional failure information. 40 | * The function checks for the condition to be true. 41 | * In case the condition is true, the function does nothing. 42 | * In case the condition is false, the function prepares a failure message by 43 | * calling the provided function and throws an exception containing the failure 44 | * information. 45 | * @param c - condition to check. 46 | * @param print - function performing output of addional failure message 47 | * information. 48 | * @param source_location - object with source file:line information. 49 | */ 50 | void check( 51 | bool c, 52 | const std::function& print, 53 | utki::source_location&& source_location 54 | #if CFG_CPP >= 20 55 | = utki::std_source_location::current() 56 | #endif 57 | ); 58 | 59 | /** 60 | * @brief Template check() function for any type convertible to bool. 61 | * This template converts the given value to boolean and then passes it to 62 | * check(bool, print, source_location) overload. 63 | * @param p - value to convert to boolean and check for true-value. 64 | * @param print - function performing output of addional failure message 65 | * information. 66 | * @param source_location - object with source file:line information. 67 | */ 68 | template 69 | void check( 70 | const check_type& p, 71 | const std::function& print, 72 | utki::source_location&& source_location 73 | #if CFG_CPP >= 20 74 | = utki::std_source_location::current() 75 | #endif 76 | ) 77 | { 78 | check(static_cast(p), print, std::move(source_location)); 79 | } 80 | 81 | /** 82 | * @brief Check result. 83 | * The object of this class is returned from check() functions which do not 84 | * have the 'print' function argument. This object can be used to insert 85 | * additional failure information in case check has failed. In case the object 86 | * holds failing check result, the object will throw a check failure exception 87 | * when it is destroyed. 88 | */ 89 | class check_result 90 | { 91 | friend check_result check(bool, utki::source_location&&); 92 | 93 | bool failed = false; 94 | utki::source_location source_location; 95 | std::stringstream ss; 96 | 97 | check_result() = default; 98 | 99 | check_result(utki::source_location&& source_location) : 100 | failed(true), 101 | source_location(std::move(source_location)) 102 | {} 103 | 104 | public: 105 | check_result(const check_result&) = delete; 106 | check_result& operator=(const check_result&) = delete; 107 | 108 | // the move constructor is almost same as default, except that it sets the 109 | // 'failed' field of the object moved from to false 110 | check_result(check_result&&) noexcept; 111 | 112 | check_result& operator=(check_result&&) = delete; 113 | 114 | /** 115 | * @brief Insert additional failure information. 116 | * The operator does nothing in case check has succeeded. 117 | * @param v - value to insert into the string stream. 118 | * @return reference to itself. 119 | */ 120 | template 121 | check_result& operator<<(const object_type& v) 122 | { 123 | if (this->failed) { 124 | // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) 125 | this->ss << v; 126 | } 127 | return *this; 128 | } 129 | 130 | // TODO: remove lint suppression when 131 | // https://github.com/llvm/llvm-project/issues/55143 is resolved 132 | // NOLINTNEXTLINE(bugprone-exception-escape) 133 | ~check_result() noexcept(false); 134 | }; 135 | 136 | /** 137 | * @brief Check for condition with additional failure information. 138 | * The function checks for the condition to be true. 139 | * @param c - condition to check. 140 | * @param source_location - object with source file:line information. 141 | * @return an instance of check_result. 142 | */ 143 | check_result check( 144 | bool c, 145 | utki::source_location&& source_location 146 | #if CFG_CPP >= 20 147 | = utki::std_source_location::current() 148 | #endif 149 | ); 150 | 151 | /** 152 | * @brief Template check() function for any type convertible to bool. 153 | * This template converts the given value to boolean and then passes it to 154 | * check(bool, source_location) overload. 155 | * @param p - value to convert to boolean and check for true-value. 156 | * @param source_location - object with source file:line information. 157 | */ 158 | template 159 | check_result check( 160 | const check_type& p, 161 | utki::source_location&& source_location 162 | #if CFG_CPP >= 20 163 | = utki::std_source_location::current() 164 | #endif 165 | ) 166 | { 167 | return check(static_cast(p), std::move(source_location)); 168 | } 169 | 170 | /** 171 | * @brief Check for equality. 172 | * This is a convenience function which checks for equality of two values. 173 | * Under the hood it calls to tst::check(), but also, it automatically prints 174 | * the input values in case of check failure. 175 | * @param a - fisrt value. 176 | * @param b - second value. 177 | * @param print - function performing output of addional failure message 178 | * information. 179 | * @param source_location - object with source file:line information. 180 | */ 181 | template 182 | void check_eq( 183 | const parameter_type& a, 184 | const parameter_type& b, 185 | const std::function& print, 186 | utki::source_location&& source_location 187 | #if CFG_CPP >= 20 188 | = utki::std_source_location::current() 189 | #endif 190 | ) 191 | { 192 | check( 193 | a == b, 194 | [&](auto& o) { 195 | o << "check_eq(" << a << ", " << b << ")"; 196 | if (print) { 197 | print(o); 198 | } 199 | }, 200 | std::move(source_location) 201 | ); 202 | } 203 | 204 | /** 205 | * @brief Check for equality. 206 | * This is a convenience function which checks for equality of two values. 207 | * Under the hood it calls to tst::check(), but also, it automatically prints 208 | * the input values in case of check failure. 209 | * @param a - fisrt value. 210 | * @param b - second value. 211 | * @param source_location - object with source file:line information. 212 | */ 213 | template 214 | check_result check_eq( 215 | const parameter_type& a, 216 | const parameter_type& b, 217 | utki::source_location&& source_location 218 | #if CFG_CPP >= 20 219 | = utki::std_source_location::current() 220 | #endif 221 | ) 222 | { 223 | auto ret = check(a == b, std::move(source_location)); 224 | ret << "check_eq(" << a << ", " << b << ")"; 225 | return ret; 226 | } 227 | 228 | /** 229 | * @brief Check for inequality. 230 | * This is a convenience function which checks for inequality of two values. 231 | * Under the hood it calls to tst::check(), but also, it automatically prints 232 | * the input values in case of check failure. 233 | * @param a - fisrt value. 234 | * @param b - second value. 235 | * @param print - function performing output of addional failure message 236 | * information. 237 | * @param source_location - object with source file:line information. 238 | */ 239 | template 240 | void check_ne( 241 | const parameter_type& a, 242 | const parameter_type& b, 243 | const std::function& print, 244 | utki::source_location&& source_location 245 | #if CFG_CPP >= 20 246 | = utki::std_source_location::current() 247 | #endif 248 | ) 249 | { 250 | check( 251 | a != b, 252 | [&](auto& o) { 253 | o << "check_ne(" << a << ", " << b << ")"; 254 | if (print) { 255 | print(o); 256 | } 257 | }, 258 | std::move(source_location) 259 | ); 260 | } 261 | 262 | /** 263 | * @brief Check for inequality. 264 | * This is a convenience function which checks for inequality of two values. 265 | * Under the hood it calls to tst::check(), but also, it automatically prints 266 | * the input values in case of check failure. 267 | * @param a - fisrt value. 268 | * @param b - second value. 269 | * @param source_location - object with source file:line information. 270 | */ 271 | template 272 | check_result check_ne( 273 | const parameter_type& a, 274 | const parameter_type& b, 275 | utki::source_location&& source_location 276 | #if CFG_CPP >= 20 277 | = utki::std_source_location::current() 278 | #endif 279 | ) 280 | { 281 | auto ret = check(a != b, std::move(source_location)); 282 | ret << "check_ne(" << a << ", " << b << ")"; 283 | return ret; 284 | } 285 | 286 | /** 287 | * @brief Check for less than. 288 | * This is a convenience function which checks for one value being less than 289 | * another value. Under the hood it calls to tst::check(), but also, it 290 | * automatically prints the input values in case of check failure. 291 | * @param a - fisrt value. 292 | * @param b - second value. 293 | * @param print - function performing output of addional failure message 294 | * information. 295 | * @param source_location - object with source file:line information. 296 | */ 297 | template 298 | void check_lt( 299 | const parameter_type& a, 300 | const parameter_type& b, 301 | const std::function& print, 302 | utki::source_location&& source_location 303 | #if CFG_CPP >= 20 304 | = utki::std_source_location::current() 305 | #endif 306 | ) 307 | { 308 | check( 309 | a < b, 310 | [&](auto& o) { 311 | o << "check_lt(" << a << ", " << b << ")"; 312 | if (print) { 313 | print(o); 314 | } 315 | }, 316 | std::move(source_location) 317 | ); 318 | } 319 | 320 | /** 321 | * @brief Check for less than. 322 | * This is a convenience function which checks for one value being less than 323 | * another value. Under the hood it calls to tst::check(), but also, it 324 | * automatically prints the input values in case of check failure. 325 | * @param a - fisrt value. 326 | * @param b - second value. 327 | * @param source_location - object with source file:line information. 328 | */ 329 | template 330 | check_result check_lt( 331 | const parameter_type& a, 332 | const parameter_type& b, 333 | utki::source_location&& source_location 334 | #if CFG_CPP >= 20 335 | = utki::std_source_location::current() 336 | #endif 337 | ) 338 | { 339 | auto ret = check(a < b, std::move(source_location)); 340 | ret << "check_lt(" << a << ", " << b << ")"; 341 | return ret; 342 | } 343 | 344 | /** 345 | * @brief Check for greater than. 346 | * This is a convenience function which checks for one value being greater than 347 | * another value. Under the hood it calls to tst::check(), but also, it 348 | * automatically prints the input values in case of check failure. 349 | * @param a - fisrt value. 350 | * @param b - second value. 351 | * @param print - function performing output of addional failure message 352 | * information. 353 | * @param source_location - object with source file:line information. 354 | */ 355 | template 356 | void check_gt( 357 | const parameter_type& a, 358 | const parameter_type& b, 359 | const std::function& print, 360 | utki::source_location&& source_location 361 | #if CFG_CPP >= 20 362 | = utki::std_source_location::current() 363 | #endif 364 | ) 365 | { 366 | check( 367 | a > b, 368 | [&](auto& o) { 369 | o << "check_gt(" << a << ", " << b << ")"; 370 | if (print) { 371 | print(o); 372 | } 373 | }, 374 | std::move(source_location) 375 | ); 376 | } 377 | 378 | /** 379 | * @brief Check for greater than. 380 | * This is a convenience function which checks for one value being greater than 381 | * another value. Under the hood it calls to tst::check(), but also, it 382 | * automatically prints the input values in case of check failure. 383 | * @param a - fisrt value. 384 | * @param b - second value. 385 | * @param source_location - object with source file:line information. 386 | */ 387 | template 388 | check_result check_gt( 389 | const parameter_type& a, 390 | const parameter_type& b, 391 | utki::source_location&& source_location 392 | #if CFG_CPP >= 20 393 | = utki::std_source_location::current() 394 | #endif 395 | ) 396 | { 397 | auto ret = check(a > b, std::move(source_location)); 398 | ret << "check_gt(" << a << ", " << b << ")"; 399 | return ret; 400 | } 401 | 402 | /** 403 | * @brief Check for less than or equal. 404 | * This is a convenience function which checks for one value being less than or 405 | * equal to another value. Under the hood it calls to tst::check(), but also, it 406 | * automatically prints the input values in case of check failure. 407 | * @param a - fisrt value. 408 | * @param b - second value. 409 | * @param print - function performing output of addional failure message 410 | * information. 411 | * @param source_location - object with source file:line information. 412 | */ 413 | template 414 | void check_le( 415 | const parameter_type& a, 416 | const parameter_type& b, 417 | const std::function& print, 418 | utki::source_location&& source_location 419 | #if CFG_CPP >= 20 420 | = utki::std_source_location::current() 421 | #endif 422 | ) 423 | { 424 | check( 425 | a <= b, 426 | [&](auto& o) { 427 | o << "check_le(" << a << ", " << b << ")"; 428 | if (print) { 429 | print(o); 430 | } 431 | }, 432 | std::move(source_location) 433 | ); 434 | } 435 | 436 | /** 437 | * @brief Check for less than or equal. 438 | * This is a convenience function which checks for one value being less than or 439 | * equal to another value. Under the hood it calls to tst::check(), but also, it 440 | * automatically prints the input values in case of check failure. 441 | * @param a - fisrt value. 442 | * @param b - second value. 443 | * @param source_location - object with source file:line information. 444 | */ 445 | template 446 | check_result check_le( 447 | const parameter_type& a, 448 | const parameter_type& b, 449 | utki::source_location&& source_location 450 | #if CFG_CPP >= 20 451 | = utki::std_source_location::current() 452 | #endif 453 | ) 454 | { 455 | auto ret = check(a <= b, std::move(source_location)); 456 | ret << "check_le(" << a << ", " << b << ")"; 457 | return ret; 458 | } 459 | 460 | /** 461 | * @brief Check for greater than or equal. 462 | * This is a convenience function which checks for one value being greater than 463 | * or equal to another value. Under the hood it calls to tst::check(), but also, 464 | * it automatically prints the input values in case of check failure. 465 | * @param a - fisrt value. 466 | * @param b - second value. 467 | * @param print - function performing output of addional failure message 468 | * information. 469 | * @param source_location - object with source file:line information. 470 | */ 471 | template 472 | void check_ge( 473 | const parameter_type& a, 474 | const parameter_type& b, 475 | const std::function& print, 476 | utki::source_location&& source_location 477 | #if CFG_CPP >= 20 478 | = utki::std_source_location::current() 479 | #endif 480 | ) 481 | { 482 | check( 483 | a >= b, 484 | [&](auto& o) { 485 | o << "check_ge(" << a << ", " << b << ")"; 486 | if (print) { 487 | print(o); 488 | } 489 | }, 490 | std::move(source_location) 491 | ); 492 | } 493 | 494 | /** 495 | * @brief Check for greater than or equal. 496 | * This is a convenience function which checks for one value being greater than 497 | * or equal to another value. Under the hood it calls to tst::check(), but also, 498 | * it automatically prints the input values in case of check failure. 499 | * @param a - fisrt value. 500 | * @param b - second value. 501 | * @param source_location - object with source file:line information. 502 | */ 503 | template 504 | check_result check_ge( 505 | const parameter_type& a, 506 | const parameter_type& b, 507 | utki::source_location&& source_location 508 | #if CFG_CPP >= 20 509 | = utki::std_source_location::current() 510 | #endif 511 | ) 512 | { 513 | auto ret = check(a >= b, std::move(source_location)); 514 | ret << "check_ge(" << a << ", " << b << ")"; 515 | return ret; 516 | } 517 | 518 | } // namespace tst 519 | -------------------------------------------------------------------------------- /src/tst/iterator.hxx: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #pragma once 28 | 29 | #include "application.hpp" 30 | #include "util.hxx" 31 | 32 | namespace tst { 33 | 34 | class iterator 35 | { 36 | const decltype(application::suites)& suites; 37 | 38 | decltype(application::suites)::const_iterator si; 39 | decltype(suite::tests)::const_iterator pi; 40 | 41 | void init_pi() 42 | { 43 | for (; this->si != this->suites.end(); ++this->si) { 44 | if (!this->si->second.tests.empty()) { 45 | this->pi = this->si->second.tests.begin(); 46 | return; 47 | } 48 | } 49 | } 50 | 51 | public: 52 | iterator(decltype(suites)& suites) : 53 | suites(suites), 54 | si(suites.begin()) 55 | { 56 | this->init_pi(); 57 | } 58 | 59 | bool is_valid() const 60 | { 61 | return this->si != this->suites.end(); 62 | } 63 | 64 | void next() 65 | { 66 | ASSERT(this->is_valid()) 67 | ++this->pi; 68 | if (this->pi == this->si->second.tests.end()) { 69 | ++this->si; 70 | this->init_pi(); 71 | } 72 | } 73 | 74 | const suite::test_info& info() const 75 | { 76 | ASSERT(this->is_valid()) 77 | return this->pi->second; 78 | } 79 | 80 | full_id id() const 81 | { 82 | ASSERT(this->is_valid()) 83 | // NOLINTNEXTLINE(modernize-use-designated-initializers) 84 | return {this->si->first, this->pi->first}; 85 | } 86 | }; 87 | 88 | } // namespace tst 89 | -------------------------------------------------------------------------------- /src/tst/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include "application.hpp" 32 | #include "settings.hxx" 33 | #include "util.hxx" 34 | 35 | namespace tst { 36 | 37 | // unexpected exceptions are not caught 38 | // NOLINTNEXTLINE(bugprone-exception-escape) 39 | int main(utki::span args) 40 | { 41 | settings settings_singleton; 42 | 43 | std::unique_ptr app; 44 | 45 | auto& factory = application_factory::get_factory(); 46 | if (factory) { 47 | app = factory(); 48 | 49 | if (!app) { 50 | throw std::logic_error("application_factory returned nullptr"); 51 | } 52 | } else { 53 | LOG([](auto& o) { 54 | o << "application_factory not found, creating basic application" << '\n'; 55 | }) 56 | app = std::make_unique(); 57 | } 58 | 59 | app->cli.parse(args); 60 | 61 | if (settings::inst().show_help) { 62 | app->print_help(); 63 | return 0; 64 | } 65 | 66 | app->init(); 67 | 68 | if (settings::inst().list_tests) { 69 | app->list_tests(std::cout); 70 | return 0; 71 | } 72 | 73 | if (!settings::inst().suite_name.empty()) { 74 | app->set_run_list_from_suite_and_test_name(); 75 | } else if (!settings::inst().test_name.empty()) { 76 | throw std::invalid_argument("--test argument requires --suite argument"); 77 | } else if (settings::inst().run_list_stdin) { 78 | app->read_run_list_from_stdin(); 79 | } 80 | 81 | return app->run(); 82 | } 83 | 84 | } // namespace tst 85 | 86 | // unexpected exceptions are not caught 87 | // NOLINTNEXTLINE(bugprone-exception-escape) 88 | int main(int argc, const char** argv) 89 | { 90 | return tst::main(utki::make_span(argv, argc)); 91 | } 92 | -------------------------------------------------------------------------------- /src/tst/reporter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #include "reporter.hxx" 28 | 29 | #include 30 | #include 31 | 32 | #include "settings.hxx" 33 | 34 | using namespace tst; 35 | 36 | void reporter::report(const full_id& id, suite::status result, uint32_t dt, std::string message) 37 | { 38 | std::lock_guardmutex)> lock_guard(this->mutex); 39 | 40 | auto si = this->app.suites.find(id.suite); 41 | ASSERT(si != this->app.suites.end()) 42 | 43 | auto& s = si->second; 44 | 45 | auto pi = s.tests.find(id.test); 46 | ASSERT(pi != s.tests.end()) 47 | 48 | auto& info = pi->second; 49 | 50 | info.result = result; 51 | info.time_ms = dt; 52 | info.message = std::move(message); 53 | 54 | switch (result) { 55 | case decltype(result)::passed: 56 | ++s.num_passed; 57 | ++this->num_passed; 58 | break; 59 | case decltype(result)::failed: 60 | ++s.num_failed; 61 | ++this->num_failed; 62 | break; 63 | case decltype(result)::errored: 64 | ++s.num_errors; 65 | ++this->num_errors; 66 | break; 67 | case decltype(result)::disabled: 68 | ++s.num_disabled; 69 | ++this->num_disabled; 70 | break; 71 | default: 72 | break; 73 | } 74 | } 75 | 76 | void reporter::print_num_tests_about_to_run(std::ostream& o) const 77 | { 78 | size_t actual_num = this->app.run_list_size(); 79 | if (actual_num == 0) { 80 | actual_num = this->num_tests; 81 | } 82 | 83 | if (tst::settings::inst().colored_output) { 84 | o << "\033[1;33;4mrunning\033[0m "; 85 | } else { 86 | o << "running "; 87 | } 88 | o << actual_num << " test(s)"; 89 | 90 | ASSERT(actual_num <= this->num_tests) 91 | 92 | if (actual_num != this->num_tests) { 93 | o << " out of " << this->num_tests; 94 | } 95 | o << std::endl; 96 | } 97 | 98 | void reporter::print_num_tests_run(std::ostream& o) const 99 | { 100 | o << "ran " << this->num_ran() << " test(s) in " << this->time_ms << " ms" << '\n'; 101 | } 102 | 103 | void reporter::print_num_tests_passed(std::ostream& o) const 104 | { 105 | if (settings::inst().colored_output) { 106 | o << "\033[1;32m" << this->num_passed << "\033[0m"; 107 | } else { 108 | o << this->num_passed; 109 | } 110 | o << " test(s) passed" << std::endl; 111 | } 112 | 113 | void reporter::print_num_tests_disabled(std::ostream& o) const 114 | { 115 | if (this->num_disabled == 0) { 116 | return; 117 | } 118 | 119 | if (settings::inst().colored_output) { 120 | std::cout << "\033[0;33m" << this->num_disabled << "\033[0m"; 121 | } else { 122 | std::cout << this->num_disabled; 123 | } 124 | std::cout << " test(s) disabled" << std::endl; 125 | } 126 | 127 | void reporter::print_num_tests_failed(std::ostream& o) const 128 | { 129 | size_t num = this->num_unsuccessful(); 130 | if (num == 0) { 131 | return; 132 | } 133 | 134 | if (settings::inst().colored_output) { 135 | std::cout << "\033[1;31m" << num << "\033[0m"; 136 | } else { 137 | std::cout << num; 138 | } 139 | std::cout << " test(s) failed" << std::endl; 140 | } 141 | 142 | void reporter::print_num_tests_skipped(std::ostream& o) const 143 | { 144 | size_t num = this->num_skipped(); 145 | if (num == 0) { 146 | return; 147 | } 148 | 149 | if (settings::inst().colored_output) { 150 | std::cout << "\033[1;90m" << num << "\033[0m"; 151 | } else { 152 | std::cout << num; 153 | } 154 | std::cout << " test(s) skipped" << std::endl; 155 | } 156 | 157 | void reporter::print_num_warnings(std::ostream& o) const 158 | { 159 | if (app.num_warnings == 0) { 160 | return; 161 | } 162 | 163 | if (tst::settings::inst().colored_output) { 164 | o << "\033[1;35m" << app.num_warnings << "\033[0m"; 165 | } else { 166 | o << app.num_warnings; 167 | } 168 | 169 | o << " warning(s)" << std::endl; 170 | } 171 | 172 | void reporter::print_outcome(std::ostream& o) const 173 | { 174 | if (!settings::inst().print_outcome) { 175 | return; 176 | } 177 | if (this->is_failed()) { 178 | // print FAILED word 179 | if (tst::settings::inst().colored_output) { 180 | o << "\t\033[1;31mFAILED\033[0m" << std::endl; 181 | } else { 182 | o << "\tFAILED" << std::endl; 183 | } 184 | } else { 185 | // print PASSED word 186 | if (tst::settings::inst().colored_output) { 187 | o << "\t\033[1;32mPASSED\033[0m" << std::endl; 188 | } else { 189 | o << "\tPASSED" << std::endl; 190 | } 191 | } 192 | } 193 | 194 | // See https://llg.cubic.org/docs/junit/ for junit report format 195 | void reporter::write_junit_report(const std::string& file_name) const 196 | { 197 | std::ofstream f(file_name, std::ios::binary); 198 | 199 | f << R"()" << '\n'; 200 | f << "" << '\n'; 221 | 222 | for (const auto& si : this->app.suites) { 223 | auto& s = si.second; 224 | f << "\t' << '\n'; 245 | 246 | for (const auto& ti : s.tests) { 247 | auto& t = ti.second; 248 | 249 | f << "\t\t' << '\n'; 264 | f << "\t\t\t<" << ([&]() { 265 | switch (t.result) { 266 | default: 267 | ASSERT(false) 268 | case suite::status::errored: 269 | return "error"; 270 | case suite::status::failed: 271 | return "failure"; 272 | case suite::status::not_run: 273 | return "skipped"; 274 | } 275 | }()) 276 | << " message='" << t.message << "'/>" << '\n'; 277 | f << "\t\t"; 278 | break; 279 | default: 280 | f << "/>"; 281 | } 282 | 283 | f << '\n'; 284 | } 285 | 286 | f << "\t" << '\n'; 287 | } 288 | 289 | f << "" << '\n'; 290 | f.flush(); 291 | } 292 | -------------------------------------------------------------------------------- /src/tst/reporter.hxx: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | 32 | #include "application.hpp" 33 | #include "suite.hpp" 34 | #include "util.hxx" 35 | 36 | namespace tst { 37 | class reporter 38 | { 39 | private: 40 | std::mutex mutex; 41 | const application& app; 42 | 43 | const size_t num_tests; 44 | 45 | size_t num_failed = 0; 46 | size_t num_passed = 0; 47 | size_t num_disabled = 0; 48 | size_t num_errors = 0; 49 | 50 | // thread safe 51 | void report(const full_id& id, suite::status result, uint32_t dt, std::string message = std::string()); 52 | 53 | public: 54 | uint32_t time_ms = 0; 55 | 56 | reporter(const application& app) : 57 | app(app), 58 | num_tests(app.num_tests()) 59 | {} 60 | 61 | // thread safe 62 | void report_pass(const full_id& id, uint32_t dt) 63 | { 64 | this->report(id, suite::status::passed, dt); 65 | } 66 | 67 | // thread safe 68 | void report_failure(const full_id& id, uint32_t dt, std::string message) 69 | { 70 | this->report(id, suite::status::failed, dt, std::move(message)); 71 | } 72 | 73 | // thread safe 74 | void report_error(const full_id& id, uint32_t dt, std::string message) 75 | { 76 | this->report(id, suite::status::errored, dt, std::move(message)); 77 | } 78 | 79 | // thread safe 80 | void report_skipped(const full_id& id, std::string message) 81 | { 82 | this->report(id, suite::status::not_run, 0, std::move(message)); 83 | } 84 | 85 | // thread safe 86 | void report_disabled_test(const full_id& id) 87 | { 88 | this->report(id, suite::status::disabled, 0); 89 | } 90 | 91 | size_t num_unsuccessful() const noexcept 92 | { 93 | return this->num_failed + this->num_errors; 94 | } 95 | 96 | size_t num_not_run() const noexcept 97 | { 98 | return this->num_disabled + this->num_skipped(); 99 | } 100 | 101 | size_t num_ran() const noexcept 102 | { 103 | return this->num_unsuccessful() + this->num_passed + this->num_disabled; 104 | } 105 | 106 | size_t num_skipped() const noexcept 107 | { 108 | ASSERT(this->num_tests >= this->num_ran()) 109 | return this->num_tests - this->num_ran(); 110 | } 111 | 112 | void print_num_tests_run(std::ostream& o) const; 113 | void print_num_tests_about_to_run(std::ostream& o) const; 114 | void print_num_tests_passed(std::ostream& o) const; 115 | void print_num_tests_disabled(std::ostream& o) const; 116 | void print_num_tests_failed(std::ostream& o) const; 117 | void print_num_tests_skipped(std::ostream& o) const; 118 | void print_num_warnings(std::ostream& o) const; 119 | void print_outcome(std::ostream& o) const; 120 | 121 | bool is_failed() const noexcept 122 | { 123 | return this->num_failed != 0 || this->num_errors != 0; 124 | } 125 | 126 | bool is_semi_passed() const noexcept 127 | { 128 | return !this->is_failed() && this->num_skipped() == 0 && this->num_not_run() == 0; 129 | } 130 | 131 | void write_junit_report(const std::string& file_name) const; 132 | }; 133 | 134 | } // namespace tst 135 | -------------------------------------------------------------------------------- /src/tst/runner.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #ifndef TST_NO_PAR 28 | 29 | # include "runner.hxx" 30 | 31 | using namespace tst; 32 | 33 | runner::runner() : 34 | nitki::loop_thread(0) 35 | {} 36 | 37 | std::optional runner::on_loop() 38 | { 39 | return {}; 40 | } 41 | 42 | #endif // ~TST_NO_PAR 43 | -------------------------------------------------------------------------------- /src/tst/runner.hxx: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | 32 | namespace tst { 33 | 34 | class runner : public nitki::loop_thread 35 | { 36 | public: 37 | runner(); 38 | 39 | std::optional on_loop() override; 40 | }; 41 | 42 | } // namespace tst 43 | -------------------------------------------------------------------------------- /src/tst/runners_pool.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #ifndef TST_NO_PAR 28 | 29 | # include "runners_pool.hxx" 30 | 31 | # include "settings.hxx" 32 | 33 | using namespace tst; 34 | 35 | runner* runners_pool::occupy_runner() 36 | { 37 | if (!this->free_runners.empty()) { 38 | auto r = this->free_runners.back(); 39 | ASSERT(r) 40 | this->free_runners.pop_back(); 41 | return r; 42 | } else if (this->runners.size() != settings::inst().num_threads) { 43 | ASSERT(this->runners.size() < settings::inst().num_threads) 44 | this->runners.push_back(std::make_unique()); 45 | auto r = this->runners.back().get(); 46 | r->start(); 47 | return r; 48 | } 49 | return nullptr; 50 | } 51 | 52 | #endif // ~TST_NO_PAR 53 | -------------------------------------------------------------------------------- /src/tst/runners_pool.hxx: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #pragma once 28 | 29 | #include 30 | 31 | #include "runner.hxx" 32 | 33 | namespace tst { 34 | 35 | class runners_pool 36 | { 37 | std::vector> runners; 38 | std::vector free_runners; 39 | 40 | public: 41 | void stop_all_runners() 42 | { 43 | for (auto& r : this->runners) { 44 | r->quit(); 45 | } 46 | } 47 | 48 | runners_pool() = default; 49 | 50 | runners_pool(const runners_pool&) = delete; 51 | runners_pool& operator=(const runners_pool&) = delete; 52 | 53 | runners_pool(runners_pool&&) = delete; 54 | runners_pool& operator=(runners_pool&&) = delete; 55 | 56 | ~runners_pool() 57 | { 58 | for (auto& r : this->runners) { 59 | r->join(); 60 | } 61 | } 62 | 63 | void free_runner(runner* r) 64 | { 65 | ASSERT( 66 | std::find(this->free_runners.begin(), this->free_runners.end(), r) == this->free_runners.end(), 67 | [](auto& o) { 68 | o << "runner is already freed"; 69 | } 70 | ) 71 | this->free_runners.push_back(r); 72 | } 73 | 74 | runner* occupy_runner(); 75 | 76 | bool no_active_runners() const noexcept 77 | { 78 | return this->runners.size() == this->free_runners.size(); 79 | } 80 | }; 81 | 82 | } // namespace tst 83 | -------------------------------------------------------------------------------- /src/tst/set.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #include "set.hpp" 28 | 29 | using namespace tst; 30 | 31 | set::inits_type& set::get_inits() 32 | { 33 | static inits_type inits; 34 | return inits; 35 | } 36 | 37 | set::set(const std::string& suite_name, inits_type::value_type::second_type::value_type init) 38 | { 39 | get_inits()[suite_name].push_back(std::move(init)); 40 | } 41 | -------------------------------------------------------------------------------- /src/tst/set.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #include "suite.hpp" 34 | 35 | namespace tst { 36 | 37 | /** 38 | * @brief Test case set. 39 | * This class provides a way to declare a group of test cases. 40 | * Just define an instance of the tst::set at a global scope and provide a test 41 | * case initializer function to its constructor. 42 | */ 43 | class set 44 | { 45 | friend class application; 46 | 47 | using inits_type = std::map>>; 48 | 49 | // The static inits map object has to be returned by getter function to avoid 50 | // undetermined initialization order of global variables problem. 51 | static inits_type& get_inits(); 52 | 53 | public: 54 | /** 55 | * @brief Constructor. 56 | * @param suite_name - name of the test suite to add the test cases to. 57 | * @param init - initializer function which adds test cases to the test suite 58 | * instance. The function takes a reference to the test suite instance as 59 | * argument. 60 | */ 61 | set(const std::string& suite_name, inits_type::value_type::second_type::value_type init); 62 | }; 63 | 64 | } // namespace tst 65 | -------------------------------------------------------------------------------- /src/tst/settings.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #include "settings.hxx" 28 | 29 | using namespace tst; 30 | 31 | // singleton instance variable 32 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 33 | settings::instance_type settings::instance; 34 | -------------------------------------------------------------------------------- /src/tst/settings.hxx: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | 32 | namespace tst { 33 | 34 | class settings : public utki::intrusive_singleton 35 | { 36 | friend singleton_type; 37 | 38 | // singleton instance variable 39 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 40 | static instance_type instance; 41 | 42 | public: 43 | bool colored_output = utki::is_terminal_cout(); 44 | 45 | bool show_help = false; 46 | 47 | bool list_tests = false; 48 | 49 | bool run_disabled = false; 50 | 51 | bool print_about_to_run = false; 52 | bool print_passed = false; 53 | bool print_skipped = false; 54 | bool print_outcome = false; 55 | 56 | unsigned long num_threads = 1; 57 | 58 | std::string junit_report_out_file; 59 | 60 | bool run_list_stdin = false; 61 | 62 | std::string suite_name; 63 | std::string test_name; 64 | }; 65 | 66 | } // namespace tst 67 | -------------------------------------------------------------------------------- /src/tst/suite.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #include "suite.hpp" 28 | 29 | #include 30 | 31 | #include "settings.hxx" 32 | #include "util.hxx" 33 | 34 | using namespace tst; 35 | 36 | void suite::add(std::string id, utki::flags flags, std::function proc) 37 | { 38 | validate_id(id); 39 | 40 | if (!proc) { 41 | throw std::invalid_argument("test procedure is nullptr"); 42 | } 43 | 44 | if (settings::inst().run_disabled) { 45 | flags.clear(flag::disabled); 46 | } 47 | 48 | if (this->tests.find(id) != this->tests.end()) { 49 | std::stringstream ss; 50 | ss << "test with id = '" << id << "' already exists in the test suite"; 51 | throw std::invalid_argument(ss.str()); 52 | } 53 | 54 | #ifdef DEBUG 55 | auto r = 56 | #endif 57 | this->tests.insert( 58 | std::make_pair( 59 | std::move(id), 60 | // NOLINTNEXTLINE(modernize-use-designated-initializers) 61 | test_info{std::move(proc), flags} 62 | ) 63 | ); 64 | ASSERT(r.second) 65 | } 66 | 67 | void suite::add_disabled(std::string id, utki::flags flags, std::function proc) 68 | { 69 | flags.set(flag::disabled); 70 | this->add(std::move(id), flags, std::move(proc)); 71 | } 72 | 73 | const char* suite::status_to_string(status s) 74 | { 75 | switch (s) { 76 | case status::passed: 77 | return "passed"; 78 | case status::disabled: 79 | return "disabled"; 80 | case status::errored: 81 | return "errored"; 82 | case status::failed: 83 | return "failed"; 84 | case status::not_run: 85 | return "not run"; 86 | } 87 | 88 | ASSERT(false) 89 | return nullptr; 90 | } 91 | 92 | std::string suite::make_indexed_id(std::string_view id, size_t index) 93 | { 94 | std::stringstream ss; 95 | #if CFG_COMPILER == CFG_COMPILER_MSVC 96 | ss << std::string(id); 97 | #else 98 | ss << id; 99 | #endif 100 | ss << "[" << index << "]"; 101 | return ss.str(); 102 | } 103 | -------------------------------------------------------------------------------- /src/tst/suite.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | 37 | namespace tst { 38 | 39 | enum class flag { 40 | disabled, 41 | no_parallel, 42 | 43 | enum_size 44 | }; 45 | 46 | /** 47 | * @brief Test suite. 48 | * The test suite object holds test case definitions belonging to a particular 49 | * test suite. 50 | */ 51 | class suite 52 | { 53 | friend class application; 54 | friend class reporter; 55 | friend class iterator; 56 | 57 | enum class status { 58 | not_run, 59 | passed, 60 | failed, 61 | errored, 62 | disabled 63 | }; 64 | 65 | static const char* status_to_string(status s); 66 | 67 | // TODO: why lint complains? 68 | // "error: an exception may be thrown in function 'test_info'" 69 | // NOLINTNEXTLINE(bugprone-exception-escape) 70 | struct test_info { 71 | std::function proc; 72 | utki::flags flags; 73 | mutable status result = status::not_run; 74 | mutable uint32_t time_ms; 75 | mutable std::string message; 76 | }; 77 | 78 | std::unordered_map tests; 79 | 80 | suite() = default; 81 | 82 | mutable size_t num_disabled = 0; 83 | mutable size_t num_failed = 0; 84 | mutable size_t num_passed = 0; 85 | mutable size_t num_errors = 0; 86 | 87 | size_t num_skipped() const noexcept 88 | { 89 | size_t num_non_skipped = this->num_disabled + this->num_errors + this->num_failed + this->num_passed; 90 | ASSERT(this->tests.size() >= num_non_skipped) 91 | return tests.size() - num_non_skipped; 92 | } 93 | 94 | static std::string make_indexed_id(std::string_view id, size_t index); 95 | 96 | public: 97 | // TODO: is it possible to hide the move constructor from user? 98 | suite(suite&&) = default; 99 | suite& operator=(suite&&) = default; 100 | 101 | suite(const suite&) = delete; 102 | suite& operator=(const suite&) = delete; 103 | 104 | ~suite() = default; 105 | 106 | /** 107 | * @brief Get number of test cases in the test suite. 108 | */ 109 | size_t size() const noexcept 110 | { 111 | return this->tests.size(); 112 | } 113 | 114 | /** 115 | * @brief Add a simple test case to the test suite. 116 | * @param id - id of the test case. 117 | * @param flags - test marks. 118 | * @param proc - test case procedure. 119 | */ 120 | void add(std::string id, utki::flags flags, std::function proc); 121 | 122 | /** 123 | * @brief Add a simple test case to the test suite. 124 | * @param id - id of the test case. 125 | * @param proc - test case procedure. 126 | */ 127 | void add(std::string id, std::function proc) 128 | { 129 | this->add(std::move(id), false, std::move(proc)); 130 | } 131 | 132 | /** 133 | * @brief Add a simple disabled test case to the test suite. 134 | * This method is same as corresponding 'add()' method but it 135 | * implicitly adds a 'disabled' mark to the test case. 136 | * @param id - id of the test case. 137 | * @param flags - test marks. 138 | * @param proc - test case procedure. 139 | */ 140 | void add_disabled(std::string id, utki::flags flags, std::function proc); 141 | 142 | /** 143 | * @brief Add a simple disabled test case to the test suite. 144 | * This method is same as corresponding 'add()' method but it 145 | * implicitly adds a 'disabled' mark to the test case. 146 | * @param id - id of the test case. 147 | * @param proc - test case procedure. 148 | */ 149 | void add_disabled(std::string id, std::function proc) 150 | { 151 | this->add_disabled(std::move(id), false, std::move(proc)); 152 | } 153 | 154 | /** 155 | * @brief Add parametrized test case to the test suite. 156 | * For each parameter value, adds a test case to the suite. 157 | * The actual test case ids are composed of the provided id string and 158 | * '[index]' suffix where index is the index of the parameter. 159 | * @param id - id of the test case. 160 | * @param flags - test marks. 161 | * @param params - collection of test procedure parameters. 162 | * @param proc - test procedure which takes a const reference to a parameter 163 | * as argument. 164 | */ 165 | template 166 | void add( 167 | std::string id, 168 | utki::flags flags, 169 | std::vector params, 170 | std::function proc 171 | ) 172 | { 173 | auto shared_proc = std::make_shared>(std::move(proc)); 174 | for (size_t i = 0; i != params.size(); ++i) { 175 | // TODO: why lint complains here on macos? 176 | // "error: an exception may be thrown" 177 | // NOLINTNEXTLINE(bugprone-exception-escape) 178 | this->add(make_indexed_id(id, i), flags, [proc = shared_proc, param = std::move(params[i])]() { 179 | ASSERT(proc) 180 | ASSERT(*proc) 181 | (*proc)(param); 182 | }); 183 | } 184 | } 185 | 186 | /** 187 | * @brief Add parametrized test case to the test suite. 188 | * For each parameter value, adds a test case to the suite. 189 | * The actual test case ids are composed of the provided id string and 190 | * '[index]' suffix where index is the index of the parameter. 191 | * @param id - id of the test case. 192 | * @param params - collection of test procedure parameters. 193 | * @param proc - test procedure which takes a const reference to a parameter 194 | * as argument. 195 | */ 196 | template 197 | void add( 198 | std::string id, // 199 | std::vector params, 200 | std::function proc 201 | ) 202 | { 203 | this->add(std::move(id), false, std::move(params), std::move(proc)); 204 | } 205 | 206 | /** 207 | * @brief Add disabled parametrized test case to the test suite. 208 | * For each parameter value, adds a test case to the suite. 209 | * The actual test case ids are composed of the provided id string and 210 | * '[index]' suffix where index is the index of the parameter. Note, that 211 | * parameter type has to be indicated as a template argument of the function. 212 | * This method is same as corresponding 'add()' method but it 213 | * implicitly adds a 'disabled' mark to the test case. 214 | * @param id - id of the test case. 215 | * @param flags - test marks. 216 | * @param params - collection of test procedure parameters. 217 | * @param proc - test procedure which takes a const reference to a parameter 218 | * as argument. 219 | */ 220 | template 221 | void add_disabled( 222 | std::string id, 223 | utki::flags flags, 224 | std::vector params, 225 | std::function proc 226 | ) 227 | { 228 | flags.set(flag::disabled); 229 | this->add(std::move(id), flags, std::move(params), std::move(proc)); 230 | } 231 | 232 | /** 233 | * @brief Add disabled parametrized test case to the test suite. 234 | * For each parameter value, adds a test case to the suite. 235 | * The actual test case ids are composed of the provided id string and 236 | * '[index]' suffix where index is the index of the parameter. Note, that 237 | * parameter type has to be indicated as a template argument of the function. 238 | * This method is same as corresponding 'add()' method but it 239 | * implicitly adds a 'disabled' mark to the test case. 240 | * @param id - id of the test case. 241 | * @param params - collection of test procedure parameters. 242 | * @param proc - test procedure which takes a const reference to a parameter 243 | * as argument. 244 | */ 245 | template 246 | void add_disabled( 247 | std::string id, 248 | std::vector params, 249 | std::function proc 250 | ) 251 | { 252 | this->add_disabled(std::move(id), false, std::move(params), std::move(proc)); 253 | } 254 | }; 255 | 256 | } // namespace tst 257 | -------------------------------------------------------------------------------- /src/tst/util.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #include "util.hxx" 28 | 29 | #include 30 | #include 31 | 32 | #include 33 | 34 | #include "settings.hxx" 35 | 36 | void tst::validate_id(std::string_view id) 37 | { 38 | auto i = std::find_if(id.begin(), id.end(), [](std::remove_reference_t::value_type c) { 39 | return !is_valid_id_char(c); 40 | }); 41 | if (i != id.end()) { 42 | std::stringstream ss; 43 | ss << "test case id validation failed: character '" << *i << "' is not allowed; id = " << 44 | #if CFG_COMPILER == CFG_COMPILER_MSVC 45 | std::string(id) 46 | #else 47 | id 48 | #endif 49 | ; 50 | throw std::invalid_argument(ss.str()); 51 | } 52 | } 53 | 54 | void tst::print_warning(std::ostream& o, const std::string& message) 55 | { 56 | std::stringstream ss; 57 | if (settings::inst().colored_output) { 58 | ss << "\033[1;35mwarning\033[0m: "; 59 | } else { 60 | ss << "warning: "; 61 | } 62 | ss << message << '\n'; 63 | o << ss.str(); 64 | } 65 | -------------------------------------------------------------------------------- /src/tst/util.hxx: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2023 Ivan Gagis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ================ LICENSE END ================ */ 26 | 27 | #pragma once 28 | 29 | #include 30 | 31 | #include 32 | 33 | namespace tst { 34 | 35 | class check_failed 36 | { 37 | public: 38 | const utki::source_location source_location; 39 | const std::string message; 40 | 41 | check_failed(std::string message, utki::source_location&& source_location) : 42 | source_location(std::move(source_location)), 43 | message(std::move(message)) 44 | {} 45 | }; 46 | 47 | struct full_id { 48 | const std::string& suite; 49 | const std::string& test; 50 | }; 51 | 52 | inline bool is_valid_id_char(char c) 53 | { 54 | return // 55 | ('a' <= c && c <= 'z') || // 56 | ('A' <= c && c <= 'Z') || // 57 | ('0' <= c && c <= '9') || // 58 | c == '_' || // 59 | c == '[' || // 60 | c == ']'; 61 | } 62 | 63 | void validate_id(std::string_view id); 64 | 65 | void print_warning(std::ostream& o, const std::string& message); 66 | 67 | } // namespace tst 68 | -------------------------------------------------------------------------------- /tests/.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: | 2 | -cppcoreguidelines-avoid-magic-numbers 3 | InheritParentConfig: true 4 | -------------------------------------------------------------------------------- /tests/basic/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../../src/tst/check.hpp" 2 | #include "../../src/tst/set.hpp" 3 | 4 | #include "../harness/testees.hpp" 5 | 6 | #include 7 | 8 | // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) 9 | 10 | namespace{ 11 | class fixture{ 12 | public: 13 | fixture() = default; 14 | 15 | fixture(const std::pair& p) : 16 | a(p.first) 17 | {} 18 | 19 | fixture(const fixture&) = delete; 20 | fixture& operator=(const fixture&) = delete; 21 | 22 | fixture(fixture&&) = delete; 23 | fixture& operator=(fixture&&) = delete; 24 | 25 | ~fixture() = default; 26 | 27 | int a = 10; 28 | }; 29 | } 30 | 31 | namespace{ 32 | const tst::set set1("factorial", [](tst::suite& suite){ 33 | suite.add( 34 | "positive_arguments_must_produce_expected_result", 35 | [](){ 36 | tst::check(factorial(1) == 1, SL); 37 | tst::check(factorial(2) == 2, SL) << "hello world!"; 38 | tst::check_eq(factorial(2), 2, SL) << "hello world!"; 39 | tst::check_ne(factorial(2), -1, SL) << "hello world!"; 40 | tst::check_lt(factorial(2), 10, SL) << "hello world!"; 41 | tst::check_gt(factorial(2), 1, SL) << "hello world!"; 42 | tst::check_le(factorial(2), 2, SL) << "hello world!"; 43 | tst::check_le(factorial(2), 3, SL) << "hello world!"; 44 | tst::check_ge(factorial(2), 2, SL) << "hello world!"; 45 | tst::check_ge(factorial(2), 1, SL) << "hello world!"; 46 | tst::check(factorial(2) == 2, [](auto& o){o << "hello world!";}, SL); 47 | tst::check_eq(factorial(3), 6, [](auto& o){o << "hello world!";}, SL); 48 | tst::check_ne(factorial(3), 7, [](auto& o){o << "hello world!";}, SL); 49 | tst::check_lt(factorial(3), 7, [](auto& o){o << "hello world!";}, SL); 50 | tst::check_gt(factorial(3), 5, [](auto& o){o << "hello world!";}, SL); 51 | tst::check_le(factorial(3), 6, [](auto& o){o << "hello world!";}, SL); 52 | tst::check_le(factorial(3), 7, [](auto& o){o << "hello world!";}, SL); 53 | tst::check_ge(factorial(3), 6, [](auto& o){o << "hello world!";}, SL); 54 | tst::check_ge(factorial(3), 5, [](auto& o){o << "hello world!";}, SL); 55 | #if CFG_CPP >= 20 56 | tst::check(factorial(2) == 2) << "hello world!"; 57 | tst::check_eq(factorial(2), 2) << "hello world!"; 58 | tst::check_ne(factorial(2), -1) << "hello world!"; 59 | tst::check_lt(factorial(2), 10) << "hello world!"; 60 | tst::check_gt(factorial(2), 1) << "hello world!"; 61 | tst::check_le(factorial(2), 2) << "hello world!"; 62 | tst::check_le(factorial(2), 3) << "hello world!"; 63 | tst::check_ge(factorial(2), 2) << "hello world!"; 64 | tst::check_ge(factorial(2), 1) << "hello world!"; 65 | tst::check(factorial(2) == 2, [](auto& o){o << "hello world!";}); 66 | tst::check_eq(factorial(3), 6, [](auto& o){o << "hello world!";}); 67 | tst::check_ne(factorial(3), 7, [](auto& o){o << "hello world!";}); 68 | tst::check_lt(factorial(3), 7, [](auto& o){o << "hello world!";}); 69 | tst::check_gt(factorial(3), 5, [](auto& o){o << "hello world!";}); 70 | tst::check_le(factorial(3), 6, [](auto& o){o << "hello world!";}); 71 | tst::check_le(factorial(3), 7, [](auto& o){o << "hello world!";}); 72 | tst::check_ge(factorial(3), 6, [](auto& o){o << "hello world!";}); 73 | tst::check_ge(factorial(3), 5, [](auto& o){o << "hello world!";}); 74 | #endif 75 | tst::check(factorial(8) == 40320, SL); 76 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 77 | } 78 | ); 79 | suite.add_disabled("disabled_test", [](){tst::check(false, SL);}); 80 | }); 81 | } 82 | 83 | namespace{ 84 | const tst::set set2("factorial", [](tst::suite& suite){ 85 | suite.add( 86 | "factorial_of_value_from_fixture", 87 | [](){ 88 | fixture f; 89 | tst::check_eq(factorial(f.a), 3628800, SL); 90 | } 91 | ); 92 | 93 | suite.add>( 94 | "positive_arguments_must_produce_expected_result", 95 | {tst::flag::no_parallel}, 96 | { 97 | {1, 1}, 98 | {2, 2}, 99 | {3, 6}, 100 | {8, 40320} 101 | }, 102 | [](const auto& i){ 103 | tst::check(factorial(i.first) == i.second, SL); 104 | } 105 | ); 106 | 107 | suite.add_disabled>( 108 | "disabled_param_test", 109 | { 110 | {1, 1}, 111 | {2, 2}, 112 | {3, 6}, 113 | {8, 40320} 114 | }, 115 | [](const auto& i){tst::check(false, SL);} 116 | ); 117 | 118 | suite.add>( 119 | "factorial_of_value_from_fixture", 120 | { 121 | {1, 1}, 122 | {2, 2}, 123 | {3, 6}, 124 | {8, 40320} 125 | }, 126 | [](const auto& i){ 127 | tst::check(factorial(i.first) == i.second, SL); 128 | } 129 | ); 130 | }); 131 | } 132 | 133 | namespace{ 134 | const tst::set empty_set("factorial", [](auto&){}); 135 | } 136 | 137 | namespace{ 138 | const tst::set set3("check_pointers", [](auto& suite){ 139 | suite.add("check_is_possible_for_simple_pointer", [](){ 140 | int a = 0; 141 | int* p = &a; 142 | tst::check(p, nullptr, SL); 143 | tst::check(p, SL) << "Hello world!"; 144 | #if CFG_CPP >=20 145 | tst::check(p, nullptr); 146 | tst::check(p) << "Hello world!"; 147 | #endif 148 | }); 149 | 150 | suite.add("check_is_possible_for_shared_ptr", [](){ 151 | auto p = std::make_shared>(13, 4); 152 | tst::check(p, nullptr, SL); 153 | tst::check(p, SL) << "Hello world!"; 154 | #if CFG_CPP >=20 155 | tst::check(p, nullptr); 156 | tst::check(p) << "Hello world!"; 157 | #endif 158 | }); 159 | 160 | suite.add("check_is_possible_for_unique_ptr", [](){ 161 | auto p = std::make_unique>(13, 4); 162 | tst::check(p, nullptr, SL); 163 | tst::check(p, SL) << "Hello world!"; 164 | #if CFG_CPP >=20 165 | tst::check(p, nullptr); 166 | tst::check(p) << "Hello world!"; 167 | #endif 168 | }); 169 | 170 | suite.add("check_is_possible_for_std_function", [](){ 171 | std::function f = [](){}; 172 | tst::check(f, nullptr, SL); 173 | tst::check(f, SL) << "Hello world!"; 174 | #if CFG_CPP >=20 175 | tst::check(f, nullptr); 176 | tst::check(f) << "Hello world!"; 177 | #endif 178 | }); 179 | }); 180 | } 181 | 182 | namespace{ 183 | const tst::set empty_set_with_empty_suite("empty_suite", [](auto&){}); 184 | } 185 | 186 | namespace{ 187 | const tst::set parametrized_set("paramterized_by_string", [](tst::suite& suite){ 188 | suite.add( 189 | "string_has_non_zero_length", 190 | { 191 | "hello", 192 | "world" 193 | }, 194 | [](auto& p){ 195 | #if CFG_CPP >= 20 196 | tst::check_ne(p.size(), 0); 197 | #else 198 | tst::check_ne(p.size(), size_t(0), SL); 199 | #endif 200 | } 201 | ); 202 | }); 203 | } 204 | 205 | // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) 206 | -------------------------------------------------------------------------------- /tests/basic/makefile: -------------------------------------------------------------------------------- 1 | include prorab.mk 2 | include prorab-test.mk 3 | 4 | $(eval $(call prorab-config, ../../config)) 5 | 6 | this_no_install := true 7 | 8 | this_name := tests 9 | 10 | this_srcs := $(call prorab-src-dir, .) 11 | this_srcs += ../harness/testees.cpp 12 | 13 | ifeq ($(os), windows) 14 | else 15 | this_ldflags += -rdynamic 16 | endif 17 | 18 | this_ldflags += -L $(d)../../src/out/$(c) 19 | 20 | ifeq ($(os),linux) 21 | # in case of static linking -pthread option is needed 22 | this_ldflags += -pthread 23 | endif 24 | 25 | this_ldlibs += -ltst -lutki 26 | 27 | $(eval $(prorab-build-app)) 28 | 29 | $(prorab_this_name): $(abspath $(d)../../src/out/$(c)/libtst$(dot_so)) 30 | 31 | this_test_deps := $(prorab_this_name) 32 | this_test_ld_path := ../../src/out/$(c) 33 | 34 | # when running the test from msys2 it cannot detect that stdin is not piped, so we need to pipe empty string 35 | # to it to avoid it hanging waiting for run list from stdin 36 | this_test_cmd := $(prorab_this_name) --jobs=auto --about-to-run --junit-out=out/$(c)/junit.xml 37 | $(eval $(prorab-test)) 38 | 39 | this_test_cmd := cat run_list.txt | $(prorab_this_name) --junit-out=out/$(c)/junit_run_list.xml --skipped --passed --outcome --run-list-stdin 40 | $(eval $(prorab-test)) 41 | 42 | # run one suite 43 | this_test_cmd := cat run_list.txt | $(prorab_this_name) --skipped --passed --outcome --run-list-stdin --suite=check_pointers 44 | $(eval $(prorab-test)) 45 | 46 | # run one test 47 | this_test_cmd := cat run_list.txt | $(prorab_this_name) --skipped --passed --outcome --run-list-stdin --suite=check_pointers --test=check_is_possible_for_simple_pointer 48 | $(eval $(prorab-test)) 49 | 50 | $(eval $(call prorab-include, ../../src/makefile)) 51 | -------------------------------------------------------------------------------- /tests/basic/run_list.txt: -------------------------------------------------------------------------------- 1 | factorial #ddsfgerg 2 | # disabled_param_fixture_test[1] 3 | #disabled_param_fixture_test[2] 4 | #disabled_param_test[0] 5 | disabled_param_test[1]#gerge 6 | factorial disabled_param_test[2] 7 | disabled_param_test[3] # tttt 8 | disabled_test 9 | factorial_of_value_from_fixture 10 | # factorial_of_value_from_fixture[0] 11 | factorial_of_value_from_fixture[1] 12 | #factorial_of_value_from_fixture[2] 13 | factorial_of_value_from_fixture[3] # 14 | positive_arguments_must_produce_expected_result 15 | # positive_arguments_must_produce_expected_result[0] 16 | positive_arguments_must_produce_expected_result[1] 17 | # positive_arguments_must_produce_expected_result[2] 18 | positive_arguments_must_produce_expected_result[3] 19 | check_pointers -------------------------------------------------------------------------------- /tests/failed/checks.cpp: -------------------------------------------------------------------------------- 1 | #include "../../src/tst/set.hpp" 2 | #include "../../src/tst/check.hpp" 3 | 4 | namespace{ 5 | const tst::set set("failing_checks", [](auto& suite){ 6 | suite.add("check", [](){ 7 | tst::check(false, SL) << "Hello world!"; 8 | }); 9 | 10 | suite.add("check_print", [](){ 11 | tst::check(false, [](auto&o){o << "failed!";}, SL); 12 | }); 13 | 14 | suite.add("check_eq", [](){ 15 | tst::check_eq(1, 2, SL) << "Hello world!"; 16 | }); 17 | 18 | suite.add("check_eq_print", [](){ 19 | tst::check_eq(1, 2, [](auto&o){o << "failed!";}, SL); 20 | }); 21 | 22 | suite.add("check_ne", [](){ 23 | tst::check_ne(2, 2, SL) << "Hello world!"; 24 | }); 25 | 26 | suite.add("check_ne_print", [](){ 27 | tst::check_ne(2, 2, [](auto&o){o << "failed!";}, SL); 28 | }); 29 | 30 | suite.add("check_lt", [](){ 31 | tst::check_lt(2, 2, SL) << "Hello world!"; 32 | }); 33 | 34 | suite.add("check_lt_print", [](){ 35 | tst::check_lt(2, 2, [](auto&o){o << "failed!";}, SL); 36 | }); 37 | 38 | suite.add("check_gt", [](){ 39 | tst::check_gt(2, 2, SL) << "Hello world!"; 40 | }); 41 | 42 | suite.add("check_gt_print", [](){ 43 | tst::check_gt(2, 2, [](auto&o){o << "failed!";}, SL); 44 | }); 45 | 46 | suite.add("check_le", [](){ 47 | tst::check_le(2, 1, SL) << "Hello world!"; 48 | }); 49 | 50 | suite.add("check_le_print", [](){ 51 | tst::check_le(2, 1, [](auto&o){o << "failed!";}, SL); 52 | }); 53 | 54 | suite.add("check_ge", [](){ 55 | tst::check_ge(2, 3, SL) << "Hello world!"; 56 | }); 57 | 58 | suite.add("check_ge_print", [](){ 59 | tst::check_ge(2, 3, [](auto&o){o << "failed!";}, SL); 60 | }); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /tests/failed/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../../src/tst/application.hpp" 2 | #include "../../src/tst/check.hpp" 3 | 4 | #include "../harness/testees.hpp" 5 | 6 | #include 7 | 8 | // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) 9 | 10 | namespace{ 11 | class fixture{ 12 | public: 13 | fixture() = default; 14 | 15 | fixture(const std::pair& p) : 16 | a(p.first) 17 | {} 18 | 19 | fixture(const fixture&) = delete; 20 | fixture& operator=(const fixture&) = delete; 21 | 22 | fixture(fixture&&) = delete; 23 | fixture& operator=(fixture&&) = delete; 24 | 25 | ~fixture() = default; 26 | 27 | int a = 10; 28 | }; 29 | } 30 | 31 | namespace{ 32 | struct some_unknown_exception{}; 33 | } 34 | 35 | class application : public tst::application{ 36 | public: 37 | application() : 38 | tst::application("failing tests", "some tests are failing") 39 | {} 40 | 41 | void init()override; 42 | }; 43 | 44 | void application::init(){ 45 | this->tst::application::init(); 46 | 47 | auto& suite = this->get_suite("factorial"); 48 | 49 | suite.add( 50 | "positive_arguments_must_produce_expected_result", 51 | [](){ 52 | tst::check(factorial(1) == 1, SL); 53 | tst::check_eq(factorial(2), 2, SL); 54 | tst::check_eq(factorial(3), 6, [](auto& o){o << "hello world!";}, SL); 55 | tst::check_ne(factorial(3), 6, [](auto& o){o << "hello world!";}, SL); 56 | tst::check_lt(factorial(3), 6, [](auto& o){o << "hello world!";}, SL); 57 | tst::check(factorial(8) == 40320, SL); 58 | throw std::runtime_error("thrown by test"); 59 | } 60 | ); 61 | 62 | suite.add( 63 | "test_which_throws_unknown_exception", 64 | [](){ 65 | throw some_unknown_exception(); 66 | } 67 | ); 68 | 69 | suite.add( 70 | "test_which_throws_runtime_error_exception", 71 | [](){ 72 | throw std::runtime_error("some runtime error"); 73 | } 74 | ); 75 | 76 | suite.add( 77 | "test_which_throws_logic_error_exception", 78 | [](){ 79 | throw std::logic_error("some logic error"); 80 | } 81 | ); 82 | 83 | suite.add( 84 | "test_which_throws_std_exception", 85 | [](){ 86 | throw std::exception(); 87 | } 88 | ); 89 | 90 | suite.add( 91 | "test_which_throws_nested_exception", 92 | [](){ 93 | try{ 94 | try{ 95 | try{ 96 | try{ 97 | throw std::logic_error("some logic error"); 98 | }catch(...){ 99 | std::throw_with_nested(std::runtime_error("some runtime error")); 100 | } 101 | }catch(...){ 102 | std::throw_with_nested(some_unknown_exception()); 103 | } 104 | }catch(...){ 105 | std::throw_with_nested(std::exception()); 106 | } 107 | }catch(...){ 108 | std::throw_with_nested(std::invalid_argument("some argument is invalid")); 109 | } 110 | } 111 | ); 112 | 113 | suite.add( 114 | "test_which_throws_stacked_exception", 115 | [](){ 116 | try{ 117 | try{ 118 | try{ 119 | throw std::logic_error("some logic error"); 120 | }catch(...){ 121 | utki::throw_with_nested(std::runtime_error("some runtime error")); 122 | } 123 | }catch(...){ 124 | utki::throw_with_nested(std::exception()); 125 | } 126 | }catch(...){ 127 | utki::throw_with_nested(std::invalid_argument("some argument is invalid")); 128 | } 129 | } 130 | ); 131 | 132 | suite.add( 133 | "test_which_fails_check_eq_with_custom_message", 134 | [](){ 135 | tst::check_eq(factorial(3), 7, [](auto& o){o << "hello world!";}, SL); 136 | } 137 | ); 138 | 139 | suite.add_disabled("disabled_test", [](){tst::check(false, SL);}); 140 | 141 | suite.add( 142 | "factorial_of_value_from_fixture", 143 | [](){ 144 | fixture f; 145 | tst::check_eq(factorial(f.a), 3628801, SL); // will fail 146 | } 147 | ); 148 | 149 | suite.add>( 150 | "positive_arguments_must_produce_expected_result", 151 | { 152 | {1, 1}, 153 | {2, 2}, 154 | {3, 7}, // will fail 155 | {8, 40320} 156 | }, 157 | [](const auto& i){ 158 | tst::check(factorial(i.first) == i.second, SL); 159 | } 160 | ); 161 | 162 | suite.add_disabled>( 163 | "disabled_param_test", 164 | { 165 | {1, 1}, 166 | {2, 3}, // will fail 167 | {3, 6}, 168 | {8, 40320} 169 | }, 170 | [](const auto& i){tst::check(false, SL);} 171 | ); 172 | 173 | suite.add>( 174 | "factorial_of_value_from_fixture", 175 | { 176 | {1, 2}, // will fail 177 | {2, 2}, 178 | {3, 6}, 179 | {8, 40320} 180 | }, 181 | [](const auto& i){ 182 | tst::check(factorial(i.first) == i.second, SL) << "expected " << i.second; 183 | } 184 | ); 185 | } 186 | 187 | const tst::application_factory fac([](){ 188 | return std::make_unique<::application>(); 189 | }); 190 | 191 | // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) 192 | -------------------------------------------------------------------------------- /tests/failed/makefile: -------------------------------------------------------------------------------- 1 | include prorab.mk 2 | include prorab-test.mk 3 | 4 | $(eval $(call prorab-config, ../../config)) 5 | 6 | this_no_install := true 7 | 8 | this_name := tests 9 | 10 | this_srcs := $(call prorab-src-dir, .) 11 | this_srcs += ../harness/testees.cpp 12 | 13 | ifeq ($(os), windows) 14 | else 15 | this_ldflags += -rdynamic 16 | endif 17 | 18 | ifeq ($(os),linux) 19 | # in case of static linking -pthread option is needed 20 | this_ldflags += -pthread 21 | endif 22 | 23 | this_ldflags += -L $(d)../../src/out/$(c) 24 | this_ldlibs += -ltst -lutki -lclargs -lnitki -lopros 25 | 26 | $(eval $(prorab-build-app)) 27 | 28 | $(prorab_this_name): $(abspath $(d)../../src/out/$(c)/libtst$(dot_so)) 29 | 30 | this_test_deps := $(prorab_this_name) 31 | this_test_ld_path := ../../src/out/$(c) 32 | 33 | # when running the test from msys2 it cannot detect that stdin is not piped, so we need to pipe empty string 34 | # to it to avoid it hanging waiting for run list from stdin 35 | this_test_cmd := echo "" | $(prorab_this_name) --jobs=$(prorab_nproc) --junit-out=out/$(c)/junit.xml || true && myci-warning.sh "NOT A REAL FAILURES! Just testing how test cases fail." 36 | $(eval $(prorab-test)) 37 | 38 | $(eval $(call prorab-include, ../../src/makefile)) 39 | -------------------------------------------------------------------------------- /tests/harness/testees.cpp: -------------------------------------------------------------------------------- 1 | #include "testees.hpp" 2 | 3 | int factorial(int n){ 4 | int ret = 1; 5 | for(int i = 2; i <= n; ++i){ 6 | ret *= i; 7 | } 8 | return ret; 9 | } 10 | -------------------------------------------------------------------------------- /tests/harness/testees.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | int factorial(int n); 4 | -------------------------------------------------------------------------------- /tests/makefile: -------------------------------------------------------------------------------- 1 | include prorab.mk 2 | 3 | $(eval $(prorab-include-subdirs)) 4 | -------------------------------------------------------------------------------- /wiki/boost-ut_comparison.md: -------------------------------------------------------------------------------- 1 | After a closer look at `Boost.UT` I could come up with some _subjective and opinionated_ comparison of `tst` and `Boost.UT` at least on several things. 2 | 3 | I don't think this kind of comparison belongs to the `tst` github repository, so I just post it here. 4 | 5 | ## JUnit report generation 6 | 7 | As far as I could understand, `Boost.UT` does not generate JUnit XML report out of the box. Although, it is certainly possible to implement a "plugin" for that. 8 | 9 | `tst` is able to generate JUnit XML reports out of the box. The name of the generated XML file is set via command line argument to the test runner application. 10 | 11 | ## Parallel tests running 12 | 13 | As far as I could understand, `Boost.UT` does not support that too, out of the box. 14 | 15 | `tst` allows running tests in a number of parallel threads. The number of threads to use is set via command line argument to the test runner application. 16 | 17 | ## Command line arguments 18 | 19 | `Boost.UT` does not provide CLI argumetns parsing. Well, it is not a CLI args parsing library anyway. 20 | 21 | `tst` is integrated with CLI arguments parsing library, which it uses for parsing `tst`'s default CLI arguments. It also allows user to add custom CLI arguments to the parser to handle them and configure the test run. E.g. some test cases might need to know some directory where test data files reside, that can be supplied via CLI. So, user gets CLI for free. 22 | 23 | ## Suite names 24 | 25 | `Boost.UT` allows declaring tests inside of suites. But I could not find how to set the name of the suite. I'd expect the suites to be named and be discoverable. Instead, as I understood, `Boost.UT`'s test suites are essentially same as `tst`'s test sets (`tst::set`). 26 | 27 | `tst` structures test cases into suites. So, it is possible to specify to run only tests from specific test suite. See `tst`'s run lists feature. 28 | 29 | ## Custom failure message 30 | 31 | `Boost.UT` uses same approach as `GoogleTest`, when the assertion function returns a stream to which values can be inserted. 32 | ```cpp 33 | expect(arg > 0) << "arg = " << arg; 34 | ``` 35 | It looks simple and convenient. Although, there is a small, and, apparently, rare problem with it. In case the check passes, all the arguments of the `<<` operator of the stream are still invoked and inserted to a stream (in case of successful check the stream just does nothing). So, there can be undesired double invocation of something, for example if trying to output some return value of a fucntion: `expect(arg > 0) << func();`. 36 | 37 | `tst` uses another approach, with supplying a callback function to perform the stream output, which is invoked **only** in case the check fails. And since the test will be stopped on the failed check, there is no risk that invoking some functions during those stream outputs will affect subsequent test execution. 38 | ```cpp 39 | int a = 3; 40 | tst::check(a == 4, [&](auto& o){o << "a = " << a;}, SL); 41 | ``` 42 | It looks a bit more awkward, but in return it is safer in that way. 43 | 44 | ## Syntax of test cases 45 | 46 | This is a matter of preferrance, but consider: 47 | 48 | `Boost.UT` 49 | ```cpp 50 | "hello world"_test = [] { 51 | //... 52 | }; 53 | ``` 54 | What we see here? A string with custom `_test` literal suffix, perhaps it creates some rvalue object. Then we assign a lambda function to it. Basically it is not very intuitive to me what's happening here. When is the test executed? Is the string a test id? Why do we assign lambda function to a string? 55 | 56 | `tst` 57 | ```cpp 58 | suite.add( 59 | "positive_arguments_must_produce_expected_result", 60 | [](){ 61 | //... 62 | } 63 | ); 64 | ``` 65 | What we see here? Well, we have `suite` to which we _add_ a string-named lambda function. I.e. adding a test, what else can we add to the suite? 66 | Much more intuitive, in my opinion. 67 | 68 | ## Syntax of parametrized tests 69 | 70 | In `Boost.UT` there is a [number of different styles](https://github.com/boost-ext/ut/blob/master/example/parameterized.cpp) to add a parametrized test case. All of them are pretty cryptic due to heavy usage of overloaded operators of custom "non-public" classes. Except for the `for`-loop method, in all other methods the list of parameter values goes after the test procedure definition. I find this inconvenient, as I want to see list of parameter value next to the test name. This is what I used to from the times I was coding a lot of unit tests in C#. 71 | -------------------------------------------------------------------------------- /wiki/main.adoc: -------------------------------------------------------------------------------- 1 | = WiKi main page 2 | 3 | == Installation 4 | :package_name: tst 5 | 6 | . Setup your OS-preferred package system repo following link:https://github.com/cppfw/wiki/blob/main/enable_repo/enable_repo.adoc[this manual] 7 | . Install package 8 | + 9 | - **vcpkg** (multi-OS): `{package_name}` 10 | - **conan** (multi-OS): `{package_name}` 11 | - **deb** (Linux): `lib{package_name}-dev` 12 | - **homebrew** (MacOS X): `lib{package_name}` 13 | - **Msys2** (Windows): `mingw-w64-i686-{package_name}`, `mingw-w64-x86_64-{package_name}` 14 | - **Nuget** (Windows, Visual Studio 2019+): `lib{package_name}` 15 | 16 | == Documentation 17 | - link:tutorial.adoc[Getting started] 18 | -------------------------------------------------------------------------------- /wiki/tutorial.adoc: -------------------------------------------------------------------------------- 1 | = Concepts 2 | 3 | The `tst` is a C++ library which provides facilities for unit and other testing. It follows basic link:https://en.wikipedia.org/wiki/XUnit[xUnit] concepts. 4 | 5 | == Test case 6 | Test case is a function which performs the testing of a program. 7 | 8 | == Test suite 9 | Test cases are organized into test suites, just to have some structuring. 10 | Then it is possible to easily run only test cases belonging to a certain test suite, or disable all test cases from a certain test suite. 11 | 12 | == Test set 13 | In `tst`, the test set is a group of test cases defined right next to each other. So, the test set means only where the test case is defined and nothing more. This is just a convenience entity and does not affect test cases structuring, unlike test suite. 14 | 15 | = Basic usage 16 | 17 | Please note, that `tst` uses C++'17 features, so it needs a compiler which supports that {cpp} standard. 18 | 19 | == Code under test 20 | 21 | For different testing frameworks it is quite сommon to use a `factorial(x)` function as a sample code under test. So, we will use it as well in this tutorial. 22 | 23 | [source,c++] 24 | .... 25 | int factorial(int n){ 26 | int ret = 1; 27 | for(int i = 2; i <= n; ++i){ 28 | ret *= i; 29 | } 30 | return ret; 31 | } 32 | .... 33 | 34 | == Test runner application 35 | 36 | For running test cases we need to create a test runner application. 37 | 38 | For simple use case `tst` does not require defining any program entry point. The library does it for you. 39 | 40 | So, for defining our first test case, our test runner application will consist of just one source file `test_factorial.cpp`, this is where we will write our test cases. 41 | 42 | == Include tst headers 43 | 44 | First of all we need to include some headers. 45 | 46 | [source,c++] 47 | .... 48 | #include // test sets are used to declare groups of tests 49 | #include // family of check() functions (think assert) 50 | .... 51 | 52 | == Create a test set 53 | 54 | We need to create a test set object which will allow us to declare test cases. 55 | 56 | [source,c++] 57 | .... 58 | namespace{ 59 | tst::set factorial_test_set("factorial", [](tst::suite& suite){ 60 | // this is where we will declare our test cases 61 | }); 62 | } 63 | .... 64 | 65 | The constructor of the `tst::set` class has two arguments: 66 | 67 | . The name of the test suite to which all test cases defined in this test set will be added. 68 | . The test case declaration functor. This functor will be called by `tst` to instantiate the test cases. The argument of the functor is a test suite object to which the test cases are to be added. 69 | 70 | Note, that the name of the `tst::set` object does not matter, it can be anything you like, but should not clash with other names, of course. In our case we used `factorial_test_set`. 71 | 72 | Also, we defined the test set object inside of the anonymous namespace to prevent the symbol to be exported. This is not mandatory and is not critical to do it that way, but just for the sake of code cleanness. 73 | 74 | == Declare first test case 75 | 76 | As a first test case, let's check that our function correctly calculates factorial of some numbers. 77 | 78 | [source,c++] 79 | .... 80 | namespace{ 81 | tst::set factorial_test_set("factorial", [](tst::suite& suite){ 82 | suite.add( 83 | "positive_arguments_must_produce_expected_result", 84 | [](){ 85 | tst::check(factorial(1) == 1, SL); 86 | tst::check(factorial(2) == 2, SL); 87 | tst::check(factorial(3) == 6, SL); 88 | tst::check(factorial(8) == 40320, SL); 89 | } 90 | ); 91 | }); 92 | } 93 | .... 94 | 95 | Here we use the `tst::suite::add()` method to add the test case to the suite. 96 | 97 | . The first argument is the test name. Note that, not all characters are allowed to be in the test case name, e.g. space characters are not allowed. Basically, only letters, numbers and underscores are allowed. 98 | . The second argument is a test case itself, or it's procedure. 99 | 100 | Note, that in the body of the test case function we used `tst::check()` function. This is basically an assertion function of `tst`. The second argument must always be `SL`. It is needed to make it possible for the function to know at which position in the source code it was acutally called. The `SL` is a macro which substitutes an object instance which holds the source file name and line number. **In case your compiler supports C++'20 standard, then adding the trailing `SL` argument is not necessary, all `check()` functions can be called without it.** 101 | 102 | == Declare a test case parametrized by value 103 | 104 | Sometimes it is handy to define a single test case function for a set of different test values. We can do that using an overload of `tst::suite::add()` method which allows passing in an array of parameter values. 105 | 106 | [source,c++] 107 | .... 108 | namespace{ 109 | tst::set factorial_test_set("factorial", [](tst::suite& suite){ 110 | suite.add>( 111 | "positive_arguments_must_produce_expected_result", 112 | { 113 | {1, 1}, // input and expected value pairs 114 | {2, 2}, 115 | {3, 6}, 116 | {8, 40320} 117 | }, 118 | [](const auto& i){ 119 | tst::check(factorial(i.first) == i.second, SL); 120 | } 121 | ); 122 | }); 123 | } 124 | .... 125 | 126 | == All our test cases in the same set 127 | 128 | To sum up, our test set would look like this: 129 | 130 | [source,c++] 131 | .... 132 | namespace{ 133 | tst::set factorial_test_set("factorial", [](tst::suite& suite){ 134 | // define simple test case 135 | suite.add( 136 | "positive_arguments_must_produce_expected_result", 137 | [](){ 138 | tst::check(factorial(1) == 1, SL); 139 | tst::check(factorial(2) == 2, SL); 140 | tst::check(factorial(3) == 6, SL); 141 | tst::check(factorial(8) == 40320, SL); 142 | } 143 | ); 144 | 145 | // define parametrized test case 146 | // Note, the name is the same as for simple test case above. 147 | // In parametrized case, a '[n]' suffix will be automatically 148 | // appended to each test case for corresponding value index. 149 | // So, in this case it is OK, and there will be no name clashing. 150 | suite.add>( 151 | "positive_arguments_must_produce_expected_result", 152 | { 153 | {1, 1}, // input and expected value pairs 154 | {2, 2}, 155 | {3, 6}, 156 | {8, 40320} 157 | }, 158 | [](const auto& i){ 159 | tst::check(factorial(i.first) == i.second, SL); 160 | } 161 | ); 162 | }); 163 | } 164 | .... 165 | 166 | == Disabling test cases 167 | 168 | Sometimes it is needed to temporarily disable the test case, for various reasons. In order to keep track of disabled test cases, instead of commenting them, one should use `tst::suite::add_disabled()` methods, instead of `tst::suite::add()`. So, just simply change the name of the `add()` method to disable the test case. 169 | 170 | == Adding custom info to check failure message 171 | 172 | When a check performed with `tst::check()` function fails, the test case is interrupted and a failure message is printed as the output. By default the message contains source file name and line number on which the check has failed. 173 | 174 | Often, it is desired to add custom information to such failure message. For that `tst` provides two different approaches. 175 | 176 | === Adding failure message via callback function 177 | 178 | [source,c++] 179 | .... 180 | int a = 3; 181 | 182 | tst::check(a == 4, [&](auto& o){o << "a = " << a;}, SL); 183 | .... 184 | 185 | So, as an additional argument of the `check()` function is a callback which is called only in case of check failure to obtain the additional failure message information. 186 | 187 | === Adding failure message via check_result object 188 | 189 | [source,c++] 190 | .... 191 | int a = 3; 192 | 193 | tst::check(a == 4, SL) << "a = " << a; 194 | .... 195 | 196 | So, the `check()` function returns a stream-like object through which we add custom message. The drawback of this method is that in case the check succeeds, all the values output to the stream are still evaluated. So, one needs to be careful with this approach to avoid undesired function calls as part of stream output evaluation in case there is no failure. 197 | 198 | == Various check functions 199 | 200 | Along with common `tst::check()` function the `tst` provides a number of secific check-functions for certain comparison type. For example `tst::check_eq()` for comparing for equality. These specific functions automatically add information about their arguments into the check failure message. 201 | 202 | == Conclusion 203 | 204 | This tutorial covers only some basic use cases. But `tst` can provide more flexibility if needed with the usage of `tst::application` class. 205 | --------------------------------------------------------------------------------