├── .clang-format ├── .github ├── dependabot.yml ├── issue_template.md ├── pull_request_template.md └── workflows │ ├── cifuzz.yml │ ├── doc.yml │ ├── lint.yml │ ├── linux.yml │ ├── macos.yml │ ├── scorecard.yml │ └── windows.yml ├── .gitignore ├── CMakeLists.txt ├── CONTRIBUTING.md ├── ChangeLog.md ├── LICENSE ├── README.md ├── doc ├── ChangeLog-old.md ├── api.md ├── fmt.css ├── fmt.js ├── get-started.md ├── index.md ├── perf.svg ├── python-license.txt └── syntax.md ├── include └── fmt │ ├── args.h │ ├── base.h │ ├── chrono.h │ ├── color.h │ ├── compile.h │ ├── core.h │ ├── format-inl.h │ ├── format.h │ ├── os.h │ ├── ostream.h │ ├── printf.h │ ├── ranges.h │ ├── std.h │ └── xchar.h ├── src ├── fmt.cc ├── format.cc └── os.cc ├── support ├── Android.mk ├── AndroidManifest.xml ├── C++.sublime-syntax ├── README ├── Vagrantfile ├── bazel │ ├── .bazelversion │ ├── BUILD.bazel │ ├── MODULE.bazel │ ├── README.md │ └── WORKSPACE.bazel ├── build.gradle ├── check-commits ├── cmake │ ├── FindSetEnv.cmake │ ├── JoinPaths.cmake │ ├── fmt-config.cmake.in │ └── fmt.pc.in ├── docopt.py ├── mkdocs ├── mkdocs.yml ├── printable.py ├── python │ └── mkdocstrings_handlers │ │ └── cxx │ │ ├── __init__.py │ │ └── templates │ │ └── README └── release.py └── test ├── CMakeLists.txt ├── add-subdirectory-test ├── CMakeLists.txt └── main.cc ├── args-test.cc ├── assert-test.cc ├── base-test.cc ├── chrono-test.cc ├── color-test.cc ├── compile-error-test └── CMakeLists.txt ├── compile-fp-test.cc ├── compile-test.cc ├── cuda-test ├── CMakeLists.txt ├── cpp14.cc └── cuda-cpp14.cu ├── detect-stdfs.cc ├── enforce-checks-test.cc ├── find-package-test ├── CMakeLists.txt └── main.cc ├── format-impl-test.cc ├── format-test.cc ├── fuzzing ├── .gitignore ├── CMakeLists.txt ├── README.md ├── build.sh ├── chrono-duration.cc ├── chrono-timepoint.cc ├── float.cc ├── fuzzer-common.h ├── main.cc ├── named-arg.cc ├── one-arg.cc └── two-args.cc ├── gtest-extra-test.cc ├── gtest-extra.cc ├── gtest-extra.h ├── gtest ├── .clang-format ├── CMakeLists.txt ├── gmock-gtest-all.cc ├── gmock │ └── gmock.h └── gtest │ ├── gtest-spi.h │ └── gtest.h ├── header-only-test.cc ├── mock-allocator.h ├── module-test.cc ├── no-builtin-types-test.cc ├── noexception-test.cc ├── os-test.cc ├── ostream-test.cc ├── perf-sanity.cc ├── posix-mock-test.cc ├── posix-mock.h ├── printf-test.cc ├── ranges-odr-test.cc ├── ranges-test.cc ├── scan-test.cc ├── scan.h ├── static-export-test ├── CMakeLists.txt ├── library.cc └── main.cc ├── std-test.cc ├── test-assert.h ├── test-main.cc ├── unicode-test.cc ├── util.cc ├── util.h └── xchar-test.cc /.clang-format: -------------------------------------------------------------------------------- 1 | # Run manually to reformat a file: 2 | # clang-format -i --style=file 3 | Language: Cpp 4 | BasedOnStyle: Google 5 | IndentPPDirectives: AfterHash 6 | IndentCaseLabels: false 7 | AlwaysBreakTemplateDeclarations: false 8 | DerivePointerAlignment: false 9 | AllowShortCaseLabelsOnASingleLine: true 10 | QualifierAlignment: Left 11 | AlignConsecutiveShortCaseStatements: 12 | Enabled: true 13 | AcrossEmptyLines: true 14 | AcrossComments: true 15 | AlignCaseColons: false -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" # Necessary to update action hashes. 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | # Allow up to 3 opened pull requests for github-actions versions. 8 | open-pull-requests-limit: 3 9 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /.github/workflows/cifuzz.yml: -------------------------------------------------------------------------------- 1 | name: CIFuzz 2 | on: [pull_request] 3 | 4 | permissions: 5 | contents: read 6 | 7 | jobs: 8 | Fuzzing: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Build fuzzers 12 | id: build 13 | uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@92182553173581f871130c71c71b17f003d47b0a 14 | with: 15 | oss-fuzz-project-name: 'fmt' 16 | dry-run: false 17 | language: c++ 18 | 19 | - name: Run fuzzers 20 | uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@92182553173581f871130c71c71b17f003d47b0a 21 | with: 22 | oss-fuzz-project-name: 'fmt' 23 | fuzz-seconds: 300 24 | dry-run: false 25 | language: c++ 26 | 27 | - name: Upload crash 28 | uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 29 | if: failure() && steps.build.outcome == 'success' 30 | with: 31 | name: artifacts 32 | path: ./out/artifacts 33 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: doc 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-22.04 11 | 12 | steps: 13 | - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 14 | 15 | - name: Add Ubuntu mirrors 16 | run: | 17 | # Github Actions caching proxy is at times unreliable 18 | # see https://github.com/actions/runner-images/issues/7048 19 | printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt 20 | curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt 21 | sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list 22 | 23 | - name: Create build environment 24 | run: | 25 | sudo apt update 26 | sudo apt install doxygen 27 | pip install mkdocs-material==9.5.25 mkdocstrings==0.26.1 mike==2.1.1 28 | cmake -E make_directory ${{runner.workspace}}/build 29 | # Workaround https://github.com/actions/checkout/issues/13: 30 | git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)" 31 | git config --global user.email "$(git --no-pager log --format=format:'%ae' -n 1)" 32 | 33 | - name: Build 34 | working-directory: ${{runner.workspace}}/build 35 | run: $GITHUB_WORKSPACE/support/mkdocs deploy dev 36 | 37 | - name: Deploy 38 | env: 39 | KEY: "${{secrets.KEY}}" 40 | if: env.KEY != '' && github.ref == 'refs/heads/master' 41 | working-directory: ${{runner.workspace}}/fmt/build/fmt.dev 42 | run: git push https://$KEY@github.com/fmtlib/fmt.dev.git 43 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.h' 7 | - '**.cc' 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | format_code: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 17 | 18 | - name: Install clang-format 19 | run: | 20 | wget https://apt.llvm.org/llvm.sh 21 | sudo bash ./llvm.sh 17 22 | sudo apt install clang-format-17 23 | 24 | - name: Run clang-format 25 | run: | 26 | find include src -name '*.h' -o -name '*.cc' | \ 27 | xargs clang-format-17 -i -style=file -fallback-style=none 28 | git diff --exit-code 29 | -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: linux 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-22.04 11 | strategy: 12 | matrix: 13 | cxx: [g++-4.9, g++-11, clang++-3.6, clang++-11] 14 | build_type: [Debug, Release] 15 | std: [11] 16 | shared: [""] 17 | include: 18 | - cxx: g++-4.9 19 | - cxx: clang++-3.6 20 | - cxx: g++-11 21 | build_type: Debug 22 | std: 14 23 | install: sudo apt install g++-11 24 | - cxx: g++-11 25 | build_type: Debug 26 | std: 17 27 | - cxx: g++-11 28 | build_type: Debug 29 | std: 20 30 | install: sudo apt install g++-11 31 | - cxx: g++-13 32 | build_type: Release 33 | std: 23 34 | install: sudo apt install g++-13 35 | shared: -DBUILD_SHARED_LIBS=ON 36 | - cxx: clang++-11 37 | build_type: Debug 38 | std: 17 39 | cxxflags: -stdlib=libc++ 40 | install: sudo apt install clang-11 libc++-11-dev libc++abi-11-dev 41 | - cxx: clang++-11 42 | install: sudo apt install clang-11 43 | - cxx: clang++-11 44 | build_type: Debug 45 | fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON 46 | std: 17 47 | install: sudo apt install clang-11 48 | - cxx: clang++-14 49 | build_type: Debug 50 | std: 20 51 | - cxx: clang++-14 52 | build_type: Debug 53 | std: 20 54 | cxxflags: -stdlib=libc++ 55 | install: sudo apt install libc++-14-dev libc++abi-14-dev 56 | 57 | steps: 58 | - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 59 | 60 | - name: Set timezone 61 | run: sudo timedatectl set-timezone 'Europe/Kyiv' 62 | 63 | - name: Install GCC 4.9 64 | run: | 65 | sudo apt update 66 | sudo apt install libatomic1 libc6-dev libgomp1 libitm1 libmpc3 67 | # https://launchpad.net/ubuntu/xenial/amd64/g++-4.9/4.9.3-13ubuntu2 68 | wget --no-verbose \ 69 | http://launchpadlibrarian.net/230069137/libmpfr4_3.1.3-2_amd64.deb \ 70 | http://launchpadlibrarian.net/253728424/libasan1_4.9.3-13ubuntu2_amd64.deb \ 71 | http://launchpadlibrarian.net/445346135/libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 72 | http://launchpadlibrarian.net/445346112/libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 73 | http://launchpadlibrarian.net/253728426/libgcc-4.9-dev_4.9.3-13ubuntu2_amd64.deb \ 74 | http://launchpadlibrarian.net/253728432/libstdc++-4.9-dev_4.9.3-13ubuntu2_amd64.deb \ 75 | http://launchpadlibrarian.net/253728314/gcc-4.9-base_4.9.3-13ubuntu2_amd64.deb \ 76 | http://launchpadlibrarian.net/445345919/gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 77 | http://launchpadlibrarian.net/253728399/cpp-4.9_4.9.3-13ubuntu2_amd64.deb \ 78 | http://launchpadlibrarian.net/253728404/gcc-4.9_4.9.3-13ubuntu2_amd64.deb \ 79 | http://launchpadlibrarian.net/253728401/g++-4.9_4.9.3-13ubuntu2_amd64.deb 80 | sudo dpkg -i \ 81 | libmpfr4_3.1.3-2_amd64.deb \ 82 | libasan1_4.9.3-13ubuntu2_amd64.deb \ 83 | libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 84 | libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 85 | libgcc-4.9-dev_4.9.3-13ubuntu2_amd64.deb \ 86 | libstdc++-4.9-dev_4.9.3-13ubuntu2_amd64.deb \ 87 | gcc-4.9-base_4.9.3-13ubuntu2_amd64.deb \ 88 | gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 89 | cpp-4.9_4.9.3-13ubuntu2_amd64.deb \ 90 | gcc-4.9_4.9.3-13ubuntu2_amd64.deb \ 91 | g++-4.9_4.9.3-13ubuntu2_amd64.deb 92 | if: ${{ matrix.cxx == 'g++-4.9' }} 93 | 94 | - name: Install Clang 3.6 95 | run: | 96 | sudo apt update 97 | sudo apt install libtinfo5 98 | # https://code.launchpad.net/ubuntu/xenial/amd64/clang-3.6/1:3.6.2-3ubuntu2 99 | wget --no-verbose \ 100 | http://launchpadlibrarian.net/230019046/libffi6_3.2.1-4_amd64.deb \ 101 | http://launchpadlibrarian.net/445346109/libasan2_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 102 | http://launchpadlibrarian.net/445346135/libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 103 | http://launchpadlibrarian.net/445346112/libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 104 | http://launchpadlibrarian.net/445346128/libmpx0_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 105 | http://launchpadlibrarian.net/445346113/libgcc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 106 | http://launchpadlibrarian.net/445346131/libstdc++-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 107 | http://launchpadlibrarian.net/445346022/libobjc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 108 | http://launchpadlibrarian.net/254405108/libllvm3.6v5_3.6.2-3ubuntu2_amd64.deb \ 109 | http://launchpadlibrarian.net/254405097/libclang-common-3.6-dev_3.6.2-3ubuntu2_amd64.deb \ 110 | http://launchpadlibrarian.net/254405101/libclang1-3.6_3.6.2-3ubuntu2_amd64.deb \ 111 | http://launchpadlibrarian.net/445345919/gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 112 | http://launchpadlibrarian.net/254405091/clang-3.6_3.6.2-3ubuntu2_amd64.deb 113 | sudo dpkg -i \ 114 | libffi6_3.2.1-4_amd64.deb \ 115 | libasan2_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 116 | libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 117 | libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 118 | libmpx0_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 119 | libgcc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 120 | libstdc++-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 121 | libobjc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 122 | libllvm3.6v5_3.6.2-3ubuntu2_amd64.deb \ 123 | libclang-common-3.6-dev_3.6.2-3ubuntu2_amd64.deb \ 124 | libclang1-3.6_3.6.2-3ubuntu2_amd64.deb \ 125 | gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \ 126 | clang-3.6_3.6.2-3ubuntu2_amd64.deb 127 | if: ${{ matrix.cxx == 'clang++-3.6' }} 128 | 129 | - name: Add repositories for newer GCC 130 | run: | 131 | sudo apt-add-repository ppa:ubuntu-toolchain-r/test 132 | if: ${{ matrix.cxx == 'g++-13' }} 133 | 134 | - name: Add Ubuntu mirrors 135 | run: | 136 | # GitHub Actions caching proxy is at times unreliable 137 | # see https://github.com/actions/runner-images/issues/7048. 138 | mirrors=/etc/apt/mirrors.txt 139 | printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | \ 140 | sudo tee $mirrors 141 | curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append $mirrors 142 | sudo sed -i \ 143 | "s~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:$mirrors~" \ 144 | /etc/apt/sources.list 145 | 146 | - name: Create build environment 147 | run: | 148 | sudo apt update 149 | ${{matrix.install}} 150 | sudo apt install locales-all 151 | cmake -E make_directory ${{runner.workspace}}/build 152 | 153 | - name: Configure 154 | working-directory: ${{runner.workspace}}/build 155 | env: 156 | CXX: ${{matrix.cxx}} 157 | CXXFLAGS: ${{matrix.cxxflags}} 158 | run: | 159 | cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ 160 | -DCMAKE_CXX_STANDARD=${{matrix.std}} \ 161 | -DCMAKE_CXX_VISIBILITY_PRESET=hidden \ 162 | -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ 163 | -DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON \ 164 | ${{matrix.fuzz}} ${{matrix.shared}} $GITHUB_WORKSPACE 165 | 166 | - name: Build 167 | working-directory: ${{runner.workspace}}/build 168 | run: | 169 | threads=`nproc` 170 | cmake --build . --config ${{matrix.build_type}} --parallel $threads 171 | 172 | - name: Test 173 | working-directory: ${{runner.workspace}}/build 174 | run: ctest -C ${{matrix.build_type}} 175 | env: 176 | CTEST_OUTPUT_ON_FAILURE: True 177 | -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: macos 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | matrix: 12 | os: [macos-13, macos-14] 13 | build_type: [Debug, Release] 14 | std: [11, 17, 20] 15 | shared: [""] 16 | exclude: 17 | - { os: macos-13, std: 11 } 18 | - { os: macos-13, std: 17 } 19 | include: 20 | - os: macos-14 21 | std: 23 22 | build_type: Release 23 | shared: -DBUILD_SHARED_LIBS=ON 24 | 25 | runs-on: '${{ matrix.os }}' 26 | 27 | steps: 28 | - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 29 | 30 | - name: Set timezone 31 | run: sudo systemsetup -settimezone 'Europe/Minsk' 32 | 33 | - name: Select Xcode 14.3 (macOS 13) 34 | run: sudo xcode-select -s "/Applications/Xcode_14.3.app" 35 | if: ${{ matrix.os == 'macos-13' }} 36 | 37 | - name: Create Build Environment 38 | run: cmake -E make_directory ${{runner.workspace}}/build 39 | 40 | - name: Configure 41 | working-directory: ${{runner.workspace}}/build 42 | run: | 43 | cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \ 44 | -DCMAKE_CXX_STANDARD=${{matrix.std}} \ 45 | -DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ 46 | -DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE 47 | 48 | - name: Build 49 | working-directory: ${{runner.workspace}}/build 50 | run: | 51 | threads=`sysctl -n hw.logicalcpu` 52 | cmake --build . --config ${{matrix.build_type}} --parallel $threads 53 | 54 | - name: Test 55 | working-directory: ${{runner.workspace}}/build 56 | run: ctest -C ${{matrix.build_type}} 57 | env: 58 | CTEST_OUTPUT_ON_FAILURE: True 59 | -------------------------------------------------------------------------------- /.github/workflows/scorecard.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '26 14 * * 5' 14 | push: 15 | branches: [ "master" ] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | 30 | steps: 31 | - name: "Checkout code" 32 | uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 33 | with: 34 | persist-credentials: false 35 | 36 | - name: "Run analysis" 37 | uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 38 | with: 39 | results_file: results.sarif 40 | results_format: sarif 41 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 42 | # - you want to enable the Branch-Protection check on a *public* repository, or 43 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 44 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 45 | 46 | # Public repositories: 47 | # - Publish results to OpenSSF REST API for easy access by consumers 48 | # - Allows the repository to include the Scorecard badge. 49 | # - See https://github.com/ossf/scorecard-action#publishing-results. 50 | publish_results: true 51 | 52 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 53 | # format to the repository Actions tab. 54 | - name: "Upload artifact" 55 | uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 56 | with: 57 | name: SARIF file 58 | path: results.sarif 59 | retention-days: 5 60 | 61 | # Upload the results to GitHub's code scanning dashboard. 62 | - name: "Upload to code-scanning" 63 | uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 64 | with: 65 | sarif_file: results.sarif 66 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: windows 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | build: 10 | runs-on: ${{matrix.os}} 11 | strategy: 12 | matrix: 13 | # windows-2019 has MSVC 2019 installed; 14 | # windows-2022 has MSVC 2022 installed: 15 | # https://github.com/actions/virtual-environments. 16 | os: [windows-2019] 17 | platform: [Win32, x64] 18 | toolset: [v141, v142] 19 | standard: [14, 17, 20] 20 | shared: ["", -DBUILD_SHARED_LIBS=ON] 21 | build_type: [Debug, Release] 22 | exclude: 23 | - { toolset: v141, standard: 20 } 24 | - { toolset: v142, standard: 14 } 25 | - { platform: Win32, toolset: v141 } 26 | - { platform: Win32, standard: 14 } 27 | - { platform: Win32, standard: 20 } 28 | - { platform: x64, toolset: v141, shared: -DBUILD_SHARED_LIBS=ON } 29 | - { platform: x64, standard: 14, shared: -DBUILD_SHARED_LIBS=ON } 30 | - { platform: x64, standard: 20, shared: -DBUILD_SHARED_LIBS=ON } 31 | include: 32 | - os: windows-2022 33 | platform: x64 34 | toolset: v143 35 | build_type: Debug 36 | standard: 20 37 | 38 | steps: 39 | - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 40 | 41 | - name: Set timezone 42 | run: tzutil /s "FLE Standard Time" 43 | 44 | - name: Create Build Environment 45 | run: cmake -E make_directory ${{runner.workspace}}/build 46 | 47 | - name: Configure 48 | # Use a bash shell for $GITHUB_WORKSPACE. 49 | shell: bash 50 | working-directory: ${{runner.workspace}}/build 51 | run: | 52 | cmake -A ${{matrix.platform}} -T ${{matrix.toolset}} \ 53 | -DCMAKE_CXX_STANDARD=${{matrix.standard}} \ 54 | ${{matrix.shared}} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ 55 | $GITHUB_WORKSPACE 56 | 57 | - name: Build 58 | working-directory: ${{runner.workspace}}/build 59 | run: | 60 | $threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors 61 | cmake --build . --config ${{matrix.build_type}} --parallel $threads 62 | 63 | - name: Test 64 | working-directory: ${{runner.workspace}}/build 65 | run: ctest -C ${{matrix.build_type}} -V 66 | env: 67 | CTEST_OUTPUT_ON_FAILURE: True 68 | 69 | mingw: 70 | runs-on: windows-latest 71 | defaults: 72 | run: 73 | shell: msys2 {0} 74 | strategy: 75 | matrix: 76 | sys: [ mingw64, ucrt64 ] 77 | steps: 78 | - name: Set timezone 79 | run: tzutil /s "Ekaterinburg Standard Time" 80 | shell: cmd 81 | - uses: msys2/setup-msys2@61f9e5e925871ba6c9e3e8da24ede83ea27fa91f # v2.27.0 82 | with: 83 | release: false 84 | msystem: ${{matrix.sys}} 85 | pacboy: cc:p cmake:p ninja:p lld:p 86 | - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 87 | - name: Configure 88 | run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug 89 | env: { LDFLAGS: -fuse-ld=lld } 90 | - name: Build 91 | run: cmake --build ../build 92 | - name: Test 93 | run: ctest -j `nproc` --test-dir ../build 94 | env: 95 | CTEST_OUTPUT_ON_FAILURE: True 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.a 2 | *.so* 3 | *.xcodeproj 4 | *~ 5 | .vscode/ 6 | .vs/ 7 | /CMakeScripts 8 | /Testing 9 | /_CPack_Packages 10 | /install_manifest.txt 11 | CMakeCache.txt 12 | CMakeFiles 13 | CPack*.cmake 14 | CTestTestfile.cmake 15 | FMT.build 16 | Makefile 17 | bin/ 18 | build/ 19 | cmake_install.cmake 20 | fmt-*.cmake 21 | fmt.pc 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to {fmt} 2 | ===================== 3 | 4 | By submitting a pull request or a patch, you represent that you have the right 5 | to license your contribution to the {fmt} project owners and the community, 6 | agree that your contributions are licensed under the {fmt} license, and agree 7 | to future changes to the licensing. 8 | 9 | All C++ code must adhere to [Google C++ Style Guide]( 10 | https://google.github.io/styleguide/cppguide.html) with the following 11 | exceptions: 12 | 13 | * Exceptions are permitted 14 | * snake_case should be used instead of UpperCamelCase for function and type 15 | names 16 | 17 | All documentation must adhere to the [Google Developer Documentation Style 18 | Guide](https://developers.google.com/style). 19 | 20 | Thanks for contributing! 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | --- Optional exception to the license --- 23 | 24 | As an exception, if, as a result of your compiling your source code, portions 25 | of this Software are embedded into a machine-executable object form of such 26 | source code, you may redistribute such embedded portions in such object form 27 | without including the above copyright and permission notices. 28 | -------------------------------------------------------------------------------- /doc/fmt.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --md-primary-fg-color: #0050D0; 3 | } 4 | 5 | .md-grid { 6 | max-width: 960px; 7 | } 8 | 9 | @media (min-width: 400px) { 10 | .md-tabs { 11 | display: block; 12 | } 13 | } 14 | 15 | .docblock { 16 | border-left: .05rem solid var(--md-primary-fg-color); 17 | } 18 | 19 | .docblock-desc { 20 | margin-left: 1em; 21 | } 22 | 23 | pre > code.decl { 24 | white-space: pre-wrap; 25 | } 26 | 27 | 28 | code.decl > div { 29 | text-indent: -2ch; /* Negative indent to counteract the indent on the first line */ 30 | padding-left: 2ch; /* Add padding to the left to create an indent */ 31 | } 32 | 33 | .features-container { 34 | display: flex; 35 | flex-wrap: wrap; 36 | gap: 20px; 37 | justify-content: center; /* Center the items horizontally */ 38 | } 39 | 40 | .feature { 41 | flex: 1 1 calc(50% - 20px); /* Two columns with space between */ 42 | max-width: 600px; /* Set the maximum width for the feature boxes */ 43 | box-sizing: border-box; 44 | padding: 10px; 45 | overflow: hidden; /* Hide overflow content */ 46 | text-overflow: ellipsis; /* Handle text overflow */ 47 | white-space: normal; /* Allow text wrapping */ 48 | } 49 | 50 | .feature h2 { 51 | margin-top: 0px; 52 | font-weight: bold; 53 | } 54 | 55 | @media (max-width: 768px) { 56 | .feature { 57 | flex: 1 1 100%; /* Stack columns on smaller screens */ 58 | max-width: 100%; /* Allow full width on smaller screens */ 59 | white-space: normal; /* Allow text wrapping on smaller screens */ 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /doc/fmt.js: -------------------------------------------------------------------------------- 1 | document$.subscribe(() => { 2 | hljs.highlightAll(), 3 | { language: 'c++' } 4 | }) 5 | -------------------------------------------------------------------------------- /doc/get-started.md: -------------------------------------------------------------------------------- 1 | # Get Started 2 | 3 | Compile and run {fmt} examples online with [Compiler Explorer]( 4 | https://godbolt.org/z/P7h6cd6o3). 5 | 6 | {fmt} is compatible with any build system. The next section describes its usage 7 | with CMake, while the [Build Systems](#build-systems) section covers the rest. 8 | 9 | ## CMake 10 | 11 | {fmt} provides two CMake targets: `fmt::fmt` for the compiled library and 12 | `fmt::fmt-header-only` for the header-only library. It is recommended to use 13 | the compiled library for improved build times. 14 | 15 | There are three primary ways to use {fmt} with CMake: 16 | 17 | * **FetchContent**: Starting from CMake 3.11, you can use [`FetchContent`]( 18 | https://cmake.org/cmake/help/v3.30/module/FetchContent.html) to automatically 19 | download {fmt} as a dependency at configure time: 20 | 21 | include(FetchContent) 22 | 23 | FetchContent_Declare( 24 | fmt 25 | GIT_REPOSITORY https://github.com/fmtlib/fmt 26 | GIT_TAG e69e5f977d458f2650bb346dadf2ad30c5320281) # 10.2.1 27 | FetchContent_MakeAvailable(fmt) 28 | 29 | target_link_libraries( fmt::fmt) 30 | 31 | * **Installed**: You can find and use an [installed](#installation) version of 32 | {fmt} in your `CMakeLists.txt` file as follows: 33 | 34 | find_package(fmt) 35 | target_link_libraries( fmt::fmt) 36 | 37 | * **Embedded**: You can add the {fmt} source tree to your project and include it 38 | in your `CMakeLists.txt` file: 39 | 40 | add_subdirectory(fmt) 41 | target_link_libraries( fmt::fmt) 42 | 43 | ## Installation 44 | 45 | ### Debian/Ubuntu 46 | 47 | To install {fmt} on Debian, Ubuntu, or any other Debian-based Linux 48 | distribution, use the following command: 49 | 50 | apt install libfmt-dev 51 | 52 | ### Homebrew 53 | 54 | Install {fmt} on macOS using [Homebrew](https://brew.sh/): 55 | 56 | brew install fmt 57 | 58 | ### Conda 59 | 60 | Install {fmt} on Linux, macOS, and Windows with [Conda]( 61 | https://docs.conda.io/en/latest/), using its [conda-forge package]( 62 | https://github.com/conda-forge/fmt-feedstock): 63 | 64 | conda install -c conda-forge fmt 65 | 66 | ### vcpkg 67 | 68 | Download and install {fmt} using the vcpkg package manager: 69 | 70 | git clone https://github.com/Microsoft/vcpkg.git 71 | cd vcpkg 72 | ./bootstrap-vcpkg.sh 73 | ./vcpkg integrate install 74 | ./vcpkg install fmt 75 | 76 | 80 | 81 | ## Building from Source 82 | 83 | CMake works by generating native makefiles or project files that can be 84 | used in the compiler environment of your choice. The typical workflow 85 | starts with: 86 | 87 | mkdir build # Create a directory to hold the build output. 88 | cd build 89 | cmake .. # Generate native build scripts. 90 | 91 | run in the `fmt` repository. 92 | 93 | If you are on a Unix-like system, you should now see a Makefile in the 94 | current directory. Now you can build the library by running `make`. 95 | 96 | Once the library has been built you can invoke `make test` to run the tests. 97 | 98 | You can control generation of the make `test` target with the `FMT_TEST` 99 | CMake option. This can be useful if you include fmt as a subdirectory in 100 | your project but don't want to add fmt's tests to your `test` target. 101 | 102 | To build a shared library set the `BUILD_SHARED_LIBS` CMake variable to `TRUE`: 103 | 104 | cmake -DBUILD_SHARED_LIBS=TRUE .. 105 | 106 | To build a static library with position-independent code (e.g. for 107 | linking it into another shared library such as a Python extension), set the 108 | `CMAKE_POSITION_INDEPENDENT_CODE` CMake variable to `TRUE`: 109 | 110 | cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. 111 | 112 | After building the library you can install it on a Unix-like system by 113 | running `sudo make install`. 114 | 115 | ### Building the Docs 116 | 117 | To build the documentation you need the following software installed on 118 | your system: 119 | 120 | - [Python](https://www.python.org/) 121 | - [Doxygen](http://www.stack.nl/~dimitri/doxygen/) 122 | - [MkDocs](https://www.mkdocs.org/) with `mkdocs-material`, `mkdocstrings`, 123 | `pymdown-extensions` and `mike` 124 | 125 | First generate makefiles or project files using CMake as described in 126 | the previous section. Then compile the `doc` target/project, for example: 127 | 128 | make doc 129 | 130 | This will generate the HTML documentation in `doc/html`. 131 | 132 | ## Build Systems 133 | 134 | ### build2 135 | 136 | You can use [build2](https://build2.org), a dependency manager and a build 137 | system, to use {fmt}. 138 | 139 | Currently this package is available in these package repositories: 140 | 141 | - for released and published versions. 142 | - for unreleased or custom versions. 143 | 144 | **Usage:** 145 | 146 | - `build2` package name: `fmt` 147 | - Library target name: `lib{fmt}` 148 | 149 | To make your `build2` project depend on `fmt`: 150 | 151 | - Add one of the repositories to your configurations, or in your 152 | `repositories.manifest`, if not already there: 153 | 154 | : 155 | role: prerequisite 156 | location: https://pkg.cppget.org/1/stable 157 | 158 | - Add this package as a dependency to your `manifest` file (example 159 | for version 10): 160 | 161 | depends: fmt ~10.0.0 162 | 163 | - Import the target and use it as a prerequisite to your own target 164 | using `fmt` in the appropriate `buildfile`: 165 | 166 | import fmt = fmt%lib{fmt} 167 | lib{mylib} : cxx{**} ... $fmt 168 | 169 | Then build your project as usual with `b` or `bdep update`. 170 | 171 | ### Meson 172 | 173 | [Meson WrapDB](https://mesonbuild.com/Wrapdb-projects.html) includes an `fmt` 174 | package. 175 | 176 | **Usage:** 177 | 178 | - Install the `fmt` subproject from the WrapDB by running: 179 | 180 | meson wrap install fmt 181 | 182 | from the root of your project. 183 | 184 | - In your project's `meson.build` file, add an entry for the new subproject: 185 | 186 | fmt = subproject('fmt') 187 | fmt_dep = fmt.get_variable('fmt_dep') 188 | 189 | - Include the new dependency object to link with fmt: 190 | 191 | my_build_target = executable( 192 | 'name', 'src/main.cc', dependencies: [fmt_dep]) 193 | 194 | **Options:** 195 | 196 | If desired, {fmt} can be built as a static library, or as a header-only library. 197 | 198 | For a static build, use the following subproject definition: 199 | 200 | fmt = subproject('fmt', default_options: 'default_library=static') 201 | fmt_dep = fmt.get_variable('fmt_dep') 202 | 203 | For the header-only version, use: 204 | 205 | fmt = subproject('fmt', default_options: ['header-only=true']) 206 | fmt_dep = fmt.get_variable('fmt_header_only_dep') 207 | 208 | ### Android NDK 209 | 210 | {fmt} provides [Android.mk file]( 211 | https://github.com/fmtlib/fmt/blob/master/support/Android.mk) that can be used 212 | to build the library with [Android NDK]( 213 | https://developer.android.com/tools/sdk/ndk/index.html). 214 | 215 | ### Other 216 | 217 | To use the {fmt} library with any other build system, add 218 | `include/fmt/base.h`, `include/fmt/format.h`, `include/fmt/format-inl.h`, 219 | `src/format.cc` and optionally other headers from a [release archive]( 220 | https://github.com/fmtlib/fmt/releases) or the [git repository]( 221 | https://github.com/fmtlib/fmt) to your project, add `include` to include 222 | directories and make sure `src/format.cc` is compiled and linked with your code. 223 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide: 3 | - navigation 4 | - toc 5 | --- 6 | 7 | # A modern formatting library 8 | 9 |
10 | 11 |
12 |

