├── .clang-tidy ├── .gitattributes ├── .github └── workflows │ ├── build.yml │ ├── cmake-integration.yml │ ├── lint.yml │ ├── sonarlint.yml │ └── tests.yml ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── cmake ├── InstallRules.cmake ├── OptionVariables.cmake ├── PreventInSourceBuilds.cmake ├── ProjectIsTopLevel.cmake └── in │ ├── libassert-config-cmake.in │ └── version-hpp.in ├── include └── libassert │ ├── assert-catch2.hpp │ ├── assert-gtest.hpp │ ├── assert.hpp │ ├── expression-decomposition.hpp │ ├── platform.hpp │ ├── stringification.hpp │ └── utilities.hpp ├── lint.sh ├── screenshots ├── assert_val_opt.png ├── breakpoint.png ├── catch2.png ├── custom_object_printing.png ├── fopen.png ├── gtest.png ├── literal_formatting.png ├── map_contains.png ├── no_redundancy.png ├── object_printing.png ├── recursion_fold.png ├── safe_comparison.png ├── vec_size.png └── wubble_trace.png ├── sonar-project.properties ├── src ├── analysis.cpp ├── analysis.hpp ├── assert.cpp ├── common.hpp ├── microfmt.hpp ├── paths.cpp ├── paths.hpp ├── platform.cpp ├── platform.hpp ├── printing.cpp ├── printing.hpp ├── stringification.cpp ├── tokenizer.cpp ├── tokenizer.hpp ├── utils.cpp └── utils.hpp └── tests ├── CMakeLists.txt ├── add_subdirectory-integration ├── CMakeLists.txt └── main.cpp ├── binaries ├── basic_demo.cpp ├── basic_test.cpp ├── catch2-demo.cpp ├── gtest-demo.cpp └── tokens_and_highlighting.cpp ├── ci-wrapper.py ├── demo ├── bar.cpp ├── baz │ └── demo.cpp ├── demo.cpp └── foo.cpp ├── fetchcontent-integration ├── CMakeLists.txt └── main.cpp ├── findpackage-integration ├── CMakeLists.txt └── main.cpp ├── integration ├── a.cpp ├── expected │ ├── clang.macos.txt │ ├── clang.txt │ ├── clang.windows.txt │ ├── gnu.macos.txt │ ├── gnu.txt │ ├── gnu.windows.txt │ └── msvc.txt ├── integration.cpp └── x │ └── a.cpp ├── pyutils ├── __init__.py └── utils.py ├── run-tests.py └── unit ├── assertion_tests.cpp ├── constexpr_contexts.cpp ├── disambiguation.cpp ├── fmt-test.cpp ├── lexer.cpp ├── literals.cpp ├── std_format20.cpp ├── std_format23.cpp ├── stringify.cpp ├── test_files └── test_program._cpp ├── test_public_utilities.cpp ├── test_type_prettier.cpp └── type_handling.cpp /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: ' 3 | -*, 4 | clang-diagnostic-*, 5 | clang-analyzer-*, 6 | bugprone-*, 7 | cert-*, 8 | clang-analyzer-*, 9 | concurrency-*, 10 | cppcoreguidelines-*, 11 | misc-*, 12 | modernize-*, 13 | performance-*, 14 | portability-*, 15 | readability-*, 16 | -cppcoreguidelines-macro-usage, 17 | -modernize-use-trailing-return-type, 18 | -misc-non-private-member-variables-in-classes, 19 | -cppcoreguidelines-pro-type-reinterpret-cast, 20 | -cppcoreguidelines-pro-type-vararg, 21 | -cppcoreguidelines-avoid-c-arrays, 22 | -modernize-avoid-c-arrays, 23 | -cppcoreguidelines-pro-bounds-pointer-arithmetic, 24 | -readability-else-after-return, 25 | -cppcoreguidelines-pro-bounds-array-to-pointer-decay, 26 | -cert-dcl50-cpp, 27 | -cppcoreguidelines-init-variables, 28 | -readability-implicit-bool-conversion, 29 | -cppcoreguidelines-pro-bounds-constant-array-index, 30 | -cppcoreguidelines-owning-memory, 31 | -cppcoreguidelines-pro-type-member-init, 32 | -readability-isolate-declaration, 33 | -cppcoreguidelines-avoid-magic-numbers, 34 | -readability-magic-numbers, 35 | -cppcoreguidelines-pro-type-union-access, 36 | -cppcoreguidelines-pro-type-cstyle-cast, 37 | -readability-named-parameter, 38 | -cppcoreguidelines-avoid-goto, 39 | -readability-uppercase-literal-suffix, 40 | -modernize-type-traits, 41 | -bugprone-easily-swappable-parameters, 42 | -readability-identifier-length, 43 | -misc-use-anonymous-namespace 44 | -performance-avoid-endl, 45 | -cppcoreguidelines-avoid-non-const-global-variables, 46 | -cppcoreguidelines-avoid-const-or-ref-data-members, 47 | -misc-no-recursion, 48 | -bugprone-branch-clone, 49 | -readability-braces-around-statements, 50 | -cppcoreguidelines-prefer-member-initializer 51 | ' 52 | WarningsAsErrors: '*' 53 | HeaderFilterRegex: '' 54 | AnalyzeTemporaryDtors: false 55 | FormatStyle: none 56 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build-linux: 9 | runs-on: ubuntu-22.04 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | compiler: [g++-10, clang++-14] 14 | cxx_version: [ cxx_std_17, cxx_std_20 ] 15 | target: [Debug, Release] 16 | shared: [ON, OFF] 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: dependencies 20 | run: sudo apt install gcc-10 g++-10 libgcc-10-dev 21 | - name: build 22 | run: | 23 | mkdir -p build 24 | cd build 25 | cmake .. \ 26 | -DCMAKE_BUILD_TYPE=${{matrix.target}} \ 27 | -DCMAKE_CXX_COMPILER=${{matrix.compiler}} \ 28 | -DLIBASSERT_DESIRED_CXX_STANDARD="${{matrix.cxx_version}}" \ 29 | -DLIBASSERT_BUILD_SHARED=${{matrix.shared}} \ 30 | -DLIBASSERT_WERROR_BUILD=On 31 | make -j 32 | build-macos: 33 | runs-on: macos-14 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | compiler: [g++-12, clang++] 38 | cxx_version: [ cxx_std_17, cxx_std_20 ] 39 | target: [Debug, Release] 40 | shared: [ON, OFF] 41 | steps: 42 | - uses: actions/checkout@v2 43 | - name: build 44 | run: | 45 | mkdir -p build 46 | cd build 47 | cmake .. \ 48 | -DCMAKE_BUILD_TYPE=${{matrix.target}} \ 49 | -DCMAKE_CXX_COMPILER=${{matrix.compiler}} \ 50 | -DLIBASSERT_DESIRED_CXX_STANDARD="${{matrix.cxx_version}}" \ 51 | -DLIBASSERT_BUILD_SHARED=${{matrix.shared}} \ 52 | -DLIBASSERT_WERROR_BUILD=On 53 | make -j 54 | build-windows: 55 | runs-on: windows-2022 56 | strategy: 57 | fail-fast: false 58 | matrix: 59 | compiler: [cl, clang++] 60 | cxx_version: [ cxx_std_17, cxx_std_20 ] 61 | target: [Debug, Release] 62 | shared: [ON, OFF] 63 | steps: 64 | - uses: actions/checkout@v2 65 | - name: Enable Developer Command Prompt 66 | uses: ilammy/msvc-dev-cmd@v1.10.0 67 | - name: build 68 | run: | 69 | mkdir -p build 70 | cd build 71 | cmake .. ` 72 | -DCMAKE_BUILD_TYPE=${{matrix.target}} ` 73 | -DCMAKE_CXX_COMPILER=${{matrix.compiler}} ` 74 | -DLIBASSERT_DESIRED_CXX_STANDARD="${{matrix.cxx_version}}" ` 75 | -DLIBASSERT_BUILD_SHARED=${{matrix.shared}} ` 76 | -DLIBASSERT_WERROR_BUILD=On 77 | msbuild .\libassert.sln 78 | build-mingw: 79 | runs-on: windows-2022 80 | strategy: 81 | fail-fast: false 82 | matrix: 83 | compiler: [g++] 84 | cxx_version: [ cxx_std_17, cxx_std_20 ] 85 | target: [Debug, Release] 86 | shared: [ON, OFF] 87 | steps: 88 | - uses: actions/checkout@v2 89 | - name: Enable Developer Command Prompt 90 | uses: ilammy/msvc-dev-cmd@v1.10.0 91 | - name: build 92 | run: | 93 | mkdir -p build 94 | cd build 95 | cmake .. ` 96 | -DCMAKE_BUILD_TYPE=${{matrix.target}} ` 97 | -DCMAKE_CXX_COMPILER=${{matrix.compiler}} ` 98 | -DLIBASSERT_DESIRED_CXX_STANDARD="${{matrix.cxx_version}}" ` 99 | -DLIBASSERT_BUILD_SHARED=${{matrix.shared}} ` 100 | -DLIBASSERT_WERROR_BUILD=On ` 101 | "-GUnix Makefiles" 102 | make -j 103 | -------------------------------------------------------------------------------- /.github/workflows/cmake-integration.yml: -------------------------------------------------------------------------------- 1 | name: cmake-integration 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test-linux-fetchcontent: 9 | runs-on: ubuntu-22.04 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: test 13 | run: | 14 | tag=$(git rev-parse --abbrev-ref HEAD) 15 | cd .. 16 | cp -rv libassert/tests/fetchcontent-integration . 17 | mkdir fetchcontent-integration/build 18 | cd fetchcontent-integration/build 19 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DLIBASSERT_TAG=$tag -DLIBASSERT_WERROR_BUILD=On 20 | make 21 | ./main 22 | test-linux-findpackage: 23 | runs-on: ubuntu-22.04 24 | steps: 25 | - uses: actions/checkout@v2 26 | - name: build 27 | run: | 28 | tag=$(git rev-parse --abbrev-ref HEAD) 29 | mkdir build 30 | cd build 31 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DLIBASSERT_WERROR_BUILD=On 32 | sudo make -j install 33 | sudo /sbin/ldconfig 34 | cd ../.. 35 | cp -rv libassert/tests/findpackage-integration . 36 | mkdir findpackage-integration/build 37 | cd findpackage-integration/build 38 | cmake .. -DCMAKE_BUILD_TYPE=Debug 39 | make 40 | ./main 41 | test-linux-add_subdirectory: 42 | runs-on: ubuntu-22.04 43 | steps: 44 | - uses: actions/checkout@v2 45 | - name: build 46 | run: | 47 | cd .. 48 | cp -rv libassert/tests/add_subdirectory-integration . 49 | cp -rv libassert add_subdirectory-integration 50 | mkdir add_subdirectory-integration/build 51 | cd add_subdirectory-integration/build 52 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DLIBASSERT_WERROR_BUILD=On 53 | make 54 | ./main 55 | 56 | test-macos-fetchcontent: 57 | runs-on: macos-14 58 | steps: 59 | - uses: actions/checkout@v2 60 | - name: test 61 | run: | 62 | tag=$(git rev-parse --abbrev-ref HEAD) 63 | cd .. 64 | cp -rv libassert/tests/fetchcontent-integration . 65 | mkdir fetchcontent-integration/build 66 | cd fetchcontent-integration/build 67 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DLIBASSERT_TAG=$tag -DLIBASSERT_WERROR_BUILD=On 68 | make 69 | ./main 70 | test-macos-findpackage: 71 | runs-on: macos-14 72 | steps: 73 | - uses: actions/checkout@v2 74 | - name: build 75 | run: | 76 | tag=$(git rev-parse --abbrev-ref HEAD) 77 | mkdir build 78 | cd build 79 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DLIBASSERT_WERROR_BUILD=On 80 | sudo make -j install 81 | cd ../.. 82 | cp -rv libassert/tests/findpackage-integration . 83 | mkdir findpackage-integration/build 84 | cd findpackage-integration/build 85 | cmake .. -DCMAKE_BUILD_TYPE=Debug 86 | make 87 | ./main 88 | test-macos-add_subdirectory: 89 | runs-on: macos-14 90 | steps: 91 | - uses: actions/checkout@v2 92 | - name: test 93 | run: | 94 | cd .. 95 | cp -rv libassert/tests/add_subdirectory-integration . 96 | cp -rv libassert add_subdirectory-integration 97 | mkdir add_subdirectory-integration/build 98 | cd add_subdirectory-integration/build 99 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DLIBASSERT_WERROR_BUILD=On 100 | make 101 | ./main 102 | 103 | test-mingw-fetchcontent: 104 | runs-on: windows-2022 105 | steps: 106 | - uses: actions/checkout@v2 107 | - name: test 108 | run: | 109 | $tag=$(git rev-parse --abbrev-ref HEAD) 110 | cd .. 111 | cp -Recurse libassert/tests/fetchcontent-integration . 112 | mkdir fetchcontent-integration/build 113 | cd fetchcontent-integration/build 114 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DLIBASSERT_TAG="$tag" "-GUnix Makefiles" -DLIBASSERT_WERROR_BUILD=On 115 | make 116 | .\main.exe 117 | test-mingw-findpackage: 118 | runs-on: windows-2022 119 | steps: 120 | - uses: actions/checkout@v2 121 | - name: test 122 | run: | 123 | $tag=$(git rev-parse --abbrev-ref HEAD) 124 | mkdir build 125 | cd build 126 | cmake .. -DCMAKE_BUILD_TYPE=Debug "-GUnix Makefiles" -DCMAKE_INSTALL_PREFIX=C:/foo -DLIBASSERT_WERROR_BUILD=On 127 | make -j install 128 | cd ../.. 129 | cp -Recurse libassert/tests/findpackage-integration . 130 | mkdir findpackage-integration/build 131 | cd findpackage-integration/build 132 | cmake .. -DCMAKE_BUILD_TYPE=Debug "-GUnix Makefiles" -DCMAKE_PREFIX_PATH=C:/foo 133 | make 134 | ./main.exe 135 | test-mingw-add_subdirectory: 136 | runs-on: windows-2022 137 | steps: 138 | - uses: actions/checkout@v2 139 | - name: test 140 | run: | 141 | cd .. 142 | cp -Recurse libassert/tests/add_subdirectory-integration . 143 | cp -Recurse libassert add_subdirectory-integration 144 | mkdir add_subdirectory-integration/build 145 | cd add_subdirectory-integration/build 146 | cmake .. -DCMAKE_BUILD_TYPE=Debug "-GUnix Makefiles" -DLIBASSERT_WERROR_BUILD=On 147 | make 148 | .\main.exe 149 | test-windows-fetchcontent: 150 | runs-on: windows-2022 151 | steps: 152 | - uses: actions/checkout@v2 153 | - name: Enable Developer Command Prompt 154 | uses: ilammy/msvc-dev-cmd@v1.10.0 155 | - name: test 156 | run: | 157 | $tag=$(git rev-parse --abbrev-ref HEAD) 158 | cd .. 159 | cp -Recurse libassert/tests/fetchcontent-integration . 160 | mkdir fetchcontent-integration/build 161 | cd fetchcontent-integration/build 162 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DLIBASSERT_TAG="$tag" -DLIBASSERT_WERROR_BUILD=On 163 | msbuild demo_project.sln 164 | .\Debug\main.exe 165 | test-windows-findpackage: 166 | runs-on: windows-2022 167 | steps: 168 | - uses: actions/checkout@v2 169 | - name: Enable Developer Command Prompt 170 | uses: ilammy/msvc-dev-cmd@v1.10.0 171 | - name: test 172 | run: | 173 | $tag=$(git rev-parse --abbrev-ref HEAD) 174 | mkdir build 175 | cd build 176 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=C:/foo -DLIBASSERT_WERROR_BUILD=On 177 | msbuild .\libassert.sln 178 | msbuild INSTALL.vcxproj 179 | cd ../.. 180 | cp -Recurse libassert/tests/findpackage-integration . 181 | mkdir findpackage-integration/build 182 | cd findpackage-integration/build 183 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=C:/foo 184 | msbuild demo_project.sln 185 | .\Debug\main.exe 186 | test-windows-add_subdirectory: 187 | runs-on: windows-2022 188 | steps: 189 | - uses: actions/checkout@v2 190 | - name: Enable Developer Command Prompt 191 | uses: ilammy/msvc-dev-cmd@v1.10.0 192 | - name: test 193 | run: | 194 | cd .. 195 | cp -Recurse libassert/tests/add_subdirectory-integration . 196 | cp -Recurse libassert add_subdirectory-integration 197 | mkdir add_subdirectory-integration/build 198 | cd add_subdirectory-integration/build 199 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DLIBASSERT_WERROR_BUILD=On 200 | msbuild demo_project.sln 201 | .\Debug\main.exe 202 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | clang-tidy: 9 | runs-on: ubuntu-22.04 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: dependencies 13 | run: | 14 | sudo apt install clang-tidy 15 | mkdir build 16 | cd build 17 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_COMPILER=clang++-14 -DCMAKE_C_COMPILER=clang-14 18 | - name: clang-tidy 19 | run: | 20 | chmod +x lint.sh 21 | # ./lint.sh 22 | 23 | # Check that we're not merging the dev branch into main 24 | (grep 'LIBASSERT_CPPTRACE_TAG "origin/dev"' CMakeLists.txt && exit 1) || exit 0 25 | -------------------------------------------------------------------------------- /.github/workflows/sonarlint.yml: -------------------------------------------------------------------------------- 1 | on: 2 | # Trigger analysis when pushing in master or pull requests, and when creating 3 | # a pull request. 4 | push: 5 | branches: 6 | - main 7 | - dev 8 | name: sonarlint 9 | jobs: 10 | sonarcloud: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | # Disabling shallow clone is recommended for improving relevancy of reporting 16 | fetch-depth: 0 17 | - name: Install sonar-scanner and build-wrapper 18 | uses: sonarsource/sonarcloud-github-c-cpp@v2 19 | - name: Run build-wrapper 20 | run: | 21 | mkdir build 22 | cd build 23 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCMAKE_CXX_STANDARD=17 24 | make -j 25 | cd .. 26 | - name: Run sonar-scanner 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 30 | run: sonar-scanner 31 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | # TODO: Check different standards? 8 | # TODO: Re-enable testing in release 9 | 10 | jobs: 11 | test-linux: 12 | runs-on: ubuntu-22.04 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | compiler: [g++-10, clang++-14] 17 | cxx_version: [ cxx_std_17, cxx_std_20 ] 18 | target: [Debug] 19 | shared: [ON, OFF] 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: dependencies 23 | run: | 24 | sudo apt install gcc-10 g++-10 libgcc-10-dev 25 | python3 -m pip install git+https://github.com/jeffkaufman/icdiff.git 26 | - name: build 27 | run: | 28 | mkdir -p build 29 | cd build 30 | cmake .. \ 31 | -DCMAKE_BUILD_TYPE=${{matrix.target}} \ 32 | -DCMAKE_CXX_COMPILER=${{matrix.compiler}} \ 33 | -DLIBASSERT_DESIRED_CXX_STANDARD="${{matrix.cxx_version}}" \ 34 | -DLIBASSERT_BUILD_SHARED=${{matrix.shared}} \ 35 | -DCPPTRACE_BUILD_SHARED=ON \ 36 | -DLIBASSERT_BUILD_TESTING=On \ 37 | -DLIBASSERT_WERROR_BUILD=On 38 | make -j 39 | - name: test 40 | working-directory: build 41 | run: | 42 | CTEST_OUTPUT_ON_FAILURE=1 make test 43 | # test-linux-gcc-8: 44 | # runs-on: ubuntu-20.04 45 | # strategy: 46 | # fail-fast: false 47 | # matrix: 48 | # compiler: [g++-8] 49 | # target: [Debug] 50 | # steps: 51 | # - uses: actions/checkout@v2 52 | # - name: dependencies 53 | # run: | 54 | # sudo apt install gcc-8 g++-8 libgcc-8-dev 55 | # python3 -m pip install git+https://github.com/jeffkaufman/icdiff.git 56 | # - name: build 57 | # run: | 58 | # mkdir -p build 59 | # cd build 60 | # cmake .. \ 61 | # -DCMAKE_BUILD_TYPE=${{matrix.target}} \ 62 | # -DCMAKE_CXX_COMPILER=${{matrix.compiler}} \ 63 | # -DLIBASSERT_BUILD_TESTING=On \ 64 | # -DLIBASSERT_WERROR_BUILD=On 65 | # make -j 66 | # - name: test 67 | # working-directory: build 68 | # run: | 69 | # CTEST_OUTPUT_ON_FAILURE=1 make test 70 | test-macos: 71 | runs-on: macos-14 72 | strategy: 73 | fail-fast: false 74 | matrix: 75 | compiler: [g++-12, clang++] 76 | cxx_version: [ cxx_std_17, cxx_std_20 ] 77 | target: [Debug] 78 | shared: [ON, OFF] 79 | steps: 80 | - uses: actions/checkout@v2 81 | - name: dependencies 82 | run: | 83 | brew install icdiff 84 | - name: build 85 | run: | 86 | mkdir -p build 87 | cd build 88 | cmake .. \ 89 | -DCMAKE_BUILD_TYPE=${{matrix.target}} \ 90 | -DCMAKE_CXX_COMPILER=${{matrix.compiler}} \ 91 | -DLIBASSERT_DESIRED_CXX_STANDARD="${{matrix.cxx_version}}" \ 92 | -DCPPTRACE_BUILD_SHARED=ON \ 93 | -DLIBASSERT_BUILD_TESTING=On \ 94 | -DLIBASSERT_WERROR_BUILD=On 95 | make -j 96 | - name: test 97 | working-directory: build 98 | run: | 99 | CTEST_OUTPUT_ON_FAILURE=1 make test 100 | test-windows: 101 | runs-on: windows-2022 102 | strategy: 103 | fail-fast: false 104 | matrix: 105 | compiler: [cl, clang++] 106 | cxx_version: [ cxx_std_17, cxx_std_20 ] 107 | target: [Debug] 108 | shared: [ON, OFF] 109 | steps: 110 | - uses: actions/checkout@v2 111 | - name: Enable Developer Command Prompt 112 | uses: ilammy/msvc-dev-cmd@v1.10.0 113 | - name: dependencies 114 | run: | 115 | pip install icdiff 116 | - name: build msvc 117 | # if: ${{ matrix.compiler == 'cl' }} 118 | run: | 119 | mkdir -p build 120 | cd build 121 | cmake .. ` 122 | -DCMAKE_BUILD_TYPE=${{matrix.target}} ` 123 | -DCMAKE_CXX_COMPILER=${{matrix.compiler}} ` 124 | -DLIBASSERT_DESIRED_CXX_STANDARD="${{matrix.cxx_version}}" ` 125 | -DCPPTRACE_BUILD_SHARED=ON ` 126 | -DLIBASSERT_BUILD_TESTING=On ` 127 | -DLIBASSERT_WERROR_BUILD=On 128 | msbuild .\libassert.sln 129 | # - name: build clang 130 | # if: ${{ matrix.compiler == 'clang++' }} 131 | # run: | 132 | # mkdir -p build 133 | # cd build 134 | # cmake .. ` 135 | # -DCMAKE_BUILD_TYPE=${{matrix.target}} ` 136 | # -DCMAKE_CXX_COMPILER="C:\msys64\mingw64\bin\clang++" ` 137 | # -DCMAKE_C_COMPILER="C:\msys64\mingw64\bin\clang" ` 138 | # -DLIBASSERT_BUILD_TESTING=On \ 139 | # -DLIBASSERT_WERROR_BUILD=On 140 | # msbuild .\libassert.sln 141 | - name: test 142 | working-directory: build 143 | run: | 144 | ctest -C ${{matrix.target}} --output-on-failure 145 | test-mingw: 146 | runs-on: windows-2022 147 | strategy: 148 | fail-fast: false 149 | matrix: 150 | compiler: [g++] 151 | cxx_version: [ cxx_std_17, cxx_std_20 ] 152 | target: [Debug] 153 | shared: [ON, OFF] 154 | steps: 155 | - uses: actions/checkout@v2 156 | - name: Enable Developer Command Prompt 157 | uses: ilammy/msvc-dev-cmd@v1.10.0 158 | - name: dependencies 159 | run: | 160 | pip install icdiff 161 | - name: build 162 | run: | 163 | mkdir -p build 164 | cd build 165 | cmake .. ` 166 | -DCMAKE_BUILD_TYPE=${{matrix.target}} ` 167 | -DCMAKE_CXX_COMPILER=${{matrix.compiler}} ` 168 | -DLIBASSERT_DESIRED_CXX_STANDARD="${{matrix.cxx_version}}" ` 169 | -DCPPTRACE_BUILD_SHARED=ON ` 170 | -DLIBASSERT_BUILD_TESTING=On ` 171 | -DLIBASSERT_USE_CI_WRAPPER=On ` 172 | "-GUnix Makefiles" ` 173 | -DLIBASSERT_WERROR_BUILD=On 174 | make -j 175 | - name: test 176 | working-directory: build 177 | run: | 178 | $env:CTEST_OUTPUT_ON_FAILURE=1 179 | make test 180 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | .vscode 3 | .cache 4 | .idea 5 | scratch 6 | build* 7 | cmake-build-* 8 | __pycache__ 9 | compile_commands.json 10 | CMakeUserPresets.json 11 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | include(cmake/PreventInSourceBuilds.cmake) 4 | 5 | 6 | # ---- Initialize Project ---- 7 | 8 | # used to support find_package 9 | set(package_name "libassert") 10 | 11 | 12 | # create base project 13 | project( 14 | libassert 15 | VERSION 2.1.5 16 | DESCRIPTION "The most over-engineered C++ assertion library" 17 | HOMEPAGE_URL "https://github.com/jeremy-rifkin/libassert" 18 | LANGUAGES CXX 19 | ) 20 | 21 | # don't change include order, OptionVariables checks if project is top level 22 | include(cmake/ProjectIsTopLevel.cmake) 23 | include(cmake/OptionVariables.cmake) 24 | 25 | if(PROJECT_IS_TOP_LEVEL) 26 | find_program(CCACHE_FOUND ccache) 27 | if(CCACHE_FOUND) 28 | set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) 29 | endif() 30 | endif() 31 | 32 | if(PROJECT_IS_TOP_LEVEL) 33 | if(CMAKE_GENERATOR STREQUAL "Ninja") 34 | if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 35 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always") 36 | elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") 37 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics") 38 | endif() 39 | if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") 40 | SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdiagnostics-color=always") 41 | elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") 42 | SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics") 43 | endif() 44 | endif() 45 | endif() 46 | 47 | 48 | # ---- Project Dependencies ---- 49 | 50 | set(LIBASSERT_MAGIC_ENUM_REPO "https://github.com/Neargye/magic_enum.git") 51 | set(LIBASSERT_MAGIC_ENUM_TAG "e046b69a3736d314fad813e159b1c192eaef92cd") # v0.9.7 52 | 53 | set(LIBASSERT_CPPTRACE_REPO "https://github.com/jeremy-rifkin/cpptrace.git") 54 | set(LIBASSERT_CPPTRACE_TAG "c37b5ed7364f4fc1c58e92d13399cd04656e6572") # v0.8.2 55 | 56 | # obtain cpptrace 57 | if(LIBASSERT_USE_EXTERNAL_CPPTRACE) 58 | find_package(cpptrace REQUIRED) 59 | else() 60 | include(FetchContent) 61 | FetchContent_Declare( 62 | cpptrace 63 | GIT_REPOSITORY "${LIBASSERT_CPPTRACE_REPO}" 64 | GIT_TAG "${LIBASSERT_CPPTRACE_TAG}" 65 | ) 66 | FetchContent_MakeAvailable(cpptrace) 67 | endif() 68 | 69 | # cpptrace potentially does not have an alias target 70 | if(NOT TARGET cpptrace::cpptrace) 71 | add_library(cpptrace::cpptrace ALIAS cpptrace) 72 | endif() 73 | 74 | 75 | # ---- Declare Library ---- 76 | 77 | # target that we can modify (can't modify ALIAS targets) 78 | # target name should not be the same as ${PROJECT_NAME}, causes add_subdirectory issues 79 | set(target_name "libassert-lib") 80 | add_library(${target_name} ${build_type}) 81 | 82 | # alias to cause error at configuration time instead of link time if target is missing 83 | add_library(libassert::assert ALIAS ${target_name}) 84 | 85 | # add /include files to target 86 | # this is solely for IDE benefit, doesn't affect building, so unconditionally list magic_enum 87 | target_sources( 88 | ${target_name} PRIVATE 89 | # include 90 | include/libassert/assert.hpp 91 | include/libassert/platform.hpp 92 | ) 93 | 94 | # add /src files to target 95 | target_sources( 96 | ${target_name} PRIVATE 97 | # src 98 | src/assert.cpp 99 | src/analysis.cpp 100 | src/utils.cpp 101 | src/stringification.cpp 102 | src/platform.cpp 103 | src/printing.cpp 104 | src/paths.cpp 105 | src/tokenizer.cpp 106 | ) 107 | 108 | # link dependencies 109 | target_link_libraries( 110 | ${target_name} PUBLIC 111 | cpptrace::cpptrace 112 | ) 113 | 114 | set( 115 | warning_options 116 | $<$>:-Wall -Wextra -Werror=return-type -Wundef> 117 | $<$:-Wuseless-cast -Wmaybe-uninitialized> 118 | $<$:/W4 /permissive-> 119 | ) 120 | 121 | if(LIBASSERT_WERROR_BUILD) 122 | set( 123 | warning_options 124 | ${warning_options} 125 | $<$>:-Werror> 126 | $<$:/WX> 127 | ) 128 | endif() 129 | 130 | target_compile_options( 131 | ${target_name} 132 | PRIVATE 133 | ${warning_options} 134 | ) 135 | 136 | set(LIBASSERT_VERSION_MAJOR ${CMAKE_PROJECT_VERSION_MAJOR}) 137 | set(LIBASSERT_VERSION_MINOR ${CMAKE_PROJECT_VERSION_MINOR}) 138 | set(LIBASSERT_VERSION_PATCH ${CMAKE_PROJECT_VERSION_PATCH}) 139 | configure_file("${PROJECT_SOURCE_DIR}/cmake/in/version-hpp.in" "${PROJECT_BINARY_DIR}/include/libassert/version.hpp") 140 | 141 | # ---- Generate Build Info Headers ---- 142 | 143 | # used in export header generated below 144 | if(build_type STREQUAL "STATIC") 145 | target_compile_definitions(${target_name} PUBLIC LIBASSERT_STATIC_DEFINE) 146 | set(LIBASSERT_STATIC_DEFINE TRUE) 147 | endif() 148 | 149 | # ---- Library Properties ---- 150 | 151 | # hide all symbols by default 152 | # use SameMajorVersion versioning for shared library runtime linker lookup 153 | set_target_properties( 154 | ${target_name} PROPERTIES 155 | CXX_VISIBILITY_PRESET hidden 156 | VISIBILITY_INLINES_HIDDEN YES 157 | VERSION "${PROJECT_VERSION}" 158 | SOVERSION "${PROJECT_VERSION_MAJOR}" 159 | EXPORT_NAME "assert" 160 | OUTPUT_NAME "assert" 161 | ) 162 | 163 | # header files generated by CMake 164 | target_include_directories( 165 | ${target_name} SYSTEM PUBLIC 166 | "$" 167 | ) 168 | 169 | # header files from /include 170 | target_include_directories( 171 | ${target_name} ${warning_guard} PUBLIC 172 | "$" 173 | ) 174 | 175 | target_compile_features( 176 | ${target_name} 177 | PUBLIC ${LIBASSERT_DESIRED_CXX_STANDARD} 178 | ) 179 | 180 | if(LIBASSERT_SANITIZER_BUILD) 181 | add_compile_options(-fsanitize=address) 182 | add_link_options(-fsanitize=address) 183 | endif() 184 | 185 | if( 186 | LIBASSERT_BUILD_TESTING AND NOT ( 187 | "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND "${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 9.0 188 | ) 189 | ) 190 | set(LIBASSERT_USE_MAGIC_ENUM ON) 191 | endif() 192 | 193 | if(LIBASSERT_USE_MAGIC_ENUM) 194 | set(MAGIC_ENUM_OPT_INSTALL ON) 195 | if(LIBASSERT_USE_EXTERNAL_MAGIC_ENUM) 196 | find_package(magic_enum REQUIRED) 197 | else() 198 | include(FetchContent) 199 | FetchContent_Declare(magic_enum 200 | GIT_REPOSITORY "${LIBASSERT_MAGIC_ENUM_REPO}" 201 | GIT_TAG "${LIBASSERT_MAGIC_ENUM_TAG}") 202 | FetchContent_MakeAvailable(magic_enum) 203 | endif() 204 | target_link_libraries(${target_name} PUBLIC magic_enum::magic_enum) 205 | target_compile_definitions( 206 | ${target_name} PUBLIC 207 | # $<$:ASSERT_USE_MAGIC_ENUM> 208 | LIBASSERT_USE_MAGIC_ENUM 209 | ) 210 | endif() 211 | 212 | 213 | # ---- Install Rules ---- 214 | 215 | if(NOT CMAKE_SKIP_INSTALL_RULES) 216 | include(cmake/InstallRules.cmake) 217 | endif() 218 | 219 | 220 | # ---- Setup Tests ---- 221 | 222 | if(LIBASSERT_BUILD_TESTING) 223 | target_compile_definitions(${target_name} PRIVATE LIBASSERT_BUILD_TESTING) 224 | 225 | # need to enable testing in case BUILD_TESTING is disabled 226 | # ctest expects that the top level project enables testing 227 | if(PROJECT_IS_TOP_LEVEL) 228 | enable_testing() 229 | endif() 230 | 231 | # tell unit tests where our files are 232 | set(LIBASSERT_BINARY_DIR "${PROJECT_BINARY_DIR}") 233 | set(LIBASSERT_SOURCE_DIR "${PROJECT_SOURCE_DIR}") 234 | 235 | # include test project 236 | # add_subdirectory(tests) 237 | include(tests/CMakeLists.txt) 238 | endif() 239 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Welcome, thank you for your interest in the project! 4 | 5 | ## Getting started 6 | 7 | Contributions are always welcome. If you have not already, consider joining the community discord 8 | (linked in the README). There is discussion about library development there as well as a development 9 | roadmap. Github issues are also a good place to start. 10 | 11 | I'm happy to merge fixes, improvements, and features as well as help with getting pull requests 12 | (PRs) over the finish line. That being said, I can't merge stylistic changes, 13 | premature-optimizations, or micro-optimizations. 14 | 15 | When contributing, please try to match the current code style in the codebase. Style doesn't matter 16 | too much ultimately but consistency within a codebase is important. 17 | 18 | ## Local development 19 | 20 | The easiest way to develop locally is to run `make build` which will handle cmake invocation and 21 | build in `build/`. Alternatively you can manually run `cmake ..`, along with any cmake 22 | configurations you desire in a build folder. Then run `make -j`, `ninja`, or 23 | `msbuild libassert.sln`. 24 | 25 | Some useful configurations: 26 | - `-DCMAKE_BUILD_TYPE=Debug|Release|RelWithDebInfo`: Build in debug / release / etc. 27 | - `-DBUILD_SHARED_LIBS=On`: Build shared library 28 | - `-DLIBSANITIZER_BUILD=On`: Turn on sanitizers 29 | - `-DLIBASSERT_BUILD_TESTING=On`: Build test and demo programs 30 | 31 | ## Testing 32 | 33 | Run `make test`, `ninja test`, or `msbuild RUN_TESTS.vcxproj` to run tests. Unfortunately testing 34 | for this project is not in a great state at the moment and while there is some unit testing it 35 | relies heavily on integration testing that is hard to maintain. 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-2024 Jeremy Rifkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial 12 | portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES 17 | OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: help 2 | 3 | # The general philosophy and functionality of this makefile is shamelessly stolen from compiler explorer 4 | 5 | help: # with thanks to Ben Rady 6 | @grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' 7 | 8 | .PHONY: build 9 | build: debug ## build in debug mode 10 | 11 | build/configured-debug: 12 | cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DLIBASSERT_BUILD_TESTING=On -DLIBASSERT_WERROR_BUILD=On 13 | rm -f build/configured-release 14 | touch build/configured-debug 15 | 16 | build/configured-release: 17 | cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DLIBASSERT_BUILD_TESTING=On -DLIBASSERT_WERROR_BUILD=On 18 | rm -f build/configured-debug 19 | touch build/configured-release 20 | 21 | .PHONY: configure-debug 22 | configure-debug: build/configured-debug 23 | 24 | .PHONY: configure-release 25 | configure-release: build/configured-release 26 | 27 | .PHONY: debug 28 | debug: configure-debug ## build in debug mode 29 | cmake --build build 30 | 31 | .PHONY: release 32 | release: configure-release ## build in release mode (with debug info) 33 | cmake --build build 34 | 35 | .PHONY: debug-msvc 36 | debug-msvc: ## build in debug mode 37 | cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DLIBASSERT_BUILD_TESTING=On 38 | cmake --build build --config Debug 39 | 40 | .PHONY: release-msvc 41 | release-msvc: ## build in release mode (with debug info) 42 | cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DLIBASSERT_BUILD_TESTING=On 43 | cmake --build build --config RelWithDebInfo 44 | 45 | .PHONY: clean 46 | clean: ## clean 47 | rm -rf build 48 | 49 | .PHONY: test 50 | test: debug ## test 51 | cd build && ninja test 52 | 53 | .PHONY: test-release 54 | test-release: release ## test-release 55 | cd build && ninja test 56 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | We take security seriously and I'm grateful for reports of security vulnerabilities. 4 | 5 | ## Reporting a Vulnerability 6 | 7 | If the vulnerability can be reported without revealing exploitable specifics, please open an issue. 8 | 9 | If the vulnerability can't be reported publically without leaving an obvious exploit in the public eye please email me 10 | at jeremy@rifkin.dev or reach out to me on [discord](https://discord.gg/7kv5AuCndG). 11 | 12 | I will do my best to get back to you within a day. 13 | -------------------------------------------------------------------------------- /cmake/InstallRules.cmake: -------------------------------------------------------------------------------- 1 | include(CMakePackageConfigHelpers) 2 | 3 | # copy header files to CMAKE_INSTALL_INCLUDEDIR 4 | # don't include third party header files 5 | install( 6 | DIRECTORY 7 | "${PROJECT_SOURCE_DIR}/include/" # our header files 8 | "${PROJECT_BINARY_DIR}/include/" # generated header files 9 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 10 | COMPONENT ${package_name}-development 11 | ) 12 | 13 | # copy target build output artifacts to OS dependent locations 14 | # (Except includes, that just sets a compiler flag with the path) 15 | install( 16 | TARGETS ${target_name} 17 | EXPORT ${package_name}-targets 18 | RUNTIME # 19 | COMPONENT ${package_name}-runtime 20 | LIBRARY # 21 | COMPONENT ${package_name}-runtime 22 | NAMELINK_COMPONENT ${package_name}-development 23 | ARCHIVE # 24 | COMPONENT ${package_name}-development 25 | INCLUDES # 26 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 27 | ) 28 | 29 | # create config file that points to targets file 30 | configure_file( 31 | "${PROJECT_SOURCE_DIR}/cmake/in/libassert-config-cmake.in" 32 | "${PROJECT_BINARY_DIR}/cmake/${package_name}-config.cmake" 33 | @ONLY 34 | ) 35 | 36 | # copy config file for find_package to find 37 | install( 38 | FILES "${PROJECT_BINARY_DIR}/cmake/${package_name}-config.cmake" 39 | DESTINATION "${LIBASSERT_INSTALL_CMAKEDIR}" 40 | COMPONENT ${package_name}-development 41 | ) 42 | 43 | # create version file for consumer to check version in CMake 44 | write_basic_package_version_file( 45 | "${package_name}-config-version.cmake" 46 | COMPATIBILITY SameMajorVersion # a.k.a SemVer 47 | ) 48 | 49 | # copy version file for find_package to find for version check 50 | install( 51 | FILES "${PROJECT_BINARY_DIR}/${package_name}-config-version.cmake" 52 | DESTINATION "${LIBASSERT_INSTALL_CMAKEDIR}" 53 | COMPONENT ${package_name}-development 54 | ) 55 | 56 | # create targets file included by config file with targets for consumers 57 | install( 58 | EXPORT ${package_name}-targets 59 | NAMESPACE libassert:: 60 | DESTINATION "${LIBASSERT_INSTALL_CMAKEDIR}" 61 | COMPONENT ${package_name}-development 62 | ) 63 | 64 | if(LIBASSERT_PROVIDE_EXPORT_SET) 65 | export( 66 | TARGETS ${target_name} 67 | NAMESPACE libassert:: 68 | FILE "${PROJECT_BINARY_DIR}/${package_name}-targets.cmake" 69 | ) 70 | endif() 71 | 72 | # support packaging library 73 | if(PROJECT_IS_TOP_LEVEL) 74 | include(CPack) 75 | endif() 76 | -------------------------------------------------------------------------------- /cmake/OptionVariables.cmake: -------------------------------------------------------------------------------- 1 | # included further down to avoid interfering with our cache variables 2 | # include(GNUInstallDirs) 3 | 4 | # Sometimes it's useful to be able to single out a dependency to be built as 5 | # static or shared, even if obtained from source 6 | if(PROJECT_IS_TOP_LEVEL) 7 | option(BUILD_SHARED_LIBS "Build shared libs" OFF) 8 | endif() 9 | option( 10 | LIBASSERT_BUILD_SHARED 11 | "Override BUILD_SHARED_LIBS for ${package_name} library" 12 | ${BUILD_SHARED_LIBS} 13 | ) 14 | mark_as_advanced(LIBASSERT_BUILD_SHARED) 15 | set(build_type STATIC) 16 | if(LIBASSERT_BUILD_SHARED) 17 | set(build_type SHARED) 18 | endif() 19 | 20 | # target_include_directories with SYSTEM modifier will request the compiler to 21 | # omit warnings from the provided paths, if the compiler supports that. 22 | # This is to provide a user experience similar to find_package when 23 | # add_subdirectory or FetchContent is used to consume this project. 24 | set(warning_guard) 25 | if(NOT PROJECT_IS_TOP_LEVEL) 26 | option( 27 | LIBASSERT_INCLUDES_WITH_SYSTEM 28 | "Use SYSTEM modifier for ${package_name}'s includes, disabling warnings" 29 | ON 30 | ) 31 | mark_as_advanced(LIBASSERT_INCLUDES_WITH_SYSTEM) 32 | if(LIBASSERT_INCLUDES_WITH_SYSTEM) 33 | set(warning_guard SYSTEM) 34 | endif() 35 | endif() 36 | 37 | # By default tests aren't enabled even with BUILD_TESTING=ON unless the library 38 | # is built as a top level project. 39 | # This is in order to cut down on unnecessary compile times, since it's unlikely 40 | # for users to want to run the tests of their dependencies. 41 | if(PROJECT_IS_TOP_LEVEL) 42 | option(BUILD_TESTING "Build tests" OFF) 43 | option(LIBASSERT_SANITIZER_BUILD "Build with sanitizers" OFF) 44 | endif() 45 | if(PROJECT_IS_TOP_LEVEL AND BUILD_TESTING) 46 | set(build_testing ON) 47 | endif() 48 | option( 49 | LIBASSERT_BUILD_TESTING 50 | "Override BUILD_TESTING for ${package_name} library" 51 | ${build_testing} 52 | ) 53 | set(build_testing) 54 | mark_as_advanced(LIBASSERT_BUILD_TESTING) 55 | 56 | # Adds an extra directory to the include path by default, so that when you link 57 | # against the target, you get `/include//include`. 59 | # This doesn't affect include paths used by consumers of this project, but helps 60 | # prevent consumers having access to other projects in the same include 61 | # directory (e.g. usr/include). 62 | # The variable type is STRING rather than PATH, because otherwise passing 63 | # -DCMAKE_INSTALL_INCLUDEDIR=include on the command line would expand to an 64 | # absolute path with the base being the current CMake directory, leading to 65 | # unexpected errors. 66 | # if(PROJECT_IS_TOP_LEVEL) 67 | # set( 68 | # CMAKE_INSTALL_INCLUDEDIR "include/${package_name}-${PROJECT_VERSION}" 69 | # CACHE STRING "" 70 | # ) 71 | # # marked as advanced in GNUInstallDirs version, so we follow their lead 72 | # mark_as_advanced(CMAKE_INSTALL_INCLUDEDIR) 73 | # endif() 74 | # do not include earlier or we can't set CMAKE_INSTALL_INCLUDEDIR above 75 | # include required for CMAKE_INSTALL_LIBDIR below 76 | include(GNUInstallDirs) 77 | 78 | # This allows package maintainers to freely override the installation path for 79 | # the CMake configs. 80 | # This doesn't affects include paths used by consumers of this project. 81 | # The variable type is STRING rather than PATH, because otherwise passing 82 | # -DLIBASSERT_INSTALL_CMAKEDIR=lib/cmake on the command line would expand to an 83 | # absolute path with the base being the current CMake directory, leading to 84 | # unexpected errors. 85 | set( 86 | LIBASSERT_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${package_name}" 87 | CACHE STRING "CMake package config location relative to the install prefix" 88 | ) 89 | # depends on CMAKE_INSTALL_LIBDIR which is marked as advanced in GNUInstallDirs 90 | mark_as_advanced(LIBASSERT_INSTALL_CMAKEDIR) 91 | 92 | # Enables obtaining cpptrace via find_package instead of using FetchContent to 93 | # obtain it from the official GitHub repo 94 | option(LIBASSERT_USE_EXTERNAL_CPPTRACE "Obtain cpptrace via find_package instead of FetchContent" OFF) 95 | 96 | # Enables the use of the magic_enum library in order to provide better 97 | # diagnostic messages for enum class types. 98 | # Because magic_enum is used in the our public header file, magic_enum is 99 | # packaged alongside the library when installed if this option is enabled. 100 | option( 101 | LIBASSERT_USE_MAGIC_ENUM 102 | "Use magic_enum library to print better diagnostics for enum classes (will also be included in ${package_name} package installation)" 103 | OFF 104 | ) 105 | option(LIBASSERT_USE_EXTERNAL_MAGIC_ENUM "Obtain magic_enum via find_package instead of FetchContent" OFF) 106 | 107 | option(LIBASSERT_WERROR_BUILD "" OFF) 108 | 109 | option(LIBASSERT_PROVIDE_EXPORT_SET "" ON) 110 | mark_as_advanced( 111 | LIBASSERT_PROVIDE_EXPORT_SET 112 | ) 113 | 114 | # -- internal -- 115 | 116 | set(LIBASSERT_DESIRED_CXX_STANDARD cxx_std_17 CACHE STRING "") 117 | option(LIBASSERT_USE_CI_WRAPPER "" OFF) 118 | mark_as_advanced( 119 | LIBASSERT_DESIRED_CXX_STANDARD 120 | LIBASSERT_USE_CI_WRAPPER 121 | ) 122 | -------------------------------------------------------------------------------- /cmake/PreventInSourceBuilds.cmake: -------------------------------------------------------------------------------- 1 | # in-source build guard 2 | if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) 3 | message( 4 | FATAL_ERROR 5 | "In-source builds are not supported. " 6 | "You may need to delete 'CMakeCache.txt' and 'CMakeFiles/' before rebuilding this project." 7 | ) 8 | endif() 9 | -------------------------------------------------------------------------------- /cmake/ProjectIsTopLevel.cmake: -------------------------------------------------------------------------------- 1 | # this variable is set by project() in CMake 3.21+ 2 | string( 3 | COMPARE EQUAL 4 | "${CMAKE_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}" 5 | PROJECT_IS_TOP_LEVEL 6 | ) 7 | -------------------------------------------------------------------------------- /cmake/in/libassert-config-cmake.in: -------------------------------------------------------------------------------- 1 | # init @ variables before doing anything else 2 | @PACKAGE_INIT@ 3 | 4 | # Dependencies 5 | include(CMakeFindDependencyMacro) 6 | find_dependency(cpptrace REQUIRED) 7 | if(@LIBASSERT_USE_MAGIC_ENUM@) 8 | find_dependency(magic_enum REQUIRED) 9 | endif() 10 | 11 | # We cannot modify an existing IMPORT target 12 | if(NOT TARGET libassert::assert) 13 | 14 | # import targets 15 | include("${CMAKE_CURRENT_LIST_DIR}/@package_name@-targets.cmake") 16 | 17 | endif() 18 | 19 | if(@LIBASSERT_STATIC_DEFINE@) 20 | target_compile_definitions(libassert::assert INTERFACE LIBASSERT_STATIC_DEFINE) 21 | endif() 22 | -------------------------------------------------------------------------------- /cmake/in/version-hpp.in: -------------------------------------------------------------------------------- 1 | #ifndef LIBASSERT_VERSION_HPP 2 | #define LIBASSERT_VERSION_HPP 3 | 4 | #define LIBASSERT_VERSION_MAJOR @LIBASSERT_VERSION_MAJOR@ 5 | #define LIBASSERT_VERSION_MINOR @LIBASSERT_VERSION_MINOR@ 6 | #define LIBASSERT_VERSION_PATCH @LIBASSERT_VERSION_PATCH@ 7 | #define LIBASSERT_TO_VERSION(MAJOR, MINOR, PATCH) ((MAJOR) * 10000 + (MINOR) * 100 + (PATCH)) 8 | #define LIBASSERT_VERSION LIBASSERT_TO_VERSION(LIBASSERT_VERSION_MAJOR, LIBASSERT_VERSION_MINOR, LIBASSERT_VERSION_PATCH) 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /include/libassert/assert-catch2.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIBASSERT_CATCH2_HPP 2 | #define LIBASSERT_CATCH2_HPP 3 | 4 | #define LIBASSERT_PREFIX_ASSERTIONS 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #if defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL != 0 11 | #error "Libassert integration does not work with MSVC's non-conformant preprocessor. /Zc:preprocessor must be used." 12 | #endif 13 | // TODO: CHECK/REQUIRE? 14 | #define ASSERT(...) do { try { LIBASSERT_ASSERT(__VA_ARGS__); SUCCEED(); } catch(std::exception& e) { FAIL(e.what()); } } while(false) 15 | 16 | namespace libassert::detail { 17 | // catch line wrapping can't handle ansi sequences before 3.6 https://github.com/catchorg/Catch2/issues/2833 18 | inline constexpr bool use_color = CATCH_VERSION_MAJOR > 3 || (CATCH_VERSION_MAJOR == 3 && CATCH_VERSION_MINOR >= 6); 19 | 20 | inline void catch2_failure_handler(const assertion_info& info) { 21 | if(use_color) { 22 | enable_virtual_terminal_processing_if_needed(); 23 | } 24 | auto scheme = use_color ? color_scheme::ansi_rgb : color_scheme::blank; 25 | std::string message = std::string(info.action()) + " at " + info.location() + ":"; 26 | if(info.message) { 27 | message += " " + *info.message; 28 | } 29 | message += "\n"; 30 | message += info.statement(scheme) 31 | + info.print_binary_diagnostics(CATCH_CONFIG_CONSOLE_WIDTH, scheme) 32 | + info.print_extra_diagnostics(CATCH_CONFIG_CONSOLE_WIDTH, scheme); 33 | throw std::runtime_error(std::move(message)); 34 | } 35 | 36 | inline auto pre_main = [] () { 37 | set_failure_handler(catch2_failure_handler); 38 | return 1; 39 | } (); 40 | } 41 | 42 | // Some testing utilities 43 | 44 | #define REQUIRE_ASSERT(expr) \ 45 | do { \ 46 | auto handler = ::libassert::get_failure_handler(); \ 47 | ::libassert::set_failure_handler([] (const ::libassert::assertion_info& info) { \ 48 | throw info; \ 49 | }); \ 50 | bool did_assert = false; \ 51 | try { \ 52 | (expr); \ 53 | } catch(const ::libassert::assertion_info& info) { \ 54 | did_assert = true; \ 55 | SUCCEED(); \ 56 | } \ 57 | if(!did_assert) { \ 58 | FAIL("Expected assertion failure from " #expr " however none happened"); \ 59 | } \ 60 | ::libassert::set_failure_handler(handler); \ 61 | } while(false) 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /include/libassert/assert-gtest.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIBASSERT_GTEST_HPP 2 | #define LIBASSERT_GTEST_HPP 3 | 4 | #include 5 | 6 | #define LIBASSERT_PREFIX_ASSERTIONS 7 | #include 8 | 9 | #if defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL != 0 10 | #error "Libassert integration does not work with MSVC's non-conformant preprocessor. /Zc:preprocessor must be used." 11 | #endif 12 | #define ASSERT(...) do { try { LIBASSERT_ASSERT(__VA_ARGS__); SUCCEED(); } catch(std::exception& e) { FAIL() << e.what(); } } while(false) 13 | #define EXPECT(...) do { try { LIBASSERT_ASSERT(__VA_ARGS__); SUCCEED(); } catch(std::exception& e) { ADD_FAILURE() << e.what(); } } while(false) 14 | 15 | namespace libassert::detail { 16 | inline void gtest_failure_handler(const assertion_info& info) { 17 | enable_virtual_terminal_processing_if_needed(); // for terminal colors on windows 18 | auto width = terminal_width(stderr_fileno); 19 | const auto& scheme = isatty(stderr_fileno) ? get_color_scheme() : color_scheme::blank; 20 | std::string message = std::string(info.action()) + " at " + info.location() + ":"; 21 | if(info.message) { 22 | message += " " + *info.message; 23 | } 24 | message += "\n"; 25 | message += info.statement() 26 | + info.print_binary_diagnostics(width, scheme) 27 | + info.print_extra_diagnostics(width, scheme); 28 | throw std::runtime_error(std::move(message)); 29 | } 30 | 31 | inline auto pre_main = [] () { 32 | set_failure_handler(gtest_failure_handler); 33 | return 1; 34 | } (); 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /include/libassert/platform.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIBASSERT_PLATFORM_HPP 2 | #define LIBASSERT_PLATFORM_HPP 3 | 4 | // Copyright (c) 2021-2024 Jeremy Rifkin under the MIT license 5 | // https://github.com/jeremy-rifkin/libassert 6 | 7 | #include 8 | 9 | // ===================================================================================================================== 10 | // || Preprocessor stuff || 11 | // ===================================================================================================================== 12 | 13 | // Set the C++ version number based on if we are on a dumb compiler like MSVC or not. 14 | #ifdef _MSVC_LANG 15 | #define LIBASSERT_CPLUSPLUS _MSVC_LANG 16 | #else 17 | #define LIBASSERT_CPLUSPLUS __cplusplus 18 | #endif 19 | 20 | #if LIBASSERT_CPLUSPLUS >= 202302L 21 | #define LIBASSERT_STD_VER 23 22 | #elif LIBASSERT_CPLUSPLUS >= 202002L 23 | #define LIBASSERT_STD_VER 20 24 | #elif LIBASSERT_CPLUSPLUS >= 201703L 25 | #define LIBASSERT_STD_VER 17 26 | #else 27 | #error "libassert requires C++17 or newer" 28 | #endif 29 | 30 | /// 31 | /// Detect compiler versions. 32 | /// 33 | 34 | #if defined(__clang__) && !defined(__ibmxl__) 35 | #define LIBASSERT_IS_CLANG 1 36 | #define LIBASSERT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) 37 | #else 38 | #define LIBASSERT_IS_CLANG 0 39 | #define LIBASSERT_CLANG_VERSION 0 40 | #endif 41 | 42 | #if (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) && !defined(__INTEL_COMPILER) 43 | #define LIBASSERT_IS_GCC 1 44 | #define LIBASSERT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) 45 | #else 46 | #define LIBASSERT_IS_GCC 0 47 | #define LIBASSERT_GCC_VERSION 0 48 | #endif 49 | 50 | #if defined(_MSC_VER) && !defined(__clang__) // clang on windows defines _MSC_VER 51 | #define LIBASSERT_IS_MSVC 1 52 | #define LIBASSERT_MSVC_VERSION _MSC_VER 53 | #else 54 | #define LIBASSERT_IS_MSVC 0 55 | #define LIBASSERT_MSVC_VERSION 0 56 | #endif 57 | 58 | #ifdef __INTEL_COMPILER 59 | #define LIBASSERT_IS_ICC 1 60 | #else 61 | #define LIBASSERT_IS_ICC 0 62 | #endif 63 | 64 | #ifdef __INTEL_LLVM_COMPILER 65 | #define LIBASSERT_IS_ICX 1 66 | #else 67 | #define LIBASSERT_IS_ICX 0 68 | #endif 69 | 70 | /// 71 | /// Detect standard library versions. 72 | /// 73 | 74 | // libstdc++ 75 | #ifdef _GLIBCXX_RELEASE 76 | #define LIBASSERT_GLIBCXX_RELEASE _GLIBCXX_RELEASE 77 | #else 78 | #define LIBASSERT_GLIBCXX_RELEASE 0 79 | #endif 80 | 81 | #ifdef _LIBCPP_VERSION 82 | #define LIBASSERT_LIBCPP_VERSION _LIBCPP_VERSION 83 | #else 84 | #define LIBASSERT_LIBCPP_VERSION 0 85 | #endif 86 | 87 | /// 88 | /// Helper macros for compiler attributes. 89 | /// 90 | 91 | #ifdef __has_cpp_attribute 92 | #define LIBASSERT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) 93 | #else 94 | #define LIBASSERT_HAS_CPP_ATTRIBUTE(x) 0 95 | #endif 96 | 97 | /// 98 | /// Compiler attribute support. 99 | /// 100 | 101 | #if LIBASSERT_HAS_CPP_ATTRIBUTE(nodiscard) && LIBASSERT_STD_VER >= 20 102 | #define LIBASSERT_ATTR_NODISCARD_MSG(msg) [[nodiscard(msg)]] 103 | #else // Assume we have normal C++17 nodiscard support. 104 | #define LIBASSERT_ATTR_NODISCARD_MSG(msg) [[nodiscard]] 105 | #endif 106 | 107 | 108 | #if LIBASSERT_HAS_CPP_ATTRIBUTE(no_unique_address) 109 | #define LIBASSERT_ATTR_NO_UNIQUE_ADDRESS [[no_unique_address]] 110 | #else 111 | #define LIBASSERT_ATTR_NO_UNIQUE_ADDRESS 112 | #endif 113 | 114 | /// 115 | /// General project macros 116 | /// 117 | 118 | #ifdef _WIN32 119 | #define LIBASSERT_EXPORT_ATTR __declspec(dllexport) 120 | #define LIBASSERT_IMPORT_ATTR __declspec(dllimport) 121 | #else 122 | #define LIBASSERT_EXPORT_ATTR __attribute__((visibility("default"))) 123 | #define LIBASSERT_IMPORT_ATTR __attribute__((visibility("default"))) 124 | #endif 125 | 126 | #ifdef LIBASSERT_STATIC_DEFINE 127 | #define LIBASSERT_EXPORT 128 | #define LIBASSERT_NO_EXPORT 129 | #else 130 | #ifndef LIBASSERT_EXPORT 131 | #ifdef libassert_lib_EXPORTS 132 | /* We are building this library */ 133 | #define LIBASSERT_EXPORT LIBASSERT_EXPORT_ATTR 134 | #else 135 | /* We are using this library */ 136 | #define LIBASSERT_EXPORT LIBASSERT_IMPORT_ATTR 137 | #endif 138 | #endif 139 | #endif 140 | 141 | #if LIBASSERT_IS_CLANG || LIBASSERT_IS_GCC 142 | #define LIBASSERT_PFUNC __extension__ __PRETTY_FUNCTION__ 143 | #define LIBASSERT_ATTR_COLD [[gnu::cold]] 144 | #define LIBASSERT_ATTR_NOINLINE [[gnu::noinline]] 145 | #define LIBASSERT_UNREACHABLE_CALL __builtin_unreachable() 146 | #else 147 | #define LIBASSERT_PFUNC __FUNCSIG__ 148 | #define LIBASSERT_ATTR_COLD 149 | #define LIBASSERT_ATTR_NOINLINE __declspec(noinline) 150 | #define LIBASSERT_UNREACHABLE_CALL __assume(false) 151 | #endif 152 | 153 | #if LIBASSERT_IS_MSVC 154 | #define LIBASSERT_STRONG_EXPECT(expr, value) (expr) 155 | #elif (defined(__clang__) && __clang_major__ >= 11) || __GNUC__ >= 9 156 | #define LIBASSERT_STRONG_EXPECT(expr, value) __builtin_expect_with_probability((expr), (value), 1) 157 | #else 158 | #define LIBASSERT_STRONG_EXPECT(expr, value) __builtin_expect((expr), (value)) 159 | #endif 160 | 161 | // deal with gcc shenanigans 162 | // at one point their std::string's move assignment was not noexcept even in c++17 163 | // https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html 164 | #if defined(_GLIBCXX_USE_CXX11_ABI) 165 | // NOLINTNEXTLINE(misc-include-cleaner) 166 | #define LIBASSERT_GCC_ISNT_STUPID _GLIBCXX_USE_CXX11_ABI 167 | #else 168 | // assume others target new abi by default - homework 169 | #define LIBASSERT_GCC_ISNT_STUPID 1 170 | #endif 171 | 172 | #if defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL 173 | #define LIBASSERT_NON_CONFORMANT_MSVC_PREPROCESSOR true 174 | #else 175 | #define LIBASSERT_NON_CONFORMANT_MSVC_PREPROCESSOR false 176 | #endif 177 | 178 | #if (LIBASSERT_IS_GCC || LIBASSERT_STD_VER >= 20) && !LIBASSERT_NON_CONFORMANT_MSVC_PREPROCESSOR 179 | // __VA_OPT__ needed for GCC, https://gcc.gnu.org/bugzilla/show_bug.cgi?id=44317 180 | #define LIBASSERT_VA_ARGS(...) __VA_OPT__(,) __VA_ARGS__ 181 | #else 182 | // clang properly eats the comma with ##__VA_ARGS__ 183 | #define LIBASSERT_VA_ARGS(...) , ##__VA_ARGS__ 184 | #endif 185 | 186 | /// 187 | /// C++20 functionality wrappers. 188 | /// 189 | 190 | // Check if we can use std::is_constant_evaluated. 191 | #ifdef __has_include 192 | #if __has_include() 193 | #include 194 | #ifdef __cpp_lib_is_constant_evaluated 195 | #include 196 | #define LIBASSERT_HAS_IS_CONSTANT_EVALUATED 197 | #endif 198 | #endif 199 | #endif 200 | 201 | // Check if we have the builtin __builtin_is_constant_evaluated. 202 | #ifdef __has_builtin 203 | #if __has_builtin(__builtin_is_constant_evaluated) 204 | #define LIBASSERT_HAS_BUILTIN_IS_CONSTANT_EVALUATED 205 | #endif 206 | #endif 207 | 208 | // GCC 9.1+ and later has __builtin_is_constant_evaluated 209 | #if defined(__GNUC__) && (__GNUC__ >= 9) && !defined(LIBASSERT_HAS_BUILTIN_IS_CONSTANT_EVALUATED) 210 | #define LIBASSERT_HAS_BUILTIN_IS_CONSTANT_EVALUATED 211 | #endif 212 | 213 | // Visual Studio 2019 (19.25) and later supports __builtin_is_constant_evaluated 214 | #if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 192528326) 215 | #define LIBASSERT_HAS_BUILTIN_IS_CONSTANT_EVALUATED 216 | #endif 217 | 218 | namespace libassert::detail { 219 | // Note: Works with >=C++20 and with C++17 for GCC 9.1+, Clang 9+, and MSVC 19.25+. 220 | constexpr bool is_constant_evaluated() noexcept { 221 | #if defined(LIBASSERT_HAS_IS_CONSTANT_EVALUATED) 222 | return std::is_constant_evaluated(); 223 | #elif defined(LIBASSERT_HAS_BUILTIN_IS_CONSTANT_EVALUATED) 224 | return __builtin_is_constant_evaluated(); 225 | #else 226 | return false; 227 | #endif 228 | } 229 | } 230 | 231 | #if LIBASSERT_IS_CLANG || LIBASSERT_IS_GCC 232 | #if LIBASSERT_IS_GCC 233 | #define LIBASSERT_WARNING_PRAGMA_PUSH_GCC _Pragma("GCC diagnostic push") 234 | #define LIBASSERT_WARNING_PRAGMA_POP_GCC _Pragma("GCC diagnostic pop") 235 | #define LIBASSERT_WARNING_PRAGMA_PUSH_CLANG 236 | #define LIBASSERT_WARNING_PRAGMA_POP_CLANG 237 | #else 238 | #define LIBASSERT_WARNING_PRAGMA_PUSH_GCC 239 | #define LIBASSERT_WARNING_PRAGMA_POP_GCC 240 | #define LIBASSERT_WARNING_PRAGMA_PUSH_CLANG _Pragma("GCC diagnostic push") 241 | #define LIBASSERT_WARNING_PRAGMA_POP_CLANG _Pragma("GCC diagnostic pop") 242 | #endif 243 | #else 244 | #define LIBASSERT_WARNING_PRAGMA_PUSH_CLANG 245 | #define LIBASSERT_WARNING_PRAGMA_POP_CLANG 246 | #define LIBASSERT_WARNING_PRAGMA_PUSH_GCC 247 | #define LIBASSERT_WARNING_PRAGMA_POP_GCC 248 | #endif 249 | 250 | #if LIBASSERT_IS_CLANG || LIBASSERT_IS_ICX 251 | // clang and icx support this as far back as this library could care 252 | #define LIBASSERT_BREAKPOINT() __builtin_debugtrap() 253 | #elif LIBASSERT_IS_MSVC || LIBASSERT_IS_ICC 254 | // msvc and icc support this as far back as this library could care 255 | #define LIBASSERT_BREAKPOINT() __debugbreak() 256 | #elif LIBASSERT_IS_GCC 257 | #if LIBASSERT_GCC_VERSION >= 1200 258 | #define LIBASSERT_IGNORE_CPP20_EXTENSION_WARNING _Pragma("GCC diagnostic ignored \"-Wc++20-extensions\"") 259 | #else 260 | #define LIBASSERT_IGNORE_CPP20_EXTENSION_WARNING 261 | #endif 262 | #define LIBASSERT_ASM_BREAKPOINT(instruction) \ 263 | do { \ 264 | LIBASSERT_WARNING_PRAGMA_PUSH_GCC \ 265 | LIBASSERT_IGNORE_CPP20_EXTENSION_WARNING \ 266 | __asm__ __volatile__(instruction) \ 267 | ; \ 268 | LIBASSERT_WARNING_PRAGMA_POP_GCC \ 269 | } while(0) 270 | // precedence for these come from llvm's __builtin_debugtrap() implementation 271 | // arm: https://github.com/llvm/llvm-project/blob/e9954ec087d640809082f46d1c7e5ac1767b798d/llvm/lib/Target/ARM/ARMInstrInfo.td#L2393-L2394 272 | // def : Pat<(debugtrap), (BKPT 0)>, Requires<[IsARM, HasV5T]>; 273 | // def : Pat<(debugtrap), (UDF 254)>, Requires<[IsARM, NoV5T]>; 274 | // thumb: https://github.com/llvm/llvm-project/blob/e9954ec087d640809082f46d1c7e5ac1767b798d/llvm/lib/Target/ARM/ARMInstrThumb.td#L1444-L1445 275 | // def : Pat<(debugtrap), (tBKPT 0)>, Requires<[IsThumb, HasV5T]>; 276 | // def : Pat<(debugtrap), (tUDF 254)>, Requires<[IsThumb, NoV5T]>; 277 | // aarch64: https://github.com/llvm/llvm-project/blob/e9954ec087d640809082f46d1c7e5ac1767b798d/llvm/lib/Target/AArch64/AArch64FastISel.cpp#L3615-L3618 278 | // case Intrinsic::debugtrap: 279 | // BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD, TII.get(AArch64::BRK)) 280 | // .addImm(0xF000); 281 | // return true; 282 | // x86: https://github.com/llvm/llvm-project/blob/e9954ec087d640809082f46d1c7e5ac1767b798d/llvm/lib/Target/X86/X86InstrSystem.td#L81-L84 283 | // def : Pat<(debugtrap), 284 | // (INT3)>, Requires<[NotPS]>; 285 | #if defined(__i386__) || defined(__x86_64__) 286 | #define LIBASSERT_BREAKPOINT() LIBASSERT_ASM_BREAKPOINT("int3") 287 | #elif defined(__arm__) || defined(__thumb__) 288 | #if __ARM_ARCH >= 5 289 | #define LIBASSERT_BREAKPOINT() LIBASSERT_ASM_BREAKPOINT("bkpt #0") 290 | #else 291 | #define LIBASSERT_BREAKPOINT() LIBASSERT_ASM_BREAKPOINT("udf #0xfe") 292 | #endif 293 | #elif defined(__aarch64__) || defined(_M_ARM64) 294 | #define LIBASSERT_BREAKPOINT() LIBASSERT_ASM_BREAKPOINT("brk #0xf000") 295 | #else 296 | // some architecture we aren't prepared for 297 | #define LIBASSERT_BREAKPOINT() 298 | #endif 299 | #else 300 | // some compiler we aren't prepared for 301 | #define LIBASSERT_BREAKPOINT() 302 | #endif 303 | 304 | #if defined(__has_include) && __has_include() && defined(__cpp_lib_format) 305 | #define LIBASSERT_USE_STD_FORMAT 306 | #endif 307 | 308 | #endif 309 | -------------------------------------------------------------------------------- /include/libassert/utilities.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIBASSERT_UTILITIES_HPP 2 | #define LIBASSERT_UTILITIES_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | // ===================================================================================================================== 11 | // || Core utilities || 12 | // ===================================================================================================================== 13 | 14 | namespace libassert { 15 | // Lightweight helper, eventually may use C++20 std::source_location if this library no longer 16 | // targets C++17. Note: __builtin_FUNCTION only returns the name, so __PRETTY_FUNCTION__ is 17 | // still needed. 18 | struct source_location { 19 | const char* file; 20 | //const char* function; // disabled for now due to static constexpr restrictions 21 | int line; 22 | constexpr source_location( 23 | //const char* _function /*= __builtin_FUNCTION()*/, 24 | const char* _file = __builtin_FILE(), 25 | int _line = __builtin_LINE() 26 | ) : file(_file), /*function(_function),*/ line(_line) {} 27 | }; 28 | } 29 | 30 | namespace libassert::detail { 31 | // bootstrap with primitive implementations 32 | LIBASSERT_EXPORT void primitive_assert_impl( 33 | bool condition, 34 | bool normal_assert, 35 | const char* expression, 36 | const char* signature, 37 | source_location location, 38 | const char* message = nullptr 39 | ); 40 | 41 | [[noreturn]] LIBASSERT_EXPORT void primitive_panic_impl ( 42 | const char* signature, 43 | source_location location, 44 | const char* message 45 | ); 46 | 47 | // always_false is just convenient to use here 48 | #define LIBASSERT_PHONY_USE(E) ((void)::libassert::detail::always_false) 49 | 50 | #ifndef NDEBUG 51 | #define LIBASSERT_PRIMITIVE_DEBUG_ASSERT(c, ...) \ 52 | libassert::detail::primitive_assert_impl(c, false, #c, LIBASSERT_PFUNC, {} LIBASSERT_VA_ARGS(__VA_ARGS__)) 53 | #else 54 | #define LIBASSERT_PRIMITIVE_DEBUG_ASSERT(c, ...) LIBASSERT_PHONY_USE(c) 55 | #endif 56 | 57 | #define LIBASSERT_PRIMITIVE_PANIC(message) ::libassert::detail::primitive_panic_impl(LIBASSERT_PFUNC, {}, message) 58 | } 59 | 60 | // ===================================================================================================================== 61 | // || Basic formatting and type tools || 62 | // ===================================================================================================================== 63 | 64 | namespace libassert::detail { 65 | [[nodiscard]] LIBASSERT_EXPORT std::string bstringf(const char* format, ...); 66 | 67 | LIBASSERT_ATTR_COLD [[nodiscard]] 68 | constexpr inline std::string_view substring_bounded_by( 69 | std::string_view sig, 70 | std::string_view l, 71 | std::string_view r 72 | ) noexcept { 73 | auto i = sig.find(l) + l.length(); 74 | return sig.substr(i, sig.rfind(r) - i); 75 | } 76 | 77 | template 78 | LIBASSERT_ATTR_COLD [[nodiscard]] 79 | std::string_view type_name() noexcept { 80 | // Cases to handle: 81 | // gcc: constexpr std::string_view ns::type_name() [with T = int; std::string_view = std::basic_string_view] 82 | // clang: std::string_view ns::type_name() [T = int] 83 | // msvc: class std::basic_string_view > __cdecl ns::type_name(void) 84 | #if LIBASSERT_IS_CLANG 85 | return substring_bounded_by(LIBASSERT_PFUNC, "[T = ", "]"); 86 | #elif LIBASSERT_IS_GCC 87 | return substring_bounded_by(LIBASSERT_PFUNC, "[with T = ", "; std::string_view = "); 88 | #elif LIBASSERT_IS_MSVC 89 | return substring_bounded_by(LIBASSERT_PFUNC, "type_name<", ">(void)"); 90 | #else 91 | return LIBASSERT_PFUNC; 92 | #endif 93 | } 94 | 95 | [[nodiscard]] LIBASSERT_EXPORT std::string prettify_type(std::string type); 96 | } 97 | 98 | // ===================================================================================================================== 99 | // || Metaprogramming utilities || 100 | // ===================================================================================================================== 101 | 102 | namespace libassert::detail { 103 | struct nothing {}; 104 | 105 | template inline constexpr bool is_nothing = std::is_same_v; 106 | 107 | // Hack to get around static_assert(false); being evaluated before any instantiation, even under 108 | // an if-constexpr branch 109 | // Also used for PHONY_USE 110 | template inline constexpr bool always_false = false; 111 | 112 | template using strip = std::remove_cv_t>; 113 | 114 | // intentionally not stripping B 115 | template inline constexpr bool isa = std::is_same_v, B>; 116 | 117 | // Is integral but not boolean 118 | template inline constexpr bool is_integral_and_not_bool = std::is_integral_v> && !isa; 119 | 120 | template 121 | inline constexpr bool is_arith_not_bool_char = std::is_arithmetic_v> && !isa && !isa; 122 | 123 | template 124 | inline constexpr bool is_c_string = 125 | isa>, char*> // <- covers literals (i.e. const char(&)[N]) too 126 | || isa>, const char*>; 127 | 128 | template 129 | inline constexpr bool is_string_type = 130 | isa 131 | || isa 132 | || is_c_string; 133 | 134 | // char(&)[20], const char(&)[20], const char(&)[] 135 | template inline constexpr bool is_string_literal = 136 | std::is_lvalue_reference_v 137 | && std::is_array_v> 138 | && isa>, char>; 139 | 140 | template typename std::add_lvalue_reference_t decllval() noexcept; 141 | } 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | status=0 4 | while read f 5 | do 6 | echo checking $f 7 | clang-tidy $f -p=build 8 | ret=$? 9 | if [ $ret -ne 0 ]; then 10 | status=1 11 | fi 12 | done <<< $(echo include/assert/assert.hpp && find src -name "*.hpp" -o -name "*.cpp") 13 | exit $status 14 | -------------------------------------------------------------------------------- /screenshots/assert_val_opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/libassert/91e0d84216f034e3e593ea57103d3f419e333873/screenshots/assert_val_opt.png -------------------------------------------------------------------------------- /screenshots/breakpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/libassert/91e0d84216f034e3e593ea57103d3f419e333873/screenshots/breakpoint.png -------------------------------------------------------------------------------- /screenshots/catch2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/libassert/91e0d84216f034e3e593ea57103d3f419e333873/screenshots/catch2.png -------------------------------------------------------------------------------- /screenshots/custom_object_printing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/libassert/91e0d84216f034e3e593ea57103d3f419e333873/screenshots/custom_object_printing.png -------------------------------------------------------------------------------- /screenshots/fopen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/libassert/91e0d84216f034e3e593ea57103d3f419e333873/screenshots/fopen.png -------------------------------------------------------------------------------- /screenshots/gtest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/libassert/91e0d84216f034e3e593ea57103d3f419e333873/screenshots/gtest.png -------------------------------------------------------------------------------- /screenshots/literal_formatting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/libassert/91e0d84216f034e3e593ea57103d3f419e333873/screenshots/literal_formatting.png -------------------------------------------------------------------------------- /screenshots/map_contains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/libassert/91e0d84216f034e3e593ea57103d3f419e333873/screenshots/map_contains.png -------------------------------------------------------------------------------- /screenshots/no_redundancy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/libassert/91e0d84216f034e3e593ea57103d3f419e333873/screenshots/no_redundancy.png -------------------------------------------------------------------------------- /screenshots/object_printing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/libassert/91e0d84216f034e3e593ea57103d3f419e333873/screenshots/object_printing.png -------------------------------------------------------------------------------- /screenshots/recursion_fold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/libassert/91e0d84216f034e3e593ea57103d3f419e333873/screenshots/recursion_fold.png -------------------------------------------------------------------------------- /screenshots/safe_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/libassert/91e0d84216f034e3e593ea57103d3f419e333873/screenshots/safe_comparison.png -------------------------------------------------------------------------------- /screenshots/vec_size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/libassert/91e0d84216f034e3e593ea57103d3f419e333873/screenshots/vec_size.png -------------------------------------------------------------------------------- /screenshots/wubble_trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/libassert/91e0d84216f034e3e593ea57103d3f419e333873/screenshots/wubble_trace.png -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.organization=jeremy-rifkin 2 | sonar.projectKey=jeremy-rifkin_libassert 3 | 4 | # relative paths to source directories. More details and properties are described 5 | # in https://sonarcloud.io/documentation/project-administration/narrowing-the-focus/ 6 | sonar.sources=src, include 7 | sonar.sourceEncoding=UTF-8 8 | sonar.cfamily.compile-commands=build/compile_commands.json 9 | -------------------------------------------------------------------------------- /src/analysis.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ANALYSIS_HPP 2 | #define ANALYSIS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace libassert::detail { 11 | struct highlight_block { 12 | std::string_view color; 13 | std::string content; 14 | highlight_block(std::string_view color_, std::string_view content_) : color(color_), content(content_) {} 15 | }; 16 | 17 | LIBASSERT_ATTR_COLD LIBASSERT_EXPORT /* FIXME */ 18 | std::string highlight(std::string_view expression, const color_scheme& scheme); 19 | 20 | LIBASSERT_ATTR_COLD 21 | std::vector highlight_blocks(std::string_view expression, const color_scheme& scheme); 22 | 23 | LIBASSERT_ATTR_COLD literal_format get_literal_format(std::string_view expression); 24 | 25 | LIBASSERT_ATTR_COLD std::string_view trim_suffix(std::string_view expression); 26 | 27 | LIBASSERT_ATTR_COLD bool is_bitwise(std::string_view op); 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/common.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_HPP 2 | #define COMMON_HPP 3 | 4 | #define ESC "\033[" 5 | #define ANSIRGB(r, g, b) ESC "38;2;" #r ";" #g ";" #b "m" 6 | #define RESET ESC "0m" 7 | // Slightly modified one dark pro colors 8 | // Original: https://coolors.co/e06b74-d19a66-e5c07a-98c379-62aeef-55b6c2-c678dd 9 | // Modified: https://coolors.co/e06b74-d19a66-e5c07a-90cc66-62aeef-56c2c0-c678dd 10 | #define RGB_RED ANSIRGB(224, 107, 116) 11 | #define RGB_ORANGE ANSIRGB(209, 154, 102) 12 | #define RGB_YELLOW ANSIRGB(229, 192, 122) 13 | #define RGB_GREEN ANSIRGB(150, 205, 112) // modified 14 | #define RGB_BLUE ANSIRGB(98, 174, 239) 15 | #define RGB_CYAN ANSIRGB(86, 194, 192) // modified 16 | #define RGB_PURPL ANSIRGB(198, 120, 221) 17 | 18 | #define BASIC_RED ESC "31m" 19 | #define BASIC_ORANGE ESC "33m" // using yellow as orange 20 | #define BASIC_YELLOW ESC "34m" // based off use (identifiers in scope res) it's better to color as blue here 21 | #define BASIC_GREEN ESC "32m" 22 | #define BASIC_BLUE ESC "34m" 23 | #define BASIC_CYAN ESC "36m" 24 | #define BASIC_PURPL ESC "35m" 25 | 26 | #if !defined(LIBASSERT_BUILD_TESTING) || defined(LIBASSERT_STATIC_DEFINE) 27 | #define LIBASSERT_EXPORT_TESTING 28 | #else 29 | #ifndef LIBASSERT_EXPORT_TESTING 30 | #ifdef libassert_lib_EXPORTS 31 | /* We are building this library */ 32 | #define LIBASSERT_EXPORT_TESTING LIBASSERT_EXPORT_ATTR 33 | #else 34 | /* We are using this library */ 35 | #define LIBASSERT_EXPORT_TESTING LIBASSERT_IMPORT_ATTR 36 | #endif 37 | #endif 38 | #endif 39 | 40 | #define IS_WINDOWS 0 41 | #define IS_LINUX 0 42 | #define IS_APPLE 0 43 | 44 | #if defined(_WIN32) 45 | #undef IS_WINDOWS 46 | #define IS_WINDOWS 1 47 | #elif defined(__linux) 48 | #undef IS_LINUX 49 | #define IS_LINUX 1 50 | #elif defined(__APPLE__) 51 | #undef IS_APPLE 52 | #define IS_APPLE 1 53 | #else 54 | #error "Libassert doesn't recognize this system, please open an issue at https://github.com/jeremy-rifkin/libassert" 55 | #endif 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /src/paths.cpp: -------------------------------------------------------------------------------- 1 | #include "paths.hpp" 2 | 3 | #include "common.hpp" 4 | #include 5 | 6 | namespace libassert::detail { 7 | using path_components = std::vector; 8 | 9 | #if IS_WINDOWS 10 | constexpr std::string_view path_delim = "/\\"; 11 | #else 12 | constexpr std::string_view path_delim = "/"; 13 | #endif 14 | 15 | LIBASSERT_ATTR_COLD 16 | path_components parse_path(const std::string_view path) { 17 | // Some cases to consider 18 | // projects/libassert/demo.cpp projects libassert demo.cpp 19 | // /glibc-2.27/csu/../csu/libc-start.c / glibc-2.27 csu libc-start.c 20 | // ./demo.exe . demo.exe 21 | // ./../demo.exe .. demo.exe 22 | // ../x.hpp .. x.hpp 23 | // /foo/./x foo x 24 | // /foo//x f x 25 | path_components parts; 26 | for(auto part : split(path, path_delim)) { 27 | if(parts.empty()) { 28 | // TODO: Maybe it could be ok to use string_view's here, have to be careful about lifetime 29 | // first gets added no matter what 30 | parts.emplace_back(part); 31 | } else { 32 | if(part.empty()) { 33 | // nop 34 | } else if(part == ".") { 35 | // nop 36 | } else if(part == "..") { 37 | // cases where we have unresolvable ..'s, e.g. ./../../demo.exe 38 | if(parts.back() == "." || parts.back() == "..") { 39 | parts.emplace_back(part); 40 | } else { 41 | parts.pop_back(); 42 | } 43 | } else { 44 | parts.emplace_back(part); 45 | } 46 | } 47 | } 48 | LIBASSERT_PRIMITIVE_DEBUG_ASSERT(!parts.empty()); 49 | LIBASSERT_PRIMITIVE_DEBUG_ASSERT(parts.back() != "." && parts.back() != ".."); 50 | return parts; 51 | } 52 | 53 | LIBASSERT_ATTR_COLD 54 | void path_trie::insert(const path_components& path) { 55 | LIBASSERT_PRIMITIVE_DEBUG_ASSERT(path.back() == root); 56 | insert(path, (int)path.size() - 2); 57 | } 58 | 59 | LIBASSERT_ATTR_COLD 60 | path_components path_trie::disambiguate(const path_components& path) { 61 | path_components result; 62 | path_trie* current = this; 63 | LIBASSERT_PRIMITIVE_DEBUG_ASSERT(path.back() == root); 64 | result.push_back(current->root); 65 | for(size_t i = path.size() - 2; i >= 1; i--) { 66 | LIBASSERT_PRIMITIVE_DEBUG_ASSERT(current->downstream_branches >= 1); 67 | if(current->downstream_branches == 1) { 68 | break; 69 | } 70 | const std::string& component = path[i]; 71 | LIBASSERT_PRIMITIVE_DEBUG_ASSERT(current->edges.count(component)); 72 | current = current->edges.at(component).get(); 73 | result.push_back(current->root); 74 | } 75 | std::reverse(result.begin(), result.end()); 76 | return result; 77 | } 78 | 79 | LIBASSERT_ATTR_COLD 80 | void path_trie::insert(const path_components& path, int i) { 81 | if(i < 0) { 82 | return; 83 | } 84 | if(!edges.count(path[i])) { 85 | if(!edges.empty()) { 86 | downstream_branches++; // this is to deal with making leaves have count 1 87 | } 88 | edges.insert({path[i], std::make_unique(path[i])}); 89 | } 90 | downstream_branches -= edges.at(path[i])->downstream_branches; 91 | edges.at(path[i])->insert(path, i - 1); 92 | downstream_branches += edges.at(path[i])->downstream_branches; 93 | } 94 | 95 | bool path_handler::has_add_path() const { 96 | return false; 97 | } 98 | 99 | void path_handler::add_path(std::string_view) { 100 | LIBASSERT_PRIMITIVE_DEBUG_ASSERT(false, "Improper path_handler::add_path"); 101 | } 102 | 103 | void path_handler::finalize() { 104 | LIBASSERT_PRIMITIVE_DEBUG_ASSERT(false, "Improper path_handler::finalize"); 105 | } 106 | 107 | LIBASSERT_ATTR_COLD 108 | std::unique_ptr identity_path_handler::clone() const { 109 | return std::make_unique(*this); 110 | } 111 | 112 | LIBASSERT_ATTR_COLD 113 | std::string_view identity_path_handler::resolve_path(std::string_view path) { 114 | return path; 115 | } 116 | 117 | LIBASSERT_ATTR_COLD 118 | std::unique_ptr disambiguating_path_handler::clone() const { 119 | return std::make_unique(*this); 120 | } 121 | 122 | LIBASSERT_ATTR_COLD 123 | std::string_view disambiguating_path_handler::resolve_path(std::string_view path) { 124 | return path_map.at(std::string(path)); 125 | } 126 | 127 | bool disambiguating_path_handler::has_add_path() const { 128 | return true; 129 | } 130 | 131 | LIBASSERT_ATTR_COLD 132 | void disambiguating_path_handler::add_path(std::string_view path) { 133 | paths.emplace_back(path); 134 | } 135 | 136 | LIBASSERT_ATTR_COLD 137 | void disambiguating_path_handler::finalize() { 138 | // raw full path -> components 139 | std::unordered_map parsed_paths; 140 | // base file name -> path trie 141 | std::unordered_map tries; 142 | for(const auto& path : paths) { 143 | if(!parsed_paths.count(path)) { 144 | auto parsed_path = parse_path(path); 145 | auto& file_name = parsed_path.back(); 146 | parsed_paths.insert({path, parsed_path}); 147 | if(tries.count(file_name) == 0) { 148 | tries.insert({file_name, path_trie(file_name)}); 149 | } 150 | tries.at(file_name).insert(parsed_path); 151 | } 152 | } 153 | // raw full path -> minified path 154 | std::unordered_map files; 155 | for(auto& [raw, parsed_path] : parsed_paths) { 156 | const std::string new_path = join(tries.at(parsed_path.back()).disambiguate(parsed_path), "/"); 157 | LIBASSERT_PRIMITIVE_ASSERT(files.insert({raw, new_path}).second); 158 | } 159 | path_map = std::move(files); 160 | // return {files, std::min(longest_file_width, size_t(50))}; 161 | } 162 | 163 | LIBASSERT_ATTR_COLD 164 | std::unique_ptr basename_path_handler::clone() const { 165 | return std::make_unique(*this); 166 | } 167 | 168 | LIBASSERT_ATTR_COLD 169 | std::string_view basename_path_handler::resolve_path(std::string_view path) { 170 | auto last = path.find_last_of(path_delim); 171 | if(last == path.npos) { 172 | last = 0; 173 | } else { 174 | last++; // go after the delim 175 | } 176 | return path.substr(last); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/paths.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PATHS_HPP 2 | #define PATHS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "utils.hpp" 10 | 11 | #include 12 | 13 | namespace libassert::detail { 14 | using path_components = std::vector; 15 | 16 | LIBASSERT_ATTR_COLD 17 | path_components parse_path(const std::string_view path); 18 | 19 | class path_trie { 20 | // Backwards path trie structure 21 | // e.g.: 22 | // a/b/c/d/e disambiguate to -> c/d/e 23 | // a/b/f/d/e disambiguate to -> f/d/e 24 | // 2 2 1 1 1 25 | // e - d - c - b - a 26 | // \ 1 1 1 27 | // \ f - b - a 28 | // Nodes are marked with the number of downstream branches 29 | size_t downstream_branches = 1; 30 | std::string root; 31 | std::unordered_map> edges; 32 | public: 33 | LIBASSERT_ATTR_COLD 34 | explicit path_trie(std::string _root) : root(std::move(_root)) {}; 35 | LIBASSERT_ATTR_COLD 36 | void insert(const path_components& path); 37 | LIBASSERT_ATTR_COLD 38 | path_components disambiguate(const path_components& path); 39 | private: 40 | LIBASSERT_ATTR_COLD 41 | void insert(const path_components& path, int i); 42 | }; 43 | 44 | class identity_path_handler : public path_handler { 45 | public: 46 | std::unique_ptr clone() const override; 47 | std::string_view resolve_path(std::string_view) override; 48 | }; 49 | 50 | class disambiguating_path_handler : public path_handler { 51 | std::vector paths; 52 | std::unordered_map path_map; 53 | public: 54 | std::unique_ptr clone() const override; 55 | std::string_view resolve_path(std::string_view) override; 56 | bool has_add_path() const override; 57 | void add_path(std::string_view) override; 58 | void finalize() override; 59 | }; 60 | 61 | class basename_path_handler : public path_handler { 62 | public: 63 | std::unique_ptr clone() const override; 64 | std::string_view resolve_path(std::string_view) override; 65 | }; 66 | } 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /src/platform.cpp: -------------------------------------------------------------------------------- 1 | #include "platform.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "common.hpp" 12 | #include "utils.hpp" 13 | 14 | #if IS_WINDOWS 15 | #include 16 | #include 17 | #undef min // fucking windows headers, man 18 | #undef max 19 | #elif IS_LINUX 20 | #include 21 | #include 22 | #include 23 | #include 24 | #elif IS_APPLE 25 | #include 26 | #include 27 | #include 28 | #include 29 | #endif 30 | 31 | #include 32 | 33 | // All platform-specific/system code lives here 34 | 35 | namespace libassert { 36 | // https://stackoverflow.com/questions/23369503/get-size-of-terminal-window-rows-columns 37 | LIBASSERT_ATTR_COLD int terminal_width(int fd) { 38 | if(fd < 0) { 39 | return 0; 40 | } 41 | #if IS_WINDOWS 42 | DWORD windows_handle = detail::needle(fd).lookup( 43 | STDIN_FILENO, STD_INPUT_HANDLE, 44 | STDOUT_FILENO, STD_OUTPUT_HANDLE, 45 | STDERR_FILENO, STD_ERROR_HANDLE 46 | ); 47 | CONSOLE_SCREEN_BUFFER_INFO csbi; 48 | HANDLE h = GetStdHandle(windows_handle); 49 | if(h == INVALID_HANDLE_VALUE) { return 0; } 50 | if(!GetConsoleScreenBufferInfo(h, &csbi)) { return 0; } 51 | return csbi.srWindow.Right - csbi.srWindow.Left + 1; 52 | #else 53 | struct winsize w; 54 | // NOLINTNEXTLINE(misc-include-cleaner) 55 | if(ioctl(fd, TIOCGWINSZ, &w) == -1) { return 0; } 56 | return w.ws_col; 57 | #endif 58 | } 59 | 60 | #if IS_LINUX 61 | class file_closer { 62 | int fd; 63 | public: 64 | file_closer(int fd_) : fd(fd_) {} 65 | file_closer(const file_closer&) = delete; 66 | file_closer(file_closer&&) = delete; 67 | file_closer& operator=(const file_closer&) = delete; 68 | file_closer& operator=(file_closer&&) = delete; 69 | ~file_closer() { 70 | if(close(fd) == -1) { 71 | perror("Libassert: Something went wrong when closing a file descriptor."); 72 | } 73 | } 74 | }; 75 | #endif 76 | 77 | LIBASSERT_ATTR_COLD bool is_debugger_present_internal() noexcept { 78 | #if IS_WINDOWS 79 | return IsDebuggerPresent(); 80 | #elif IS_LINUX 81 | // https://www.man7.org/linux/man-pages/man5/proc.5.html 82 | // We're looking at the top of /proc/self/status until we find "TracerPid:" 83 | // Sample: 84 | // Name: cat 85 | // Umask: 0022 86 | // State: R (running) 87 | // Tgid: 106085 88 | // Ngid: 0 89 | // Pid: 106085 90 | // PPid: 104045 91 | // TracerPid: 0 92 | // The name is truncated at 16 characters, so this whole thing should be under 256 chars 93 | constexpr std::size_t read_goal = 256; 94 | int fd = open("/proc/self/status", O_RDONLY); 95 | if(fd == -1) { 96 | // something went wrong 97 | return false; 98 | } 99 | file_closer closer(fd); 100 | char buffer[read_goal]; 101 | auto size = read(fd, buffer, read_goal); 102 | if(size == -1) { 103 | // something went wrong 104 | return false; 105 | } 106 | std::string_view status{buffer, std::size_t(size)}; 107 | constexpr std::string_view key = "TracerPid:"; 108 | auto pos = status.find(key); 109 | if(pos == std::string_view::npos) { 110 | return false; 111 | } 112 | auto pid_start = status.find_first_not_of(detail::whitespace_chars, pos + key.size()); 113 | if(pid_start == std::string_view::npos) { 114 | return false; 115 | } 116 | auto pid_end = status.find_first_of(detail::whitespace_chars, pid_start); 117 | if(pid_end == std::string_view::npos) { 118 | return false; 119 | } 120 | // since we found a whitespace character after the pid we know the read wasn't somehow weirdly truncated 121 | auto tracer_pid = status.substr(pid_start, pid_end - pid_start); 122 | int value; 123 | if(std::from_chars(tracer_pid.data(), tracer_pid.data() + tracer_pid.size(), value).ec == std::errc{}) { 124 | return value != 0; 125 | } else { 126 | return false; 127 | } 128 | return false; 129 | #else 130 | // https://developer.apple.com/library/archive/qa/qa1361/_index.html 131 | int mib[4] {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()}; 132 | struct kinfo_proc info; 133 | info.kp_proc.p_flag = 0; 134 | size_t size = sizeof(info); 135 | int res = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); 136 | if(res != 0) { 137 | // something went wrong, assume false 138 | return false; 139 | } 140 | return info.kp_proc.p_flag & P_TRACED; 141 | #endif 142 | } 143 | 144 | std::atomic check_mode = debugger_check_mode::check_once; 145 | std::mutex is_debugger_present_mutex; 146 | std::optional cached_is_debugger_present; 147 | 148 | LIBASSERT_ATTR_COLD 149 | bool is_debugger_present() noexcept { 150 | if(check_mode.load() == debugger_check_mode::check_every_time) { 151 | return is_debugger_present_internal(); 152 | } else { 153 | std::unique_lock lock(is_debugger_present_mutex); 154 | if(cached_is_debugger_present) { 155 | return *cached_is_debugger_present; 156 | } else { 157 | cached_is_debugger_present = is_debugger_present_internal(); 158 | return *cached_is_debugger_present; 159 | } 160 | } 161 | } 162 | 163 | LIBASSERT_ATTR_COLD LIBASSERT_EXPORT 164 | void set_debugger_check_mode(debugger_check_mode mode) noexcept { 165 | check_mode = mode; 166 | } 167 | 168 | LIBASSERT_ATTR_COLD LIBASSERT_EXPORT void enable_virtual_terminal_processing_if_needed() { 169 | // enable colors / ansi processing if necessary 170 | #if IS_WINDOWS 171 | // https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing 172 | #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING 173 | constexpr DWORD ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4; 174 | #endif 175 | HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); 176 | DWORD dwMode = 0; 177 | if(hOut == INVALID_HANDLE_VALUE) return; 178 | if(!GetConsoleMode(hOut, &dwMode)) return; 179 | if(dwMode != (dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) 180 | if(!SetConsoleMode(hOut, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) return; 181 | #endif 182 | } 183 | 184 | LIBASSERT_ATTR_COLD bool isatty(int fd) { 185 | #if IS_WINDOWS 186 | return _isatty(fd); 187 | #else 188 | return ::isatty(fd); 189 | #endif 190 | } 191 | } 192 | 193 | namespace libassert::detail { 194 | std::mutex strerror_mutex; 195 | 196 | LIBASSERT_ATTR_COLD std::string strerror_wrapper(int e) { 197 | std::unique_lock lock(strerror_mutex); 198 | return strerror(e); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/platform.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PLATFORM_HPP 2 | #define PLATFORM_HPP 3 | 4 | #include "common.hpp" 5 | 6 | #ifndef _CRT_SECURE_NO_WARNINGS 7 | // NOLINTNEXTLINE(bugprone-reserved-identifier, cert-dcl37-c, cert-dcl51-cpp) 8 | #define _CRT_SECURE_NO_WARNINGS // done only for strerror 9 | #endif 10 | 11 | #if IS_WINDOWS 12 | #ifndef STDIN_FILENO 13 | #define STDIN_FILENO _fileno(stdin) 14 | #define STDOUT_FILENO _fileno(stdout) 15 | #define STDERR_FILENO _fileno(stderr) 16 | #endif 17 | #else 18 | #include 19 | #endif 20 | 21 | #include 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/printing.cpp: -------------------------------------------------------------------------------- 1 | #include "printing.hpp" 2 | 3 | #include "microfmt.hpp" 4 | 5 | namespace libassert::detail { 6 | LIBASSERT_ATTR_COLD 7 | // TODO 8 | // NOLINTNEXTLINE(readability-function-cognitive-complexity) 9 | std::string wrapped_print(const std::vector& columns, const color_scheme& scheme) { 10 | // 2d array rows/columns 11 | struct line_content { 12 | size_t length; 13 | std::string content; 14 | }; 15 | std::vector> lines; 16 | lines.emplace_back(columns.size()); 17 | // populate one column at a time 18 | for(size_t i = 0; i < columns.size(); i++) { 19 | auto [width, blocks, _] = columns[i]; 20 | size_t current_line = 0; 21 | for(auto& block : blocks) { 22 | size_t block_i = 0; 23 | // digest block 24 | while(block_i != block.content.size()) { 25 | if(lines.size() == current_line) { 26 | lines.emplace_back(columns.size()); 27 | } 28 | // number of characters we can extract from the block 29 | size_t extract = std::min(width - lines[current_line][i].length, block.content.size() - block_i); 30 | LIBASSERT_PRIMITIVE_DEBUG_ASSERT(block_i + extract <= block.content.size()); 31 | auto substr = std::string_view(block.content).substr(block_i, extract); 32 | // handle newlines 33 | if(auto x = substr.find('\n'); x != std::string_view::npos) { 34 | substr = substr.substr(0, x); 35 | extract = x + 1; // extract newline but don't print 36 | } 37 | // append 38 | lines[current_line][i].content += block.color; 39 | lines[current_line][i].content += substr; 40 | lines[current_line][i].content += block.color.empty() ? "" : scheme.reset; 41 | // advance 42 | block_i += extract; 43 | lines[current_line][i].length += extract; 44 | // new line if necessary 45 | // substr.size() != extract iff newline 46 | if(lines[current_line][i].length >= width || substr.size() != extract) { 47 | current_line++; 48 | } 49 | } 50 | } 51 | } 52 | // print 53 | std::string output; 54 | for(auto& line : lines) { 55 | // don't print empty columns with no content in subsequent columns and more importantly 56 | // don't print empty spaces they'll mess up lines after terminal resizing even more 57 | size_t last_col = 0; 58 | for(size_t i = 0; i < line.size(); i++) { 59 | if(!line[i].content.empty()) { 60 | last_col = i; 61 | } 62 | } 63 | for(size_t i = 0; i <= last_col; i++) { 64 | auto& content = line[i]; 65 | if(columns[i].right_align) { 66 | output += microfmt::format( 67 | "{<{}}{}{}", 68 | i == last_col ? 0 : int(columns[i].width - content.length), 69 | "", 70 | content.content, 71 | i == last_col ? "\n" : " " 72 | ); 73 | } else { 74 | output += microfmt::format( 75 | "{}{<{}}{}", 76 | content.content, 77 | i == last_col ? 0 : int(columns[i].width - content.length), 78 | "", 79 | i == last_col ? "\n" : " " 80 | ); 81 | } 82 | } 83 | } 84 | return output; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/printing.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRINTING_HPP 2 | #define PRINTING_HPP 3 | 4 | #include 5 | 6 | #include "analysis.hpp" 7 | #include "utils.hpp" 8 | 9 | #include 10 | 11 | namespace libassert::detail { 12 | struct column_t { 13 | size_t width; 14 | std::vector blocks; 15 | bool right_align = false; 16 | LIBASSERT_ATTR_COLD column_t(size_t _width, std::vector _blocks, bool _right_align = false) 17 | : width(_width), blocks(std::move(_blocks)), right_align(_right_align) {} 18 | LIBASSERT_ATTR_COLD column_t(const column_t&) = default; 19 | LIBASSERT_ATTR_COLD column_t(column_t&&) = default; 20 | LIBASSERT_ATTR_COLD ~column_t() = default; 21 | LIBASSERT_ATTR_COLD column_t& operator=(const column_t&) = default; 22 | LIBASSERT_ATTR_COLD column_t& operator=(column_t&&) = default; 23 | }; 24 | 25 | LIBASSERT_ATTR_COLD 26 | std::string wrapped_print(const std::vector& columns, const color_scheme& scheme); 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/tokenizer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TOKENIZER_HPP 2 | #define TOKENIZER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "utils.hpp" 9 | #include "common.hpp" 10 | 11 | namespace libassert::detail { 12 | enum class token_e { 13 | keyword, 14 | punctuation, 15 | number, 16 | string, 17 | named_literal, 18 | identifier, 19 | whitespace, 20 | unknown 21 | }; 22 | 23 | struct token_t { 24 | token_e type; 25 | std::string_view str; 26 | token_t(token_e type_, std::string_view str_) : type(type_), str(str_) {} 27 | 28 | bool operator==(const token_t& other) const { 29 | return type == other.type && str == other.str; 30 | } 31 | }; 32 | 33 | // lifetime notes: token_t's store string_views to data with at least the same lifetime as the source string_view's 34 | // data 35 | LIBASSERT_EXPORT_TESTING 36 | std::optional> tokenize(std::string_view source, bool decompose_shr = false); 37 | } 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /src/utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #if defined(__has_include) && __has_include() 13 | #include 14 | #include 15 | #else 16 | #include 17 | #endif 18 | 19 | #include "utils.hpp" 20 | #include "microfmt.hpp" 21 | 22 | #include 23 | 24 | namespace libassert::detail { 25 | // to save template instantiation work in TUs a variadic stringf is used 26 | LIBASSERT_ATTR_COLD 27 | std::string bstringf(const char* format, ...) { 28 | va_list args1; 29 | va_list args2; 30 | va_start(args1, format); 31 | va_start(args2, format); 32 | const int length = vsnprintf(nullptr, 0, format, args1); 33 | if(length < 0) { LIBASSERT_PRIMITIVE_DEBUG_ASSERT(false, "Invalid arguments to stringf"); } 34 | std::string str(length, 0); 35 | (void)vsnprintf(str.data(), length + 1, format, args2); 36 | va_end(args1); 37 | va_end(args2); 38 | return str; 39 | } 40 | 41 | LIBASSERT_ATTR_COLD LIBASSERT_EXPORT 42 | void primitive_assert_impl( 43 | bool condition, 44 | bool normal_assert, 45 | const char* expression, 46 | const char* signature, 47 | source_location location, 48 | const char* message 49 | ) { 50 | if(!condition) { 51 | const char* action = normal_assert ? "Assert" : "Debug assert"; 52 | const char* name = normal_assert ? "LIBASSERT_PRIMITIVE_ASSERT" : "LIBASSERT_PRIMITIVE_DEBUG_ASSERT"; 53 | std::string out_message; 54 | if(message == nullptr) { 55 | out_message += microfmt::format( 56 | "{} failed at {}:{}: {}\n", 57 | action, 58 | location.file, 59 | location.line, 60 | signature 61 | ); 62 | } else { 63 | out_message += microfmt::format( 64 | "{} failed at {}:{}: {}: {}\n", 65 | action, 66 | location.file, 67 | location.line, 68 | signature, 69 | message 70 | ); 71 | } 72 | out_message += microfmt::format(" {}({});\n", name, expression); 73 | throw cpptrace::runtime_error(std::move(out_message)); 74 | } 75 | } 76 | 77 | [[noreturn]] LIBASSERT_ATTR_COLD LIBASSERT_EXPORT 78 | void primitive_panic_impl( 79 | const char* signature, 80 | source_location location, 81 | const char* message 82 | ) { 83 | std::string out_message = microfmt::format( 84 | "PANIC failed at {}:{}: {}: {}\n", 85 | location.file, 86 | location.line, 87 | signature, 88 | message 89 | ); 90 | out_message += " LIBASSERT_PRIMITIVE_PANIC(...);\n"; 91 | throw cpptrace::runtime_error(std::move(out_message)); 92 | } 93 | 94 | /* 95 | * string utilities 96 | */ 97 | 98 | LIBASSERT_ATTR_COLD LIBASSERT_EXPORT_TESTING 99 | std::vector split(std::string_view s, std::string_view delims) { 100 | std::vector vec; 101 | size_t old_pos = 0; 102 | size_t pos = 0; 103 | while((pos = s.find_first_of(delims, old_pos)) != std::string::npos) { 104 | vec.emplace_back(s.substr(old_pos, pos - old_pos)); 105 | old_pos = pos + 1; 106 | } 107 | vec.emplace_back(s.substr(old_pos)); 108 | return vec; 109 | } 110 | 111 | LIBASSERT_ATTR_COLD 112 | std::string_view trim(const std::string_view s) { 113 | const size_t l = s.find_first_not_of(whitespace_chars); 114 | if(l == std::string_view::npos) { 115 | return ""; 116 | } 117 | const size_t r = s.find_last_not_of(whitespace_chars) + 1; 118 | return s.substr(l, r - l); 119 | } 120 | 121 | LIBASSERT_ATTR_COLD 122 | void replace_all_dynamic(std::string& str, std::string_view text, std::string_view replacement) { 123 | std::string::size_type pos = 0; 124 | while((pos = str.find(text.data(), pos, text.length())) != std::string::npos) { 125 | str.replace(pos, text.length(), replacement.data(), replacement.length()); 126 | // advancing by one rather than replacement.length() in case replacement leads to 127 | // another replacement opportunity, e.g. folding > > > to >> > then >>> 128 | pos++; 129 | } 130 | } 131 | 132 | LIBASSERT_ATTR_COLD LIBASSERT_EXPORT_TESTING 133 | void replace_all(std::string& str, const std::regex& re, std::string_view replacement) { 134 | std::smatch match; 135 | std::size_t i = 0; 136 | // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) 137 | while(std::regex_search(str.cbegin() + i, str.cend(), match, re)) { 138 | str.replace(i + match.position(), match.length(), replacement); 139 | i += match.position() + replacement.length(); 140 | } 141 | } 142 | 143 | LIBASSERT_ATTR_COLD LIBASSERT_EXPORT_TESTING 144 | void replace_all(std::string& str, std::string_view substr, std::string_view replacement) { 145 | std::string::size_type pos = 0; 146 | // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) 147 | while((pos = str.find(substr.data(), pos, substr.length())) != std::string::npos) { 148 | str.replace(pos, substr.length(), replacement.data(), replacement.length()); 149 | pos += replacement.length(); 150 | } 151 | } 152 | 153 | LIBASSERT_ATTR_COLD 154 | void replace_all_template(std::string& str, const std::pair& rule) { 155 | const auto& [re, replacement] = rule; 156 | std::smatch match; 157 | std::size_t cursor = 0; 158 | // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) 159 | while(std::regex_search(str.cbegin() + cursor, str.cend(), match, re)) { 160 | // find matching > 161 | const std::size_t match_begin = cursor + match.position(); 162 | std::size_t end = match_begin + match.length(); 163 | for(int c = 1; end < str.size() && c > 0; end++) { 164 | if(str[end] == '<') { 165 | c++; 166 | } else if(str[end] == '>') { 167 | c--; 168 | } 169 | } 170 | // make the replacement 171 | str.replace(match_begin, end - match_begin, replacement); 172 | cursor = match_begin + replacement.length(); 173 | } 174 | } 175 | 176 | LIBASSERT_ATTR_COLD 177 | std::string indent(const std::string_view str, size_t depth, char c, bool ignore_first) { 178 | size_t i = 0; 179 | size_t j; 180 | std::string output; 181 | while((j = str.find('\n', i)) != std::string::npos) { 182 | if(i != 0 || !ignore_first) { output.insert(output.end(), depth, c); } 183 | output.insert(output.end(), str.begin() + i, str.begin() + j + 1); 184 | i = j + 1; 185 | } 186 | if(i != 0 || !ignore_first) { output.insert(output.end(), depth, c); } 187 | output.insert(output.end(), str.begin() + i, str.end()); 188 | return output; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_HPP 2 | #define UTILS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #include "common.hpp" 19 | 20 | namespace libassert::detail { 21 | // Still present in release mode, nonfatal 22 | #define LIBASSERT_PRIMITIVE_ASSERT(c, ...) ::libassert::detail::primitive_assert_impl( \ 23 | c, \ 24 | true, \ 25 | #c, \ 26 | LIBASSERT_PFUNC, \ 27 | {} LIBASSERT_VA_ARGS(__VA_ARGS__) \ 28 | ) 29 | 30 | /* 31 | * string utilities 32 | */ 33 | 34 | LIBASSERT_ATTR_COLD LIBASSERT_EXPORT_TESTING 35 | std::vector split(std::string_view s, std::string_view delims); 36 | 37 | template 38 | LIBASSERT_ATTR_COLD 39 | std::string join(const C& container, const std::string_view delim) { 40 | auto iter = std::begin(container); 41 | auto end = std::end(container); 42 | std::string str; 43 | if(std::distance(iter, end) > 0) { 44 | str += *iter; 45 | while(++iter != end) { 46 | str += delim; 47 | str += *iter; 48 | } 49 | } 50 | return str; 51 | } 52 | 53 | template 54 | LIBASSERT_ATTR_COLD 55 | std::vector concat(std::vector a, std::vector b) { 56 | a.insert( 57 | a.end(), 58 | std::make_move_iterator(b.begin()), 59 | std::make_move_iterator(b.end()) 60 | ); 61 | return a; 62 | } 63 | 64 | constexpr const char* const whitespace_chars = " \t\n\r\f\v"; 65 | 66 | LIBASSERT_ATTR_COLD 67 | std::string_view trim(std::string_view s); 68 | 69 | LIBASSERT_ATTR_COLD 70 | void replace_all_dynamic(std::string& str, std::string_view text, std::string_view replacement); 71 | 72 | LIBASSERT_ATTR_COLD LIBASSERT_EXPORT_TESTING 73 | void replace_all(std::string& str, const std::regex& re, std::string_view replacement); 74 | 75 | LIBASSERT_ATTR_COLD LIBASSERT_EXPORT_TESTING 76 | void replace_all(std::string& str, std::string_view substr, std::string_view replacement); 77 | 78 | LIBASSERT_ATTR_COLD 79 | void replace_all_template(std::string& str, const std::pair& rule); 80 | 81 | LIBASSERT_ATTR_COLD 82 | std::string indent(std::string_view str, size_t depth, char c = ' ', bool ignore_first = false); 83 | 84 | /* 85 | * Other 86 | */ 87 | 88 | // Container utility 89 | template class needle { 90 | // TODO: Re-evaluate 91 | const N& needle_value; 92 | public: 93 | explicit needle(const N& n) : needle_value(n) {} 94 | template 95 | constexpr V lookup(const K& option, const V& result, const Rest&... rest) { 96 | if(needle_value == option) { return result; } 97 | if constexpr(sizeof...(Rest) > 0) { return lookup(rest...); } 98 | else { LIBASSERT_PRIMITIVE_DEBUG_ASSERT(false); LIBASSERT_UNREACHABLE_CALL; } 99 | } 100 | template 101 | constexpr bool is_in(const Args&... option) { 102 | return ((needle_value == option) || ... || false); 103 | } 104 | }; 105 | 106 | #if LIBASSERT_IS_GCC && LIBASSERT_GCC_VERSION < 900 107 | // note: the use of U here is to workaround a gcc 8 issue https://godbolt.org/z/bdsWhdGj3 108 | template 109 | constexpr std::array, N> to_array_impl(U(&&a)[N], std::index_sequence) { 110 | return {{std::move(a[I])...}}; 111 | } 112 | template 113 | constexpr std::array, N> to_array(U(&&a)[N]) { 114 | return to_array_impl(std::move(a), std::make_index_sequence{}); 115 | } 116 | #else 117 | // unfortunately the above workaround ICEs MSVC https://godbolt.org/z/bjMEcY9fM 118 | template 119 | constexpr std::array, N> to_array_impl(T(&&a)[N], std::index_sequence) { 120 | return {{std::move(a[I])...}}; 121 | } 122 | template 123 | constexpr std::array, N> to_array(T(&&a)[N]) { 124 | return to_array_impl(std::move(a), std::make_index_sequence{}); 125 | } 126 | #endif 127 | 128 | template 129 | constexpr void constexpr_swap(A& a, B& b) { 130 | B tmp = std::move(b); 131 | b = std::move(a); 132 | a = std::move(tmp); 133 | } 134 | 135 | // cmp(a, b) should return whether a comes before b 136 | template 137 | constexpr void constexpr_sort(std::array& arr, const C& cmp) { 138 | // insertion sort is fine for small arrays 139 | static_assert(N <= 65); 140 | for(std::size_t i = 1; i < arr.size(); i++) { 141 | for(std::size_t j = 0; j < i; j++) { 142 | if(cmp(arr[j], arr[i])) { 143 | constexpr_swap(arr[i], arr[j]); 144 | } 145 | } 146 | } 147 | } 148 | 149 | template::value, int>::type = 0> 150 | constexpr T popcount(T value) { 151 | T pop = 0; 152 | while(value) { 153 | value &= value - 1; 154 | pop++; 155 | } 156 | return pop; 157 | } 158 | 159 | static_assert(popcount(0U) == 0); 160 | static_assert(popcount(1U) == 1); 161 | static_assert(popcount(2U) == 1); 162 | static_assert(popcount(3U) == 2); 163 | static_assert(popcount(0xf0U) == 4); 164 | 165 | template 166 | LIBASSERT_ATTR_COLD 167 | static constexpr T n_digits(T value) { 168 | return value < 10 ? 1 : 1 + n_digits(value / 10); 169 | } 170 | 171 | static_assert(n_digits(0) == 1); 172 | static_assert(n_digits(1) == 1); 173 | static_assert(n_digits(9) == 1); 174 | static_assert(n_digits(10) == 2); 175 | static_assert(n_digits(11) == 2); 176 | static_assert(n_digits(1024) == 4); 177 | 178 | inline bool operator==(const color_scheme& a, const color_scheme& b) { 179 | return a.string == b.string 180 | && a.escape == b.escape 181 | && a.keyword == b.keyword 182 | && a.named_literal == b.named_literal 183 | && a.number == b.number 184 | && a.punctuation == b.punctuation 185 | && a.operator_token == b.operator_token 186 | && a.call_identifier == b.call_identifier 187 | && a.scope_resolution_identifier == b.scope_resolution_identifier 188 | && a.identifier == b.identifier 189 | && a.accent == b.accent 190 | && a.unknown == b.unknown 191 | && a.reset == b.reset; 192 | } 193 | } 194 | 195 | #endif 196 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Don't run tests when library is used with add_subdirectory 2 | if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 3 | if(WIN32) 4 | add_custom_command( 5 | TARGET libassert-lib POST_BUILD 6 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 7 | $ 8 | $ 9 | ) 10 | endif() 11 | include(CTest) 12 | 13 | add_executable( 14 | demo 15 | tests/demo/bar.cpp 16 | tests/demo/baz/demo.cpp 17 | tests/demo/demo.cpp 18 | tests/demo/foo.cpp 19 | ) 20 | target_link_libraries(demo PRIVATE libassert-lib) 21 | target_compile_definitions(demo PRIVATE LIBASSERT_LOWERCASE) 22 | if(LIBASSERT_USE_MAGIC_ENUM) 23 | target_compile_definitions(demo PRIVATE LIBASSERT_USE_MAGIC_ENUM) 24 | endif() 25 | target_compile_features( 26 | demo 27 | PUBLIC cxx_std_20 28 | ) 29 | target_compile_definitions( 30 | demo 31 | PUBLIC LIBASSERT_SAFE_COMPARISONS 32 | ) 33 | 34 | if(LIBASSERT_USE_MAGIC_ENUM) 35 | add_executable(integration tests/integration/integration.cpp tests/integration/a.cpp tests/integration/x/a.cpp) 36 | # Temporary workaround for Visual Studio 2022 bug with __builtin_LINE() and __builtin_FILE() 37 | # https://developercommunity.visualstudio.com/t/__builtin_LINE-function-is-reporting-w/10439054?space=62&q=__builtin_function 38 | # TODO: Workaround in the header for end users? 39 | target_compile_features(integration PUBLIC cxx_std_20) 40 | target_link_libraries(integration PRIVATE libassert-lib) 41 | target_compile_definitions( 42 | integration 43 | PRIVATE 44 | LIBASSERT_USE_MAGIC_ENUM 45 | LIBASSERT_LOWERCASE 46 | LIBASSERT_SAFE_COMPARISONS 47 | ) 48 | add_test( 49 | NAME integration 50 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests 51 | COMMAND 52 | python3 run-tests.py $ ${CMAKE_BUILD_TYPE} ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_STANDARD} 53 | ) 54 | set(integration_target integration) 55 | else() 56 | set(integration_target) 57 | endif() 58 | 59 | set( 60 | dsym_targets 61 | demo 62 | ${integration_target} 63 | ) 64 | 65 | set( 66 | all_targets 67 | demo 68 | ${integration_target} 69 | ) 70 | 71 | include(FetchContent) 72 | FetchContent_Declare( 73 | googletest 74 | GIT_REPOSITORY "https://github.com/google/googletest.git" 75 | GIT_TAG f8d7d77c06936315286eb55f8de22cd23c188571 # v1.14.0 76 | ) 77 | # For Windows: Prevent overriding the parent project's compiler/linker settings 78 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 79 | FetchContent_MakeAvailable(googletest) 80 | 81 | include(FetchContent) 82 | FetchContent_Declare( 83 | Catch2 84 | GIT_SHALLOW TRUE 85 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 86 | GIT_TAG 4e8d92bf02f7d1c8006a0e7a5ecabd8e62d98502 # v3.6.0 87 | ) 88 | FetchContent_MakeAvailable(Catch2) 89 | 90 | include(FetchContent) 91 | FetchContent_Declare( 92 | fmt 93 | GIT_SHALLOW TRUE 94 | GIT_REPOSITORY https://github.com/fmtlib/fmt.git 95 | GIT_TAG e69e5f977d458f2650bb346dadf2ad30c5320281 # v10.2.1 96 | ) 97 | FetchContent_MakeAvailable(fmt) 98 | 99 | # For catch 2 only 100 | # list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/contrib) 101 | # For catch 3 only 102 | list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras) 103 | 104 | set( 105 | unit_test_sources 106 | tests/unit/constexpr_contexts.cpp 107 | tests/unit/disambiguation.cpp 108 | tests/unit/lexer.cpp 109 | tests/unit/literals.cpp 110 | tests/unit/test_public_utilities.cpp 111 | tests/unit/test_type_prettier.cpp 112 | tests/unit/type_handling.cpp 113 | tests/unit/stringify.cpp 114 | tests/unit/fmt-test.cpp 115 | tests/unit/std_format20.cpp 116 | tests/unit/assertion_tests.cpp 117 | ) 118 | if(cxx_std_23 IN_LIST CMAKE_CXX_COMPILE_FEATURES) 119 | LIST(APPEND unit_test_sources tests/unit/std_format23.cpp) 120 | endif() 121 | foreach(test_file ${unit_test_sources}) 122 | get_filename_component(test_name ${test_file} NAME_WE) 123 | list(APPEND all_targets ${test_name}) 124 | add_executable(${test_name} ${test_file}) 125 | target_link_libraries(${test_name} PRIVATE libassert-lib) 126 | target_include_directories(${test_name} PRIVATE src) 127 | target_compile_definitions(${test_name} PRIVATE LIBASSERT_BUILD_TESTING) 128 | target_compile_features(${test_name} PUBLIC cxx_std_17) 129 | if(LIBASSERT_USE_CI_WRAPPER) 130 | add_test( 131 | NAME ${test_name} 132 | COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/tests/ci-wrapper.py $ 133 | ) 134 | else() 135 | add_test(NAME ${test_name} COMMAND ${test_name}) 136 | endif() 137 | list(APPEND dsym_targets ${test_name}) 138 | endforeach() 139 | 140 | target_link_libraries(lexer PRIVATE GTest::gtest_main) 141 | target_link_libraries(fmt-test PRIVATE GTest::gtest_main fmt::fmt) 142 | target_link_libraries(std_format20 PRIVATE GTest::gtest_main fmt::fmt) 143 | target_link_libraries(assertion_tests PRIVATE GTest::gtest_main) 144 | target_link_libraries(stringify PRIVATE GTest::gtest_main) 145 | target_compile_options(assertion_tests PRIVATE $<$:/permissive- /Zc:preprocessor>) 146 | target_compile_definitions(fmt-test PRIVATE LIBASSERT_USE_FMT) 147 | target_compile_definitions(std_format20 PRIVATE LIBASSERT_USE_FMT) 148 | target_compile_options(lexer PRIVATE $<$:/Zc:preprocessor>) 149 | target_compile_options(fmt-test PRIVATE $<$:/Zc:preprocessor>) 150 | target_compile_options(stringify PRIVATE $<$:/Zc:preprocessor>) 151 | target_compile_options(std_format20 PRIVATE $<$:/Zc:preprocessor>) 152 | target_compile_features(std_format20 PRIVATE cxx_std_20) 153 | if(cxx_std_23 IN_LIST CMAKE_CXX_COMPILE_FEATURES) 154 | target_link_libraries(std_format23 PRIVATE GTest::gtest_main fmt::fmt) 155 | target_compile_definitions(std_format23 PRIVATE LIBASSERT_USE_FMT) 156 | target_compile_features(std_format23 PRIVATE cxx_std_23) 157 | target_compile_options(std_format23 PRIVATE $<$:/Zc:preprocessor>) 158 | endif() 159 | 160 | set( 161 | binary_sources 162 | tests/binaries/basic_test.cpp 163 | tests/binaries/basic_demo.cpp 164 | tests/binaries/gtest-demo.cpp 165 | tests/binaries/catch2-demo.cpp 166 | tests/binaries/tokens_and_highlighting.cpp 167 | ) 168 | foreach(test_file ${binary_sources}) 169 | get_filename_component(test_name ${test_file} NAME_WE) 170 | list(APPEND all_targets ${test_name}) 171 | add_executable(${test_name} ${test_file}) 172 | target_link_libraries(${test_name} PRIVATE libassert-lib) 173 | target_include_directories(${test_name} PRIVATE src) 174 | target_compile_definitions(${test_name} PRIVATE LIBASSERT_BUILD_TESTING) 175 | target_compile_features(${test_name} PUBLIC cxx_std_17) 176 | list(APPEND dsym_targets ${test_name}) 177 | endforeach() 178 | 179 | target_link_libraries(gtest-demo PRIVATE GTest::gtest_main) 180 | target_compile_options(gtest-demo PRIVATE $<$:/Zc:preprocessor>) 181 | target_link_libraries(catch2-demo PRIVATE Catch2::Catch2WithMain) 182 | target_compile_options(catch2-demo PRIVATE $<$:/Zc:preprocessor>) 183 | target_compile_definitions(basic_demo PRIVATE LIBASSERT_BREAK_ON_FAIL) 184 | 185 | if(APPLE) 186 | foreach(target ${dsym_targets}) 187 | add_custom_command( 188 | TARGET ${target} 189 | POST_BUILD 190 | COMMAND dsymutil $ 191 | ) 192 | endforeach() 193 | endif() 194 | 195 | foreach(target ${all_targets}) 196 | target_compile_options( 197 | ${target_name} 198 | PRIVATE 199 | ${warning_options} 200 | ) 201 | endforeach() 202 | endif() 203 | -------------------------------------------------------------------------------- /tests/add_subdirectory-integration/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(demo_project VERSION 0.0.1 LANGUAGES CXX) 4 | 5 | add_executable(main main.cpp) 6 | 7 | add_subdirectory(libassert) 8 | target_link_libraries(main libassert::assert) 9 | 10 | if(WIN32) 11 | add_custom_command( 12 | TARGET main POST_BUILD 13 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 14 | $ 15 | $ 16 | ) 17 | add_custom_command( 18 | TARGET main POST_BUILD 19 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 20 | $ 21 | $ 22 | ) 23 | endif() 24 | -------------------------------------------------------------------------------- /tests/add_subdirectory-integration/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | ASSERT(true); 5 | ASSUME(true); 6 | DEBUG_ASSERT(true); 7 | } 8 | -------------------------------------------------------------------------------- /tests/binaries/basic_demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | ASSERT(1 + 1 == 3); 5 | } 6 | -------------------------------------------------------------------------------- /tests/binaries/basic_test.cpp: -------------------------------------------------------------------------------- 1 | // Most basic of tests 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | std::optional foo() { 10 | return 2.5f; 11 | } 12 | 13 | int main() { 14 | auto f = *DEBUG_ASSERT_VAL(foo()); 15 | static_assert(std::is_same::value); 16 | assert(f == 2.5f); 17 | } 18 | -------------------------------------------------------------------------------- /tests/binaries/catch2-demo.cpp: -------------------------------------------------------------------------------- 1 | #include "catch2/catch_test_macros.hpp" 2 | #include 3 | 4 | TEST_CASE("1 + 1 is 2") { 5 | ASSERT(1 + 1 == 3); 6 | } 7 | 8 | void foo(int x) { 9 | LIBASSERT_ASSERT(x >= 20, "foobar"); 10 | } 11 | 12 | TEST_CASE("REQUIRE_ASSERT FAIL") { 13 | REQUIRE_ASSERT(foo(20)); 14 | } 15 | 16 | TEST_CASE("REQUIRE_ASSERT PASS") { 17 | REQUIRE_ASSERT(foo(5)); 18 | } 19 | -------------------------------------------------------------------------------- /tests/binaries/gtest-demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | TEST(Addition, Arithmetic) { 4 | ASSERT(1 + 1 == 3); 5 | } 6 | -------------------------------------------------------------------------------- /tests/binaries/tokens_and_highlighting.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "analysis.hpp" 9 | 10 | int main() { 11 | std::ifstream file("tests/test_program.cpp"); // just a test program that doesn't have preprocessor directives, which we don't tokenize 12 | std::ostringstream buf; 13 | buf< 3 | -------------------------------------------------------------------------------- /tests/demo/baz/demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // This file is used for testing path disambiguation 3 | 4 | void wubble() { 5 | debug_assert(false); 6 | } 7 | -------------------------------------------------------------------------------- /tests/demo/demo.cpp: -------------------------------------------------------------------------------- 1 | #define _CRT_SECURE_NO_WARNINGS // for fopen 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #define ESC "\033[" 24 | #define RED ESC "1;31m" 25 | #define GREEN ESC "1;32m" 26 | #define BLUE ESC "1;34m" 27 | #define CYAN ESC "1;36m" 28 | #define PURPL ESC "1;35m" 29 | #define DARK ESC "1;30m" 30 | #define RESET ESC "0m" 31 | 32 | #define STDIN_FILENO 0 33 | #define STDOUT_FILENO 1 34 | #define STDERR_FILENO 2 35 | 36 | void qux(); 37 | void wubble(); 38 | 39 | void custom_fail(const libassert::assertion_info& assertion) { 40 | std::cerr< struct S { 61 | T x; 62 | S() = default; 63 | ~S() = default; 64 | S(T&& _x) : x(std::forward(_x)) {} 65 | // moveable, not copyable 66 | S(const S&) = delete; 67 | S(S&&) noexcept = default; 68 | S& operator=(const S&) = delete; 69 | S& operator=(S&&) noexcept = default; 70 | bool operator==(const S& s) const { return x == s.x; } 71 | friend std::ostream& operator<<(std::ostream& o, const S& s) { 72 | o<<"I'm S<"<()<<"> and I contain:"< struct S { 81 | bool operator==(const S&) const { return false; } 82 | }; 83 | 84 | struct P { 85 | std::string str; 86 | P() = default; 87 | ~P() = delete; 88 | P(const P&) = delete; 89 | P(P&&) = default; 90 | P& operator=(const P&) = delete; 91 | P& operator=(P&&) = delete; 92 | bool operator==(const P& p) const { return str == p.str; } 93 | friend std::ostream& operator<<(std::ostream& o, const P& p) { 94 | o<& map) { 144 | #if __cplusplus >= 202002L 145 | DEBUG_ASSERT(map.contains("foo"), "expected key not found", map); 146 | #else 147 | DEBUG_ASSERT(map.count("foo") != 1, "expected key not found", map); 148 | #endif 149 | DEBUG_ASSERT(map.at("bar") >= 0, "unexpected value for foo in the map", map); 150 | } 151 | 152 | #define O_RDONLY 0 153 | int open(const char*, int) { 154 | return -1; 155 | } 156 | 157 | std::optional get_param() { 158 | return {}; 159 | } 160 | 161 | int get_mask() { 162 | return 0b00101101; 163 | } 164 | 165 | // disable unsafe use of bool warning msvc 166 | #ifdef _MSC_VER 167 | #pragma warning(disable: 4806) 168 | #endif 169 | 170 | class foo { 171 | public: 172 | template void bar([[maybe_unused]] std::pair x) { 173 | baz(); 174 | } 175 | 176 | void baz() { 177 | puts(""); 178 | // General demos 179 | { 180 | zoog({ 181 | //{ "foo", 2 }, 182 | { "bar", -2 }, 183 | { "baz", 20 } 184 | }); 185 | std::vector vec = {2, 3, 5, 7, 11, 13}; 186 | debug_assert(vec.size() > min_items(), "vector doesn't have enough items", vec); 187 | } 188 | const char* path = "/home/foobar/baz"; 189 | { 190 | int fd = open(path, O_RDONLY); 191 | debug_assert(fd >= 0, "Internal error with foobars", errno, path); 192 | LIBASSERT_PHONY_USE(fd); 193 | } 194 | { 195 | debug_assert(open(path, O_RDONLY) >= 0, "Internal error with foobars", errno, path); 196 | } 197 | { 198 | FILE* f = ASSERT_VAL(fopen(path, "r") != nullptr, "Internal error with foobars", errno, path); 199 | LIBASSERT_PHONY_USE(f); 200 | } 201 | debug_assert(false, "Error while doing XYZ"); 202 | debug_assert(false); 203 | DEBUG_ASSERT((puts("DEBUG_ASSERT called") && false)); 204 | 205 | { 206 | std::map map {{1,1}}; 207 | DEBUG_ASSERT(map.count(1) == 2); 208 | debug_assert(map.count(1) >= 2 * garple(), "Error while doing XYZ"); 209 | } 210 | debug_assert(0, 2 == garple()); 211 | { 212 | std::optional parameter; 213 | #ifndef _MSC_VER 214 | if(auto i = *ASSERT_VAL(parameter)) { 215 | static_assert(std::is_same::value); 216 | } 217 | float f = *assert_val(get_param()); 218 | (void)f; 219 | #else 220 | ASSERT(parameter); 221 | debug_assert(get_param()); 222 | #endif 223 | auto x = [&] () -> decltype(auto) { return ASSERT_VAL(parameter); }; 224 | static_assert(std::is_same&>::value); 225 | } 226 | 227 | qux(); 228 | 229 | { 230 | M() < 2; 231 | puts("----"); 232 | debug_assert(M() < 2); 233 | puts("----"); 234 | M m; 235 | puts("----"); 236 | debug_assert(m < 2); 237 | puts("----"); 238 | } 239 | 240 | 241 | debug_assert(true ? false : true == false); 242 | debug_assert(true ? false : true, "pffft"); 243 | 244 | wubble(); 245 | 246 | rec(10); 247 | 248 | recursive_a(10); 249 | 250 | ASSERT(18446744073709551606ULL == -10); 251 | 252 | ASSERT(get_mask() == 0b00001101); 253 | debug_assert(0xf == 16); 254 | 255 | { 256 | std::string s = "test\n"; 257 | int i = 0; 258 | debug_assert(s == "test"); 259 | ASSERT(s[i] == 'c', "", s, i); 260 | } 261 | { 262 | debug_assert(S>(2) == S>(4)); 263 | S e, f; 264 | debug_assert(e == f); 265 | } 266 | 267 | long long x = -9'223'372'036'854'775'807; 268 | debug_assert(x & 0x4); 269 | 270 | debug_assert(!x and true == 2); 271 | debug_assert((puts("A"), false) && (puts("B"), false)); 272 | 273 | { 274 | #if defined(__GNUC__) || defined(__GNUG__) // gcc/clang 275 | #pragma GCC diagnostic ignored "-Wshadow" 276 | #endif 277 | std::string s = "h1eLlo"; 278 | debug_assert(std::find_if(s.begin(), s.end(), [](char c) { 279 | debug_assert(not isdigit(c), c); 280 | return c >= 'A' and c <= 'Z'; 281 | }) == s.end()); 282 | } 283 | 284 | { 285 | std::set a = { 2, 2, 4, 6, 10 }; 286 | std::set b = { 2, 2, 5, 6, 10 }; 287 | std::vector c = { 1.2, 2.44, 3.15159, 5.2 }; 288 | debug_assert(a == b, c); 289 | std::map m0 = { 290 | {"foo", 2}, 291 | {"bar", -2} 292 | }; 293 | debug_assert(false, m0); 294 | std::map> m1 = { 295 | {"foo", {1, -2, 3, -4}}, 296 | {"bar", {-100, 200, 400, -800}} 297 | }; 298 | debug_assert(false, m1); 299 | auto t = std::make_tuple(1, 0.1 + 0.2, "foobars"); 300 | debug_assert(false, t); 301 | std::array arr = {1,2,3,4,5,6,7,8,9,10}; 302 | debug_assert(false, arr); 303 | } 304 | 305 | // Numeric 306 | // Tests useful during development 307 | /*debug_assert(.1f == .1); 308 | debug_assert(1.0 == 1.0 + std::numeric_limits::epsilon()); 309 | ASSERT_EQ(0x12p2, 12); 310 | ASSERT_EQ(0x12p2, 0b10); 311 | debug_assert(0b1000000 == 0x3); 312 | debug_assert(.1 == 2); 313 | debug_assert(true == false); 314 | debug_assert(true ? false : true == false); 315 | debug_assert(0b100 == 0x3); 316 | 317 | debug_assert(0 == (2 == garple())); 318 | ASSERT_GTEQ(map.count(1 == 1), 2); 319 | ASSERT_EQ(map.count(1), 2, "Error while doing XYZ"); 320 | ASSERT_GTEQ(map.count(2 * garple()), 2, "Error while doing XYZ"); 321 | debug_assert(S>(2) == S>(4)); 322 | S> a(1), b(2); 323 | ASSERT_EQ(a, b); 324 | const S> c(4), d(8); 325 | ASSERT_EQ(c, d); 326 | S g, h; 327 | ASSERT_EQ(g, h); 328 | ASSERT_EQ(1, 2); 329 | ASSERT_EQ(&a, nullptr); 330 | ASSERT_EQ((uintptr_t)&a, 0ULL & 0ULL); 331 | ASSERT_AND(&a, nullptr); 332 | ASSERT_AND(nullptr && nullptr, nullptr); 333 | ASSERT_AND(&a, nullptr && nullptr); 334 | ASSERT_AND((bool)nullptr && (bool)nullptr, (bool)nullptr); 335 | ASSERT_AND((uintptr_t)&a, (bool)nullptr && (bool)nullptr); // FIXME: parentheses 336 | ASSERT_EQ(foo, (int*)nullptr); 337 | 338 | 339 | debug_assert(0 == (2 == garple())); 340 | //debug_assert(0 == 2 == garple()); 341 | 342 | debug_assert(true ? false : true, "pffft"); 343 | { 344 | std::string x = "aa"; 345 | std::string y = "bb"; 346 | debug_assert(x == y); 347 | } 348 | { 349 | P x {"aa"}; 350 | P y {"bb"}; 351 | debug_assert(x == y); 352 | } 353 | { 354 | P x {"aa"}; 355 | debug_assert(x == P {"bb"}); 356 | } 357 | { 358 | const P x {"aa"}; 359 | debug_assert(x == P {"bb"}); 360 | } 361 | debug_assert((42 & 3U) == 1UL); 362 | 363 | debug_assert([](int a, int b) { 364 | return a + b; 365 | } (10, 32) not_eq 42); 366 | debug_assert([](){return 42;}() not_eq 42); 367 | debug_assert([&](T a, T b){return a+b;}(10, 32) not_eq 42); 368 | ASSERT_NEQ([](int a, int b) { 369 | return a + b; 370 | } (10, 32), 42); 371 | debug_assert('\n' == '\t'); 372 | debug_assert(<:](){return 42;%>() not_eq 42); 373 | 374 | debug_assert(&a == nullptr); 375 | 376 | { 377 | std::string s = "h1ello"; 378 | debug_assert(std::find_if(s.begin(), s.end(), [](char c) { 379 | if(c == '1') debug_assert(c != '1'); 380 | //debug_assert(!isdigit(c), c); 381 | return c == 'e'; 382 | }) == s.end()); 383 | } 384 | 385 | debug_assert(0.1 == 0.2); 386 | debug_assert(.1 == 0.2); 387 | debug_assert(.1f == 0.2); 388 | 389 | debug_assert(true); // this should lead to another debug_assert(false) because we're in demo mode*/ 390 | } 391 | }; 392 | 393 | int main() { 394 | libassert::enable_virtual_terminal_processing_if_needed(); 395 | libassert::set_failure_handler(custom_fail); 396 | foo f; 397 | f.bar({}); 398 | } 399 | -------------------------------------------------------------------------------- /tests/demo/foo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void qux() { 4 | debug_assert(false); 5 | } 6 | -------------------------------------------------------------------------------- /tests/fetchcontent-integration/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(demo_project VERSION 0.0.1 LANGUAGES CXX) 4 | 5 | add_executable(main main.cpp) 6 | 7 | set(LIBASSERT_TAG "" CACHE STRING "libassert git tag") 8 | 9 | include(FetchContent) 10 | FetchContent_Declare( 11 | assert 12 | GIT_REPOSITORY https://github.com/jeremy-rifkin/libassert.git 13 | GIT_TAG ${LIBASSERT_TAG} 14 | ) 15 | FetchContent_MakeAvailable(assert) 16 | target_link_libraries(main libassert::assert) 17 | 18 | if(WIN32) 19 | add_custom_command( 20 | TARGET main POST_BUILD 21 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 22 | $ 23 | $ 24 | ) 25 | add_custom_command( 26 | TARGET main POST_BUILD 27 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 28 | $ 29 | $ 30 | ) 31 | endif() 32 | -------------------------------------------------------------------------------- /tests/fetchcontent-integration/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | int main() { 6 | ASSERT(true); 7 | ASSUME(true); 8 | DEBUG_ASSERT(true); 9 | std::cout<<"Good to go"< 2 | 3 | #include 4 | 5 | int main() { 6 | ASSERT(true); 7 | ASSUME(true); 8 | DEBUG_ASSERT(true); 9 | std::cout<<"Good to go"< 2 | // This file is used for testing path disambiguation 3 | 4 | void test_path_differentiation_2(); 5 | 6 | void test_path_differentiation() { 7 | test_path_differentiation_2(); 8 | } 9 | -------------------------------------------------------------------------------- /tests/integration/expected/clang.macos.txt: -------------------------------------------------------------------------------- 1 | ===================== [simple recursion] ===================== 2 | Debug Assertion failed at integration.cpp:501: void rec(int): 3 | debug_assert(false); 4 | 5 | Stack trace: 6 | # 1 rec(int) 7 | at integration.cpp:501 8 | # 2 rec(int) 9 | at integration.cpp:502 10 | | | 11 | | 8 layers of recursion were folded | 12 | | | 13 | #11 rec(int) 14 | at integration.cpp:502 15 | #12 test_class::something_else() 16 | at integration.cpp:2600 17 | #13 void test_class::something(std::__1::pair) 18 | at integration.cpp:1004 19 | #14 main 20 | at integration.cpp:402 21 | 22 | 23 | ===================== [other recursion] ===================== 24 | Debug Assertion failed at integration.cpp:508: void recursive_a(int): 25 | debug_assert(false); 26 | 27 | Stack trace: 28 | # 1 recursive_a(int) 29 | at integration.cpp:508 30 | # 2 recursive_b(int) 31 | at integration.cpp:514 32 | # 3 recursive_a(int) 33 | at integration.cpp:509 34 | # 4 recursive_b(int) 35 | at integration.cpp:514 36 | # 5 recursive_a(int) 37 | at integration.cpp:509 38 | # 6 recursive_b(int) 39 | at integration.cpp:514 40 | # 7 recursive_a(int) 41 | at integration.cpp:509 42 | # 8 recursive_b(int) 43 | at integration.cpp:514 44 | # 9 recursive_a(int) 45 | at integration.cpp:509 46 | #10 recursive_b(int) 47 | at integration.cpp:514 48 | #11 recursive_a(int) 49 | at integration.cpp:509 50 | #12 test_class::something_else() 51 | at integration.cpp:2700 52 | #13 void test_class::something(std::__1::pair) 53 | at integration.cpp:1004 54 | #14 main 55 | at integration.cpp:402 56 | 57 | 58 | ===================== [Path differentiation] ===================== 59 | Debug Assertion failed at x/a.cpp:5: void test_path_differentiation_2(): 60 | debug_assert(false); 61 | 62 | Stack trace: 63 | # 1 test_path_differentiation_2() 64 | at x/a.cpp:5 65 | # 2 test_path_differentiation() 66 | at integration/a.cpp:7 67 | # 3 test_class::something_else() 68 | at integration.cpp:2800 69 | # 4 void test_class::something(std::__1::pair) 70 | at integration.cpp:1004 71 | # 5 main 72 | at integration.cpp:402 73 | 74 | 75 | ===================== [Type cleaning] ===================== 76 | Debug Assertion failed at integration.cpp:3305: void test_class::test_pretty_function_cleaning(const std::map> &) [T = int]: 77 | debug_assert(map == other); 78 | Where: 79 | map => std::map>: [["bar", ["b1", "b3", "b5"]], ["foo", ["f1", "f3", "f5"]]] 80 | other => std::map>: [] 81 | 82 | Stack trace: 83 | # 1 test_class::test_pretty_function_cleaning(std::__1::map, std::__1::less> const&) 84 | at integration.cpp:3305 85 | # 2 test_class::something_else() 86 | at integration.cpp:3201 87 | # 3 void test_class::something(std::__1::pair) 88 | at integration.cpp:1004 89 | # 4 main 90 | at integration.cpp:402 91 | 92 | 93 | ===================== [Complex type resolution] ===================== 94 | Debug Assertion failed at integration.cpp:3602: void test_class::test_complex_typing(const volatile std::vector *const &, int *, const char (&)[4], decltype(&complex_typing::S::foo), int complex_typing::S::*) [T = int]: 95 | debug_assert(false); 96 | 97 | Stack trace: 98 | # 1 test_class::test_complex_typing(std::__1::vector const volatile* const&, int*, char const (&) [4], void (complex_typing::S::*)(int, std::string, void***, void* (*)(int), void (* (*)(int))(), void (* (* (*)(int)) [5])()), int complex_typing::S::*) 99 | at integration.cpp:3602 100 | # 2 test_class::something_else() 101 | at integration.cpp:3502 102 | # 3 void test_class::something(std::__1::pair) 103 | at integration.cpp:1004 104 | # 4 main 105 | at integration.cpp:402 106 | 107 | 108 | -------------------------------------------------------------------------------- /tests/integration/expected/clang.txt: -------------------------------------------------------------------------------- 1 | ===================== [simple recursion] ===================== 2 | Debug Assertion failed at integration.cpp:501: void rec(int): 3 | debug_assert(false); 4 | 5 | Stack trace: 6 | # 1 rec(int) 7 | at integration.cpp:501 8 | # 2 rec(int) 9 | at integration.cpp:502 10 | | | 11 | | 8 layers of recursion were folded | 12 | | | 13 | #11 rec(int) 14 | at integration.cpp:502 15 | #12 test_class::something_else() 16 | at integration.cpp:2600 17 | #13 void test_class::something(std::pair) 18 | at integration.cpp:1004 19 | #14 main 20 | at integration.cpp:402 21 | 22 | 23 | ===================== [other recursion] ===================== 24 | Debug Assertion failed at integration.cpp:508: void recursive_a(int): 25 | debug_assert(false); 26 | 27 | Stack trace: 28 | # 1 recursive_a(int) 29 | at integration.cpp:508 30 | # 2 recursive_b(int) 31 | at integration.cpp:514 32 | # 3 recursive_a(int) 33 | at integration.cpp:509 34 | # 4 recursive_b(int) 35 | at integration.cpp:514 36 | # 5 recursive_a(int) 37 | at integration.cpp:509 38 | # 6 recursive_b(int) 39 | at integration.cpp:514 40 | # 7 recursive_a(int) 41 | at integration.cpp:509 42 | # 8 recursive_b(int) 43 | at integration.cpp:514 44 | # 9 recursive_a(int) 45 | at integration.cpp:509 46 | #10 recursive_b(int) 47 | at integration.cpp:514 48 | #11 recursive_a(int) 49 | at integration.cpp:509 50 | #12 test_class::something_else() 51 | at integration.cpp:2700 52 | #13 void test_class::something(std::pair) 53 | at integration.cpp:1004 54 | #14 main 55 | at integration.cpp:402 56 | 57 | 58 | ===================== [Path differentiation] ===================== 59 | Debug Assertion failed at x/a.cpp:5: void test_path_differentiation_2(): 60 | debug_assert(false); 61 | 62 | Stack trace: 63 | # 1 test_path_differentiation_2() 64 | at x/a.cpp:5 65 | # 2 test_path_differentiation() 66 | at integration/a.cpp:7 67 | # 3 test_class::something_else() 68 | at integration.cpp:2800 69 | # 4 void test_class::something(std::pair) 70 | at integration.cpp:1004 71 | # 5 main 72 | at integration.cpp:402 73 | 74 | 75 | ===================== [Type cleaning] ===================== 76 | Debug Assertion failed at integration.cpp:3305: void test_class::test_pretty_function_cleaning(const std::map> &) [T = int]: 77 | debug_assert(map == other); 78 | Where: 79 | map => std::map>: [["bar", ["b1", "b3", "b5"]], ["foo", ["f1", "f3", "f5"]]] 80 | other => std::map>: [] 81 | 82 | Stack trace: 83 | # 1 test_class::test_pretty_function_cleaning(std::map, std::less> const&) 84 | at integration.cpp:3305 85 | # 2 test_class::something_else() 86 | at integration.cpp:3201 87 | # 3 void test_class::something(std::pair) 88 | at integration.cpp:1004 89 | # 4 main 90 | at integration.cpp:402 91 | 92 | 93 | ===================== [Complex type resolution] ===================== 94 | Debug Assertion failed at integration.cpp:3602: void test_class::test_complex_typing(const volatile std::vector *const &, int *, const char (&)[4], decltype(&complex_typing::S::foo), int complex_typing::S::*) [T = int]: 95 | debug_assert(false); 96 | 97 | Stack trace: 98 | # 1 test_class::test_complex_typing(std::vector const volatile* const&, int*, char const (&) [4], void (complex_typing::S::*)(int, std::string, void***, void* (*)(int), void (*(*)(int))(), void (* (*(*)(int)) [5])()), int complex_typing::S::*) 99 | at integration.cpp:3602 100 | # 2 test_class::something_else() 101 | at integration.cpp:3502 102 | # 3 void test_class::something(std::pair) 103 | at integration.cpp:1004 104 | # 4 main 105 | at integration.cpp:402 106 | 107 | 108 | -------------------------------------------------------------------------------- /tests/integration/expected/clang.windows.txt: -------------------------------------------------------------------------------- 1 | ===================== [simple recursion] ===================== 2 | Debug Assertion failed at integration.cpp:501: void rec(int): 3 | debug_assert(false); 4 | 5 | Stack trace: 6 | # 1 rec(int) 7 | at integration.cpp:501 8 | # 2 rec(int) 9 | at integration.cpp:503 10 | | | 11 | | 8 layers of recursion were folded | 12 | | | 13 | #11 rec(int) 14 | at integration.cpp:503 15 | #12 test_class::something_else() 16 | at integration.cpp:2602 17 | #13 test_class::something(std::pair) 18 | at integration.cpp:1005 19 | #14 main() 20 | at integration.cpp:403 21 | 22 | 23 | ===================== [other recursion] ===================== 24 | Debug Assertion failed at integration.cpp:508: void recursive_a(int): 25 | debug_assert(false); 26 | 27 | Stack trace: 28 | # 1 recursive_a(int) 29 | at integration.cpp:508 30 | # 2 recursive_b(int) 31 | at integration.cpp:515 32 | # 3 recursive_a(int) 33 | at integration.cpp:510 34 | # 4 recursive_b(int) 35 | at integration.cpp:515 36 | # 5 recursive_a(int) 37 | at integration.cpp:510 38 | # 6 recursive_b(int) 39 | at integration.cpp:515 40 | # 7 recursive_a(int) 41 | at integration.cpp:510 42 | # 8 recursive_b(int) 43 | at integration.cpp:515 44 | # 9 recursive_a(int) 45 | at integration.cpp:510 46 | #10 recursive_b(int) 47 | at integration.cpp:515 48 | #11 recursive_a(int) 49 | at integration.cpp:510 50 | #12 test_class::something_else() 51 | at integration.cpp:2703 52 | #13 test_class::something(std::pair) 53 | at integration.cpp:1005 54 | #14 main() 55 | at integration.cpp:403 56 | 57 | 58 | ===================== [Path differentiation] ===================== 59 | Debug Assertion failed at x/a.cpp:5: void test_path_differentiation_2(): 60 | debug_assert(false); 61 | 62 | Stack trace: 63 | # 1 test_path_differentiation_2() 64 | at x/a.cpp:5 65 | # 2 test_path_differentiation() 66 | at integration/a.cpp:8 67 | # 3 test_class::something_else() 68 | at integration.cpp:2801 69 | # 4 test_class::something(std::pair) 70 | at integration.cpp:1005 71 | # 5 main() 72 | at integration.cpp:403 73 | 74 | 75 | ===================== [Type cleaning] ===================== 76 | Debug Assertion failed at integration.cpp:3305: void test_class::test_pretty_function_cleaning(const std::map> &) [T = int]: 77 | debug_assert(map == other); 78 | Where: 79 | map => std::map>: [["bar", ["b1", "b3", "b5"]], ["foo", ["f1", "f3", "f5"]]] 80 | other => std::map>: [] 81 | 82 | Stack trace: 83 | # 1 test_class::test_pretty_function_cleaning(std::map, std::less>&) 84 | at integration.cpp:3305 85 | # 2 test_class::something_else() 86 | at integration.cpp:3201 87 | # 3 test_class::something(std::pair) 88 | at integration.cpp:1005 89 | # 4 main() 90 | at integration.cpp:403 91 | 92 | 93 | ===================== [Complex type resolution] ===================== 94 | Debug Assertion failed at integration.cpp:3602: void test_class::test_complex_typing(const volatile std::vector *const &, int *, const char (&)[4], decltype(&complex_typing::S::foo), int complex_typing::S::*) [T = int]: 95 | debug_assert(false); 96 | 97 | Stack trace: 98 | # 1 test_class::test_complex_typing() 99 | at integration.cpp:3602 100 | # 2 test_class::something_else() 101 | at integration.cpp:3504 102 | # 3 test_class::something(std::pair) 103 | at integration.cpp:1005 104 | # 4 main() 105 | at integration.cpp:403 106 | 107 | 108 | -------------------------------------------------------------------------------- /tests/integration/expected/gnu.macos.txt: -------------------------------------------------------------------------------- 1 | ===================== [simple recursion] ===================== 2 | Debug Assertion failed at integration.cpp:501: void rec(int): 3 | debug_assert(false); 4 | 5 | Stack trace: 6 | # 1 rec(int) 7 | at integration.cpp:501 8 | # 2 rec(int) 9 | at integration.cpp:502 10 | | | 11 | | 8 layers of recursion were folded | 12 | | | 13 | #11 rec(int) 14 | at integration.cpp:502 15 | #12 test_class::something_else() 16 | at integration.cpp:2600 17 | #13 void test_class::something(std::pair) 18 | at integration.cpp:1004 19 | #14 main 20 | at integration.cpp:402 21 | 22 | 23 | ===================== [other recursion] ===================== 24 | Debug Assertion failed at integration.cpp:508: void recursive_a(int): 25 | debug_assert(false); 26 | 27 | Stack trace: 28 | # 1 recursive_a(int) 29 | at integration.cpp:508 30 | # 2 recursive_b(int) 31 | at integration.cpp:514 32 | # 3 recursive_a(int) 33 | at integration.cpp:509 34 | # 4 recursive_b(int) 35 | at integration.cpp:514 36 | # 5 recursive_a(int) 37 | at integration.cpp:509 38 | # 6 recursive_b(int) 39 | at integration.cpp:514 40 | # 7 recursive_a(int) 41 | at integration.cpp:509 42 | # 8 recursive_b(int) 43 | at integration.cpp:514 44 | # 9 recursive_a(int) 45 | at integration.cpp:509 46 | #10 recursive_b(int) 47 | at integration.cpp:514 48 | #11 recursive_a(int) 49 | at integration.cpp:509 50 | #12 test_class::something_else() 51 | at integration.cpp:2700 52 | #13 void test_class::something(std::pair) 53 | at integration.cpp:1004 54 | #14 main 55 | at integration.cpp:402 56 | 57 | 58 | ===================== [Path differentiation] ===================== 59 | Debug Assertion failed at x/a.cpp:5: void test_path_differentiation_2(): 60 | debug_assert(false); 61 | 62 | Stack trace: 63 | # 1 test_path_differentiation_2() 64 | at x/a.cpp:5 65 | # 2 test_path_differentiation() 66 | at integration/a.cpp:7 67 | # 3 test_class::something_else() 68 | at integration.cpp:2800 69 | # 4 void test_class::something(std::pair) 70 | at integration.cpp:1004 71 | # 5 main 72 | at integration.cpp:402 73 | 74 | 75 | ===================== [Type cleaning] ===================== 76 | Debug Assertion failed at integration.cpp:3305: void test_class::test_pretty_function_cleaning(const std::map>&) [with T = int]: 77 | debug_assert(map == other); 78 | Where: 79 | map => std::map>: [["bar", ["b1", "b3", "b5"]], ["foo", ["f1", "f3", "f5"]]] 80 | other => std::map>: [] 81 | 82 | Stack trace: 83 | # 1 test_class::test_pretty_function_cleaning(std::map, std::less> const&) 84 | at integration.cpp:3305 85 | # 2 test_class::something_else() 86 | at integration.cpp:3201 87 | # 3 void test_class::something(std::pair) 88 | at integration.cpp:1004 89 | # 4 main 90 | at integration.cpp:402 91 | 92 | 93 | ===================== [Complex type resolution] ===================== 94 | Debug Assertion failed at integration.cpp:3602: void test_class::test_complex_typing(const volatile std::vector* const&, int*, const char (&)[4], void (complex_typing::S::*)(int, std::string, void***, void* (*)(int), void (* (*)(int))(), void (* (* (*)(int))[5])()), int complex_typing::S::*) [with T = int; std::string = std::string]: 95 | debug_assert(false); 96 | 97 | Stack trace: 98 | # 1 test_class::test_complex_typing(std::vector const volatile* const&, int*, char const (&) [4], void (complex_typing::S::*)(int, std::string, void***, void* (*)(int), void (*(*)(int))(), void (* (*(*)(int)) [5])()), int complex_typing::S::*) 99 | at integration.cpp:3602 100 | # 2 test_class::something_else() 101 | at integration.cpp:3502 102 | # 3 void test_class::something(std::pair) 103 | at integration.cpp:1004 104 | # 4 main 105 | at integration.cpp:402 106 | 107 | 108 | -------------------------------------------------------------------------------- /tests/integration/expected/gnu.txt: -------------------------------------------------------------------------------- 1 | ===================== [simple recursion] ===================== 2 | Debug Assertion failed at integration.cpp:501: void rec(int): 3 | debug_assert(false); 4 | 5 | Stack trace: 6 | # 1 rec(int) 7 | at integration.cpp:501 8 | # 2 rec(int) 9 | at integration.cpp:502 10 | | | 11 | | 8 layers of recursion were folded | 12 | | | 13 | #11 rec(int) 14 | at integration.cpp:502 15 | #12 test_class::something_else() 16 | at integration.cpp:2600 17 | #13 void test_class::something(std::pair) 18 | at integration.cpp:1004 19 | #14 main 20 | at integration.cpp:402 21 | 22 | 23 | ===================== [other recursion] ===================== 24 | Debug Assertion failed at integration.cpp:508: void recursive_a(int): 25 | debug_assert(false); 26 | 27 | Stack trace: 28 | # 1 recursive_a(int) 29 | at integration.cpp:508 30 | # 2 recursive_b(int) 31 | at integration.cpp:514 32 | # 3 recursive_a(int) 33 | at integration.cpp:509 34 | # 4 recursive_b(int) 35 | at integration.cpp:514 36 | # 5 recursive_a(int) 37 | at integration.cpp:509 38 | # 6 recursive_b(int) 39 | at integration.cpp:514 40 | # 7 recursive_a(int) 41 | at integration.cpp:509 42 | # 8 recursive_b(int) 43 | at integration.cpp:514 44 | # 9 recursive_a(int) 45 | at integration.cpp:509 46 | #10 recursive_b(int) 47 | at integration.cpp:514 48 | #11 recursive_a(int) 49 | at integration.cpp:509 50 | #12 test_class::something_else() 51 | at integration.cpp:2700 52 | #13 void test_class::something(std::pair) 53 | at integration.cpp:1004 54 | #14 main 55 | at integration.cpp:402 56 | 57 | 58 | ===================== [Path differentiation] ===================== 59 | Debug Assertion failed at x/a.cpp:5: void test_path_differentiation_2(): 60 | debug_assert(false); 61 | 62 | Stack trace: 63 | # 1 test_path_differentiation_2() 64 | at x/a.cpp:5 65 | # 2 test_path_differentiation() 66 | at integration/a.cpp:7 67 | # 3 test_class::something_else() 68 | at integration.cpp:2800 69 | # 4 void test_class::something(std::pair) 70 | at integration.cpp:1004 71 | # 5 main 72 | at integration.cpp:402 73 | 74 | 75 | ===================== [Type cleaning] ===================== 76 | Debug Assertion failed at integration.cpp:3305: void test_class::test_pretty_function_cleaning(const std::map>&) [with T = int]: 77 | debug_assert(map == other); 78 | Where: 79 | map => std::map>: [["bar", ["b1", "b3", "b5"]], ["foo", ["f1", "f3", "f5"]]] 80 | other => std::map>: [] 81 | 82 | Stack trace: 83 | # 1 test_class::test_pretty_function_cleaning(std::map, std::less> const&) 84 | at integration.cpp:3305 85 | # 2 test_class::something_else() 86 | at integration.cpp:3201 87 | # 3 void test_class::something(std::pair) 88 | at integration.cpp:1004 89 | # 4 main 90 | at integration.cpp:402 91 | 92 | 93 | ===================== [Complex type resolution] ===================== 94 | Debug Assertion failed at integration.cpp:3602: void test_class::test_complex_typing(const volatile std::vector* const&, int*, const char (&)[4], void (complex_typing::S::*)(int, std::string, void***, void* (*)(int), void (* (*)(int))(), void (* (* (*)(int))[5])()), int complex_typing::S::*) [with T = int; std::string = std::string]: 95 | debug_assert(false); 96 | 97 | Stack trace: 98 | # 1 test_class::test_complex_typing(std::vector const volatile* const&, int*, char const (&) [4], void (complex_typing::S::*)(int, std::string, void***, void* (*)(int), void (*(*)(int))(), void (* (*(*)(int)) [5])()), int complex_typing::S::*) 99 | at integration.cpp:3602 100 | # 2 test_class::something_else() 101 | at integration.cpp:3502 102 | # 3 void test_class::something(std::pair) 103 | at integration.cpp:1004 104 | # 4 main 105 | at integration.cpp:402 106 | 107 | 108 | -------------------------------------------------------------------------------- /tests/integration/expected/gnu.windows.txt: -------------------------------------------------------------------------------- 1 | ===================== [simple recursion] ===================== 2 | Debug Assertion failed at integration.cpp:501: void rec(int): 3 | debug_assert(false); 4 | 5 | Stack trace: 6 | # 1 rec(int) 7 | at integration.cpp:501 8 | # 2 rec(int) 9 | at integration.cpp:502 10 | | | 11 | | 8 layers of recursion were folded | 12 | | | 13 | #11 rec(int) 14 | at integration.cpp:502 15 | #12 test_class::something_else() 16 | at integration.cpp:2600 17 | #13 void test_class::something(std::pair) 18 | at integration.cpp:1004 19 | #14 main 20 | at integration.cpp:402 21 | 22 | 23 | ===================== [other recursion] ===================== 24 | Debug Assertion failed at integration.cpp:508: void recursive_a(int): 25 | debug_assert(false); 26 | 27 | Stack trace: 28 | # 1 recursive_a(int) 29 | at integration.cpp:508 30 | # 2 recursive_b(int) 31 | at integration.cpp:514 32 | # 3 recursive_a(int) 33 | at integration.cpp:509 34 | # 4 recursive_b(int) 35 | at integration.cpp:514 36 | # 5 recursive_a(int) 37 | at integration.cpp:509 38 | # 6 recursive_b(int) 39 | at integration.cpp:514 40 | # 7 recursive_a(int) 41 | at integration.cpp:509 42 | # 8 recursive_b(int) 43 | at integration.cpp:514 44 | # 9 recursive_a(int) 45 | at integration.cpp:509 46 | #10 recursive_b(int) 47 | at integration.cpp:514 48 | #11 recursive_a(int) 49 | at integration.cpp:509 50 | #12 test_class::something_else() 51 | at integration.cpp:2700 52 | #13 void test_class::something(std::pair) 53 | at integration.cpp:1004 54 | #14 main 55 | at integration.cpp:402 56 | 57 | 58 | ===================== [Path differentiation] ===================== 59 | Debug Assertion failed at x/a.cpp:5: void test_path_differentiation_2(): 60 | debug_assert(false); 61 | 62 | Stack trace: 63 | # 1 test_path_differentiation_2() 64 | at x/a.cpp:5 65 | # 2 test_path_differentiation() 66 | at integration/a.cpp:7 67 | # 3 test_class::something_else() 68 | at integration.cpp:2800 69 | # 4 void test_class::something(std::pair) 70 | at integration.cpp:1004 71 | # 5 main 72 | at integration.cpp:402 73 | 74 | 75 | ===================== [Type cleaning] ===================== 76 | Debug Assertion failed at integration.cpp:3305: void test_class::test_pretty_function_cleaning(const std::map>&) [with T = int]: 77 | debug_assert(map == other); 78 | Where: 79 | map => std::map>: [["bar", ["b1", "b3", "b5"]], ["foo", ["f1", "f3", "f5"]]] 80 | other => std::map>: [] 81 | 82 | Stack trace: 83 | # 1 test_class::test_pretty_function_cleaning(std::map, std::less> const&) 84 | at integration.cpp:3305 85 | # 2 test_class::something_else() 86 | at integration.cpp:3201 87 | # 3 void test_class::something(std::pair) 88 | at integration.cpp:1004 89 | # 4 main 90 | at integration.cpp:402 91 | 92 | 93 | ===================== [Complex type resolution] ===================== 94 | Debug Assertion failed at integration.cpp:3602: void test_class::test_complex_typing(const volatile std::vector* const&, int*, const char (&)[4], void (complex_typing::S::*)(int, std::string, void***, void* (*)(int), void (* (*)(int))(), void (* (* (*)(int))[5])()), int complex_typing::S::*) [with T = int; std::string = std::string]: 95 | debug_assert(false); 96 | 97 | Stack trace: 98 | # 1 test_class::test_complex_typing(std::vector const volatile* const&, int*, char const (&) [4], void (complex_typing::S::*)(int, std::string, void***, void* (*)(int), void (*(*)(int))(), void (* (*(*)(int)) [5])()), int complex_typing::S::*) 99 | at integration.cpp:3602 100 | # 2 test_class::something_else() 101 | at integration.cpp:3502 102 | # 3 void test_class::something(std::pair) 103 | at integration.cpp:1004 104 | # 4 main 105 | at integration.cpp:402 106 | 107 | 108 | -------------------------------------------------------------------------------- /tests/integration/expected/msvc.txt: -------------------------------------------------------------------------------- 1 | ===================== [simple recursion] ===================== 2 | Debug Assertion failed at integration.cpp:501: void __cdecl rec(int): 3 | debug_assert(false); 4 | 5 | Stack trace: 6 | # 1 rec(int) 7 | at integration.cpp:501 8 | # 2 rec(int) 9 | at integration.cpp:502 10 | | | 11 | | 8 layers of recursion were folded | 12 | | | 13 | #11 rec(int) 14 | at integration.cpp:502 15 | #12 test_class::something_else() 16 | at integration.cpp:2600 17 | #13 test_class::something(std::pair) 18 | at integration.cpp:1004 19 | #14 main() 20 | at integration.cpp:402 21 | 22 | 23 | ===================== [other recursion] ===================== 24 | Debug Assertion failed at integration.cpp:508: void __cdecl recursive_a(int): 25 | debug_assert(false); 26 | 27 | Stack trace: 28 | # 1 recursive_a(int) 29 | at integration.cpp:508 30 | # 2 recursive_b(int) 31 | at integration.cpp:514 32 | # 3 recursive_a(int) 33 | at integration.cpp:509 34 | # 4 recursive_b(int) 35 | at integration.cpp:514 36 | # 5 recursive_a(int) 37 | at integration.cpp:509 38 | # 6 recursive_b(int) 39 | at integration.cpp:514 40 | # 7 recursive_a(int) 41 | at integration.cpp:509 42 | # 8 recursive_b(int) 43 | at integration.cpp:514 44 | # 9 recursive_a(int) 45 | at integration.cpp:509 46 | #10 recursive_b(int) 47 | at integration.cpp:514 48 | #11 recursive_a(int) 49 | at integration.cpp:509 50 | #12 test_class::something_else() 51 | at integration.cpp:2700 52 | #13 test_class::something(std::pair) 53 | at integration.cpp:1004 54 | #14 main() 55 | at integration.cpp:402 56 | 57 | 58 | ===================== [Path differentiation] ===================== 59 | Debug Assertion failed at x/a.cpp:5: void __cdecl test_path_differentiation_2(void): 60 | debug_assert(false); 61 | 62 | Stack trace: 63 | # 1 test_path_differentiation_2() 64 | at x/a.cpp:5 65 | # 2 test_path_differentiation() 66 | at integration/a.cpp:7 67 | # 3 test_class::something_else() 68 | at integration.cpp:2800 69 | # 4 test_class::something(std::pair) 70 | at integration.cpp:1004 71 | # 5 main() 72 | at integration.cpp:402 73 | 74 | 75 | ===================== [Type cleaning] ===================== 76 | Debug Assertion failed at integration.cpp:3305: void __cdecl test_class::test_pretty_function_cleaning(const std::map, std::less> &): 77 | debug_assert(map == other); 78 | Where: 79 | map => std::map, std::less>: [["bar", ["b1", "b3", "b5"]], ["foo", ["f1", "f3", "f5"]]] 80 | other => std::map, std::less>: [] 81 | 82 | Stack trace: 83 | # 1 test_class::test_pretty_function_cleaning(std::map, std::less 84 | at integration.cpp:3305 85 | # 2 test_class::something_else() 86 | at integration.cpp:3201 87 | # 3 test_class::something(std::pair) 88 | at integration.cpp:1004 89 | # 4 main() 90 | at integration.cpp:402 91 | 92 | 93 | ===================== [Complex type resolution] ===================== 94 | Debug Assertion failed at integration.cpp:3602: void __cdecl test_class::test_complex_typing(volatile const std::vector *const &, int [], const char (&)[4], void (__cdecl complex_typing::S::* )(int, std::string, void ***, void *(__cdecl *)(int), void (__cdecl *(__cdecl *)(int))(void), void (__cdecl *(*(__cdecl *)(int))[5])(void)), int complex_typing::S::* ): 95 | debug_assert(false); 96 | 97 | Stack trace: 98 | # 1 test_class::test_complex_typing(std::vector*&, int*, char(&)[4], void(complex_typing::S::*)(int, std::string, void***, void*(*)(int), void(*(*)(int))(), void(*(*(*)(int))[5])()), int complex_typing::S::*) 99 | at integration.cpp:3602 100 | # 2 test_class::something_else() 101 | at integration.cpp:3502 102 | # 3 test_class::something(std::pair) 103 | at integration.cpp:1004 104 | # 4 main() 105 | at integration.cpp:402 106 | 107 | 108 | -------------------------------------------------------------------------------- /tests/integration/integration.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | using namespace std::literals; 19 | 20 | void test_path_differentiation(); 21 | 22 | void custom_fail(const libassert::assertion_info& assertion) { 23 | std::cout< struct libassert::stringifier { 41 | std::string stringify(const debug_print_customization& p) { 42 | return "(debug_print_customization = " + std::to_string(p.x) + ")"; 43 | } 44 | }; 45 | 46 | int foo() { 47 | std::cout<<"foo() called"< 91 | class test_class { 92 | public: 93 | template void something([[maybe_unused]] std::pair x) { 94 | something_else(); 95 | } 96 | 97 | void something_else() { 98 | // general stack traces are tested throughout 99 | // simple recursion 100 | SECTION("simple recursion"); 101 | #line 2600 102 | rec(10); // FIXME: Check stacktrace in clang 103 | // other recursion 104 | SECTION("other recursion"); 105 | #line 2700 106 | recursive_a(10); // FIXME: Check stacktrace in clang 107 | 108 | // path differentiation 109 | SECTION("Path differentiation"); 110 | #line 2800 111 | test_path_differentiation(); 112 | 113 | SECTION("Type cleaning"); // also pretty thoughroughly tested above aside from std::string_view 114 | #line 3200 115 | { 116 | test_pretty_function_cleaning({}); 117 | } 118 | 119 | SECTION("Complex type resolution"); 120 | #line 3500 121 | { 122 | const volatile std::vector* ptr = nullptr; 123 | test_complex_typing(ptr, nullptr, "foo", nullptr, nullptr); 124 | } 125 | } 126 | 127 | #line 3300 128 | void test_pretty_function_cleaning(const std::map>& other) { 129 | std::map> map = { 130 | {"foo", {"f1", "f3", "f5"}}, 131 | {"bar", {"b1", "b3", "b5"}} 132 | }; 133 | debug_assert(map == other); 134 | } 135 | 136 | #line 3600 137 | void test_complex_typing(const volatile std::vector* const &, int[], const char(&)[4], 138 | decltype(&complex_typing::S::foo), int complex_typing::S::*) { 139 | debug_assert(false); 140 | } 141 | }; 142 | 143 | struct N { }; 144 | 145 | #line 400 146 | int main() { 147 | libassert::set_failure_handler(custom_fail); 148 | libassert::set_color_scheme(libassert::color_scheme::blank); 149 | test_class t; 150 | #line 402 151 | t.something(std::pair {N(), 1}); 152 | } 153 | -------------------------------------------------------------------------------- /tests/integration/x/a.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // This file is used for testing path disambiguation 3 | 4 | void test_path_differentiation_2() { 5 | debug_assert(false); 6 | } 7 | -------------------------------------------------------------------------------- /tests/pyutils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/libassert/91e0d84216f034e3e593ea57103d3f419e333873/tests/pyutils/__init__.py -------------------------------------------------------------------------------- /tests/run-tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import re 4 | import subprocess 5 | import sys 6 | import platform 7 | from typing import List 8 | 9 | sys.stdout.reconfigure(encoding='utf-8') # for windows gh runner 10 | 11 | from pyutils.utils import critical_difference, icdiff, parse_output 12 | 13 | ok = True 14 | 15 | MAX_LINE_DIFF = 2 16 | 17 | env = os.environ.copy() 18 | lp = "{}/../bin".format(os.path.dirname(os.path.realpath(__file__))) 19 | if "LD_LIBRARY_PATH" in env: 20 | env["LD_LIBRARY_PATH"] += ":" + lp 21 | else: 22 | env["LD_LIBRARY_PATH"] = lp 23 | 24 | # Workaround for https://github.com/actions/runner-images/issues/8803 25 | buggy_path = "C:\\Program Files\\Git\\mingw64\\bin;C:\\Program Files\\Git\\usr\\bin;C:\\Users\\runneradmin\\bin;" 26 | if env["PATH"].startswith(buggy_path): 27 | env["PATH"] = env["PATH"].replace(buggy_path, "", 1) 28 | 29 | def run_integration(integration_binary: str, expected: str, opt: bool): 30 | p = subprocess.Popen( 31 | [integration_binary], 32 | stdout=subprocess.PIPE, 33 | stderr=subprocess.PIPE, 34 | env=env 35 | ) 36 | output, err = p.communicate() 37 | output = output.decode("utf-8").replace("\r", "") 38 | passed = True 39 | expected_blocks = parse_output(expected) 40 | output_blocks = parse_output(output) 41 | if output_blocks != expected_blocks: # TODO for later: room for improvement for trace handling under opt 42 | if critical_difference(output, expected, MAX_LINE_DIFF, not opt): 43 | passed = False 44 | else: 45 | print("WARNING: Difference in output but deemed non-critical", flush=True) 46 | print(os.path.basename(expected)) 47 | if opt: 48 | expected_blocks = filter(lambda b: b["type"] != "trace", expected_blocks) 49 | output_blocks = filter(lambda b: b["type"] != "trace", output_blocks) 50 | icdiff( 51 | ("\n".join(map(lambda b: "\n".join(b["lines"]), expected_blocks)), "expected"), 52 | ("\n".join(map(lambda b: "\n".join(b["lines"]), output_blocks)), "output") 53 | ) 54 | if p.returncode != 0: 55 | print("p.retruncode = {}".format(p.returncode), flush=True) 56 | passed = False 57 | if len(err) != 0: 58 | print("Warning: Process stderr not empty:\n{}".format(err.decode("utf-8")), flush=True) 59 | print("[{}]".format("🟢 Passed" if passed else "🔴 Failed"), flush=True) 60 | return passed 61 | 62 | def similarity(name: str, target: List[str]) -> int: 63 | parts = name.split(".txt")[0].split(".") 64 | c = 0 65 | for part in parts: 66 | if part in target: 67 | c += 1 68 | else: 69 | return -1 70 | return c 71 | 72 | def main(): 73 | if len(sys.argv) < 2: 74 | print("Expected at least one arg") 75 | sys.exit(1) 76 | 77 | integration_binary = sys.argv[1] 78 | # cmake build type 79 | build_type = sys.argv[2].lower() 80 | 81 | target = [] 82 | 83 | compiler_id = sys.argv[3].lower() 84 | if compiler_id.startswith("gcc") or compiler_id.startswith("g++") or compiler_id.startswith("gnu"): 85 | target.append("gnu") 86 | elif "clang" in compiler_id: 87 | target.append("clang") 88 | elif compiler_id.startswith("cl") or compiler_id.startswith("msvc"): 89 | target.append("msvc") 90 | 91 | if platform.system() == "Windows": 92 | target.append("windows") 93 | elif platform.system() == "Darwin": 94 | target.append("macos") 95 | else: 96 | target.append("linux") 97 | 98 | other_configs = sys.argv[4:] 99 | for config in other_configs: 100 | target.append(config.lower()) 101 | 102 | print(f"Searching for expected file best matching {target}") 103 | 104 | expected_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "integration/expected/") 105 | files = [f for f in os.listdir(expected_dir) if os.path.isfile(os.path.join(expected_dir, f))] 106 | if len(files) == 0: 107 | print(f"Error: No expected files to use (searching {expected_dir})", file=sys.stderr) 108 | sys.exit(1) 109 | files = list(map(lambda f: (f, similarity(f, target)), files)) 110 | m = max(files, key=lambda entry: entry[1])[1] 111 | if m <= 0: 112 | print(f"Error: Could not find match for {target} in {files}", file=sys.stderr) 113 | sys.exit(1) 114 | files = [entry[0] for entry in files if entry[1] == m] 115 | if len(files) > 1: 116 | print(f"Error: Ambiguous expected file to use ({files})", file=sys.stderr) 117 | sys.exit(1) 118 | 119 | file = files[0] 120 | print(f"Reading from {file}") 121 | 122 | with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "integration/expected/", file), "r") as f: 123 | expected = f.read() 124 | 125 | if run_integration(integration_binary, expected, build_type != "debug"): 126 | print("Test passed") 127 | else: 128 | print("Test failed") 129 | sys.exit(1) 130 | 131 | main() 132 | -------------------------------------------------------------------------------- /tests/unit/constexpr_contexts.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | template void foo() {} 4 | 5 | constexpr int bar(int x) { 6 | DEBUG_ASSERT(x % 2 == 0); 7 | return x / 2; 8 | } 9 | 10 | int main() { 11 | foo(); 12 | } 13 | -------------------------------------------------------------------------------- /tests/unit/disambiguation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "analysis.hpp" 9 | 10 | #define ESC "\033[" 11 | #define RED ESC "1;31m" 12 | #define GREEN ESC "1;32m" 13 | #define BLUE ESC "1;34m" 14 | #define CYAN ESC "1;36m" 15 | #define PURPL ESC "1;35m" 16 | #define DARK ESC "1;30m" 17 | #define RESET ESC "0m" 18 | 19 | int main() { 20 | std::tuple tests[] = { 21 | {"a < 1 == 2 > ( 1 + 3 )", "==", true}, 22 | {"a < 1 == 2 > - 3 == ( 1 + 3 )", "==", true}, // <- disambiguated despite ambiguity 23 | {"( 1 + 3 ) == a < 1 == 2 > - 3", "==", false}, // <- ambiguous 24 | {"( 1 + 3 ) == a < 1 == 2 > ()", "==", true}, 25 | {"( 1 + 3 ) not_eq a < 1 not_eq 2 > ()", "!=", true}, 26 | {"a>>>>>>>>", "<", true}, 27 | {"a>>>>>>>>>", "<", false}, // <- max depth exceeded 28 | {"1 == something>2", "==", false}, // <- ambiguous 29 | {"1 == something>2", "<", false}, // <- should be an error 30 | {"1 < something>2", "<", false}, // <- ambiguous 31 | {"1 < something> - 2", "<", false}, // <- ambiguous 32 | {"18446744073709551606ULL == -10", "==", true} 33 | }; 34 | bool ok = true; 35 | for(auto [expression, target_op, should_disambiguate] : tests) { 36 | std::cout< 2 | 3 | #include 4 | 5 | struct S { 6 | int x; 7 | }; 8 | 9 | template<> 10 | struct fmt::formatter { 11 | template 12 | constexpr auto parse(ParseContext& ctx) { 13 | return ctx.begin(); 14 | } 15 | 16 | template 17 | auto format(const S& s, FormatContext& ctx) const { 18 | return fmt::format_to(ctx.out(), "s.x={}", s.x); 19 | } 20 | }; 21 | 22 | TEST(LibassertFmt, FmtWorks) { 23 | ASSERT(libassert::stringify(S{42}) == "s.x=42"); 24 | } 25 | 26 | struct S2 { 27 | int x; 28 | }; 29 | 30 | template<> 31 | struct fmt::formatter { 32 | template 33 | constexpr auto parse(ParseContext& ctx) { 34 | return ctx.begin(); 35 | } 36 | 37 | template 38 | auto format(const S2& s, FormatContext& ctx) const { 39 | return fmt::format_to(ctx.out(), "s2.x={}", s.x); 40 | } 41 | }; 42 | 43 | template<> struct libassert::stringifier { 44 | std::string stringify(const S2& s) { 45 | return fmt::format("{}", s) + "--"; 46 | } 47 | }; 48 | 49 | TEST(LibassertFmt, FmtPriority) { 50 | ASSERT(libassert::stringify(S2{42}) == "s2.x=42--"); 51 | } 52 | 53 | struct fmtable { 54 | int x; 55 | }; 56 | 57 | template <> struct fmt::formatter: formatter { 58 | auto format(const fmtable& f, format_context& ctx) const { 59 | return fmt::format_to(ctx.out(), "{{{}}}", f.x); 60 | } 61 | }; 62 | 63 | TEST(LibassertFmt, FmtContainers) { 64 | fmtable f{2}; 65 | ASSERT(libassert::detail::generate_stringification(f) == "{2}"); 66 | 67 | std::vector fvec{{{2}, {3}}}; 68 | ASSERT(libassert::detail::generate_stringification(fvec) == "std::vector: [{2}, {3}]"); 69 | } 70 | -------------------------------------------------------------------------------- /tests/unit/literals.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "microfmt.hpp" 11 | 12 | inline std::vector split(std::string_view s, std::string_view delim) { 13 | std::vector vec; 14 | size_t old_pos = 0; 15 | size_t pos = 0; 16 | std::string token; 17 | while((pos = s.find(delim, old_pos)) != std::string::npos) { 18 | token = s.substr(old_pos, pos - old_pos); 19 | vec.push_back(token); 20 | old_pos = pos + delim.length(); 21 | } 22 | //if(old_pos != s.length()) { // TODO: verify condition? 23 | vec.emplace_back(s.substr(old_pos)); 24 | //} 25 | return vec; 26 | } 27 | 28 | constexpr const char * const ws = " \t\n\r\f\v"; 29 | 30 | // trim from end of string (right) 31 | inline std::string& rtrim(std::string& s, const char* t = ws) { 32 | s.erase(s.find_last_not_of(t) + 1); 33 | return s; 34 | } 35 | 36 | // trim from beginning of string (left) 37 | inline std::string& ltrim(std::string& s, const char* t = ws) { 38 | s.erase(0, s.find_first_not_of(t)); 39 | return s; 40 | } 41 | 42 | // trim from both ends of string (right then left) 43 | inline std::string& trim(std::string& s, const char* t = ws) { 44 | return ltrim(rtrim(s, t), t); 45 | } 46 | 47 | #define let auto 48 | 49 | int main() { 50 | std::string match_raw = R"QQ( 51 | 0b0 52 | 0B0 53 | 0b1'10101010'0'1 54 | 0 55 | 0771237 56 | 0'7'7'1'237 57 | 120958701982375086125098123650981237409871234 58 | 1'1234'234'2'2 59 | 0X11 60 | 0x1ff0f 61 | 0x1'f'f'0f 62 | 0x1aA 63 | 1.5 64 | 1.5'5 65 | 1. 66 | 1.f 67 | 1.e2 68 | 1.5E1 69 | 1.5E-1 70 | 1.5E+1 71 | 1.5E1L 72 | 0x1f.aP2 73 | 0x1f.aP+2f 74 | 0x1f.aP-2 75 | 0x1p2 76 | 1e2 77 | 1e2f 78 | )QQ"; 79 | std::string dont_match_raw = R"QQ( 80 | 0B 81 | 0b'1'1'0'1 82 | 0b1'1'0'1' 83 | 0b1'1''0'1 84 | '0 85 | 0' 86 | 078 87 | 0''7'7'1'237 88 | 1234'2'2'2'''1 89 | '1 90 | 1' 91 | 0X 92 | 0xabcq 93 | 0x'a'bcf 94 | 0xa''bcf 95 | 0xa'bcf' 96 | something 97 | foo.bar() 98 | 1+2 99 | template 100 | 1 5 101 | 1.'5 102 | '1.5 103 | 1'.5 104 | 1.5' 105 | 1.5E1a 106 | 1.5E- 107 | 1.5'E+1 108 | 1.5E1'L 109 | 0x1f.ae2 110 | 0x1f.a+2f 111 | 0x1f.aP-2a0 112 | 0x1f.a'P2 113 | 0x1f.aP'2f 114 | 0x1f.aP-2' 115 | 0x1p'2 116 | 1'e2 117 | 1'e2f 118 | 0x'1p2 119 | '1e2 120 | 1e2'f 121 | 0'x1p2 122 | 1'e2 123 | 1e2f' 124 | )QQ"; 125 | let match_cases = split(trim(match_raw), "\n"); 126 | let dont_match_cases = split(trim(dont_match_raw), "\n"); 127 | for(let& set : {&match_cases, &dont_match_cases}) { 128 | for(let& item : *set) { 129 | item = trim(item); 130 | } 131 | } 132 | static std::string optional_integer_suffix = "(?:[Uu](?:LL?|ll?|Z|z)?|(?:LL?|ll?|Z|z)[Uu]?)?"; 133 | static std::regex int_binary = std::regex("^0[Bb][01](?:'?[01])*" + optional_integer_suffix + "$"); 134 | static std::regex int_octal = std::regex("^0(?:'?[0-7])+" + optional_integer_suffix + "$"); 135 | static std::regex int_decimal = std::regex("^(?:0|[1-9](?:'?\\d)*)" + optional_integer_suffix + "$"); 136 | static std::regex int_hex = std::regex("^0[Xx](?!')(?:'?[\\da-fA-F])+" + optional_integer_suffix + "$"); 137 | 138 | static std::string digit_sequence = "\\d(?:'?\\d)*"; 139 | static std::string fractional_constant = libassert::microfmt::format("(?:(?:{})?\\.{}|{}\\.)", digit_sequence, digit_sequence, digit_sequence); 140 | static std::string exponent_part = "(?:[Ee][\\+-]?" + digit_sequence + ")"; 141 | static std::string suffix = "[FfLl]"; 142 | static std::regex float_decimal = std::regex(libassert::microfmt::format("^(?:{}{}?|{}{}){}?$", 143 | fractional_constant, exponent_part, 144 | digit_sequence, exponent_part, suffix)); 145 | static std::string hex_digit_sequence = "[\\da-fA-F](?:'?[\\da-fA-F])*"; 146 | static std::string hex_frac_const = libassert::microfmt::format("(?:(?:{})?\\.{}|{}\\.)", hex_digit_sequence, hex_digit_sequence, hex_digit_sequence); 147 | static std::string binary_exp = "[Pp][\\+-]?" + digit_sequence; 148 | static std::regex float_hex = std::regex(libassert::microfmt::format("^0[Xx](?:{}|{}){}{}?$", 149 | hex_frac_const, hex_digit_sequence, binary_exp, suffix)); 150 | let matches_any = [&](const std::string& str) { 151 | let matches = [](const std::string& value, const std::regex& re) { 152 | std::smatch base_match; 153 | return std::regex_match(value, base_match, re); 154 | }; 155 | return matches(str, int_binary) 156 | || matches(str, int_octal) 157 | || matches(str, int_decimal) 158 | || matches(str, int_hex) 159 | || matches(str, float_decimal) 160 | || matches(str, float_hex); 161 | }; 162 | bool ok = true; 163 | for(let const& item : match_cases) { 164 | if(!matches_any(item)) { 165 | printf("test case failed, valid literal not matched: %s\n", item.c_str()); 166 | ok = false; 167 | } 168 | } 169 | for(let const& item : dont_match_cases) { 170 | if(matches_any(item)) { 171 | printf("test case failed, invalid literal matched: %s\n", item.c_str()); 172 | ok = false; 173 | } 174 | } 175 | printf("Done\n"); 176 | return !ok; 177 | } 178 | -------------------------------------------------------------------------------- /tests/unit/std_format20.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef LIBASSERT_USE_STD_FORMAT 4 | 5 | #include 6 | 7 | struct S { 8 | int x; 9 | }; 10 | 11 | template<> 12 | struct std::formatter { 13 | template 14 | constexpr auto parse(ParseContext& ctx) { 15 | return ctx.begin(); 16 | } 17 | 18 | template 19 | auto format(const S& s, FormatContext& ctx) const { 20 | return std::format_to(ctx.out(), "s.x={}", s.x); 21 | } 22 | }; 23 | 24 | TEST(LibassertFmt, StdFormatWorks) { 25 | ASSERT(libassert::stringify(S{42}) == "s.x=42"); 26 | } 27 | 28 | struct S2 { 29 | int x; 30 | }; 31 | 32 | template<> 33 | struct std::formatter { 34 | template 35 | constexpr auto parse(ParseContext& ctx) { 36 | return ctx.begin(); 37 | } 38 | 39 | template 40 | auto format(const S2& s, FormatContext& ctx) const { 41 | return std::format_to(ctx.out(), "s2.x={}", s.x); 42 | } 43 | }; 44 | 45 | template<> struct libassert::stringifier { 46 | std::string stringify(const S2& s) { 47 | return std::format("{}", s) + "--"; 48 | } 49 | }; 50 | 51 | TEST(LibassertFmt, StdFormatPriority1) { 52 | ASSERT(libassert::stringify(S2{42}) == "s2.x=42--"); 53 | } 54 | 55 | struct S3 { 56 | int x; 57 | }; 58 | 59 | template<> 60 | struct std::formatter { 61 | template 62 | constexpr auto parse(ParseContext& ctx) { 63 | return ctx.begin(); 64 | } 65 | 66 | template 67 | auto format(const S3& s, FormatContext& ctx) const { 68 | return std::format_to(ctx.out(), "std::format chosen"); 69 | } 70 | }; 71 | 72 | template<> 73 | struct fmt::formatter { 74 | template 75 | constexpr auto parse(ParseContext& ctx) { 76 | return ctx.begin(); 77 | } 78 | 79 | template 80 | auto format(const S3& s, FormatContext& ctx) const { 81 | return fmt::format_to(ctx.out(), "fmt::format chosen"); 82 | } 83 | }; 84 | 85 | TEST(LibassertFmt, StdFormatPriority2) { 86 | ASSERT(libassert::stringify(S3{42}) == "std::format chosen"); 87 | } 88 | 89 | struct fmtable { 90 | int x; 91 | }; 92 | 93 | template <> struct std::formatter: formatter { 94 | auto format(const fmtable& f, format_context& ctx) const { 95 | return std::format_to(ctx.out(), "{{{}}}", f.x); 96 | } 97 | }; 98 | 99 | TEST(LibassertFmt, StdFormatContainers) { 100 | fmtable f{2}; 101 | ASSERT(libassert::detail::generate_stringification(f) == "{2}"); 102 | 103 | std::vector fvec{{{2}, {3}}}; 104 | ASSERT(libassert::detail::generate_stringification(fvec) == "std::vector: [{2}, {3}]"); 105 | } 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /tests/unit/std_format23.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef LIBASSERT_USE_STD_FORMAT 4 | 5 | #include 6 | 7 | struct S { 8 | int x; 9 | }; 10 | 11 | template<> 12 | struct std::formatter { 13 | template 14 | constexpr auto parse(ParseContext& ctx) { 15 | return ctx.begin(); 16 | } 17 | 18 | template 19 | auto format(const S& s, FormatContext& ctx) const { 20 | return std::format_to(ctx.out(), "s.x={}", s.x); 21 | } 22 | }; 23 | 24 | TEST(LibassertFmt, StdFormatWorks) { 25 | ASSERT(libassert::stringify(S{42}) == "s.x=42"); 26 | } 27 | 28 | struct S2 { 29 | int x; 30 | }; 31 | 32 | template<> 33 | struct std::formatter { 34 | template 35 | constexpr auto parse(ParseContext& ctx) { 36 | return ctx.begin(); 37 | } 38 | 39 | template 40 | auto format(const S2& s, FormatContext& ctx) const { 41 | return std::format_to(ctx.out(), "s2.x={}", s.x); 42 | } 43 | }; 44 | 45 | template<> struct libassert::stringifier { 46 | std::string stringify(const S2& s) { 47 | return std::format("{}", s) + "--"; 48 | } 49 | }; 50 | 51 | TEST(LibassertFmt, StdFormatPriority1) { 52 | ASSERT(libassert::stringify(S2{42}) == "s2.x=42--"); 53 | } 54 | 55 | struct S3 { 56 | int x; 57 | }; 58 | 59 | template<> 60 | struct std::formatter { 61 | template 62 | constexpr auto parse(ParseContext& ctx) { 63 | return ctx.begin(); 64 | } 65 | 66 | template 67 | auto format(const S3& s, FormatContext& ctx) const { 68 | return std::format_to(ctx.out(), "std::format chosen"); 69 | } 70 | }; 71 | 72 | template<> 73 | struct fmt::formatter { 74 | template 75 | constexpr auto parse(ParseContext& ctx) { 76 | return ctx.begin(); 77 | } 78 | 79 | template 80 | auto format(const S3& s, FormatContext& ctx) const { 81 | return fmt::format_to(ctx.out(), "fmt::format chosen"); 82 | } 83 | }; 84 | 85 | TEST(LibassertFmt, StdFormatPriority2) { 86 | ASSERT(libassert::stringify(S3{42}) == "std::format chosen"); 87 | } 88 | 89 | struct fmtable { 90 | int x; 91 | }; 92 | 93 | template <> struct std::formatter: formatter { 94 | template 95 | auto format(const fmtable& f, FormatContext& ctx) const { 96 | return std::format_to(ctx.out(), "{{{}}}", f.x); 97 | } 98 | }; 99 | 100 | static_assert(std::formattable); 101 | 102 | TEST(LibassertFmt, StdFormatContainers) { 103 | fmtable f{2}; 104 | ASSERT(libassert::detail::generate_stringification(f) == "{2}"); 105 | 106 | std::vector fvec{{{2}, {3}}}; 107 | ASSERT(libassert::detail::generate_stringification(fvec) == "std::vector: [{2}, {3}]"); 108 | } 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /tests/unit/stringify.cpp: -------------------------------------------------------------------------------- 1 | #undef ASSERT_LOWERCASE 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace libassert::detail; 17 | using namespace std::literals; 18 | 19 | struct ostream_printable { 20 | int x; 21 | }; 22 | 23 | std::ostream& operator<<(std::ostream& os, ostream_printable item) { 24 | return os << "{" << item.x << "}"; 25 | } 26 | 27 | struct S {}; 28 | struct S2 {}; 29 | 30 | struct A {}; 31 | struct B {}; 32 | struct C {}; 33 | 34 | TEST(Stringify, BasicTypes) { 35 | ASSERT(generate_stringification(false) == R"(false)"); 36 | ASSERT(generate_stringification(42) == R"(42)"); 37 | ASSERT(generate_stringification(2.25) == R"(2.25)"); 38 | } 39 | 40 | TEST(Stringify, Pointers) { 41 | ASSERT(generate_stringification(nullptr) == R"(nullptr)"); 42 | int x; 43 | int* ptr = &x; 44 | auto s = generate_stringification(ptr); 45 | ASSERT(s.find("int*: 0x") == 0 || s.find("int *: 0x") == 0, "", s); 46 | } 47 | 48 | TEST(Stringify, SmartPointers) { 49 | auto uptr = std::make_unique(62); 50 | ASSERT(generate_stringification(uptr) == R"(std::unique_ptr: 62)"); 51 | ASSERT(generate_stringification(std::unique_ptr()) == R"(std::unique_ptr: nullptr)"); 52 | ASSERT(generate_stringification(std::make_unique()).find(R"(std::unique_ptr: 0x)") == 0, generate_stringification(std::make_unique())); 53 | 54 | std::unique_ptr> uptr2(new std::vector{1,2,3,4}); 55 | ASSERT(generate_stringification(uptr2) == R"(std::unique_ptr>: [1, 2, 3, 4])"); 56 | auto d = [](int*) {}; 57 | std::unique_ptr uptr3(nullptr, d); 58 | ASSERT(generate_stringification(uptr3).find("std::unique_ptr= 9 73 | // Somehow bugged for gcc 8, resulting in a segfault on std::filesystem::path::~path(). This happens completely 74 | // independent of anything cpptrace, some awful ABI issue and I can't be bothered to figure it out. Can't repro on CE. 75 | TEST(Stringify, Paths) { 76 | ASSERT(generate_stringification(std::filesystem::path("/home/foo")) == R"("/home/foo")"); 77 | } 78 | #endif 79 | 80 | struct recursive_stringify { 81 | using value_type = int; 82 | struct const_iterator { 83 | int i; 84 | using value_type = recursive_stringify; 85 | value_type operator*() const { 86 | return recursive_stringify{}; 87 | } 88 | const_iterator operator++(int) { 89 | auto copy = *this; 90 | i++; 91 | return copy; 92 | } 93 | bool operator!=(const const_iterator& other) const { 94 | return i != other.i; 95 | } 96 | }; 97 | const_iterator begin() const { 98 | return {0}; 99 | } 100 | const_iterator end() const { 101 | return {5}; 102 | } 103 | }; 104 | 105 | TEST(Stringify, RecursiveStringify) { 106 | ASSERT(generate_stringification(recursive_stringify{}) == R"(recursive_stringify: [, , , , ])"); 107 | } 108 | 109 | struct recursive_stringify_pathological { 110 | using value_type = int; 111 | struct const_iterator { 112 | int i; 113 | using value_type = std::vector; 114 | value_type operator*() const { 115 | return value_type{}; 116 | } 117 | const_iterator operator++(int) { 118 | auto copy = *this; 119 | i++; 120 | return copy; 121 | } 122 | bool operator!=(const const_iterator& other) const { 123 | return i != other.i; 124 | } 125 | }; 126 | const_iterator begin() const { 127 | return {0}; 128 | } 129 | const_iterator end() const { 130 | return {5}; 131 | } 132 | }; 133 | 134 | TEST(Stringify, RecursiveStringifyPathological) { 135 | ASSERT(generate_stringification(recursive_stringify_pathological{}) == R"(recursive_stringify_pathological: [[], [], [], [], []])"); 136 | } 137 | 138 | TEST(Stringify, Containers) { 139 | std::array arr{1,2,3,4,5}; 140 | static_assert(stringifiable>); 141 | ASSERT(generate_stringification(arr) == R"(std::array: [1, 2, 3, 4, 5])"); 142 | std::map map{{1,2},{3,4}}; 143 | #if defined(_WIN32) && !LIBASSERT_IS_GCC 144 | ASSERT(generate_stringification(map) == R"(std::map>: [[1, 2], [3, 4]])"); 145 | #else 146 | ASSERT(generate_stringification(map) == R"(std::map: [[1, 2], [3, 4]])"); 147 | #endif 148 | std::tuple> tuple = {1, 1.25f, "good", arr}; 149 | ASSERT(generate_stringification(tuple) == R"(std::tuple>: [1, 1.25, "good", [1, 2, 3, 4, 5]])"); 150 | std::optional opt; 151 | ASSERT(generate_stringification(opt) == R"(std::optional: nullopt)"); 152 | opt = 63; 153 | ASSERT(generate_stringification(opt) == R"(std::optional: 63)"); 154 | ASSERT(generate_stringification(std::optional(S{})) == R"(std::optional: )"); 155 | std::vector vec2 {2, 42, {}, 60}; 156 | ASSERT(generate_stringification(vec2) == R"(std::vector: [2, 42, 0, 60])"); 157 | std::vector> vec {2, 42, {}, 60}; 158 | ASSERT(generate_stringification(vec) == R"(std::vector>: [2, 42, nullopt, 60])"); 159 | std::vector>>> vovp {{{{2, 1.2f}}}, {}, {{{20, 6.2f}}}}; 160 | ASSERT(generate_stringification(vovp) == R"(std::vector>>>: [[[2, 1.20000005]], nullopt, [[20, 6.19999981]]])"); 161 | std::vector vlong(1100); 162 | #define TEN "0, 0, 0, 0, 0, 0, 0, 0, 0, 0, " 163 | #define HUNDRED TEN TEN TEN TEN TEN TEN TEN TEN TEN TEN 164 | ASSERT( 165 | generate_stringification(vlong) == "std::vector: [" 166 | HUNDRED HUNDRED HUNDRED HUNDRED HUNDRED HUNDRED HUNDRED HUNDRED HUNDRED HUNDRED "..." 167 | "]" 168 | ); 169 | int carr[] = {1, 1, 2, 3, 5, 8}; 170 | static_assert(stringifiable); 171 | static_assert(stringifiable_container()); 172 | #if LIBASSERT_IS_CLANG || LIBASSERT_IS_MSVC 173 | ASSERT(generate_stringification(carr) == R"(int[6]: [1, 1, 2, 3, 5, 8])"); 174 | #else 175 | ASSERT(generate_stringification(carr) == R"(int [6]: [1, 1, 2, 3, 5, 8])"); 176 | #endif 177 | // non-printable containers 178 | std::vector svec(10); 179 | static_assert(!stringifiable>); 180 | static_assert(!stringifiable_container>()); 181 | static_assert(!stringifiable); 182 | static_assert(!stringifiable_container()); 183 | ASSERT(generate_stringification(svec) == R"(>)"); 184 | std::vector> svec2(10, std::vector(10)); 185 | //static_assert(!stringification::stringifiable>>); 186 | ASSERT(generate_stringification(svec2) == R"(>>)"); 187 | std::vector> svec3(10, std::vector(10)); 188 | static_assert(stringifiable>>); 189 | ASSERT(generate_stringification(svec3) == R"(std::vector>: [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])"); 190 | } 191 | 192 | TEST(Stringify, LiteralFormatting) { 193 | ASSERT(generate_stringification(100) == "100"); 194 | libassert::set_fixed_literal_format(libassert::literal_format::integer_hex | libassert::literal_format::integer_octal); 195 | libassert::detail::set_literal_format("", "", "", false); 196 | ASSERT(generate_stringification(100) == "100 0x64 0144"); 197 | libassert::set_literal_format_mode(libassert::literal_format_mode::infer); 198 | 199 | std::tuple tuple2; 200 | ASSERT(generate_stringification(tuple2) == R"(>)"); 201 | std::tuple tuple3 = {{}, {}, 1.2, {}}; 202 | ASSERT(generate_stringification(tuple3) == R"(std::tuple: [, , 1.20000005, ])"); 203 | 204 | ostream_printable op{2}; 205 | ASSERT(generate_stringification(op) == "{2}"); 206 | 207 | std::vector opvec{{{2}, {3}}}; 208 | ASSERT(generate_stringification(opvec) == "std::vector: [{2}, {3}]"); 209 | } 210 | 211 | struct basic_fields { 212 | struct value_type { 213 | value_type() {} 214 | }; 215 | struct const_iterator { 216 | bool operator==(const const_iterator&) const { 217 | return true; 218 | } 219 | }; 220 | const_iterator begin() { 221 | return {}; 222 | } 223 | const_iterator end() { 224 | return {}; 225 | } 226 | }; 227 | 228 | TEST(Stringify, Regression01) { 229 | // regression test for #90, just making sure this can compile 230 | constexpr bool b = stringifiable_container(); 231 | ASSERT(!b); 232 | basic_fields fields; 233 | ASSERT(fields.begin() == fields.end()); 234 | } 235 | 236 | // TODO: Other formats 237 | // enum E { EE, FF }; 238 | // ASSERT(generate_stringification(FF) == R"(int [6]: [1, 1, 2, 3, 5, 8])"); 239 | 240 | // error codes 241 | // customization point objects 242 | // libfmt 243 | // std::format 244 | // stringification tests 245 | -------------------------------------------------------------------------------- /tests/unit/test_files/test_program._cpp: -------------------------------------------------------------------------------- 1 | template std::string stringf(T... args) { 2 | size_t length = snprintf(0, 0, args...); 3 | if(length < 0) abort(); 4 | std::string str(length, 0); 5 | snprintf(str.data(), length + 1, args...); 6 | return str; 7 | } 8 | 9 | template 10 | inline std::string join(I iter, I end, const std::string& delim) { 11 | std::string str; 12 | if(std::distance(iter, end) > 0) { 13 | str += *iter; 14 | while(++iter != end) { 15 | str += delim; 16 | str += *iter; 17 | } 18 | } 19 | return str; 20 | } 21 | 22 | template 23 | inline std::string join(C container, std::string delim) { 24 | return join(container.begin(), container.end(), delim); 25 | } 26 | 27 | template 28 | inline std::string join(const std::string (&container)[N], std::string delim) { 29 | return join(std::begin(container), std::end(container), delim); 30 | } 31 | 32 | // C++20 polyfill 33 | struct source_location { 34 | const char* const file; 35 | const char* const function; 36 | const int line; 37 | constexpr source_location( 38 | const char* file = __builtin_FILE(), 39 | const char* function = __builtin_FUNCTION(), 40 | int line = __builtin_LINE() 41 | ) : file(file), function(function), line(line) {} 42 | }; 43 | 44 | struct token_t { 45 | token_e token_type; 46 | std::string value; 47 | }; 48 | 49 | std::string union_regexes(std::initializer_list regexes) { 50 | std::string composite; 51 | for(const std::string& str : regexes) { 52 | if(composite != "") composite += "|"; 53 | composite += stringf("(?:%s)", str.c_str()); 54 | } 55 | return composite; 56 | } 57 | 58 | std::regex generate_lexer() { // Should only be called once 59 | std::string keywords[] = { "alignas", "constinit", "false", "public", "true", 60 | "alignof", "const_cast", "float", "register", "try", "asm", "continue", "for", 61 | "reinterpret_cast", "typedef", "auto", "co_await", "friend", "requires", "typeid", 62 | "bool", "co_return", "goto", "return", "typename", "break", "co_yield", "if", 63 | "short", "union", "case", "decltype", "inline", "signed", "unsigned", "catch", 64 | "default", "int", "sizeof", "using", "char", "delete", "long", "static", "virtual", 65 | "char8_t", "do", "mutable", "static_assert", "void", "char16_t", "double", 66 | "namespace", "static_cast", "volatile", "char32_t", "dynamic_cast", "new", "struct", 67 | "wchar_t", "class", "else", "noexcept", "switch", "while", "concept", "enum", 68 | "nullptr", "template", "const", "explicit", "operator", "this", "consteval", 69 | "export", "private", "thread_local", "constexpr", "extern", "protected", "throw" }; 70 | std::string punctuators[] = { "{", "}", "[", "]", "(", ")", "<:", ":>", "<%", 71 | "%>", ";", ":", "...", "?", "::", ".", ".*", "->", "->*", "~", "!", "+", "-", "*", 72 | "/", "%", "^", "&", "|", "=", "+=", "-=", "*=", "/=", "%=", "^=", "&=", "|=", "==", 73 | "!=", "<", ">", "<=", ">=", "<=>", "&&", "||", "<<", ">>", "<<=", ">>=", "++", "--", 74 | ",", "and", "or", "xor", "not", "bitand", "bitor", "compl", "and_eq", "or_eq", 75 | "xor_eq", "not_eq" }; 76 | // Sort longest -> shortest (secondarily A->Z) 77 | const auto cmp = [](const std::string& a, const std::string& b) { 78 | if(a.length() > b.length()) return true; 79 | else if(a.length() == b.length()) return a < b; 80 | else return false; 81 | }; 82 | std::sort(std::begin(keywords), std::end(keywords), cmp); 83 | std::sort(std::begin(punctuators), std::end(punctuators), cmp); 84 | // Escape special characters 85 | const auto escape = [](const std::string& str) { 86 | const std::regex special_chars { R"([-[\]{}()*+?.,\^$|#\s])" }; 87 | return std::regex_replace(str, special_chars, "\\$&"); 88 | }; 89 | std::transform(std::begin(keywords), std::end(keywords), std::begin(keywords), escape); 90 | std::transform(std::begin(punctuators), std::end(punctuators), std::begin(punctuators), escape); 91 | // regular expressions 92 | std::string keywords_re = join(keywords, "|"); 93 | std::string punctuators_re = join(punctuators, "|"); 94 | // literals 95 | std::string optional_integer_suffix = "(?:[Uu](?:LL?|ll?|Z|z)?|(?:LL?|ll?|Z|z)[Uu]?)?"; 96 | std::string int_binary = "^0[Bb][01](?:'?[01])*" + optional_integer_suffix + "$"; 97 | // slightly modified so 0 is lexed as a decimal literal instead of octal 98 | std::string int_octal = "^0(?:'?[0-7])+" + optional_integer_suffix + "$"; 99 | std::string int_decimal = "^(?:0|[1-9](?:'?\\d)*)" + optional_integer_suffix + "$"; 100 | std::string int_hex = "^0[Xx](?!')(?:'?[\\da-fA-F])+" + optional_integer_suffix + "$"; 101 | 102 | std::string digit_sequence = "\\d(?:'?\\d)*"; 103 | std::string fractional_constant = stringf("(?:(?:%s)?\\.%s|%s\\.)", digit_sequence.c_str(), digit_sequence.c_str(), digit_sequence.c_str()); 104 | std::string exponent_part = "(?:[Ee][\\+-]?" + digit_sequence + ")"; 105 | std::string suffix = "[FfLl]"; 106 | std::string float_decimal = stringf("^(?:%s%s?|%s%s)%s?$", 107 | fractional_constant.c_str(), exponent_part.c_str(), 108 | digit_sequence.c_str(), exponent_part.c_str(), suffix.c_str()); 109 | std::string hex_digit_sequence = "[\\da-fA-F](?:'?[\\da-fA-F])*"; 110 | std::string hex_frac_const = stringf("(?:(?:%s)?\\.%s|%s\\.)", hex_digit_sequence.c_str(), hex_digit_sequence.c_str(), hex_digit_sequence.c_str()); 111 | std::string binary_exp = "[Pp][\\+-]?" + digit_sequence; 112 | std::string float_hex = stringf("^0[Xx](?:%s|%s)%s%s?$", 113 | hex_frac_const.c_str(), hex_digit_sequence.c_str(), binary_exp.c_str(), suffix.c_str()); 114 | // char and string 115 | std::string char_literal = R"((?:u8|[UuL])?'(?:\\[0-7]{1,3}|\\x[\da-fA-F]+|\\.|[^'])+')"; 116 | std::string string_literal = R"((?:u8|[UuL])?"(?:\\[0-7]{1,3}|\\x[\da-fA-F]+|\\.|[^'])+")"; 117 | std::string raw_string_literal = R"((?:u8|[UuL])?R"(?<__raw_delim>[^ ()\\t\r\v\n]*)\((?:(?!\)\k<__raw_delim>).)+\)\k<__raw_delim>")"; 118 | // final rule set 119 | std::pair rules[] = { 120 | { "keyword" , keywords_re }, 121 | { "punctuation", punctuators_re }, 122 | { "literal" , union_regexes({ 123 | int_binary, 124 | int_octal, 125 | int_decimal, 126 | int_hex, 127 | float_decimal, 128 | float_hex, 129 | char_literal, 130 | string_literal, 131 | raw_string_literal, 132 | "true|false|nullptr" 133 | }) }, 134 | { "identifier" , R"((?!\d+)(?:[\da-zA-Z_\$]|\\u[\da-fA-F]{4}|\\U[\da-fA-F]{8})+)" }, 135 | { "whitespace" , R"(\s+)" } 136 | }; 137 | // build the lexer 138 | std::string lexer; 139 | for(const auto& pair : rules) { 140 | if(lexer != "") lexer += "|"; 141 | lexer += stringf("(?<%s>%s)", pair.first.c_str(), pair.second.c_str()); 142 | } 143 | return std::regex { lexer }; 144 | } -------------------------------------------------------------------------------- /tests/unit/test_public_utilities.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | using namespace libassert; 8 | 9 | std::string replace(std::string str, std::string_view substr, std::string_view replacement) { 10 | std::string::size_type pos = 0; 11 | // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) 12 | while((pos = str.find(substr.data(), pos, substr.length())) != std::string::npos) { 13 | str.replace(pos, substr.length(), replacement.data(), replacement.length()); 14 | pos += replacement.length(); 15 | } 16 | return str; 17 | } 18 | 19 | int main() { 20 | // pretty_type_name tests 21 | auto pretty_name = pretty_type_name>(); 22 | DEBUG_ASSERT(pretty_name.find("basic_string") == std::string::npos); 23 | DEBUG_ASSERT(pretty_name.find("allocator") == std::string::npos); 24 | // stringification tests 25 | DEBUG_ASSERT(stringify(12) == "12"); 26 | DEBUG_ASSERT(stringify('x') == "'x'"); 27 | DEBUG_ASSERT( 28 | replace( 29 | replace(stringify(std::make_pair("foobar", 20)), "char const", "const char"), 30 | "char *", 31 | "char*" 32 | ) == R"(std::pair: ["foobar", 20])" 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /tests/unit/test_type_prettier.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | int main() { 7 | bool success = true; 8 | auto test = [&success](const std::string& type, const std::string& expected) { 9 | auto pretty = libassert::detail::prettify_type(type); 10 | if(pretty != expected) { 11 | std::cout<<"Error:"<,class std::allocator >,class std::vector >,struct std::less,class std::allocator > >,class std::allocator,class std::allocator > const ,class std::vector > > > >)", 19 | R"(std::map, std::less>)" 20 | ); 21 | return !success; 22 | } 23 | -------------------------------------------------------------------------------- /tests/unit/type_handling.cpp: -------------------------------------------------------------------------------- 1 | #undef ASSERT_LOWERCASE 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace libassert::detail; 14 | 15 | void custom_fail(libassert::assert_type, const libassert::assertion_info& assertion) { 16 | std::cerr<>().get_value()), int&>::value); 22 | static_assert(std::is_same>().get_value()), int&>::value); 23 | static_assert(std::is_same>().get_value()), bool>::value); 24 | static_assert(std::is_same>().take_lhs()), int>::value); 25 | static_assert(std::is_same>().take_lhs()), int&>::value); 26 | 27 | static_assert(is_string_type); 28 | static_assert(is_string_type); 29 | static_assert(is_string_type); 30 | static_assert(is_string_type); 31 | static_assert(!is_string_type); 32 | static_assert(is_string_type); 33 | static_assert(is_string_type); 34 | static_assert(!is_string_type>); 35 | static_assert(!is_string_type); 36 | static_assert(is_string_type); 37 | static_assert(is_string_type); 38 | 39 | template constexpr bool is_lvalue(T&&) { 40 | return std::is_lvalue_reference::value; 41 | } 42 | 43 | struct only_move_constructable { 44 | int x; 45 | only_move_constructable(int _x) : x(_x) {} 46 | ~only_move_constructable() = default; 47 | only_move_constructable(const only_move_constructable&) = delete; 48 | only_move_constructable(only_move_constructable&&) = default; 49 | only_move_constructable& operator=(const only_move_constructable&) = delete; 50 | only_move_constructable& operator=(only_move_constructable&&) = delete; 51 | bool operator==(int y) const { 52 | return x == y; 53 | } 54 | }; 55 | 56 | int main() { 57 | // test rvalue 58 | { 59 | decltype(auto) a = DEBUG_ASSERT_VAL(only_move_constructable(2) == 2); 60 | static_assert(std::is_same::value); 61 | assert(!is_lvalue(DEBUG_ASSERT_VAL(only_move_constructable(2) == 2))); 62 | assert(DEBUG_ASSERT_VAL(only_move_constructable(2) == 2).x == 2); 63 | //assert(debug_assert(only_move_constructable(2) == 2).x++ == 2); // not allowed 64 | } 65 | 66 | // test lvalue 67 | { 68 | only_move_constructable x(2); 69 | decltype(auto) b = DEBUG_ASSERT_VAL(x == 2); 70 | static_assert(std::is_same::value); 71 | assert(is_lvalue(DEBUG_ASSERT_VAL(x == 2))); 72 | DEBUG_ASSERT_VAL(x == 2).x++; 73 | ASSERT(x.x == 3); 74 | } 75 | 76 | // test values are forwarded properly 77 | // serves as a regression test against issue with std::forwarding with the wrong type 78 | { 79 | // there was an issue with the decomposer trying to forward s << 2 as an S rather than a B 80 | struct S; 81 | struct B { 82 | bool operator==(int) const { 83 | return true; 84 | } 85 | }; 86 | struct S { 87 | B operator<<(int) const { 88 | return B{}; 89 | } 90 | }; 91 | S s; 92 | decltype(auto) v = DEBUG_ASSERT_VAL(s << 2 == false); 93 | static_assert(std::is_same::value); 94 | } 95 | { 96 | struct S; 97 | struct B { 98 | bool operator==(int) const { 99 | return true; 100 | } 101 | }; 102 | B b; 103 | struct S { 104 | B& _b; 105 | B& operator<<(int) const { 106 | return _b; 107 | } 108 | }; 109 | S s{b}; 110 | decltype(auto) v = DEBUG_ASSERT_VAL(s << 2 == false); 111 | static_assert(std::is_same::value); 112 | } 113 | 114 | // above cases test lhs returns, now test the case where the full value is returned 115 | { 116 | auto v0 = DEBUG_ASSERT_VAL(1 | 2); 117 | ASSERT(v0 == 3); 118 | auto v1 = DEBUG_ASSERT_VAL(7 & 4); 119 | ASSERT(v1 == 4); 120 | auto v2 = DEBUG_ASSERT_VAL(1 << 16); 121 | ASSERT(v2 == 65536); 122 | auto v3 = DEBUG_ASSERT_VAL(32 >> 2); 123 | ASSERT(v3 == 8); 124 | } 125 | 126 | // test _VAL returns the correct type 127 | { 128 | auto f = [] { 129 | return DEBUG_ASSERT_VAL(false); 130 | }; 131 | static_assert(std::is_same::value); 132 | } 133 | 134 | return 0; 135 | } 136 | --------------------------------------------------------------------------------