Safety

13 |

14 | Inspired by Python's formatting facility, {fmt} provides a safe replacement 15 | for the printf family of functions. Errors in format strings, 16 | which are a common source of vulnerabilities in C, are reported at 17 | compile time. For example: 18 | 19 |

fmt::format("{:d}", "I am not a number");
21 | 22 | will give a compile-time error because d is not a valid 23 | format specifier for strings. APIs like 24 | fmt::format prevent buffer overflow errors via 25 | automatic memory management. 26 |

27 | → Learn more 28 |
29 | 30 |
31 |

Extensibility

32 |

33 | Formatting of most standard types, including all containers, dates, 34 | and times is supported out-of-the-box. For example: 35 | 36 |

fmt::print("{}", std::vector{1, 2, 3});
38 | 39 | prints the vector in a JSON-like format: 40 | 41 |
[1, 2, 3]
42 | 43 | You can make your own types formattable and even make compile-time 44 | checks work for them. 45 |

46 | → Learn more 47 |
48 | 49 |
50 |

Performance

51 |

52 | {fmt} can be anywhere from tens of percent to 20-30 times faster than 53 | iostreams and sprintf, especially for numeric formatting. 54 | 55 | 56 | 57 | 58 | 59 | The library minimizes dynamic memory allocations and can optionally 60 | compile format strings to optimal code. 61 |

62 |
63 | 64 |
65 |

Unicode support

66 |

67 | {fmt} provides portable Unicode support on major operating systems 68 | with UTF-8 and char strings. For example: 69 | 70 |

fmt::print("Слава Україні!");
72 | 73 | will be printed correctly on Linux, macOS, and even Windows console, 74 | irrespective of the codepages. 75 |

76 |

77 | The default is locale-independent, but you can opt into localized 78 | formatting and {fmt} makes it work with Unicode, addressing issues in the 79 | standard libary. 80 |

81 |
82 | 83 |
84 |

Fast compilation

85 |

86 | The library makes extensive use of type erasure to achieve fast 87 | compilation. fmt/base.h provides a subset of the API with 88 | minimal include dependencies and enough functionality to replace 89 | all uses of *printf. 90 |

91 |

92 | Code using {fmt} is usually several times faster to compile than the 93 | equivalent iostreams code, and while printf compiles faster 94 | still, the gap is narrowing. 95 |

96 | 98 | → Learn more 99 |
100 | 101 |
102 |

Small binary footprint

103 |

104 | Type erasure is also used to prevent template bloat, resulting in compact 105 | per-call binary code. For example, a call to fmt::print with 106 | a single argument is just a few 107 | instructions, comparable to printf despite adding 108 | runtime safety, and much smaller than the equivalent iostreams code. 109 |

110 |

111 | The library itself has small binary footprint and some components such as 112 | floating-point formatting can be disabled to make it even smaller for 113 | resource-constrained devices. 114 |

115 |
116 | 117 |
118 |

Portability

119 |

120 | {fmt} has a small self-contained codebase with the core consisting of 121 | just three headers and no external dependencies. 122 |

123 |

124 | The library is highly portable and requires only a minimal subset of 125 | C++11 features which are available in GCC 4.9, Clang 3.6, MSVC 19.10 126 | (2017) and later. Newer compiler and standard library features are used 127 | if available, and enable additional functionality. 128 |

129 |

130 | Where possible, the output of formatting functions is consistent across 131 | platforms. 132 |

133 |

134 |
135 | 136 |
137 |

Open source

138 |

139 | {fmt} is in the top hundred open-source C++ libraries on GitHub and has 140 | hundreds of 141 | all-time contributors. 142 |

143 |

144 | The library is distributed under a permissive MIT 145 | license and is 146 | relied upon by many open-source projects, including Blender, PyTorch, 147 | Apple's FoundationDB, Windows Terminal, MongoDB, and others. 148 |

149 |
150 | 151 |
152 | -------------------------------------------------------------------------------- /doc/perf.svg: -------------------------------------------------------------------------------- 1 | double to string02505007501,0001,2501,500ostringstreamostrstreamsprintfdoubleconvfmtTime (ns), smaller is better -------------------------------------------------------------------------------- /include/fmt/args.h: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - dynamic argument lists 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #ifndef FMT_ARGS_H_ 9 | #define FMT_ARGS_H_ 10 | 11 | #ifndef FMT_MODULE 12 | # include // std::reference_wrapper 13 | # include // std::unique_ptr 14 | # include 15 | #endif 16 | 17 | #include "format.h" // std_string_view 18 | 19 | FMT_BEGIN_NAMESPACE 20 | namespace detail { 21 | 22 | template struct is_reference_wrapper : std::false_type {}; 23 | template 24 | struct is_reference_wrapper> : std::true_type {}; 25 | 26 | template auto unwrap(const T& v) -> const T& { return v; } 27 | template 28 | auto unwrap(const std::reference_wrapper& v) -> const T& { 29 | return static_cast(v); 30 | } 31 | 32 | // node is defined outside dynamic_arg_list to workaround a C2504 bug in MSVC 33 | // 2022 (v17.10.0). 34 | // 35 | // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for 36 | // templates it doesn't complain about inability to deduce single translation 37 | // unit for placing vtable. So node is made a fake template. 38 | template struct node { 39 | virtual ~node() = default; 40 | std::unique_ptr> next; 41 | }; 42 | 43 | class dynamic_arg_list { 44 | template struct typed_node : node<> { 45 | T value; 46 | 47 | template 48 | FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {} 49 | 50 | template 51 | FMT_CONSTEXPR typed_node(const basic_string_view& arg) 52 | : value(arg.data(), arg.size()) {} 53 | }; 54 | 55 | std::unique_ptr> head_; 56 | 57 | public: 58 | template auto push(const Arg& arg) -> const T& { 59 | auto new_node = std::unique_ptr>(new typed_node(arg)); 60 | auto& value = new_node->value; 61 | new_node->next = std::move(head_); 62 | head_ = std::move(new_node); 63 | return value; 64 | } 65 | }; 66 | } // namespace detail 67 | 68 | /** 69 | * A dynamic list of formatting arguments with storage. 70 | * 71 | * It can be implicitly converted into `fmt::basic_format_args` for passing 72 | * into type-erased formatting functions such as `fmt::vformat`. 73 | */ 74 | FMT_EXPORT template class dynamic_format_arg_store { 75 | private: 76 | using char_type = typename Context::char_type; 77 | 78 | template struct need_copy { 79 | static constexpr detail::type mapped_type = 80 | detail::mapped_type_constant::value; 81 | 82 | enum { 83 | value = !(detail::is_reference_wrapper::value || 84 | std::is_same>::value || 85 | std::is_same>::value || 86 | (mapped_type != detail::type::cstring_type && 87 | mapped_type != detail::type::string_type && 88 | mapped_type != detail::type::custom_type)) 89 | }; 90 | }; 91 | 92 | template 93 | using stored_t = conditional_t< 94 | std::is_convertible>::value && 95 | !detail::is_reference_wrapper::value, 96 | std::basic_string, T>; 97 | 98 | // Storage of basic_format_arg must be contiguous. 99 | std::vector> data_; 100 | std::vector> named_info_; 101 | 102 | // Storage of arguments not fitting into basic_format_arg must grow 103 | // without relocation because items in data_ refer to it. 104 | detail::dynamic_arg_list dynamic_args_; 105 | 106 | friend class basic_format_args; 107 | 108 | auto data() const -> const basic_format_arg* { 109 | return named_info_.empty() ? data_.data() : data_.data() + 1; 110 | } 111 | 112 | template void emplace_arg(const T& arg) { 113 | data_.emplace_back(arg); 114 | } 115 | 116 | template 117 | void emplace_arg(const detail::named_arg& arg) { 118 | if (named_info_.empty()) 119 | data_.insert(data_.begin(), basic_format_arg(nullptr, 0)); 120 | data_.emplace_back(detail::unwrap(arg.value)); 121 | auto pop_one = [](std::vector>* data) { 122 | data->pop_back(); 123 | }; 124 | std::unique_ptr>, decltype(pop_one)> 125 | guard{&data_, pop_one}; 126 | named_info_.push_back({arg.name, static_cast(data_.size() - 2u)}); 127 | data_[0] = {named_info_.data(), named_info_.size()}; 128 | guard.release(); 129 | } 130 | 131 | public: 132 | constexpr dynamic_format_arg_store() = default; 133 | 134 | operator basic_format_args() const { 135 | return basic_format_args(data(), static_cast(data_.size()), 136 | !named_info_.empty()); 137 | } 138 | 139 | /** 140 | * Adds an argument into the dynamic store for later passing to a formatting 141 | * function. 142 | * 143 | * Note that custom types and string types (but not string views) are copied 144 | * into the store dynamically allocating memory if necessary. 145 | * 146 | * **Example**: 147 | * 148 | * fmt::dynamic_format_arg_store store; 149 | * store.push_back(42); 150 | * store.push_back("abc"); 151 | * store.push_back(1.5f); 152 | * std::string result = fmt::vformat("{} and {} and {}", store); 153 | */ 154 | template void push_back(const T& arg) { 155 | if (detail::const_check(need_copy::value)) 156 | emplace_arg(dynamic_args_.push>(arg)); 157 | else 158 | emplace_arg(detail::unwrap(arg)); 159 | } 160 | 161 | /** 162 | * Adds a reference to the argument into the dynamic store for later passing 163 | * to a formatting function. 164 | * 165 | * **Example**: 166 | * 167 | * fmt::dynamic_format_arg_store store; 168 | * char band[] = "Rolling Stones"; 169 | * store.push_back(std::cref(band)); 170 | * band[9] = 'c'; // Changing str affects the output. 171 | * std::string result = fmt::vformat("{}", store); 172 | * // result == "Rolling Scones" 173 | */ 174 | template void push_back(std::reference_wrapper arg) { 175 | static_assert( 176 | need_copy::value, 177 | "objects of built-in types and string views are always copied"); 178 | emplace_arg(arg.get()); 179 | } 180 | 181 | /** 182 | * Adds named argument into the dynamic store for later passing to a 183 | * formatting function. `std::reference_wrapper` is supported to avoid 184 | * copying of the argument. The name is always copied into the store. 185 | */ 186 | template 187 | void push_back(const detail::named_arg& arg) { 188 | const char_type* arg_name = 189 | dynamic_args_.push>(arg.name).c_str(); 190 | if (detail::const_check(need_copy::value)) { 191 | emplace_arg( 192 | fmt::arg(arg_name, dynamic_args_.push>(arg.value))); 193 | } else { 194 | emplace_arg(fmt::arg(arg_name, arg.value)); 195 | } 196 | } 197 | 198 | /// Erase all elements from the store. 199 | void clear() { 200 | data_.clear(); 201 | named_info_.clear(); 202 | dynamic_args_ = {}; 203 | } 204 | 205 | /// Reserves space to store at least `new_cap` arguments including 206 | /// `new_cap_named` named arguments. 207 | void reserve(size_t new_cap, size_t new_cap_named) { 208 | FMT_ASSERT(new_cap >= new_cap_named, 209 | "set of arguments includes set of named arguments"); 210 | data_.reserve(new_cap); 211 | named_info_.reserve(new_cap_named); 212 | } 213 | 214 | /// Returns the number of elements in the store. 215 | size_t size() const noexcept { return data_.size(); } 216 | }; 217 | 218 | FMT_END_NAMESPACE 219 | 220 | #endif // FMT_ARGS_H_ 221 | -------------------------------------------------------------------------------- /include/fmt/core.h: -------------------------------------------------------------------------------- 1 | // This file is only provided for compatibility and may be removed in future 2 | // versions. Use fmt/base.h if you don't need fmt::format and fmt/format.h 3 | // otherwise. 4 | 5 | #include "format.h" 6 | -------------------------------------------------------------------------------- /include/fmt/ostream.h: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - std::ostream support 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #ifndef FMT_OSTREAM_H_ 9 | #define FMT_OSTREAM_H_ 10 | 11 | #ifndef FMT_MODULE 12 | # include // std::filebuf 13 | #endif 14 | 15 | #ifdef _WIN32 16 | # ifdef __GLIBCXX__ 17 | # include 18 | # include 19 | # endif 20 | # include 21 | #endif 22 | 23 | #include "chrono.h" // formatbuf 24 | 25 | #ifdef _MSVC_STL_UPDATE 26 | # define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE 27 | #elif defined(_MSC_VER) && _MSC_VER < 1912 // VS 15.5 28 | # define FMT_MSVC_STL_UPDATE _MSVC_LANG 29 | #else 30 | # define FMT_MSVC_STL_UPDATE 0 31 | #endif 32 | 33 | FMT_BEGIN_NAMESPACE 34 | namespace detail { 35 | 36 | // Generate a unique explicit instantiation in every translation unit using a 37 | // tag type in an anonymous namespace. 38 | namespace { 39 | struct file_access_tag {}; 40 | } // namespace 41 | template 42 | class file_access { 43 | friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; } 44 | }; 45 | 46 | #if FMT_MSVC_STL_UPDATE 47 | template class file_access; 49 | auto get_file(std::filebuf&) -> FILE*; 50 | #endif 51 | 52 | // Write the content of buf to os. 53 | // It is a separate function rather than a part of vprint to simplify testing. 54 | template 55 | void write_buffer(std::basic_ostream& os, buffer& buf) { 56 | const Char* buf_data = buf.data(); 57 | using unsigned_streamsize = make_unsigned_t; 58 | unsigned_streamsize size = buf.size(); 59 | unsigned_streamsize max_size = to_unsigned(max_value()); 60 | do { 61 | unsigned_streamsize n = size <= max_size ? size : max_size; 62 | os.write(buf_data, static_cast(n)); 63 | buf_data += n; 64 | size -= n; 65 | } while (size != 0); 66 | } 67 | 68 | template struct streamed_view { 69 | const T& value; 70 | }; 71 | } // namespace detail 72 | 73 | // Formats an object of type T that has an overloaded ostream operator<<. 74 | template 75 | struct basic_ostream_formatter : formatter, Char> { 76 | void set_debug_format() = delete; 77 | 78 | template 79 | auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) { 80 | auto buffer = basic_memory_buffer(); 81 | auto&& formatbuf = detail::formatbuf>(buffer); 82 | auto&& output = std::basic_ostream(&formatbuf); 83 | output.imbue(std::locale::classic()); // The default is always unlocalized. 84 | output << value; 85 | output.exceptions(std::ios_base::failbit | std::ios_base::badbit); 86 | return formatter, Char>::format( 87 | {buffer.data(), buffer.size()}, ctx); 88 | } 89 | }; 90 | 91 | using ostream_formatter = basic_ostream_formatter; 92 | 93 | template 94 | struct formatter, Char> 95 | : basic_ostream_formatter { 96 | template 97 | auto format(detail::streamed_view view, Context& ctx) const 98 | -> decltype(ctx.out()) { 99 | return basic_ostream_formatter::format(view.value, ctx); 100 | } 101 | }; 102 | 103 | /** 104 | * Returns a view that formats `value` via an ostream `operator<<`. 105 | * 106 | * **Example**: 107 | * 108 | * fmt::print("Current thread id: {}\n", 109 | * fmt::streamed(std::this_thread::get_id())); 110 | */ 111 | template 112 | constexpr auto streamed(const T& value) -> detail::streamed_view { 113 | return {value}; 114 | } 115 | 116 | inline void vprint(std::ostream& os, string_view fmt, format_args args) { 117 | auto buffer = memory_buffer(); 118 | detail::vformat_to(buffer, fmt, args); 119 | FILE* f = nullptr; 120 | #if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI 121 | if (auto* buf = dynamic_cast(os.rdbuf())) 122 | f = detail::get_file(*buf); 123 | #elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI 124 | auto* rdbuf = os.rdbuf(); 125 | if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf*>(rdbuf)) 126 | f = sfbuf->file(); 127 | else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf*>(rdbuf)) 128 | f = fbuf->file(); 129 | #endif 130 | #ifdef _WIN32 131 | if (f) { 132 | int fd = _fileno(f); 133 | if (_isatty(fd)) { 134 | os.flush(); 135 | if (detail::write_console(fd, {buffer.data(), buffer.size()})) return; 136 | } 137 | } 138 | #endif 139 | detail::ignore_unused(f); 140 | detail::write_buffer(os, buffer); 141 | } 142 | 143 | /** 144 | * Prints formatted data to the stream `os`. 145 | * 146 | * **Example**: 147 | * 148 | * fmt::print(cerr, "Don't {}!", "panic"); 149 | */ 150 | FMT_EXPORT template 151 | void print(std::ostream& os, format_string fmt, T&&... args) { 152 | fmt::vargs vargs = {{args...}}; 153 | if (detail::const_check(detail::use_utf8)) return vprint(os, fmt.str, vargs); 154 | auto buffer = memory_buffer(); 155 | detail::vformat_to(buffer, fmt.str, vargs); 156 | detail::write_buffer(os, buffer); 157 | } 158 | 159 | FMT_EXPORT template 160 | void println(std::ostream& os, format_string fmt, T&&... args) { 161 | fmt::print(os, FMT_STRING("{}\n"), 162 | fmt::format(fmt, std::forward(args)...)); 163 | } 164 | 165 | FMT_END_NAMESPACE 166 | 167 | #endif // FMT_OSTREAM_H_ 168 | -------------------------------------------------------------------------------- /src/fmt.cc: -------------------------------------------------------------------------------- 1 | module; 2 | 3 | #define FMT_MODULE 4 | 5 | #ifdef _MSVC_LANG 6 | # define FMT_CPLUSPLUS _MSVC_LANG 7 | #else 8 | # define FMT_CPLUSPLUS __cplusplus 9 | #endif 10 | 11 | // Put all implementation-provided headers into the global module fragment 12 | // to prevent attachment to this module. 13 | #ifndef FMT_IMPORT_STD 14 | # include 15 | # include 16 | # include 17 | # include 18 | # include 19 | # include 20 | # include 21 | # include 22 | # include 23 | # include 24 | # include 25 | # include 26 | # if FMT_CPLUSPLUS > 202002L 27 | # include 28 | # endif 29 | # include 30 | # include 31 | # include 32 | # include 33 | # include 34 | # include 35 | # include 36 | # include 37 | # include 38 | # include 39 | # include 40 | # include 41 | # include 42 | # include 43 | # include 44 | # include 45 | # include 46 | # include 47 | # include 48 | # include 49 | #else 50 | # include 51 | # include 52 | # include 53 | # include 54 | #endif 55 | #include 56 | #include 57 | #include 58 | 59 | #if __has_include() 60 | # include 61 | #endif 62 | #if defined(_MSC_VER) || defined(__MINGW32__) 63 | # include 64 | #endif 65 | #if defined __APPLE__ || defined(__FreeBSD__) 66 | # include 67 | #endif 68 | #if __has_include() 69 | # include 70 | #endif 71 | #if (__has_include() || defined(__APPLE__) || \ 72 | defined(__linux__)) && \ 73 | (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) 74 | # include 75 | # include 76 | # include 77 | # ifndef _WIN32 78 | # include 79 | # else 80 | # include 81 | # endif 82 | #endif 83 | #ifdef _WIN32 84 | # if defined(__GLIBCXX__) 85 | # include 86 | # include 87 | # endif 88 | # define WIN32_LEAN_AND_MEAN 89 | # include 90 | #endif 91 | 92 | export module fmt; 93 | 94 | #ifdef FMT_IMPORT_STD 95 | import std; 96 | #endif 97 | 98 | #define FMT_EXPORT export 99 | #define FMT_BEGIN_EXPORT export { 100 | #define FMT_END_EXPORT } 101 | 102 | // If you define FMT_ATTACH_TO_GLOBAL_MODULE 103 | // - all declarations are detached from module 'fmt' 104 | // - the module behaves like a traditional static library, too 105 | // - all library symbols are mangled traditionally 106 | // - you can mix TUs with either importing or #including the {fmt} API 107 | #ifdef FMT_ATTACH_TO_GLOBAL_MODULE 108 | extern "C++" { 109 | #endif 110 | 111 | #ifndef FMT_OS 112 | # define FMT_OS 1 113 | #endif 114 | 115 | // All library-provided declarations and definitions must be in the module 116 | // purview to be exported. 117 | #include "fmt/args.h" 118 | #include "fmt/chrono.h" 119 | #include "fmt/color.h" 120 | #include "fmt/compile.h" 121 | #include "fmt/format.h" 122 | #if FMT_OS 123 | # include "fmt/os.h" 124 | #endif 125 | #include "fmt/ostream.h" 126 | #include "fmt/printf.h" 127 | #include "fmt/ranges.h" 128 | #include "fmt/std.h" 129 | #include "fmt/xchar.h" 130 | 131 | #ifdef FMT_ATTACH_TO_GLOBAL_MODULE 132 | } 133 | #endif 134 | 135 | // gcc doesn't yet implement private module fragments 136 | #if !FMT_GCC_VERSION 137 | module :private; 138 | #endif 139 | 140 | #ifdef FMT_ATTACH_TO_GLOBAL_MODULE 141 | extern "C++" { 142 | #endif 143 | 144 | #if FMT_HAS_INCLUDE("format.cc") 145 | # include "format.cc" 146 | #endif 147 | #if FMT_OS && FMT_HAS_INCLUDE("os.cc") 148 | # include "os.cc" 149 | #endif 150 | 151 | #ifdef FMT_ATTACH_TO_GLOBAL_MODULE 152 | } 153 | #endif 154 | -------------------------------------------------------------------------------- /src/format.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ 2 | // 3 | // Copyright (c) 2012 - 2016, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include "fmt/format-inl.h" 9 | 10 | FMT_BEGIN_NAMESPACE 11 | namespace detail { 12 | 13 | template FMT_API auto dragonbox::to_decimal(float x) noexcept 14 | -> dragonbox::decimal_fp; 15 | template FMT_API auto dragonbox::to_decimal(double x) noexcept 16 | -> dragonbox::decimal_fp; 17 | 18 | #if FMT_USE_LOCALE 19 | // DEPRECATED! locale_ref in the detail namespace 20 | template FMT_API locale_ref::locale_ref(const std::locale& loc); 21 | template FMT_API auto locale_ref::get() const -> std::locale; 22 | #endif 23 | 24 | // Explicit instantiations for char. 25 | 26 | template FMT_API auto thousands_sep_impl(locale_ref) 27 | -> thousands_sep_result; 28 | template FMT_API auto decimal_point_impl(locale_ref) -> char; 29 | 30 | // DEPRECATED! 31 | template FMT_API void buffer::append(const char*, const char*); 32 | 33 | // DEPRECATED! 34 | template FMT_API void vformat_to(buffer&, string_view, 35 | typename vformat_args<>::type, locale_ref); 36 | 37 | // Explicit instantiations for wchar_t. 38 | 39 | template FMT_API auto thousands_sep_impl(locale_ref) 40 | -> thousands_sep_result; 41 | template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; 42 | 43 | template FMT_API void buffer::append(const wchar_t*, const wchar_t*); 44 | 45 | } // namespace detail 46 | FMT_END_NAMESPACE 47 | -------------------------------------------------------------------------------- /support/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | include $(CLEAR_VARS) 3 | 4 | LOCAL_MODULE := fmt_static 5 | LOCAL_MODULE_FILENAME := libfmt 6 | 7 | LOCAL_SRC_FILES := ../src/format.cc 8 | 9 | LOCAL_C_INCLUDES := $(LOCAL_PATH) 10 | LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) 11 | 12 | LOCAL_CFLAGS += -std=c++11 -fexceptions 13 | 14 | include $(BUILD_STATIC_LIBRARY) 15 | 16 | -------------------------------------------------------------------------------- /support/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /support/README: -------------------------------------------------------------------------------- 1 | This directory contains build support files such as 2 | 3 | * CMake modules 4 | * Build scripts 5 | -------------------------------------------------------------------------------- /support/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # A vagrant config for testing against gcc-4.8. 5 | Vagrant.configure("2") do |config| 6 | config.vm.box = "bento/ubuntu-22.04-arm64" 7 | 8 | config.vm.provider "vmware_desktop" do |vb| 9 | vb.memory = "4096" 10 | end 11 | 12 | config.vm.provision "shell", inline: <<-SHELL 13 | apt-get update 14 | apt-get install -y g++ make wget git 15 | wget -q https://github.com/Kitware/CMake/releases/download/v3.26.0/cmake-3.26.0-Linux-x86_64.tar.gz 16 | tar xzf cmake-3.26.0-Linux-x86_64.tar.gz 17 | ln -s `pwd`/cmake-3.26.0-Linux-x86_64/bin/cmake /usr/local/bin 18 | SHELL 19 | end 20 | -------------------------------------------------------------------------------- /support/bazel/.bazelversion: -------------------------------------------------------------------------------- 1 | 8.1.1 2 | -------------------------------------------------------------------------------- /support/bazel/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_library") 2 | 3 | cc_library( 4 | name = "fmt", 5 | srcs = [ 6 | #"src/fmt.cc", # No C++ module support, yet in Bazel (https://github.com/bazelbuild/bazel/pull/19940) 7 | "src/format.cc", 8 | "src/os.cc", 9 | ], 10 | hdrs = glob([ 11 | "include/fmt/*.h", 12 | ]), 13 | copts = select({ 14 | "@platforms//os:windows": ["-utf-8"], 15 | "//conditions:default": [], 16 | }), 17 | includes = [ 18 | "include", 19 | ], 20 | strip_include_prefix = "include", 21 | visibility = ["//visibility:public"], 22 | ) 23 | -------------------------------------------------------------------------------- /support/bazel/MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "fmt", 3 | compatibility_level = 10, 4 | ) 5 | 6 | bazel_dep(name = "platforms", version = "0.0.11") 7 | bazel_dep(name = "rules_cc", version = "0.1.1") 8 | -------------------------------------------------------------------------------- /support/bazel/README.md: -------------------------------------------------------------------------------- 1 | # Bazel support 2 | 3 | To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, 4 | `MODULE.bazel`, `WORKSPACE.bazel`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project. 5 | This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}). 6 | 7 | ## Using {fmt} as a dependency 8 | 9 | ### Using Bzlmod 10 | 11 | The [Bazel Central Registry](https://github.com/bazelbuild/bazel-central-registry/tree/main/modules/fmt) provides support for {fmt}. 12 | 13 | For instance, to use {fmt} add to your `MODULE.bazel` file: 14 | 15 | ``` 16 | bazel_dep(name = "fmt", version = "11.1.4") 17 | ``` 18 | 19 | ### Live at head 20 | 21 | For a live-at-head approach, you can copy the contents of this repository and move the Bazel-related build files to the root folder of this project as described above and make use of `local_path_override`, e.g.: 22 | 23 | ``` 24 | local_path_override( 25 | module_name = "fmt", 26 | path = "../third_party/fmt", 27 | ) 28 | ``` 29 | -------------------------------------------------------------------------------- /support/bazel/WORKSPACE.bazel: -------------------------------------------------------------------------------- 1 | # WORKSPACE marker file needed by Bazel 2 | 3 | -------------------------------------------------------------------------------- /support/build.gradle: -------------------------------------------------------------------------------- 1 | import java.nio.file.Paths 2 | 3 | // General gradle arguments for root project 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | // 11 | // https://developer.android.com/studio/releases/gradle-plugin#updating-gradle 12 | // 13 | // Notice that 4.0.0 here is the version of [Android Gradle Plugin] 14 | // According to URL above you will need Gradle 6.1 or higher 15 | // 16 | classpath "com.android.tools.build:gradle:4.1.1" 17 | } 18 | } 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | 24 | // Project's root where CMakeLists.txt exists: rootDir/support/.cxx -> rootDir 25 | def rootDir = Paths.get(project.buildDir.getParent()).getParent() 26 | println("rootDir: ${rootDir}") 27 | 28 | // Output: Shared library (.so) for Android 29 | apply plugin: "com.android.library" 30 | android { 31 | compileSdkVersion 25 // Android 7.0 32 | 33 | // Target ABI 34 | // - This option controls target platform of module 35 | // - The platform might be limited by compiler's support 36 | // some can work with Clang(default), but some can work only with GCC... 37 | // if bad, both toolchains might not support it 38 | splits { 39 | abi { 40 | enable true 41 | // Specify platforms for Application 42 | reset() 43 | include "arm64-v8a", "armeabi-v7a", "x86_64" 44 | } 45 | } 46 | ndkVersion "21.3.6528147" // ANDROID_NDK_HOME is deprecated. Be explicit 47 | 48 | defaultConfig { 49 | minSdkVersion 21 // Android 5.0+ 50 | targetSdkVersion 25 // Follow Compile SDK 51 | versionCode 34 // Follow release count 52 | versionName "7.1.2" // Follow Official version 53 | 54 | externalNativeBuild { 55 | cmake { 56 | arguments "-DANDROID_STL=c++_shared" // Specify Android STL 57 | arguments "-DBUILD_SHARED_LIBS=true" // Build shared object 58 | arguments "-DFMT_TEST=false" // Skip test 59 | arguments "-DFMT_DOC=false" // Skip document 60 | cppFlags "-std=c++17" 61 | targets "fmt" 62 | } 63 | } 64 | println(externalNativeBuild.cmake.cppFlags) 65 | println(externalNativeBuild.cmake.arguments) 66 | } 67 | 68 | // External Native build 69 | // - Use existing CMakeList.txt 70 | // - Give path to CMake. This gradle file should be 71 | // neighbor of the top level cmake 72 | externalNativeBuild { 73 | cmake { 74 | version "3.10.0+" 75 | path "${rootDir}/CMakeLists.txt" 76 | // buildStagingDirectory "./build" // Custom path for cmake output 77 | } 78 | } 79 | 80 | sourceSets{ 81 | // Android Manifest for Gradle 82 | main { 83 | manifest.srcFile "AndroidManifest.xml" 84 | } 85 | } 86 | 87 | // https://developer.android.com/studio/build/native-dependencies#build_system_configuration 88 | buildFeatures { 89 | prefab true 90 | prefabPublishing true 91 | } 92 | prefab { 93 | fmt { 94 | headers "${rootDir}/include" 95 | } 96 | } 97 | } 98 | 99 | assemble.doLast 100 | { 101 | // Instead of `ninja install`, Gradle will deploy the files. 102 | // We are doing this since FMT is dependent to the ANDROID_STL after build 103 | copy { 104 | from "build/intermediates/cmake" 105 | into "${rootDir}/libs" 106 | } 107 | // Copy debug binaries 108 | copy { 109 | from "${rootDir}/libs/debug/obj" 110 | into "${rootDir}/libs/debug" 111 | } 112 | // Copy Release binaries 113 | copy { 114 | from "${rootDir}/libs/release/obj" 115 | into "${rootDir}/libs/release" 116 | } 117 | // Remove empty directory 118 | delete "${rootDir}/libs/debug/obj" 119 | delete "${rootDir}/libs/release/obj" 120 | 121 | // Copy AAR files. Notice that the aar is named after the folder of this script. 122 | copy { 123 | from "build/outputs/aar/support-release.aar" 124 | into "${rootDir}/libs" 125 | rename "support-release.aar", "fmt-release.aar" 126 | } 127 | copy { 128 | from "build/outputs/aar/support-debug.aar" 129 | into "${rootDir}/libs" 130 | rename "support-debug.aar", "fmt-debug.aar" 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /support/check-commits: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Compile source on a range of commits 4 | 5 | Usage: 6 | check-commits 7 | """ 8 | 9 | import docopt, os, sys, tempfile 10 | from subprocess import check_call, check_output, run 11 | 12 | args = docopt.docopt(__doc__) 13 | start = args.get('') 14 | source = args.get('') 15 | 16 | cwd = os.getcwd() 17 | 18 | with tempfile.TemporaryDirectory() as work_dir: 19 | check_call(['git', 'clone', 'https://github.com/fmtlib/fmt.git'], 20 | cwd=work_dir) 21 | repo_dir = os.path.join(work_dir, 'fmt') 22 | commits = check_output( 23 | ['git', 'rev-list', f'{start}..HEAD', '--abbrev-commit', 24 | '--', 'include', 'src'], 25 | text=True, cwd=repo_dir).rstrip().split('\n') 26 | commits.reverse() 27 | print('Time\tCommit') 28 | for commit in commits: 29 | check_call(['git', '-c', 'advice.detachedHead=false', 'checkout', commit], 30 | cwd=repo_dir) 31 | returncode = run( 32 | ['c++', '-std=c++11', '-O3', '-DNDEBUG', '-I', 'include', 33 | 'src/format.cc', os.path.join(cwd, source)], cwd=repo_dir).returncode 34 | if returncode != 0: 35 | continue 36 | times = [] 37 | for i in range(5): 38 | output = check_output([os.path.join(repo_dir, 'a.out')], text=True) 39 | times.append(float(output)) 40 | message = check_output(['git', 'log', '-1', '--pretty=format:%s', commit], 41 | cwd=repo_dir, text=True) 42 | print(f'{min(times)}\t{commit} {message[:40]}') 43 | sys.stdout.flush() 44 | -------------------------------------------------------------------------------- /support/cmake/FindSetEnv.cmake: -------------------------------------------------------------------------------- 1 | # A CMake script to find SetEnv.cmd. 2 | 3 | find_program(WINSDK_SETENV NAMES SetEnv.cmd 4 | PATHS "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]/bin") 5 | if (WINSDK_SETENV AND PRINT_PATH) 6 | execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${WINSDK_SETENV}") 7 | endif () 8 | -------------------------------------------------------------------------------- /support/cmake/JoinPaths.cmake: -------------------------------------------------------------------------------- 1 | # This module provides function for joining paths 2 | # known from from most languages 3 | # 4 | # Original license: 5 | # SPDX-License-Identifier: (MIT OR CC0-1.0) 6 | # Explicit permission given to distribute this module under 7 | # the terms of the project as described in /LICENSE.rst. 8 | # Copyright 2020 Jan Tojnar 9 | # https://github.com/jtojnar/cmake-snips 10 | # 11 | # Modelled after Python’s os.path.join 12 | # https://docs.python.org/3.7/library/os.path.html#os.path.join 13 | # Windows not supported 14 | function(join_paths joined_path first_path_segment) 15 | set(temp_path "${first_path_segment}") 16 | foreach(current_segment IN LISTS ARGN) 17 | if(NOT ("${current_segment}" STREQUAL "")) 18 | if(IS_ABSOLUTE "${current_segment}") 19 | set(temp_path "${current_segment}") 20 | else() 21 | set(temp_path "${temp_path}/${current_segment}") 22 | endif() 23 | endif() 24 | endforeach() 25 | set(${joined_path} "${temp_path}" PARENT_SCOPE) 26 | endfunction() 27 | -------------------------------------------------------------------------------- /support/cmake/fmt-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | if (NOT TARGET fmt::fmt) 4 | include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake) 5 | endif () 6 | 7 | check_required_components(fmt) 8 | -------------------------------------------------------------------------------- /support/cmake/fmt.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=@CMAKE_INSTALL_PREFIX@ 3 | libdir=@libdir_for_pc_file@ 4 | includedir=@includedir_for_pc_file@ 5 | 6 | Name: fmt 7 | Description: A modern formatting library 8 | Version: @FMT_VERSION@ 9 | Libs: -L${libdir} -l@FMT_LIB_NAME@ 10 | Cflags: -I${includedir} 11 | 12 | -------------------------------------------------------------------------------- /support/mkdocs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # A script to invoke mkdocs with the correct environment. 3 | # Additionally supports deploying via mike: 4 | # ./mkdocs deploy [mike-deploy-options] 5 | 6 | import errno, os, shutil, sys 7 | from subprocess import call 8 | 9 | support_dir = os.path.dirname(os.path.normpath(__file__)) 10 | build_dir = os.path.join(os.path.dirname(support_dir), 'build') 11 | 12 | # Set PYTHONPATH for the mkdocstrings handler. 13 | env = os.environ.copy() 14 | path = env.get('PYTHONPATH') 15 | env['PYTHONPATH'] = \ 16 | (path + ':' if path else '') + os.path.join(support_dir, 'python') 17 | 18 | redirect_page = \ 19 | ''' 20 | 21 | 22 | 23 | Redirecting 24 | 27 | 32 | 33 | 34 | Redirecting to api... 35 | 36 | 37 | ''' 38 | 39 | config_path = os.path.join(support_dir, 'mkdocs.yml') 40 | args = sys.argv[1:] 41 | if len(args) > 0: 42 | command = args[0] 43 | if command == 'deploy': 44 | git_url = 'https://github.com/' if 'CI' in os.environ else 'git@github.com:' 45 | site_repo = git_url + 'fmtlib/fmt.dev.git' 46 | 47 | site_dir = os.path.join(build_dir, 'fmt.dev') 48 | try: 49 | shutil.rmtree(site_dir) 50 | except OSError as e: 51 | if e.errno == errno.ENOENT: 52 | pass 53 | ret = call(['git', 'clone', '--depth=1', site_repo, site_dir]) 54 | if ret != 0: 55 | sys.exit(ret) 56 | 57 | # Copy the config to the build dir because the site is built relative to it. 58 | config_build_path = os.path.join(build_dir, 'mkdocs.yml') 59 | shutil.copyfile(config_path, config_build_path) 60 | 61 | version = args[1] 62 | ret = call(['mike'] + args + ['--config-file', config_build_path, 63 | '--branch', 'master'], cwd=site_dir, env=env) 64 | if ret != 0 or version == 'dev': 65 | sys.exit(ret) 66 | current_doc_path = os.path.join(site_dir, version) 67 | os.makedirs(current_doc_path, exist_ok=True) 68 | redirect_page_path = os.path.join(current_doc_path, 'api.html') 69 | with open(redirect_page_path, "w") as file: 70 | file.write(redirect_page) 71 | ret = call(['git', 'add', redirect_page_path], cwd=site_dir) 72 | if ret != 0: 73 | sys.exit(ret) 74 | ret = call(['git', 'commit', '--amend', '--no-edit'], cwd=site_dir) 75 | sys.exit(ret) 76 | elif not command.startswith('-'): 77 | args += ['-f', config_path] 78 | sys.exit(call(['mkdocs'] + args, env=env)) 79 | -------------------------------------------------------------------------------- /support/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: '{fmt}' 2 | 3 | docs_dir: ../doc 4 | 5 | repo_url: https://github.com/fmtlib/fmt 6 | 7 | theme: 8 | name: material 9 | features: 10 | - navigation.tabs 11 | - navigation.top 12 | - toc.integrate 13 | 14 | extra_javascript: 15 | - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/highlight.min.js 16 | - fmt.js 17 | 18 | extra_css: 19 | - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/styles/default.min.css 20 | - fmt.css 21 | 22 | markdown_extensions: 23 | - pymdownx.highlight: 24 | # Use JavaScript syntax highlighter instead of Pygments because it 25 | # automatically applies to code blocks extracted through Doxygen. 26 | use_pygments: false 27 | anchor_linenums: true 28 | line_spans: __span 29 | pygments_lang_class: true 30 | - pymdownx.inlinehilite 31 | - pymdownx.snippets 32 | 33 | plugins: 34 | - search 35 | - mkdocstrings: 36 | default_handler: cxx 37 | nav: 38 | - Home: index.md 39 | - Get Started: get-started.md 40 | - API: api.md 41 | - Syntax: syntax.md 42 | 43 | exclude_docs: ChangeLog-old.md 44 | 45 | extra: 46 | version: 47 | provider: mike 48 | generator: false 49 | -------------------------------------------------------------------------------- /support/printable.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This script is based on 4 | # https://github.com/rust-lang/rust/blob/master/library/core/src/unicode/printable.py 5 | # distributed under https://github.com/rust-lang/rust/blob/master/LICENSE-MIT. 6 | 7 | # This script uses the following Unicode tables: 8 | # - UnicodeData.txt 9 | 10 | 11 | from collections import namedtuple 12 | import csv 13 | import os 14 | import subprocess 15 | 16 | NUM_CODEPOINTS=0x110000 17 | 18 | def to_ranges(iter): 19 | current = None 20 | for i in iter: 21 | if current is None or i != current[1] or i in (0x10000, 0x20000): 22 | if current is not None: 23 | yield tuple(current) 24 | current = [i, i + 1] 25 | else: 26 | current[1] += 1 27 | if current is not None: 28 | yield tuple(current) 29 | 30 | def get_escaped(codepoints): 31 | for c in codepoints: 32 | if (c.class_ or "Cn") in "Cc Cf Cs Co Cn Zl Zp Zs".split() and c.value != ord(' '): 33 | yield c.value 34 | 35 | def get_file(f): 36 | try: 37 | return open(os.path.basename(f)) 38 | except FileNotFoundError: 39 | subprocess.run(["curl", "-O", f], check=True) 40 | return open(os.path.basename(f)) 41 | 42 | Codepoint = namedtuple('Codepoint', 'value class_') 43 | 44 | def get_codepoints(f): 45 | r = csv.reader(f, delimiter=";") 46 | prev_codepoint = 0 47 | class_first = None 48 | for row in r: 49 | codepoint = int(row[0], 16) 50 | name = row[1] 51 | class_ = row[2] 52 | 53 | if class_first is not None: 54 | if not name.endswith("Last>"): 55 | raise ValueError("Missing Last after First") 56 | 57 | for c in range(prev_codepoint + 1, codepoint): 58 | yield Codepoint(c, class_first) 59 | 60 | class_first = None 61 | if name.endswith("First>"): 62 | class_first = class_ 63 | 64 | yield Codepoint(codepoint, class_) 65 | prev_codepoint = codepoint 66 | 67 | if class_first is not None: 68 | raise ValueError("Missing Last after First") 69 | 70 | for c in range(prev_codepoint + 1, NUM_CODEPOINTS): 71 | yield Codepoint(c, None) 72 | 73 | def compress_singletons(singletons): 74 | uppers = [] # (upper, # items in lowers) 75 | lowers = [] 76 | 77 | for i in singletons: 78 | upper = i >> 8 79 | lower = i & 0xff 80 | if len(uppers) == 0 or uppers[-1][0] != upper: 81 | uppers.append((upper, 1)) 82 | else: 83 | upper, count = uppers[-1] 84 | uppers[-1] = upper, count + 1 85 | lowers.append(lower) 86 | 87 | return uppers, lowers 88 | 89 | def compress_normal(normal): 90 | # lengths 0x00..0x7f are encoded as 00, 01, ..., 7e, 7f 91 | # lengths 0x80..0x7fff are encoded as 80 80, 80 81, ..., ff fe, ff ff 92 | compressed = [] # [truelen, (truelenaux), falselen, (falselenaux)] 93 | 94 | prev_start = 0 95 | for start, count in normal: 96 | truelen = start - prev_start 97 | falselen = count 98 | prev_start = start + count 99 | 100 | assert truelen < 0x8000 and falselen < 0x8000 101 | entry = [] 102 | if truelen > 0x7f: 103 | entry.append(0x80 | (truelen >> 8)) 104 | entry.append(truelen & 0xff) 105 | else: 106 | entry.append(truelen & 0x7f) 107 | if falselen > 0x7f: 108 | entry.append(0x80 | (falselen >> 8)) 109 | entry.append(falselen & 0xff) 110 | else: 111 | entry.append(falselen & 0x7f) 112 | 113 | compressed.append(entry) 114 | 115 | return compressed 116 | 117 | def print_singletons(uppers, lowers, uppersname, lowersname): 118 | print(" static constexpr singleton {}[] = {{".format(uppersname)) 119 | for u, c in uppers: 120 | print(" {{{:#04x}, {}}},".format(u, c)) 121 | print(" };") 122 | print(" static constexpr unsigned char {}[] = {{".format(lowersname)) 123 | for i in range(0, len(lowers), 8): 124 | print(" {}".format(" ".join("{:#04x},".format(l) for l in lowers[i:i+8]))) 125 | print(" };") 126 | 127 | def print_normal(normal, normalname): 128 | print(" static constexpr unsigned char {}[] = {{".format(normalname)) 129 | for v in normal: 130 | print(" {}".format(" ".join("{:#04x},".format(i) for i in v))) 131 | print(" };") 132 | 133 | def main(): 134 | file = get_file("https://www.unicode.org/Public/UNIDATA/UnicodeData.txt") 135 | 136 | codepoints = get_codepoints(file) 137 | 138 | CUTOFF=0x10000 139 | singletons0 = [] 140 | singletons1 = [] 141 | normal0 = [] 142 | normal1 = [] 143 | extra = [] 144 | 145 | for a, b in to_ranges(get_escaped(codepoints)): 146 | if a > 2 * CUTOFF: 147 | extra.append((a, b - a)) 148 | elif a == b - 1: 149 | if a & CUTOFF: 150 | singletons1.append(a & ~CUTOFF) 151 | else: 152 | singletons0.append(a) 153 | elif a == b - 2: 154 | if a & CUTOFF: 155 | singletons1.append(a & ~CUTOFF) 156 | singletons1.append((a + 1) & ~CUTOFF) 157 | else: 158 | singletons0.append(a) 159 | singletons0.append(a + 1) 160 | else: 161 | if a >= 2 * CUTOFF: 162 | extra.append((a, b - a)) 163 | elif a & CUTOFF: 164 | normal1.append((a & ~CUTOFF, b - a)) 165 | else: 166 | normal0.append((a, b - a)) 167 | 168 | singletons0u, singletons0l = compress_singletons(singletons0) 169 | singletons1u, singletons1l = compress_singletons(singletons1) 170 | normal0 = compress_normal(normal0) 171 | normal1 = compress_normal(normal1) 172 | 173 | print("""\ 174 | FMT_FUNC auto is_printable(uint32_t cp) -> bool {\ 175 | """) 176 | print_singletons(singletons0u, singletons0l, 'singletons0', 'singletons0_lower') 177 | print_singletons(singletons1u, singletons1l, 'singletons1', 'singletons1_lower') 178 | print_normal(normal0, 'normal0') 179 | print_normal(normal1, 'normal1') 180 | print("""\ 181 | auto lower = static_cast(cp); 182 | if (cp < 0x10000) { 183 | return is_printable(lower, singletons0, 184 | sizeof(singletons0) / sizeof(*singletons0), 185 | singletons0_lower, normal0, sizeof(normal0)); 186 | } 187 | if (cp < 0x20000) { 188 | return is_printable(lower, singletons1, 189 | sizeof(singletons1) / sizeof(*singletons1), 190 | singletons1_lower, normal1, sizeof(normal1)); 191 | }\ 192 | """) 193 | for a, b in extra: 194 | print(" if (0x{:x} <= cp && cp < 0x{:x}) return false;".format(a, a + b)) 195 | print("""\ 196 | return cp < 0x{:x}; 197 | }}\ 198 | """.format(NUM_CODEPOINTS)) 199 | 200 | if __name__ == '__main__': 201 | main() 202 | -------------------------------------------------------------------------------- /support/python/mkdocstrings_handlers/cxx/templates/README: -------------------------------------------------------------------------------- 1 | mkdocsstrings requires a handler to have a templates directory. 2 | -------------------------------------------------------------------------------- /support/release.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Make a release. 4 | 5 | Usage: 6 | release.py [] 7 | 8 | For the release command $FMT_TOKEN should contain a GitHub personal access token 9 | obtained from https://github.com/settings/tokens. 10 | """ 11 | 12 | from __future__ import print_function 13 | import datetime, docopt, errno, fileinput, json, os 14 | import re, shutil, sys 15 | from subprocess import check_call 16 | import urllib.request 17 | 18 | 19 | class Git: 20 | def __init__(self, dir): 21 | self.dir = dir 22 | 23 | def call(self, method, args, **kwargs): 24 | return check_call(['git', method] + list(args), **kwargs) 25 | 26 | def add(self, *args): 27 | return self.call('add', args, cwd=self.dir) 28 | 29 | def checkout(self, *args): 30 | return self.call('checkout', args, cwd=self.dir) 31 | 32 | def clean(self, *args): 33 | return self.call('clean', args, cwd=self.dir) 34 | 35 | def clone(self, *args): 36 | return self.call('clone', list(args) + [self.dir]) 37 | 38 | def commit(self, *args): 39 | return self.call('commit', args, cwd=self.dir) 40 | 41 | def pull(self, *args): 42 | return self.call('pull', args, cwd=self.dir) 43 | 44 | def push(self, *args): 45 | return self.call('push', args, cwd=self.dir) 46 | 47 | def reset(self, *args): 48 | return self.call('reset', args, cwd=self.dir) 49 | 50 | def update(self, *args): 51 | clone = not os.path.exists(self.dir) 52 | if clone: 53 | self.clone(*args) 54 | return clone 55 | 56 | 57 | def clean_checkout(repo, branch): 58 | repo.clean('-f', '-d') 59 | repo.reset('--hard') 60 | repo.checkout(branch) 61 | 62 | 63 | class Runner: 64 | def __init__(self, cwd): 65 | self.cwd = cwd 66 | 67 | def __call__(self, *args, **kwargs): 68 | kwargs['cwd'] = kwargs.get('cwd', self.cwd) 69 | check_call(args, **kwargs) 70 | 71 | 72 | def create_build_env(): 73 | """Create a build environment.""" 74 | class Env: 75 | pass 76 | env = Env() 77 | env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 78 | env.build_dir = 'build' 79 | env.fmt_repo = Git(os.path.join(env.build_dir, 'fmt')) 80 | return env 81 | 82 | 83 | if __name__ == '__main__': 84 | args = docopt.docopt(__doc__) 85 | env = create_build_env() 86 | fmt_repo = env.fmt_repo 87 | 88 | branch = args.get('') 89 | if branch is None: 90 | branch = 'master' 91 | if not fmt_repo.update('-b', branch, 'git@github.com:fmtlib/fmt'): 92 | clean_checkout(fmt_repo, branch) 93 | 94 | # Update the date in the changelog and extract the version and the first 95 | # section content. 96 | changelog = 'ChangeLog.md' 97 | changelog_path = os.path.join(fmt_repo.dir, changelog) 98 | is_first_section = True 99 | first_section = [] 100 | for i, line in enumerate(fileinput.input(changelog_path, inplace=True)): 101 | if i == 0: 102 | version = re.match(r'# (.*) - TBD', line).group(1) 103 | line = '# {} - {}\n'.format( 104 | version, datetime.date.today().isoformat()) 105 | elif not is_first_section: 106 | pass 107 | elif line.startswith('#'): 108 | is_first_section = False 109 | else: 110 | first_section.append(line) 111 | sys.stdout.write(line) 112 | if first_section[0] == '\n': 113 | first_section.pop(0) 114 | 115 | ns_version = None 116 | base_h_path = os.path.join(fmt_repo.dir, 'include', 'fmt', 'base.h') 117 | for line in fileinput.input(base_h_path): 118 | m = re.match(r'\s*inline namespace v(.*) .*', line) 119 | if m: 120 | ns_version = m.group(1) 121 | break 122 | major_version = version.split('.')[0] 123 | if not ns_version or ns_version != major_version: 124 | raise Exception(f'Version mismatch {ns_version} != {major_version}') 125 | 126 | # Workaround GitHub-flavored Markdown treating newlines as
. 127 | changes = '' 128 | code_block = False 129 | stripped = False 130 | for line in first_section: 131 | if re.match(r'^\s*```', line): 132 | code_block = not code_block 133 | changes += line 134 | stripped = False 135 | continue 136 | if code_block: 137 | changes += line 138 | continue 139 | if line == '\n' or re.match(r'^\s*\|.*', line): 140 | if stripped: 141 | changes += '\n' 142 | stripped = False 143 | changes += line 144 | continue 145 | if stripped: 146 | line = ' ' + line.lstrip() 147 | changes += line.rstrip() 148 | stripped = True 149 | 150 | fmt_repo.checkout('-B', 'release') 151 | fmt_repo.add(changelog) 152 | fmt_repo.commit('-m', 'Update version') 153 | 154 | # Build the docs and package. 155 | run = Runner(fmt_repo.dir) 156 | run('cmake', '.') 157 | run('make', 'doc', 'package_source') 158 | 159 | # Create a release on GitHub. 160 | fmt_repo.push('origin', 'release') 161 | auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')} 162 | req = urllib.request.Request( 163 | 'https://api.github.com/repos/fmtlib/fmt/releases', 164 | data=json.dumps({'tag_name': version, 165 | 'target_commitish': 'release', 166 | 'body': changes, 'draft': True}).encode('utf-8'), 167 | headers=auth_headers, method='POST') 168 | with urllib.request.urlopen(req) as response: 169 | if response.status != 201: 170 | raise Exception(f'Failed to create a release ' + 171 | '{response.status} {response.reason}') 172 | response_data = json.loads(response.read().decode('utf-8')) 173 | id = response_data['id'] 174 | 175 | # Upload the package. 176 | uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases' 177 | package = 'fmt-{}.zip'.format(version) 178 | req = urllib.request.Request( 179 | f'{uploads_url}/{id}/assets?name={package}', 180 | headers={'Content-Type': 'application/zip'} | auth_headers, 181 | data=open('build/fmt/' + package, 'rb').read(), method='POST') 182 | with urllib.request.urlopen(req) as response: 183 | if response.status != 201: 184 | raise Exception(f'Failed to upload an asset ' 185 | '{response.status} {response.reason}') 186 | 187 | short_version = '.'.join(version.split('.')[:-1]) 188 | check_call(['./mkdocs', 'deploy', short_version]) 189 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(gtest) 2 | 3 | include(CheckSymbolExists) 4 | 5 | set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc) 6 | add_library(test-main STATIC ${TEST_MAIN_SRC}) 7 | target_include_directories(test-main PUBLIC 8 | $) 9 | target_link_libraries(test-main gtest fmt) 10 | 11 | # Adds a test. 12 | # Usage: add_fmt_test(name srcs...) 13 | function(add_fmt_test name) 14 | cmake_parse_arguments(ADD_FMT_TEST "HEADER_ONLY;MODULE" "" "" ${ARGN}) 15 | 16 | set(sources ${name}.cc ${ADD_FMT_TEST_UNPARSED_ARGUMENTS}) 17 | if (ADD_FMT_TEST_HEADER_ONLY) 18 | set(sources ${sources} ${TEST_MAIN_SRC} ../src/os.cc) 19 | set(libs gtest fmt-header-only) 20 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 21 | set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables) 22 | endif () 23 | elseif (ADD_FMT_TEST_MODULE) 24 | set(libs test-main test-module) 25 | set_source_files_properties(${name}.cc PROPERTIES OBJECT_DEPENDS test-module) 26 | else () 27 | set(libs test-main fmt) 28 | endif () 29 | add_executable(${name} ${sources}) 30 | target_link_libraries(${name} ${libs}) 31 | 32 | if (ADD_FMT_TEST_HEADER_ONLY AND NOT FMT_UNICODE) 33 | target_compile_definitions(${name} PUBLIC FMT_UNICODE=0) 34 | endif () 35 | 36 | # Define if certain C++ features can be used. 37 | if (FMT_PEDANTIC) 38 | target_compile_options(${name} PRIVATE ${PEDANTIC_COMPILE_FLAGS}) 39 | endif () 40 | if (FMT_WERROR) 41 | target_compile_options(${name} PRIVATE ${WERROR_FLAG}) 42 | endif () 43 | add_test(NAME ${name} COMMAND ${name}) 44 | endfunction() 45 | 46 | if (FMT_MODULE) 47 | return () 48 | endif () 49 | 50 | add_fmt_test(args-test) 51 | add_fmt_test(base-test) 52 | add_fmt_test(assert-test) 53 | add_fmt_test(chrono-test) 54 | add_fmt_test(color-test) 55 | add_fmt_test(gtest-extra-test) 56 | add_fmt_test(format-test mock-allocator.h) 57 | if (MSVC) 58 | target_compile_options(format-test PRIVATE /bigobj) 59 | endif () 60 | if (NOT (MSVC AND BUILD_SHARED_LIBS)) 61 | add_fmt_test(format-impl-test HEADER_ONLY header-only-test.cc) 62 | endif () 63 | add_fmt_test(ostream-test) 64 | add_fmt_test(compile-test) 65 | add_fmt_test(compile-fp-test) 66 | if (MSVC) 67 | # Without this option, MSVC returns 199711L for the __cplusplus macro. 68 | target_compile_options(compile-fp-test PRIVATE /Zc:__cplusplus) 69 | endif() 70 | add_fmt_test(printf-test) 71 | add_fmt_test(ranges-test ranges-odr-test.cc) 72 | add_fmt_test(no-builtin-types-test HEADER_ONLY) 73 | 74 | add_fmt_test(scan-test HEADER_ONLY) 75 | check_symbol_exists(strptime "time.h" HAVE_STRPTIME) 76 | if (HAVE_STRPTIME) 77 | target_compile_definitions(scan-test PRIVATE FMT_HAVE_STRPTIME) 78 | endif () 79 | 80 | add_fmt_test(std-test) 81 | try_compile(compile_result_unused 82 | ${CMAKE_CURRENT_BINARY_DIR} 83 | SOURCES ${CMAKE_CURRENT_LIST_DIR}/detect-stdfs.cc 84 | OUTPUT_VARIABLE RAWOUTPUT) 85 | string(REGEX REPLACE ".*libfound \"([^\"]*)\".*" "\\1" STDLIBFS "${RAWOUTPUT}") 86 | if (STDLIBFS) 87 | target_link_libraries(std-test ${STDLIBFS}) 88 | endif () 89 | add_fmt_test(unicode-test HEADER_ONLY) 90 | if (MSVC) 91 | target_compile_options(unicode-test PRIVATE /utf-8) 92 | endif () 93 | add_fmt_test(xchar-test) 94 | add_fmt_test(enforce-checks-test) 95 | target_compile_definitions(enforce-checks-test PRIVATE 96 | -DFMT_ENFORCE_COMPILE_STRING) 97 | 98 | add_executable(perf-sanity perf-sanity.cc) 99 | target_link_libraries(perf-sanity fmt::fmt) 100 | 101 | if (FMT_MODULE) 102 | # The tests need {fmt} to be compiled as traditional library 103 | # because of visibility of implementation details. 104 | # If module support is present the module tests require a 105 | # test-only module to be built from {fmt} 106 | add_library(test-module OBJECT ${CMAKE_SOURCE_DIR}/src/fmt.cc) 107 | target_compile_features(test-module PUBLIC cxx_std_11) 108 | target_include_directories(test-module PUBLIC 109 | $) 110 | enable_module(test-module) 111 | 112 | add_fmt_test(module-test MODULE test-main.cc) 113 | if (MSVC) 114 | target_compile_options(test-module PRIVATE /utf-8 /Zc:__cplusplus 115 | /Zc:externConstexpr /Zc:inline) 116 | target_compile_options(module-test PRIVATE /utf-8 /Zc:__cplusplus 117 | /Zc:externConstexpr /Zc:inline) 118 | endif () 119 | endif () 120 | 121 | if (NOT DEFINED MSVC_STATIC_RUNTIME AND MSVC) 122 | foreach (flag_var 123 | CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE 124 | CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) 125 | if (${flag_var} MATCHES "^(/|-)(MT|MTd)") 126 | set(MSVC_STATIC_RUNTIME ON) 127 | break() 128 | endif() 129 | endforeach() 130 | endif() 131 | 132 | if (NOT MSVC_STATIC_RUNTIME) 133 | add_executable(posix-mock-test 134 | posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC}) 135 | target_include_directories( 136 | posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR}/include) 137 | target_link_libraries(posix-mock-test gtest) 138 | if (FMT_PEDANTIC) 139 | target_compile_options(posix-mock-test PRIVATE ${PEDANTIC_COMPILE_FLAGS}) 140 | endif () 141 | if (MSVC) 142 | target_compile_options(posix-mock-test PRIVATE /utf-8) 143 | endif () 144 | add_test(NAME posix-mock-test COMMAND posix-mock-test) 145 | add_fmt_test(os-test) 146 | endif () 147 | 148 | message(STATUS "FMT_PEDANTIC: ${FMT_PEDANTIC}") 149 | 150 | if (FMT_PEDANTIC) 151 | # Test that the library can be compiled with exceptions disabled. 152 | # -fno-exception is broken in icc: https://github.com/fmtlib/fmt/issues/822. 153 | if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Intel") 154 | check_cxx_compiler_flag(-fno-exceptions HAVE_FNO_EXCEPTIONS_FLAG) 155 | endif () 156 | if (HAVE_FNO_EXCEPTIONS_FLAG) 157 | add_library(noexception-test ../src/format.cc noexception-test.cc) 158 | target_include_directories( 159 | noexception-test PRIVATE ${PROJECT_SOURCE_DIR}/include) 160 | target_compile_options(noexception-test PRIVATE -fno-exceptions) 161 | target_compile_options(noexception-test PRIVATE ${PEDANTIC_COMPILE_FLAGS}) 162 | endif () 163 | 164 | # Test that the library compiles without locale. 165 | add_library(nolocale-test ../src/format.cc) 166 | target_include_directories( 167 | nolocale-test PRIVATE ${PROJECT_SOURCE_DIR}/include) 168 | target_compile_definitions( 169 | nolocale-test PRIVATE FMT_STATIC_THOUSANDS_SEPARATOR=1) 170 | endif () 171 | 172 | # These tests are disabled on Windows because they take too long. 173 | # They are disabled on GCC < 4.9 because it can not parse UDLs without 174 | # a space after `operator""` but that is an incorrect syntax for any more 175 | # modern compiler. 176 | if (FMT_PEDANTIC AND NOT WIN32 AND NOT ( 177 | CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND 178 | CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9)) 179 | # Test if incorrect API usages produce compilation error. 180 | add_test(compile-error-test ${CMAKE_CTEST_COMMAND} 181 | --build-and-test 182 | "${CMAKE_CURRENT_SOURCE_DIR}/compile-error-test" 183 | "${CMAKE_CURRENT_BINARY_DIR}/compile-error-test" 184 | --build-generator ${CMAKE_GENERATOR} 185 | --build-makeprogram ${CMAKE_MAKE_PROGRAM} 186 | --build-options 187 | "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" 188 | "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" 189 | "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" 190 | "-DCXX_STANDARD_FLAG=${CXX_STANDARD_FLAG}" 191 | "-DFMT_DIR=${CMAKE_SOURCE_DIR}") 192 | 193 | # Test if the targets are found from the build directory. 194 | add_test(find-package-test ${CMAKE_CTEST_COMMAND} 195 | -C ${CMAKE_BUILD_TYPE} 196 | --build-and-test 197 | "${CMAKE_CURRENT_SOURCE_DIR}/find-package-test" 198 | "${CMAKE_CURRENT_BINARY_DIR}/find-package-test" 199 | --build-generator ${CMAKE_GENERATOR} 200 | --build-makeprogram ${CMAKE_MAKE_PROGRAM} 201 | --build-options 202 | "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" 203 | "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" 204 | "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" 205 | "-DFMT_DIR=${PROJECT_BINARY_DIR}" 206 | "-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}" 207 | "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") 208 | 209 | # Test if the targets are found when add_subdirectory is used. 210 | add_test(add-subdirectory-test ${CMAKE_CTEST_COMMAND} 211 | -C ${CMAKE_BUILD_TYPE} 212 | --build-and-test 213 | "${CMAKE_CURRENT_SOURCE_DIR}/add-subdirectory-test" 214 | "${CMAKE_CURRENT_BINARY_DIR}/add-subdirectory-test" 215 | --build-generator ${CMAKE_GENERATOR} 216 | --build-makeprogram ${CMAKE_MAKE_PROGRAM} 217 | --build-options 218 | "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" 219 | "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" 220 | "-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}" 221 | "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") 222 | endif () 223 | 224 | # This test is disabled on Windows because it is POSIX-specific. 225 | if (FMT_PEDANTIC AND NOT WIN32) 226 | add_test(static-export-test ${CMAKE_CTEST_COMMAND} 227 | -C ${CMAKE_BUILD_TYPE} 228 | --build-and-test 229 | "${CMAKE_CURRENT_SOURCE_DIR}/static-export-test" 230 | "${CMAKE_CURRENT_BINARY_DIR}/static-export-test" 231 | --build-generator ${CMAKE_GENERATOR} 232 | --build-makeprogram ${CMAKE_MAKE_PROGRAM} 233 | --build-options 234 | "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" 235 | "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" 236 | "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") 237 | endif () 238 | 239 | # Activate optional CUDA tests if CUDA is found. For version selection see 240 | # https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features 241 | if (FMT_CUDA_TEST) 242 | if (${CMAKE_VERSION} VERSION_LESS 3.15) 243 | find_package(CUDA 9.0) 244 | else () 245 | include(CheckLanguage) 246 | check_language(CUDA) 247 | if (CMAKE_CUDA_COMPILER) 248 | enable_language(CUDA OPTIONAL) 249 | set(CUDA_FOUND TRUE) 250 | endif () 251 | endif () 252 | 253 | if (CUDA_FOUND) 254 | add_subdirectory(cuda-test) 255 | add_test(NAME cuda-test COMMAND fmt-in-cuda-test) 256 | endif () 257 | endif () 258 | -------------------------------------------------------------------------------- /test/add-subdirectory-test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8...3.25) 2 | 3 | project(fmt-test CXX) 4 | 5 | add_subdirectory(../.. fmt) 6 | 7 | add_executable(library-test main.cc) 8 | target_include_directories(library-test PUBLIC SYSTEM .) 9 | target_compile_options(library-test PRIVATE ${PEDANTIC_COMPILE_FLAGS}) 10 | target_link_libraries(library-test fmt::fmt) 11 | 12 | if (TARGET fmt::fmt-header-only) 13 | add_executable(header-only-test main.cc) 14 | target_include_directories(header-only-test PUBLIC SYSTEM .) 15 | target_compile_options(header-only-test PRIVATE ${PEDANTIC_COMPILE_FLAGS}) 16 | target_link_libraries(header-only-test fmt::fmt-header-only) 17 | endif () 18 | -------------------------------------------------------------------------------- /test/add-subdirectory-test/main.cc: -------------------------------------------------------------------------------- 1 | #include "fmt/base.h" 2 | 3 | int main(int argc, char** argv) { 4 | for (int i = 0; i < argc; ++i) fmt::print("{}: {}\n", i, argv[i]); 5 | } 6 | -------------------------------------------------------------------------------- /test/args-test.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - dynamic argument store tests 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include "fmt/args.h" 9 | 10 | #include 11 | 12 | #include "gtest/gtest.h" 13 | 14 | TEST(args_test, basic) { 15 | fmt::dynamic_format_arg_store store; 16 | store.push_back(42); 17 | store.push_back("abc1"); 18 | store.push_back(1.5f); 19 | EXPECT_EQ("42 and abc1 and 1.5", fmt::vformat("{} and {} and {}", store)); 20 | } 21 | 22 | TEST(args_test, strings_and_refs) { 23 | // Unfortunately the tests are compiled with old ABI so strings use COW. 24 | fmt::dynamic_format_arg_store store; 25 | char str[] = "1234567890"; 26 | store.push_back(str); 27 | store.push_back(std::cref(str)); 28 | store.push_back(fmt::string_view{str}); 29 | str[0] = 'X'; 30 | 31 | auto result = fmt::vformat("{} and {} and {}", store); 32 | EXPECT_EQ("1234567890 and X234567890 and X234567890", result); 33 | } 34 | 35 | struct custom_type { 36 | int i = 0; 37 | }; 38 | 39 | FMT_BEGIN_NAMESPACE 40 | template <> struct formatter { 41 | auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { 42 | return ctx.begin(); 43 | } 44 | 45 | template 46 | auto format(const custom_type& p, FormatContext& ctx) const 47 | -> decltype(ctx.out()) { 48 | return fmt::format_to(ctx.out(), "cust={}", p.i); 49 | } 50 | }; 51 | FMT_END_NAMESPACE 52 | 53 | TEST(args_test, custom_format) { 54 | fmt::dynamic_format_arg_store store; 55 | auto c = custom_type(); 56 | store.push_back(c); 57 | ++c.i; 58 | store.push_back(c); 59 | ++c.i; 60 | store.push_back(std::cref(c)); 61 | ++c.i; 62 | auto result = fmt::vformat("{} and {} and {}", store); 63 | EXPECT_EQ("cust=0 and cust=1 and cust=3", result); 64 | } 65 | 66 | struct to_stringable { 67 | friend fmt::string_view to_string_view(to_stringable) { return {}; } 68 | }; 69 | 70 | FMT_BEGIN_NAMESPACE 71 | template <> struct formatter { 72 | auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { 73 | return ctx.begin(); 74 | } 75 | 76 | auto format(to_stringable, format_context& ctx) const -> decltype(ctx.out()) { 77 | return ctx.out(); 78 | } 79 | }; 80 | FMT_END_NAMESPACE 81 | 82 | TEST(args_test, to_string_and_formatter) { 83 | fmt::dynamic_format_arg_store store; 84 | auto s = to_stringable(); 85 | store.push_back(s); 86 | store.push_back(std::cref(s)); 87 | fmt::vformat("", store); 88 | } 89 | 90 | TEST(args_test, named_int) { 91 | fmt::dynamic_format_arg_store store; 92 | store.push_back(fmt::arg("a1", 42)); 93 | EXPECT_EQ("42", fmt::vformat("{a1}", store)); 94 | } 95 | 96 | TEST(args_test, named_strings) { 97 | fmt::dynamic_format_arg_store store; 98 | char str[] = "1234567890"; 99 | store.push_back(fmt::arg("a1", str)); 100 | store.push_back(fmt::arg("a2", std::cref(str))); 101 | str[0] = 'X'; 102 | EXPECT_EQ("1234567890 and X234567890", fmt::vformat("{a1} and {a2}", store)); 103 | } 104 | 105 | TEST(args_test, named_arg_by_ref) { 106 | fmt::dynamic_format_arg_store store; 107 | char band[] = "Rolling Stones"; 108 | store.push_back(fmt::arg("band", std::cref(band))); 109 | band[9] = 'c'; // Changing band affects the output. 110 | EXPECT_EQ(fmt::vformat("{band}", store), "Rolling Scones"); 111 | } 112 | 113 | TEST(args_test, named_custom_format) { 114 | fmt::dynamic_format_arg_store store; 115 | auto c = custom_type(); 116 | store.push_back(fmt::arg("c1", c)); 117 | ++c.i; 118 | store.push_back(fmt::arg("c2", c)); 119 | ++c.i; 120 | store.push_back(fmt::arg("c_ref", std::cref(c))); 121 | ++c.i; 122 | auto result = fmt::vformat("{c1} and {c2} and {c_ref}", store); 123 | EXPECT_EQ("cust=0 and cust=1 and cust=3", result); 124 | } 125 | 126 | TEST(args_test, clear) { 127 | fmt::dynamic_format_arg_store store; 128 | store.push_back(42); 129 | 130 | auto result = fmt::vformat("{}", store); 131 | EXPECT_EQ("42", result); 132 | 133 | store.push_back(43); 134 | result = fmt::vformat("{} and {}", store); 135 | EXPECT_EQ("42 and 43", result); 136 | 137 | store.clear(); 138 | store.push_back(44); 139 | result = fmt::vformat("{}", store); 140 | EXPECT_EQ("44", result); 141 | } 142 | 143 | TEST(args_test, reserve) { 144 | fmt::dynamic_format_arg_store store; 145 | store.reserve(2, 1); 146 | store.push_back(1.5f); 147 | store.push_back(fmt::arg("a", 42)); 148 | auto result = fmt::vformat("{} and {a}", store); 149 | EXPECT_EQ("1.5 and 42", result); 150 | } 151 | 152 | struct copy_throwable { 153 | copy_throwable() {} 154 | copy_throwable(const copy_throwable&) { throw "deal with it"; } 155 | }; 156 | 157 | FMT_BEGIN_NAMESPACE 158 | template <> struct formatter { 159 | auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { 160 | return ctx.begin(); 161 | } 162 | auto format(copy_throwable, format_context& ctx) const 163 | -> decltype(ctx.out()) { 164 | return ctx.out(); 165 | } 166 | }; 167 | FMT_END_NAMESPACE 168 | 169 | TEST(args_test, throw_on_copy) { 170 | fmt::dynamic_format_arg_store store; 171 | store.push_back(std::string("foo")); 172 | try { 173 | store.push_back(copy_throwable()); 174 | } catch (...) { 175 | } 176 | EXPECT_EQ(fmt::vformat("{}", store), "foo"); 177 | } 178 | 179 | TEST(args_test, move_constructor) { 180 | using store_type = fmt::dynamic_format_arg_store; 181 | auto store = std::unique_ptr(new store_type()); 182 | store->push_back(42); 183 | store->push_back(std::string("foo")); 184 | store->push_back(fmt::arg("a1", "foo")); 185 | auto moved_store = std::move(*store); 186 | store.reset(); 187 | EXPECT_EQ(fmt::vformat("{} {} {a1}", moved_store), "42 foo foo"); 188 | } 189 | 190 | TEST(args_test, size) { 191 | fmt::dynamic_format_arg_store store; 192 | EXPECT_EQ(store.size(), 0); 193 | 194 | store.push_back(42); 195 | EXPECT_EQ(store.size(), 1); 196 | 197 | store.push_back("Molybdenum"); 198 | EXPECT_EQ(store.size(), 2); 199 | 200 | store.clear(); 201 | EXPECT_EQ(store.size(), 0); 202 | } 203 | -------------------------------------------------------------------------------- /test/assert-test.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - FMT_ASSERT test 2 | // 3 | // It is a separate test to minimize the number of EXPECT_DEBUG_DEATH checks 4 | // which are slow on some platforms. In other tests FMT_ASSERT is made to throw 5 | // an exception which is much faster and easier to check. 6 | // 7 | // Copyright (c) 2012 - present, Victor Zverovich 8 | // All rights reserved. 9 | // 10 | // For the license information refer to format.h. 11 | 12 | #include "fmt/base.h" 13 | #include "gtest/gtest.h" 14 | 15 | TEST(assert_test, fail) { 16 | #if GTEST_HAS_DEATH_TEST 17 | EXPECT_DEBUG_DEATH(FMT_ASSERT(false, "don't panic!"), "don't panic!"); 18 | #else 19 | fmt::print("warning: death tests are not supported\n"); 20 | #endif 21 | } 22 | 23 | TEST(assert_test, dangling_else) { 24 | bool test_condition = false; 25 | bool executed_else = false; 26 | if (test_condition) 27 | FMT_ASSERT(true, ""); 28 | else 29 | executed_else = true; 30 | EXPECT_TRUE(executed_else); 31 | } 32 | -------------------------------------------------------------------------------- /test/color-test.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - color tests 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include "fmt/color.h" 9 | 10 | #include // std::back_inserter 11 | 12 | #include "gtest-extra.h" // EXPECT_WRITE, EXPECT_THROW_MSG 13 | 14 | TEST(color_test, text_style) { 15 | EXPECT_FALSE(fmt::text_style().has_foreground()); 16 | EXPECT_FALSE(fmt::text_style().has_background()); 17 | EXPECT_FALSE(fmt::text_style().has_emphasis()); 18 | 19 | EXPECT_TRUE(fg(fmt::rgb(0)).has_foreground()); 20 | EXPECT_FALSE(fg(fmt::rgb(0)).has_background()); 21 | EXPECT_FALSE(fg(fmt::rgb(0)).has_emphasis()); 22 | EXPECT_TRUE(bg(fmt::rgb(0)).has_background()); 23 | EXPECT_FALSE(bg(fmt::rgb(0)).has_foreground()); 24 | EXPECT_FALSE(bg(fmt::rgb(0)).has_emphasis()); 25 | 26 | EXPECT_TRUE( 27 | (fg(fmt::rgb(0xFFFFFF)) | bg(fmt::rgb(0xFFFFFF))).has_foreground()); 28 | EXPECT_TRUE( 29 | (fg(fmt::rgb(0xFFFFFF)) | bg(fmt::rgb(0xFFFFFF))).has_background()); 30 | EXPECT_FALSE( 31 | (fg(fmt::rgb(0xFFFFFF)) | bg(fmt::rgb(0xFFFFFF))).has_emphasis()); 32 | 33 | EXPECT_EQ(fg(fmt::rgb(0x000000)) | fg(fmt::rgb(0x000000)), 34 | fg(fmt::rgb(0x000000))); 35 | EXPECT_EQ(fg(fmt::rgb(0x00000F)) | fg(fmt::rgb(0x00000F)), 36 | fg(fmt::rgb(0x00000F))); 37 | EXPECT_EQ(fg(fmt::rgb(0xC0F000)) | fg(fmt::rgb(0x000FEE)), 38 | fg(fmt::rgb(0xC0FFEE))); 39 | 40 | EXPECT_THROW_MSG( 41 | fg(fmt::terminal_color::black) | fg(fmt::terminal_color::black), 42 | fmt::format_error, "can't OR a terminal color"); 43 | EXPECT_THROW_MSG( 44 | fg(fmt::terminal_color::black) | fg(fmt::terminal_color::white), 45 | fmt::format_error, "can't OR a terminal color"); 46 | EXPECT_THROW_MSG( 47 | bg(fmt::terminal_color::black) | bg(fmt::terminal_color::black), 48 | fmt::format_error, "can't OR a terminal color"); 49 | EXPECT_THROW_MSG( 50 | bg(fmt::terminal_color::black) | bg(fmt::terminal_color::white), 51 | fmt::format_error, "can't OR a terminal color"); 52 | EXPECT_THROW_MSG(fg(fmt::terminal_color::black) | fg(fmt::color::black), 53 | fmt::format_error, "can't OR a terminal color"); 54 | EXPECT_THROW_MSG(bg(fmt::terminal_color::black) | bg(fmt::color::black), 55 | fmt::format_error, "can't OR a terminal color"); 56 | 57 | EXPECT_NO_THROW(fg(fmt::terminal_color::white) | 58 | bg(fmt::terminal_color::white)); 59 | EXPECT_NO_THROW(fg(fmt::terminal_color::white) | bg(fmt::rgb(0xFFFFFF))); 60 | EXPECT_NO_THROW(fg(fmt::terminal_color::white) | fmt::text_style()); 61 | EXPECT_NO_THROW(bg(fmt::terminal_color::white) | fmt::text_style()); 62 | } 63 | 64 | TEST(color_test, format) { 65 | EXPECT_EQ(fmt::format(fmt::text_style(), "no style"), "no style"); 66 | EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"), 67 | "\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m"); 68 | EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 0, 0)) | fg(fmt::rgb(0, 20, 30)), 69 | "rgb(255,20,30)"), 70 | "\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m"); 71 | EXPECT_EQ( 72 | fmt::format(fg(fmt::rgb(0, 0, 0)) | fg(fmt::rgb(0, 0, 0)), "rgb(0,0,0)"), 73 | "\x1b[38;2;000;000;000mrgb(0,0,0)\x1b[0m"); 74 | EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"), 75 | "\x1b[38;2;000;000;255mblue\x1b[0m"); 76 | EXPECT_EQ( 77 | fmt::format(fg(fmt::color::blue) | bg(fmt::color::red), "two color"), 78 | "\x1b[38;2;000;000;255m\x1b[48;2;255;000;000mtwo color\x1b[0m"); 79 | EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold"), "\x1b[1mbold\x1b[0m"); 80 | EXPECT_EQ(fmt::format(fmt::emphasis::faint, "faint"), "\x1b[2mfaint\x1b[0m"); 81 | EXPECT_EQ(fmt::format(fmt::emphasis::italic, "italic"), 82 | "\x1b[3mitalic\x1b[0m"); 83 | EXPECT_EQ(fmt::format(fmt::emphasis::underline, "underline"), 84 | "\x1b[4munderline\x1b[0m"); 85 | EXPECT_EQ(fmt::format(fmt::emphasis::blink, "blink"), "\x1b[5mblink\x1b[0m"); 86 | EXPECT_EQ(fmt::format(fmt::emphasis::reverse, "reverse"), 87 | "\x1b[7mreverse\x1b[0m"); 88 | EXPECT_EQ(fmt::format(fmt::emphasis::conceal, "conceal"), 89 | "\x1b[8mconceal\x1b[0m"); 90 | EXPECT_EQ(fmt::format(fmt::emphasis::strikethrough, "strikethrough"), 91 | "\x1b[9mstrikethrough\x1b[0m"); 92 | EXPECT_EQ( 93 | fmt::format(fg(fmt::color::blue) | fmt::emphasis::bold, "blue/bold"), 94 | "\x1b[1m\x1b[38;2;000;000;255mblue/bold\x1b[0m"); 95 | EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold error"), 96 | "\x1b[1mbold error\x1b[0m"); 97 | EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue log"), 98 | "\x1b[38;2;000;000;255mblue log\x1b[0m"); 99 | EXPECT_EQ(fmt::format(fmt::text_style(), "hi"), "hi"); 100 | EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "tred"), 101 | "\x1b[31mtred\x1b[0m"); 102 | EXPECT_EQ(fmt::format(bg(fmt::terminal_color::cyan), "tcyan"), 103 | "\x1b[46mtcyan\x1b[0m"); 104 | EXPECT_EQ(fmt::format(fg(fmt::terminal_color::bright_green), "tbgreen"), 105 | "\x1b[92mtbgreen\x1b[0m"); 106 | EXPECT_EQ(fmt::format(bg(fmt::terminal_color::bright_magenta), "tbmagenta"), 107 | "\x1b[105mtbmagenta\x1b[0m"); 108 | EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "{}", "foo"), 109 | "\x1b[31mfoo\x1b[0m"); 110 | EXPECT_EQ(fmt::format("{}{}", fmt::styled("red", fg(fmt::color::red)), 111 | fmt::styled("bold", fmt::emphasis::bold)), 112 | "\x1b[38;2;255;000;000mred\x1b[0m\x1b[1mbold\x1b[0m"); 113 | EXPECT_EQ(fmt::format("{}", fmt::styled("bar", fg(fmt::color::blue) | 114 | fmt::emphasis::underline)), 115 | "\x1b[4m\x1b[38;2;000;000;255mbar\x1b[0m"); 116 | } 117 | 118 | TEST(color_test, format_to) { 119 | auto out = std::string(); 120 | fmt::format_to(std::back_inserter(out), fg(fmt::rgb(255, 20, 30)), 121 | "rgb(255,20,30){}{}{}", 1, 2, 3); 122 | EXPECT_EQ(fmt::to_string(out), 123 | "\x1b[38;2;255;020;030mrgb(255,20,30)123\x1b[0m"); 124 | } 125 | 126 | TEST(color_test, print) { 127 | EXPECT_WRITE(stdout, fmt::print(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"), 128 | "\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m"); 129 | } 130 | -------------------------------------------------------------------------------- /test/compile-error-test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Test if compile errors are produced where necessary. 2 | 3 | cmake_minimum_required(VERSION 3.8...3.25) 4 | project(compile-error-test CXX) 5 | 6 | set(fmt_headers " 7 | #include 8 | #include 9 | #include 10 | #include 11 | ") 12 | 13 | set(error_test_names "") 14 | set(non_error_test_content "") 15 | 16 | # For error tests (we expect them to produce compilation error): 17 | # * adds a name of test into `error_test_names` list 18 | # * generates a single source file (with the same name) for each test 19 | # For non-error tests (we expect them to compile successfully): 20 | # * adds a code segment as separate function to `non_error_test_content` 21 | function (expect_compile name code_fragment) 22 | cmake_parse_arguments(EXPECT_COMPILE "ERROR" "" "" ${ARGN}) 23 | string(MAKE_C_IDENTIFIER "${name}" test_name) 24 | 25 | if (EXPECT_COMPILE_ERROR) 26 | file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/${test_name}.cc" " 27 | ${fmt_headers} 28 | void ${test_name}() { 29 | ${code_fragment} 30 | } 31 | ") 32 | set(error_test_names_copy "${error_test_names}") 33 | list(APPEND error_test_names_copy "${test_name}") 34 | set(error_test_names "${error_test_names_copy}" PARENT_SCOPE) 35 | else() 36 | set(non_error_test_content " 37 | ${non_error_test_content} 38 | void ${test_name}() { 39 | ${code_fragment} 40 | }" PARENT_SCOPE) 41 | endif() 42 | endfunction () 43 | 44 | # Generates a source file for non-error test with `non_error_test_content` and 45 | # CMake project file with all error and single non-error test targets. 46 | function (run_tests) 47 | set(cmake_targets "") 48 | foreach(test_name IN LISTS error_test_names) 49 | set(cmake_targets " 50 | ${cmake_targets} 51 | add_library(test-${test_name} ${test_name}.cc) 52 | target_link_libraries(test-${test_name} PRIVATE fmt::fmt) 53 | ") 54 | endforeach() 55 | 56 | file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/non_error_test.cc" " 57 | ${fmt_headers} 58 | ${non_error_test_content} 59 | ") 60 | set(cmake_targets " 61 | ${cmake_targets} 62 | add_library(non-error-test non_error_test.cc) 63 | target_link_libraries(non-error-test PRIVATE fmt::fmt) 64 | ") 65 | 66 | file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/CMakeLists.txt" " 67 | cmake_minimum_required(VERSION 3.8...3.25) 68 | project(tests CXX) 69 | add_subdirectory(${FMT_DIR} fmt) 70 | ${cmake_targets} 71 | ") 72 | 73 | set(build_directory "${CMAKE_CURRENT_BINARY_DIR}/test/build") 74 | file(MAKE_DIRECTORY "${build_directory}") 75 | execute_process( 76 | COMMAND 77 | "${CMAKE_COMMAND}" 78 | "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" 79 | "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" 80 | "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" 81 | "-DCMAKE_GENERATOR=${CMAKE_GENERATOR}" 82 | "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}" 83 | "-DFMT_DIR=${FMT_DIR}" 84 | "${CMAKE_CURRENT_BINARY_DIR}/test" 85 | WORKING_DIRECTORY "${build_directory}" 86 | RESULT_VARIABLE result_var 87 | OUTPUT_VARIABLE output_var 88 | ERROR_VARIABLE output_var) 89 | if (NOT result_var EQUAL 0) 90 | message(FATAL_ERROR "Unable to configure:\n${output_var}") 91 | endif() 92 | 93 | foreach(test_name IN LISTS error_test_names) 94 | execute_process( 95 | COMMAND 96 | "${CMAKE_COMMAND}" --build "${build_directory}" --target "test-${test_name}" 97 | WORKING_DIRECTORY "${build_directory}" 98 | RESULT_VARIABLE result_var 99 | OUTPUT_VARIABLE output_var 100 | ERROR_QUIET) 101 | if (result_var EQUAL 0) 102 | message(SEND_ERROR "No compile error for \"${test_name}\":\n${output_var}") 103 | endif () 104 | endforeach() 105 | 106 | execute_process( 107 | COMMAND 108 | "${CMAKE_COMMAND}" --build "${build_directory}" --target "non-error-test" 109 | WORKING_DIRECTORY "${build_directory}" 110 | RESULT_VARIABLE result_var 111 | OUTPUT_VARIABLE output_var 112 | ERROR_VARIABLE output_var) 113 | if (NOT result_var EQUAL 0) 114 | message(SEND_ERROR "Compile error for combined non-error test:\n${output_var}") 115 | endif () 116 | endfunction () 117 | 118 | # Check if the source file skeleton compiles. 119 | expect_compile(check "") 120 | expect_compile(check-error "compilation_error" ERROR) 121 | 122 | # Formatting a wide character with a narrow format string is forbidden. 123 | expect_compile(wide-character-narrow-format-string "fmt::format(L\"{}\", L'a');") 124 | expect_compile(wide-character-narrow-format-string-error "fmt::format(\"{}\", L'a');" ERROR) 125 | 126 | # Formatting a wide string with a narrow format string is forbidden. 127 | expect_compile(wide-string-narrow-format-string "fmt::format(L\"{}\", L\"foo\");") 128 | expect_compile(wide-string-narrow-format-string-error "fmt::format(\"{}\", L\"foo\");" ERROR) 129 | 130 | # Formatting a narrow string with a wide format string is forbidden because 131 | # mixing UTF-8 with UTF-16/32 can result in an invalid output. 132 | expect_compile(narrow-string-wide-format-string "fmt::format(L\"{}\", L\"foo\");") 133 | expect_compile(narrow-string-wide-format-string-error "fmt::format(L\"{}\", \"foo\");" ERROR) 134 | 135 | expect_compile(cast-to-string " 136 | struct S { 137 | operator std::string() const { return std::string(); } 138 | }; 139 | fmt::format(\"{}\", std::string(S())); 140 | ") 141 | expect_compile(cast-to-string-error " 142 | struct S { 143 | operator std::string() const { return std::string(); } 144 | }; 145 | fmt::format(\"{}\", S()); 146 | " ERROR) 147 | 148 | # Formatting a function 149 | expect_compile(format-function " 150 | void (*f)(); 151 | fmt::format(\"{}\", fmt::ptr(f)); 152 | ") 153 | expect_compile(format-function-error " 154 | void (*f)(); 155 | fmt::format(\"{}\", f); 156 | " ERROR) 157 | 158 | # Formatting an unformattable argument should always be a compile time error 159 | expect_compile(format-lots-of-arguments-with-unformattable " 160 | struct E {}; 161 | fmt::format(\"\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, E()); 162 | " ERROR) 163 | expect_compile(format-lots-of-arguments-with-function " 164 | void (*f)(); 165 | fmt::format(\"\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, f); 166 | " ERROR) 167 | 168 | if (CMAKE_CXX_STANDARD GREATER_EQUAL 20) 169 | # Compile-time argument type check 170 | expect_compile(format-string-number-spec " 171 | #ifdef FMT_HAS_CONSTEVAL 172 | fmt::format(\"{:d}\", 42); 173 | #endif 174 | ") 175 | expect_compile(format-string-number-spec-error " 176 | #ifdef FMT_HAS_CONSTEVAL 177 | fmt::format(\"{:d}\", \"I am not a number\"); 178 | #else 179 | #error 180 | #endif 181 | " ERROR) 182 | expect_compile(print-string-number-spec-error " 183 | #ifdef FMT_HAS_CONSTEVAL 184 | fmt::print(\"{:d}\", \"I am not a number\"); 185 | #else 186 | #error 187 | #endif 188 | " ERROR) 189 | expect_compile(print-stream-string-number-spec-error " 190 | #ifdef FMT_HAS_CONSTEVAL 191 | fmt::print(std::cout, \"{:d}\", \"I am not a number\"); 192 | #else 193 | #error 194 | #endif 195 | " ERROR) 196 | 197 | # Compile-time argument name check 198 | expect_compile(format-string-name " 199 | #if defined(FMT_HAS_CONSTEVAL) && FMT_USE_NONTYPE_TEMPLATE_ARGS 200 | using namespace fmt::literals; 201 | fmt::print(\"{foo}\", \"foo\"_a=42); 202 | #endif 203 | ") 204 | expect_compile(format-string-name-error " 205 | #if defined(FMT_HAS_CONSTEVAL) && FMT_USE_NONTYPE_TEMPLATE_ARGS 206 | using namespace fmt::literals; 207 | fmt::print(\"{foo}\", \"bar\"_a=42); 208 | #else 209 | #error 210 | #endif 211 | " ERROR) 212 | endif () 213 | 214 | # Run all tests 215 | run_tests() 216 | -------------------------------------------------------------------------------- /test/compile-fp-test.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - formatting library tests 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include "fmt/compile.h" 9 | #include "gmock/gmock.h" 10 | 11 | #if FMT_USE_CONSTEVAL 12 | 13 | template struct test_string { 14 | template constexpr bool operator==(const T& rhs) const noexcept { 15 | return fmt::basic_string_view(rhs).compare(buffer) == 0; 16 | } 17 | Char buffer[max_string_length]{}; 18 | }; 19 | 20 | template 21 | consteval auto test_format(auto format, const Args&... args) { 22 | test_string string{}; 23 | fmt::format_to(string.buffer, format, args...); 24 | return string; 25 | } 26 | 27 | TEST(compile_time_formatting_test, floating_point) { 28 | EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f)); 29 | EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f)); 30 | 31 | EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0)); 32 | EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0)); 33 | EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0)); 34 | EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65)); 35 | EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65)); 36 | EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65)); 37 | EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6)); 38 | EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65)); 39 | EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65)); 40 | 41 | EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65)); 42 | EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65)); 43 | EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65)); 44 | EXPECT_EQ("9223372036854775808.000000", 45 | test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0)); 46 | 47 | constexpr double nan = std::numeric_limits::quiet_NaN(); 48 | EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan)); 49 | EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan)); 50 | if (std::signbit(-nan)) 51 | EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan)); 52 | else 53 | fmt::print("Warning: compiler doesn't handle negative NaN correctly"); 54 | 55 | constexpr double inf = std::numeric_limits::infinity(); 56 | EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf)); 57 | EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf)); 58 | EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf)); 59 | } 60 | 61 | #endif // FMT_USE_CONSTEVAL 62 | -------------------------------------------------------------------------------- /test/cuda-test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # We can find some usecases which follow the guide of CMake which uses 2 | # `enable_language(CUDA)` instead of `find_package(CUDA)` and let the CMake 3 | # built-in functions use NVCC. 4 | 5 | # See: https://cmake.org/cmake/help/latest/module/FindCUDA.html#replacement 6 | # 7 | # However, this requires CMake version 3.10 or higher and we can't be sure most 8 | # of the CUDA projects are using those. 9 | # 10 | # This test relies on `find_package(CUDA)` in the parent CMake config. 11 | 12 | # These can be updated when NVCC becomes ready for C++ 17 features 13 | # https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features 14 | set(CMAKE_CUDA_STANDARD 14) 15 | set(CMAKE_CUDA_STANDARD_REQUIRED 14) 16 | 17 | # In this test, we assume that the user is going to compile CUDA source code 18 | # with some libraries (fmt in this case). 19 | # 20 | # In addition to that, this test invokes both the C++ host compiler and NVCC 21 | # by providing another (non-CUDA) C++ source code. 22 | if (${CMAKE_VERSION} VERSION_LESS 3.15) 23 | # https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html 24 | list(APPEND CUDA_NVCC_FLAGS "-std=c++14") 25 | if (MSVC) 26 | # This is the solution of pytorch: 27 | # https://github.com/pytorch/pytorch/pull/7118 28 | list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "/std:c++14") 29 | list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "/Zc:__cplusplus") 30 | # for the reason of this -Xcompiler options, see below. 31 | endif () 32 | cuda_add_executable(fmt-in-cuda-test cuda-cpp14.cu cpp14.cc) 33 | target_compile_features(fmt-in-cuda-test PRIVATE cxx_std_14) 34 | if (MSVC) 35 | # This part is for (non-CUDA) C++ code. MSVC can define incorrect 36 | # `__cplusplus` macro. Fix for the issue is to use additional compiler flag. 37 | # 38 | # See Also: 39 | # https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ 40 | # https://github.com/Microsoft/vscode-cpptools/issues/2595 41 | target_compile_options(fmt-in-cuda-test PRIVATE /Zc:__cplusplus /permissive-) 42 | endif () 43 | else() 44 | # now using a "new" way of handling CUDA 45 | add_executable(fmt-in-cuda-test cuda-cpp14.cu cpp14.cc) 46 | set_target_properties(fmt-in-cuda-test PROPERTIES CUDA_SEPARABLE_COMPILATION ON) 47 | target_compile_features(fmt-in-cuda-test PRIVATE cxx_std_14) 48 | if (MSVC) 49 | # with MSVC, 'cxx_std_14' will only propagate to the host code (MSVC), but will 50 | # not set __cplusplus correctly anyway, while nvcc will ignore it. 51 | # If specified for nvcc on the command line as '-std=c++14' nvcc will emit this 52 | # message instead: 53 | # nvcc warning : The -std=c++14 flag is not supported with the configured host 54 | # compiler. Flag will be ignored. 55 | set_property(SOURCE cuda-cpp14.cu APPEND PROPERTY 56 | COMPILE_OPTIONS -Xcompiler /std:c++14 -Xcompiler /Zc:__cplusplus) 57 | set_property(SOURCE cpp14.cc APPEND PROPERTY 58 | COMPILE_OPTIONS /std:c++14 /Zc:__cplusplus) 59 | endif() 60 | endif() 61 | 62 | get_target_property(IN_USE_CUDA_STANDARD fmt-in-cuda-test CUDA_STANDARD) 63 | message(STATUS "cuda_standard: ${IN_USE_CUDA_STANDARD}") 64 | 65 | get_target_property(IN_USE_CUDA_STANDARD_REQUIRED 66 | fmt-in-cuda-test CUDA_STANDARD_REQUIRED) 67 | message(STATUS "cuda_standard_required: ${IN_USE_CUDA_STANDARD_REQUIRED}") 68 | 69 | # We don't use PUBLIC or other keyword for reasons explained in the 70 | # CUDA_LINK_LIBRARIES_KEYWORD section in 71 | # https://cmake.org/cmake/help/latest/module/FindCUDA.html 72 | target_link_libraries(fmt-in-cuda-test fmt::fmt) 73 | 74 | -------------------------------------------------------------------------------- /test/cuda-test/cpp14.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // The purpose of this part is to ensure NVCC's host compiler also supports 4 | // the standard version. See 'cuda-cpp14.cu'. 5 | // 6 | // https://en.cppreference.com/w/cpp/preprocessor/replace#Predefined_macros 7 | static_assert(__cplusplus >= 201402L, "expect C++ 2014 for host compiler"); 8 | 9 | auto make_message_cpp() -> std::string { 10 | return fmt::format("host compiler \t: __cplusplus == {}", __cplusplus); 11 | } 12 | -------------------------------------------------------------------------------- /test/cuda-test/cuda-cpp14.cu: -------------------------------------------------------------------------------- 1 | // Direct NVCC command line example: 2 | // 3 | // nvcc ./cuda-cpp14.cu -x cu -I"../include" -l"fmtd" -L"../build/Debug" \ 4 | // -std=c++14 -Xcompiler /std:c++14 -Xcompiler /Zc:__cplusplus 5 | 6 | // Ensure that we are using the latest C++ standard for NVCC 7 | // The version is C++14 8 | // 9 | // https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#c-cplusplus-language-support 10 | // https://en.cppreference.com/w/cpp/preprocessor/replace#Predefined_macros 11 | static_assert(__cplusplus >= 201402L, "expect C++ 2014 for nvcc"); 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | extern auto make_message_cpp() -> std::string; 19 | extern auto make_message_cuda() -> std::string; 20 | 21 | int main() { 22 | std::cout << make_message_cuda() << std::endl; 23 | std::cout << make_message_cpp() << std::endl; 24 | } 25 | 26 | auto make_message_cuda() -> std::string { 27 | return fmt::format("nvcc compiler \t: __cplusplus == {}", __cplusplus); 28 | } 29 | -------------------------------------------------------------------------------- /test/detect-stdfs.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - tests of formatters for standard library types 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include // _GLIBCXX_RELEASE & _LIBCPP_VERSION 9 | 10 | #if defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE == 8 11 | # error libfound "stdc++fs" 12 | #elif !defined(__apple_build_version__) && defined(_LIBCPP_VERSION) && \ 13 | _LIBCPP_VERSION >= 7000 && _LIBCPP_VERSION < 9000 14 | # error libfound "c++fs" 15 | #else 16 | // none if std::filesystem does not require additional libraries 17 | # error libfound "" 18 | #endif 19 | -------------------------------------------------------------------------------- /test/enforce-checks-test.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - formatting library tests 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include 9 | #include 10 | 11 | #define I 42 // simulate https://en.cppreference.com/w/c/numeric/complex/I 12 | #include "fmt/chrono.h" 13 | #include "fmt/color.h" 14 | #include "fmt/format.h" 15 | #include "fmt/ostream.h" 16 | #include "fmt/ranges.h" 17 | #include "fmt/xchar.h" 18 | #undef I 19 | 20 | // Exercise the API to verify that everything we expect to can compile. 21 | void test_format_api() { 22 | (void)fmt::format(FMT_STRING("{}"), 42); 23 | (void)fmt::format(FMT_STRING(L"{}"), 42); 24 | (void)fmt::format(FMT_STRING("noop")); 25 | 26 | (void)fmt::to_string(42); 27 | (void)fmt::to_wstring(42); 28 | 29 | std::vector out; 30 | fmt::format_to(std::back_inserter(out), FMT_STRING("{}"), 42); 31 | 32 | char buffer[4]; 33 | fmt::format_to_n(buffer, 3, FMT_STRING("{}"), 12345); 34 | 35 | wchar_t wbuffer[4]; 36 | fmt::format_to_n(wbuffer, 3, FMT_STRING(L"{}"), 12345); 37 | } 38 | 39 | void test_chrono() { 40 | (void)fmt::format(FMT_STRING("{}"), std::chrono::seconds(42)); 41 | (void)fmt::format(FMT_STRING(L"{}"), std::chrono::seconds(42)); 42 | } 43 | 44 | void test_text_style() { 45 | fmt::print(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), "rgb(255,20,30)"); 46 | (void)fmt::format(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), 47 | "rgb(255,20,30)"); 48 | 49 | fmt::text_style ts = fg(fmt::rgb(255, 20, 30)); 50 | std::string out; 51 | fmt::format_to(std::back_inserter(out), ts, 52 | FMT_STRING("rgb(255,20,30){}{}{}"), 1, 2, 3); 53 | } 54 | 55 | void test_range() { 56 | std::vector hello = {'h', 'e', 'l', 'l', 'o'}; 57 | (void)fmt::format(FMT_STRING("{}"), hello); 58 | } 59 | 60 | int main() { 61 | test_format_api(); 62 | test_chrono(); 63 | test_text_style(); 64 | test_range(); 65 | } 66 | -------------------------------------------------------------------------------- /test/find-package-test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8...3.25) 2 | 3 | project(fmt-test) 4 | 5 | find_package(FMT REQUIRED) 6 | 7 | add_executable(library-test main.cc) 8 | target_link_libraries(library-test fmt::fmt) 9 | target_compile_options(library-test PRIVATE ${PEDANTIC_COMPILE_FLAGS}) 10 | target_include_directories(library-test PUBLIC SYSTEM .) 11 | 12 | if (TARGET fmt::fmt-header-only) 13 | add_executable(header-only-test main.cc) 14 | target_link_libraries(header-only-test fmt::fmt-header-only) 15 | target_compile_options(header-only-test PRIVATE ${PEDANTIC_COMPILE_FLAGS}) 16 | target_include_directories(header-only-test PUBLIC SYSTEM .) 17 | endif () 18 | -------------------------------------------------------------------------------- /test/find-package-test/main.cc: -------------------------------------------------------------------------------- 1 | #include "fmt/format.h" 2 | 3 | int main(int argc, char** argv) { 4 | for (int i = 0; i < argc; ++i) fmt::print("{}: {}\n", i, argv[i]); 5 | } 6 | -------------------------------------------------------------------------------- /test/fuzzing/.gitignore: -------------------------------------------------------------------------------- 1 | # ignore artifacts from the build.sh script 2 | build-*/ 3 | 4 | -------------------------------------------------------------------------------- /test/fuzzing/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019, Paul Dreik 2 | # License: see LICENSE.rst in the fmt root directory 3 | 4 | # Link in the main function. Useful for reproducing, kcov, gdb, afl, valgrind. 5 | # (Note that libFuzzer can also reproduce, just pass it the files.) 6 | option(FMT_FUZZ_LINKMAIN "Enables the reproduce mode, instead of libFuzzer" On) 7 | 8 | # For oss-fuzz - insert $LIB_FUZZING_ENGINE into the link flags, but only for 9 | # the fuzz targets, otherwise the CMake configuration step fails. 10 | set(FMT_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets") 11 | 12 | # Adds a binary for reproducing, i.e. no fuzzing, just enables replaying data 13 | # through the fuzzers. 14 | function(add_fuzzer source) 15 | get_filename_component(basename ${source} NAME_WE) 16 | set(name ${basename}-fuzzer) 17 | add_executable(${name} ${source} fuzzer-common.h) 18 | if (FMT_FUZZ_LINKMAIN) 19 | target_sources(${name} PRIVATE main.cc) 20 | endif () 21 | target_link_libraries(${name} PRIVATE fmt) 22 | if (FMT_FUZZ_LDFLAGS) 23 | target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS}) 24 | endif () 25 | target_compile_features(${name} PRIVATE cxx_std_14) 26 | endfunction() 27 | 28 | foreach (source chrono-duration.cc chrono-timepoint.cc float.cc named-arg.cc one-arg.cc two-args.cc) 29 | add_fuzzer(${source}) 30 | endforeach () 31 | -------------------------------------------------------------------------------- /test/fuzzing/README.md: -------------------------------------------------------------------------------- 1 | # Running the fuzzers locally 2 | 3 | There is a [helper script](build.sh) to build the fuzzers, which has only been 4 | tested on Debian and Ubuntu linux so far. There should be no problems fuzzing on 5 | Windows (using clang>=8) or on Mac, but the script will probably not work out of 6 | the box. 7 | 8 | Something along 9 | ```sh 10 | mkdir build 11 | cd build 12 | export CXX=clang++ 13 | export CXXFLAGS="-fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g" 14 | cmake .. -DFMT_SAFE_DURATION_CAST=On -DFMT_FUZZ=On -DFMT_FUZZ_LINKMAIN=Off -DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer" 15 | cmake --build . 16 | ``` 17 | should work to build the fuzzers for all platforms which clang supports. 18 | 19 | Execute a fuzzer with for instance 20 | ```sh 21 | cd build 22 | export UBSAN_OPTIONS=halt_on_error=1 23 | mkdir out_chrono 24 | bin/fuzzer_chrono_duration out_chrono 25 | ``` 26 | -------------------------------------------------------------------------------- /test/fuzzing/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Creates fuzzer builds of various kinds 4 | # - oss-fuzz emulated mode (makes sure a simulated invocation by oss-fuzz works) 5 | # - libFuzzer build (you will need clang) 6 | # - afl build (you will need afl) 7 | # 8 | # 9 | # Copyright (c) 2019 Paul Dreik 10 | # 11 | # For the license information refer to format.h. 12 | 13 | set -e 14 | me=$(basename $0) 15 | root=$(readlink -f "$(dirname "$0")/../..") 16 | 17 | 18 | echo $me: root=$root 19 | 20 | here=$(pwd) 21 | 22 | CXXFLAGSALL="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g" 23 | CMAKEFLAGSALL="$root -GNinja -DCMAKE_BUILD_TYPE=Debug -DFMT_DOC=Off -DFMT_TEST=Off -DFMT_FUZZ=On -DCMAKE_CXX_STANDARD=17" 24 | 25 | CLANG=clang++-11 26 | 27 | # For performance analysis of the fuzzers. 28 | builddir=$here/build-fuzzers-perfanalysis 29 | mkdir -p $builddir 30 | cd $builddir 31 | CXX="ccache g++" CXXFLAGS="$CXXFLAGSALL -g" cmake \ 32 | $CMAKEFLAGSALL \ 33 | -DFMT_FUZZ_LINKMAIN=On \ 34 | -DCMAKE_BUILD_TYPE=Release 35 | 36 | cmake --build $builddir 37 | 38 | # Builds the fuzzers as oss-fuzz does. 39 | builddir=$here/build-fuzzers-ossfuzz 40 | mkdir -p $builddir 41 | cd $builddir 42 | CXX=$CLANG \ 43 | CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link" cmake \ 44 | cmake $CMAKEFLAGSALL \ 45 | -DFMT_FUZZ_LINKMAIN=Off \ 46 | -DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer" 47 | 48 | cmake --build $builddir 49 | 50 | 51 | # Builds fuzzers for local fuzzing with libfuzzer with asan+usan. 52 | builddir=$here/build-fuzzers-libfuzzer 53 | mkdir -p $builddir 54 | cd $builddir 55 | CXX=$CLANG \ 56 | CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link,address,undefined" cmake \ 57 | cmake $CMAKEFLAGSALL \ 58 | -DFMT_FUZZ_LINKMAIN=Off \ 59 | -DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer" 60 | 61 | cmake --build $builddir 62 | 63 | # Builds a fast fuzzer for making coverage fast. 64 | builddir=$here/build-fuzzers-fast 65 | mkdir -p $builddir 66 | cd $builddir 67 | CXX=$CLANG \ 68 | CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link -O3" cmake \ 69 | cmake $CMAKEFLAGSALL \ 70 | -DFMT_FUZZ_LINKMAIN=Off \ 71 | -DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer" \ 72 | -DCMAKE_BUILD_TYPE=Release 73 | 74 | cmake --build $builddir 75 | 76 | 77 | # Builds fuzzers for local fuzzing with afl. 78 | builddir=$here/build-fuzzers-afl 79 | mkdir -p $builddir 80 | cd $builddir 81 | CXX="afl-g++" \ 82 | CXXFLAGS="$CXXFLAGSALL -fsanitize=address,undefined" \ 83 | cmake $CMAKEFLAGSALL \ 84 | -DFMT_FUZZ_LINKMAIN=On 85 | 86 | cmake --build $builddir 87 | 88 | 89 | echo $me: all good 90 | 91 | -------------------------------------------------------------------------------- /test/fuzzing/chrono-duration.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, Paul Dreik 2 | // For the license information refer to format.h. 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "fuzzer-common.h" 9 | 10 | template 11 | void invoke_inner(fmt::string_view format_str, Rep rep) { 12 | auto value = std::chrono::duration(rep); 13 | try { 14 | #if FMT_FUZZ_FORMAT_TO_STRING 15 | std::string message = fmt::format(format_str, value); 16 | #else 17 | auto buf = fmt::memory_buffer(); 18 | fmt::format_to(std::back_inserter(buf), format_str, value); 19 | #endif 20 | } catch (std::exception&) { 21 | } 22 | } 23 | 24 | // Rep is a duration's representation type. 25 | template 26 | void invoke_outer(const uint8_t* data, size_t size, int period) { 27 | // Always use a fixed location of the data. 28 | static_assert(sizeof(Rep) <= fixed_size, "fixed size is too small"); 29 | if (size <= fixed_size + 1) return; 30 | 31 | const Rep rep = assign_from_buf(data); 32 | data += fixed_size; 33 | size -= fixed_size; 34 | 35 | // data is already allocated separately in libFuzzer so reading past the end 36 | // will most likely be detected anyway. 37 | const auto format_str = fmt::string_view(as_chars(data), size); 38 | 39 | // yocto, zepto, zetta and yotta are not handled. 40 | switch (period) { 41 | case 1: 42 | invoke_inner(format_str, rep); 43 | break; 44 | case 2: 45 | invoke_inner(format_str, rep); 46 | break; 47 | case 3: 48 | invoke_inner(format_str, rep); 49 | break; 50 | case 4: 51 | invoke_inner(format_str, rep); 52 | break; 53 | case 5: 54 | invoke_inner(format_str, rep); 55 | break; 56 | case 6: 57 | invoke_inner(format_str, rep); 58 | break; 59 | case 7: 60 | invoke_inner(format_str, rep); 61 | break; 62 | case 8: 63 | invoke_inner(format_str, rep); 64 | break; 65 | case 9: 66 | invoke_inner(format_str, rep); 67 | break; 68 | case 10: 69 | invoke_inner(format_str, rep); 70 | break; 71 | case 11: 72 | invoke_inner(format_str, rep); 73 | break; 74 | case 12: 75 | invoke_inner(format_str, rep); 76 | break; 77 | case 13: 78 | invoke_inner(format_str, rep); 79 | break; 80 | case 14: 81 | invoke_inner(format_str, rep); 82 | break; 83 | case 15: 84 | invoke_inner(format_str, rep); 85 | break; 86 | } 87 | } 88 | 89 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { 90 | if (size <= 4) return 0; 91 | 92 | const auto representation = data[0]; 93 | const auto period = data[1]; 94 | data += 2; 95 | size -= 2; 96 | 97 | switch (representation) { 98 | case 1: 99 | invoke_outer(data, size, period); 100 | break; 101 | case 2: 102 | invoke_outer(data, size, period); 103 | break; 104 | case 3: 105 | invoke_outer(data, size, period); 106 | break; 107 | case 4: 108 | invoke_outer(data, size, period); 109 | break; 110 | case 5: 111 | invoke_outer(data, size, period); 112 | break; 113 | case 6: 114 | invoke_outer(data, size, period); 115 | break; 116 | case 7: 117 | invoke_outer(data, size, period); 118 | break; 119 | case 8: 120 | invoke_outer(data, size, period); 121 | break; 122 | case 9: 123 | invoke_outer(data, size, period); 124 | break; 125 | case 10: 126 | invoke_outer(data, size, period); 127 | break; 128 | case 11: 129 | invoke_outer(data, size, period); 130 | break; 131 | case 12: 132 | invoke_outer(data, size, period); 133 | break; 134 | } 135 | return 0; 136 | } 137 | -------------------------------------------------------------------------------- /test/fuzzing/chrono-timepoint.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, Paul Dreik 2 | // For license information refer to format.h. 3 | #include 4 | 5 | #include "fuzzer-common.h" 6 | 7 | /* 8 | * a fuzzer for the chrono timepoints formatters 9 | * C is a clock (std::chrono::system_clock etc) 10 | */ 11 | template void doit(const uint8_t* data, size_t size) { 12 | using Rep = typename C::time_point::rep; 13 | constexpr auto N = sizeof(Rep); 14 | if (size < N) return; 15 | 16 | const auto x = assign_from_buf(data); 17 | typename C::duration dur{x}; 18 | typename C::time_point timepoint{dur}; 19 | data += N; 20 | size -= N; 21 | data_to_string format_str(data, size); 22 | 23 | std::string message = fmt::format(format_str.get(), timepoint); 24 | } 25 | 26 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { 27 | try { 28 | doit(data, size); 29 | } catch (...) { 30 | } 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /test/fuzzing/float.cc: -------------------------------------------------------------------------------- 1 | // A fuzzer for floating-point formatter. 2 | // For the license information refer to format.h. 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "fuzzer-common.h" 12 | 13 | void check_round_trip(fmt::string_view format_str, double value) { 14 | auto buffer = fmt::memory_buffer(); 15 | fmt::format_to(std::back_inserter(buffer), format_str, value); 16 | 17 | if (std::isnan(value)) { 18 | auto nan = std::signbit(value) ? "-nan" : "nan"; 19 | if (fmt::string_view(buffer.data(), buffer.size()) != nan) 20 | throw std::runtime_error("round trip failure"); 21 | return; 22 | } 23 | 24 | buffer.push_back('\0'); 25 | char* ptr = nullptr; 26 | if (std::strtod(buffer.data(), &ptr) != value) 27 | throw std::runtime_error("round trip failure"); 28 | if (ptr + 1 != buffer.end()) throw std::runtime_error("unparsed output"); 29 | } 30 | 31 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { 32 | if (size <= sizeof(double) || !std::numeric_limits::is_iec559) 33 | return 0; 34 | check_round_trip("{}", assign_from_buf(data)); 35 | // A larger than necessary precision is used to trigger the fallback 36 | // formatter. 37 | check_round_trip("{:.50g}", assign_from_buf(data)); 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /test/fuzzing/fuzzer-common.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, Paul Dreik 2 | // For the license information refer to format.h. 3 | 4 | #ifndef FUZZER_COMMON_H 5 | #define FUZZER_COMMON_H 6 | 7 | #include 8 | 9 | #include // std::uint8_t 10 | #include // memcpy 11 | #include 12 | 13 | // One can format to either a string, or a buffer. The latter is faster, but 14 | // one may be interested in formatting to a string instead to verify it works 15 | // as intended. To avoid a combinatoric explosion, select this at compile time 16 | // instead of dynamically from the fuzz data. 17 | #define FMT_FUZZ_FORMAT_TO_STRING 0 18 | 19 | // If {fmt} is given a buffer that is separately allocated, chances that address 20 | // sanitizer detects out of bound reads is much higher. However, it slows down 21 | // the fuzzing. 22 | #define FMT_FUZZ_SEPARATE_ALLOCATION 1 23 | 24 | // The size of the largest possible type in use. 25 | // To let the the fuzzer mutation be efficient at cross pollinating between 26 | // different types, use a fixed size format. The same bit pattern, interpreted 27 | // as another type, is likely interesting. 28 | constexpr auto fixed_size = 16; 29 | 30 | // Casts data to a char pointer. 31 | template inline const char* as_chars(const T* data) { 32 | return reinterpret_cast(data); 33 | } 34 | 35 | // Casts data to a byte pointer. 36 | template inline const std::uint8_t* as_bytes(const T* data) { 37 | return reinterpret_cast(data); 38 | } 39 | 40 | // Blits bytes from data to form an (assumed trivially constructible) object 41 | // of type Item. 42 | template inline Item assign_from_buf(const std::uint8_t* data) { 43 | auto item = Item(); 44 | std::memcpy(&item, data, sizeof(Item)); 45 | return item; 46 | } 47 | 48 | // Reads a boolean value by looking at the first byte from data. 49 | template <> inline bool assign_from_buf(const std::uint8_t* data) { 50 | return *data != 0; 51 | } 52 | 53 | struct data_to_string { 54 | #if FMT_FUZZ_SEPARATE_ALLOCATION 55 | std::vector buffer; 56 | 57 | data_to_string(const uint8_t* data, size_t size, bool add_terminator = false) 58 | : buffer(size + (add_terminator ? 1 : 0)) { 59 | if (size) { 60 | std::memcpy(buffer.data(), data, size); 61 | } 62 | } 63 | 64 | fmt::string_view get() const { return {buffer.data(), buffer.size()}; } 65 | #else 66 | fmt::string_view sv; 67 | 68 | data_to_string(const uint8_t* data, size_t size, bool = false) 69 | : str(as_chars(data), size) {} 70 | 71 | fmt::string_view get() const { return sv; } 72 | #endif 73 | 74 | const char* data() const { return get().data(); } 75 | }; 76 | 77 | #endif // FUZZER_COMMON_H 78 | -------------------------------------------------------------------------------- /test/fuzzing/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "fuzzer-common.h" 6 | 7 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); 8 | 9 | int main(int argc, char** argv) { 10 | for (int i = 1; i < argc; ++i) { 11 | std::ifstream in(argv[i]); 12 | assert(in); 13 | in.seekg(0, std::ios_base::end); 14 | const auto size = in.tellg(); 15 | assert(size >= 0); 16 | in.seekg(0, std::ios_base::beg); 17 | std::vector buf(static_cast(size)); 18 | in.read(buf.data(), size); 19 | assert(in.gcount() == size); 20 | LLVMFuzzerTestOneInput(as_bytes(buf.data()), buf.size()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/fuzzing/named-arg.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, Paul Dreik 2 | // For the license information refer to format.h. 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "fuzzer-common.h" 11 | 12 | template 13 | void invoke_fmt(const uint8_t* data, size_t size, unsigned arg_name_size) { 14 | static_assert(sizeof(T) <= fixed_size, "fixed_size too small"); 15 | if (size <= fixed_size) return; 16 | const T value = assign_from_buf(data); 17 | data += fixed_size; 18 | size -= fixed_size; 19 | 20 | if (arg_name_size <= 0 || arg_name_size >= size) return; 21 | data_to_string arg_name(data, arg_name_size, true); 22 | data += arg_name_size; 23 | size -= arg_name_size; 24 | 25 | data_to_string format_str(data, size); 26 | try { 27 | #if FMT_FUZZ_FORMAT_TO_STRING 28 | std::string message = 29 | fmt::format(format_str.get(), fmt::arg(arg_name.data(), value)); 30 | #else 31 | fmt::memory_buffer out; 32 | fmt::format_to(std::back_inserter(out), format_str.get(), 33 | fmt::arg(arg_name.data(), value)); 34 | #endif 35 | } catch (std::exception&) { 36 | } 37 | } 38 | 39 | // For dynamic dispatching to an explicit instantiation. 40 | template void invoke(int type, Callback callback) { 41 | switch (type) { 42 | case 0: 43 | callback(bool()); 44 | break; 45 | case 1: 46 | callback(char()); 47 | break; 48 | case 2: 49 | using sc = signed char; 50 | callback(sc()); 51 | break; 52 | case 3: 53 | using uc = unsigned char; 54 | callback(uc()); 55 | break; 56 | case 4: 57 | callback(short()); 58 | break; 59 | case 5: 60 | using us = unsigned short; 61 | callback(us()); 62 | break; 63 | case 6: 64 | callback(int()); 65 | break; 66 | case 7: 67 | callback(unsigned()); 68 | break; 69 | case 8: 70 | callback(long()); 71 | break; 72 | case 9: 73 | using ul = unsigned long; 74 | callback(ul()); 75 | break; 76 | case 10: 77 | callback(float()); 78 | break; 79 | case 11: 80 | callback(double()); 81 | break; 82 | case 12: 83 | using LD = long double; 84 | callback(LD()); 85 | break; 86 | } 87 | } 88 | 89 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { 90 | if (size <= 3) return 0; 91 | 92 | // Switch types depending on the first byte of the input. 93 | const auto type = data[0] & 0x0F; 94 | const unsigned arg_name_size = (data[0] & 0xF0) >> 4; 95 | data++; 96 | size--; 97 | 98 | invoke(type, [=](auto arg) { 99 | invoke_fmt(data, size, arg_name_size); 100 | }); 101 | return 0; 102 | } 103 | -------------------------------------------------------------------------------- /test/fuzzing/one-arg.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, Paul Dreik 2 | // For the license information refer to format.h. 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "fuzzer-common.h" 10 | 11 | template const T* from_repr(const Repr& r) { 12 | return &r; 13 | } 14 | 15 | template <> const std::tm* from_repr(const std::time_t& t) { 16 | return std::localtime(&t); 17 | } 18 | 19 | template 20 | void invoke_fmt(const uint8_t* data, size_t size) { 21 | static_assert(sizeof(Repr) <= fixed_size, "Nfixed is too small"); 22 | if (size <= fixed_size) return; 23 | auto repr = assign_from_buf(data); 24 | const T* value = from_repr(repr); 25 | if (!value) return; 26 | data += fixed_size; 27 | size -= fixed_size; 28 | data_to_string format_str(data, size); 29 | try { 30 | #if FMT_FUZZ_FORMAT_TO_STRING 31 | std::string message = fmt::format(format_str.get(), *value); 32 | #else 33 | auto buf = fmt::memory_buffer(); 34 | fmt::format_to(std::back_inserter(buf), format_str.get(), *value); 35 | #endif 36 | } catch (std::exception&) { 37 | } 38 | } 39 | 40 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { 41 | if (size <= 3) return 0; 42 | 43 | const auto first = data[0]; 44 | data++; 45 | size--; 46 | 47 | switch (first) { 48 | case 0: 49 | invoke_fmt(data, size); 50 | break; 51 | case 1: 52 | invoke_fmt(data, size); 53 | break; 54 | case 2: 55 | invoke_fmt(data, size); 56 | break; 57 | case 3: 58 | invoke_fmt(data, size); 59 | break; 60 | case 4: 61 | invoke_fmt(data, size); 62 | break; 63 | case 5: 64 | invoke_fmt(data, size); 65 | break; 66 | case 6: 67 | invoke_fmt(data, size); 68 | break; 69 | case 7: 70 | invoke_fmt(data, size); 71 | break; 72 | case 8: 73 | invoke_fmt(data, size); 74 | break; 75 | case 9: 76 | invoke_fmt(data, size); 77 | break; 78 | case 10: 79 | invoke_fmt(data, size); 80 | break; 81 | case 11: 82 | invoke_fmt(data, size); 83 | break; 84 | case 12: 85 | invoke_fmt(data, size); 86 | break; 87 | case 13: 88 | invoke_fmt(data, size); 89 | break; 90 | } 91 | return 0; 92 | } 93 | -------------------------------------------------------------------------------- /test/fuzzing/two-args.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, Paul Dreik 2 | // For the license information refer to format.h. 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "fuzzer-common.h" 11 | 12 | template 13 | void invoke_fmt(const uint8_t* data, size_t size) { 14 | static_assert(sizeof(Item1) <= fixed_size, "size1 exceeded"); 15 | static_assert(sizeof(Item2) <= fixed_size, "size2 exceeded"); 16 | if (size <= fixed_size + fixed_size) return; 17 | 18 | const Item1 item1 = assign_from_buf(data); 19 | data += fixed_size; 20 | size -= fixed_size; 21 | 22 | const Item2 item2 = assign_from_buf(data); 23 | data += fixed_size; 24 | size -= fixed_size; 25 | 26 | auto format_str = fmt::string_view(as_chars(data), size); 27 | #if FMT_FUZZ_FORMAT_TO_STRING 28 | std::string message = fmt::format(format_str, item1, item2); 29 | #else 30 | auto buf = fmt::memory_buffer(); 31 | fmt::format_to(std::back_inserter(buf), format_str, item1, item2); 32 | #endif 33 | } 34 | 35 | // For dynamic dispatching to an explicit instantiation. 36 | template void invoke(int index, Callback callback) { 37 | switch (index) { 38 | case 0: 39 | callback(bool()); 40 | break; 41 | case 1: 42 | callback(char()); 43 | break; 44 | case 2: 45 | using sc = signed char; 46 | callback(sc()); 47 | break; 48 | case 3: 49 | using uc = unsigned char; 50 | callback(uc()); 51 | break; 52 | case 4: 53 | callback(short()); 54 | break; 55 | case 5: 56 | using us = unsigned short; 57 | callback(us()); 58 | break; 59 | case 6: 60 | callback(int()); 61 | break; 62 | case 7: 63 | callback(unsigned()); 64 | break; 65 | case 8: 66 | callback(long()); 67 | break; 68 | case 9: 69 | using ul = unsigned long; 70 | callback(ul()); 71 | break; 72 | case 10: 73 | callback(float()); 74 | break; 75 | case 11: 76 | callback(double()); 77 | break; 78 | case 12: 79 | using LD = long double; 80 | callback(LD()); 81 | break; 82 | case 13: 83 | using ptr = void*; 84 | callback(ptr()); 85 | break; 86 | } 87 | } 88 | 89 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { 90 | if (size <= 3) return 0; 91 | 92 | // Switch types depending on the first byte of the input. 93 | const auto type1 = data[0] & 0x0F; 94 | const auto type2 = (data[0] & 0xF0) >> 4; 95 | data++; 96 | size--; 97 | try { 98 | invoke(type1, [=](auto param1) { 99 | invoke(type2, [=](auto param2) { 100 | invoke_fmt(data, size); 101 | }); 102 | }); 103 | } catch (std::exception&) { 104 | } 105 | return 0; 106 | } 107 | -------------------------------------------------------------------------------- /test/gtest-extra.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - custom Google Test assertions 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include "gtest-extra.h" 9 | 10 | #if FMT_USE_FCNTL 11 | 12 | using fmt::file; 13 | 14 | output_redirect::output_redirect(FILE* f, bool flush) : file_(f) { 15 | if (flush) this->flush(); 16 | int fd = FMT_POSIX(fileno(f)); 17 | // Create a file object referring to the original file. 18 | original_ = file::dup(fd); 19 | // Create a pipe. 20 | auto pipe = fmt::pipe(); 21 | read_end_ = std::move(pipe.read_end); 22 | // Connect the passed FILE object to the write end of the pipe. 23 | pipe.write_end.dup2(fd); 24 | } 25 | 26 | output_redirect::~output_redirect() noexcept { 27 | try { 28 | restore(); 29 | } catch (const std::exception& e) { 30 | std::fputs(e.what(), stderr); 31 | } 32 | } 33 | 34 | void output_redirect::flush() { 35 | int result = 0; 36 | do { 37 | result = fflush(file_); 38 | } while (result == EOF && errno == EINTR); 39 | if (result != 0) throw fmt::system_error(errno, "cannot flush stream"); 40 | } 41 | 42 | void output_redirect::restore() { 43 | if (original_.descriptor() == -1) return; // Already restored. 44 | flush(); 45 | // Restore the original file. 46 | original_.dup2(FMT_POSIX(fileno(file_))); 47 | original_.close(); 48 | } 49 | 50 | std::string output_redirect::restore_and_read() { 51 | // Restore output. 52 | restore(); 53 | 54 | // Read everything from the pipe. 55 | std::string content; 56 | if (read_end_.descriptor() == -1) return content; // Already read. 57 | enum { BUFFER_SIZE = 4096 }; 58 | char buffer[BUFFER_SIZE]; 59 | size_t count = 0; 60 | do { 61 | count = read_end_.read(buffer, BUFFER_SIZE); 62 | content.append(buffer, count); 63 | } while (count != 0); 64 | read_end_.close(); 65 | return content; 66 | } 67 | 68 | std::string read(file& f, size_t count) { 69 | std::string buffer(count, '\0'); 70 | size_t n = 0, offset = 0; 71 | do { 72 | n = f.read(&buffer[offset], count - offset); 73 | // We can't read more than size_t bytes since count has type size_t. 74 | offset += n; 75 | } while (offset < count && n != 0); 76 | buffer.resize(offset); 77 | return buffer; 78 | } 79 | 80 | #endif // FMT_USE_FCNTL 81 | -------------------------------------------------------------------------------- /test/gtest-extra.h: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - custom Google Test assertions 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #ifndef FMT_GTEST_EXTRA_H_ 9 | #define FMT_GTEST_EXTRA_H_ 10 | 11 | #include // _invalid_parameter_handler 12 | 13 | #include 14 | 15 | #include "fmt/os.h" 16 | #include "gmock/gmock.h" 17 | 18 | #define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \ 19 | GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ 20 | if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \ 21 | std::string gtest_expected_message = expected_message; \ 22 | bool gtest_caught_expected = false; \ 23 | try { \ 24 | GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ 25 | } catch (expected_exception const& e) { \ 26 | if (gtest_expected_message != e.what()) { \ 27 | gtest_ar << #statement \ 28 | " throws an exception with a different message.\n" \ 29 | << "Expected: " << gtest_expected_message << "\n" \ 30 | << " Actual: " << e.what(); \ 31 | goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ 32 | } \ 33 | gtest_caught_expected = true; \ 34 | } catch (...) { \ 35 | gtest_ar << "Expected: " #statement \ 36 | " throws an exception of type " #expected_exception \ 37 | ".\n Actual: it throws a different type."; \ 38 | goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ 39 | } \ 40 | if (!gtest_caught_expected) { \ 41 | gtest_ar << "Expected: " #statement \ 42 | " throws an exception of type " #expected_exception \ 43 | ".\n Actual: it throws nothing."; \ 44 | goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ 45 | } \ 46 | } else \ 47 | GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__) \ 48 | : fail(gtest_ar.failure_message()) 49 | 50 | // Tests that the statement throws the expected exception and the exception's 51 | // what() method returns expected message. 52 | #define EXPECT_THROW_MSG(statement, expected_exception, expected_message) \ 53 | FMT_TEST_THROW_(statement, expected_exception, expected_message, \ 54 | GTEST_NONFATAL_FAILURE_) 55 | 56 | inline std::string system_error_message(int error_code, 57 | const std::string& message) { 58 | auto ec = std::error_code(error_code, std::generic_category()); 59 | return std::system_error(ec, message).what(); 60 | } 61 | 62 | #define EXPECT_SYSTEM_ERROR(statement, error_code, message) \ 63 | EXPECT_THROW_MSG(statement, std::system_error, \ 64 | system_error_message(error_code, message)) 65 | 66 | #if FMT_USE_FCNTL 67 | 68 | // Captures file output by redirecting it to a pipe. 69 | // The output it can handle is limited by the pipe capacity. 70 | class output_redirect { 71 | private: 72 | FILE* file_; 73 | fmt::file original_; // Original file passed to redirector. 74 | fmt::file read_end_; // Read end of the pipe where the output is redirected. 75 | 76 | void flush(); 77 | void restore(); 78 | 79 | public: 80 | explicit output_redirect(FILE* file, bool flush = true); 81 | ~output_redirect() noexcept; 82 | 83 | output_redirect(const output_redirect&) = delete; 84 | void operator=(const output_redirect&) = delete; 85 | 86 | // Restores the original file, reads output from the pipe into a string 87 | // and returns it. 88 | std::string restore_and_read(); 89 | }; 90 | 91 | # define FMT_TEST_WRITE_(statement, expected_output, file, fail) \ 92 | GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ 93 | if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \ 94 | std::string gtest_expected_output = expected_output; \ 95 | output_redirect gtest_redir(file); \ 96 | GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ 97 | std::string gtest_output = gtest_redir.restore_and_read(); \ 98 | if (gtest_output != gtest_expected_output) { \ 99 | gtest_ar << #statement " produces different output.\n" \ 100 | << "Expected: " << gtest_expected_output << "\n" \ 101 | << " Actual: " << gtest_output; \ 102 | goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ 103 | } \ 104 | } else \ 105 | GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__) \ 106 | : fail(gtest_ar.failure_message()) 107 | 108 | // Tests that the statement writes the expected output to file. 109 | # define EXPECT_WRITE(file, statement, expected_output) \ 110 | FMT_TEST_WRITE_(statement, expected_output, file, GTEST_NONFATAL_FAILURE_) 111 | 112 | # ifdef _MSC_VER 113 | 114 | // Suppresses Windows assertions on invalid file descriptors, making 115 | // POSIX functions return proper error codes instead of crashing on Windows. 116 | class suppress_assert { 117 | private: 118 | _invalid_parameter_handler original_handler_; 119 | int original_report_mode_; 120 | 121 | static void handle_invalid_parameter(const wchar_t*, const wchar_t*, 122 | const wchar_t*, unsigned, uintptr_t) {} 123 | 124 | public: 125 | suppress_assert() 126 | : original_handler_( 127 | _set_invalid_parameter_handler(handle_invalid_parameter)), 128 | original_report_mode_(_CrtSetReportMode(_CRT_ASSERT, 0)) {} 129 | ~suppress_assert() { 130 | _set_invalid_parameter_handler(original_handler_); 131 | _CrtSetReportMode(_CRT_ASSERT, original_report_mode_); 132 | (void)original_report_mode_; 133 | } 134 | }; 135 | 136 | # define SUPPRESS_ASSERT(statement) \ 137 | { \ 138 | suppress_assert sa; \ 139 | statement; \ 140 | } 141 | # else 142 | # define SUPPRESS_ASSERT(statement) statement 143 | # endif // _MSC_VER 144 | 145 | # define EXPECT_SYSTEM_ERROR_NOASSERT(statement, error_code, message) \ 146 | EXPECT_SYSTEM_ERROR(SUPPRESS_ASSERT(statement), error_code, message) 147 | 148 | // Attempts to read count characters from a file. 149 | std::string read(fmt::file& f, size_t count); 150 | 151 | # define EXPECT_READ(file, expected_content) \ 152 | EXPECT_EQ(expected_content, \ 153 | read(file, fmt::string_view(expected_content).size())) 154 | 155 | #else 156 | # define EXPECT_WRITE(file, statement, expected_output) \ 157 | do { \ 158 | (void)(file); \ 159 | (void)(statement); \ 160 | (void)(expected_output); \ 161 | SUCCEED(); \ 162 | } while (false) 163 | #endif // FMT_USE_FCNTL 164 | 165 | #endif // FMT_GTEST_EXTRA_H_ 166 | -------------------------------------------------------------------------------- /test/gtest/.clang-format: -------------------------------------------------------------------------------- 1 | # Disable clang-format here 2 | DisableFormat: true 3 | SortIncludes: Never 4 | -------------------------------------------------------------------------------- /test/gtest/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Build the google test library 3 | 4 | # We compile Google Test ourselves instead of using pre-compiled libraries. 5 | # See the Google Test FAQ "Why is it not recommended to install a 6 | # pre-compiled copy of Google Test (for example, into /usr/local)?" 7 | # at http://code.google.com/p/googletest/wiki/FAQ for more details. 8 | add_library(gtest STATIC 9 | gmock-gtest-all.cc gmock/gmock.h gtest/gtest.h gtest/gtest-spi.h) 10 | target_compile_definitions(gtest PUBLIC GTEST_HAS_STD_WSTRING=1) 11 | target_include_directories(gtest SYSTEM PUBLIC .) 12 | target_compile_features(gtest PUBLIC cxx_std_11) 13 | 14 | find_package(Threads) 15 | if (Threads_FOUND) 16 | target_link_libraries(gtest ${CMAKE_THREAD_LIBS_INIT}) 17 | else () 18 | target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0) 19 | endif () 20 | 21 | if (MSVC) 22 | # Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions. 23 | target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS) 24 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 25 | # Disable MSVC warnings of POSIX functions. 26 | target_compile_options(gtest PUBLIC -Wno-deprecated-declarations) 27 | endif () 28 | endif () 29 | 30 | # Silence MSVC tr1 deprecation warning in gmock. 31 | target_compile_definitions(gtest 32 | PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=1) 33 | -------------------------------------------------------------------------------- /test/gtest/gtest/gtest-spi.h: -------------------------------------------------------------------------------- 1 | // Copyright 2007, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | // 31 | // Utilities for testing Google Test itself and code that uses Google Test 32 | // (e.g. frameworks built on top of Google Test). 33 | 34 | // GOOGLETEST_CM0004 DO NOT DELETE 35 | 36 | #ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_ 37 | #define GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_ 38 | 39 | #include "gtest/gtest.h" 40 | 41 | GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ 42 | /* class A needs to have dll-interface to be used by clients of class B */) 43 | 44 | namespace testing { 45 | 46 | // This helper class can be used to mock out Google Test failure reporting 47 | // so that we can test Google Test or code that builds on Google Test. 48 | // 49 | // An object of this class appends a TestPartResult object to the 50 | // TestPartResultArray object given in the constructor whenever a Google Test 51 | // failure is reported. It can either intercept only failures that are 52 | // generated in the same thread that created this object or it can intercept 53 | // all generated failures. The scope of this mock object can be controlled with 54 | // the second argument to the two arguments constructor. 55 | class GTEST_API_ ScopedFakeTestPartResultReporter 56 | : public TestPartResultReporterInterface { 57 | public: 58 | // The two possible mocking modes of this object. 59 | enum InterceptMode { 60 | INTERCEPT_ONLY_CURRENT_THREAD, // Intercepts only thread local failures. 61 | INTERCEPT_ALL_THREADS // Intercepts all failures. 62 | }; 63 | 64 | // The c'tor sets this object as the test part result reporter used 65 | // by Google Test. The 'result' parameter specifies where to report the 66 | // results. This reporter will only catch failures generated in the current 67 | // thread. DEPRECATED 68 | explicit ScopedFakeTestPartResultReporter(TestPartResultArray* result); 69 | 70 | // Same as above, but you can choose the interception scope of this object. 71 | ScopedFakeTestPartResultReporter(InterceptMode intercept_mode, 72 | TestPartResultArray* result); 73 | 74 | // The d'tor restores the previous test part result reporter. 75 | ~ScopedFakeTestPartResultReporter() override; 76 | 77 | // Appends the TestPartResult object to the TestPartResultArray 78 | // received in the constructor. 79 | // 80 | // This method is from the TestPartResultReporterInterface 81 | // interface. 82 | void ReportTestPartResult(const TestPartResult& result) override; 83 | 84 | private: 85 | void Init(); 86 | 87 | const InterceptMode intercept_mode_; 88 | TestPartResultReporterInterface* old_reporter_; 89 | TestPartResultArray* const result_; 90 | 91 | GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedFakeTestPartResultReporter); 92 | }; 93 | 94 | namespace internal { 95 | 96 | // A helper class for implementing EXPECT_FATAL_FAILURE() and 97 | // EXPECT_NONFATAL_FAILURE(). Its destructor verifies that the given 98 | // TestPartResultArray contains exactly one failure that has the given 99 | // type and contains the given substring. If that's not the case, a 100 | // non-fatal failure will be generated. 101 | class GTEST_API_ SingleFailureChecker { 102 | public: 103 | // The constructor remembers the arguments. 104 | SingleFailureChecker(const TestPartResultArray* results, 105 | TestPartResult::Type type, const std::string& substr); 106 | ~SingleFailureChecker(); 107 | private: 108 | const TestPartResultArray* const results_; 109 | const TestPartResult::Type type_; 110 | const std::string substr_; 111 | 112 | GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker); 113 | }; 114 | 115 | } // namespace internal 116 | 117 | } // namespace testing 118 | 119 | GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 120 | 121 | // A set of macros for testing Google Test assertions or code that's expected 122 | // to generate Google Test fatal failures. It verifies that the given 123 | // statement will cause exactly one fatal Google Test failure with 'substr' 124 | // being part of the failure message. 125 | // 126 | // There are two different versions of this macro. EXPECT_FATAL_FAILURE only 127 | // affects and considers failures generated in the current thread and 128 | // EXPECT_FATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. 129 | // 130 | // The verification of the assertion is done correctly even when the statement 131 | // throws an exception or aborts the current function. 132 | // 133 | // Known restrictions: 134 | // - 'statement' cannot reference local non-static variables or 135 | // non-static members of the current object. 136 | // - 'statement' cannot return a value. 137 | // - You cannot stream a failure message to this macro. 138 | // 139 | // Note that even though the implementations of the following two 140 | // macros are much alike, we cannot refactor them to use a common 141 | // helper macro, due to some peculiarity in how the preprocessor 142 | // works. The AcceptsMacroThatExpandsToUnprotectedComma test in 143 | // gtest_unittest.cc will fail to compile if we do that. 144 | #define EXPECT_FATAL_FAILURE(statement, substr) \ 145 | do { \ 146 | class GTestExpectFatalFailureHelper {\ 147 | public:\ 148 | static void Execute() { statement; }\ 149 | };\ 150 | ::testing::TestPartResultArray gtest_failures;\ 151 | ::testing::internal::SingleFailureChecker gtest_checker(\ 152 | >est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\ 153 | {\ 154 | ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ 155 | ::testing::ScopedFakeTestPartResultReporter:: \ 156 | INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ 157 | GTestExpectFatalFailureHelper::Execute();\ 158 | }\ 159 | } while (::testing::internal::AlwaysFalse()) 160 | 161 | #define EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ 162 | do { \ 163 | class GTestExpectFatalFailureHelper {\ 164 | public:\ 165 | static void Execute() { statement; }\ 166 | };\ 167 | ::testing::TestPartResultArray gtest_failures;\ 168 | ::testing::internal::SingleFailureChecker gtest_checker(\ 169 | >est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\ 170 | {\ 171 | ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ 172 | ::testing::ScopedFakeTestPartResultReporter:: \ 173 | INTERCEPT_ALL_THREADS, >est_failures);\ 174 | GTestExpectFatalFailureHelper::Execute();\ 175 | }\ 176 | } while (::testing::internal::AlwaysFalse()) 177 | 178 | // A macro for testing Google Test assertions or code that's expected to 179 | // generate Google Test non-fatal failures. It asserts that the given 180 | // statement will cause exactly one non-fatal Google Test failure with 'substr' 181 | // being part of the failure message. 182 | // 183 | // There are two different versions of this macro. EXPECT_NONFATAL_FAILURE only 184 | // affects and considers failures generated in the current thread and 185 | // EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. 186 | // 187 | // 'statement' is allowed to reference local variables and members of 188 | // the current object. 189 | // 190 | // The verification of the assertion is done correctly even when the statement 191 | // throws an exception or aborts the current function. 192 | // 193 | // Known restrictions: 194 | // - You cannot stream a failure message to this macro. 195 | // 196 | // Note that even though the implementations of the following two 197 | // macros are much alike, we cannot refactor them to use a common 198 | // helper macro, due to some peculiarity in how the preprocessor 199 | // works. If we do that, the code won't compile when the user gives 200 | // EXPECT_NONFATAL_FAILURE() a statement that contains a macro that 201 | // expands to code containing an unprotected comma. The 202 | // AcceptsMacroThatExpandsToUnprotectedComma test in gtest_unittest.cc 203 | // catches that. 204 | // 205 | // For the same reason, we have to write 206 | // if (::testing::internal::AlwaysTrue()) { statement; } 207 | // instead of 208 | // GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) 209 | // to avoid an MSVC warning on unreachable code. 210 | #define EXPECT_NONFATAL_FAILURE(statement, substr) \ 211 | do {\ 212 | ::testing::TestPartResultArray gtest_failures;\ 213 | ::testing::internal::SingleFailureChecker gtest_checker(\ 214 | >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ 215 | (substr));\ 216 | {\ 217 | ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ 218 | ::testing::ScopedFakeTestPartResultReporter:: \ 219 | INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ 220 | if (::testing::internal::AlwaysTrue()) { statement; }\ 221 | }\ 222 | } while (::testing::internal::AlwaysFalse()) 223 | 224 | #define EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ 225 | do {\ 226 | ::testing::TestPartResultArray gtest_failures;\ 227 | ::testing::internal::SingleFailureChecker gtest_checker(\ 228 | >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ 229 | (substr));\ 230 | {\ 231 | ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ 232 | ::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS, \ 233 | >est_failures);\ 234 | if (::testing::internal::AlwaysTrue()) { statement; }\ 235 | }\ 236 | } while (::testing::internal::AlwaysFalse()) 237 | 238 | #endif // GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_ 239 | -------------------------------------------------------------------------------- /test/header-only-test.cc: -------------------------------------------------------------------------------- 1 | // Header-only configuration test 2 | 3 | #include "fmt/base.h" 4 | #include "fmt/ostream.h" 5 | #include "gtest/gtest.h" 6 | 7 | #ifndef FMT_HEADER_ONLY 8 | # error "Not in the header-only mode." 9 | #endif 10 | 11 | TEST(header_only_test, format) { EXPECT_EQ(fmt::format("foo"), "foo"); } 12 | -------------------------------------------------------------------------------- /test/mock-allocator.h: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - mock allocator 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #ifndef FMT_MOCK_ALLOCATOR_H_ 9 | #define FMT_MOCK_ALLOCATOR_H_ 10 | 11 | #include // assert 12 | #include // size_t 13 | 14 | #include // std::allocator_traits 15 | 16 | #include "gmock/gmock.h" 17 | 18 | template class mock_allocator { 19 | public: 20 | using value_type = T; 21 | using size_type = size_t; 22 | 23 | using pointer = T*; 24 | using const_pointer = const T*; 25 | using reference = T&; 26 | using const_reference = const T&; 27 | using difference_type = ptrdiff_t; 28 | 29 | template struct rebind { 30 | using other = mock_allocator; 31 | }; 32 | 33 | mock_allocator() {} 34 | mock_allocator(const mock_allocator&) {} 35 | 36 | MOCK_METHOD(T*, allocate, (size_t)); 37 | MOCK_METHOD(void, deallocate, (T*, size_t)); 38 | }; 39 | 40 | template class allocator_ref { 41 | private: 42 | Allocator* alloc_; 43 | 44 | void move(allocator_ref& other) { 45 | alloc_ = other.alloc_; 46 | other.alloc_ = nullptr; 47 | } 48 | 49 | public: 50 | using value_type = typename Allocator::value_type; 51 | 52 | explicit allocator_ref(Allocator* alloc = nullptr) : alloc_(alloc) {} 53 | 54 | allocator_ref(const allocator_ref& other) : alloc_(other.alloc_) {} 55 | allocator_ref(allocator_ref&& other) { move(other); } 56 | 57 | allocator_ref& operator=(allocator_ref&& other) { 58 | assert(this != &other); 59 | move(other); 60 | return *this; 61 | } 62 | 63 | allocator_ref& operator=(const allocator_ref& other) { 64 | alloc_ = other.alloc_; 65 | return *this; 66 | } 67 | 68 | public: 69 | Allocator* get() const { return alloc_; } 70 | 71 | value_type* allocate(size_t n) { 72 | return std::allocator_traits::allocate(*alloc_, n); 73 | } 74 | void deallocate(value_type* p, size_t n) { alloc_->deallocate(p, n); } 75 | }; 76 | 77 | #endif // FMT_MOCK_ALLOCATOR_H_ 78 | -------------------------------------------------------------------------------- /test/no-builtin-types-test.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - formatting library tests 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include "gtest/gtest.h" 9 | 10 | #if !defined(__GNUC__) || __GNUC__ >= 5 11 | # define FMT_BUILTIN_TYPES 0 12 | # include "fmt/format.h" 13 | 14 | TEST(no_builtin_types_test, format) { 15 | EXPECT_EQ(fmt::format("{}", 42), "42"); 16 | EXPECT_EQ(fmt::format("{}", 42L), "42"); 17 | } 18 | 19 | TEST(no_builtin_types_test, double_is_custom_type) { 20 | double d = 42; 21 | auto args = fmt::make_format_args(d); 22 | EXPECT_EQ(fmt::format_args(args).get(0).type(), 23 | fmt::detail::type::custom_type); 24 | } 25 | #endif 26 | -------------------------------------------------------------------------------- /test/noexception-test.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - Noexception tests 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include "fmt/args.h" 9 | #include "fmt/base.h" 10 | #include "fmt/chrono.h" 11 | #include "fmt/color.h" 12 | #include "fmt/compile.h" 13 | #include "fmt/format.h" 14 | #include "fmt/os.h" 15 | #include "fmt/ostream.h" 16 | #include "fmt/printf.h" 17 | #include "fmt/ranges.h" 18 | #include "fmt/xchar.h" 19 | -------------------------------------------------------------------------------- /test/ostream-test.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - std::ostream support tests 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include 9 | 10 | #include "fmt/format.h" 11 | 12 | using fmt::runtime; 13 | 14 | struct test {}; 15 | 16 | // Test that there is no issues with specializations when fmt/ostream.h is 17 | // included after fmt/format.h. 18 | namespace fmt { 19 | template <> struct formatter : formatter { 20 | auto format(const test&, format_context& ctx) const -> decltype(ctx.out()) { 21 | return formatter::format(42, ctx); 22 | } 23 | }; 24 | } // namespace fmt 25 | 26 | #include 27 | 28 | #include "fmt/compile.h" 29 | #include "fmt/ostream.h" 30 | #include "fmt/ranges.h" 31 | #include "gmock/gmock.h" 32 | #include "gtest-extra.h" 33 | #include "util.h" 34 | 35 | auto operator<<(std::ostream& os, const date& d) -> std::ostream& { 36 | os << d.year() << '-' << d.month() << '-' << d.day(); 37 | return os; 38 | } 39 | 40 | auto operator<<(std::wostream& os, const date& d) -> std::wostream& { 41 | os << d.year() << L'-' << d.month() << L'-' << d.day(); 42 | return os; 43 | } 44 | 45 | // Make sure that overloaded comma operators do no harm to is_streamable. 46 | struct type_with_comma_op {}; 47 | template void operator,(type_with_comma_op, const T&); 48 | template type_with_comma_op operator<<(T&, const date&); 49 | 50 | enum streamable_enum {}; 51 | 52 | auto operator<<(std::ostream& os, streamable_enum) -> std::ostream& { 53 | return os << "streamable_enum"; 54 | } 55 | 56 | enum unstreamable_enum {}; 57 | auto format_as(unstreamable_enum e) -> int { return e; } 58 | 59 | struct empty_test {}; 60 | auto operator<<(std::ostream& os, empty_test) -> std::ostream& { 61 | return os << ""; 62 | } 63 | 64 | namespace fmt { 65 | template <> struct formatter : ostream_formatter {}; 66 | template <> struct formatter : ostream_formatter {}; 67 | template <> struct formatter : ostream_formatter {}; 68 | template <> struct formatter : ostream_formatter {}; 69 | } // namespace fmt 70 | 71 | TEST(ostream_test, enum) { 72 | EXPECT_EQ("streamable_enum", fmt::format("{}", streamable_enum())); 73 | EXPECT_EQ("0", fmt::format("{}", unstreamable_enum())); 74 | } 75 | 76 | TEST(ostream_test, format) { 77 | EXPECT_EQ("a string", fmt::format("{0}", test_string("a string"))); 78 | EXPECT_EQ("The date is 2012-12-9", 79 | fmt::format("The date is {0}", date(2012, 12, 9))); 80 | } 81 | 82 | TEST(ostream_test, format_specs) { 83 | using fmt::format_error; 84 | EXPECT_EQ("def ", fmt::format("{0:<5}", test_string("def"))); 85 | EXPECT_EQ(" def", fmt::format("{0:>5}", test_string("def"))); 86 | EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def"))); 87 | EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def"))); 88 | EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), test_string()), 89 | format_error, "invalid format specifier"); 90 | EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), test_string()), 91 | format_error, "invalid format specifier"); 92 | EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), test_string()), 93 | format_error, "invalid format specifier"); 94 | EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), test_string()), 95 | format_error, "invalid format specifier"); 96 | EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), test_string()), 97 | format_error, "format specifier requires numeric argument"); 98 | EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test"))); 99 | EXPECT_EQ("test ", fmt::format("{0:{1}}", test_string("test"), 13)); 100 | EXPECT_EQ("te", fmt::format("{0:.2}", test_string("test"))); 101 | EXPECT_EQ("te", fmt::format("{0:.{1}}", test_string("test"), 2)); 102 | } 103 | 104 | TEST(ostream_test, empty_custom_output) { 105 | EXPECT_EQ("", fmt::format("{}", empty_test())); 106 | } 107 | 108 | TEST(ostream_test, print) { 109 | { 110 | std::ostringstream os; 111 | fmt::print(os, "Don't {}!", "panic"); 112 | EXPECT_EQ("Don't panic!", os.str()); 113 | } 114 | 115 | { 116 | std::ostringstream os; 117 | fmt::println(os, "Don't {}!", "panic"); 118 | EXPECT_EQ("Don't panic!\n", os.str()); 119 | } 120 | } 121 | 122 | TEST(ostream_test, write_to_ostream) { 123 | std::ostringstream os; 124 | fmt::memory_buffer buffer; 125 | const char* foo = "foo"; 126 | buffer.append(foo, foo + std::strlen(foo)); 127 | fmt::detail::write_buffer(os, buffer); 128 | EXPECT_EQ("foo", os.str()); 129 | } 130 | 131 | TEST(ostream_test, write_to_ostream_max_size) { 132 | auto max_size = fmt::detail::max_value(); 133 | auto max_streamsize = fmt::detail::max_value(); 134 | if (max_size <= fmt::detail::to_unsigned(max_streamsize)) return; 135 | 136 | struct test_buffer final : fmt::detail::buffer { 137 | explicit test_buffer(size_t size) 138 | : fmt::detail::buffer([](buffer&, size_t) {}, nullptr, size, 139 | size) {} 140 | } buffer(max_size); 141 | 142 | struct mock_streambuf : std::streambuf { 143 | MOCK_METHOD(std::streamsize, xsputn, (const void*, std::streamsize)); 144 | auto xsputn(const char* s, std::streamsize n) -> std::streamsize override { 145 | const void* v = s; 146 | return xsputn(v, n); 147 | } 148 | } streambuf; 149 | 150 | struct test_ostream : std::ostream { 151 | explicit test_ostream(mock_streambuf& output_buffer) 152 | : std::ostream(&output_buffer) {} 153 | } os(streambuf); 154 | 155 | testing::InSequence sequence; 156 | const char* data = nullptr; 157 | using ustreamsize = std::make_unsigned::type; 158 | ustreamsize size = max_size; 159 | do { 160 | auto n = std::min(size, fmt::detail::to_unsigned(max_streamsize)); 161 | EXPECT_CALL(streambuf, xsputn(data, static_cast(n))) 162 | .WillOnce(testing::Return(max_streamsize)); 163 | data += n; 164 | size -= n; 165 | } while (size != 0); 166 | fmt::detail::write_buffer(os, buffer); 167 | } 168 | 169 | TEST(ostream_test, join) { 170 | int v[3] = {1, 2, 3}; 171 | EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join(v, v + 3, ", "))); 172 | } 173 | 174 | TEST(ostream_test, join_fallback_formatter) { 175 | auto strs = std::vector{test_string("foo"), test_string("bar")}; 176 | EXPECT_EQ("foo, bar", fmt::format("{}", fmt::join(strs, ", "))); 177 | } 178 | 179 | #if FMT_USE_CONSTEXPR 180 | TEST(ostream_test, constexpr_string) { 181 | EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), std::string("42"))); 182 | EXPECT_EQ("a string", 183 | fmt::format(FMT_STRING("{0}"), test_string("a string"))); 184 | } 185 | #endif 186 | 187 | namespace fmt_test { 188 | struct abc {}; 189 | 190 | template auto operator<<(Output& out, abc) -> Output& { 191 | return out << "abc"; 192 | } 193 | } // namespace fmt_test 194 | 195 | template struct test_template {}; 196 | 197 | template 198 | auto operator<<(std::ostream& os, test_template) -> std::ostream& { 199 | return os << 1; 200 | } 201 | 202 | namespace fmt { 203 | template struct formatter> : formatter { 204 | auto format(test_template, format_context& ctx) const 205 | -> decltype(ctx.out()) { 206 | return formatter::format(2, ctx); 207 | } 208 | }; 209 | 210 | template <> struct formatter : ostream_formatter {}; 211 | } // namespace fmt 212 | 213 | TEST(ostream_test, template) { 214 | EXPECT_EQ("2", fmt::format("{}", test_template())); 215 | } 216 | 217 | TEST(ostream_test, format_to_n) { 218 | char buffer[4]; 219 | buffer[3] = 'x'; 220 | auto result = fmt::format_to_n(buffer, 3, "{}", fmt_test::abc()); 221 | EXPECT_EQ(3u, result.size); 222 | EXPECT_EQ(buffer + 3, result.out); 223 | EXPECT_EQ("abcx", fmt::string_view(buffer, 4)); 224 | result = fmt::format_to_n(buffer, 3, "x{}y", fmt_test::abc()); 225 | EXPECT_EQ(5u, result.size); 226 | EXPECT_EQ(buffer + 3, result.out); 227 | EXPECT_EQ("xabx", fmt::string_view(buffer, 4)); 228 | } 229 | 230 | struct copyfmt_test {}; 231 | 232 | std::ostream& operator<<(std::ostream& os, copyfmt_test) { 233 | std::ios ios(nullptr); 234 | ios.copyfmt(os); 235 | return os << "foo"; 236 | } 237 | 238 | namespace fmt { 239 | template <> struct formatter : ostream_formatter {}; 240 | } // namespace fmt 241 | 242 | TEST(ostream_test, copyfmt) { 243 | EXPECT_EQ("foo", fmt::format("{}", copyfmt_test())); 244 | } 245 | 246 | TEST(ostream_test, to_string) { 247 | EXPECT_EQ("abc", fmt::to_string(fmt_test::abc())); 248 | } 249 | 250 | TEST(ostream_test, range) { 251 | auto strs = std::vector{test_string("foo"), test_string("bar")}; 252 | EXPECT_EQ("[foo, bar]", fmt::format("{}", strs)); 253 | } 254 | 255 | struct abstract { 256 | virtual ~abstract() = default; 257 | virtual void f() = 0; 258 | friend auto operator<<(std::ostream& os, const abstract&) -> std::ostream& { 259 | return os; 260 | } 261 | }; 262 | 263 | namespace fmt { 264 | template <> struct formatter : ostream_formatter {}; 265 | } // namespace fmt 266 | 267 | void format_abstract_compiles(const abstract& a) { 268 | fmt::format(FMT_COMPILE("{}"), a); 269 | } 270 | 271 | TEST(ostream_test, is_formattable) { 272 | EXPECT_TRUE(fmt::is_formattable()); 273 | EXPECT_TRUE(fmt::is_formattable>()); 274 | } 275 | 276 | struct streamable_and_unformattable {}; 277 | 278 | auto operator<<(std::ostream& os, streamable_and_unformattable) 279 | -> std::ostream& { 280 | return os << "foo"; 281 | } 282 | 283 | TEST(ostream_test, streamed) { 284 | EXPECT_FALSE(fmt::is_formattable()); 285 | EXPECT_EQ(fmt::format("{}", fmt::streamed(streamable_and_unformattable())), 286 | "foo"); 287 | } 288 | 289 | TEST(ostream_test, closed_ofstream) { 290 | std::ofstream ofs; 291 | fmt::print(ofs, "discard"); 292 | } 293 | 294 | struct unlocalized {}; 295 | 296 | auto operator<<(std::ostream& os, unlocalized) -> std::ostream& { 297 | return os << 12345; 298 | } 299 | 300 | namespace fmt { 301 | template <> struct formatter : ostream_formatter {}; 302 | } // namespace fmt 303 | 304 | TEST(ostream_test, unlocalized) { 305 | auto loc = get_locale("en_US.UTF-8"); 306 | std::locale::global(loc); 307 | EXPECT_EQ(fmt::format(loc, "{}", unlocalized()), "12345"); 308 | } 309 | -------------------------------------------------------------------------------- /test/perf-sanity.cc: -------------------------------------------------------------------------------- 1 | // A quick and dirty performance test. 2 | // For actual benchmarks see https://github.com/fmtlib/format-benchmark. 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "fmt/format.h" 9 | 10 | int main() { 11 | const int n = 10000000; 12 | 13 | auto start = std::chrono::steady_clock::now(); 14 | for (int iteration = 0; iteration < n; ++iteration) { 15 | auto buf = fmt::memory_buffer(); 16 | fmt::format_to(std::back_inserter(buf), 17 | "Hello, {}. The answer is {} and {}.", 1, 2345, 6789); 18 | } 19 | std::atomic_signal_fence(std::memory_order_acq_rel); // Clobber memory. 20 | auto end = std::chrono::steady_clock::now(); 21 | 22 | // Print time in milliseconds. 23 | std::chrono::duration duration = end - start; 24 | fmt::print("{:.1f}\n", duration.count() * 1000); 25 | } 26 | -------------------------------------------------------------------------------- /test/posix-mock.h: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - mocks of POSIX functions 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #ifndef FMT_POSIX_TEST_H 9 | #define FMT_POSIX_TEST_H 10 | 11 | #include 12 | #include 13 | #include 14 | #ifdef __APPLE__ 15 | # include 16 | #endif 17 | 18 | #ifdef _WIN32 19 | # include 20 | #else 21 | # include // for FreeBSD version 22 | # include // for ssize_t 23 | #endif 24 | 25 | #ifndef _MSC_VER 26 | struct stat; 27 | #endif 28 | 29 | namespace test { 30 | 31 | #ifndef _MSC_VER 32 | // Size type for read and write. 33 | using size_t = size_t; 34 | using ssize_t = ssize_t; 35 | int open(const char* path, int oflag, int mode); 36 | int fstat(int fd, struct stat* buf); 37 | #else 38 | using size_t = unsigned; 39 | using ssize_t = int; 40 | #endif 41 | 42 | #ifndef _WIN32 43 | long sysconf(int name); 44 | #else 45 | DWORD GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh); 46 | #endif 47 | 48 | int close(int fildes); 49 | 50 | int dup(int fildes); 51 | int dup2(int fildes, int fildes2); 52 | 53 | FILE* fdopen(int fildes, const char* mode); 54 | 55 | ssize_t read(int fildes, void* buf, size_t nbyte); 56 | ssize_t write(int fildes, const void* buf, size_t nbyte); 57 | 58 | #ifndef _WIN32 59 | int pipe(int fildes[2]); 60 | #else 61 | int pipe(int* pfds, unsigned psize, int textmode); 62 | #endif 63 | 64 | FILE* fopen(const char* filename, const char* mode); 65 | int fclose(FILE* stream); 66 | int(fileno)(FILE* stream); 67 | 68 | #if defined(FMT_LOCALE) && !defined(_WIN32) 69 | locale_t newlocale(int category_mask, const char* locale, locale_t base); 70 | #endif 71 | } // namespace test 72 | 73 | #define FMT_SYSTEM(call) test::call 74 | 75 | #endif // FMT_POSIX_TEST_H 76 | -------------------------------------------------------------------------------- /test/ranges-odr-test.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - the core API 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include 9 | 10 | #include "fmt/format.h" 11 | #include "fmt/ranges.h" 12 | #include "gtest/gtest.h" 13 | 14 | // call fmt::format from another translation unit to test ODR 15 | TEST(ranges_odr_test, format_vector) { 16 | auto v = std::vector{1, 2, 3, 5, 7, 11}; 17 | EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]"); 18 | } 19 | -------------------------------------------------------------------------------- /test/scan-test.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - scanning API test 2 | // 3 | // Copyright (c) 2019 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include "scan.h" 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include "fmt/os.h" 16 | #include "gmock/gmock.h" 17 | #include "gtest-extra.h" 18 | 19 | TEST(scan_test, read_text) { 20 | fmt::string_view s = "foo"; 21 | auto end = fmt::scan_to(s, "foo"); 22 | EXPECT_EQ(end, s.end()); 23 | EXPECT_THROW_MSG(fmt::scan("fob", "foo"), fmt::format_error, 24 | "invalid input"); 25 | } 26 | 27 | TEST(scan_test, read_int) { 28 | EXPECT_EQ(fmt::scan("42", "{}")->value(), 42); 29 | EXPECT_EQ(fmt::scan("-42", "{}")->value(), -42); 30 | EXPECT_EQ(fmt::scan("42", "{:}")->value(), 42); 31 | EXPECT_THROW_MSG(fmt::scan(std::to_string(INT_MAX + 1u), "{}"), 32 | fmt::format_error, "number is too big"); 33 | } 34 | 35 | TEST(scan_test, read_long_long) { 36 | EXPECT_EQ(fmt::scan("42", "{}")->value(), 42); 37 | EXPECT_EQ(fmt::scan("-42", "{}")->value(), -42); 38 | } 39 | 40 | TEST(scan_test, read_uint) { 41 | EXPECT_EQ(fmt::scan("42", "{}")->value(), 42); 42 | EXPECT_THROW_MSG(fmt::scan("-42", "{}"), fmt::format_error, 43 | "invalid input"); 44 | } 45 | 46 | TEST(scan_test, read_ulong_long) { 47 | EXPECT_EQ(fmt::scan("42", "{}")->value(), 42); 48 | EXPECT_THROW_MSG(fmt::scan("-42", "{}")->value(), 49 | fmt::format_error, "invalid input"); 50 | } 51 | 52 | TEST(scan_test, read_hex) { 53 | EXPECT_EQ(fmt::scan("2a", "{:x}")->value(), 42); 54 | auto num_digits = std::numeric_limits::digits / 4; 55 | EXPECT_THROW_MSG( 56 | fmt::scan(fmt::format("1{:0{}}", 0, num_digits), "{:x}") 57 | ->value(), 58 | fmt::format_error, "number is too big"); 59 | } 60 | 61 | TEST(scan_test, read_string) { 62 | EXPECT_EQ(fmt::scan("foo", "{}")->value(), "foo"); 63 | } 64 | 65 | TEST(scan_test, read_string_view) { 66 | EXPECT_EQ(fmt::scan("foo", "{}")->value(), "foo"); 67 | } 68 | 69 | TEST(scan_test, separator) { 70 | int n1 = 0, n2 = 0; 71 | fmt::scan_to("10 20", "{} {}", n1, n2); 72 | EXPECT_EQ(n1, 10); 73 | EXPECT_EQ(n2, 20); 74 | } 75 | 76 | struct num { 77 | int value; 78 | }; 79 | 80 | namespace fmt { 81 | template <> struct scanner { 82 | bool hex = false; 83 | 84 | auto parse(scan_parse_context& ctx) -> scan_parse_context::iterator { 85 | auto it = ctx.begin(), end = ctx.end(); 86 | if (it != end && *it == 'x') { 87 | hex = true; 88 | ++it; 89 | } 90 | if (it != end && *it != '}') report_error("invalid format"); 91 | return it; 92 | } 93 | 94 | template 95 | auto scan(num& n, ScanContext& ctx) const -> typename ScanContext::iterator { 96 | return hex ? scan_to(ctx, "{:x}", n.value) : scan_to(ctx, "{}", n.value); 97 | } 98 | }; 99 | } // namespace fmt 100 | 101 | TEST(scan_test, read_custom) { 102 | EXPECT_EQ(fmt::scan("42", "{}")->value().value, 42); 103 | EXPECT_EQ(fmt::scan("2a", "{:x}")->value().value, 42); 104 | } 105 | 106 | TEST(scan_test, invalid_format) { 107 | EXPECT_THROW_MSG(fmt::scan_to("", "{}"), fmt::format_error, 108 | "argument index out of range"); 109 | EXPECT_THROW_MSG(fmt::scan_to("", "{"), fmt::format_error, 110 | "invalid format string"); 111 | } 112 | 113 | namespace std { 114 | using fmt::scan; 115 | using fmt::scan_error; 116 | } // namespace std 117 | 118 | TEST(scan_test, example) { 119 | // Example from https://wg21.link/p1729r3. 120 | if (auto result = std::scan("answer = 42", "{} = {}")) { 121 | auto range = result->range(); 122 | EXPECT_EQ(range.begin(), range.end()); 123 | EXPECT_EQ(result->begin(), result->end()); 124 | #ifdef __cpp_structured_bindings 125 | const auto& [key, value] = result->values(); 126 | EXPECT_EQ(key, "answer"); 127 | EXPECT_EQ(value, 42); 128 | #endif 129 | } else { 130 | std::scan_error error = result.error(); 131 | (void)error; 132 | FAIL(); 133 | } 134 | } 135 | 136 | TEST(scan_test, end_of_input) { fmt::scan("", "{}"); } 137 | 138 | #if FMT_USE_FCNTL 139 | TEST(scan_test, file) { 140 | auto pipe = fmt::pipe(); 141 | 142 | fmt::string_view input = "10 20"; 143 | pipe.write_end.write(input.data(), input.size()); 144 | pipe.write_end.close(); 145 | 146 | int n1 = 0, n2 = 0; 147 | fmt::buffered_file f = pipe.read_end.fdopen("r"); 148 | fmt::scan_to(f.get(), "{} {}", n1, n2); 149 | EXPECT_EQ(n1, 10); 150 | EXPECT_EQ(n2, 20); 151 | } 152 | 153 | TEST(scan_test, lock) { 154 | auto pipe = fmt::pipe(); 155 | 156 | std::thread producer([&]() { 157 | fmt::string_view input = "42 "; 158 | for (int i = 0; i < 1000; ++i) 159 | pipe.write_end.write(input.data(), input.size()); 160 | pipe.write_end.close(); 161 | }); 162 | 163 | std::atomic count(0); 164 | fmt::buffered_file f = pipe.read_end.fdopen("r"); 165 | auto fun = [&]() { 166 | int value = 0; 167 | while (fmt::scan_to(f.get(), "{}", value)) { 168 | if (value != 42) { 169 | pipe.read_end.close(); 170 | EXPECT_EQ(value, 42); 171 | break; 172 | } 173 | ++count; 174 | } 175 | }; 176 | std::thread consumer1(fun); 177 | std::thread consumer2(fun); 178 | 179 | producer.join(); 180 | consumer1.join(); 181 | consumer2.join(); 182 | EXPECT_EQ(count, 1000); 183 | } 184 | #endif // FMT_USE_FCNTL 185 | -------------------------------------------------------------------------------- /test/static-export-test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8...3.25) 2 | 3 | project(fmt-link CXX) 4 | 5 | set(BUILD_SHARED_LIBS OFF) 6 | set(CMAKE_VISIBILITY_INLINES_HIDDEN TRUE) 7 | set(CMAKE_CXX_VISIBILITY_PRESET "hidden") 8 | 9 | # Broken LTO on GCC 4 10 | if (CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5) 11 | set(BROKEN_LTO ON) 12 | endif () 13 | 14 | if (NOT BROKEN_LTO AND CMAKE_VERSION VERSION_GREATER "3.8") 15 | # CMake 3.9+ 16 | include(CheckIPOSupported) 17 | check_ipo_supported(RESULT HAVE_IPO) 18 | if (HAVE_IPO) 19 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) 20 | endif () 21 | endif () 22 | 23 | add_subdirectory(../.. fmt) 24 | set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) 25 | 26 | add_library(library-test SHARED library.cc) 27 | target_link_libraries(library-test PRIVATE fmt::fmt) 28 | 29 | add_executable(exe-test main.cc) 30 | target_link_libraries(exe-test PRIVATE library-test) 31 | -------------------------------------------------------------------------------- /test/static-export-test/library.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | __attribute__((visibility("default"))) std::string foo() { 4 | return fmt::format(FMT_COMPILE("foo bar {}"), 4242); 5 | } 6 | -------------------------------------------------------------------------------- /test/static-export-test/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern std::string foo(); 5 | 6 | int main() { std::cout << foo() << std::endl; } 7 | -------------------------------------------------------------------------------- /test/test-assert.h: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - test version of FMT_ASSERT 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #ifndef FMT_TEST_ASSERT_H_ 9 | #define FMT_TEST_ASSERT_H_ 10 | 11 | #include 12 | 13 | void throw_assertion_failure(const char* message); 14 | #define FMT_ASSERT(condition, message) \ 15 | ((condition) ? (void)0 : throw_assertion_failure(message)) 16 | 17 | #include "gtest/gtest.h" 18 | 19 | class assertion_failure : public std::logic_error { 20 | public: 21 | explicit assertion_failure(const char* message) : std::logic_error(message) {} 22 | 23 | private: 24 | virtual void avoid_weak_vtable(); 25 | }; 26 | 27 | void assertion_failure::avoid_weak_vtable() {} 28 | 29 | // We use a separate function (rather than throw directly from FMT_ASSERT) to 30 | // avoid GCC's -Wterminate warning when FMT_ASSERT is used in a destructor. 31 | inline void throw_assertion_failure(const char* message) { 32 | throw assertion_failure(message); 33 | } 34 | 35 | // Expects an assertion failure. 36 | #define EXPECT_ASSERT(stmt, message) \ 37 | FMT_TEST_THROW_(stmt, assertion_failure, message, GTEST_NONFATAL_FAILURE_) 38 | 39 | #endif // FMT_TEST_ASSERT_H_ 40 | -------------------------------------------------------------------------------- /test/test-main.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - test main function. 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include 9 | 10 | #include "gtest/gtest.h" 11 | 12 | #ifdef _WIN32 13 | # include 14 | #endif 15 | 16 | #ifdef _MSC_VER 17 | # include 18 | #endif 19 | 20 | int main(int argc, char** argv) { 21 | #ifdef _WIN32 22 | // Don't display any error dialogs. This also suppresses message boxes 23 | // on assertion failures in MinGW where _set_error_mode/CrtSetReportMode 24 | // doesn't help. 25 | SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | 26 | SEM_NOOPENFILEERRORBOX); 27 | #endif 28 | #ifdef _MSC_VER 29 | // Disable message boxes on assertion failures. 30 | _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); 31 | _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); 32 | _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); 33 | _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); 34 | #endif 35 | try { 36 | testing::InitGoogleTest(&argc, argv); 37 | testing::FLAGS_gtest_death_test_style = "threadsafe"; 38 | return RUN_ALL_TESTS(); 39 | } catch (...) { 40 | // Catch all exceptions to make Coverity happy. 41 | } 42 | return EXIT_FAILURE; 43 | } 44 | -------------------------------------------------------------------------------- /test/unicode-test.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - Unicode tests 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "fmt/chrono.h" 13 | #include "gmock/gmock.h" 14 | #include "util.h" // get_locale 15 | 16 | using testing::Contains; 17 | 18 | TEST(unicode_test, use_utf8) { EXPECT_TRUE(fmt::detail::use_utf8); } 19 | 20 | TEST(unicode_test, legacy_locale) { 21 | auto loc = get_locale("be_BY.CP1251", "Belarusian_Belarus.1251"); 22 | if (loc == std::locale::classic()) return; 23 | 24 | auto s = std::string(); 25 | try { 26 | s = fmt::format(loc, "Дзень тыдня: {:L}", fmt::weekday(1)); 27 | } catch (const fmt::format_error& e) { 28 | // Formatting can fail due to an unsupported encoding. 29 | fmt::print("Format error: {}\n", e.what()); 30 | return; 31 | } 32 | 33 | #if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 500 34 | auto&& os = std::ostringstream(); 35 | os.imbue(loc); 36 | auto tm = std::tm(); 37 | tm.tm_wday = 1; 38 | os << std::put_time(&tm, "%a"); 39 | auto wd = os.str(); 40 | if (wd == "??") { 41 | EXPECT_EQ(s, "Дзень тыдня: ??"); 42 | fmt::print("std::locale gives ?? as a weekday.\n"); 43 | return; 44 | } 45 | #endif 46 | EXPECT_THAT((std::vector{"Дзень тыдня: пн", "Дзень тыдня: Пан"}), 47 | Contains(s)); 48 | } 49 | -------------------------------------------------------------------------------- /test/util.cc: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - test utilities 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include "util.h" 9 | 10 | #include 11 | 12 | const char* const file_content = "Don't panic!"; 13 | 14 | fmt::buffered_file open_buffered_file(FILE** fp) { 15 | #if FMT_USE_FCNTL 16 | auto pipe = fmt::pipe(); 17 | pipe.write_end.write(file_content, std::strlen(file_content)); 18 | pipe.write_end.close(); 19 | fmt::buffered_file f = pipe.read_end.fdopen("r"); 20 | if (fp) *fp = f.get(); 21 | #else 22 | fmt::buffered_file f("test-file", "w"); 23 | fputs(file_content, f.get()); 24 | if (fp) *fp = f.get(); 25 | #endif 26 | return f; 27 | } 28 | 29 | std::locale do_get_locale(const char* name) { 30 | try { 31 | return std::locale(name); 32 | } catch (const std::runtime_error&) { 33 | } 34 | return std::locale::classic(); 35 | } 36 | 37 | std::locale get_locale(const char* name, const char* alt_name) { 38 | auto loc = do_get_locale(name); 39 | if (loc == std::locale::classic() && alt_name) loc = do_get_locale(alt_name); 40 | #ifdef __OpenBSD__ 41 | // Locales are not working in OpenBSD: 42 | // https://github.com/fmtlib/fmt/issues/3670. 43 | loc = std::locale::classic(); 44 | #endif 45 | if (loc == std::locale::classic()) 46 | fmt::print(stderr, "{} locale is missing.\n", name); 47 | return loc; 48 | } 49 | -------------------------------------------------------------------------------- /test/util.h: -------------------------------------------------------------------------------- 1 | // Formatting library for C++ - test utilities 2 | // 3 | // Copyright (c) 2012 - present, Victor Zverovich 4 | // All rights reserved. 5 | // 6 | // For the license information refer to format.h. 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "fmt/os.h" 14 | 15 | #ifdef _MSC_VER 16 | # define FMT_VSNPRINTF vsprintf_s 17 | #else 18 | # define FMT_VSNPRINTF vsnprintf 19 | #endif 20 | 21 | template 22 | void safe_sprintf(char (&buffer)[SIZE], const char* format, ...) { 23 | std::va_list args; 24 | va_start(args, format); 25 | FMT_VSNPRINTF(buffer, SIZE, format, args); 26 | va_end(args); 27 | } 28 | 29 | extern const char* const file_content; 30 | 31 | // Opens a buffered file for reading. 32 | auto open_buffered_file(FILE** fp = nullptr) -> fmt::buffered_file; 33 | 34 | inline auto safe_fopen(const char* filename, const char* mode) -> FILE* { 35 | #if defined(_WIN32) && !defined(__MINGW32__) 36 | // Fix MSVC warning about "unsafe" fopen. 37 | FILE* f = nullptr; 38 | errno = fopen_s(&f, filename, mode); 39 | return f; 40 | #else 41 | return std::fopen(filename, mode); 42 | #endif 43 | } 44 | 45 | template class basic_test_string { 46 | private: 47 | std::basic_string value_; 48 | 49 | static const Char empty[]; 50 | 51 | public: 52 | explicit basic_test_string(const Char* value = empty) : value_(value) {} 53 | 54 | auto value() const -> const std::basic_string& { return value_; } 55 | }; 56 | 57 | template const Char basic_test_string::empty[] = {0}; 58 | 59 | using test_string = basic_test_string; 60 | using test_wstring = basic_test_string; 61 | 62 | template 63 | auto operator<<(std::basic_ostream& os, const basic_test_string& s) 64 | -> std::basic_ostream& { 65 | os << s.value(); 66 | return os; 67 | } 68 | 69 | class date { 70 | int year_, month_, day_; 71 | 72 | public: 73 | date(int year, int month, int day) : year_(year), month_(month), day_(day) {} 74 | 75 | auto year() const -> int { return year_; } 76 | auto month() const -> int { return month_; } 77 | auto day() const -> int { return day_; } 78 | }; 79 | 80 | // Returns a locale with the given name if available or classic locale 81 | // otherwise. 82 | auto get_locale(const char* name, const char* alt_name = nullptr) 83 | -> std::locale; 84 | --------------------------------------------------------------------------------