pax_global_header00006660000000000000000000000064147452013340014515gustar00rootroot0000000000000052 comment=3eda91b2e1ce7d569f84ba295507c4cd8fd96910 argparse-3.2/000077500000000000000000000000001474520133400131655ustar00rootroot00000000000000argparse-3.2/.bazelrc000066400000000000000000000003251474520133400146100ustar00rootroot00000000000000build --enable_platform_specific_config build:linux --cxxopt=-std=c++17 build:windows --copt=/utf-8 build:windows --copt=/Zc:preprocessor build:windows --cxxopt=/std:c++17 build:windows --cxxopt=/Zc:__cplusplus argparse-3.2/.clang-format000066400000000000000000000063121474520133400155420ustar00rootroot00000000000000--- Language: Cpp # BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Right AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false BinPackArguments: true BinPackParameters: true BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 80 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IncludeBlocks: Preserve IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|gmock|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '(Test)?$' IndentCaseLabels: false IndentPPDirectives: None IndentWidth: 2 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right RawStringFormats: - Language: TextProto Delimiters: - 'pb' - 'proto' BasedOnStyle: google ReflowComments: true SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: c++17 TabWidth: 8 UseTab: Never ... argparse-3.2/.clang-tidy000066400000000000000000000020261474520133400152210ustar00rootroot00000000000000Checks: -*, clang-analyzer-*, cppcoreguidelines-avoid-c-arrays, cppcoreguidelines-special-member-functions, readability-*, CheckOptions: - { key: readability-identifier-naming.ClassCase, value: CamelCase } - { key: readability-identifier-naming.ConstexprVariableCase, value: lower_case } - { key: readability-identifier-naming.ConstexprVariableIgnoredRegexp, value: "^Is.+" } - { key: readability-identifier-naming.FunctionCase, value: lower_case } - { key: readability-identifier-naming.NamespaceCase, value: lower_case } - { key: readability-identifier-naming.ParameterCase, value: lower_case } - { key: readability-identifier-naming.PrivateMemberCase, value: lower_case } - { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ } - { key: readability-identifier-naming.StructCase, value: CamelCase } - { key: readability-identifier-naming.StructIgnoredRegexp, value: "parse_number" } - { key: readability-identifier-naming.VariableCase, value: lower_case } HeaderFilterRegex: 'argparse/.+\.hpp' argparse-3.2/.github/000077500000000000000000000000001474520133400145255ustar00rootroot00000000000000argparse-3.2/.github/workflows/000077500000000000000000000000001474520133400165625ustar00rootroot00000000000000argparse-3.2/.github/workflows/ci.yml000066400000000000000000000037641474520133400177120ustar00rootroot00000000000000 name: CI on: pull_request jobs: test: name: ${{ matrix.toolchain }} runs-on: ${{ matrix.os }} strategy: matrix: toolchain: - macos-latest-clang - macos-12-clang - ubuntu-latest-clang - ubuntu-latest-gcc - windows-2019-msvc - windows-latest-msvc - windows-latest-clang include: - toolchain: macos-latest-clang os: macos-latest c_compiler: clang cxx_compiler: clang++ - toolchain: macos-12-clang os: macos-latest c_compiler: clang cxx_compiler: clang++ - toolchain: ubuntu-latest-clang os: ubuntu-latest c_compiler: clang cxx_compiler: clang++ - toolchain: ubuntu-latest-gcc os: ubuntu-latest c_compiler: cc cxx_compiler: g++ - toolchain: windows-2019-msvc os: windows-2019 c_compiler: msvc cxx_compiler: msvc - toolchain: windows-latest-msvc os: windows-latest c_compiler: msvc cxx_compiler: msvc - toolchain: windows-latest-clang os: windows-latest c_compiler: clang-cl cxx_compiler: clang-cl cmake_opts: -T ClangCL steps: - name: Checkout Code uses: actions/checkout@v2 - name: Configure working-directory: test run: cmake -S . -B build ${{ matrix.cmake_opts }} env: CC: ${{ matrix.c_compiler }} CXX: ${{ matrix.cxx_compiler }} - name: Build for ${{ matrix.os }} with ${{ matrix.compiler }} working-directory: test run: cmake --build build - name: Test if: ${{ ! startsWith(matrix.os, 'windows') }} working-directory: test/build run: ./tests - name: Test (Windows) if: ${{ startsWith(matrix.os, 'windows') }} working-directory: test/build run: ./Debug/tests.exe argparse-3.2/.github/workflows/tidy-analysis-stage-01.yml000066400000000000000000000022301474520133400234130ustar00rootroot00000000000000# Insecure workflow with limited permissions that should provide analysis # results through an artifact. name: Tidy analysis on: pull_request jobs: clang-tidy: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 with: fetch-depth: 2 - name: Install clang-tidy run: | sudo apt-get update sudo apt-get install -y clang-tidy-12 - name: Prepare compile_commands.json run: cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - name: Create results directory run: mkdir clang-tidy-result - name: Analyze run: git diff -U0 HEAD^ | clang-tidy-diff-12.py -p1 -regex ".+hpp" -extra-arg=-Iinclude -extra-arg=-std=c++17 -export-fixes clang-tidy-result/fixes.yml - name: Save PR metadata run: | echo ${{ github.event.number }} > clang-tidy-result/pr-id.txt echo ${{ github.event.pull_request.head.repo.full_name }} > clang-tidy-result/pr-head-repo.txt echo ${{ github.event.pull_request.head.ref }} > clang-tidy-result/pr-head-ref.txt - uses: actions/upload-artifact@v4 with: name: clang-tidy-result path: clang-tidy-result/ argparse-3.2/.github/workflows/tidy-analysis-stage-02.yml000066400000000000000000000061331474520133400234220ustar00rootroot00000000000000# Secure workflow with access to repository secrets and GitHub token # for posting analysis results. name: Post the Tidy analysis results on: workflow_run: workflows: [ "Tidy analysis" ] types: [ completed ] jobs: clang-tidy-results: # Trigger the job only if the previous (insecure) workflow completed successfully if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-20.04 steps: - name: Download analysis results uses: actions/github-script@v3.1.0 with: script: | let artifacts = await github.actions.listWorkflowRunArtifacts({ owner: context.repo.owner, repo: context.repo.repo, run_id: ${{github.event.workflow_run.id }}, }); let matchArtifact = artifacts.data.artifacts.filter((artifact) => { return artifact.name == "clang-tidy-result" })[0]; let download = await github.actions.downloadArtifact({ owner: context.repo.owner, repo: context.repo.repo, artifact_id: matchArtifact.id, archive_format: "zip", }); let fs = require("fs"); fs.writeFileSync("${{github.workspace}}/clang-tidy-result.zip", Buffer.from(download.data)); - name: Set environment variables run: | mkdir clang-tidy-result unzip clang-tidy-result.zip -d clang-tidy-result echo "pr_id=$(cat clang-tidy-result/pr-id.txt)" >> $GITHUB_ENV echo "pr_head_repo=$(cat clang-tidy-result/pr-head-repo.txt)" >> $GITHUB_ENV echo "pr_head_ref=$(cat clang-tidy-result/pr-head-ref.txt)" >> $GITHUB_ENV - uses: actions/checkout@v3 with: repository: ${{ env.pr_head_repo }} ref: ${{ env.pr_head_ref }} persist-credentials: false - name: Redownload analysis results uses: actions/github-script@v3.1.0 with: script: | let artifacts = await github.actions.listWorkflowRunArtifacts({ owner: context.repo.owner, repo: context.repo.repo, run_id: ${{github.event.workflow_run.id }}, }); let matchArtifact = artifacts.data.artifacts.filter((artifact) => { return artifact.name == "clang-tidy-result" })[0]; let download = await github.actions.downloadArtifact({ owner: context.repo.owner, repo: context.repo.repo, artifact_id: matchArtifact.id, archive_format: "zip", }); let fs = require("fs"); fs.writeFileSync("${{github.workspace}}/clang-tidy-result.zip", Buffer.from(download.data)); - name: Extract analysis results run: | mkdir clang-tidy-result unzip clang-tidy-result.zip -d clang-tidy-result - name: Run clang-tidy-pr-comments action uses: platisd/clang-tidy-pr-comments@master with: github_token: ${{ secrets.GITHUB_TOKEN }} clang_tidy_fixes: clang-tidy-result/fixes.yml pull_request_id: ${{ env.pr_id }} argparse-3.2/.gitignore000066400000000000000000000107571474520133400151670ustar00rootroot00000000000000## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015 cache/options directory .vs/ .vscode/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json project.fragment.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted #*.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings node_modules/ orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml # CodeRush .cr/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # CMake build directory build # Cppcheck build directory analysis-cppcheck-build-dir # Ideas directory ideas desktop.iniimages/ # Ignore all bazel-* symlinks. There is no full list since this can change # based on the name of the directory bazel is cloned into. /bazel-* argparse-3.2/.stylua.toml000066400000000000000000000000271474520133400154600ustar00rootroot00000000000000indent_type = "Spaces" argparse-3.2/.travis.yml000066400000000000000000000015141474520133400152770ustar00rootroot00000000000000matrix: include: - os: linux dist: bionic language: cpp compiler: gcc addons: apt: sources: - ubuntu-toolchain-r-test packages: - g++-8 env: CXX=g++-8 CC=gcc-8 - os: osx osx_image: xcode10.2 language: cpp compiler: clang - os: windows language: bash env: CXX=cl.exe install: - | if [[ $TRAVIS_OS_NAME == 'windows' ]]; then choco install ninja cmake elif [[ $TRAVIS_OS_NAME == 'osx' ]]; then export PATH=~/Library/Python/3.7/bin:$PATH pip3 install --user ninja cmake else pipenv global 3.6 pip install --user ninja cmake fi script: - | if [[ $TRAVIS_OS_NAME == 'windows' ]]; then tools/build.bat else sh tools/build.sh fi - ./build/test/tests argparse-3.2/BUILD.bazel000066400000000000000000000002311474520133400150370ustar00rootroot00000000000000cc_library( name = "argparse", hdrs = ["include/argparse/argparse.hpp"], includes = ["include"], visibility = ["//visibility:public"], ) argparse-3.2/CMakeLists.txt000066400000000000000000000067111474520133400157320ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.12.4) if(NOT DEFINED PROJECT_NAME) set(ARGPARSE_IS_TOP_LEVEL ON) else() set(ARGPARSE_IS_TOP_LEVEL OFF) endif() project(argparse VERSION 3.2.0 DESCRIPTION "A single header argument parser for C++17" HOMEPAGE_URL "https://github.com/p-ranav/argparse" LANGUAGES CXX ) option(ARGPARSE_INSTALL "Include an install target" ${ARGPARSE_IS_TOP_LEVEL}) option(ARGPARSE_BUILD_TESTS "Build tests" ${ARGPARSE_IS_TOP_LEVEL}) option(ARGPARSE_BUILD_SAMPLES "Build samples" OFF) include(GNUInstallDirs) include(CMakePackageConfigHelpers) add_library(argparse INTERFACE) add_library(argparse::argparse ALIAS argparse) target_compile_features(argparse INTERFACE cxx_std_17) target_include_directories(argparse INTERFACE $ $) if(ARGPARSE_BUILD_SAMPLES) add_subdirectory(samples) endif() if(ARGPARSE_BUILD_TESTS) add_subdirectory(test) endif() if(ARGPARSE_INSTALL) install(TARGETS argparse EXPORT argparseConfig) install(EXPORT argparseConfig NAMESPACE argparse:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) install(FILES ${CMAKE_CURRENT_LIST_DIR}/include/argparse/argparse.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/argparse) set(CONFIG_FILE_NAME_WITHOUT_EXT "${PROJECT_NAME}Config") set(CMAKE_CONFIG_FILE_BASENAME "${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_FILE_NAME_WITHOUT_EXT}") set(CMAKE_CONFIG_VERSION_FILE_NAME "${CMAKE_CONFIG_FILE_BASENAME}-version.cmake") set(CMAKE_CONFIG_FILE_NAME "${CMAKE_CONFIG_FILE_BASENAME}.cmake") if(${CMAKE_VERSION} VERSION_GREATER "3.14") set(OPTIONAL_ARCH_INDEPENDENT "ARCH_INDEPENDENT") endif() write_basic_package_version_file("${CMAKE_CONFIG_VERSION_FILE_NAME}" COMPATIBILITY SameMajorVersion ${OPTIONAL_ARCH_INDEPENDENT} ) export(EXPORT argparseConfig NAMESPACE argparse::) install(FILES "${CMAKE_CONFIG_VERSION_FILE_NAME}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") set(PackagingTemplatesDir "${CMAKE_CURRENT_SOURCE_DIR}/packaging") set(CPACK_PACKAGE_NAME "${PROJECT_NAME}") set(CPACK_PACKAGE_VENDOR "argparse (C++) developers") set(CPACK_PACKAGE_DESCRIPTION "${PROJECT_DESCRIPTION}") set(CPACK_DEBIAN_PACKAGE_NAME "${CPACK_PACKAGE_NAME}") set(CPACK_RPM_PACKAGE_NAME "${CPACK_PACKAGE_NAME}") set(CPACK_PACKAGE_HOMEPAGE_URL "${PROJECT_HOMEPAGE_URL}") set(CPACK_PACKAGE_MAINTAINER "Pranav Srinivas Kumar") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_MAINTAINER}") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") set(CPACK_DEBIAN_PACKAGE_NAME "lib${PROJECT_NAME}-dev") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6-dev") set(CPACK_DEBIAN_PACKAGE_SUGGESTS "cmake, pkg-config, pkg-conf") set(CPACK_RPM_PACKAGE_NAME "lib${PROJECT_NAME}-devel") set(CPACK_RPM_PACKAGE_SUGGESTS "${CPACK_DEBIAN_PACKAGE_SUGGESTS}") set(CPACK_DEB_COMPONENT_INSTALL ON) set(CPACK_RPM_COMPONENT_INSTALL ON) set(CPACK_NSIS_COMPONENT_INSTALL ON) set(CPACK_DEBIAN_COMPRESSION_TYPE "xz") set(PKG_CONFIG_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc") configure_file("${PackagingTemplatesDir}/pkgconfig.pc.in" "${PKG_CONFIG_FILE_NAME}" @ONLY) install(FILES "${PKG_CONFIG_FILE_NAME}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" ) endif() include(CPack) argparse-3.2/CONTRIBUTING.md000066400000000000000000000011431474520133400154150ustar00rootroot00000000000000# Contributing Contributions are welcomed. Open a pull-request or an issue. ## Code of conduct This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code. [code-of-conduct]: https://github.com/spotify/code-of-conduct/blob/master/code-of-conduct.md ## Code Style This project prefers, but does not strictly enforce, a specific source code style. The style is described in `.clang-format` and `.clang-tidy`. To generate a clang-tidy report: ```bash clang-tidy --extra-arg=-std=c++17 --config-file=.clang-tidy include/argparse/argparse.hpp ``` argparse-3.2/LICENSE000066400000000000000000000021121474520133400141660ustar00rootroot00000000000000Copyright (c) 2018 Pranav Srinivas Kumar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.argparse-3.2/README.md000066400000000000000000001327051474520133400144540ustar00rootroot00000000000000

argparse

license version

## Highlights * Single header file * Requires C++17 * MIT License ## Table of Contents * [Quick Start](#quick-start) * [Positional Arguments](#positional-arguments) * [Optional Arguments](#optional-arguments) * [Requiring optional arguments](#requiring-optional-arguments) * [Accessing optional arguments without default values](#accessing-optional-arguments-without-default-values) * [Deciding if the value was given by the user](#deciding-if-the-value-was-given-by-the-user) * [Joining values of repeated optional arguments](#joining-values-of-repeated-optional-arguments) * [Repeating an argument to increase a value](#repeating-an-argument-to-increase-a-value) * [Mutually Exclusive Group](#mutually-exclusive-group) * [Storing values into variables](#store-into) * [Negative Numbers](#negative-numbers) * [Combining Positional and Optional Arguments](#combining-positional-and-optional-arguments) * [Printing Help](#printing-help) * [Adding a description and an epilog to help](#adding-a-description-and-an-epilog-to-help) * [List of Arguments](#list-of-arguments) * [Compound Arguments](#compound-arguments) * [Converting to Numeric Types](#converting-to-numeric-types) * [Default Arguments](#default-arguments) * [Gathering Remaining Arguments](#gathering-remaining-arguments) * [Parent Parsers](#parent-parsers) * [Subcommands](#subcommands) * [Getting Argument and Subparser Instances](#getting-argument-and-subparser-instances) * [Parse Known Args](#parse-known-args) * [Hidden argument and alias](#hidden-argument-alias) * [ArgumentParser in bool Context](#argumentparser-in-bool-context) * [Custom Prefix Characters](#custom-prefix-characters) * [Custom Assignment Characters](#custom-assignment-characters) * [Further Examples](#further-examples) * [Construct a JSON object from a filename argument](#construct-a-json-object-from-a-filename-argument) * [Positional Arguments with Compound Toggle Arguments](#positional-arguments-with-compound-toggle-arguments) * [Restricting the set of values for an argument](#restricting-the-set-of-values-for-an-argument) * [Using `option=value` syntax](#using-optionvalue-syntax) * [Advanced usage formatting](#advanced-usage-formatting) * [Developer Notes](#developer-notes) * [Copying and Moving](#copying-and-moving) * [CMake Integration](#cmake-integration) * [Building, Installing, and Testing](#building-installing-and-testing) * [Supported Toolchains](#supported-toolchains) * [Contributing](#contributing) * [License](#license) ## Quick Start Simply include argparse.hpp and you're good to go. ```cpp #include ``` To start parsing command-line arguments, create an ```ArgumentParser```. ```cpp argparse::ArgumentParser program("program_name"); ``` **NOTE:** There is an optional second argument to the `ArgumentParser` which is the program version. Example: `argparse::ArgumentParser program("libfoo", "1.9.0");` **NOTE:** There are optional third and fourth arguments to the `ArgumentParser` which control default arguments. Example: `argparse::ArgumentParser program("libfoo", "1.9.0", default_arguments::help, false);` See [Default Arguments](#default-arguments), below. To add a new argument, simply call ```.add_argument(...)```. You can provide a variadic list of argument names that you want to group together, e.g., ```-v``` and ```--verbose``` ```cpp program.add_argument("foo"); program.add_argument("-v", "--verbose"); // parameter packing ``` Argparse supports a variety of argument types including positional, optional, and compound arguments. Below you can see how to configure each of these types: ### Positional Arguments Here's an example of a ***positional argument***: ```cpp #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("program_name"); program.add_argument("square") .help("display the square of a given integer") .scan<'i', int>(); try { program.parse_args(argc, argv); } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } auto input = program.get("square"); std::cout << (input * input) << std::endl; return 0; } ``` And running the code: ```console foo@bar:/home/dev/$ ./main 15 225 ``` Here's what's happening: * The ```add_argument()``` method is used to specify which command-line options the program is willing to accept. In this case, I’ve named it square so that it’s in line with its function. * Command-line arguments are strings. To square the argument and print the result, we need to convert this argument to a number. In order to do this, we use the ```.scan``` method to convert user input into an integer. * We can get the value stored by the parser for a given argument using ```parser.get(key)``` method. ### Optional Arguments Now, let's look at ***optional arguments***. Optional arguments start with ```-``` or ```--```, e.g., ```--verbose``` or ```-a```. Optional arguments can be placed anywhere in the input sequence. ```cpp argparse::ArgumentParser program("test"); program.add_argument("--verbose") .help("increase output verbosity") .default_value(false) .implicit_value(true); try { program.parse_args(argc, argv); } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); } if (program["--verbose"] == true) { std::cout << "Verbosity enabled" << std::endl; } ``` ```console foo@bar:/home/dev/$ ./main --verbose Verbosity enabled ``` Here's what's happening: * The program is written so as to display something when --verbose is specified and display nothing when not. * Since the argument is actually optional, no error is thrown when running the program without ```--verbose```. Note that by using ```.default_value(false)```, if the optional argument isn’t used, it's value is automatically set to false. * By using ```.implicit_value(true)```, the user specifies that this option is more of a flag than something that requires a value. When the user provides the --verbose option, it's value is set to true. #### Flag When defining flag arguments, you can use the shorthand `flag()` which is the same as `default_value(false).implicit_value(true)`. ```cpp argparse::ArgumentParser program("test"); program.add_argument("--verbose") .help("increase output verbosity") .flag(); try { program.parse_args(argc, argv); } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); } if (program["--verbose"] == true) { std::cout << "Verbosity enabled" << std::endl; } ``` #### Requiring optional arguments There are scenarios where you would like to make an optional argument ***required***. As discussed above, optional arguments either begin with `-` or `--`. You can make these types of arguments required like so: ```cpp program.add_argument("-o", "--output") .required() .help("specify the output file."); ``` If the user does not provide a value for this parameter, an exception is thrown. Alternatively, you could provide a default value like so: ```cpp program.add_argument("-o", "--output") .default_value(std::string("-")) .required() .help("specify the output file."); ``` #### Accessing optional arguments without default values If you require an optional argument to be present but have no good default value for it, you can combine testing and accessing the argument as following: ```cpp if (auto fn = program.present("-o")) { do_something_with(*fn); } ``` Similar to `get`, the `present` method also accepts a template argument. But rather than returning `T`, `parser.present(key)` returns `std::optional`, so that when the user does not provide a value to this parameter, the return value compares equal to `std::nullopt`. #### Deciding if the value was given by the user If you want to know whether the user supplied a value for an argument that has a ```.default_value```, check whether the argument ```.is_used()```. ```cpp program.add_argument("--color") .default_value(std::string{"orange"}) // might otherwise be type const char* leading to an error when trying program.get .help("specify the cat's fur color"); try { program.parse_args(argc, argv); // Example: ./main --color orange } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); } auto color = program.get("--color"); // "orange" auto explicit_color = program.is_used("--color"); // true, user provided orange ``` #### Joining values of repeated optional arguments You may want to allow an optional argument to be repeated and gather all values in one place. ```cpp program.add_argument("--color") .default_value>({ "orange" }) .append() .help("specify the cat's fur color"); try { program.parse_args(argc, argv); // Example: ./main --color red --color green --color blue } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); } auto colors = program.get>("--color"); // {"red", "green", "blue"} ``` Notice that ```.default_value``` is given an explicit template parameter to match the type you want to ```.get```. #### Repeating an argument to increase a value A common pattern is to repeat an argument to indicate a greater value. ```cpp int verbosity = 0; program.add_argument("-V", "--verbose") .action([&](const auto &) { ++verbosity; }) .append() .default_value(false) .implicit_value(true) .nargs(0); program.parse_args(argc, argv); // Example: ./main -VVVV std::cout << "verbose level: " << verbosity << std::endl; // verbose level: 4 ``` #### Mutually Exclusive Group Create a mutually exclusive group using `program.add_mutually_exclusive_group(required = false)`. `argparse`` will make sure that only one of the arguments in the mutually exclusive group was present on the command line: ```cpp auto &group = program.add_mutually_exclusive_group(); group.add_argument("--first"); group.add_argument("--second"); ``` with the following usage will yield an error: ```console foo@bar:/home/dev/$ ./main --first 1 --second 2 Argument '--second VAR' not allowed with '--first VAR' ``` The `add_mutually_exclusive_group()` function also accepts a `required` argument, to indicate that at least one of the mutually exclusive arguments is required: ```cpp auto &group = program.add_mutually_exclusive_group(true); group.add_argument("--first"); group.add_argument("--second"); ``` with the following usage will yield an error: ```console foo@bar:/home/dev/$ ./main One of the arguments '--first VAR' or '--second VAR' is required ``` ### Storing values into variables It is possible to bind arguments to a variable storing their value, as an alternative to explicitly calling ``program.get(arg_name)`` or ``program[arg_name]`` This is currently implementeted for variables of type ``bool`` (this also implicitly calls ``flag()``), ``int``, ``double``, ``std::string``, ``std::vector`` and ``std::vector``. If the argument is not specified in the command line, the default value (if set) is set into the variable. ```cpp bool flagvar = false; program.add_argument("--flagvar").store_into(flagvar); int intvar = 0; program.add_argument("--intvar").store_into(intvar); double doublevar = 0; program.add_argument("--doublevar").store_into(doublevar); std::string strvar; program.add_argument("--strvar").store_into(strvar); std::vector strvar_repeated; program.add_argument("--strvar-repeated").append().store_into(strvar_repeated); std::vector strvar_multi_valued; program.add_argument("--strvar-multi-valued").nargs(2).store_into(strvar_multi_valued); std::vector intvar_repeated; program.add_argument("--intvar-repeated").append().store_into(intvar_repeated); std::vector intvar_multi_valued; program.add_argument("--intvar-multi-valued").nargs(2).store_into(intvar_multi_valued); ``` ### Negative Numbers Optional arguments start with ```-```. Can ```argparse``` handle negative numbers? The answer is yes! ```cpp argparse::ArgumentParser program; program.add_argument("integer") .help("Input number") .scan<'i', int>(); program.add_argument("floats") .help("Vector of floats") .nargs(4) .scan<'g', float>(); try { program.parse_args(argc, argv); } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); } // Some code to print arguments ``` ```console foo@bar:/home/dev/$ ./main -5 -1.1 -3.1415 -3.1e2 -4.51329E3 integer : -5 floats : -1.1 -3.1415 -310 -4513.29 ``` As you can see here, ```argparse``` supports negative integers, negative floats and scientific notation. ### Combining Positional and Optional Arguments ```cpp argparse::ArgumentParser program("main"); program.add_argument("square") .help("display the square of a given number") .scan<'i', int>(); program.add_argument("--verbose") .default_value(false) .implicit_value(true); try { program.parse_args(argc, argv); } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); } int input = program.get("square"); if (program["--verbose"] == true) { std::cout << "The square of " << input << " is " << (input * input) << std::endl; } else { std::cout << (input * input) << std::endl; } ``` ```console foo@bar:/home/dev/$ ./main 4 16 foo@bar:/home/dev/$ ./main 4 --verbose The square of 4 is 16 foo@bar:/home/dev/$ ./main --verbose 4 The square of 4 is 16 ``` ### Printing Help `std::cout << program` prints a help message, including the program usage and information about the arguments registered with the `ArgumentParser`. For the previous example, here's the default help message: ``` foo@bar:/home/dev/$ ./main --help Usage: main [-h] [--verbose] square Positional arguments: square display the square of a given number Optional arguments: -h, --help shows help message and exits -v, --version prints version information and exits --verbose ``` You may also get the help message in string via `program.help().str()`. #### Adding a description and an epilog to help `ArgumentParser::add_description` will add text before the detailed argument information. `ArgumentParser::add_epilog` will add text after all other help output. ```cpp #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("main"); program.add_argument("thing").help("Thing to use.").metavar("THING"); program.add_argument("--member").help("The alias for the member to pass to.").metavar("ALIAS"); program.add_argument("--verbose").default_value(false).implicit_value(true); program.add_description("Forward a thing to the next member."); program.add_epilog("Possible things include betingalw, chiz, and res."); program.parse_args(argc, argv); std::cout << program << std::endl; } ``` ```console Usage: main [-h] [--member ALIAS] [--verbose] THING Forward a thing to the next member. Positional arguments: THING Thing to use. Optional arguments: -h, --help shows help message and exits -v, --version prints version information and exits --member ALIAS The alias for the member to pass to. --verbose Possible things include betingalw, chiz, and res. ``` ### List of Arguments ArgumentParser objects usually associate a single command-line argument with a single action to be taken. The ```.nargs``` associates a different number of command-line arguments with a single action. When using ```nargs(N)```, N arguments from the command line will be gathered together into a list. ```cpp argparse::ArgumentParser program("main"); program.add_argument("--input_files") .help("The list of input files") .nargs(2); try { program.parse_args(argc, argv); // Example: ./main --input_files config.yml System.xml } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); } auto files = program.get>("--input_files"); // {"config.yml", "System.xml"} ``` ```ArgumentParser.get()``` has specializations for ```std::vector``` and ```std::list```. So, the following variant, ```.get```, will also work. ```cpp auto files = program.get>("--input_files"); // {"config.yml", "System.xml"} ``` Using ```.scan```, one can quickly build a list of desired value types from command line arguments. Here's an example: ```cpp argparse::ArgumentParser program("main"); program.add_argument("--query_point") .help("3D query point") .nargs(3) .default_value(std::vector{0.0, 0.0, 0.0}) .scan<'g', double>(); try { program.parse_args(argc, argv); // Example: ./main --query_point 3.5 4.7 9.2 } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); } auto query_point = program.get>("--query_point"); // {3.5, 4.7, 9.2} ``` You can also make a variable length list of arguments with the ```.nargs```. Below are some examples. ```cpp program.add_argument("--input_files") .nargs(1, 3); // This accepts 1 to 3 arguments. ``` Some useful patterns are defined like "?", "*", "+" of argparse in Python. ```cpp program.add_argument("--input_files") .nargs(argparse::nargs_pattern::any); // "*" in Python. This accepts any number of arguments including 0. ``` ```cpp program.add_argument("--input_files") .nargs(argparse::nargs_pattern::at_least_one); // "+" in Python. This accepts one or more number of arguments. ``` ```cpp program.add_argument("--input_files") .nargs(argparse::nargs_pattern::optional); // "?" in Python. This accepts an argument optionally. ``` ### Compound Arguments Compound arguments are optional arguments that are combined and provided as a single argument. Example: ```ps -aux``` ```cpp argparse::ArgumentParser program("test"); program.add_argument("-a") .default_value(false) .implicit_value(true); program.add_argument("-b") .default_value(false) .implicit_value(true); program.add_argument("-c") .nargs(2) .default_value(std::vector{0.0f, 0.0f}) .scan<'g', float>(); try { program.parse_args(argc, argv); // Example: ./main -abc 1.95 2.47 } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); } auto a = program.get("-a"); // true auto b = program.get("-b"); // true auto c = program.get>("-c"); // {1.95, 2.47} /// Some code that prints parsed arguments ``` ```console foo@bar:/home/dev/$ ./main -ac 3.14 2.718 a = true b = false c = {3.14, 2.718} foo@bar:/home/dev/$ ./main -cb a = false b = true c = {0.0, 0.0} ``` Here's what's happening: * We have three optional arguments ```-a```, ```-b``` and ```-c```. * ```-a``` and ```-b``` are toggle arguments. * ```-c``` requires 2 floating point numbers from the command-line. * argparse can handle compound arguments, e.g., ```-abc``` or ```-bac``` or ```-cab```. This only works with short single-character argument names. - ```-a``` and ```-b``` become true. - argv is further parsed to identify the inputs mapped to ```-c```. - If argparse cannot find any arguments to map to c, then c defaults to {0.0, 0.0} as defined by ```.default_value``` ### Converting to Numeric Types For inputs, users can express a primitive type for the value. The ```.scan``` method attempts to convert the incoming `std::string` to `T` following the `Shape` conversion specifier. An `std::invalid_argument` or `std::range_error` exception is thrown for errors. ```cpp program.add_argument("-x") .scan<'d', int>(); program.add_argument("scale") .scan<'g', double>(); ``` `Shape` specifies what the input "looks like", and the type template argument specifies the return value of the predefined action. Acceptable types are floating point (i.e float, double, long double) and integral (i.e. signed char, short, int, long, long long). The grammar follows `std::from_chars`, but does not exactly duplicate it. For example, hexadecimal numbers may begin with `0x` or `0X` and numbers with a leading zero may be handled as octal values. | Shape | interpretation | | :--------: | ----------------------------------------- | | 'a' or 'A' | hexadecimal floating point | | 'e' or 'E' | scientific notation (floating point) | | 'f' or 'F' | fixed notation (floating point) | | 'g' or 'G' | general form (either fixed or scientific) | | | | | 'd' | decimal | | 'i' | `std::from_chars` grammar with base == 10 | | 'o' | octal (unsigned) | | 'u' | decimal (unsigned) | | 'x' or 'X' | hexadecimal (unsigned) | ### Default Arguments `argparse` provides predefined arguments and actions for `-h`/`--help` and `-v`/`--version`. By default, these actions will **exit** the program after displaying a help or version message, respectively. This exit does not call destructors, skipping clean-up of taken resources. These default arguments can be disabled during `ArgumentParser` creation so that you can handle these arguments in your own way. (Note that a program name and version must be included when choosing default arguments.) ```cpp argparse::ArgumentParser program("test", "1.0", default_arguments::none); program.add_argument("-h", "--help") .action([=](const std::string& s) { std::cout << help().str(); }) .default_value(false) .help("shows help message") .implicit_value(true) .nargs(0); ``` The above code snippet outputs a help message and continues to run. It does not support a `--version` argument. The default is `default_arguments::all` for included arguments. No default arguments will be added with `default_arguments::none`. `default_arguments::help` and `default_arguments::version` will individually add `--help` and `--version`. The default arguments can be used while disabling the default exit with these arguments. This forth argument to `ArgumentParser` (`exit_on_default_arguments`) is a bool flag with a default **true** value. The following call will retain `--help` and `--version`, but will not exit when those arguments are used. ```cpp argparse::ArgumentParser program("test", "1.0", default_arguments::all, false) ``` ### Gathering Remaining Arguments `argparse` supports gathering "remaining" arguments at the end of the command, e.g., for use in a compiler: ```console foo@bar:/home/dev/$ compiler file1 file2 file3 ``` To enable this, simply create an argument and mark it as `remaining`. All remaining arguments passed to argparse are gathered here. ```cpp argparse::ArgumentParser program("compiler"); program.add_argument("files") .remaining(); try { program.parse_args(argc, argv); } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); } try { auto files = program.get>("files"); std::cout << files.size() << " files provided" << std::endl; for (auto& file : files) std::cout << file << std::endl; } catch (std::logic_error& e) { std::cout << "No files provided" << std::endl; } ``` When no arguments are provided: ```console foo@bar:/home/dev/$ ./compiler No files provided ``` and when multiple arguments are provided: ```console foo@bar:/home/dev/$ ./compiler foo.txt bar.txt baz.txt 3 files provided foo.txt bar.txt baz.txt ``` The process of gathering remaining arguments plays nicely with optional arguments too: ```cpp argparse::ArgumentParser program("compiler"); program.add_arguments("-o") .default_value(std::string("a.out")); program.add_argument("files") .remaining(); try { program.parse_args(argc, argv); } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); } auto output_filename = program.get("-o"); std::cout << "Output filename: " << output_filename << std::endl; try { auto files = program.get>("files"); std::cout << files.size() << " files provided" << std::endl; for (auto& file : files) std::cout << file << std::endl; } catch (std::logic_error& e) { std::cout << "No files provided" << std::endl; } ``` ```console foo@bar:/home/dev/$ ./compiler -o main foo.cpp bar.cpp baz.cpp Output filename: main 3 files provided foo.cpp bar.cpp baz.cpp ``` ***NOTE***: Remember to place all optional arguments BEFORE the remaining argument. If the optional argument is placed after the remaining arguments, it too will be deemed remaining: ```console foo@bar:/home/dev/$ ./compiler foo.cpp bar.cpp baz.cpp -o main 5 arguments provided foo.cpp bar.cpp baz.cpp -o main ``` ### Parent Parsers A parser may use arguments that could be used by other parsers. These shared arguments can be added to a parser which is then used as a "parent" for parsers which also need those arguments. One or more parent parsers may be added to a parser with `.add_parents`. The positional and optional arguments in each parent is added to the child parser. ```cpp argparse::ArgumentParser surface_parser("surface", "1.0", argparse::default_arguments::none); surface_parser.add_argument("--area") .default_value(0) .scan<'i', int>(); argparse::ArgumentParser floor_parser("floor"); floor_parser.add_argument("tile_size").scan<'i', int>(); floor_parser.add_parents(surface_parser); floor_parser.parse_args({ "./main", "--area", "200", "12" }); // --area = 200, tile_size = 12 argparse::ArgumentParser ceiling_parser("ceiling"); ceiling_parser.add_argument("--color"); ceiling_parser.add_parents(surface_parser); ceiling_parser.parse_args({ "./main", "--color", "gray" }); // --area = 0, --color = "gray" ``` Changes made to parents after they are added to a parser are not reflected in any child parsers. Completely initialize parent parsers before adding them to a parser. Each parser will have the standard set of default arguments. Disable the default arguments in parent parsers to avoid duplicate help output. ### Subcommands Many programs split up their functionality into a number of sub-commands, for example, the `git` program can invoke sub-commands like `git checkout`, `git add`, and `git commit`. Splitting up functionality this way can be a particularly good idea when a program performs several different functions which require different kinds of command-line arguments. `ArgumentParser` supports the creation of such sub-commands with the `add_subparser()` member function. ```cpp #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("git"); // git add subparser argparse::ArgumentParser add_command("add"); add_command.add_description("Add file contents to the index"); add_command.add_argument("files") .help("Files to add content from. Fileglobs (e.g. *.c) can be given to add all matching files.") .remaining(); // git commit subparser argparse::ArgumentParser commit_command("commit"); commit_command.add_description("Record changes to the repository"); commit_command.add_argument("-a", "--all") .help("Tell the command to automatically stage files that have been modified and deleted.") .default_value(false) .implicit_value(true); commit_command.add_argument("-m", "--message") .help("Use the given as the commit message."); // git cat-file subparser argparse::ArgumentParser catfile_command("cat-file"); catfile_command.add_description("Provide content or type and size information for repository objects"); catfile_command.add_argument("-t") .help("Instead of the content, show the object type identified by ."); catfile_command.add_argument("-p") .help("Pretty-print the contents of based on its type."); // git submodule subparser argparse::ArgumentParser submodule_command("submodule"); submodule_command.add_description("Initialize, update or inspect submodules"); argparse::ArgumentParser submodule_update_command("update"); submodule_update_command.add_description("Update the registered submodules to match what the superproject expects"); submodule_update_command.add_argument("--init") .default_value(false) .implicit_value(true); submodule_update_command.add_argument("--recursive") .default_value(false) .implicit_value(true); submodule_command.add_subparser(submodule_update_command); program.add_subparser(add_command); program.add_subparser(commit_command); program.add_subparser(catfile_command); program.add_subparser(submodule_command); try { program.parse_args(argc, argv); } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } // Use arguments } ``` ```console foo@bar:/home/dev/$ ./git --help Usage: git [-h] {add,cat-file,commit,submodule} Optional arguments: -h, --help shows help message and exits -v, --version prints version information and exits Subcommands: add Add file contents to the index cat-file Provide content or type and size information for repository objects commit Record changes to the repository submodule Initialize, update or inspect submodules foo@bar:/home/dev/$ ./git add --help Usage: add [-h] files Add file contents to the index Positional arguments: files Files to add content from. Fileglobs (e.g. *.c) can be given to add all matching files. Optional arguments: -h, --help shows help message and exits -v, --version prints version information and exits foo@bar:/home/dev/$ ./git commit --help Usage: commit [-h] [--all] [--message VAR] Record changes to the repository Optional arguments: -h, --help shows help message and exits -v, --version prints version information and exits -a, --all Tell the command to automatically stage files that have been modified and deleted. -m, --message Use the given as the commit message. foo@bar:/home/dev/$ ./git submodule --help Usage: submodule [-h] {update} Initialize, update or inspect submodules Optional arguments: -h, --help shows help message and exits -v, --version prints version information and exits Subcommands: update Update the registered submodules to match what the superproject expects ``` When a help message is requested from a subparser, only the help for that particular parser will be printed. The help message will not include parent parser or sibling parser messages. Additionally, every parser has the `.is_subcommand_used("")` and `.is_subcommand_used(subparser)` member functions to check if a subcommand was used. Sometimes there may be a need to hide part of the subcommands from the user by suppressing information about them in an help message. To do this, ```ArgumentParser``` contains the method ```.set_suppress(bool suppress)```: ```cpp argparse::ArgumentParser program("test"); argparse::ArgumentParser hidden_cmd("hidden"); hidden_cmd.add_argument("files").remaining(); hidden_cmd.set_suppress(true); program.add_subparser(hidden_cmd); ``` ```console foo@bar:/home/dev/$ ./main -h Usage: test [--help] [--version] {} Optional arguments: -h, --help shows help message and exits -v, --version prints version information and exits foo@bar:/home/dev/$ ./main hidden -h Usage: hidden [--help] [--version] files Positional arguments: files [nargs: 0 or more] Optional arguments: -h, --help shows help message and exits -v, --version prints version information and exits ``` ### Getting Argument and Subparser Instances ```Argument``` and ```ArgumentParser``` instances added to an ```ArgumentParser``` can be retrieved with ```.at()```. The default return type is ```Argument```. ```cpp argparse::ArgumentParser program("test"); program.add_argument("--dir"); program.at("--dir").default_value(std::string("/home/user")); program.add_subparser(argparse::ArgumentParser{"walk"}); program.at("walk").add_argument("depth"); ``` ### Parse Known Args Sometimes a program may only parse a few of the command-line arguments, passing the remaining arguments on to another script or program. In these cases, the `parse_known_args()` function can be useful. It works much like `parse_args()` except that it does not produce an error when extra arguments are present. Instead, it returns a list of remaining argument strings. ```cpp #include #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("test"); program.add_argument("--foo").implicit_value(true).default_value(false); program.add_argument("bar"); auto unknown_args = program.parse_known_args({"test", "--foo", "--badger", "BAR", "spam"}); assert(program.get("--foo") == true); assert(program.get("bar") == std::string{"BAR"}); assert((unknown_args == std::vector{"--badger", "spam"})); } ``` ### Hidden argument and alias It is sometimes desirable to offer an alias for an argument, but without it appearing it in the usage. For example, to phase out a deprecated wording of an argument while not breaking backwards compatible. This can be done with the ``ArgumentParser::add_hidden_alias_for()` method. ```cpp argparse::ArgumentParser program("test"); auto &arg = program.add_argument("--suppress").flag(); program.add_hidden_alias_for(arg, "--supress"); // old misspelled alias ``` The ``Argument::hidden()`` method can also be used to prevent a (generally optional) argument from appearing in the usage or help. ```cpp argparse::ArgumentParser program("test"); program.add_argument("--non-documented").flag().hidden(); ``` This can also be used on positional arguments, but in that later case it only makes sense in practice for the last ones. ### ArgumentParser in bool Context An `ArgumentParser` is `false` until it (or one of its subparsers) have extracted known value(s) with `.parse_args` or `.parse_known_args`. When using `.parse_known_args`, unknown arguments will not make a parser `true`. ### Custom Prefix Characters Most command-line options will use `-` as the prefix, e.g. `-f/--foo`. Parsers that need to support different or additional prefix characters, e.g. for options like `+f` or `/foo`, may specify them using the `set_prefix_chars()`. The default prefix character is `-`. ```cpp #include #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("test"); program.set_prefix_chars("-+/"); program.add_argument("+f"); program.add_argument("--bar"); program.add_argument("/foo"); try { program.parse_args(argc, argv); } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } if (program.is_used("+f")) { std::cout << "+f : " << program.get("+f") << "\n"; } if (program.is_used("--bar")) { std::cout << "--bar : " << program.get("--bar") << "\n"; } if (program.is_used("/foo")) { std::cout << "/foo : " << program.get("/foo") << "\n"; } } ``` ```console foo@bar:/home/dev/$ ./main +f 5 --bar 3.14f /foo "Hello" +f : 5 --bar : 3.14f /foo : Hello ``` ### Custom Assignment Characters In addition to prefix characters, custom 'assign' characters can be set. This setting is used to allow invocations like `./test --foo=Foo /B:Bar`. The default assign character is `=`. ```cpp #include #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("test"); program.set_prefix_chars("-+/"); program.set_assign_chars("=:"); program.add_argument("--foo"); program.add_argument("/B"); try { program.parse_args(argc, argv); } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } if (program.is_used("--foo")) { std::cout << "--foo : " << program.get("--foo") << "\n"; } if (program.is_used("/B")) { std::cout << "/B : " << program.get("/B") << "\n"; } } ``` ```console foo@bar:/home/dev/$ ./main --foo=Foo /B:Bar --foo : Foo /B : Bar ``` ## Further Examples ### Construct a JSON object from a filename argument ```cpp argparse::ArgumentParser program("json_test"); program.add_argument("config") .action([](const std::string& value) { // read a JSON file std::ifstream stream(value); nlohmann::json config_json; stream >> config_json; return config_json; }); try { program.parse_args({"./test", "config.json"}); } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); } nlohmann::json config = program.get("config"); ``` ### Positional Arguments with Compound Toggle Arguments ```cpp argparse::ArgumentParser program("test"); program.add_argument("numbers") .nargs(3) .scan<'i', int>(); program.add_argument("-a") .default_value(false) .implicit_value(true); program.add_argument("-b") .default_value(false) .implicit_value(true); program.add_argument("-c") .nargs(2) .scan<'g', float>(); program.add_argument("--files") .nargs(3); try { program.parse_args(argc, argv); } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); } auto numbers = program.get>("numbers"); // {1, 2, 3} auto a = program.get("-a"); // true auto b = program.get("-b"); // true auto c = program.get>("-c"); // {3.14f, 2.718f} auto files = program.get>("--files"); // {"a.txt", "b.txt", "c.txt"} /// Some code that prints parsed arguments ``` ```console foo@bar:/home/dev/$ ./main 1 2 3 -abc 3.14 2.718 --files a.txt b.txt c.txt numbers = {1, 2, 3} a = true b = true c = {3.14, 2.718} files = {"a.txt", "b.txt", "c.txt"} ``` ### Restricting the set of values for an argument ```cpp argparse::ArgumentParser program("test"); program.add_argument("input") .default_value(std::string{"baz"}) .choices("foo", "bar", "baz"); try { program.parse_args(argc, argv); } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); } auto input = program.get("input"); std::cout << input << std::endl; ``` ```console foo@bar:/home/dev/$ ./main fex Invalid argument "fex" - allowed options: {foo, bar, baz} ``` Using choices also works with integer types, e.g., ```cpp argparse::ArgumentParser program("test"); program.add_argument("input") .default_value(0) .choices(0, 1, 2, 3, 4, 5); try { program.parse_args(argc, argv); } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; std::exit(1); } auto input = program.get("input"); std::cout << input << std::endl; ``` ```console foo@bar:/home/dev/$ ./main 6 Invalid argument "6" - allowed options: {0, 1, 2, 3, 4, 5} ``` ### Using `option=value` syntax ```cpp #include "argparse.hpp" #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("test"); program.add_argument("--foo").implicit_value(true).default_value(false); program.add_argument("--bar"); try { program.parse_args(argc, argv); } catch (const std::exception& err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } if (program.is_used("--foo")) { std::cout << "--foo: " << std::boolalpha << program.get("--foo") << "\n"; } if (program.is_used("--bar")) { std::cout << "--bar: " << program.get("--bar") << "\n"; } } ``` ```console foo@bar:/home/dev/$ ./test --bar=BAR --foo --foo: true --bar: BAR ``` ### Advanced usage formatting By default usage is reported on a single line. The ``ArgumentParser::set_usage_max_line_width(width)`` method can be used to display the usage() on multiple lines, by defining the maximum line width. It can be combined with a call to ``ArgumentParser::set_usage_break_on_mutex()`` to ask grouped mutually exclusive arguments to be displayed on a separate line. ``ArgumentParser::add_usage_newline()`` can also be used to force the next argument to be displayed on a new line in the usage output. The following snippet ```cpp argparse::ArgumentParser program("program"); program.set_usage_max_line_width(80); program.set_usage_break_on_mutex(); program.add_argument("--quite-long-option-name").flag(); auto &group = program.add_mutually_exclusive_group(); group.add_argument("-a").flag(); group.add_argument("-b").flag(); program.add_argument("-c").flag(); program.add_argument("--another-one").flag(); program.add_argument("-d").flag(); program.add_argument("--yet-another-long-one").flag(); program.add_argument("--will-go-on-new-line").flag(); program.add_usage_newline(); program.add_argument("--new-line").flag(); std::cout << program.usage() << std::endl; ``` will display: ```console Usage: program [--help] [--version] [--quite-long-option-name] [[-a]|[-b]] [-c] [--another-one] [-d] [--yet-another-long-one] [--will-go-on-new-line] [--new-line] ``` Furthermore arguments can be separated into several groups by calling ``ArgumentParser::add_group(group_name)``. Only optional arguments should be specified after the first call to add_group(). ```cpp argparse::ArgumentParser program("program"); program.set_usage_max_line_width(80); program.add_argument("-a").flag().help("help_a"); program.add_group("Advanced options"); program.add_argument("-b").flag().help("help_b"); ``` will display: ```console Usage: program [--help] [--version] [-a] Advanced options: [-b] ``` ## Developer Notes ### Copying and Moving `argparse::ArgumentParser` is intended to be used in a single function - setup everything and parse arguments in one place. Attempting to move or copy invalidates internal references (issue #260). Thus, starting with v3.0, `argparse::ArgumentParser` copy and move constructors are marked as `delete`. ## CMake Integration Use the latest argparse in your CMake project without copying any content. ```cmake cmake_minimum_required(VERSION 3.14) PROJECT(myproject) # fetch latest argparse include(FetchContent) FetchContent_Declare( argparse GIT_REPOSITORY https://github.com/p-ranav/argparse.git ) FetchContent_MakeAvailable(argparse) add_executable(myproject main.cpp) target_link_libraries(myproject argparse) ``` ## Bazel Integration Add an `http_archive` in WORKSPACE.bazel, for example ```starlark http_archive( name = "argparse", sha256 = "674e724c2702f0bfef1619161815257a407e1babce30d908327729fba6ce4124", strip_prefix = "argparse-3.1", url = "https://github.com/p-ranav/argparse/archive/refs/tags/v3.1.zip", ) ``` ## Building, Installing, and Testing ```bash # Clone the repository git clone https://github.com/p-ranav/argparse cd argparse # Build the tests mkdir build cd build cmake -DARGPARSE_BUILD_SAMPLES=on -DARGPARSE_BUILD_TESTS=on .. make # Run tests ./test/tests # Install the library sudo make install ``` ## Supported Toolchains | Compiler | Standard Library | Test Environment | | :------------------- | :--------------- | :----------------- | | GCC >= 8.3.0 | libstdc++ | Ubuntu 18.04 | | Clang >= 7.0.0 | libc++ | Xcode 10.2 | | MSVC >= 16.8 | Microsoft STL | Visual Studio 2019 | ## Contributing Contributions are welcome, have a look at the [CONTRIBUTING.md](CONTRIBUTING.md) document for more information. ## License The project is available under the [MIT](https://opensource.org/licenses/MIT) license. argparse-3.2/WORKSPACE.bazel000066400000000000000000000000321474520133400155350ustar00rootroot00000000000000workspace(name="argparse")argparse-3.2/clang_format.bash000077500000000000000000000001001474520133400164520ustar00rootroot00000000000000clang-format -i include/argparse/*.hpp test/*.cpp samples/*.cpp argparse-3.2/conanfile.py000066400000000000000000000003511474520133400154740ustar00rootroot00000000000000from conans import ConanFile class ArgparseConan(ConanFile): name = "argparse" version = "3.1" exports_sources = "include/argparse.hpp" no_copy_source = True def package(self): self.copy("argparse.hpp") argparse-3.2/include/000077500000000000000000000000001474520133400146105ustar00rootroot00000000000000argparse-3.2/include/argparse/000077500000000000000000000000001474520133400164145ustar00rootroot00000000000000argparse-3.2/include/argparse/argparse.hpp000066400000000000000000002475261474520133400207510ustar00rootroot00000000000000/* __ _ _ __ __ _ _ __ __ _ _ __ ___ ___ / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++ | (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse \__,_|_| \__, | .__/ \__,_|_| |___/\___| |___/|_| Licensed under the MIT License . SPDX-License-Identifier: MIT Copyright (c) 2019-2022 Pranav Srinivas Kumar and other contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #ifndef ARGPARSE_MODULE_USE_STD_MODULE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif #ifndef ARGPARSE_CUSTOM_STRTOF #define ARGPARSE_CUSTOM_STRTOF strtof #endif #ifndef ARGPARSE_CUSTOM_STRTOD #define ARGPARSE_CUSTOM_STRTOD strtod #endif #ifndef ARGPARSE_CUSTOM_STRTOLD #define ARGPARSE_CUSTOM_STRTOLD strtold #endif namespace argparse { namespace details { // namespace for helper methods template struct HasContainerTraits : std::false_type {}; template <> struct HasContainerTraits : std::false_type {}; template <> struct HasContainerTraits : std::false_type {}; template struct HasContainerTraits< T, std::void_t().begin()), decltype(std::declval().end()), decltype(std::declval().size())>> : std::true_type {}; template inline constexpr bool IsContainer = HasContainerTraits::value; template struct HasStreamableTraits : std::false_type {}; template struct HasStreamableTraits< T, std::void_t() << std::declval())>> : std::true_type {}; template inline constexpr bool IsStreamable = HasStreamableTraits::value; constexpr std::size_t repr_max_container_size = 5; template std::string repr(T const &val) { if constexpr (std::is_same_v) { return val ? "true" : "false"; } else if constexpr (std::is_convertible_v) { return '"' + std::string{std::string_view{val}} + '"'; } else if constexpr (IsContainer) { std::stringstream out; out << "{"; const auto size = val.size(); if (size > 1) { out << repr(*val.begin()); std::for_each( std::next(val.begin()), std::next( val.begin(), static_cast( std::min(size, repr_max_container_size) - 1)), [&out](const auto &v) { out << " " << repr(v); }); if (size <= repr_max_container_size) { out << " "; } else { out << "..."; } } if (size > 0) { out << repr(*std::prev(val.end())); } out << "}"; return out.str(); } else if constexpr (IsStreamable) { std::stringstream out; out << val; return out.str(); } else { return ""; } } namespace { template constexpr bool standard_signed_integer = false; template <> constexpr bool standard_signed_integer = true; template <> constexpr bool standard_signed_integer = true; template <> constexpr bool standard_signed_integer = true; template <> constexpr bool standard_signed_integer = true; template <> constexpr bool standard_signed_integer = true; template constexpr bool standard_unsigned_integer = false; template <> constexpr bool standard_unsigned_integer = true; template <> constexpr bool standard_unsigned_integer = true; template <> constexpr bool standard_unsigned_integer = true; template <> constexpr bool standard_unsigned_integer = true; template <> constexpr bool standard_unsigned_integer = true; } // namespace constexpr int radix_2 = 2; constexpr int radix_8 = 8; constexpr int radix_10 = 10; constexpr int radix_16 = 16; template constexpr bool standard_integer = standard_signed_integer || standard_unsigned_integer; template constexpr decltype(auto) apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x, std::index_sequence /*unused*/) { return std::invoke(std::forward(f), std::get(std::forward(t))..., std::forward(x)); } template constexpr decltype(auto) apply_plus_one(F &&f, Tuple &&t, Extra &&x) { return details::apply_plus_one_impl( std::forward(f), std::forward(t), std::forward(x), std::make_index_sequence< std::tuple_size_v>>{}); } constexpr auto pointer_range(std::string_view s) noexcept { return std::tuple(s.data(), s.data() + s.size()); } template constexpr bool starts_with(std::basic_string_view prefix, std::basic_string_view s) noexcept { return s.substr(0, prefix.size()) == prefix; } enum class chars_format { scientific = 0xf1, fixed = 0xf2, hex = 0xf4, binary = 0xf8, general = fixed | scientific }; struct ConsumeBinaryPrefixResult { bool is_binary; std::string_view rest; }; constexpr auto consume_binary_prefix(std::string_view s) -> ConsumeBinaryPrefixResult { if (starts_with(std::string_view{"0b"}, s) || starts_with(std::string_view{"0B"}, s)) { s.remove_prefix(2); return {true, s}; } return {false, s}; } struct ConsumeHexPrefixResult { bool is_hexadecimal; std::string_view rest; }; using namespace std::literals; constexpr auto consume_hex_prefix(std::string_view s) -> ConsumeHexPrefixResult { if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { s.remove_prefix(2); return {true, s}; } return {false, s}; } template inline auto do_from_chars(std::string_view s) -> T { T x{0}; auto [first, last] = pointer_range(s); auto [ptr, ec] = std::from_chars(first, last, x, Param); if (ec == std::errc()) { if (ptr == last) { return x; } throw std::invalid_argument{"pattern '" + std::string(s) + "' does not match to the end"}; } if (ec == std::errc::invalid_argument) { throw std::invalid_argument{"pattern '" + std::string(s) + "' not found"}; } if (ec == std::errc::result_out_of_range) { throw std::range_error{"'" + std::string(s) + "' not representable"}; } return x; // unreachable } template struct parse_number { auto operator()(std::string_view s) -> T { return do_from_chars(s); } }; template struct parse_number { auto operator()(std::string_view s) -> T { if (auto [ok, rest] = consume_binary_prefix(s); ok) { return do_from_chars(rest); } throw std::invalid_argument{"pattern not found"}; } }; template struct parse_number { auto operator()(std::string_view s) -> T { if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { if (auto [ok, rest] = consume_hex_prefix(s); ok) { try { return do_from_chars(rest); } catch (const std::invalid_argument &err) { throw std::invalid_argument("Failed to parse '" + std::string(s) + "' as hexadecimal: " + err.what()); } catch (const std::range_error &err) { throw std::range_error("Failed to parse '" + std::string(s) + "' as hexadecimal: " + err.what()); } } } else { // Allow passing hex numbers without prefix // Shape 'x' already has to be specified try { return do_from_chars(s); } catch (const std::invalid_argument &err) { throw std::invalid_argument("Failed to parse '" + std::string(s) + "' as hexadecimal: " + err.what()); } catch (const std::range_error &err) { throw std::range_error("Failed to parse '" + std::string(s) + "' as hexadecimal: " + err.what()); } } throw std::invalid_argument{"pattern '" + std::string(s) + "' not identified as hexadecimal"}; } }; template struct parse_number { auto operator()(std::string_view s) -> T { auto [ok, rest] = consume_hex_prefix(s); if (ok) { try { return do_from_chars(rest); } catch (const std::invalid_argument &err) { throw std::invalid_argument("Failed to parse '" + std::string(s) + "' as hexadecimal: " + err.what()); } catch (const std::range_error &err) { throw std::range_error("Failed to parse '" + std::string(s) + "' as hexadecimal: " + err.what()); } } auto [ok_binary, rest_binary] = consume_binary_prefix(s); if (ok_binary) { try { return do_from_chars(rest_binary); } catch (const std::invalid_argument &err) { throw std::invalid_argument("Failed to parse '" + std::string(s) + "' as binary: " + err.what()); } catch (const std::range_error &err) { throw std::range_error("Failed to parse '" + std::string(s) + "' as binary: " + err.what()); } } if (starts_with("0"sv, s)) { try { return do_from_chars(rest); } catch (const std::invalid_argument &err) { throw std::invalid_argument("Failed to parse '" + std::string(s) + "' as octal: " + err.what()); } catch (const std::range_error &err) { throw std::range_error("Failed to parse '" + std::string(s) + "' as octal: " + err.what()); } } try { return do_from_chars(rest); } catch (const std::invalid_argument &err) { throw std::invalid_argument("Failed to parse '" + std::string(s) + "' as decimal integer: " + err.what()); } catch (const std::range_error &err) { throw std::range_error("Failed to parse '" + std::string(s) + "' as decimal integer: " + err.what()); } } }; namespace { template inline const auto generic_strtod = nullptr; template <> inline const auto generic_strtod = ARGPARSE_CUSTOM_STRTOF; template <> inline const auto generic_strtod = ARGPARSE_CUSTOM_STRTOD; template <> inline const auto generic_strtod = ARGPARSE_CUSTOM_STRTOLD; } // namespace template inline auto do_strtod(std::string const &s) -> T { if (isspace(static_cast(s[0])) || s[0] == '+') { throw std::invalid_argument{"pattern '" + s + "' not found"}; } auto [first, last] = pointer_range(s); char *ptr; errno = 0; auto x = generic_strtod(first, &ptr); if (errno == 0) { if (ptr == last) { return x; } throw std::invalid_argument{"pattern '" + s + "' does not match to the end"}; } if (errno == ERANGE) { throw std::range_error{"'" + s + "' not representable"}; } return x; // unreachable } template struct parse_number { auto operator()(std::string const &s) -> T { if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { throw std::invalid_argument{ "chars_format::general does not parse hexfloat"}; } if (auto r = consume_binary_prefix(s); r.is_binary) { throw std::invalid_argument{ "chars_format::general does not parse binfloat"}; } try { return do_strtod(s); } catch (const std::invalid_argument &err) { throw std::invalid_argument("Failed to parse '" + s + "' as number: " + err.what()); } catch (const std::range_error &err) { throw std::range_error("Failed to parse '" + s + "' as number: " + err.what()); } } }; template struct parse_number { auto operator()(std::string const &s) -> T { if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) { throw std::invalid_argument{"chars_format::hex parses hexfloat"}; } if (auto r = consume_binary_prefix(s); r.is_binary) { throw std::invalid_argument{"chars_format::hex does not parse binfloat"}; } try { return do_strtod(s); } catch (const std::invalid_argument &err) { throw std::invalid_argument("Failed to parse '" + s + "' as hexadecimal: " + err.what()); } catch (const std::range_error &err) { throw std::range_error("Failed to parse '" + s + "' as hexadecimal: " + err.what()); } } }; template struct parse_number { auto operator()(std::string const &s) -> T { if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { throw std::invalid_argument{ "chars_format::binary does not parse hexfloat"}; } if (auto r = consume_binary_prefix(s); !r.is_binary) { throw std::invalid_argument{"chars_format::binary parses binfloat"}; } return do_strtod(s); } }; template struct parse_number { auto operator()(std::string const &s) -> T { if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { throw std::invalid_argument{ "chars_format::scientific does not parse hexfloat"}; } if (auto r = consume_binary_prefix(s); r.is_binary) { throw std::invalid_argument{ "chars_format::scientific does not parse binfloat"}; } if (s.find_first_of("eE") == std::string::npos) { throw std::invalid_argument{ "chars_format::scientific requires exponent part"}; } try { return do_strtod(s); } catch (const std::invalid_argument &err) { throw std::invalid_argument("Failed to parse '" + s + "' as scientific notation: " + err.what()); } catch (const std::range_error &err) { throw std::range_error("Failed to parse '" + s + "' as scientific notation: " + err.what()); } } }; template struct parse_number { auto operator()(std::string const &s) -> T { if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { throw std::invalid_argument{ "chars_format::fixed does not parse hexfloat"}; } if (auto r = consume_binary_prefix(s); r.is_binary) { throw std::invalid_argument{ "chars_format::fixed does not parse binfloat"}; } if (s.find_first_of("eE") != std::string::npos) { throw std::invalid_argument{ "chars_format::fixed does not parse exponent part"}; } try { return do_strtod(s); } catch (const std::invalid_argument &err) { throw std::invalid_argument("Failed to parse '" + s + "' as fixed notation: " + err.what()); } catch (const std::range_error &err) { throw std::range_error("Failed to parse '" + s + "' as fixed notation: " + err.what()); } } }; template std::string join(StrIt first, StrIt last, const std::string &separator) { if (first == last) { return ""; } std::stringstream value; value << *first; ++first; while (first != last) { value << separator << *first; ++first; } return value.str(); } template struct can_invoke_to_string { template static auto test(int) -> decltype(std::to_string(std::declval()), std::true_type{}); template static auto test(...) -> std::false_type; static constexpr bool value = decltype(test(0))::value; }; template struct IsChoiceTypeSupported { using CleanType = typename std::decay::type; static const bool value = std::is_integral::value || std::is_same::value || std::is_same::value || std::is_same::value; }; template std::size_t get_levenshtein_distance(const StringType &s1, const StringType &s2) { std::vector> dp( s1.size() + 1, std::vector(s2.size() + 1, 0)); for (std::size_t i = 0; i <= s1.size(); ++i) { for (std::size_t j = 0; j <= s2.size(); ++j) { if (i == 0) { dp[i][j] = j; } else if (j == 0) { dp[i][j] = i; } else if (s1[i - 1] == s2[j - 1]) { dp[i][j] = dp[i - 1][j - 1]; } else { dp[i][j] = 1 + std::min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}); } } } return dp[s1.size()][s2.size()]; } template std::string get_most_similar_string(const std::map &map, const std::string &input) { std::string most_similar{}; std::size_t min_distance = (std::numeric_limits::max)(); for (const auto &entry : map) { std::size_t distance = get_levenshtein_distance(entry.first, input); if (distance < min_distance) { min_distance = distance; most_similar = entry.first; } } return most_similar; } } // namespace details enum class nargs_pattern { optional, any, at_least_one }; enum class default_arguments : unsigned int { none = 0, help = 1, version = 2, all = help | version, }; inline default_arguments operator&(const default_arguments &a, const default_arguments &b) { return static_cast( static_cast::type>(a) & static_cast::type>(b)); } class ArgumentParser; class Argument { friend class ArgumentParser; friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) -> std::ostream &; template explicit Argument(std::string_view prefix_chars, std::array &&a, std::index_sequence /*unused*/) : m_accepts_optional_like_value(false), m_is_optional((is_optional(a[I], prefix_chars) || ...)), m_is_required(false), m_is_repeatable(false), m_is_used(false), m_is_hidden(false), m_prefix_chars(prefix_chars) { ((void)m_names.emplace_back(a[I]), ...); std::sort( m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) { return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size(); }); } public: template explicit Argument(std::string_view prefix_chars, std::array &&a) : Argument(prefix_chars, std::move(a), std::make_index_sequence{}) {} Argument &help(std::string help_text) { m_help = std::move(help_text); return *this; } Argument &metavar(std::string metavar) { m_metavar = std::move(metavar); return *this; } template Argument &default_value(T &&value) { m_num_args_range = NArgsRange{0, m_num_args_range.get_max()}; m_default_value_repr = details::repr(value); if constexpr (std::is_convertible_v) { m_default_value_str = std::string{std::string_view{value}}; } else if constexpr (details::can_invoke_to_string::value) { m_default_value_str = std::to_string(value); } m_default_value = std::forward(value); return *this; } Argument &default_value(const char *value) { return default_value(std::string(value)); } Argument &required() { m_is_required = true; return *this; } Argument &implicit_value(std::any value) { m_implicit_value = std::move(value); m_num_args_range = NArgsRange{0, 0}; return *this; } // This is shorthand for: // program.add_argument("foo") // .default_value(false) // .implicit_value(true) Argument &flag() { default_value(false); implicit_value(true); return *this; } template auto action(F &&callable, Args &&... bound_args) -> std::enable_if_t, Argument &> { using action_type = std::conditional_t< std::is_void_v>, void_action, valued_action>; if constexpr (sizeof...(Args) == 0) { m_actions.emplace_back(std::forward(callable)); } else { m_actions.emplace_back( [f = std::forward(callable), tup = std::make_tuple(std::forward(bound_args)...)]( std::string const &opt) mutable { return details::apply_plus_one(f, tup, opt); }); } return *this; } auto &store_into(bool &var) { if ((!m_default_value.has_value()) && (!m_implicit_value.has_value())) { flag(); } if (m_default_value.has_value()) { var = std::any_cast(m_default_value); } action([&var](const auto & /*unused*/) { var = true; return var; }); return *this; } template ::value>::type * = nullptr> auto &store_into(T &var) { if (m_default_value.has_value()) { var = std::any_cast(m_default_value); } action([&var](const auto &s) { var = details::parse_number()(s); return var; }); return *this; } auto &store_into(double &var) { if (m_default_value.has_value()) { var = std::any_cast(m_default_value); } action([&var](const auto &s) { var = details::parse_number()(s); return var; }); return *this; } auto &store_into(std::string &var) { if (m_default_value.has_value()) { var = std::any_cast(m_default_value); } action([&var](const std::string &s) { var = s; return var; }); return *this; } auto &store_into(std::filesystem::path &var) { if (m_default_value.has_value()) { var = std::any_cast(m_default_value); } action([&var](const std::string &s) { var = s; }); return *this; } auto &store_into(std::vector &var) { if (m_default_value.has_value()) { var = std::any_cast>(m_default_value); } action([this, &var](const std::string &s) { if (!m_is_used) { var.clear(); } m_is_used = true; var.push_back(s); return var; }); return *this; } auto &store_into(std::vector &var) { if (m_default_value.has_value()) { var = std::any_cast>(m_default_value); } action([this, &var](const std::string &s) { if (!m_is_used) { var.clear(); } m_is_used = true; var.push_back(details::parse_number()(s)); return var; }); return *this; } auto &store_into(std::set &var) { if (m_default_value.has_value()) { var = std::any_cast>(m_default_value); } action([this, &var](const std::string &s) { if (!m_is_used) { var.clear(); } m_is_used = true; var.insert(s); return var; }); return *this; } auto &store_into(std::set &var) { if (m_default_value.has_value()) { var = std::any_cast>(m_default_value); } action([this, &var](const std::string &s) { if (!m_is_used) { var.clear(); } m_is_used = true; var.insert(details::parse_number()(s)); return var; }); return *this; } auto &append() { m_is_repeatable = true; return *this; } // Cause the argument to be invisible in usage and help auto &hidden() { m_is_hidden = true; return *this; } template auto scan() -> std::enable_if_t, Argument &> { static_assert(!(std::is_const_v || std::is_volatile_v), "T should not be cv-qualified"); auto is_one_of = [](char c, auto... x) constexpr { return ((c == x) || ...); }; if constexpr (is_one_of(Shape, 'd') && details::standard_integer) { action(details::parse_number()); } else if constexpr (is_one_of(Shape, 'i') && details::standard_integer) { action(details::parse_number()); } else if constexpr (is_one_of(Shape, 'u') && details::standard_unsigned_integer) { action(details::parse_number()); } else if constexpr (is_one_of(Shape, 'b') && details::standard_unsigned_integer) { action(details::parse_number()); } else if constexpr (is_one_of(Shape, 'o') && details::standard_unsigned_integer) { action(details::parse_number()); } else if constexpr (is_one_of(Shape, 'x', 'X') && details::standard_unsigned_integer) { action(details::parse_number()); } else if constexpr (is_one_of(Shape, 'a', 'A') && std::is_floating_point_v) { action(details::parse_number()); } else if constexpr (is_one_of(Shape, 'e', 'E') && std::is_floating_point_v) { action(details::parse_number()); } else if constexpr (is_one_of(Shape, 'f', 'F') && std::is_floating_point_v) { action(details::parse_number()); } else if constexpr (is_one_of(Shape, 'g', 'G') && std::is_floating_point_v) { action(details::parse_number()); } else { static_assert(alignof(T) == 0, "No scan specification for T"); } return *this; } Argument &nargs(std::size_t num_args) { m_num_args_range = NArgsRange{num_args, num_args}; return *this; } Argument &nargs(std::size_t num_args_min, std::size_t num_args_max) { m_num_args_range = NArgsRange{num_args_min, num_args_max}; return *this; } Argument &nargs(nargs_pattern pattern) { switch (pattern) { case nargs_pattern::optional: m_num_args_range = NArgsRange{0, 1}; break; case nargs_pattern::any: m_num_args_range = NArgsRange{0, (std::numeric_limits::max)()}; break; case nargs_pattern::at_least_one: m_num_args_range = NArgsRange{1, (std::numeric_limits::max)()}; break; } return *this; } Argument &remaining() { m_accepts_optional_like_value = true; return nargs(nargs_pattern::any); } template void add_choice(T &&choice) { static_assert(details::IsChoiceTypeSupported::value, "Only string or integer type supported for choice"); static_assert(std::is_convertible_v || details::can_invoke_to_string::value, "Choice is not convertible to string_type"); if (!m_choices.has_value()) { m_choices = std::vector{}; } if constexpr (std::is_convertible_v) { m_choices.value().push_back( std::string{std::string_view{std::forward(choice)}}); } else if constexpr (details::can_invoke_to_string::value) { m_choices.value().push_back(std::to_string(std::forward(choice))); } } Argument &choices() { if (!m_choices.has_value()) { throw std::runtime_error("Zero choices provided"); } return *this; } template Argument &choices(T &&first, U &&... rest) { add_choice(std::forward(first)); choices(std::forward(rest)...); return *this; } void find_default_value_in_choices_or_throw() const { const auto &choices = m_choices.value(); if (m_default_value.has_value()) { if (std::find(choices.begin(), choices.end(), m_default_value_str) == choices.end()) { // provided arg not in list of allowed choices // report error std::string choices_as_csv = std::accumulate(choices.begin(), choices.end(), std::string(), [](const std::string &a, const std::string &b) { return a + (a.empty() ? "" : ", ") + b; }); throw std::runtime_error( std::string{"Invalid default value "} + m_default_value_repr + " - allowed options: {" + choices_as_csv + "}"); } } } template bool is_value_in_choices(Iterator option_it) const { const auto &choices = m_choices.value(); return (std::find(choices.begin(), choices.end(), *option_it) != choices.end()); } template void throw_invalid_arguments_error(Iterator option_it) const { const auto &choices = m_choices.value(); const std::string choices_as_csv = std::accumulate( choices.begin(), choices.end(), std::string(), [](const std::string &option_a, const std::string &option_b) { return option_a + (option_a.empty() ? "" : ", ") + option_b; }); throw std::runtime_error(std::string{"Invalid argument "} + details::repr(*option_it) + " - allowed options: {" + choices_as_csv + "}"); } /* The dry_run parameter can be set to true to avoid running the actions, * and setting m_is_used. This may be used by a pre-processing step to do * a first iteration over arguments. */ template Iterator consume(Iterator start, Iterator end, std::string_view used_name = {}, bool dry_run = false) { if (!m_is_repeatable && m_is_used) { throw std::runtime_error( std::string("Duplicate argument ").append(used_name)); } m_used_name = used_name; std::size_t passed_options = 0; if (m_choices.has_value()) { // Check each value in (start, end) and make sure // it is in the list of allowed choices/options const auto max_number_of_args = m_num_args_range.get_max(); const auto min_number_of_args = m_num_args_range.get_min(); for (auto it = start; it != end; ++it) { if (is_value_in_choices(it)) { passed_options += 1; continue; } if ((passed_options >= min_number_of_args) && (passed_options <= max_number_of_args)) { break; } throw_invalid_arguments_error(it); } } const auto num_args_max = (m_choices.has_value()) ? passed_options : m_num_args_range.get_max(); const auto num_args_min = m_num_args_range.get_min(); std::size_t dist = 0; if (num_args_max == 0) { if (!dry_run) { m_values.emplace_back(m_implicit_value); for(auto &action: m_actions) { std::visit([&](const auto &f) { f({}); }, action); } if(m_actions.empty()){ std::visit([&](const auto &f) { f({}); }, m_default_action); } m_is_used = true; } return start; } if ((dist = static_cast(std::distance(start, end))) >= num_args_min) { if (num_args_max < dist) { end = std::next(start, static_cast( num_args_max)); } if (!m_accepts_optional_like_value) { end = std::find_if( start, end, std::bind(is_optional, std::placeholders::_1, m_prefix_chars)); dist = static_cast(std::distance(start, end)); if (dist < num_args_min) { throw std::runtime_error("Too few arguments for '" + std::string(m_used_name) + "'."); } } struct ActionApply { void operator()(valued_action &f) { std::transform(first, last, std::back_inserter(self.m_values), f); } void operator()(void_action &f) { std::for_each(first, last, f); if (!self.m_default_value.has_value()) { if (!self.m_accepts_optional_like_value) { self.m_values.resize( static_cast(std::distance(first, last))); } } } Iterator first, last; Argument &self; }; if (!dry_run) { for(auto &action: m_actions) { std::visit(ActionApply{start, end, *this}, action); } if(m_actions.empty()){ std::visit(ActionApply{start, end, *this}, m_default_action); } m_is_used = true; } return end; } if (m_default_value.has_value()) { if (!dry_run) { m_is_used = true; } return start; } throw std::runtime_error("Too few arguments for '" + std::string(m_used_name) + "'."); } /* * @throws std::runtime_error if argument values are not valid */ void validate() const { if (m_is_optional) { // TODO: check if an implicit value was programmed for this argument if (!m_is_used && !m_default_value.has_value() && m_is_required) { throw_required_arg_not_used_error(); } if (m_is_used && m_is_required && m_values.empty()) { throw_required_arg_no_value_provided_error(); } } else { if (!m_num_args_range.contains(m_values.size()) && !m_default_value.has_value()) { throw_nargs_range_validation_error(); } } if (m_choices.has_value()) { // Make sure the default value (if provided) // is in the list of choices find_default_value_in_choices_or_throw(); } } std::string get_names_csv(char separator = ',') const { return std::accumulate( m_names.begin(), m_names.end(), std::string{""}, [&](const std::string &result, const std::string &name) { return result.empty() ? name : result + separator + name; }); } std::string get_usage_full() const { std::stringstream usage; usage << get_names_csv('/'); const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR"; if (m_num_args_range.get_max() > 0) { usage << " " << metavar; if (m_num_args_range.get_max() > 1) { usage << "..."; } } return usage.str(); } std::string get_inline_usage() const { std::stringstream usage; // Find the longest variant to show in the usage string std::string longest_name = m_names.front(); for (const auto &s : m_names) { if (s.size() > longest_name.size()) { longest_name = s; } } if (!m_is_required) { usage << "["; } usage << longest_name; const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR"; if (m_num_args_range.get_max() > 0) { usage << " " << metavar; if (m_num_args_range.get_max() > 1 && m_metavar.find("> <") == std::string::npos) { usage << "..."; } } if (!m_is_required) { usage << "]"; } if (m_is_repeatable) { usage << "..."; } return usage.str(); } std::size_t get_arguments_length() const { std::size_t names_size = std::accumulate( std::begin(m_names), std::end(m_names), std::size_t(0), [](const auto &sum, const auto &s) { return sum + s.size(); }); if (is_positional(m_names.front(), m_prefix_chars)) { // A set metavar means this replaces the names if (!m_metavar.empty()) { // Indent and metavar return 2 + m_metavar.size(); } // Indent and space-separated return 2 + names_size + (m_names.size() - 1); } // Is an option - include both names _and_ metavar // size = text + (", " between names) std::size_t size = names_size + 2 * (m_names.size() - 1); if (!m_metavar.empty() && m_num_args_range == NArgsRange{1, 1}) { size += m_metavar.size() + 1; } return size + 2; // indent } friend std::ostream &operator<<(std::ostream &stream, const Argument &argument) { std::stringstream name_stream; name_stream << " "; // indent if (argument.is_positional(argument.m_names.front(), argument.m_prefix_chars)) { if (!argument.m_metavar.empty()) { name_stream << argument.m_metavar; } else { name_stream << details::join(argument.m_names.begin(), argument.m_names.end(), " "); } } else { name_stream << details::join(argument.m_names.begin(), argument.m_names.end(), ", "); // If we have a metavar, and one narg - print the metavar if (!argument.m_metavar.empty() && argument.m_num_args_range == NArgsRange{1, 1}) { name_stream << " " << argument.m_metavar; } else if (!argument.m_metavar.empty() && argument.m_num_args_range.get_min() == argument.m_num_args_range.get_max() && argument.m_metavar.find("> <") != std::string::npos) { name_stream << " " << argument.m_metavar; } } // align multiline help message auto stream_width = stream.width(); auto name_padding = std::string(name_stream.str().size(), ' '); auto pos = std::string::size_type{}; auto prev = std::string::size_type{}; auto first_line = true; auto hspace = " "; // minimal space between name and help message stream << name_stream.str(); std::string_view help_view(argument.m_help); while ((pos = argument.m_help.find('\n', prev)) != std::string::npos) { auto line = help_view.substr(prev, pos - prev + 1); if (first_line) { stream << hspace << line; first_line = false; } else { stream.width(stream_width); stream << name_padding << hspace << line; } prev += pos - prev + 1; } if (first_line) { stream << hspace << argument.m_help; } else { auto leftover = help_view.substr(prev, argument.m_help.size() - prev); if (!leftover.empty()) { stream.width(stream_width); stream << name_padding << hspace << leftover; } } // print nargs spec if (!argument.m_help.empty()) { stream << " "; } stream << argument.m_num_args_range; bool add_space = false; if (argument.m_default_value.has_value() && argument.m_num_args_range != NArgsRange{0, 0}) { stream << "[default: " << argument.m_default_value_repr << "]"; add_space = true; } else if (argument.m_is_required) { stream << "[required]"; add_space = true; } if (argument.m_is_repeatable) { if (add_space) { stream << " "; } stream << "[may be repeated]"; } stream << "\n"; return stream; } template bool operator!=(const T &rhs) const { return !(*this == rhs); } /* * Compare to an argument value of known type * @throws std::logic_error in case of incompatible types */ template bool operator==(const T &rhs) const { if constexpr (!details::IsContainer) { return get() == rhs; } else { using ValueType = typename T::value_type; auto lhs = get(); return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs), std::end(rhs), [](const auto &a, const auto &b) { return std::any_cast(a) == b; }); } } /* * positional: * _empty_ * '-' * '-' decimal-literal * !'-' anything */ static bool is_positional(std::string_view name, std::string_view prefix_chars) { auto first = lookahead(name); if (first == eof) { return true; } if (prefix_chars.find(static_cast(first)) != std::string_view::npos) { name.remove_prefix(1); if (name.empty()) { return true; } return is_decimal_literal(name); } return true; } private: class NArgsRange { std::size_t m_min; std::size_t m_max; public: NArgsRange(std::size_t minimum, std::size_t maximum) : m_min(minimum), m_max(maximum) { if (minimum > maximum) { throw std::logic_error("Range of number of arguments is invalid"); } } bool contains(std::size_t value) const { return value >= m_min && value <= m_max; } bool is_exact() const { return m_min == m_max; } bool is_right_bounded() const { return m_max < (std::numeric_limits::max)(); } std::size_t get_min() const { return m_min; } std::size_t get_max() const { return m_max; } // Print help message friend auto operator<<(std::ostream &stream, const NArgsRange &range) -> std::ostream & { if (range.m_min == range.m_max) { if (range.m_min != 0 && range.m_min != 1) { stream << "[nargs: " << range.m_min << "] "; } } else { if (range.m_max == (std::numeric_limits::max)()) { stream << "[nargs: " << range.m_min << " or more] "; } else { stream << "[nargs=" << range.m_min << ".." << range.m_max << "] "; } } return stream; } bool operator==(const NArgsRange &rhs) const { return rhs.m_min == m_min && rhs.m_max == m_max; } bool operator!=(const NArgsRange &rhs) const { return !(*this == rhs); } }; void throw_nargs_range_validation_error() const { std::stringstream stream; if (!m_used_name.empty()) { stream << m_used_name << ": "; } else { stream << m_names.front() << ": "; } if (m_num_args_range.is_exact()) { stream << m_num_args_range.get_min(); } else if (m_num_args_range.is_right_bounded()) { stream << m_num_args_range.get_min() << " to " << m_num_args_range.get_max(); } else { stream << m_num_args_range.get_min() << " or more"; } stream << " argument(s) expected. " << m_values.size() << " provided."; throw std::runtime_error(stream.str()); } void throw_required_arg_not_used_error() const { std::stringstream stream; stream << m_names.front() << ": required."; throw std::runtime_error(stream.str()); } void throw_required_arg_no_value_provided_error() const { std::stringstream stream; stream << m_used_name << ": no value provided."; throw std::runtime_error(stream.str()); } static constexpr int eof = std::char_traits::eof(); static auto lookahead(std::string_view s) -> int { if (s.empty()) { return eof; } return static_cast(static_cast(s[0])); } /* * decimal-literal: * '0' * nonzero-digit digit-sequence_opt * integer-part fractional-part * fractional-part * integer-part '.' exponent-part_opt * integer-part exponent-part * * integer-part: * digit-sequence * * fractional-part: * '.' post-decimal-point * * post-decimal-point: * digit-sequence exponent-part_opt * * exponent-part: * 'e' post-e * 'E' post-e * * post-e: * sign_opt digit-sequence * * sign: one of * '+' '-' */ static bool is_decimal_literal(std::string_view s) { auto is_digit = [](auto c) constexpr { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return true; default: return false; } }; // precondition: we have consumed or will consume at least one digit auto consume_digits = [=](std::string_view sd) { // NOLINTNEXTLINE(readability-qualified-auto) auto it = std::find_if_not(std::begin(sd), std::end(sd), is_digit); return sd.substr(static_cast(it - std::begin(sd))); }; switch (lookahead(s)) { case '0': { s.remove_prefix(1); if (s.empty()) { return true; } goto integer_part; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { s = consume_digits(s); if (s.empty()) { return true; } goto integer_part_consumed; } case '.': { s.remove_prefix(1); goto post_decimal_point; } default: return false; } integer_part: s = consume_digits(s); integer_part_consumed: switch (lookahead(s)) { case '.': { s.remove_prefix(1); if (is_digit(lookahead(s))) { goto post_decimal_point; } else { goto exponent_part_opt; } } case 'e': case 'E': { s.remove_prefix(1); goto post_e; } default: return false; } post_decimal_point: if (is_digit(lookahead(s))) { s = consume_digits(s); goto exponent_part_opt; } return false; exponent_part_opt: switch (lookahead(s)) { case eof: return true; case 'e': case 'E': { s.remove_prefix(1); goto post_e; } default: return false; } post_e: switch (lookahead(s)) { case '-': case '+': s.remove_prefix(1); } if (is_digit(lookahead(s))) { s = consume_digits(s); return s.empty(); } return false; } static bool is_optional(std::string_view name, std::string_view prefix_chars) { return !is_positional(name, prefix_chars); } /* * Get argument value given a type * @throws std::logic_error in case of incompatible types */ template T get() const { if (!m_values.empty()) { if constexpr (details::IsContainer) { return any_cast_container(m_values); } else { return std::any_cast(m_values.front()); } } if (m_default_value.has_value()) { return std::any_cast(m_default_value); } if constexpr (details::IsContainer) { if (!m_accepts_optional_like_value) { return any_cast_container(m_values); } } throw std::logic_error("No value provided for '" + m_names.back() + "'."); } /* * Get argument value given a type. * @pre The object has no default value. * @returns The stored value if any, std::nullopt otherwise. */ template auto present() const -> std::optional { if (m_default_value.has_value()) { throw std::logic_error("Argument with default value always presents"); } if (m_values.empty()) { return std::nullopt; } if constexpr (details::IsContainer) { return any_cast_container(m_values); } return std::any_cast(m_values.front()); } template static auto any_cast_container(const std::vector &operand) -> T { using ValueType = typename T::value_type; T result; std::transform( std::begin(operand), std::end(operand), std::back_inserter(result), [](const auto &value) { return std::any_cast(value); }); return result; } void set_usage_newline_counter(int i) { m_usage_newline_counter = i; } void set_group_idx(std::size_t i) { m_group_idx = i; } std::vector m_names; std::string_view m_used_name; std::string m_help; std::string m_metavar; std::any m_default_value; std::string m_default_value_repr; std::optional m_default_value_str; // used for checking default_value against choices std::any m_implicit_value; std::optional> m_choices{std::nullopt}; using valued_action = std::function; using void_action = std::function; std::vector> m_actions; std::variant m_default_action{ std::in_place_type, [](const std::string &value) { return value; }}; std::vector m_values; NArgsRange m_num_args_range{1, 1}; // Bit field of bool values. Set default value in ctor. bool m_accepts_optional_like_value : 1; bool m_is_optional : 1; bool m_is_required : 1; bool m_is_repeatable : 1; bool m_is_used : 1; bool m_is_hidden : 1; // if set, does not appear in usage or help std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars int m_usage_newline_counter = 0; std::size_t m_group_idx = 0; }; class ArgumentParser { public: explicit ArgumentParser(std::string program_name = {}, std::string version = "1.0", default_arguments add_args = default_arguments::all, bool exit_on_default_arguments = true, std::ostream &os = std::cout) : m_program_name(std::move(program_name)), m_version(std::move(version)), m_exit_on_default_arguments(exit_on_default_arguments), m_parser_path(m_program_name) { if ((add_args & default_arguments::help) == default_arguments::help) { add_argument("-h", "--help") .action([&](const auto & /*unused*/) { os << help().str(); if (m_exit_on_default_arguments) { std::exit(0); } }) .default_value(false) .help("shows help message and exits") .implicit_value(true) .nargs(0); } if ((add_args & default_arguments::version) == default_arguments::version) { add_argument("-v", "--version") .action([&](const auto & /*unused*/) { os << m_version << std::endl; if (m_exit_on_default_arguments) { std::exit(0); } }) .default_value(false) .help("prints version information and exits") .implicit_value(true) .nargs(0); } } ~ArgumentParser() = default; // ArgumentParser is meant to be used in a single function. // Setup everything and parse arguments in one place. // // ArgumentParser internally uses std::string_views, // references, iterators, etc. // Many of these elements become invalidated after a copy or move. ArgumentParser(const ArgumentParser &other) = delete; ArgumentParser &operator=(const ArgumentParser &other) = delete; ArgumentParser(ArgumentParser &&) noexcept = delete; ArgumentParser &operator=(ArgumentParser &&) = delete; explicit operator bool() const { auto arg_used = std::any_of(m_argument_map.cbegin(), m_argument_map.cend(), [](auto &it) { return it.second->m_is_used; }); auto subparser_used = std::any_of(m_subparser_used.cbegin(), m_subparser_used.cend(), [](auto &it) { return it.second; }); return m_is_parsed && (arg_used || subparser_used); } // Parameter packing // Call add_argument with variadic number of string arguments template Argument &add_argument(Targs... f_args) { using array_of_sv = std::array; auto argument = m_optional_arguments.emplace(std::cend(m_optional_arguments), m_prefix_chars, array_of_sv{f_args...}); if (!argument->m_is_optional) { m_positional_arguments.splice(std::cend(m_positional_arguments), m_optional_arguments, argument); } argument->set_usage_newline_counter(m_usage_newline_counter); argument->set_group_idx(m_group_names.size()); index_argument(argument); return *argument; } class MutuallyExclusiveGroup { friend class ArgumentParser; public: MutuallyExclusiveGroup() = delete; explicit MutuallyExclusiveGroup(ArgumentParser &parent, bool required = false) : m_parent(parent), m_required(required), m_elements({}) {} MutuallyExclusiveGroup(const MutuallyExclusiveGroup &other) = delete; MutuallyExclusiveGroup & operator=(const MutuallyExclusiveGroup &other) = delete; MutuallyExclusiveGroup(MutuallyExclusiveGroup &&other) noexcept : m_parent(other.m_parent), m_required(other.m_required), m_elements(std::move(other.m_elements)) { other.m_elements.clear(); } template Argument &add_argument(Targs... f_args) { auto &argument = m_parent.add_argument(std::forward(f_args)...); m_elements.push_back(&argument); argument.set_usage_newline_counter(m_parent.m_usage_newline_counter); argument.set_group_idx(m_parent.m_group_names.size()); return argument; } private: ArgumentParser &m_parent; bool m_required{false}; std::vector m_elements{}; }; MutuallyExclusiveGroup &add_mutually_exclusive_group(bool required = false) { m_mutually_exclusive_groups.emplace_back(*this, required); return m_mutually_exclusive_groups.back(); } // Parameter packed add_parents method // Accepts a variadic number of ArgumentParser objects template ArgumentParser &add_parents(const Targs &... f_args) { for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) { for (const auto &argument : parent_parser.m_positional_arguments) { auto it = m_positional_arguments.insert( std::cend(m_positional_arguments), argument); index_argument(it); } for (const auto &argument : parent_parser.m_optional_arguments) { auto it = m_optional_arguments.insert(std::cend(m_optional_arguments), argument); index_argument(it); } } return *this; } // Ask for the next optional arguments to be displayed on a separate // line in usage() output. Only effective if set_usage_max_line_width() is // also used. ArgumentParser &add_usage_newline() { ++m_usage_newline_counter; return *this; } // Ask for the next optional arguments to be displayed in a separate section // in usage() and help (<< *this) output. // For usage(), this is only effective if set_usage_max_line_width() is // also used. ArgumentParser &add_group(std::string group_name) { m_group_names.emplace_back(std::move(group_name)); return *this; } ArgumentParser &add_description(std::string description) { m_description = std::move(description); return *this; } ArgumentParser &add_epilog(std::string epilog) { m_epilog = std::move(epilog); return *this; } // Add a un-documented/hidden alias for an argument. // Ideally we'd want this to be a method of Argument, but Argument // does not own its owing ArgumentParser. ArgumentParser &add_hidden_alias_for(Argument &arg, std::string_view alias) { for (auto it = m_optional_arguments.begin(); it != m_optional_arguments.end(); ++it) { if (&(*it) == &arg) { m_argument_map.insert_or_assign(std::string(alias), it); return *this; } } throw std::logic_error( "Argument is not an optional argument of this parser"); } /* Getter for arguments and subparsers. * @throws std::logic_error in case of an invalid argument or subparser name */ template T &at(std::string_view name) { if constexpr (std::is_same_v) { return (*this)[name]; } else { std::string str_name(name); auto subparser_it = m_subparser_map.find(str_name); if (subparser_it != m_subparser_map.end()) { return subparser_it->second->get(); } throw std::logic_error("No such subparser: " + str_name); } } ArgumentParser &set_prefix_chars(std::string prefix_chars) { m_prefix_chars = std::move(prefix_chars); return *this; } ArgumentParser &set_assign_chars(std::string assign_chars) { m_assign_chars = std::move(assign_chars); return *this; } /* Call parse_args_internal - which does all the work * Then, validate the parsed arguments * This variant is used mainly for testing * @throws std::runtime_error in case of any invalid argument */ void parse_args(const std::vector &arguments) { parse_args_internal(arguments); // Check if all arguments are parsed for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { argument->validate(); } // Check each mutually exclusive group and make sure // there are no constraint violations for (const auto &group : m_mutually_exclusive_groups) { auto mutex_argument_used{false}; Argument *mutex_argument_it{nullptr}; for (Argument *arg : group.m_elements) { if (!mutex_argument_used && arg->m_is_used) { mutex_argument_used = true; mutex_argument_it = arg; } else if (mutex_argument_used && arg->m_is_used) { // Violation throw std::runtime_error("Argument '" + arg->get_usage_full() + "' not allowed with '" + mutex_argument_it->get_usage_full() + "'"); } } if (!mutex_argument_used && group.m_required) { // at least one argument from the group is // required std::string argument_names{}; std::size_t i = 0; std::size_t size = group.m_elements.size(); for (Argument *arg : group.m_elements) { if (i + 1 == size) { // last argument_names += std::string("'") + arg->get_usage_full() + std::string("' "); } else { argument_names += std::string("'") + arg->get_usage_full() + std::string("' or "); } i += 1; } throw std::runtime_error("One of the arguments " + argument_names + "is required"); } } } /* Call parse_known_args_internal - which does all the work * Then, validate the parsed arguments * This variant is used mainly for testing * @throws std::runtime_error in case of any invalid argument */ std::vector parse_known_args(const std::vector &arguments) { auto unknown_arguments = parse_known_args_internal(arguments); // Check if all arguments are parsed for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { argument->validate(); } return unknown_arguments; } /* Main entry point for parsing command-line arguments using this * ArgumentParser * @throws std::runtime_error in case of any invalid argument */ // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) void parse_args(int argc, const char *const argv[]) { parse_args({argv, argv + argc}); } /* Main entry point for parsing command-line arguments using this * ArgumentParser * @throws std::runtime_error in case of any invalid argument */ // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) auto parse_known_args(int argc, const char *const argv[]) { return parse_known_args({argv, argv + argc}); } /* Getter for options with default values. * @throws std::logic_error if parse_args() has not been previously called * @throws std::logic_error if there is no such option * @throws std::logic_error if the option has no value * @throws std::bad_any_cast if the option is not of type T */ template T get(std::string_view arg_name) const { if (!m_is_parsed) { throw std::logic_error("Nothing parsed, no arguments are available."); } return (*this)[arg_name].get(); } /* Getter for options without default values. * @pre The option has no default value. * @throws std::logic_error if there is no such option * @throws std::bad_any_cast if the option is not of type T */ template auto present(std::string_view arg_name) const -> std::optional { return (*this)[arg_name].present(); } /* Getter that returns true for user-supplied options. Returns false if not * user-supplied, even with a default value. */ auto is_used(std::string_view arg_name) const { return (*this)[arg_name].m_is_used; } /* Getter that returns true if a subcommand is used. */ auto is_subcommand_used(std::string_view subcommand_name) const { return m_subparser_used.at(std::string(subcommand_name)); } /* Getter that returns true if a subcommand is used. */ auto is_subcommand_used(const ArgumentParser &subparser) const { return is_subcommand_used(subparser.m_program_name); } /* Indexing operator. Return a reference to an Argument object * Used in conjunction with Argument.operator== e.g., parser["foo"] == true * @throws std::logic_error in case of an invalid argument name */ Argument &operator[](std::string_view arg_name) const { std::string name(arg_name); auto it = m_argument_map.find(name); if (it != m_argument_map.end()) { return *(it->second); } if (!is_valid_prefix_char(arg_name.front())) { const auto legal_prefix_char = get_any_valid_prefix_char(); const auto prefix = std::string(1, legal_prefix_char); // "-" + arg_name name = prefix + name; it = m_argument_map.find(name); if (it != m_argument_map.end()) { return *(it->second); } // "--" + arg_name name = prefix + name; it = m_argument_map.find(name); if (it != m_argument_map.end()) { return *(it->second); } } throw std::logic_error("No such argument: " + std::string(arg_name)); } // Print help message friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) -> std::ostream & { stream.setf(std::ios_base::left); auto longest_arg_length = parser.get_length_of_longest_argument(); stream << parser.usage() << "\n\n"; if (!parser.m_description.empty()) { stream << parser.m_description << "\n\n"; } const bool has_visible_positional_args = std::find_if( parser.m_positional_arguments.begin(), parser.m_positional_arguments.end(), [](const auto &argument) { return !argument.m_is_hidden; }) != parser.m_positional_arguments.end(); if (has_visible_positional_args) { stream << "Positional arguments:\n"; } for (const auto &argument : parser.m_positional_arguments) { if (!argument.m_is_hidden) { stream.width(static_cast(longest_arg_length)); stream << argument; } } if (!parser.m_optional_arguments.empty()) { stream << (!has_visible_positional_args ? "" : "\n") << "Optional arguments:\n"; } for (const auto &argument : parser.m_optional_arguments) { if (argument.m_group_idx == 0 && !argument.m_is_hidden) { stream.width(static_cast(longest_arg_length)); stream << argument; } } for (size_t i_group = 0; i_group < parser.m_group_names.size(); ++i_group) { stream << "\n" << parser.m_group_names[i_group] << " (detailed usage):\n"; for (const auto &argument : parser.m_optional_arguments) { if (argument.m_group_idx == i_group + 1 && !argument.m_is_hidden) { stream.width(static_cast(longest_arg_length)); stream << argument; } } } bool has_visible_subcommands = std::any_of( parser.m_subparser_map.begin(), parser.m_subparser_map.end(), [](auto &p) { return !p.second->get().m_suppress; }); if (has_visible_subcommands) { stream << (parser.m_positional_arguments.empty() ? (parser.m_optional_arguments.empty() ? "" : "\n") : "\n") << "Subcommands:\n"; for (const auto &[command, subparser] : parser.m_subparser_map) { if (subparser->get().m_suppress) { continue; } stream << std::setw(2) << " "; stream << std::setw(static_cast(longest_arg_length - 2)) << command; stream << " " << subparser->get().m_description << "\n"; } } if (!parser.m_epilog.empty()) { stream << '\n'; stream << parser.m_epilog << "\n\n"; } return stream; } // Format help message auto help() const -> std::stringstream { std::stringstream out; out << *this; return out; } // Sets the maximum width for a line of the Usage message ArgumentParser &set_usage_max_line_width(size_t w) { this->m_usage_max_line_width = w; return *this; } // Asks to display arguments of mutually exclusive group on separate lines in // the Usage message ArgumentParser &set_usage_break_on_mutex() { this->m_usage_break_on_mutex = true; return *this; } // Format usage part of help only auto usage() const -> std::string { std::stringstream stream; std::string curline("Usage: "); curline += this->m_parser_path; const bool multiline_usage = this->m_usage_max_line_width < (std::numeric_limits::max)(); const size_t indent_size = curline.size(); const auto deal_with_options_of_group = [&](std::size_t group_idx) { bool found_options = false; // Add any options inline here const MutuallyExclusiveGroup *cur_mutex = nullptr; int usage_newline_counter = -1; for (const auto &argument : this->m_optional_arguments) { if (argument.m_is_hidden) { continue; } if (multiline_usage) { if (argument.m_group_idx != group_idx) { continue; } if (usage_newline_counter != argument.m_usage_newline_counter) { if (usage_newline_counter >= 0) { if (curline.size() > indent_size) { stream << curline << std::endl; curline = std::string(indent_size, ' '); } } usage_newline_counter = argument.m_usage_newline_counter; } } found_options = true; const std::string arg_inline_usage = argument.get_inline_usage(); const MutuallyExclusiveGroup *arg_mutex = get_belonging_mutex(&argument); if ((cur_mutex != nullptr) && (arg_mutex == nullptr)) { curline += ']'; if (this->m_usage_break_on_mutex) { stream << curline << std::endl; curline = std::string(indent_size, ' '); } } else if ((cur_mutex == nullptr) && (arg_mutex != nullptr)) { if ((this->m_usage_break_on_mutex && curline.size() > indent_size) || curline.size() + 3 + arg_inline_usage.size() > this->m_usage_max_line_width) { stream << curline << std::endl; curline = std::string(indent_size, ' '); } curline += " ["; } else if ((cur_mutex != nullptr) && (arg_mutex != nullptr)) { if (cur_mutex != arg_mutex) { curline += ']'; if (this->m_usage_break_on_mutex || curline.size() + 3 + arg_inline_usage.size() > this->m_usage_max_line_width) { stream << curline << std::endl; curline = std::string(indent_size, ' '); } curline += " ["; } else { curline += '|'; } } cur_mutex = arg_mutex; if (curline.size() != indent_size && curline.size() + 1 + arg_inline_usage.size() > this->m_usage_max_line_width) { stream << curline << std::endl; curline = std::string(indent_size, ' '); curline += " "; } else if (cur_mutex == nullptr) { curline += " "; } curline += arg_inline_usage; } if (cur_mutex != nullptr) { curline += ']'; } return found_options; }; const bool found_options = deal_with_options_of_group(0); if (found_options && multiline_usage && !this->m_positional_arguments.empty()) { stream << curline << std::endl; curline = std::string(indent_size, ' '); } // Put positional arguments after the optionals for (const auto &argument : this->m_positional_arguments) { if (argument.m_is_hidden) { continue; } const std::string pos_arg = !argument.m_metavar.empty() ? argument.m_metavar : argument.m_names.front(); if (curline.size() + 1 + pos_arg.size() > this->m_usage_max_line_width) { stream << curline << std::endl; curline = std::string(indent_size, ' '); } curline += " "; if (argument.m_num_args_range.get_min() == 0 && !argument.m_num_args_range.is_right_bounded()) { curline += "["; curline += pos_arg; curline += "]..."; } else if (argument.m_num_args_range.get_min() == 1 && !argument.m_num_args_range.is_right_bounded()) { curline += pos_arg; curline += "..."; } else { curline += pos_arg; } } if (multiline_usage) { // Display options of other groups for (std::size_t i = 0; i < m_group_names.size(); ++i) { stream << curline << std::endl << std::endl; stream << m_group_names[i] << ":" << std::endl; curline = std::string(indent_size, ' '); deal_with_options_of_group(i + 1); } } stream << curline; // Put subcommands after positional arguments if (!m_subparser_map.empty()) { stream << " {"; std::size_t i{0}; for (const auto &[command, subparser] : m_subparser_map) { if (subparser->get().m_suppress) { continue; } if (i == 0) { stream << command; } else { stream << "," << command; } ++i; } stream << "}"; } return stream.str(); } // Printing the one and only help message // I've stuck with a simple message format, nothing fancy. [[deprecated("Use cout << program; instead. See also help().")]] std::string print_help() const { auto out = help(); std::cout << out.rdbuf(); return out.str(); } void add_subparser(ArgumentParser &parser) { parser.m_parser_path = m_program_name + " " + parser.m_program_name; auto it = m_subparsers.emplace(std::cend(m_subparsers), parser); m_subparser_map.insert_or_assign(parser.m_program_name, it); m_subparser_used.insert_or_assign(parser.m_program_name, false); } void set_suppress(bool suppress) { m_suppress = suppress; } protected: const MutuallyExclusiveGroup *get_belonging_mutex(const Argument *arg) const { for (const auto &mutex : m_mutually_exclusive_groups) { if (std::find(mutex.m_elements.begin(), mutex.m_elements.end(), arg) != mutex.m_elements.end()) { return &mutex; } } return nullptr; } bool is_valid_prefix_char(char c) const { return m_prefix_chars.find(c) != std::string::npos; } char get_any_valid_prefix_char() const { return m_prefix_chars[0]; } /* * Pre-process this argument list. Anything starting with "--", that * contains an =, where the prefix before the = has an entry in the * options table, should be split. */ std::vector preprocess_arguments(const std::vector &raw_arguments) const { std::vector arguments{}; for (const auto &arg : raw_arguments) { const auto argument_starts_with_prefix_chars = [this](const std::string &a) -> bool { if (!a.empty()) { const auto legal_prefix = [this](char c) -> bool { return m_prefix_chars.find(c) != std::string::npos; }; // Windows-style // if '/' is a legal prefix char // then allow single '/' followed by argument name, followed by an // assign char, e.g., ':' e.g., 'test.exe /A:Foo' const auto windows_style = legal_prefix('/'); if (windows_style) { if (legal_prefix(a[0])) { return true; } } else { // Slash '/' is not a legal prefix char // For all other characters, only support long arguments // i.e., the argument must start with 2 prefix chars, e.g, // '--foo' e,g, './test --foo=Bar -DARG=yes' if (a.size() > 1) { return (legal_prefix(a[0]) && legal_prefix(a[1])); } } } return false; }; // Check that: // - We don't have an argument named exactly this // - The argument starts with a prefix char, e.g., "--" // - The argument contains an assign char, e.g., "=" auto assign_char_pos = arg.find_first_of(m_assign_chars); if (m_argument_map.find(arg) == m_argument_map.end() && argument_starts_with_prefix_chars(arg) && assign_char_pos != std::string::npos) { // Get the name of the potential option, and check it exists std::string opt_name = arg.substr(0, assign_char_pos); if (m_argument_map.find(opt_name) != m_argument_map.end()) { // This is the name of an option! Split it into two parts arguments.push_back(std::move(opt_name)); arguments.push_back(arg.substr(assign_char_pos + 1)); continue; } } // If we've fallen through to here, then it's a standard argument arguments.push_back(arg); } return arguments; } /* * @throws std::runtime_error in case of any invalid argument */ void parse_args_internal(const std::vector &raw_arguments) { auto arguments = preprocess_arguments(raw_arguments); if (m_program_name.empty() && !arguments.empty()) { m_program_name = arguments.front(); } auto end = std::end(arguments); auto positional_argument_it = std::begin(m_positional_arguments); for (auto it = std::next(std::begin(arguments)); it != end;) { const auto ¤t_argument = *it; if (Argument::is_positional(current_argument, m_prefix_chars)) { if (positional_argument_it == std::end(m_positional_arguments)) { // Check sub-parsers auto subparser_it = m_subparser_map.find(current_argument); if (subparser_it != m_subparser_map.end()) { // build list of remaining args const auto unprocessed_arguments = std::vector(it, end); // invoke subparser m_is_parsed = true; m_subparser_used[current_argument] = true; return subparser_it->second->get().parse_args( unprocessed_arguments); } if (m_positional_arguments.empty()) { // Ask the user if they argument they provided was a typo // for some sub-parser, // e.g., user provided `git totes` instead of `git notes` if (!m_subparser_map.empty()) { throw std::runtime_error( "Failed to parse '" + current_argument + "', did you mean '" + std::string{details::get_most_similar_string( m_subparser_map, current_argument)} + "'"); } // Ask the user if they meant to use a specific optional argument if (!m_optional_arguments.empty()) { for (const auto &opt : m_optional_arguments) { if (!opt.m_implicit_value.has_value()) { // not a flag, requires a value if (!opt.m_is_used) { throw std::runtime_error( "Zero positional arguments expected, did you mean " + opt.get_usage_full()); } } } throw std::runtime_error("Zero positional arguments expected"); } else { throw std::runtime_error("Zero positional arguments expected"); } } else { throw std::runtime_error("Maximum number of positional arguments " "exceeded, failed to parse '" + current_argument + "'"); } } auto argument = positional_argument_it++; // Deal with the situation of ... if (argument->m_num_args_range.get_min() == 1 && argument->m_num_args_range.get_max() == (std::numeric_limits::max)() && positional_argument_it != std::end(m_positional_arguments) && std::next(positional_argument_it) == std::end(m_positional_arguments) && positional_argument_it->m_num_args_range.get_min() == 1 && positional_argument_it->m_num_args_range.get_max() == 1 ) { if (std::next(it) != end) { positional_argument_it->consume(std::prev(end), end); end = std::prev(end); } else { throw std::runtime_error("Missing " + positional_argument_it->m_names.front()); } } it = argument->consume(it, end); continue; } auto arg_map_it = m_argument_map.find(current_argument); if (arg_map_it != m_argument_map.end()) { auto argument = arg_map_it->second; it = argument->consume(std::next(it), end, arg_map_it->first); } else if (const auto &compound_arg = current_argument; compound_arg.size() > 1 && is_valid_prefix_char(compound_arg[0]) && !is_valid_prefix_char(compound_arg[1])) { ++it; for (std::size_t j = 1; j < compound_arg.size(); j++) { auto hypothetical_arg = std::string{'-', compound_arg[j]}; auto arg_map_it2 = m_argument_map.find(hypothetical_arg); if (arg_map_it2 != m_argument_map.end()) { auto argument = arg_map_it2->second; it = argument->consume(it, end, arg_map_it2->first); } else { throw std::runtime_error("Unknown argument: " + current_argument); } } } else { throw std::runtime_error("Unknown argument: " + current_argument); } } m_is_parsed = true; } /* * Like parse_args_internal but collects unused args into a vector */ std::vector parse_known_args_internal(const std::vector &raw_arguments) { auto arguments = preprocess_arguments(raw_arguments); std::vector unknown_arguments{}; if (m_program_name.empty() && !arguments.empty()) { m_program_name = arguments.front(); } auto end = std::end(arguments); auto positional_argument_it = std::begin(m_positional_arguments); for (auto it = std::next(std::begin(arguments)); it != end;) { const auto ¤t_argument = *it; if (Argument::is_positional(current_argument, m_prefix_chars)) { if (positional_argument_it == std::end(m_positional_arguments)) { // Check sub-parsers auto subparser_it = m_subparser_map.find(current_argument); if (subparser_it != m_subparser_map.end()) { // build list of remaining args const auto unprocessed_arguments = std::vector(it, end); // invoke subparser m_is_parsed = true; m_subparser_used[current_argument] = true; return subparser_it->second->get().parse_known_args_internal( unprocessed_arguments); } // save current argument as unknown and go to next argument unknown_arguments.push_back(current_argument); ++it; } else { // current argument is the value of a positional argument // consume it auto argument = positional_argument_it++; it = argument->consume(it, end); } continue; } auto arg_map_it = m_argument_map.find(current_argument); if (arg_map_it != m_argument_map.end()) { auto argument = arg_map_it->second; it = argument->consume(std::next(it), end, arg_map_it->first); } else if (const auto &compound_arg = current_argument; compound_arg.size() > 1 && is_valid_prefix_char(compound_arg[0]) && !is_valid_prefix_char(compound_arg[1])) { ++it; for (std::size_t j = 1; j < compound_arg.size(); j++) { auto hypothetical_arg = std::string{'-', compound_arg[j]}; auto arg_map_it2 = m_argument_map.find(hypothetical_arg); if (arg_map_it2 != m_argument_map.end()) { auto argument = arg_map_it2->second; it = argument->consume(it, end, arg_map_it2->first); } else { unknown_arguments.push_back(current_argument); break; } } } else { // current argument is an optional-like argument that is unknown // save it and move to next argument unknown_arguments.push_back(current_argument); ++it; } } m_is_parsed = true; return unknown_arguments; } // Used by print_help. std::size_t get_length_of_longest_argument() const { if (m_argument_map.empty()) { return 0; } std::size_t max_size = 0; for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { max_size = std::max(max_size, argument->get_arguments_length()); } for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) { max_size = std::max(max_size, command.size()); } return max_size; } using argument_it = std::list::iterator; using mutex_group_it = std::vector::iterator; using argument_parser_it = std::list>::iterator; void index_argument(argument_it it) { for (const auto &name : std::as_const(it->m_names)) { m_argument_map.insert_or_assign(name, it); } } std::string m_program_name; std::string m_version; std::string m_description; std::string m_epilog; bool m_exit_on_default_arguments = true; std::string m_prefix_chars{"-"}; std::string m_assign_chars{"="}; bool m_is_parsed = false; std::list m_positional_arguments; std::list m_optional_arguments; std::map m_argument_map; std::string m_parser_path; std::list> m_subparsers; std::map m_subparser_map; std::map m_subparser_used; std::vector m_mutually_exclusive_groups; bool m_suppress = false; std::size_t m_usage_max_line_width = (std::numeric_limits::max)(); bool m_usage_break_on_mutex = false; int m_usage_newline_counter = 0; std::vector m_group_names; }; } // namespace argparse argparse-3.2/module/000077500000000000000000000000001474520133400144525ustar00rootroot00000000000000argparse-3.2/module/argparse.cppm000066400000000000000000000041001474520133400171320ustar00rootroot00000000000000/* __ _ _ __ __ _ _ __ __ _ _ __ ___ ___ / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++ | (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse \__,_|_| \__, | .__/ \__,_|_| |___/\___| |___/|_| Licensed under the MIT License . SPDX-License-Identifier: MIT Copyright (c) 2019-2022 Pranav Srinivas Kumar and other contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ module; #ifndef ARGPARSE_MODULE_USE_STD_MODULE #include #endif export module argparse; #ifdef ARGPARSE_MODULE_USE_STD_MODULE import std; import std.compat; extern "C++" { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winclude-angled-in-module-purview" #include #pragma clang diagnostic pop } #endif export namespace argparse { using argparse::nargs_pattern; using argparse::default_arguments; using argparse::operator&; using argparse::Argument; using argparse::ArgumentParser; } argparse-3.2/packaging/000077500000000000000000000000001474520133400151115ustar00rootroot00000000000000argparse-3.2/packaging/pkgconfig.pc.in000066400000000000000000000002721474520133400200120ustar00rootroot00000000000000prefix=@CMAKE_INSTALL_PREFIX@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ Name: @PROJECT_NAME@ Description: @PROJECT_DESCRIPTION@ Version: @PROJECT_VERSION@ Cflags: -I${includedir} argparse-3.2/samples/000077500000000000000000000000001474520133400146315ustar00rootroot00000000000000argparse-3.2/samples/BUILD.bazel000066400000000000000000000013111474520133400165030ustar00rootroot00000000000000load(":add_sample.bzl", "add_sample") add_sample(name = "positional_argument") add_sample(name = "optional_flag_argument") add_sample(name = "required_optional_argument") add_sample(name = "is_used") add_sample(name = "joining_repeated_optional_arguments") add_sample(name = "repeating_argument_to_increase_value") add_sample(name = "negative_numbers") add_sample(name = "description_epilog_metavar") add_sample(name = "list_of_arguments") add_sample(name = "compound_arguments") add_sample(name = "gathering_remaining_arguments") add_sample(name = "subcommands") add_sample(name = "parse_known_args") add_sample(name = "custom_prefix_characters") add_sample(name = "custom_assignment_characters") argparse-3.2/samples/CMakeLists.txt000066400000000000000000000027151474520133400173760ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) project(argparse_samples) if(MSVC) # Force to always compile with W4 if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") endif() elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) # Update if necessary set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic -Wsign-conversion -Wshadow -Wconversion") endif() if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() # Disable deprecation for windows if (WIN32) add_compile_definitions(_CRT_SECURE_NO_WARNINGS) endif() function(add_sample NAME) ADD_EXECUTABLE(ARGPARSE_SAMPLE_${NAME} ${NAME}.cpp) INCLUDE_DIRECTORIES("../include" ".") set_target_properties(ARGPARSE_SAMPLE_${NAME} PROPERTIES OUTPUT_NAME ${NAME}) set_property(TARGET ARGPARSE_SAMPLE_${NAME} PROPERTY CXX_STANDARD 17) endfunction() add_sample(positional_argument) add_sample(optional_flag_argument) add_sample(required_optional_argument) add_sample(is_used) add_sample(joining_repeated_optional_arguments) add_sample(repeating_argument_to_increase_value) add_sample(negative_numbers) add_sample(description_epilog_metavar) add_sample(list_of_arguments) add_sample(compound_arguments) add_sample(gathering_remaining_arguments) add_sample(subcommands) add_sample(parse_known_args) add_sample(custom_prefix_characters) add_sample(custom_assignment_characters)argparse-3.2/samples/add_sample.bzl000066400000000000000000000002171474520133400174330ustar00rootroot00000000000000def add_sample(name): native.cc_binary( name = name, srcs = ["{}.cpp".format(name)], deps = ["//:argparse"], ) argparse-3.2/samples/compound_arguments.cpp000066400000000000000000000017011474520133400212450ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("test"); program.add_argument("-a").flag(); program.add_argument("-b").flag(); program.add_argument("-c") .nargs(2) .default_value(std::vector{0.0f, 0.0f}) .scan<'g', float>(); try { program.parse_args(argc, argv); // Example: ./main -abc 1.95 2.47 } catch (const std::exception &err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } auto a = program.get("-a"); // true auto b = program.get("-b"); // true auto c = program.get>("-c"); // {1.95, 2.47} std::cout << "a: " << std::boolalpha << a << "\n"; std::cout << "b: " << b << "\n"; if (!c.empty()) { std::cout << "c: "; for (auto &v : c) { std::cout << v << " "; } std::cout << std::endl; } }argparse-3.2/samples/custom_assignment_characters.cpp000066400000000000000000000012301474520133400232720ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("test"); program.set_prefix_chars("-+/"); program.set_assign_chars("=:"); program.add_argument("--foo"); program.add_argument("/B"); try { program.parse_args(argc, argv); } catch (const std::exception &err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } if (program.is_used("--foo")) { std::cout << "--foo : " << program.get("--foo") << "\n"; } if (program.is_used("/B")) { std::cout << "/B : " << program.get("/B") << "\n"; } }argparse-3.2/samples/custom_prefix_characters.cpp000066400000000000000000000013701474520133400224240ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("test"); program.set_prefix_chars("-+/"); program.add_argument("+f"); program.add_argument("--bar"); program.add_argument("/foo"); try { program.parse_args(argc, argv); } catch (const std::exception &err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } if (program.is_used("+f")) { std::cout << "+f : " << program.get("+f") << "\n"; } if (program.is_used("--bar")) { std::cout << "--bar : " << program.get("--bar") << "\n"; } if (program.is_used("/foo")) { std::cout << "/foo : " << program.get("/foo") << "\n"; } }argparse-3.2/samples/description_epilog_metavar.cpp000066400000000000000000000011161474520133400227350ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("main"); program.add_argument("thing").help("Thing to use.").metavar("THING"); program.add_argument("--member") .help("The alias for the member to pass to.") .metavar("ALIAS"); program.add_argument("--verbose").flag(); program.add_description("Forward a thing to the next member."); program.add_epilog("Possible things include betingalw, chiz, and res."); program.parse_args(argc, argv); std::cout << program << std::endl; }argparse-3.2/samples/gathering_remaining_arguments.cpp000066400000000000000000000012271474520133400234250ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("compiler"); program.add_argument("files").remaining(); try { program.parse_args(argc, argv); } catch (const std::exception &err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } try { auto files = program.get>("files"); std::cout << files.size() << " files provided" << std::endl; for (auto &file : files) std::cout << file << std::endl; } catch (std::logic_error &e) { std::cout << "No files provided" << std::endl; } }argparse-3.2/samples/is_used.cpp000066400000000000000000000016301474520133400167700ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("test"); program.add_argument("--color") .default_value(std::string{ "orange"}) // might otherwise be type const char* leading to an error // when trying program.get .help("specify the cat's fur color"); try { program.parse_args(argc, argv); // Example: ./main --color orange } catch (const std::exception &err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } auto color = program.get("--color"); // "orange" auto explicit_color = program.is_used("--color"); // true, user provided orange std::cout << "Color: " << color << "\n"; std::cout << "Argument was explicitly provided by user? " << std::boolalpha << explicit_color << "\n"; } argparse-3.2/samples/joining_repeated_optional_arguments.cpp000066400000000000000000000013631474520133400246400ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("test"); program.add_argument("--color") .default_value>({"orange"}) .append() .help("specify the cat's fur color"); try { program.parse_args( argc, argv); // Example: ./main --color red --color green --color blue } catch (const std::exception &err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } auto colors = program.get>( "--color"); // {"red", "green", "blue"} std::cout << "Colors: "; for (const auto &c : colors) { std::cout << c << " "; } std::cout << "\n"; } argparse-3.2/samples/list_of_arguments.cpp000066400000000000000000000013511474520133400210610ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("main"); program.add_argument("--input_files") .help("The list of input files") .nargs(2); try { program.parse_args( argc, argv); // Example: ./main --input_files config.yml System.xml } catch (const std::exception &err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } auto files = program.get>( "--input_files"); // {"config.yml", "System.xml"} if (!files.empty()) { std::cout << "Files: "; for (auto &file : files) { std::cout << file << " "; } std::cout << std::endl; } }argparse-3.2/samples/negative_numbers.cpp000066400000000000000000000014611474520133400206740ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("test"); program.add_argument("integer").help("Input number").scan<'i', int>(); program.add_argument("floats") .help("Vector of floats") .nargs(4) .scan<'g', float>(); try { program.parse_args(argc, argv); } catch (const std::exception &err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } if (program.is_used("integer")) { std::cout << "Integer : " << program.get("integer") << "\n"; } if (program.is_used("floats")) { std::cout << "Floats : "; for (const auto &f : program.get>("floats")) { std::cout << f << " "; } std::cout << std::endl; } } argparse-3.2/samples/optional_flag_argument.cpp000066400000000000000000000010441474520133400220540ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("test"); program.add_argument("--verbose") .help("increase output verbosity") .default_value(false) .implicit_value(true); try { program.parse_args(argc, argv); } catch (const std::exception &err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } if (program["--verbose"] == true) { std::cout << "Verbosity enabled" << std::endl; } } argparse-3.2/samples/parse_known_args.cpp000066400000000000000000000013421474520133400206770ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("test"); program.add_argument("--foo").implicit_value(true).default_value(false); program.add_argument("bar"); auto unknown_args = program.parse_known_args(argc, argv); if (program.is_used("--foo")) { std::cout << "--foo : " << program.get("--foo") << "\n"; } if (program.is_used("bar")) { std::cout << "bar : " << program.get("bar") << "\n"; } if (!unknown_args.empty()) { std::cout << "Unknown args : "; for (const auto &u : unknown_args) { std::cout << u << " "; } std::cout << std::endl; } }argparse-3.2/samples/positional_argument.cpp000066400000000000000000000013171474520133400214220ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("main"); program.add_argument("square") .help("display the square of a given number") .scan<'i', int>(); program.add_argument("--verbose").flag(); try { program.parse_args(argc, argv); } catch (const std::exception &err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } int input = program.get("square"); if (program["--verbose"] == true) { std::cout << "The square of " << input << " is " << (input * input) << std::endl; } else { std::cout << (input * input) << std::endl; } } argparse-3.2/samples/repeating_argument_to_increase_value.cpp000066400000000000000000000007541474520133400247720ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("test"); int verbosity = 0; program.add_argument("-V", "--verbose") .action([&](const auto &) { ++verbosity; }) .append() .default_value(false) .implicit_value(true) .nargs(0); program.parse_args(argc, argv); // Example: ./main -VVVV std::cout << "verbose level: " << verbosity << std::endl; // verbose level: 4 } argparse-3.2/samples/required_optional_argument.cpp000066400000000000000000000007471474520133400227740ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("test"); program.add_argument("-o", "--output") .required() .help("specify the output file."); try { program.parse_args(argc, argv); } catch (const std::exception &err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } std::cout << "Output written to " << program.get("-o") << "\n"; } argparse-3.2/samples/subcommands.cpp000066400000000000000000000045001474520133400176470ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include int main(int argc, char *argv[]) { argparse::ArgumentParser program("git"); // git add subparser argparse::ArgumentParser add_command("add"); add_command.add_description("Add file contents to the index"); add_command.add_argument("files") .help("Files to add content from. Fileglobs (e.g. *.c) can be given to " "add all matching files.") .remaining(); // git commit subparser argparse::ArgumentParser commit_command("commit"); commit_command.add_description("Record changes to the repository"); commit_command.add_argument("-a", "--all") .help("Tell the command to automatically stage files that have been " "modified and deleted.") .default_value(false) .implicit_value(true); commit_command.add_argument("-m", "--message") .help("Use the given as the commit message."); // git cat-file subparser argparse::ArgumentParser catfile_command("cat-file"); catfile_command.add_description( "Provide content or type and size information for repository objects"); catfile_command.add_argument("-t").help( "Instead of the content, show the object type identified by ."); catfile_command.add_argument("-p").help( "Pretty-print the contents of based on its type."); // git submodule subparser argparse::ArgumentParser submodule_command("submodule"); submodule_command.add_description("Initialize, update or inspect submodules"); argparse::ArgumentParser submodule_update_command("update"); submodule_update_command.add_description( "Update the registered submodules to match what the superproject " "expects"); submodule_update_command.add_argument("--init") .default_value(false) .implicit_value(true); submodule_update_command.add_argument("--recursive") .default_value(false) .implicit_value(true); submodule_command.add_subparser(submodule_update_command); program.add_subparser(add_command); program.add_subparser(commit_command); program.add_subparser(catfile_command); program.add_subparser(submodule_command); try { program.parse_args(argc, argv); } catch (const std::exception &err) { std::cerr << err.what() << std::endl; std::cerr << program; return 1; } // Use arguments } argparse-3.2/test/000077500000000000000000000000001474520133400141445ustar00rootroot00000000000000argparse-3.2/test/.gitignore000066400000000000000000000000231474520133400161270ustar00rootroot00000000000000build/ build_linux/argparse-3.2/test/BUILD.bazel000066400000000000000000000006041474520133400160220ustar00rootroot00000000000000cc_library( name = "doctest", srcs = [ "main.cpp", ], hdrs = ["doctest.hpp"], includes = ["."], local_defines = [ "DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN", ], ) cc_test( name = "test", srcs = glob(["test_*.cpp"]) + [ "test_utility.hpp", ], includes = ["."], deps = [ ":doctest", "//:argparse", ], ) argparse-3.2/test/CMakeLists.txt000066400000000000000000000040511474520133400167040ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) project(argparse_tests) if(MSVC) # Force to always compile with W4 if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") endif() elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) # Update if necessary set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -Wpedantic -Wsign-conversion -Wshadow -Wconversion -Werror -Wextra") endif() if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() # Disable deprecation for windows if (WIN32) add_compile_definitions(_CRT_SECURE_NO_WARNINGS) endif() # ARGPARSE executable file(GLOB ARGPARSE_TEST_SOURCES main.cpp test_actions.cpp test_append.cpp test_as_container.cpp test_bool_operator.cpp test_choices.cpp test_compound_arguments.cpp test_container_arguments.cpp test_default_args.cpp test_default_value.cpp test_error_reporting.cpp test_get.cpp test_help.cpp test_hidden_alias.cpp test_hidden_argument.cpp test_invalid_arguments.cpp test_is_used.cpp test_issue_37.cpp test_mutually_exclusive_group.cpp test_negative_numbers.cpp test_optional_arguments.cpp test_parent_parsers.cpp test_parse_args.cpp test_positional_arguments.cpp test_repr.cpp test_required_arguments.cpp test_scan.cpp test_store_into.cpp test_stringstream.cpp test_version.cpp test_subparsers.cpp test_parse_known_args.cpp test_equals_form.cpp test_prefix_chars.cpp ) set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) ADD_EXECUTABLE(ARGPARSE_TESTS ${ARGPARSE_TEST_SOURCES}) INCLUDE_DIRECTORIES("../include" ".") set_target_properties(ARGPARSE_TESTS PROPERTIES OUTPUT_NAME tests) set_property(TARGET ARGPARSE_TESTS PROPERTY CXX_STANDARD 17) # Set ${PROJECT_NAME} as the startup project set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ARGPARSE_TESTS) argparse-3.2/test/README.md000066400000000000000000000004441474520133400154250ustar00rootroot00000000000000# Argparse Tests ## Linux ```bash $ mkdir build $ cd build $ cmake ../. $ make $ ./tests ``` ## Windows 1. Generate Visual Studio solution ```bash $ mkdir build $ cd build $ cmake ../. -G "Visual Studio 15 2017" ``` 2. Open ARGPARSE.sln 3. Build tests in RELEASE | x64 4. Run tests.exe argparse-3.2/test/argparse_details.cppm000066400000000000000000000002271474520133400203370ustar00rootroot00000000000000module; #include export module argparse.details; export namespace argparse::details { using argparse::details::repr; } argparse-3.2/test/doctest.hpp000066400000000000000000011641541474520133400163360ustar00rootroot00000000000000// ====================================================================== lgtm [cpp/missing-header-guard] // == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! == // ====================================================================== // // doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD // // Copyright (c) 2016-2023 Viktor Kirilov // // Distributed under the MIT Software License // See accompanying file LICENSE.txt or copy at // https://opensource.org/licenses/MIT // // The documentation can be found at the library's page: // https://github.com/doctest/doctest/blob/master/doc/markdown/readme.md // // ================================================================================================= // ================================================================================================= // ================================================================================================= // // The library is heavily influenced by Catch - https://github.com/catchorg/Catch2 // which uses the Boost Software License - Version 1.0 // see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt // // The concept of subcases (sections in Catch) and expression decomposition are from there. // Some parts of the code are taken directly: // - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<> // - the Approx() helper class for floating point comparison // - colors in the console // - breaking into a debugger // - signal / SEH handling // - timer // - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste) // // The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest // which uses the Boost Software License - Version 1.0 // see here - https://github.com/martinmoene/lest/blob/master/LICENSE.txt // // ================================================================================================= // ================================================================================================= // ================================================================================================= #ifndef DOCTEST_LIBRARY_INCLUDED #define DOCTEST_LIBRARY_INCLUDED // ================================================================================================= // == VERSION ====================================================================================== // ================================================================================================= #define DOCTEST_VERSION_MAJOR 2 #define DOCTEST_VERSION_MINOR 4 #define DOCTEST_VERSION_PATCH 11 // util we need here #define DOCTEST_TOSTR_IMPL(x) #x #define DOCTEST_TOSTR(x) DOCTEST_TOSTR_IMPL(x) #define DOCTEST_VERSION_STR \ DOCTEST_TOSTR(DOCTEST_VERSION_MAJOR) "." \ DOCTEST_TOSTR(DOCTEST_VERSION_MINOR) "." \ DOCTEST_TOSTR(DOCTEST_VERSION_PATCH) #define DOCTEST_VERSION \ (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) // ================================================================================================= // == COMPILER VERSION ============================================================================= // ================================================================================================= // ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect #ifdef _MSC_VER #define DOCTEST_CPLUSPLUS _MSVC_LANG #else #define DOCTEST_CPLUSPLUS __cplusplus #endif #define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH)) // GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl... #if defined(_MSC_VER) && defined(_MSC_FULL_VER) #if _MSC_VER == _MSC_FULL_VER / 10000 #define DOCTEST_MSVC DOCTEST_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000) #else // MSVC #define DOCTEST_MSVC \ DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000) #endif // MSVC #endif // MSVC #if defined(__clang__) && defined(__clang_minor__) && defined(__clang_patchlevel__) #define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__) #elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && \ !defined(__INTEL_COMPILER) #define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) #endif // GCC #if defined(__INTEL_COMPILER) #define DOCTEST_ICC DOCTEST_COMPILER(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) #endif // ICC #ifndef DOCTEST_MSVC #define DOCTEST_MSVC 0 #endif // DOCTEST_MSVC #ifndef DOCTEST_CLANG #define DOCTEST_CLANG 0 #endif // DOCTEST_CLANG #ifndef DOCTEST_GCC #define DOCTEST_GCC 0 #endif // DOCTEST_GCC #ifndef DOCTEST_ICC #define DOCTEST_ICC 0 #endif // DOCTEST_ICC // ================================================================================================= // == COMPILER WARNINGS HELPERS ==================================================================== // ================================================================================================= #if DOCTEST_CLANG && !DOCTEST_ICC #define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) #define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push") #define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w) #define DOCTEST_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop") #define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) \ DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING(w) #else // DOCTEST_CLANG #define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH #define DOCTEST_CLANG_SUPPRESS_WARNING(w) #define DOCTEST_CLANG_SUPPRESS_WARNING_POP #define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) #endif // DOCTEST_CLANG #if DOCTEST_GCC #define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) #define DOCTEST_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push") #define DOCTEST_GCC_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(GCC diagnostic ignored w) #define DOCTEST_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop") #define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) \ DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING(w) #else // DOCTEST_GCC #define DOCTEST_GCC_SUPPRESS_WARNING_PUSH #define DOCTEST_GCC_SUPPRESS_WARNING(w) #define DOCTEST_GCC_SUPPRESS_WARNING_POP #define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) #endif // DOCTEST_GCC #if DOCTEST_MSVC #define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push)) #define DOCTEST_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w)) #define DOCTEST_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop)) #define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) \ DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(w) #else // DOCTEST_MSVC #define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH #define DOCTEST_MSVC_SUPPRESS_WARNING(w) #define DOCTEST_MSVC_SUPPRESS_WARNING_POP #define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) #endif // DOCTEST_MSVC // ================================================================================================= // == COMPILER WARNINGS ============================================================================ // ================================================================================================= // both the header and the implementation suppress all of these, // so it only makes sense to aggregate them like so #define DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH \ DOCTEST_CLANG_SUPPRESS_WARNING_PUSH \ DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") \ DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") \ DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") \ DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") \ DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") \ DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") \ \ DOCTEST_GCC_SUPPRESS_WARNING_PUSH \ DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") \ DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") \ DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") \ DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") \ DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") \ DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") \ DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") \ DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") \ \ DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ /* these 4 also disabled globally via cmake: */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4514) /* unreferenced inline function has been removed */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4571) /* SEH related */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4710) /* function not inlined */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4711) /* function selected for inline expansion*/ \ /* common ones */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4616) /* invalid compiler warning */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4619) /* invalid compiler warning */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4996) /* The compiler encountered a deprecated declaration */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4706) /* assignment within conditional expression */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4512) /* 'class' : assignment operator could not be generated */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4127) /* conditional expression is constant */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4640) /* construction of local static object not thread-safe */ \ DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ DOCTEST_MSVC_SUPPRESS_WARNING(5264) /* 'variable-name': 'const' variable is not used */ \ /* static analysis */ \ DOCTEST_MSVC_SUPPRESS_WARNING(26439) /* Function may not throw. Declare it 'noexcept' */ \ DOCTEST_MSVC_SUPPRESS_WARNING(26495) /* Always initialize a member variable */ \ DOCTEST_MSVC_SUPPRESS_WARNING(26451) /* Arithmetic overflow ... */ \ DOCTEST_MSVC_SUPPRESS_WARNING(26444) /* Avoid unnamed objects with custom ctor and dtor... */ \ DOCTEST_MSVC_SUPPRESS_WARNING(26812) /* Prefer 'enum class' over 'enum' */ #define DOCTEST_SUPPRESS_COMMON_WARNINGS_POP \ DOCTEST_CLANG_SUPPRESS_WARNING_POP \ DOCTEST_GCC_SUPPRESS_WARNING_POP \ DOCTEST_MSVC_SUPPRESS_WARNING_POP DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor") DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated") DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy") DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor") DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo") DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted #define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \ DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ DOCTEST_MSVC_SUPPRESS_WARNING(4548) /* before comma no effect; expected side - effect */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4265) /* virtual functions, but destructor is not virtual */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4986) /* exception specification does not match previous */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4350) /* 'member1' called instead of 'member2' */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4668) /* not defined as a preprocessor macro */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4365) /* signed/unsigned mismatch */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4774) /* format string not a string literal */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4623) /* default constructor was implicitly deleted */ \ DOCTEST_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \ DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */ \ DOCTEST_MSVC_SUPPRESS_WARNING(4738) /* storing float result in memory, loss of performance */ \ DOCTEST_MSVC_SUPPRESS_WARNING(5262) /* implicit fall-through */ #define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP // ================================================================================================= // == FEATURE DETECTION ============================================================================ // ================================================================================================= // general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support // MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx // GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html // MSVC version table: // https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering // MSVC++ 14.3 (17) _MSC_VER == 1930 (Visual Studio 2022) // MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019) // MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017) // MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) // MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) // MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) // MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) // MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) // MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) // Universal Windows Platform support #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) #define DOCTEST_CONFIG_NO_WINDOWS_SEH #endif // WINAPI_FAMILY #if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH) #define DOCTEST_CONFIG_WINDOWS_SEH #endif // MSVC #if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH) #undef DOCTEST_CONFIG_WINDOWS_SEH #endif // DOCTEST_CONFIG_NO_WINDOWS_SEH #if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \ !defined(__EMSCRIPTEN__) && !defined(__wasi__) #define DOCTEST_CONFIG_POSIX_SIGNALS #endif // _WIN32 #if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS) #undef DOCTEST_CONFIG_POSIX_SIGNALS #endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS #if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) \ || defined(__wasi__) #define DOCTEST_CONFIG_NO_EXCEPTIONS #endif // no exceptions #endif // DOCTEST_CONFIG_NO_EXCEPTIONS #ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS #define DOCTEST_CONFIG_NO_EXCEPTIONS #endif // DOCTEST_CONFIG_NO_EXCEPTIONS #endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS #if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS) #define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS #endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS #ifdef __wasi__ #define DOCTEST_CONFIG_NO_MULTITHREADING #endif #if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT) #define DOCTEST_CONFIG_IMPLEMENT #endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #if defined(_WIN32) || defined(__CYGWIN__) #if DOCTEST_MSVC #define DOCTEST_SYMBOL_EXPORT __declspec(dllexport) #define DOCTEST_SYMBOL_IMPORT __declspec(dllimport) #else // MSVC #define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport)) #define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport)) #endif // MSVC #else // _WIN32 #define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default"))) #define DOCTEST_SYMBOL_IMPORT #endif // _WIN32 #ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL #ifdef DOCTEST_CONFIG_IMPLEMENT #define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT #else // DOCTEST_CONFIG_IMPLEMENT #define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT #endif // DOCTEST_CONFIG_IMPLEMENT #else // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL #define DOCTEST_INTERFACE #endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL // needed for extern template instantiations // see https://github.com/fmtlib/fmt/issues/2228 #if DOCTEST_MSVC #define DOCTEST_INTERFACE_DECL #define DOCTEST_INTERFACE_DEF DOCTEST_INTERFACE #else // DOCTEST_MSVC #define DOCTEST_INTERFACE_DECL DOCTEST_INTERFACE #define DOCTEST_INTERFACE_DEF #endif // DOCTEST_MSVC #define DOCTEST_EMPTY #if DOCTEST_MSVC #define DOCTEST_NOINLINE __declspec(noinline) #define DOCTEST_UNUSED #define DOCTEST_ALIGNMENT(x) #elif DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 5, 0) #define DOCTEST_NOINLINE #define DOCTEST_UNUSED #define DOCTEST_ALIGNMENT(x) #else #define DOCTEST_NOINLINE __attribute__((noinline)) #define DOCTEST_UNUSED __attribute__((unused)) #define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x))) #endif #ifdef DOCTEST_CONFIG_NO_CONTRADICTING_INLINE #define DOCTEST_INLINE_NOINLINE inline #else #define DOCTEST_INLINE_NOINLINE inline DOCTEST_NOINLINE #endif #ifndef DOCTEST_NORETURN #if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) #define DOCTEST_NORETURN #else // DOCTEST_MSVC #define DOCTEST_NORETURN [[noreturn]] #endif // DOCTEST_MSVC #endif // DOCTEST_NORETURN #ifndef DOCTEST_NOEXCEPT #if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) #define DOCTEST_NOEXCEPT #else // DOCTEST_MSVC #define DOCTEST_NOEXCEPT noexcept #endif // DOCTEST_MSVC #endif // DOCTEST_NOEXCEPT #ifndef DOCTEST_CONSTEXPR #if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) #define DOCTEST_CONSTEXPR const #define DOCTEST_CONSTEXPR_FUNC inline #else // DOCTEST_MSVC #define DOCTEST_CONSTEXPR constexpr #define DOCTEST_CONSTEXPR_FUNC constexpr #endif // DOCTEST_MSVC #endif // DOCTEST_CONSTEXPR #ifndef DOCTEST_NO_SANITIZE_INTEGER #if DOCTEST_CLANG >= DOCTEST_COMPILER(3, 7, 0) #define DOCTEST_NO_SANITIZE_INTEGER __attribute__((no_sanitize("integer"))) #else #define DOCTEST_NO_SANITIZE_INTEGER #endif #endif // DOCTEST_NO_SANITIZE_INTEGER // ================================================================================================= // == FEATURE DETECTION END ======================================================================== // ================================================================================================= #define DOCTEST_DECLARE_INTERFACE(name) \ virtual ~name(); \ name() = default; \ name(const name&) = delete; \ name(name&&) = delete; \ name& operator=(const name&) = delete; \ name& operator=(name&&) = delete; #define DOCTEST_DEFINE_INTERFACE(name) \ name::~name() = default; // internal macros for string concatenation and anonymous variable name generation #define DOCTEST_CAT_IMPL(s1, s2) s1##s2 #define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2) #ifdef __COUNTER__ // not standard and may be missing for some compilers #define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__) #else // __COUNTER__ #define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__) #endif // __COUNTER__ #ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE #define DOCTEST_REF_WRAP(x) x& #else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE #define DOCTEST_REF_WRAP(x) x #endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE // not using __APPLE__ because... this is how Catch does it #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED #define DOCTEST_PLATFORM_MAC #elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #define DOCTEST_PLATFORM_IPHONE #elif defined(_WIN32) #define DOCTEST_PLATFORM_WINDOWS #elif defined(__wasi__) #define DOCTEST_PLATFORM_WASI #else // DOCTEST_PLATFORM #define DOCTEST_PLATFORM_LINUX #endif // DOCTEST_PLATFORM namespace doctest { namespace detail { static DOCTEST_CONSTEXPR int consume(const int*, int) noexcept { return 0; } }} #define DOCTEST_GLOBAL_NO_WARNINGS(var, ...) \ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ static const int var = doctest::detail::consume(&var, __VA_ARGS__); \ DOCTEST_CLANG_SUPPRESS_WARNING_POP #ifndef DOCTEST_BREAK_INTO_DEBUGGER // should probably take a look at https://github.com/scottt/debugbreak #ifdef DOCTEST_PLATFORM_LINUX #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) // Break at the location of the failing check if possible #define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler) #else #include #define DOCTEST_BREAK_INTO_DEBUGGER() raise(SIGTRAP) #endif #elif defined(DOCTEST_PLATFORM_MAC) #if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__i386) #define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler) #elif defined(__ppc__) || defined(__ppc64__) // https://www.cocoawithlove.com/2008/03/break-into-debugger.html #define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n": : : "memory","r0","r3","r4") // NOLINT(hicpp-no-assembler) #else #define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT(hicpp-no-assembler) #endif #elif DOCTEST_MSVC #define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() #elif defined(__MINGW32__) DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wredundant-decls") extern "C" __declspec(dllimport) void __stdcall DebugBreak(); DOCTEST_GCC_SUPPRESS_WARNING_POP #define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak() #else // linux #define DOCTEST_BREAK_INTO_DEBUGGER() (static_cast(0)) #endif // linux #endif // DOCTEST_BREAK_INTO_DEBUGGER // this is kept here for backwards compatibility since the config option was changed #ifdef DOCTEST_CONFIG_USE_IOSFWD #ifndef DOCTEST_CONFIG_USE_STD_HEADERS #define DOCTEST_CONFIG_USE_STD_HEADERS #endif #endif // DOCTEST_CONFIG_USE_IOSFWD // for clang - always include ciso646 (which drags some std stuff) because // we want to check if we are using libc++ with the _LIBCPP_VERSION macro in // which case we don't want to forward declare stuff from std - for reference: // https://github.com/doctest/doctest/issues/126 // https://github.com/doctest/doctest/issues/356 #if DOCTEST_CLANG #include #endif // clang #ifdef _LIBCPP_VERSION #ifndef DOCTEST_CONFIG_USE_STD_HEADERS #define DOCTEST_CONFIG_USE_STD_HEADERS #endif #endif // _LIBCPP_VERSION #ifdef DOCTEST_CONFIG_USE_STD_HEADERS #ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS #define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS #endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #include #include #include DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END #else // DOCTEST_CONFIG_USE_STD_HEADERS // Forward declaring 'X' in namespace std is not permitted by the C++ Standard. DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643) namespace std { // NOLINT(cert-dcl58-cpp) typedef decltype(nullptr) nullptr_t; // NOLINT(modernize-use-using) typedef decltype(sizeof(void*)) size_t; // NOLINT(modernize-use-using) template struct char_traits; template <> struct char_traits; template class basic_ostream; // NOLINT(fuchsia-virtual-inheritance) typedef basic_ostream> ostream; // NOLINT(modernize-use-using) template // NOLINTNEXTLINE basic_ostream& operator<<(basic_ostream&, const char*); template class basic_istream; typedef basic_istream> istream; // NOLINT(modernize-use-using) template class tuple; #if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) // see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 template class allocator; template class basic_string; using string = basic_string, allocator>; #endif // VS 2019 } // namespace std DOCTEST_MSVC_SUPPRESS_WARNING_POP #endif // DOCTEST_CONFIG_USE_STD_HEADERS #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS #include #endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS namespace doctest { using std::size_t; DOCTEST_INTERFACE extern bool is_running_in_test; #ifndef DOCTEST_CONFIG_STRING_SIZE_TYPE #define DOCTEST_CONFIG_STRING_SIZE_TYPE unsigned #endif // A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length // of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for: // - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128) // - if small - capacity left before going on the heap - using the lowest 5 bits // - if small - 2 bits are left unused - the second and third highest ones // - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator) // and the "is small" bit remains "0" ("as well as the capacity left") so its OK // Idea taken from this lecture about the string implementation of facebook/folly - fbstring // https://www.youtube.com/watch?v=kPR8h4-qZdk // TODO: // - optimizations - like not deleting memory unnecessarily in operator= and etc. // - resize/reserve/clear // - replace // - back/front // - iterator stuff // - find & friends // - push_back/pop_back // - assign/insert/erase // - relational operators as free functions - taking const char* as one of the params class DOCTEST_INTERFACE String { public: using size_type = DOCTEST_CONFIG_STRING_SIZE_TYPE; private: static DOCTEST_CONSTEXPR size_type len = 24; //!OCLINT avoid private static members static DOCTEST_CONSTEXPR size_type last = len - 1; //!OCLINT avoid private static members struct view // len should be more than sizeof(view) - because of the final byte for flags { char* ptr; size_type size; size_type capacity; }; union { char buf[len]; // NOLINT(*-avoid-c-arrays) view data; }; char* allocate(size_type sz); bool isOnStack() const noexcept { return (buf[last] & 128) == 0; } void setOnHeap() noexcept; void setLast(size_type in = last) noexcept; void setSize(size_type sz) noexcept; void copy(const String& other); public: static DOCTEST_CONSTEXPR size_type npos = static_cast(-1); String() noexcept; ~String(); // cppcheck-suppress noExplicitConstructor String(const char* in); String(const char* in, size_type in_size); String(std::istream& in, size_type in_size); String(const String& other); String& operator=(const String& other); String& operator+=(const String& other); String(String&& other) noexcept; String& operator=(String&& other) noexcept; char operator[](size_type i) const; char& operator[](size_type i); // the only functions I'm willing to leave in the interface - available for inlining const char* c_str() const { return const_cast(this)->c_str(); } // NOLINT char* c_str() { if (isOnStack()) { return reinterpret_cast(buf); } return data.ptr; } size_type size() const; size_type capacity() const; String substr(size_type pos, size_type cnt = npos) &&; String substr(size_type pos, size_type cnt = npos) const &; size_type find(char ch, size_type pos = 0) const; size_type rfind(char ch, size_type pos = npos) const; int compare(const char* other, bool no_case = false) const; int compare(const String& other, bool no_case = false) const; friend DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in); }; DOCTEST_INTERFACE String operator+(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs); class DOCTEST_INTERFACE Contains { public: explicit Contains(const String& string); bool checkWith(const String& other) const; String string; }; DOCTEST_INTERFACE String toString(const Contains& in); DOCTEST_INTERFACE bool operator==(const String& lhs, const Contains& rhs); DOCTEST_INTERFACE bool operator==(const Contains& lhs, const String& rhs); DOCTEST_INTERFACE bool operator!=(const String& lhs, const Contains& rhs); DOCTEST_INTERFACE bool operator!=(const Contains& lhs, const String& rhs); namespace Color { enum Enum { None = 0, White, Red, Green, Blue, Cyan, Yellow, Grey, Bright = 0x10, BrightRed = Bright | Red, BrightGreen = Bright | Green, LightGrey = Bright | Grey, BrightWhite = Bright | White }; DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code); } // namespace Color namespace assertType { enum Enum { // macro traits is_warn = 1, is_check = 2 * is_warn, is_require = 2 * is_check, is_normal = 2 * is_require, is_throws = 2 * is_normal, is_throws_as = 2 * is_throws, is_throws_with = 2 * is_throws_as, is_nothrow = 2 * is_throws_with, is_false = 2 * is_nothrow, is_unary = 2 * is_false, // not checked anywhere - used just to distinguish the types is_eq = 2 * is_unary, is_ne = 2 * is_eq, is_lt = 2 * is_ne, is_gt = 2 * is_lt, is_ge = 2 * is_gt, is_le = 2 * is_ge, // macro types DT_WARN = is_normal | is_warn, DT_CHECK = is_normal | is_check, DT_REQUIRE = is_normal | is_require, DT_WARN_FALSE = is_normal | is_false | is_warn, DT_CHECK_FALSE = is_normal | is_false | is_check, DT_REQUIRE_FALSE = is_normal | is_false | is_require, DT_WARN_THROWS = is_throws | is_warn, DT_CHECK_THROWS = is_throws | is_check, DT_REQUIRE_THROWS = is_throws | is_require, DT_WARN_THROWS_AS = is_throws_as | is_warn, DT_CHECK_THROWS_AS = is_throws_as | is_check, DT_REQUIRE_THROWS_AS = is_throws_as | is_require, DT_WARN_THROWS_WITH = is_throws_with | is_warn, DT_CHECK_THROWS_WITH = is_throws_with | is_check, DT_REQUIRE_THROWS_WITH = is_throws_with | is_require, DT_WARN_THROWS_WITH_AS = is_throws_with | is_throws_as | is_warn, DT_CHECK_THROWS_WITH_AS = is_throws_with | is_throws_as | is_check, DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require, DT_WARN_NOTHROW = is_nothrow | is_warn, DT_CHECK_NOTHROW = is_nothrow | is_check, DT_REQUIRE_NOTHROW = is_nothrow | is_require, DT_WARN_EQ = is_normal | is_eq | is_warn, DT_CHECK_EQ = is_normal | is_eq | is_check, DT_REQUIRE_EQ = is_normal | is_eq | is_require, DT_WARN_NE = is_normal | is_ne | is_warn, DT_CHECK_NE = is_normal | is_ne | is_check, DT_REQUIRE_NE = is_normal | is_ne | is_require, DT_WARN_GT = is_normal | is_gt | is_warn, DT_CHECK_GT = is_normal | is_gt | is_check, DT_REQUIRE_GT = is_normal | is_gt | is_require, DT_WARN_LT = is_normal | is_lt | is_warn, DT_CHECK_LT = is_normal | is_lt | is_check, DT_REQUIRE_LT = is_normal | is_lt | is_require, DT_WARN_GE = is_normal | is_ge | is_warn, DT_CHECK_GE = is_normal | is_ge | is_check, DT_REQUIRE_GE = is_normal | is_ge | is_require, DT_WARN_LE = is_normal | is_le | is_warn, DT_CHECK_LE = is_normal | is_le | is_check, DT_REQUIRE_LE = is_normal | is_le | is_require, DT_WARN_UNARY = is_normal | is_unary | is_warn, DT_CHECK_UNARY = is_normal | is_unary | is_check, DT_REQUIRE_UNARY = is_normal | is_unary | is_require, DT_WARN_UNARY_FALSE = is_normal | is_false | is_unary | is_warn, DT_CHECK_UNARY_FALSE = is_normal | is_false | is_unary | is_check, DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require, }; } // namespace assertType DOCTEST_INTERFACE const char* assertString(assertType::Enum at); DOCTEST_INTERFACE const char* failureString(assertType::Enum at); DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file); struct DOCTEST_INTERFACE TestCaseData { String m_file; // the file in which the test was registered (using String - see #350) unsigned m_line; // the line where the test was registered const char* m_name; // name of the test case const char* m_test_suite; // the test suite in which the test was added const char* m_description; bool m_skip; bool m_no_breaks; bool m_no_output; bool m_may_fail; bool m_should_fail; int m_expected_failures; double m_timeout; }; struct DOCTEST_INTERFACE AssertData { // common - for all asserts const TestCaseData* m_test_case; assertType::Enum m_at; const char* m_file; int m_line; const char* m_expr; bool m_failed; // exception-related - for all asserts bool m_threw; String m_exception; // for normal asserts String m_decomp; // for specific exception-related asserts bool m_threw_as; const char* m_exception_type; class DOCTEST_INTERFACE StringContains { private: Contains content; bool isContains; public: StringContains(const String& str) : content(str), isContains(false) { } StringContains(Contains cntn) : content(static_cast(cntn)), isContains(true) { } bool check(const String& str) { return isContains ? (content == str) : (content.string == str); } operator const String&() const { return content.string; } const char* c_str() const { return content.string.c_str(); } } m_exception_string; AssertData(assertType::Enum at, const char* file, int line, const char* expr, const char* exception_type, const StringContains& exception_string); }; struct DOCTEST_INTERFACE MessageData { String m_string; const char* m_file; int m_line; assertType::Enum m_severity; }; struct DOCTEST_INTERFACE SubcaseSignature { String m_name; const char* m_file; int m_line; bool operator==(const SubcaseSignature& other) const; bool operator<(const SubcaseSignature& other) const; }; struct DOCTEST_INTERFACE IContextScope { DOCTEST_DECLARE_INTERFACE(IContextScope) virtual void stringify(std::ostream*) const = 0; }; namespace detail { struct DOCTEST_INTERFACE TestCase; } // namespace detail struct ContextOptions //!OCLINT too many fields { std::ostream* cout = nullptr; // stdout stream String binary_name; // the test binary name const detail::TestCase* currentTest = nullptr; // == parameters from the command line String out; // output filename String order_by; // how tests should be ordered unsigned rand_seed; // the seed for rand ordering unsigned first; // the first (matching) test to be executed unsigned last; // the last (matching) test to be executed int abort_after; // stop tests after this many failed assertions int subcase_filter_levels; // apply the subcase filters for the first N levels bool success; // include successful assertions in output bool case_sensitive; // if filtering should be case sensitive bool exit; // if the program should be exited after the tests are ran/whatever bool duration; // print the time duration of each test case bool minimal; // minimal console output (only test failures) bool quiet; // no console output bool no_throw; // to skip exceptions-related assertion macros bool no_exitcode; // if the framework should return 0 as the exitcode bool no_run; // to not run the tests at all (can be done with an "*" exclude) bool no_intro; // to not print the intro of the framework bool no_version; // to not print the version of the framework bool no_colors; // if output to the console should be colorized bool force_colors; // forces the use of colors even when a tty cannot be detected bool no_breaks; // to not break into the debugger bool no_skip; // don't skip test cases which are marked to be skipped bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x): bool no_path_in_filenames; // if the path to files should be removed from the output bool no_line_numbers; // if source code line numbers should be omitted from the output bool no_debug_output; // no output in the debug console when a debugger is attached bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!! bool no_time_in_output; // omit any time/timestamps from output !!! UNDOCUMENTED !!! bool help; // to print the help bool version; // to print the version bool count; // if only the count of matching tests is to be retrieved bool list_test_cases; // to list all tests matching the filters bool list_test_suites; // to list all suites matching the filters bool list_reporters; // lists all registered reporters }; namespace detail { namespace types { #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS using namespace std; #else template struct enable_if { }; template struct enable_if { using type = T; }; struct true_type { static DOCTEST_CONSTEXPR bool value = true; }; struct false_type { static DOCTEST_CONSTEXPR bool value = false; }; template struct remove_reference { using type = T; }; template struct remove_reference { using type = T; }; template struct remove_reference { using type = T; }; template struct is_rvalue_reference : false_type { }; template struct is_rvalue_reference : true_type { }; template struct remove_const { using type = T; }; template struct remove_const { using type = T; }; // Compiler intrinsics template struct is_enum { static DOCTEST_CONSTEXPR bool value = __is_enum(T); }; template struct underlying_type { using type = __underlying_type(T); }; template struct is_pointer : false_type { }; template struct is_pointer : true_type { }; template struct is_array : false_type { }; // NOLINTNEXTLINE(*-avoid-c-arrays) template struct is_array : true_type { }; #endif } // template T&& declval(); template DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference::type& t) DOCTEST_NOEXCEPT { return static_cast(t); } template DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference::type&& t) DOCTEST_NOEXCEPT { return static_cast(t); } template struct deferred_false : types::false_type { }; // MSVS 2015 :( #if !DOCTEST_CLANG && defined(_MSC_VER) && _MSC_VER <= 1900 template struct has_global_insertion_operator : types::false_type { }; template struct has_global_insertion_operator(), declval()), void())> : types::true_type { }; template struct has_insertion_operator { static DOCTEST_CONSTEXPR bool value = has_global_insertion_operator::value; }; template struct insert_hack; template struct insert_hack { static void insert(std::ostream& os, const T& t) { ::operator<<(os, t); } }; template struct insert_hack { static void insert(std::ostream& os, const T& t) { operator<<(os, t); } }; template using insert_hack_t = insert_hack::value>; #else template struct has_insertion_operator : types::false_type { }; #endif template struct has_insertion_operator(), declval()), void())> : types::true_type { }; template struct should_stringify_as_underlying_type { static DOCTEST_CONSTEXPR bool value = detail::types::is_enum::value && !doctest::detail::has_insertion_operator::value; }; DOCTEST_INTERFACE std::ostream* tlssPush(); DOCTEST_INTERFACE String tlssPop(); template struct StringMakerBase { template static String convert(const DOCTEST_REF_WRAP(T)) { #ifdef DOCTEST_CONFIG_REQUIRE_STRINGIFICATION_FOR_ALL_USED_TYPES static_assert(deferred_false::value, "No stringification detected for type T. See string conversion manual"); #endif return "{?}"; } }; template struct filldata; template void filloss(std::ostream* stream, const T& in) { filldata::fill(stream, in); } template void filloss(std::ostream* stream, const T (&in)[N]) { // NOLINT(*-avoid-c-arrays) // T[N], T(&)[N], T(&&)[N] have same behaviour. // Hence remove reference. filloss::type>(stream, in); } template String toStream(const T& in) { std::ostream* stream = tlssPush(); filloss(stream, in); return tlssPop(); } template <> struct StringMakerBase { template static String convert(const DOCTEST_REF_WRAP(T) in) { return toStream(in); } }; } // namespace detail template struct StringMaker : public detail::StringMakerBase< detail::has_insertion_operator::value || detail::types::is_pointer::value || detail::types::is_array::value> {}; #ifndef DOCTEST_STRINGIFY #ifdef DOCTEST_CONFIG_DOUBLE_STRINGIFY #define DOCTEST_STRINGIFY(...) toString(toString(__VA_ARGS__)) #else #define DOCTEST_STRINGIFY(...) toString(__VA_ARGS__) #endif #endif template String toString() { #if DOCTEST_CLANG == 0 && DOCTEST_GCC == 0 && DOCTEST_ICC == 0 String ret = __FUNCSIG__; // class doctest::String __cdecl doctest::toString(void) String::size_type beginPos = ret.find('<'); return ret.substr(beginPos + 1, ret.size() - beginPos - static_cast(sizeof(">(void)"))); #else String ret = __PRETTY_FUNCTION__; // doctest::String toString() [with T = TYPE] String::size_type begin = ret.find('=') + 2; return ret.substr(begin, ret.size() - begin - 1); #endif } template ::value, bool>::type = true> String toString(const DOCTEST_REF_WRAP(T) value) { return StringMaker::convert(value); } #ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING DOCTEST_INTERFACE String toString(const char* in); #endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING #if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) // see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 DOCTEST_INTERFACE String toString(const std::string& in); #endif // VS 2019 DOCTEST_INTERFACE String toString(String in); DOCTEST_INTERFACE String toString(std::nullptr_t); DOCTEST_INTERFACE String toString(bool in); DOCTEST_INTERFACE String toString(float in); DOCTEST_INTERFACE String toString(double in); DOCTEST_INTERFACE String toString(double long in); DOCTEST_INTERFACE String toString(char in); DOCTEST_INTERFACE String toString(char signed in); DOCTEST_INTERFACE String toString(char unsigned in); DOCTEST_INTERFACE String toString(short in); DOCTEST_INTERFACE String toString(short unsigned in); DOCTEST_INTERFACE String toString(signed in); DOCTEST_INTERFACE String toString(unsigned in); DOCTEST_INTERFACE String toString(long in); DOCTEST_INTERFACE String toString(long unsigned in); DOCTEST_INTERFACE String toString(long long in); DOCTEST_INTERFACE String toString(long long unsigned in); template ::value, bool>::type = true> String toString(const DOCTEST_REF_WRAP(T) value) { using UT = typename detail::types::underlying_type::type; return (DOCTEST_STRINGIFY(static_cast(value))); } namespace detail { template struct filldata { static void fill(std::ostream* stream, const T& in) { #if defined(_MSC_VER) && _MSC_VER <= 1900 insert_hack_t::insert(*stream, in); #else operator<<(*stream, in); #endif } }; DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866) // NOLINTBEGIN(*-avoid-c-arrays) template struct filldata { static void fill(std::ostream* stream, const T(&in)[N]) { *stream << "["; for (size_t i = 0; i < N; i++) { if (i != 0) { *stream << ", "; } *stream << (DOCTEST_STRINGIFY(in[i])); } *stream << "]"; } }; // NOLINTEND(*-avoid-c-arrays) DOCTEST_MSVC_SUPPRESS_WARNING_POP // Specialized since we don't want the terminating null byte! // NOLINTBEGIN(*-avoid-c-arrays) template struct filldata { static void fill(std::ostream* stream, const char (&in)[N]) { *stream << String(in, in[N - 1] ? N : N - 1); } // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) }; // NOLINTEND(*-avoid-c-arrays) template <> struct filldata { static void fill(std::ostream* stream, const void* in); }; template struct filldata { DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4180) static void fill(std::ostream* stream, const T* in) { DOCTEST_MSVC_SUPPRESS_WARNING_POP DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wmicrosoft-cast") filldata::fill(stream, #if DOCTEST_GCC == 0 || DOCTEST_GCC >= DOCTEST_COMPILER(4, 9, 0) reinterpret_cast(in) #else *reinterpret_cast(&in) #endif ); DOCTEST_CLANG_SUPPRESS_WARNING_POP } }; } struct DOCTEST_INTERFACE Approx { Approx(double value); Approx operator()(double value) const; #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS template explicit Approx(const T& value, typename detail::types::enable_if::value>::type* = static_cast(nullptr)) { *this = static_cast(value); } #endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS Approx& epsilon(double newEpsilon); #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS template typename std::enable_if::value, Approx&>::type epsilon( const T& newEpsilon) { m_epsilon = static_cast(newEpsilon); return *this; } #endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS Approx& scale(double newScale); #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS template typename std::enable_if::value, Approx&>::type scale( const T& newScale) { m_scale = static_cast(newScale); return *this; } #endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS // clang-format off DOCTEST_INTERFACE friend bool operator==(double lhs, const Approx & rhs); DOCTEST_INTERFACE friend bool operator==(const Approx & lhs, double rhs); DOCTEST_INTERFACE friend bool operator!=(double lhs, const Approx & rhs); DOCTEST_INTERFACE friend bool operator!=(const Approx & lhs, double rhs); DOCTEST_INTERFACE friend bool operator<=(double lhs, const Approx & rhs); DOCTEST_INTERFACE friend bool operator<=(const Approx & lhs, double rhs); DOCTEST_INTERFACE friend bool operator>=(double lhs, const Approx & rhs); DOCTEST_INTERFACE friend bool operator>=(const Approx & lhs, double rhs); DOCTEST_INTERFACE friend bool operator< (double lhs, const Approx & rhs); DOCTEST_INTERFACE friend bool operator< (const Approx & lhs, double rhs); DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs); DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs); #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS #define DOCTEST_APPROX_PREFIX \ template friend typename std::enable_if::value, bool>::type DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(static_cast(lhs), rhs); } DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); } DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); } DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); } DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return static_cast(lhs) < rhs.m_value || lhs == rhs; } DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast(rhs) || lhs == rhs; } DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return static_cast(lhs) > rhs.m_value || lhs == rhs; } DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast(rhs) || lhs == rhs; } DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return static_cast(lhs) < rhs.m_value && lhs != rhs; } DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast(rhs) && lhs != rhs; } DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return static_cast(lhs) > rhs.m_value && lhs != rhs; } DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast(rhs) && lhs != rhs; } #undef DOCTEST_APPROX_PREFIX #endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS // clang-format on double m_epsilon; double m_scale; double m_value; }; DOCTEST_INTERFACE String toString(const Approx& in); DOCTEST_INTERFACE const ContextOptions* getContextOptions(); template struct DOCTEST_INTERFACE_DECL IsNaN { F value; bool flipped; IsNaN(F f, bool flip = false) : value(f), flipped(flip) { } IsNaN operator!() const { return { value, !flipped }; } operator bool() const; }; #ifndef __MINGW32__ extern template struct DOCTEST_INTERFACE_DECL IsNaN; extern template struct DOCTEST_INTERFACE_DECL IsNaN; extern template struct DOCTEST_INTERFACE_DECL IsNaN; #endif DOCTEST_INTERFACE String toString(IsNaN in); DOCTEST_INTERFACE String toString(IsNaN in); DOCTEST_INTERFACE String toString(IsNaN in); #ifndef DOCTEST_CONFIG_DISABLE namespace detail { // clang-format off #ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING template struct decay_array { using type = T; }; template struct decay_array { using type = T*; }; template struct decay_array { using type = T*; }; template struct not_char_pointer { static DOCTEST_CONSTEXPR int value = 1; }; template<> struct not_char_pointer { static DOCTEST_CONSTEXPR int value = 0; }; template<> struct not_char_pointer { static DOCTEST_CONSTEXPR int value = 0; }; template struct can_use_op : public not_char_pointer::type> {}; #endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING // clang-format on struct DOCTEST_INTERFACE TestFailureException { }; DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at); #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS DOCTEST_NORETURN #endif // DOCTEST_CONFIG_NO_EXCEPTIONS DOCTEST_INTERFACE void throwException(); struct DOCTEST_INTERFACE Subcase { SubcaseSignature m_signature; bool m_entered = false; Subcase(const String& name, const char* file, int line); Subcase(const Subcase&) = delete; Subcase(Subcase&&) = delete; Subcase& operator=(const Subcase&) = delete; Subcase& operator=(Subcase&&) = delete; ~Subcase(); operator bool() const; private: bool checkFilters(); }; template String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, const DOCTEST_REF_WRAP(R) rhs) { return (DOCTEST_STRINGIFY(lhs)) + op + (DOCTEST_STRINGIFY(rhs)); } #if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") #endif // This will check if there is any way it could find a operator like member or friend and uses it. // If not it doesn't find the operator or if the operator at global scope is defined after // this template, the template won't be instantiated due to SFINAE. Once the template is not // instantiated it can look for global operator using normal conversions. #ifdef __NVCC__ #define SFINAE_OP(ret,op) ret #else #define SFINAE_OP(ret,op) decltype((void)(doctest::detail::declval() op doctest::detail::declval()),ret{}) #endif #define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \ template \ DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) { \ bool res = op_macro(doctest::detail::forward(lhs), doctest::detail::forward(rhs)); \ if(m_at & assertType::is_false) \ res = !res; \ if(!res || doctest::getContextOptions()->success) \ return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \ return Result(res); \ } // more checks could be added - like in Catch: // https://github.com/catchorg/Catch2/pull/1480/files // https://github.com/catchorg/Catch2/pull/1481/files #define DOCTEST_FORBIT_EXPRESSION(rt, op) \ template \ rt& operator op(const R&) { \ static_assert(deferred_false::value, \ "Expression Too Complex Please Rewrite As Binary Comparison!"); \ return *this; \ } struct DOCTEST_INTERFACE Result // NOLINT(*-member-init) { bool m_passed; String m_decomp; Result() = default; // TODO: Why do we need this? (To remove NOLINT) Result(bool passed, const String& decomposition = String()); // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence DOCTEST_FORBIT_EXPRESSION(Result, &) DOCTEST_FORBIT_EXPRESSION(Result, ^) DOCTEST_FORBIT_EXPRESSION(Result, |) DOCTEST_FORBIT_EXPRESSION(Result, &&) DOCTEST_FORBIT_EXPRESSION(Result, ||) DOCTEST_FORBIT_EXPRESSION(Result, ==) DOCTEST_FORBIT_EXPRESSION(Result, !=) DOCTEST_FORBIT_EXPRESSION(Result, <) DOCTEST_FORBIT_EXPRESSION(Result, >) DOCTEST_FORBIT_EXPRESSION(Result, <=) DOCTEST_FORBIT_EXPRESSION(Result, >=) DOCTEST_FORBIT_EXPRESSION(Result, =) DOCTEST_FORBIT_EXPRESSION(Result, +=) DOCTEST_FORBIT_EXPRESSION(Result, -=) DOCTEST_FORBIT_EXPRESSION(Result, *=) DOCTEST_FORBIT_EXPRESSION(Result, /=) DOCTEST_FORBIT_EXPRESSION(Result, %=) DOCTEST_FORBIT_EXPRESSION(Result, <<=) DOCTEST_FORBIT_EXPRESSION(Result, >>=) DOCTEST_FORBIT_EXPRESSION(Result, &=) DOCTEST_FORBIT_EXPRESSION(Result, ^=) DOCTEST_FORBIT_EXPRESSION(Result, |=) }; #ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-compare") //DOCTEST_CLANG_SUPPRESS_WARNING("-Wdouble-promotion") //DOCTEST_CLANG_SUPPRESS_WARNING("-Wconversion") //DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal") DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-compare") //DOCTEST_GCC_SUPPRESS_WARNING("-Wdouble-promotion") //DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal") DOCTEST_MSVC_SUPPRESS_WARNING_PUSH // https://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389 DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch //DOCTEST_MSVC_SUPPRESS_WARNING(4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation #endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION // clang-format off #ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING #define DOCTEST_COMPARISON_RETURN_TYPE bool #else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING #define DOCTEST_COMPARISON_RETURN_TYPE typename types::enable_if::value || can_use_op::value, bool>::type inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); } inline bool gt(const char* lhs, const char* rhs) { return String(lhs) > String(rhs); } inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); } inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); } #endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING // clang-format on #define DOCTEST_RELATIONAL_OP(name, op) \ template \ DOCTEST_COMPARISON_RETURN_TYPE name(const DOCTEST_REF_WRAP(L) lhs, \ const DOCTEST_REF_WRAP(R) rhs) { \ return lhs op rhs; \ } DOCTEST_RELATIONAL_OP(eq, ==) DOCTEST_RELATIONAL_OP(ne, !=) DOCTEST_RELATIONAL_OP(lt, <) DOCTEST_RELATIONAL_OP(gt, >) DOCTEST_RELATIONAL_OP(le, <=) DOCTEST_RELATIONAL_OP(ge, >=) #ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING #define DOCTEST_CMP_EQ(l, r) l == r #define DOCTEST_CMP_NE(l, r) l != r #define DOCTEST_CMP_GT(l, r) l > r #define DOCTEST_CMP_LT(l, r) l < r #define DOCTEST_CMP_GE(l, r) l >= r #define DOCTEST_CMP_LE(l, r) l <= r #else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING #define DOCTEST_CMP_EQ(l, r) eq(l, r) #define DOCTEST_CMP_NE(l, r) ne(l, r) #define DOCTEST_CMP_GT(l, r) gt(l, r) #define DOCTEST_CMP_LT(l, r) lt(l, r) #define DOCTEST_CMP_GE(l, r) ge(l, r) #define DOCTEST_CMP_LE(l, r) le(l, r) #endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING template // cppcheck-suppress copyCtorAndEqOperator struct Expression_lhs { L lhs; assertType::Enum m_at; explicit Expression_lhs(L&& in, assertType::Enum at) : lhs(static_cast(in)) , m_at(at) {} DOCTEST_NOINLINE operator Result() { // this is needed only for MSVC 2015 DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4800) // 'int': forcing value to bool bool res = static_cast(lhs); DOCTEST_MSVC_SUPPRESS_WARNING_POP if(m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional res = !res; } if(!res || getContextOptions()->success) { return { res, (DOCTEST_STRINGIFY(lhs)) }; } return { res }; } /* This is required for user-defined conversions from Expression_lhs to L */ operator L() const { return lhs; } // clang-format off DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>, " > ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<, " < ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional // clang-format on // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &&) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ||) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, =) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, +=) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, -=) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, *=) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, /=) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, %=) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<=) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>=) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &=) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^=) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |=) // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression... DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>) }; #ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP #endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION #if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) DOCTEST_CLANG_SUPPRESS_WARNING_POP #endif struct DOCTEST_INTERFACE ExpressionDecomposer { assertType::Enum m_at; ExpressionDecomposer(assertType::Enum at); // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table) // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now... // https://github.com/catchorg/Catch2/issues/870 // https://github.com/catchorg/Catch2/issues/565 template Expression_lhs operator<<(L&& operand) { return Expression_lhs(static_cast(operand), m_at); } template ::value,void >::type* = nullptr> Expression_lhs operator<<(const L &operand) { return Expression_lhs(operand, m_at); } }; struct DOCTEST_INTERFACE TestSuite { const char* m_test_suite = nullptr; const char* m_description = nullptr; bool m_skip = false; bool m_no_breaks = false; bool m_no_output = false; bool m_may_fail = false; bool m_should_fail = false; int m_expected_failures = 0; double m_timeout = 0; TestSuite& operator*(const char* in); template TestSuite& operator*(const T& in) { in.fill(*this); return *this; } }; using funcType = void (*)(); struct DOCTEST_INTERFACE TestCase : public TestCaseData { funcType m_test; // a function pointer to the test case String m_type; // for templated test cases - gets appended to the real name int m_template_id; // an ID used to distinguish between the different versions of a templated test case String m_full_name; // contains the name (only for templated test cases!) + the template type TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, const String& type = String(), int template_id = -1); TestCase(const TestCase& other); TestCase(TestCase&&) = delete; DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function TestCase& operator=(const TestCase& other); DOCTEST_MSVC_SUPPRESS_WARNING_POP TestCase& operator=(TestCase&&) = delete; TestCase& operator*(const char* in); template TestCase& operator*(const T& in) { in.fill(*this); return *this; } bool operator<(const TestCase& other) const; ~TestCase() = default; }; // forward declarations of functions used by the macros DOCTEST_INTERFACE int regTest(const TestCase& tc); DOCTEST_INTERFACE int setTestSuite(const TestSuite& ts); DOCTEST_INTERFACE bool isDebuggerActive(); template int instantiationHelper(const T&) { return 0; } namespace binaryAssertComparison { enum Enum { eq = 0, ne, gt, lt, ge, le }; } // namespace binaryAssertComparison // clang-format off template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L), const DOCTEST_REF_WRAP(R) ) const { return false; } }; #define DOCTEST_BINARY_RELATIONAL_OP(n, op) \ template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } }; // clang-format on DOCTEST_BINARY_RELATIONAL_OP(0, doctest::detail::eq) DOCTEST_BINARY_RELATIONAL_OP(1, doctest::detail::ne) DOCTEST_BINARY_RELATIONAL_OP(2, doctest::detail::gt) DOCTEST_BINARY_RELATIONAL_OP(3, doctest::detail::lt) DOCTEST_BINARY_RELATIONAL_OP(4, doctest::detail::ge) DOCTEST_BINARY_RELATIONAL_OP(5, doctest::detail::le) struct DOCTEST_INTERFACE ResultBuilder : public AssertData { ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, const char* exception_type = "", const String& exception_string = ""); ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, const char* exception_type, const Contains& exception_string); void setResult(const Result& res); template DOCTEST_NOINLINE bool binary_assert(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { m_failed = !RelationalComparator()(lhs, rhs); if (m_failed || getContextOptions()->success) { m_decomp = stringifyBinaryExpr(lhs, ", ", rhs); } return !m_failed; } template DOCTEST_NOINLINE bool unary_assert(const DOCTEST_REF_WRAP(L) val) { m_failed = !val; if (m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional m_failed = !m_failed; } if (m_failed || getContextOptions()->success) { m_decomp = (DOCTEST_STRINGIFY(val)); } return !m_failed; } void translateException(); bool log(); void react() const; }; namespace assertAction { enum Enum { nothing = 0, dbgbreak = 1, shouldthrow = 2 }; } // namespace assertAction DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad); DOCTEST_INTERFACE bool decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, const Result& result); #define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \ do { \ if(!is_running_in_test) { \ if(failed) { \ ResultBuilder rb(at, file, line, expr); \ rb.m_failed = failed; \ rb.m_decomp = decomp; \ failed_out_of_a_testing_context(rb); \ if(isDebuggerActive() && !getContextOptions()->no_breaks) \ DOCTEST_BREAK_INTO_DEBUGGER(); \ if(checkIfShouldThrow(at)) \ throwException(); \ } \ return !failed; \ } \ } while(false) #define DOCTEST_ASSERT_IN_TESTS(decomp) \ ResultBuilder rb(at, file, line, expr); \ rb.m_failed = failed; \ if(rb.m_failed || getContextOptions()->success) \ rb.m_decomp = decomp; \ if(rb.log()) \ DOCTEST_BREAK_INTO_DEBUGGER(); \ if(rb.m_failed && checkIfShouldThrow(at)) \ throwException() template DOCTEST_NOINLINE bool binary_assert(assertType::Enum at, const char* file, int line, const char* expr, const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { bool failed = !RelationalComparator()(lhs, rhs); // ################################################################################### // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED // ################################################################################### DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); return !failed; } template DOCTEST_NOINLINE bool unary_assert(assertType::Enum at, const char* file, int line, const char* expr, const DOCTEST_REF_WRAP(L) val) { bool failed = !val; if(at & assertType::is_false) //!OCLINT bitwise operator in conditional failed = !failed; // ################################################################################### // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED // ################################################################################### DOCTEST_ASSERT_OUT_OF_TESTS((DOCTEST_STRINGIFY(val))); DOCTEST_ASSERT_IN_TESTS((DOCTEST_STRINGIFY(val))); return !failed; } struct DOCTEST_INTERFACE IExceptionTranslator { DOCTEST_DECLARE_INTERFACE(IExceptionTranslator) virtual bool translate(String&) const = 0; }; template class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class { public: explicit ExceptionTranslator(String (*translateFunction)(T)) : m_translateFunction(translateFunction) {} bool translate(String& res) const override { #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS try { throw; // lgtm [cpp/rethrow-no-exception] // cppcheck-suppress catchExceptionByValue } catch(const T& ex) { res = m_translateFunction(ex); //!OCLINT parameter reassignment return true; } catch(...) {} //!OCLINT - empty catch statement #endif // DOCTEST_CONFIG_NO_EXCEPTIONS static_cast(res); // to silence -Wunused-parameter return false; } private: String (*m_translateFunction)(T); }; DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et); // ContextScope base class used to allow implementing methods of ContextScope // that don't depend on the template parameter in doctest.cpp. struct DOCTEST_INTERFACE ContextScopeBase : public IContextScope { ContextScopeBase(const ContextScopeBase&) = delete; ContextScopeBase& operator=(const ContextScopeBase&) = delete; ContextScopeBase& operator=(ContextScopeBase&&) = delete; ~ContextScopeBase() override = default; protected: ContextScopeBase(); ContextScopeBase(ContextScopeBase&& other) noexcept; void destroy(); bool need_to_destroy{true}; }; template class ContextScope : public ContextScopeBase { L lambda_; public: explicit ContextScope(const L &lambda) : lambda_(lambda) {} explicit ContextScope(L&& lambda) : lambda_(static_cast(lambda)) { } ContextScope(const ContextScope&) = delete; ContextScope(ContextScope&&) noexcept = default; ContextScope& operator=(const ContextScope&) = delete; ContextScope& operator=(ContextScope&&) = delete; void stringify(std::ostream* s) const override { lambda_(s); } ~ContextScope() override { if (need_to_destroy) { destroy(); } } }; struct DOCTEST_INTERFACE MessageBuilder : public MessageData { std::ostream* m_stream; bool logged = false; MessageBuilder(const char* file, int line, assertType::Enum severity); MessageBuilder(const MessageBuilder&) = delete; MessageBuilder(MessageBuilder&&) = delete; MessageBuilder& operator=(const MessageBuilder&) = delete; MessageBuilder& operator=(MessageBuilder&&) = delete; ~MessageBuilder(); // the preferred way of chaining parameters for stringification DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866) template MessageBuilder& operator,(const T& in) { *m_stream << (DOCTEST_STRINGIFY(in)); return *this; } DOCTEST_MSVC_SUPPRESS_WARNING_POP // kept here just for backwards-compatibility - the comma operator should be preferred now template MessageBuilder& operator<<(const T& in) { return this->operator,(in); } // the `,` operator has the lowest operator precedence - if `<<` is used by the user then // the `,` operator will be called last which is not what we want and thus the `*` operator // is used first (has higher operator precedence compared to `<<`) so that we guarantee that // an operator of the MessageBuilder class is called first before the rest of the parameters template MessageBuilder& operator*(const T& in) { return this->operator,(in); } bool log(); void react(); }; template ContextScope MakeContextScope(const L &lambda) { return ContextScope(lambda); } } // namespace detail #define DOCTEST_DEFINE_DECORATOR(name, type, def) \ struct name \ { \ type data; \ name(type in = def) \ : data(in) {} \ void fill(detail::TestCase& state) const { state.DOCTEST_CAT(m_, name) = data; } \ void fill(detail::TestSuite& state) const { state.DOCTEST_CAT(m_, name) = data; } \ } DOCTEST_DEFINE_DECORATOR(test_suite, const char*, ""); DOCTEST_DEFINE_DECORATOR(description, const char*, ""); DOCTEST_DEFINE_DECORATOR(skip, bool, true); DOCTEST_DEFINE_DECORATOR(no_breaks, bool, true); DOCTEST_DEFINE_DECORATOR(no_output, bool, true); DOCTEST_DEFINE_DECORATOR(timeout, double, 0); DOCTEST_DEFINE_DECORATOR(may_fail, bool, true); DOCTEST_DEFINE_DECORATOR(should_fail, bool, true); DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0); template int registerExceptionTranslator(String (*translateFunction)(T)) { DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") static detail::ExceptionTranslator exceptionTranslator(translateFunction); DOCTEST_CLANG_SUPPRESS_WARNING_POP detail::registerExceptionTranslatorImpl(&exceptionTranslator); return 0; } } // namespace doctest // in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro // introduces an anonymous namespace in which getCurrentTestSuite gets overridden namespace doctest_detail_test_suite_ns { DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite(); } // namespace doctest_detail_test_suite_ns namespace doctest { #else // DOCTEST_CONFIG_DISABLE template int registerExceptionTranslator(String (*)(T)) { return 0; } #endif // DOCTEST_CONFIG_DISABLE namespace detail { using assert_handler = void (*)(const AssertData&); struct ContextState; } // namespace detail class DOCTEST_INTERFACE Context { detail::ContextState* p; void parseArgs(int argc, const char* const* argv, bool withDefaults = false); public: explicit Context(int argc = 0, const char* const* argv = nullptr); Context(const Context&) = delete; Context(Context&&) = delete; Context& operator=(const Context&) = delete; Context& operator=(Context&&) = delete; ~Context(); // NOLINT(performance-trivially-destructible) void applyCommandLine(int argc, const char* const* argv); void addFilter(const char* filter, const char* value); void clearFilters(); void setOption(const char* option, bool value); void setOption(const char* option, int value); void setOption(const char* option, const char* value); bool shouldExit(); void setAsDefaultForAssertsOutOfTestCases(); void setAssertHandler(detail::assert_handler ah); void setCout(std::ostream* out); int run(); }; namespace TestCaseFailureReason { enum Enum { None = 0, AssertFailure = 1, // an assertion has failed in the test case Exception = 2, // test case threw an exception Crash = 4, // a crash... TooManyFailedAsserts = 8, // the abort-after option Timeout = 16, // see the timeout decorator ShouldHaveFailedButDidnt = 32, // see the should_fail decorator ShouldHaveFailedAndDid = 64, // see the should_fail decorator DidntFailExactlyNumTimes = 128, // see the expected_failures decorator FailedExactlyNumTimes = 256, // see the expected_failures decorator CouldHaveFailedAndDid = 512 // see the may_fail decorator }; } // namespace TestCaseFailureReason struct DOCTEST_INTERFACE CurrentTestCaseStats { int numAssertsCurrentTest; int numAssertsFailedCurrentTest; double seconds; int failure_flags; // use TestCaseFailureReason::Enum bool testCaseSuccess; }; struct DOCTEST_INTERFACE TestCaseException { String error_string; bool is_crash; }; struct DOCTEST_INTERFACE TestRunStats { unsigned numTestCases; unsigned numTestCasesPassingFilters; unsigned numTestSuitesPassingFilters; unsigned numTestCasesFailed; int numAsserts; int numAssertsFailed; }; struct QueryData { const TestRunStats* run_stats = nullptr; const TestCaseData** data = nullptr; unsigned num_data = 0; }; struct DOCTEST_INTERFACE IReporter { // The constructor has to accept "const ContextOptions&" as a single argument // which has most of the options for the run + a pointer to the stdout stream // Reporter(const ContextOptions& in) // called when a query should be reported (listing test cases, printing the version, etc.) virtual void report_query(const QueryData&) = 0; // called when the whole test run starts virtual void test_run_start() = 0; // called when the whole test run ends (caching a pointer to the input doesn't make sense here) virtual void test_run_end(const TestRunStats&) = 0; // called when a test case is started (safe to cache a pointer to the input) virtual void test_case_start(const TestCaseData&) = 0; // called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input) virtual void test_case_reenter(const TestCaseData&) = 0; // called when a test case has ended virtual void test_case_end(const CurrentTestCaseStats&) = 0; // called when an exception is thrown from the test case (or it crashes) virtual void test_case_exception(const TestCaseException&) = 0; // called whenever a subcase is entered (don't cache pointers to the input) virtual void subcase_start(const SubcaseSignature&) = 0; // called whenever a subcase is exited (don't cache pointers to the input) virtual void subcase_end() = 0; // called for each assert (don't cache pointers to the input) virtual void log_assert(const AssertData&) = 0; // called for each message (don't cache pointers to the input) virtual void log_message(const MessageData&) = 0; // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator // or isn't in the execution range (between first and last) (safe to cache a pointer to the input) virtual void test_case_skipped(const TestCaseData&) = 0; DOCTEST_DECLARE_INTERFACE(IReporter) // can obtain all currently active contexts and stringify them if one wishes to do so static int get_num_active_contexts(); static const IContextScope* const* get_active_contexts(); // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown static int get_num_stringified_contexts(); static const String* get_stringified_contexts(); }; namespace detail { using reporterCreatorFunc = IReporter* (*)(const ContextOptions&); DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter); template IReporter* reporterCreator(const ContextOptions& o) { return new Reporter(o); } } // namespace detail template int registerReporter(const char* name, int priority, bool isReporter) { detail::registerReporterImpl(name, priority, detail::reporterCreator, isReporter); return 0; } } // namespace doctest #ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES #define DOCTEST_FUNC_EMPTY [] { return false; }() #else #define DOCTEST_FUNC_EMPTY (void)0 #endif // if registering is not disabled #ifndef DOCTEST_CONFIG_DISABLE #ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES #define DOCTEST_FUNC_SCOPE_BEGIN [&] #define DOCTEST_FUNC_SCOPE_END () #define DOCTEST_FUNC_SCOPE_RET(v) return v #else #define DOCTEST_FUNC_SCOPE_BEGIN do #define DOCTEST_FUNC_SCOPE_END while(false) #define DOCTEST_FUNC_SCOPE_RET(v) (void)0 #endif // common code in asserts - for convenience #define DOCTEST_ASSERT_LOG_REACT_RETURN(b) \ if(b.log()) DOCTEST_BREAK_INTO_DEBUGGER(); \ b.react(); \ DOCTEST_FUNC_SCOPE_RET(!b.m_failed) #ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS #define DOCTEST_WRAP_IN_TRY(x) x; #else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS #define DOCTEST_WRAP_IN_TRY(x) \ try { \ x; \ } catch(...) { DOCTEST_RB.translateException(); } #endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS #ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS #define DOCTEST_CAST_TO_VOID(...) \ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast") \ static_cast(__VA_ARGS__); \ DOCTEST_GCC_SUPPRESS_WARNING_POP #else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS #define DOCTEST_CAST_TO_VOID(...) __VA_ARGS__; #endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS // registers the test by initializing a dummy var with a function #define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \ global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT */ \ doctest::detail::regTest( \ doctest::detail::TestCase( \ f, __FILE__, __LINE__, \ doctest_detail_test_suite_ns::getCurrentTestSuite()) * \ decorators)) #define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \ namespace { /* NOLINT */ \ struct der : public base \ { \ void f(); \ }; \ static DOCTEST_INLINE_NOINLINE void func() { \ der v; \ v.f(); \ } \ DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators) \ } \ DOCTEST_INLINE_NOINLINE void der::f() // NOLINT(misc-definitions-in-headers) #define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \ static void f(); \ DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators) \ static void f() #define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \ static doctest::detail::funcType proxy() { return f; } \ DOCTEST_REGISTER_FUNCTION(inline, proxy(), decorators) \ static void f() // for registering tests #define DOCTEST_TEST_CASE(decorators) \ DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) // for registering tests in classes - requires C++17 for inline variables! #if DOCTEST_CPLUSPLUS >= 201703L #define DOCTEST_TEST_CASE_CLASS(decorators) \ DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), \ DOCTEST_ANONYMOUS(DOCTEST_ANON_PROXY_), \ decorators) #else // DOCTEST_TEST_CASE_CLASS #define DOCTEST_TEST_CASE_CLASS(...) \ TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER #endif // DOCTEST_TEST_CASE_CLASS // for registering tests with a fixture #define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \ DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), c, \ DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) // for converting types to strings without the header and demangling #define DOCTEST_TYPE_TO_STRING_AS(str, ...) \ namespace doctest { \ template <> \ inline String toString<__VA_ARGS__>() { \ return str; \ } \ } \ static_assert(true, "") #define DOCTEST_TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING_AS(#__VA_ARGS__, __VA_ARGS__) #define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \ template \ static void func(); \ namespace { /* NOLINT */ \ template \ struct iter; \ template \ struct iter> \ { \ iter(const char* file, unsigned line, int index) { \ doctest::detail::regTest(doctest::detail::TestCase(func, file, line, \ doctest_detail_test_suite_ns::getCurrentTestSuite(), \ doctest::toString(), \ int(line) * 1000 + index) \ * dec); \ iter>(file, line, index + 1); \ } \ }; \ template <> \ struct iter> \ { \ iter(const char*, unsigned, int) {} \ }; \ } \ template \ static void func() #define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \ DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \ DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)) #define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...) \ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY), /* NOLINT(cert-err58-cpp, fuchsia-statically-constructed-objects) */ \ doctest::detail::instantiationHelper( \ DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0))) #define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ static_assert(true, "") #define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) \ static_assert(true, "") #define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \ DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \ DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(anon, anon, std::tuple<__VA_ARGS__>) \ template \ static void anon() #define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \ DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) // for subcases #define DOCTEST_SUBCASE(name) \ if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ doctest::detail::Subcase(name, __FILE__, __LINE__)) // for grouping tests in test suites by using code blocks #define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \ namespace ns_name { namespace doctest_detail_test_suite_ns { \ static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() noexcept { \ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmissing-field-initializers") \ static doctest::detail::TestSuite data{}; \ static bool inited = false; \ DOCTEST_MSVC_SUPPRESS_WARNING_POP \ DOCTEST_CLANG_SUPPRESS_WARNING_POP \ DOCTEST_GCC_SUPPRESS_WARNING_POP \ if(!inited) { \ data* decorators; \ inited = true; \ } \ return data; \ } \ } \ } \ namespace ns_name #define DOCTEST_TEST_SUITE(decorators) \ DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(DOCTEST_ANON_SUITE_)) // for starting a testsuite block #define DOCTEST_TEST_SUITE_BEGIN(decorators) \ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */ \ doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators)) \ static_assert(true, "") // for ending a testsuite block #define DOCTEST_TEST_SUITE_END \ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */ \ doctest::detail::setTestSuite(doctest::detail::TestSuite() * "")) \ using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int // for registering exception translators #define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \ inline doctest::String translatorName(signature); \ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), /* NOLINT(cert-err58-cpp) */ \ doctest::registerExceptionTranslator(translatorName)) \ doctest::String translatorName(signature) #define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), \ signature) // for registering reporters #define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \ doctest::registerReporter(name, priority, true)) \ static_assert(true, "") // for registering listeners #define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \ doctest::registerReporter(name, priority, false)) \ static_assert(true, "") // clang-format off // for logging - disabling formatting because it's important to have these on 2 separate lines - see PR #557 #define DOCTEST_INFO(...) \ DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_), \ DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_OTHER_), \ __VA_ARGS__) // clang-format on #define DOCTEST_INFO_IMPL(mb_name, s_name, ...) \ auto DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \ [&](std::ostream* s_name) { \ doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \ mb_name.m_stream = s_name; \ mb_name * __VA_ARGS__; \ }) #define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := ", x) #define DOCTEST_ADD_AT_IMPL(type, file, line, mb, ...) \ DOCTEST_FUNC_SCOPE_BEGIN { \ doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \ mb * __VA_ARGS__; \ if(mb.log()) \ DOCTEST_BREAK_INTO_DEBUGGER(); \ mb.react(); \ } DOCTEST_FUNC_SCOPE_END // clang-format off #define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) #define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) #define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) // clang-format on #define DOCTEST_MESSAGE(...) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, __VA_ARGS__) #define DOCTEST_FAIL_CHECK(...) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, __VA_ARGS__) #define DOCTEST_FAIL(...) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, __VA_ARGS__) #define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility. #ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS #define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */ \ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, #__VA_ARGS__); \ DOCTEST_WRAP_IN_TRY(DOCTEST_RB.setResult( \ doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ << __VA_ARGS__)) /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */ \ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB) \ DOCTEST_CLANG_SUPPRESS_WARNING_POP #define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ DOCTEST_FUNC_SCOPE_BEGIN { \ DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \ } DOCTEST_FUNC_SCOPE_END // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) #define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \ DOCTEST_FUNC_SCOPE_BEGIN { \ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, #__VA_ARGS__); \ DOCTEST_WRAP_IN_TRY( \ DOCTEST_RB.binary_assert( \ __VA_ARGS__)) \ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ } DOCTEST_FUNC_SCOPE_END #define DOCTEST_UNARY_ASSERT(assert_type, ...) \ DOCTEST_FUNC_SCOPE_BEGIN { \ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, #__VA_ARGS__); \ DOCTEST_WRAP_IN_TRY(DOCTEST_RB.unary_assert(__VA_ARGS__)) \ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ } DOCTEST_FUNC_SCOPE_END #else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS // necessary for _MESSAGE #define DOCTEST_ASSERT_IMPLEMENT_2 DOCTEST_ASSERT_IMPLEMENT_1 #define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ doctest::detail::decomp_assert( \ doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, \ doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP #define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \ doctest::detail::binary_assert( \ doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__) #define DOCTEST_UNARY_ASSERT(assert_type, ...) \ doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \ #__VA_ARGS__, __VA_ARGS__) #endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS #define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__) #define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK, __VA_ARGS__) #define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE, __VA_ARGS__) #define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN_FALSE, __VA_ARGS__) #define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK_FALSE, __VA_ARGS__) #define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__) // clang-format off #define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } DOCTEST_FUNC_SCOPE_END // clang-format on #define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__) #define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__) #define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__) #define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__) #define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__) #define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__) #define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__) #define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__) #define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__) #define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__) #define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__) #define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__) #define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__) #define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__) #define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__) #define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__) #define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__) #define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__) #define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__) #define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__) #define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__) #define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__) #define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__) #define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__) #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS #define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \ DOCTEST_FUNC_SCOPE_BEGIN { \ if(!doctest::getContextOptions()->no_throw) { \ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, #expr, #__VA_ARGS__, message); \ try { \ DOCTEST_CAST_TO_VOID(expr) \ } catch(const typename doctest::detail::types::remove_const< \ typename doctest::detail::types::remove_reference<__VA_ARGS__>::type>::type&) {\ DOCTEST_RB.translateException(); \ DOCTEST_RB.m_threw_as = true; \ } catch(...) { DOCTEST_RB.translateException(); } \ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ } else { /* NOLINT(*-else-after-return) */ \ DOCTEST_FUNC_SCOPE_RET(false); \ } \ } DOCTEST_FUNC_SCOPE_END #define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...) \ DOCTEST_FUNC_SCOPE_BEGIN { \ if(!doctest::getContextOptions()->no_throw) { \ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, expr_str, "", __VA_ARGS__); \ try { \ DOCTEST_CAST_TO_VOID(expr) \ } catch(...) { DOCTEST_RB.translateException(); } \ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ } else { /* NOLINT(*-else-after-return) */ \ DOCTEST_FUNC_SCOPE_RET(false); \ } \ } DOCTEST_FUNC_SCOPE_END #define DOCTEST_ASSERT_NOTHROW(assert_type, ...) \ DOCTEST_FUNC_SCOPE_BEGIN { \ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, #__VA_ARGS__); \ try { \ DOCTEST_CAST_TO_VOID(__VA_ARGS__) \ } catch(...) { DOCTEST_RB.translateException(); } \ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ } DOCTEST_FUNC_SCOPE_END // clang-format off #define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "") #define DOCTEST_CHECK_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_CHECK_THROWS, "") #define DOCTEST_REQUIRE_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_REQUIRE_THROWS, "") #define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__) #define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__) #define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__) #define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_WARN_THROWS_WITH, __VA_ARGS__) #define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_CHECK_THROWS_WITH, __VA_ARGS__) #define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__) #define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__) #define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__) #define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__) #define DOCTEST_WARN_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_WARN_NOTHROW, __VA_ARGS__) #define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__) #define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__) #define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END #define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END // clang-format on #endif // DOCTEST_CONFIG_NO_EXCEPTIONS // ================================================================================================= // == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING! == // == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY! == // ================================================================================================= #else // DOCTEST_CONFIG_DISABLE #define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \ namespace /* NOLINT */ { \ template \ struct der : public base \ { void f(); }; \ } \ template \ inline void der::f() #define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name) \ template \ static inline void f() // for registering tests #define DOCTEST_TEST_CASE(name) \ DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) // for registering tests in classes #define DOCTEST_TEST_CASE_CLASS(name) \ DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) // for registering tests with a fixture #define DOCTEST_TEST_CASE_FIXTURE(x, name) \ DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), x, \ DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) // for converting types to strings without the header and demangling #define DOCTEST_TYPE_TO_STRING_AS(str, ...) static_assert(true, "") #define DOCTEST_TYPE_TO_STRING(...) static_assert(true, "") // for typed tests #define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \ template \ inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() #define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \ template \ inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() #define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) static_assert(true, "") #define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) static_assert(true, "") // for subcases #define DOCTEST_SUBCASE(name) // for a testsuite block #define DOCTEST_TEST_SUITE(name) namespace // NOLINT // for starting a testsuite block #define DOCTEST_TEST_SUITE_BEGIN(name) static_assert(true, "") // for ending a testsuite block #define DOCTEST_TEST_SUITE_END using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int #define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ template \ static inline doctest::String DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_)(signature) #define DOCTEST_REGISTER_REPORTER(name, priority, reporter) #define DOCTEST_REGISTER_LISTENER(name, priority, reporter) #define DOCTEST_INFO(...) (static_cast(0)) #define DOCTEST_CAPTURE(x) (static_cast(0)) #define DOCTEST_ADD_MESSAGE_AT(file, line, ...) (static_cast(0)) #define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) (static_cast(0)) #define DOCTEST_ADD_FAIL_AT(file, line, ...) (static_cast(0)) #define DOCTEST_MESSAGE(...) (static_cast(0)) #define DOCTEST_FAIL_CHECK(...) (static_cast(0)) #define DOCTEST_FAIL(...) (static_cast(0)) #if defined(DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED) \ && defined(DOCTEST_CONFIG_ASSERTS_RETURN_VALUES) #define DOCTEST_WARN(...) [&] { return __VA_ARGS__; }() #define DOCTEST_CHECK(...) [&] { return __VA_ARGS__; }() #define DOCTEST_REQUIRE(...) [&] { return __VA_ARGS__; }() #define DOCTEST_WARN_FALSE(...) [&] { return !(__VA_ARGS__); }() #define DOCTEST_CHECK_FALSE(...) [&] { return !(__VA_ARGS__); }() #define DOCTEST_REQUIRE_FALSE(...) [&] { return !(__VA_ARGS__); }() #define DOCTEST_WARN_MESSAGE(cond, ...) [&] { return cond; }() #define DOCTEST_CHECK_MESSAGE(cond, ...) [&] { return cond; }() #define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] { return cond; }() #define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() #define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() #define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() namespace doctest { namespace detail { #define DOCTEST_RELATIONAL_OP(name, op) \ template \ bool name(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs op rhs; } DOCTEST_RELATIONAL_OP(eq, ==) DOCTEST_RELATIONAL_OP(ne, !=) DOCTEST_RELATIONAL_OP(lt, <) DOCTEST_RELATIONAL_OP(gt, >) DOCTEST_RELATIONAL_OP(le, <=) DOCTEST_RELATIONAL_OP(ge, >=) } // namespace detail } // namespace doctest #define DOCTEST_WARN_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() #define DOCTEST_CHECK_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() #define DOCTEST_REQUIRE_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() #define DOCTEST_WARN_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() #define DOCTEST_CHECK_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() #define DOCTEST_REQUIRE_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() #define DOCTEST_WARN_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() #define DOCTEST_CHECK_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() #define DOCTEST_REQUIRE_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() #define DOCTEST_WARN_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() #define DOCTEST_CHECK_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() #define DOCTEST_REQUIRE_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() #define DOCTEST_WARN_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() #define DOCTEST_CHECK_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() #define DOCTEST_REQUIRE_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() #define DOCTEST_WARN_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() #define DOCTEST_CHECK_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() #define DOCTEST_REQUIRE_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() #define DOCTEST_WARN_UNARY(...) [&] { return __VA_ARGS__; }() #define DOCTEST_CHECK_UNARY(...) [&] { return __VA_ARGS__; }() #define DOCTEST_REQUIRE_UNARY(...) [&] { return __VA_ARGS__; }() #define DOCTEST_WARN_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() #define DOCTEST_CHECK_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() #define DOCTEST_REQUIRE_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS #define DOCTEST_WARN_THROWS_WITH(expr, with, ...) [] { static_assert(false, "Exception translation is not available when doctest is disabled."); return false; }() #define DOCTEST_CHECK_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) #define DOCTEST_REQUIRE_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) #define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) #define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) #define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) #define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) #define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) #define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) #define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) #define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) #define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) #define DOCTEST_WARN_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() #define DOCTEST_CHECK_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() #define DOCTEST_REQUIRE_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() #define DOCTEST_WARN_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() #define DOCTEST_CHECK_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() #define DOCTEST_REQUIRE_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() #define DOCTEST_WARN_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() #define DOCTEST_CHECK_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() #define DOCTEST_REQUIRE_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() #define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() #define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() #define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() #define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() #define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() #define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() #define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() #define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() #define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() #endif // DOCTEST_CONFIG_NO_EXCEPTIONS #else // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED #define DOCTEST_WARN(...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK(...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE(...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_FALSE(...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_FALSE(...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_FALSE(...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_EQ(...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_EQ(...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_EQ(...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_NE(...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_NE(...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_NE(...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_GT(...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_GT(...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_GT(...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_LT(...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_LT(...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_LT(...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_GE(...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_GE(...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_GE(...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_LE(...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_LE(...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_LE(...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_UNARY(...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_UNARY(...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_UNARY(...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS #define DOCTEST_WARN_THROWS(...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_THROWS(...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_THROWS(...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_NOTHROW(...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_NOTHROW(...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY #define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY #endif // DOCTEST_CONFIG_NO_EXCEPTIONS #endif // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED #endif // DOCTEST_CONFIG_DISABLE #ifdef DOCTEST_CONFIG_NO_EXCEPTIONS #ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS #define DOCTEST_EXCEPTION_EMPTY_FUNC DOCTEST_FUNC_EMPTY #else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS #define DOCTEST_EXCEPTION_EMPTY_FUNC [] { static_assert(false, "Exceptions are disabled! " \ "Use DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS if you want to compile with exceptions disabled."); return false; }() #undef DOCTEST_REQUIRE #undef DOCTEST_REQUIRE_FALSE #undef DOCTEST_REQUIRE_MESSAGE #undef DOCTEST_REQUIRE_FALSE_MESSAGE #undef DOCTEST_REQUIRE_EQ #undef DOCTEST_REQUIRE_NE #undef DOCTEST_REQUIRE_GT #undef DOCTEST_REQUIRE_LT #undef DOCTEST_REQUIRE_GE #undef DOCTEST_REQUIRE_LE #undef DOCTEST_REQUIRE_UNARY #undef DOCTEST_REQUIRE_UNARY_FALSE #define DOCTEST_REQUIRE DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_FALSE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_EQ DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_NE DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_GT DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_LT DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_GE DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_LE DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_UNARY DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_UNARY_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC #endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS #define DOCTEST_WARN_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_CHECK_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_WARN_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_CHECK_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC #endif // DOCTEST_CONFIG_NO_EXCEPTIONS // clang-format off // KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS #define DOCTEST_FAST_WARN_EQ DOCTEST_WARN_EQ #define DOCTEST_FAST_CHECK_EQ DOCTEST_CHECK_EQ #define DOCTEST_FAST_REQUIRE_EQ DOCTEST_REQUIRE_EQ #define DOCTEST_FAST_WARN_NE DOCTEST_WARN_NE #define DOCTEST_FAST_CHECK_NE DOCTEST_CHECK_NE #define DOCTEST_FAST_REQUIRE_NE DOCTEST_REQUIRE_NE #define DOCTEST_FAST_WARN_GT DOCTEST_WARN_GT #define DOCTEST_FAST_CHECK_GT DOCTEST_CHECK_GT #define DOCTEST_FAST_REQUIRE_GT DOCTEST_REQUIRE_GT #define DOCTEST_FAST_WARN_LT DOCTEST_WARN_LT #define DOCTEST_FAST_CHECK_LT DOCTEST_CHECK_LT #define DOCTEST_FAST_REQUIRE_LT DOCTEST_REQUIRE_LT #define DOCTEST_FAST_WARN_GE DOCTEST_WARN_GE #define DOCTEST_FAST_CHECK_GE DOCTEST_CHECK_GE #define DOCTEST_FAST_REQUIRE_GE DOCTEST_REQUIRE_GE #define DOCTEST_FAST_WARN_LE DOCTEST_WARN_LE #define DOCTEST_FAST_CHECK_LE DOCTEST_CHECK_LE #define DOCTEST_FAST_REQUIRE_LE DOCTEST_REQUIRE_LE #define DOCTEST_FAST_WARN_UNARY DOCTEST_WARN_UNARY #define DOCTEST_FAST_CHECK_UNARY DOCTEST_CHECK_UNARY #define DOCTEST_FAST_REQUIRE_UNARY DOCTEST_REQUIRE_UNARY #define DOCTEST_FAST_WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE #define DOCTEST_FAST_CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE #define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE #define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id,__VA_ARGS__) // clang-format on // BDD style macros // clang-format off #define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE(" Scenario: " name) #define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS(" Scenario: " name) #define DOCTEST_SCENARIO_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(" Scenario: " name, T, __VA_ARGS__) #define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(" Scenario: " name, T, id) #define DOCTEST_GIVEN(name) DOCTEST_SUBCASE(" Given: " name) #define DOCTEST_WHEN(name) DOCTEST_SUBCASE(" When: " name) #define DOCTEST_AND_WHEN(name) DOCTEST_SUBCASE("And when: " name) #define DOCTEST_THEN(name) DOCTEST_SUBCASE(" Then: " name) #define DOCTEST_AND_THEN(name) DOCTEST_SUBCASE(" And: " name) // clang-format on // == SHORT VERSIONS OF THE MACROS #ifndef DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES #define TEST_CASE(name) DOCTEST_TEST_CASE(name) #define TEST_CASE_CLASS(name) DOCTEST_TEST_CASE_CLASS(name) #define TEST_CASE_FIXTURE(x, name) DOCTEST_TEST_CASE_FIXTURE(x, name) #define TYPE_TO_STRING_AS(str, ...) DOCTEST_TYPE_TO_STRING_AS(str, __VA_ARGS__) #define TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING(__VA_ARGS__) #define TEST_CASE_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(name, T, __VA_ARGS__) #define TEST_CASE_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, T, id) #define TEST_CASE_TEMPLATE_INVOKE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, __VA_ARGS__) #define TEST_CASE_TEMPLATE_APPLY(id, ...) DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, __VA_ARGS__) #define SUBCASE(name) DOCTEST_SUBCASE(name) #define TEST_SUITE(decorators) DOCTEST_TEST_SUITE(decorators) #define TEST_SUITE_BEGIN(name) DOCTEST_TEST_SUITE_BEGIN(name) #define TEST_SUITE_END DOCTEST_TEST_SUITE_END #define REGISTER_EXCEPTION_TRANSLATOR(signature) DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) #define REGISTER_REPORTER(name, priority, reporter) DOCTEST_REGISTER_REPORTER(name, priority, reporter) #define REGISTER_LISTENER(name, priority, reporter) DOCTEST_REGISTER_LISTENER(name, priority, reporter) #define INFO(...) DOCTEST_INFO(__VA_ARGS__) #define CAPTURE(x) DOCTEST_CAPTURE(x) #define ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_MESSAGE_AT(file, line, __VA_ARGS__) #define ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_FAIL_CHECK_AT(file, line, __VA_ARGS__) #define ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_FAIL_AT(file, line, __VA_ARGS__) #define MESSAGE(...) DOCTEST_MESSAGE(__VA_ARGS__) #define FAIL_CHECK(...) DOCTEST_FAIL_CHECK(__VA_ARGS__) #define FAIL(...) DOCTEST_FAIL(__VA_ARGS__) #define TO_LVALUE(...) DOCTEST_TO_LVALUE(__VA_ARGS__) #define WARN(...) DOCTEST_WARN(__VA_ARGS__) #define WARN_FALSE(...) DOCTEST_WARN_FALSE(__VA_ARGS__) #define WARN_THROWS(...) DOCTEST_WARN_THROWS(__VA_ARGS__) #define WARN_THROWS_AS(expr, ...) DOCTEST_WARN_THROWS_AS(expr, __VA_ARGS__) #define WARN_THROWS_WITH(expr, ...) DOCTEST_WARN_THROWS_WITH(expr, __VA_ARGS__) #define WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_WARN_THROWS_WITH_AS(expr, with, __VA_ARGS__) #define WARN_NOTHROW(...) DOCTEST_WARN_NOTHROW(__VA_ARGS__) #define CHECK(...) DOCTEST_CHECK(__VA_ARGS__) #define CHECK_FALSE(...) DOCTEST_CHECK_FALSE(__VA_ARGS__) #define CHECK_THROWS(...) DOCTEST_CHECK_THROWS(__VA_ARGS__) #define CHECK_THROWS_AS(expr, ...) DOCTEST_CHECK_THROWS_AS(expr, __VA_ARGS__) #define CHECK_THROWS_WITH(expr, ...) DOCTEST_CHECK_THROWS_WITH(expr, __VA_ARGS__) #define CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_AS(expr, with, __VA_ARGS__) #define CHECK_NOTHROW(...) DOCTEST_CHECK_NOTHROW(__VA_ARGS__) #define REQUIRE(...) DOCTEST_REQUIRE(__VA_ARGS__) #define REQUIRE_FALSE(...) DOCTEST_REQUIRE_FALSE(__VA_ARGS__) #define REQUIRE_THROWS(...) DOCTEST_REQUIRE_THROWS(__VA_ARGS__) #define REQUIRE_THROWS_AS(expr, ...) DOCTEST_REQUIRE_THROWS_AS(expr, __VA_ARGS__) #define REQUIRE_THROWS_WITH(expr, ...) DOCTEST_REQUIRE_THROWS_WITH(expr, __VA_ARGS__) #define REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, __VA_ARGS__) #define REQUIRE_NOTHROW(...) DOCTEST_REQUIRE_NOTHROW(__VA_ARGS__) #define WARN_MESSAGE(cond, ...) DOCTEST_WARN_MESSAGE(cond, __VA_ARGS__) #define WARN_FALSE_MESSAGE(cond, ...) DOCTEST_WARN_FALSE_MESSAGE(cond, __VA_ARGS__) #define WARN_THROWS_MESSAGE(expr, ...) DOCTEST_WARN_THROWS_MESSAGE(expr, __VA_ARGS__) #define WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) #define WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) #define WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) #define WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_WARN_NOTHROW_MESSAGE(expr, __VA_ARGS__) #define CHECK_MESSAGE(cond, ...) DOCTEST_CHECK_MESSAGE(cond, __VA_ARGS__) #define CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_CHECK_FALSE_MESSAGE(cond, __VA_ARGS__) #define CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_CHECK_THROWS_MESSAGE(expr, __VA_ARGS__) #define CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) #define CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) #define CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) #define CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_CHECK_NOTHROW_MESSAGE(expr, __VA_ARGS__) #define REQUIRE_MESSAGE(cond, ...) DOCTEST_REQUIRE_MESSAGE(cond, __VA_ARGS__) #define REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_REQUIRE_FALSE_MESSAGE(cond, __VA_ARGS__) #define REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_REQUIRE_THROWS_MESSAGE(expr, __VA_ARGS__) #define REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) #define REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) #define REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) #define REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, __VA_ARGS__) #define SCENARIO(name) DOCTEST_SCENARIO(name) #define SCENARIO_CLASS(name) DOCTEST_SCENARIO_CLASS(name) #define SCENARIO_TEMPLATE(name, T, ...) DOCTEST_SCENARIO_TEMPLATE(name, T, __VA_ARGS__) #define SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) #define GIVEN(name) DOCTEST_GIVEN(name) #define WHEN(name) DOCTEST_WHEN(name) #define AND_WHEN(name) DOCTEST_AND_WHEN(name) #define THEN(name) DOCTEST_THEN(name) #define AND_THEN(name) DOCTEST_AND_THEN(name) #define WARN_EQ(...) DOCTEST_WARN_EQ(__VA_ARGS__) #define CHECK_EQ(...) DOCTEST_CHECK_EQ(__VA_ARGS__) #define REQUIRE_EQ(...) DOCTEST_REQUIRE_EQ(__VA_ARGS__) #define WARN_NE(...) DOCTEST_WARN_NE(__VA_ARGS__) #define CHECK_NE(...) DOCTEST_CHECK_NE(__VA_ARGS__) #define REQUIRE_NE(...) DOCTEST_REQUIRE_NE(__VA_ARGS__) #define WARN_GT(...) DOCTEST_WARN_GT(__VA_ARGS__) #define CHECK_GT(...) DOCTEST_CHECK_GT(__VA_ARGS__) #define REQUIRE_GT(...) DOCTEST_REQUIRE_GT(__VA_ARGS__) #define WARN_LT(...) DOCTEST_WARN_LT(__VA_ARGS__) #define CHECK_LT(...) DOCTEST_CHECK_LT(__VA_ARGS__) #define REQUIRE_LT(...) DOCTEST_REQUIRE_LT(__VA_ARGS__) #define WARN_GE(...) DOCTEST_WARN_GE(__VA_ARGS__) #define CHECK_GE(...) DOCTEST_CHECK_GE(__VA_ARGS__) #define REQUIRE_GE(...) DOCTEST_REQUIRE_GE(__VA_ARGS__) #define WARN_LE(...) DOCTEST_WARN_LE(__VA_ARGS__) #define CHECK_LE(...) DOCTEST_CHECK_LE(__VA_ARGS__) #define REQUIRE_LE(...) DOCTEST_REQUIRE_LE(__VA_ARGS__) #define WARN_UNARY(...) DOCTEST_WARN_UNARY(__VA_ARGS__) #define CHECK_UNARY(...) DOCTEST_CHECK_UNARY(__VA_ARGS__) #define REQUIRE_UNARY(...) DOCTEST_REQUIRE_UNARY(__VA_ARGS__) #define WARN_UNARY_FALSE(...) DOCTEST_WARN_UNARY_FALSE(__VA_ARGS__) #define CHECK_UNARY_FALSE(...) DOCTEST_CHECK_UNARY_FALSE(__VA_ARGS__) #define REQUIRE_UNARY_FALSE(...) DOCTEST_REQUIRE_UNARY_FALSE(__VA_ARGS__) // KEPT FOR BACKWARDS COMPATIBILITY #define FAST_WARN_EQ(...) DOCTEST_FAST_WARN_EQ(__VA_ARGS__) #define FAST_CHECK_EQ(...) DOCTEST_FAST_CHECK_EQ(__VA_ARGS__) #define FAST_REQUIRE_EQ(...) DOCTEST_FAST_REQUIRE_EQ(__VA_ARGS__) #define FAST_WARN_NE(...) DOCTEST_FAST_WARN_NE(__VA_ARGS__) #define FAST_CHECK_NE(...) DOCTEST_FAST_CHECK_NE(__VA_ARGS__) #define FAST_REQUIRE_NE(...) DOCTEST_FAST_REQUIRE_NE(__VA_ARGS__) #define FAST_WARN_GT(...) DOCTEST_FAST_WARN_GT(__VA_ARGS__) #define FAST_CHECK_GT(...) DOCTEST_FAST_CHECK_GT(__VA_ARGS__) #define FAST_REQUIRE_GT(...) DOCTEST_FAST_REQUIRE_GT(__VA_ARGS__) #define FAST_WARN_LT(...) DOCTEST_FAST_WARN_LT(__VA_ARGS__) #define FAST_CHECK_LT(...) DOCTEST_FAST_CHECK_LT(__VA_ARGS__) #define FAST_REQUIRE_LT(...) DOCTEST_FAST_REQUIRE_LT(__VA_ARGS__) #define FAST_WARN_GE(...) DOCTEST_FAST_WARN_GE(__VA_ARGS__) #define FAST_CHECK_GE(...) DOCTEST_FAST_CHECK_GE(__VA_ARGS__) #define FAST_REQUIRE_GE(...) DOCTEST_FAST_REQUIRE_GE(__VA_ARGS__) #define FAST_WARN_LE(...) DOCTEST_FAST_WARN_LE(__VA_ARGS__) #define FAST_CHECK_LE(...) DOCTEST_FAST_CHECK_LE(__VA_ARGS__) #define FAST_REQUIRE_LE(...) DOCTEST_FAST_REQUIRE_LE(__VA_ARGS__) #define FAST_WARN_UNARY(...) DOCTEST_FAST_WARN_UNARY(__VA_ARGS__) #define FAST_CHECK_UNARY(...) DOCTEST_FAST_CHECK_UNARY(__VA_ARGS__) #define FAST_REQUIRE_UNARY(...) DOCTEST_FAST_REQUIRE_UNARY(__VA_ARGS__) #define FAST_WARN_UNARY_FALSE(...) DOCTEST_FAST_WARN_UNARY_FALSE(__VA_ARGS__) #define FAST_CHECK_UNARY_FALSE(...) DOCTEST_FAST_CHECK_UNARY_FALSE(__VA_ARGS__) #define FAST_REQUIRE_UNARY_FALSE(...) DOCTEST_FAST_REQUIRE_UNARY_FALSE(__VA_ARGS__) #define TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, __VA_ARGS__) #endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES #ifndef DOCTEST_CONFIG_DISABLE // this is here to clear the 'current test suite' for the current translation unit - at the top DOCTEST_TEST_SUITE_END(); #endif // DOCTEST_CONFIG_DISABLE DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP DOCTEST_SUPPRESS_COMMON_WARNINGS_POP #endif // DOCTEST_LIBRARY_INCLUDED #ifndef DOCTEST_SINGLE_HEADER #define DOCTEST_SINGLE_HEADER #endif // DOCTEST_SINGLE_HEADER #if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER) #ifndef DOCTEST_SINGLE_HEADER #include "doctest_fwd.h" #endif // DOCTEST_SINGLE_HEADER DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros") #ifndef DOCTEST_LIBRARY_IMPLEMENTATION #define DOCTEST_LIBRARY_IMPLEMENTATION DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors") DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations") DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch") DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum") DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn") DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers") DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") DOCTEST_CLANG_SUPPRESS_WARNING("-Wnonportable-system-include-path") DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers") DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces") DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch") DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum") DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default") DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations") DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast") DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function") DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance") DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute") DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning) DOCTEST_MSVC_SUPPRESS_WARNING(5245) // unreferenced function with internal linkage has been removed DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN // required includes - will go only in one translation unit! #include #include #include // borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/doctest/doctest/pull/37 #ifdef __BORLANDC__ #include #endif // __BORLANDC__ #include #include #include #include #include #include #include #include #ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM #include #endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM #include #include #include #ifndef DOCTEST_CONFIG_NO_MULTITHREADING #include #include #define DOCTEST_DECLARE_MUTEX(name) std::mutex name; #define DOCTEST_DECLARE_STATIC_MUTEX(name) static DOCTEST_DECLARE_MUTEX(name) #define DOCTEST_LOCK_MUTEX(name) std::lock_guard DOCTEST_ANONYMOUS(DOCTEST_ANON_LOCK_)(name); #else // DOCTEST_CONFIG_NO_MULTITHREADING #define DOCTEST_DECLARE_MUTEX(name) #define DOCTEST_DECLARE_STATIC_MUTEX(name) #define DOCTEST_LOCK_MUTEX(name) #endif // DOCTEST_CONFIG_NO_MULTITHREADING #include #include #include #include #include #include #include #include #include #include #ifdef DOCTEST_PLATFORM_MAC #include #include #include #endif // DOCTEST_PLATFORM_MAC #ifdef DOCTEST_PLATFORM_WINDOWS // defines for a leaner windows.h #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #define DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN #endif // WIN32_LEAN_AND_MEAN #ifndef NOMINMAX #define NOMINMAX #define DOCTEST_UNDEF_NOMINMAX #endif // NOMINMAX // not sure what AfxWin.h is for - here I do what Catch does #ifdef __AFXDLL #include #else #include #endif #include #else // DOCTEST_PLATFORM_WINDOWS #include #include #endif // DOCTEST_PLATFORM_WINDOWS // this is a fix for https://github.com/doctest/doctest/issues/348 // https://mail.gnome.org/archives/xml/2012-January/msg00000.html #if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO) #define STDOUT_FILENO fileno(stdout) #endif // HAVE_UNISTD_H DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END // counts the number of elements in a C array #define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0])) #ifdef DOCTEST_CONFIG_DISABLE #define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled #else // DOCTEST_CONFIG_DISABLE #define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled #endif // DOCTEST_CONFIG_DISABLE #ifndef DOCTEST_CONFIG_OPTIONS_PREFIX #define DOCTEST_CONFIG_OPTIONS_PREFIX "dt-" #endif #ifndef DOCTEST_THREAD_LOCAL #if defined(DOCTEST_CONFIG_NO_MULTITHREADING) || DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) #define DOCTEST_THREAD_LOCAL #else // DOCTEST_MSVC #define DOCTEST_THREAD_LOCAL thread_local #endif // DOCTEST_MSVC #endif // DOCTEST_THREAD_LOCAL #ifndef DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES #define DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES 32 #endif #ifndef DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE #define DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE 64 #endif #ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS #define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX #else #define DOCTEST_OPTIONS_PREFIX_DISPLAY "" #endif #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) #define DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS #endif #ifndef DOCTEST_CDECL #define DOCTEST_CDECL __cdecl #endif namespace doctest { bool is_running_in_test = false; namespace { using namespace detail; template DOCTEST_NORETURN void throw_exception(Ex const& e) { #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS throw e; #else // DOCTEST_CONFIG_NO_EXCEPTIONS #ifdef DOCTEST_CONFIG_HANDLE_EXCEPTION DOCTEST_CONFIG_HANDLE_EXCEPTION(e); #else // DOCTEST_CONFIG_HANDLE_EXCEPTION #ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM std::cerr << "doctest will terminate because it needed to throw an exception.\n" << "The message was: " << e.what() << '\n'; #endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM #endif // DOCTEST_CONFIG_HANDLE_EXCEPTION std::terminate(); #endif // DOCTEST_CONFIG_NO_EXCEPTIONS } #ifndef DOCTEST_INTERNAL_ERROR #define DOCTEST_INTERNAL_ERROR(msg) \ throw_exception(std::logic_error( \ __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) #endif // DOCTEST_INTERNAL_ERROR // case insensitive strcmp int stricmp(const char* a, const char* b) { for(;; a++, b++) { const int d = tolower(*a) - tolower(*b); if(d != 0 || !*a) return d; } } struct Endianness { enum Arch { Big, Little }; static Arch which() { int x = 1; // casting any data pointer to char* is allowed auto ptr = reinterpret_cast(&x); if(*ptr) return Little; return Big; } }; } // namespace namespace detail { DOCTEST_THREAD_LOCAL class { std::vector stack; std::stringstream ss; public: std::ostream* push() { stack.push_back(ss.tellp()); return &ss; } String pop() { if (stack.empty()) DOCTEST_INTERNAL_ERROR("TLSS was empty when trying to pop!"); std::streampos pos = stack.back(); stack.pop_back(); unsigned sz = static_cast(ss.tellp() - pos); ss.rdbuf()->pubseekpos(pos, std::ios::in | std::ios::out); return String(ss, sz); } } g_oss; std::ostream* tlssPush() { return g_oss.push(); } String tlssPop() { return g_oss.pop(); } #ifndef DOCTEST_CONFIG_DISABLE namespace timer_large_integer { #if defined(DOCTEST_PLATFORM_WINDOWS) using type = ULONGLONG; #else // DOCTEST_PLATFORM_WINDOWS using type = std::uint64_t; #endif // DOCTEST_PLATFORM_WINDOWS } using ticks_t = timer_large_integer::type; #ifdef DOCTEST_CONFIG_GETCURRENTTICKS ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); } #elif defined(DOCTEST_PLATFORM_WINDOWS) ticks_t getCurrentTicks() { static LARGE_INTEGER hz = { {0} }, hzo = { {0} }; if(!hz.QuadPart) { QueryPerformanceFrequency(&hz); QueryPerformanceCounter(&hzo); } LARGE_INTEGER t; QueryPerformanceCounter(&t); return ((t.QuadPart - hzo.QuadPart) * LONGLONG(1000000)) / hz.QuadPart; } #else // DOCTEST_PLATFORM_WINDOWS ticks_t getCurrentTicks() { timeval t; gettimeofday(&t, nullptr); return static_cast(t.tv_sec) * 1000000 + static_cast(t.tv_usec); } #endif // DOCTEST_PLATFORM_WINDOWS struct Timer { void start() { m_ticks = getCurrentTicks(); } unsigned int getElapsedMicroseconds() const { return static_cast(getCurrentTicks() - m_ticks); } //unsigned int getElapsedMilliseconds() const { // return static_cast(getElapsedMicroseconds() / 1000); //} double getElapsedSeconds() const { return static_cast(getCurrentTicks() - m_ticks) / 1000000.0; } private: ticks_t m_ticks = 0; }; #ifdef DOCTEST_CONFIG_NO_MULTITHREADING template using Atomic = T; #else // DOCTEST_CONFIG_NO_MULTITHREADING template using Atomic = std::atomic; #endif // DOCTEST_CONFIG_NO_MULTITHREADING #if defined(DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS) || defined(DOCTEST_CONFIG_NO_MULTITHREADING) template using MultiLaneAtomic = Atomic; #else // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS // Provides a multilane implementation of an atomic variable that supports add, sub, load, // store. Instead of using a single atomic variable, this splits up into multiple ones, // each sitting on a separate cache line. The goal is to provide a speedup when most // operations are modifying. It achieves this with two properties: // // * Multiple atomics are used, so chance of congestion from the same atomic is reduced. // * Each atomic sits on a separate cache line, so false sharing is reduced. // // The disadvantage is that there is a small overhead due to the use of TLS, and load/store // is slower because all atomics have to be accessed. template class MultiLaneAtomic { struct CacheLineAlignedAtomic { Atomic atomic{}; char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(Atomic)]; }; CacheLineAlignedAtomic m_atomics[DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES]; static_assert(sizeof(CacheLineAlignedAtomic) == DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE, "guarantee one atomic takes exactly one cache line"); public: T operator++() DOCTEST_NOEXCEPT { return fetch_add(1) + 1; } T operator++(int) DOCTEST_NOEXCEPT { return fetch_add(1); } T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { return myAtomic().fetch_add(arg, order); } T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { return myAtomic().fetch_sub(arg, order); } operator T() const DOCTEST_NOEXCEPT { return load(); } T load(std::memory_order order = std::memory_order_seq_cst) const DOCTEST_NOEXCEPT { auto result = T(); for(auto const& c : m_atomics) { result += c.atomic.load(order); } return result; } T operator=(T desired) DOCTEST_NOEXCEPT { // lgtm [cpp/assignment-does-not-return-this] store(desired); return desired; } void store(T desired, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { // first value becomes desired", all others become 0. for(auto& c : m_atomics) { c.atomic.store(desired, order); desired = {}; } } private: // Each thread has a different atomic that it operates on. If more than NumLanes threads // use this, some will use the same atomic. So performance will degrade a bit, but still // everything will work. // // The logic here is a bit tricky. The call should be as fast as possible, so that there // is minimal to no overhead in determining the correct atomic for the current thread. // // 1. A global static counter laneCounter counts continuously up. // 2. Each successive thread will use modulo operation of that counter so it gets an atomic // assigned in a round-robin fashion. // 3. This tlsLaneIdx is stored in the thread local data, so it is directly available with // little overhead. Atomic& myAtomic() DOCTEST_NOEXCEPT { static Atomic laneCounter; DOCTEST_THREAD_LOCAL size_t tlsLaneIdx = laneCounter++ % DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES; return m_atomics[tlsLaneIdx].atomic; } }; #endif // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS // this holds both parameters from the command line and runtime data for tests struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats { MultiLaneAtomic numAssertsCurrentTest_atomic; MultiLaneAtomic numAssertsFailedCurrentTest_atomic; std::vector> filters = decltype(filters)(9); // 9 different filters std::vector reporters_currently_used; assert_handler ah = nullptr; Timer timer; std::vector stringifiedContexts; // logging from INFO() due to an exception // stuff for subcases bool reachedLeaf; std::vector subcaseStack; std::vector nextSubcaseStack; std::unordered_set fullyTraversedSubcases; size_t currentSubcaseDepth; Atomic shouldLogCurrentException; void resetRunData() { numTestCases = 0; numTestCasesPassingFilters = 0; numTestSuitesPassingFilters = 0; numTestCasesFailed = 0; numAsserts = 0; numAssertsFailed = 0; numAssertsCurrentTest = 0; numAssertsFailedCurrentTest = 0; } void finalizeTestCaseData() { seconds = timer.getElapsedSeconds(); // update the non-atomic counters numAsserts += numAssertsCurrentTest_atomic; numAssertsFailed += numAssertsFailedCurrentTest_atomic; numAssertsCurrentTest = numAssertsCurrentTest_atomic; numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic; if(numAssertsFailedCurrentTest) failure_flags |= TestCaseFailureReason::AssertFailure; if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 && Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout) failure_flags |= TestCaseFailureReason::Timeout; if(currentTest->m_should_fail) { if(failure_flags) { failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid; } else { failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt; } } else if(failure_flags && currentTest->m_may_fail) { failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid; } else if(currentTest->m_expected_failures > 0) { if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) { failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes; } else { failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes; } } bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) || (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) || (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags); // if any subcase has failed - the whole test case has failed testCaseSuccess = !(failure_flags && !ok_to_fail); if(!testCaseSuccess) numTestCasesFailed++; } }; ContextState* g_cs = nullptr; // used to avoid locks for the debug output // TODO: figure out if this is indeed necessary/correct - seems like either there still // could be a race or that there wouldn't be a race even if using the context directly DOCTEST_THREAD_LOCAL bool g_no_colors; #endif // DOCTEST_CONFIG_DISABLE } // namespace detail char* String::allocate(size_type sz) { if (sz <= last) { buf[sz] = '\0'; setLast(last - sz); return buf; } else { setOnHeap(); data.size = sz; data.capacity = data.size + 1; data.ptr = new char[data.capacity]; data.ptr[sz] = '\0'; return data.ptr; } } void String::setOnHeap() noexcept { *reinterpret_cast(&buf[last]) = 128; } void String::setLast(size_type in) noexcept { buf[last] = char(in); } void String::setSize(size_type sz) noexcept { if (isOnStack()) { buf[sz] = '\0'; setLast(last - sz); } else { data.ptr[sz] = '\0'; data.size = sz; } } void String::copy(const String& other) { if(other.isOnStack()) { memcpy(buf, other.buf, len); } else { memcpy(allocate(other.data.size), other.data.ptr, other.data.size); } } String::String() noexcept { buf[0] = '\0'; setLast(); } String::~String() { if(!isOnStack()) delete[] data.ptr; } // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) String::String(const char* in) : String(in, strlen(in)) {} String::String(const char* in, size_type in_size) { memcpy(allocate(in_size), in, in_size); } String::String(std::istream& in, size_type in_size) { in.read(allocate(in_size), in_size); } String::String(const String& other) { copy(other); } String& String::operator=(const String& other) { if(this != &other) { if(!isOnStack()) delete[] data.ptr; copy(other); } return *this; } String& String::operator+=(const String& other) { const size_type my_old_size = size(); const size_type other_size = other.size(); const size_type total_size = my_old_size + other_size; if(isOnStack()) { if(total_size < len) { // append to the current stack space memcpy(buf + my_old_size, other.c_str(), other_size + 1); // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) setLast(last - total_size); } else { // alloc new chunk char* temp = new char[total_size + 1]; // copy current data to new location before writing in the union memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed // update data in union setOnHeap(); data.size = total_size; data.capacity = data.size + 1; data.ptr = temp; // transfer the rest of the data memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); } } else { if(data.capacity > total_size) { // append to the current heap block data.size = total_size; memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); } else { // resize data.capacity *= 2; if(data.capacity <= total_size) data.capacity = total_size + 1; // alloc new chunk char* temp = new char[data.capacity]; // copy current data to new location before releasing it memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed // release old chunk delete[] data.ptr; // update the rest of the union members data.size = total_size; data.ptr = temp; // transfer the rest of the data memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); } } return *this; } String::String(String&& other) noexcept { memcpy(buf, other.buf, len); other.buf[0] = '\0'; other.setLast(); } String& String::operator=(String&& other) noexcept { if(this != &other) { if(!isOnStack()) delete[] data.ptr; memcpy(buf, other.buf, len); other.buf[0] = '\0'; other.setLast(); } return *this; } char String::operator[](size_type i) const { return const_cast(this)->operator[](i); } char& String::operator[](size_type i) { if(isOnStack()) return reinterpret_cast(buf)[i]; return data.ptr[i]; } DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized") String::size_type String::size() const { if(isOnStack()) return last - (size_type(buf[last]) & 31); // using "last" would work only if "len" is 32 return data.size; } DOCTEST_GCC_SUPPRESS_WARNING_POP String::size_type String::capacity() const { if(isOnStack()) return len; return data.capacity; } String String::substr(size_type pos, size_type cnt) && { cnt = std::min(cnt, size() - 1 - pos); char* cptr = c_str(); memmove(cptr, cptr + pos, cnt); setSize(cnt); return std::move(*this); } String String::substr(size_type pos, size_type cnt) const & { cnt = std::min(cnt, size() - 1 - pos); return String{ c_str() + pos, cnt }; } String::size_type String::find(char ch, size_type pos) const { const char* begin = c_str(); const char* end = begin + size(); const char* it = begin + pos; for (; it < end && *it != ch; it++); if (it < end) { return static_cast(it - begin); } else { return npos; } } String::size_type String::rfind(char ch, size_type pos) const { const char* begin = c_str(); const char* it = begin + std::min(pos, size() - 1); for (; it >= begin && *it != ch; it--); if (it >= begin) { return static_cast(it - begin); } else { return npos; } } int String::compare(const char* other, bool no_case) const { if(no_case) return doctest::stricmp(c_str(), other); return std::strcmp(c_str(), other); } int String::compare(const String& other, bool no_case) const { return compare(other.c_str(), no_case); } String operator+(const String& lhs, const String& rhs) { return String(lhs) += rhs; } bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; } bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; } bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; } bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; } bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; } std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); } Contains::Contains(const String& str) : string(str) { } bool Contains::checkWith(const String& other) const { return strstr(other.c_str(), string.c_str()) != nullptr; } String toString(const Contains& in) { return "Contains( " + in.string + " )"; } bool operator==(const String& lhs, const Contains& rhs) { return rhs.checkWith(lhs); } bool operator==(const Contains& lhs, const String& rhs) { return lhs.checkWith(rhs); } bool operator!=(const String& lhs, const Contains& rhs) { return !rhs.checkWith(lhs); } bool operator!=(const Contains& lhs, const String& rhs) { return !lhs.checkWith(rhs); } namespace { void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;) } // namespace namespace Color { std::ostream& operator<<(std::ostream& s, Color::Enum code) { color_to_stream(s, code); return s; } } // namespace Color // clang-format off const char* assertString(assertType::Enum at) { DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4061) // enum 'x' in switch of enum 'y' is not explicitly handled #define DOCTEST_GENERATE_ASSERT_TYPE_CASE(assert_type) case assertType::DT_ ## assert_type: return #assert_type #define DOCTEST_GENERATE_ASSERT_TYPE_CASES(assert_type) \ DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN_ ## assert_type); \ DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK_ ## assert_type); \ DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE_ ## assert_type) switch(at) { DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN); DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK); DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE); DOCTEST_GENERATE_ASSERT_TYPE_CASES(FALSE); DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS); DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_AS); DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH); DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH_AS); DOCTEST_GENERATE_ASSERT_TYPE_CASES(NOTHROW); DOCTEST_GENERATE_ASSERT_TYPE_CASES(EQ); DOCTEST_GENERATE_ASSERT_TYPE_CASES(NE); DOCTEST_GENERATE_ASSERT_TYPE_CASES(GT); DOCTEST_GENERATE_ASSERT_TYPE_CASES(LT); DOCTEST_GENERATE_ASSERT_TYPE_CASES(GE); DOCTEST_GENERATE_ASSERT_TYPE_CASES(LE); DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY); DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY_FALSE); default: DOCTEST_INTERNAL_ERROR("Tried stringifying invalid assert type!"); } DOCTEST_MSVC_SUPPRESS_WARNING_POP } // clang-format on const char* failureString(assertType::Enum at) { if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional return "WARNING"; if(at & assertType::is_check) //!OCLINT bitwise operator in conditional return "ERROR"; if(at & assertType::is_require) //!OCLINT bitwise operator in conditional return "FATAL ERROR"; return ""; } DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") // depending on the current options this will remove the path of filenames const char* skipPathFromFilename(const char* file) { #ifndef DOCTEST_CONFIG_DISABLE if(getContextOptions()->no_path_in_filenames) { auto back = std::strrchr(file, '\\'); auto forward = std::strrchr(file, '/'); if(back || forward) { if(back > forward) forward = back; return forward + 1; } } #endif // DOCTEST_CONFIG_DISABLE return file; } DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP bool SubcaseSignature::operator==(const SubcaseSignature& other) const { return m_line == other.m_line && std::strcmp(m_file, other.m_file) == 0 && m_name == other.m_name; } bool SubcaseSignature::operator<(const SubcaseSignature& other) const { if(m_line != other.m_line) return m_line < other.m_line; if(std::strcmp(m_file, other.m_file) != 0) return std::strcmp(m_file, other.m_file) < 0; return m_name.compare(other.m_name) < 0; } DOCTEST_DEFINE_INTERFACE(IContextScope) namespace detail { void filldata::fill(std::ostream* stream, const void* in) { if (in) { *stream << in; } else { *stream << "nullptr"; } } template String toStreamLit(T t) { std::ostream* os = tlssPush(); os->operator<<(t); return tlssPop(); } } #ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } #endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING #if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) // see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 String toString(const std::string& in) { return in.c_str(); } #endif // VS 2019 String toString(String in) { return in; } String toString(std::nullptr_t) { return "nullptr"; } String toString(bool in) { return in ? "true" : "false"; } String toString(float in) { return toStreamLit(in); } String toString(double in) { return toStreamLit(in); } String toString(double long in) { return toStreamLit(in); } String toString(char in) { return toStreamLit(static_cast(in)); } String toString(char signed in) { return toStreamLit(static_cast(in)); } String toString(char unsigned in) { return toStreamLit(static_cast(in)); } String toString(short in) { return toStreamLit(in); } String toString(short unsigned in) { return toStreamLit(in); } String toString(signed in) { return toStreamLit(in); } String toString(unsigned in) { return toStreamLit(in); } String toString(long in) { return toStreamLit(in); } String toString(long unsigned in) { return toStreamLit(in); } String toString(long long in) { return toStreamLit(in); } String toString(long long unsigned in) { return toStreamLit(in); } Approx::Approx(double value) : m_epsilon(static_cast(std::numeric_limits::epsilon()) * 100) , m_scale(1.0) , m_value(value) {} Approx Approx::operator()(double value) const { Approx approx(value); approx.epsilon(m_epsilon); approx.scale(m_scale); return approx; } Approx& Approx::epsilon(double newEpsilon) { m_epsilon = newEpsilon; return *this; } Approx& Approx::scale(double newScale) { m_scale = newScale; return *this; } bool operator==(double lhs, const Approx& rhs) { // Thanks to Richard Harris for his help refining this formula return std::fabs(lhs - rhs.m_value) < rhs.m_epsilon * (rhs.m_scale + std::max(std::fabs(lhs), std::fabs(rhs.m_value))); } bool operator==(const Approx& lhs, double rhs) { return operator==(rhs, lhs); } bool operator!=(double lhs, const Approx& rhs) { return !operator==(lhs, rhs); } bool operator!=(const Approx& lhs, double rhs) { return !operator==(rhs, lhs); } bool operator<=(double lhs, const Approx& rhs) { return lhs < rhs.m_value || lhs == rhs; } bool operator<=(const Approx& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; } bool operator>=(double lhs, const Approx& rhs) { return lhs > rhs.m_value || lhs == rhs; } bool operator>=(const Approx& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; } bool operator<(double lhs, const Approx& rhs) { return lhs < rhs.m_value && lhs != rhs; } bool operator<(const Approx& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; } bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs != rhs; } bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } String toString(const Approx& in) { return "Approx( " + doctest::toString(in.m_value) + " )"; } const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); } DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4738) template IsNaN::operator bool() const { return std::isnan(value) ^ flipped; } DOCTEST_MSVC_SUPPRESS_WARNING_POP template struct DOCTEST_INTERFACE_DEF IsNaN; template struct DOCTEST_INTERFACE_DEF IsNaN; template struct DOCTEST_INTERFACE_DEF IsNaN; template String toString(IsNaN in) { return String(in.flipped ? "! " : "") + "IsNaN( " + doctest::toString(in.value) + " )"; } String toString(IsNaN in) { return toString(in); } String toString(IsNaN in) { return toString(in); } String toString(IsNaN in) { return toString(in); } } // namespace doctest #ifdef DOCTEST_CONFIG_DISABLE namespace doctest { Context::Context(int, const char* const*) {} Context::~Context() = default; void Context::applyCommandLine(int, const char* const*) {} void Context::addFilter(const char*, const char*) {} void Context::clearFilters() {} void Context::setOption(const char*, bool) {} void Context::setOption(const char*, int) {} void Context::setOption(const char*, const char*) {} bool Context::shouldExit() { return false; } void Context::setAsDefaultForAssertsOutOfTestCases() {} void Context::setAssertHandler(detail::assert_handler) {} void Context::setCout(std::ostream*) {} int Context::run() { return 0; } int IReporter::get_num_active_contexts() { return 0; } const IContextScope* const* IReporter::get_active_contexts() { return nullptr; } int IReporter::get_num_stringified_contexts() { return 0; } const String* IReporter::get_stringified_contexts() { return nullptr; } int registerReporter(const char*, int, IReporter*) { return 0; } } // namespace doctest #else // DOCTEST_CONFIG_DISABLE #if !defined(DOCTEST_CONFIG_COLORS_NONE) #if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI) #ifdef DOCTEST_PLATFORM_WINDOWS #define DOCTEST_CONFIG_COLORS_WINDOWS #else // linux #define DOCTEST_CONFIG_COLORS_ANSI #endif // platform #endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI #endif // DOCTEST_CONFIG_COLORS_NONE namespace doctest_detail_test_suite_ns { // holds the current test suite doctest::detail::TestSuite& getCurrentTestSuite() { static doctest::detail::TestSuite data{}; return data; } } // namespace doctest_detail_test_suite_ns namespace doctest { namespace { // the int (priority) is part of the key for automatic sorting - sadly one can register a // reporter with a duplicate name and a different priority but hopefully that won't happen often :| using reporterMap = std::map, reporterCreatorFunc>; reporterMap& getReporters() { static reporterMap data; return data; } reporterMap& getListeners() { static reporterMap data; return data; } } // namespace namespace detail { #define DOCTEST_ITERATE_THROUGH_REPORTERS(function, ...) \ for(auto& curr_rep : g_cs->reporters_currently_used) \ curr_rep->function(__VA_ARGS__) bool checkIfShouldThrow(assertType::Enum at) { if(at & assertType::is_require) //!OCLINT bitwise operator in conditional return true; if((at & assertType::is_check) //!OCLINT bitwise operator in conditional && getContextOptions()->abort_after > 0 && (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >= getContextOptions()->abort_after) return true; return false; } #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS DOCTEST_NORETURN void throwException() { g_cs->shouldLogCurrentException = false; throw TestFailureException(); // NOLINT(hicpp-exception-baseclass) } #else // DOCTEST_CONFIG_NO_EXCEPTIONS void throwException() {} #endif // DOCTEST_CONFIG_NO_EXCEPTIONS } // namespace detail namespace { using namespace detail; // matching of a string against a wildcard mask (case sensitivity configurable) taken from // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing int wildcmp(const char* str, const char* wild, bool caseSensitive) { const char* cp = str; const char* mp = wild; while((*str) && (*wild != '*')) { if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) && (*wild != '?')) { return 0; } wild++; str++; } while(*str) { if(*wild == '*') { if(!*++wild) { return 1; } mp = wild; cp = str + 1; } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) || (*wild == '?')) { wild++; str++; } else { wild = mp; //!OCLINT parameter reassignment str = cp++; //!OCLINT parameter reassignment } } while(*wild == '*') { wild++; } return !*wild; } // checks if the name matches any of the filters (and can be configured what to do when empty) bool matchesAny(const char* name, const std::vector& filters, bool matchEmpty, bool caseSensitive) { if (filters.empty() && matchEmpty) return true; for (auto& curr : filters) if (wildcmp(name, curr.c_str(), caseSensitive)) return true; return false; } DOCTEST_NO_SANITIZE_INTEGER unsigned long long hash(unsigned long long a, unsigned long long b) { return (a << 5) + b; } // C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html DOCTEST_NO_SANITIZE_INTEGER unsigned long long hash(const char* str) { unsigned long long hash = 5381; char c; while ((c = *str++)) hash = ((hash << 5) + hash) + c; // hash * 33 + c return hash; } unsigned long long hash(const SubcaseSignature& sig) { return hash(hash(hash(sig.m_file), hash(sig.m_name.c_str())), sig.m_line); } unsigned long long hash(const std::vector& sigs, size_t count) { unsigned long long running = 0; auto end = sigs.begin() + count; for (auto it = sigs.begin(); it != end; it++) { running = hash(running, hash(*it)); } return running; } unsigned long long hash(const std::vector& sigs) { unsigned long long running = 0; for (const SubcaseSignature& sig : sigs) { running = hash(running, hash(sig)); } return running; } } // namespace namespace detail { bool Subcase::checkFilters() { if (g_cs->subcaseStack.size() < size_t(g_cs->subcase_filter_levels)) { if (!matchesAny(m_signature.m_name.c_str(), g_cs->filters[6], true, g_cs->case_sensitive)) return true; if (matchesAny(m_signature.m_name.c_str(), g_cs->filters[7], false, g_cs->case_sensitive)) return true; } return false; } Subcase::Subcase(const String& name, const char* file, int line) : m_signature({name, file, line}) { if (!g_cs->reachedLeaf) { if (g_cs->nextSubcaseStack.size() <= g_cs->subcaseStack.size() || g_cs->nextSubcaseStack[g_cs->subcaseStack.size()] == m_signature) { // Going down. if (checkFilters()) { return; } g_cs->subcaseStack.push_back(m_signature); g_cs->currentSubcaseDepth++; m_entered = true; DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); } } else { if (g_cs->subcaseStack[g_cs->currentSubcaseDepth] == m_signature) { // This subcase is reentered via control flow. g_cs->currentSubcaseDepth++; m_entered = true; DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); } else if (g_cs->nextSubcaseStack.size() <= g_cs->currentSubcaseDepth && g_cs->fullyTraversedSubcases.find(hash(hash(g_cs->subcaseStack, g_cs->currentSubcaseDepth), hash(m_signature))) == g_cs->fullyTraversedSubcases.end()) { if (checkFilters()) { return; } // This subcase is part of the one to be executed next. g_cs->nextSubcaseStack.clear(); g_cs->nextSubcaseStack.insert(g_cs->nextSubcaseStack.end(), g_cs->subcaseStack.begin(), g_cs->subcaseStack.begin() + g_cs->currentSubcaseDepth); g_cs->nextSubcaseStack.push_back(m_signature); } } } DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") Subcase::~Subcase() { if (m_entered) { g_cs->currentSubcaseDepth--; if (!g_cs->reachedLeaf) { // Leaf. g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack)); g_cs->nextSubcaseStack.clear(); g_cs->reachedLeaf = true; } else if (g_cs->nextSubcaseStack.empty()) { // All children are finished. g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack)); } #if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) if(std::uncaught_exceptions() > 0 #else if(std::uncaught_exception() #endif && g_cs->shouldLogCurrentException) { DOCTEST_ITERATE_THROUGH_REPORTERS( test_case_exception, {"exception thrown in subcase - will translate later " "when the whole test case has been exited (cannot " "translate while there is an active exception)", false}); g_cs->shouldLogCurrentException = false; } DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); } } DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP Subcase::operator bool() const { return m_entered; } Result::Result(bool passed, const String& decomposition) : m_passed(passed) , m_decomp(decomposition) {} ExpressionDecomposer::ExpressionDecomposer(assertType::Enum at) : m_at(at) {} TestSuite& TestSuite::operator*(const char* in) { m_test_suite = in; return *this; } TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, const String& type, int template_id) { m_file = file; m_line = line; m_name = nullptr; // will be later overridden in operator* m_test_suite = test_suite.m_test_suite; m_description = test_suite.m_description; m_skip = test_suite.m_skip; m_no_breaks = test_suite.m_no_breaks; m_no_output = test_suite.m_no_output; m_may_fail = test_suite.m_may_fail; m_should_fail = test_suite.m_should_fail; m_expected_failures = test_suite.m_expected_failures; m_timeout = test_suite.m_timeout; m_test = test; m_type = type; m_template_id = template_id; } TestCase::TestCase(const TestCase& other) : TestCaseData() { *this = other; } DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function TestCase& TestCase::operator=(const TestCase& other) { TestCaseData::operator=(other); m_test = other.m_test; m_type = other.m_type; m_template_id = other.m_template_id; m_full_name = other.m_full_name; if(m_template_id != -1) m_name = m_full_name.c_str(); return *this; } DOCTEST_MSVC_SUPPRESS_WARNING_POP TestCase& TestCase::operator*(const char* in) { m_name = in; // make a new name with an appended type for templated test case if(m_template_id != -1) { m_full_name = String(m_name) + "<" + m_type + ">"; // redirect the name to point to the newly constructed full name m_name = m_full_name.c_str(); } return *this; } bool TestCase::operator<(const TestCase& other) const { // this will be used only to differentiate between test cases - not relevant for sorting if(m_line != other.m_line) return m_line < other.m_line; const int name_cmp = strcmp(m_name, other.m_name); if(name_cmp != 0) return name_cmp < 0; const int file_cmp = m_file.compare(other.m_file); if(file_cmp != 0) return file_cmp < 0; return m_template_id < other.m_template_id; } // all the registered tests std::set& getRegisteredTests() { static std::set data; return data; } } // namespace detail namespace { using namespace detail; // for sorting tests by file/line bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) { // this is needed because MSVC gives different case for drive letters // for __FILE__ when evaluated in a header and a source file const int res = lhs->m_file.compare(rhs->m_file, bool(DOCTEST_MSVC)); if(res != 0) return res < 0; if(lhs->m_line != rhs->m_line) return lhs->m_line < rhs->m_line; return lhs->m_template_id < rhs->m_template_id; } // for sorting tests by suite/file/line bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) { const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite); if(res != 0) return res < 0; return fileOrderComparator(lhs, rhs); } // for sorting tests by name/suite/file/line bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) { const int res = std::strcmp(lhs->m_name, rhs->m_name); if(res != 0) return res < 0; return suiteOrderComparator(lhs, rhs); } DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") void color_to_stream(std::ostream& s, Color::Enum code) { static_cast(s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS static_cast(code); // for DOCTEST_CONFIG_COLORS_NONE #ifdef DOCTEST_CONFIG_COLORS_ANSI if(g_no_colors || (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false)) return; auto col = ""; // clang-format off switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement case Color::Red: col = "[0;31m"; break; case Color::Green: col = "[0;32m"; break; case Color::Blue: col = "[0;34m"; break; case Color::Cyan: col = "[0;36m"; break; case Color::Yellow: col = "[0;33m"; break; case Color::Grey: col = "[1;30m"; break; case Color::LightGrey: col = "[0;37m"; break; case Color::BrightRed: col = "[1;31m"; break; case Color::BrightGreen: col = "[1;32m"; break; case Color::BrightWhite: col = "[1;37m"; break; case Color::Bright: // invalid case Color::None: case Color::White: default: col = "[0m"; } // clang-format on s << "\033" << col; #endif // DOCTEST_CONFIG_COLORS_ANSI #ifdef DOCTEST_CONFIG_COLORS_WINDOWS if(g_no_colors || (_isatty(_fileno(stdout)) == false && getContextOptions()->force_colors == false)) return; static struct ConsoleHelper { HANDLE stdoutHandle; WORD origFgAttrs; WORD origBgAttrs; ConsoleHelper() { stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO csbiInfo; GetConsoleScreenBufferInfo(stdoutHandle, &csbiInfo); origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY); origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY); } } ch; #define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(ch.stdoutHandle, x | ch.origBgAttrs) // clang-format off switch (code) { case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break; case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break; case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break; case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break; case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break; case Color::Grey: DOCTEST_SET_ATTR(0); break; case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break; case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break; case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break; case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; case Color::None: case Color::Bright: // invalid default: DOCTEST_SET_ATTR(ch.origFgAttrs); } // clang-format on #endif // DOCTEST_CONFIG_COLORS_WINDOWS } DOCTEST_CLANG_SUPPRESS_WARNING_POP std::vector& getExceptionTranslators() { static std::vector data; return data; } String translateActiveException() { #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS String res; auto& translators = getExceptionTranslators(); for(auto& curr : translators) if(curr->translate(res)) return res; // clang-format off DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcatch-value") try { throw; } catch(std::exception& ex) { return ex.what(); } catch(std::string& msg) { return msg.c_str(); } catch(const char* msg) { return msg; } catch(...) { return "unknown exception"; } DOCTEST_GCC_SUPPRESS_WARNING_POP // clang-format on #else // DOCTEST_CONFIG_NO_EXCEPTIONS return ""; #endif // DOCTEST_CONFIG_NO_EXCEPTIONS } } // namespace namespace detail { // used by the macros for registering tests int regTest(const TestCase& tc) { getRegisteredTests().insert(tc); return 0; } // sets the current test suite int setTestSuite(const TestSuite& ts) { doctest_detail_test_suite_ns::getCurrentTestSuite() = ts; return 0; } #ifdef DOCTEST_IS_DEBUGGER_ACTIVE bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); } #else // DOCTEST_IS_DEBUGGER_ACTIVE #ifdef DOCTEST_PLATFORM_LINUX class ErrnoGuard { public: ErrnoGuard() : m_oldErrno(errno) {} ~ErrnoGuard() { errno = m_oldErrno; } private: int m_oldErrno; }; // See the comments in Catch2 for the reasoning behind this implementation: // https://github.com/catchorg/Catch2/blob/v2.13.1/include/internal/catch_debugger.cpp#L79-L102 bool isDebuggerActive() { ErrnoGuard guard; std::ifstream in("/proc/self/status"); for(std::string line; std::getline(in, line);) { static const int PREFIX_LEN = 11; if(line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0) { return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; } } return false; } #elif defined(DOCTEST_PLATFORM_MAC) // The following function is taken directly from the following technical note: // https://developer.apple.com/library/archive/qa/qa1361/_index.html // Returns true if the current process is being debugged (either // running under the debugger or has a debugger attached post facto). bool isDebuggerActive() { int mib[4]; kinfo_proc info; size_t size; // Initialize the flags so that, if sysctl fails for some bizarre // reason, we get a predictable result. info.kp_proc.p_flag = 0; // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid(); // Call sysctl. size = sizeof(info); if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) { std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n"; return false; } // We're being debugged if the P_TRACED flag is set. return ((info.kp_proc.p_flag & P_TRACED) != 0); } #elif DOCTEST_MSVC || defined(__MINGW32__) || defined(__MINGW64__) bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } #else bool isDebuggerActive() { return false; } #endif // Platform #endif // DOCTEST_IS_DEBUGGER_ACTIVE void registerExceptionTranslatorImpl(const IExceptionTranslator* et) { if(std::find(getExceptionTranslators().begin(), getExceptionTranslators().end(), et) == getExceptionTranslators().end()) getExceptionTranslators().push_back(et); } DOCTEST_THREAD_LOCAL std::vector g_infoContexts; // for logging with INFO() ContextScopeBase::ContextScopeBase() { g_infoContexts.push_back(this); } ContextScopeBase::ContextScopeBase(ContextScopeBase&& other) noexcept { if (other.need_to_destroy) { other.destroy(); } other.need_to_destroy = false; g_infoContexts.push_back(this); } DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") // destroy cannot be inlined into the destructor because that would mean calling stringify after // ContextScope has been destroyed (base class destructors run after derived class destructors). // Instead, ContextScope calls this method directly from its destructor. void ContextScopeBase::destroy() { #if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) if(std::uncaught_exceptions() > 0) { #else if(std::uncaught_exception()) { #endif std::ostringstream s; this->stringify(&s); g_cs->stringifiedContexts.push_back(s.str().c_str()); } g_infoContexts.pop_back(); } DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP } // namespace detail namespace { using namespace detail; #if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH) struct FatalConditionHandler { static void reset() {} static void allocateAltStackMem() {} static void freeAltStackMem() {} }; #else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH void reportFatal(const std::string&); #ifdef DOCTEST_PLATFORM_WINDOWS struct SignalDefs { DWORD id; const char* name; }; // There is no 1-1 mapping between signals and windows exceptions. // Windows can easily distinguish between SO and SigSegV, // but SigInt, SigTerm, etc are handled differently. SignalDefs signalDefs[] = { {static_cast(EXCEPTION_ILLEGAL_INSTRUCTION), "SIGILL - Illegal instruction signal"}, {static_cast(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow"}, {static_cast(EXCEPTION_ACCESS_VIOLATION), "SIGSEGV - Segmentation violation signal"}, {static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error"}, }; struct FatalConditionHandler { static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) { // Multiple threads may enter this filter/handler at once. We want the error message to be printed on the // console just once no matter how many threads have crashed. DOCTEST_DECLARE_STATIC_MUTEX(mutex) static bool execute = true; { DOCTEST_LOCK_MUTEX(mutex) if(execute) { bool reported = false; for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { reportFatal(signalDefs[i].name); reported = true; break; } } if(reported == false) reportFatal("Unhandled SEH exception caught"); if(isDebuggerActive() && !g_cs->no_breaks) DOCTEST_BREAK_INTO_DEBUGGER(); } execute = false; } std::exit(EXIT_FAILURE); } static void allocateAltStackMem() {} static void freeAltStackMem() {} FatalConditionHandler() { isSet = true; // 32k seems enough for doctest to handle stack overflow, // but the value was found experimentally, so there is no strong guarantee guaranteeSize = 32 * 1024; // Register an unhandled exception filter previousTop = SetUnhandledExceptionFilter(handleException); // Pass in guarantee size to be filled SetThreadStackGuarantee(&guaranteeSize); // On Windows uncaught exceptions from another thread, exceptions from // destructors, or calls to std::terminate are not a SEH exception // The terminal handler gets called when: // - std::terminate is called FROM THE TEST RUNNER THREAD // - an exception is thrown from a destructor FROM THE TEST RUNNER THREAD original_terminate_handler = std::get_terminate(); std::set_terminate([]() DOCTEST_NOEXCEPT { reportFatal("Terminate handler called"); if(isDebuggerActive() && !g_cs->no_breaks) DOCTEST_BREAK_INTO_DEBUGGER(); std::exit(EXIT_FAILURE); // explicitly exit - otherwise the SIGABRT handler may be called as well }); // SIGABRT is raised when: // - std::terminate is called FROM A DIFFERENT THREAD // - an exception is thrown from a destructor FROM A DIFFERENT THREAD // - an uncaught exception is thrown FROM A DIFFERENT THREAD prev_sigabrt_handler = std::signal(SIGABRT, [](int signal) DOCTEST_NOEXCEPT { if(signal == SIGABRT) { reportFatal("SIGABRT - Abort (abnormal termination) signal"); if(isDebuggerActive() && !g_cs->no_breaks) DOCTEST_BREAK_INTO_DEBUGGER(); std::exit(EXIT_FAILURE); } }); // The following settings are taken from google test, and more // specifically from UnitTest::Run() inside of gtest.cc // the user does not want to see pop-up dialogs about crashes prev_error_mode_1 = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); // This forces the abort message to go to stderr in all circumstances. prev_error_mode_2 = _set_error_mode(_OUT_TO_STDERR); // In the debug version, Visual Studio pops up a separate dialog // offering a choice to debug the aborted program - we want to disable that. prev_abort_behavior = _set_abort_behavior(0x0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); // In debug mode, the Windows CRT can crash with an assertion over invalid // input (e.g. passing an invalid file descriptor). The default handling // for these assertions is to pop up a dialog and wait for user input. // Instead ask the CRT to dump such assertions to stderr non-interactively. prev_report_mode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); prev_report_file = _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); } static void reset() { if(isSet) { // Unregister handler and restore the old guarantee SetUnhandledExceptionFilter(previousTop); SetThreadStackGuarantee(&guaranteeSize); std::set_terminate(original_terminate_handler); std::signal(SIGABRT, prev_sigabrt_handler); SetErrorMode(prev_error_mode_1); _set_error_mode(prev_error_mode_2); _set_abort_behavior(prev_abort_behavior, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); static_cast(_CrtSetReportMode(_CRT_ASSERT, prev_report_mode)); static_cast(_CrtSetReportFile(_CRT_ASSERT, prev_report_file)); isSet = false; } } ~FatalConditionHandler() { reset(); } private: static UINT prev_error_mode_1; static int prev_error_mode_2; static unsigned int prev_abort_behavior; static int prev_report_mode; static _HFILE prev_report_file; static void (DOCTEST_CDECL *prev_sigabrt_handler)(int); static std::terminate_handler original_terminate_handler; static bool isSet; static ULONG guaranteeSize; static LPTOP_LEVEL_EXCEPTION_FILTER previousTop; }; UINT FatalConditionHandler::prev_error_mode_1; int FatalConditionHandler::prev_error_mode_2; unsigned int FatalConditionHandler::prev_abort_behavior; int FatalConditionHandler::prev_report_mode; _HFILE FatalConditionHandler::prev_report_file; void (DOCTEST_CDECL *FatalConditionHandler::prev_sigabrt_handler)(int); std::terminate_handler FatalConditionHandler::original_terminate_handler; bool FatalConditionHandler::isSet = false; ULONG FatalConditionHandler::guaranteeSize = 0; LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr; #else // DOCTEST_PLATFORM_WINDOWS struct SignalDefs { int id; const char* name; }; SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"}, {SIGILL, "SIGILL - Illegal instruction signal"}, {SIGFPE, "SIGFPE - Floating point error signal"}, {SIGSEGV, "SIGSEGV - Segmentation violation signal"}, {SIGTERM, "SIGTERM - Termination request signal"}, {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}}; struct FatalConditionHandler { static bool isSet; static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)]; static stack_t oldSigStack; static size_t altStackSize; static char* altStackMem; static void handleSignal(int sig) { const char* name = ""; for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { SignalDefs& def = signalDefs[i]; if(sig == def.id) { name = def.name; break; } } reset(); reportFatal(name); raise(sig); } static void allocateAltStackMem() { altStackMem = new char[altStackSize]; } static void freeAltStackMem() { delete[] altStackMem; } FatalConditionHandler() { isSet = true; stack_t sigStack; sigStack.ss_sp = altStackMem; sigStack.ss_size = altStackSize; sigStack.ss_flags = 0; sigaltstack(&sigStack, &oldSigStack); struct sigaction sa = {}; sa.sa_handler = handleSignal; sa.sa_flags = SA_ONSTACK; for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); } } ~FatalConditionHandler() { reset(); } static void reset() { if(isSet) { // Set signals back to previous values -- hopefully nobody overwrote them in the meantime for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); } // Return the old stack sigaltstack(&oldSigStack, nullptr); isSet = false; } } }; bool FatalConditionHandler::isSet = false; struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {}; stack_t FatalConditionHandler::oldSigStack = {}; size_t FatalConditionHandler::altStackSize = 4 * SIGSTKSZ; char* FatalConditionHandler::altStackMem = nullptr; #endif // DOCTEST_PLATFORM_WINDOWS #endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH } // namespace namespace { using namespace detail; #ifdef DOCTEST_PLATFORM_WINDOWS #define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text) #else // TODO: integration with XCode and other IDEs #define DOCTEST_OUTPUT_DEBUG_STRING(text) #endif // Platform void addAssert(assertType::Enum at) { if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional g_cs->numAssertsCurrentTest_atomic++; } void addFailedAssert(assertType::Enum at) { if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional g_cs->numAssertsFailedCurrentTest_atomic++; } #if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH) void reportFatal(const std::string& message) { g_cs->failure_flags |= TestCaseFailureReason::Crash; DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true}); while (g_cs->subcaseStack.size()) { g_cs->subcaseStack.pop_back(); DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); } g_cs->finalizeTestCaseData(); DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); } #endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH } // namespace AssertData::AssertData(assertType::Enum at, const char* file, int line, const char* expr, const char* exception_type, const StringContains& exception_string) : m_test_case(g_cs->currentTest), m_at(at), m_file(file), m_line(line), m_expr(expr), m_failed(true), m_threw(false), m_threw_as(false), m_exception_type(exception_type), m_exception_string(exception_string) { #if DOCTEST_MSVC if (m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC ++m_expr; #endif // MSVC } namespace detail { ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, const char* exception_type, const String& exception_string) : AssertData(at, file, line, expr, exception_type, exception_string) { } ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, const char* exception_type, const Contains& exception_string) : AssertData(at, file, line, expr, exception_type, exception_string) { } void ResultBuilder::setResult(const Result& res) { m_decomp = res.m_decomp; m_failed = !res.m_passed; } void ResultBuilder::translateException() { m_threw = true; m_exception = translateActiveException(); } bool ResultBuilder::log() { if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional m_failed = !m_threw; } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT m_failed = !m_threw_as || !m_exception_string.check(m_exception); } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional m_failed = !m_threw_as; } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional m_failed = !m_exception_string.check(m_exception); } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional m_failed = m_threw; } if(m_exception.size()) m_exception = "\"" + m_exception + "\""; if(is_running_in_test) { addAssert(m_at); DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this); if(m_failed) addFailedAssert(m_at); } else if(m_failed) { failed_out_of_a_testing_context(*this); } return m_failed && isDebuggerActive() && !getContextOptions()->no_breaks && (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger } void ResultBuilder::react() const { if(m_failed && checkIfShouldThrow(m_at)) throwException(); } void failed_out_of_a_testing_context(const AssertData& ad) { if(g_cs->ah) g_cs->ah(ad); else std::abort(); } bool decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, const Result& result) { bool failed = !result.m_passed; // ################################################################################### // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED // ################################################################################### DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp); DOCTEST_ASSERT_IN_TESTS(result.m_decomp); return !failed; } MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) { m_stream = tlssPush(); m_file = file; m_line = line; m_severity = severity; } MessageBuilder::~MessageBuilder() { if (!logged) tlssPop(); } DOCTEST_DEFINE_INTERFACE(IExceptionTranslator) bool MessageBuilder::log() { if (!logged) { m_string = tlssPop(); logged = true; } DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this); const bool isWarn = m_severity & assertType::is_warn; // warn is just a message in this context so we don't treat it as an assert if(!isWarn) { addAssert(m_severity); addFailedAssert(m_severity); } return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn && (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger } void MessageBuilder::react() { if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional throwException(); } } // namespace detail namespace { using namespace detail; // clang-format off // ================================================================================================= // The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp // This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. // ================================================================================================= class XmlEncode { public: enum ForWhat { ForTextNodes, ForAttributes }; XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); void encodeTo( std::ostream& os ) const; friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); private: std::string m_str; ForWhat m_forWhat; }; class XmlWriter { public: class ScopedElement { public: ScopedElement( XmlWriter* writer ); ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT; ScopedElement& operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT; ~ScopedElement(); ScopedElement& writeText( std::string const& text, bool indent = true ); template ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { m_writer->writeAttribute( name, attribute ); return *this; } private: mutable XmlWriter* m_writer = nullptr; }; #ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM XmlWriter( std::ostream& os = std::cout ); #else // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM XmlWriter( std::ostream& os ); #endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM ~XmlWriter(); XmlWriter( XmlWriter const& ) = delete; XmlWriter& operator=( XmlWriter const& ) = delete; XmlWriter& startElement( std::string const& name ); ScopedElement scopedElement( std::string const& name ); XmlWriter& endElement(); XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); XmlWriter& writeAttribute( std::string const& name, const char* attribute ); XmlWriter& writeAttribute( std::string const& name, bool attribute ); template XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { std::stringstream rss; rss << attribute; return writeAttribute( name, rss.str() ); } XmlWriter& writeText( std::string const& text, bool indent = true ); //XmlWriter& writeComment( std::string const& text ); //void writeStylesheetRef( std::string const& url ); //XmlWriter& writeBlankLine(); void ensureTagClosed(); void writeDeclaration(); private: void newlineIfNecessary(); bool m_tagIsOpen = false; bool m_needsNewline = false; std::vector m_tags; std::string m_indent; std::ostream& m_os; }; // ================================================================================================= // The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp // This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. // ================================================================================================= using uchar = unsigned char; namespace { size_t trailingBytes(unsigned char c) { if ((c & 0xE0) == 0xC0) { return 2; } if ((c & 0xF0) == 0xE0) { return 3; } if ((c & 0xF8) == 0xF0) { return 4; } DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); } uint32_t headerValue(unsigned char c) { if ((c & 0xE0) == 0xC0) { return c & 0x1F; } if ((c & 0xF0) == 0xE0) { return c & 0x0F; } if ((c & 0xF8) == 0xF0) { return c & 0x07; } DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); } void hexEscapeChar(std::ostream& os, unsigned char c) { std::ios_base::fmtflags f(os.flags()); os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast(c); os.flags(f); } } // anonymous namespace XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) : m_str( str ), m_forWhat( forWhat ) {} void XmlEncode::encodeTo( std::ostream& os ) const { // Apostrophe escaping not necessary if we always use " to write attributes // (see: https://www.w3.org/TR/xml/#syntax) for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { uchar c = m_str[idx]; switch (c) { case '<': os << "<"; break; case '&': os << "&"; break; case '>': // See: https://www.w3.org/TR/xml/#syntax if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') os << ">"; else os << c; break; case '\"': if (m_forWhat == ForAttributes) os << """; else os << c; break; default: // Check for control characters and invalid utf-8 // Escape control characters in standard ascii // see https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { hexEscapeChar(os, c); break; } // Plain ASCII: Write it to stream if (c < 0x7F) { os << c; break; } // UTF-8 territory // Check if the encoding is valid and if it is not, hex escape bytes. // Important: We do not check the exact decoded values for validity, only the encoding format // First check that this bytes is a valid lead byte: // This means that it is not encoded as 1111 1XXX // Or as 10XX XXXX if (c < 0xC0 || c >= 0xF8) { hexEscapeChar(os, c); break; } auto encBytes = trailingBytes(c); // Are there enough bytes left to avoid accessing out-of-bounds memory? if (idx + encBytes - 1 >= m_str.size()) { hexEscapeChar(os, c); break; } // The header is valid, check data // The next encBytes bytes must together be a valid utf-8 // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) bool valid = true; uint32_t value = headerValue(c); for (std::size_t n = 1; n < encBytes; ++n) { uchar nc = m_str[idx + n]; valid &= ((nc & 0xC0) == 0x80); value = (value << 6) | (nc & 0x3F); } if ( // Wrong bit pattern of following bytes (!valid) || // Overlong encodings (value < 0x80) || ( value < 0x800 && encBytes > 2) || // removed "0x80 <= value &&" because redundant (0x800 < value && value < 0x10000 && encBytes > 3) || // Encoded value out of range (value >= 0x110000) ) { hexEscapeChar(os, c); break; } // If we got here, this is in fact a valid(ish) utf-8 sequence for (std::size_t n = 0; n < encBytes; ++n) { os << m_str[idx + n]; } idx += encBytes - 1; break; } } } std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { xmlEncode.encodeTo( os ); return os; } XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) : m_writer( writer ) {} XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT : m_writer( other.m_writer ){ other.m_writer = nullptr; } XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT { if ( m_writer ) { m_writer->endElement(); } m_writer = other.m_writer; other.m_writer = nullptr; return *this; } XmlWriter::ScopedElement::~ScopedElement() { if( m_writer ) m_writer->endElement(); } XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { m_writer->writeText( text, indent ); return *this; } XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) { // writeDeclaration(); // called explicitly by the reporters that use the writer class - see issue #627 } XmlWriter::~XmlWriter() { while( !m_tags.empty() ) endElement(); } XmlWriter& XmlWriter::startElement( std::string const& name ) { ensureTagClosed(); newlineIfNecessary(); m_os << m_indent << '<' << name; m_tags.push_back( name ); m_indent += " "; m_tagIsOpen = true; return *this; } XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { ScopedElement scoped( this ); startElement( name ); return scoped; } XmlWriter& XmlWriter::endElement() { newlineIfNecessary(); m_indent = m_indent.substr( 0, m_indent.size()-2 ); if( m_tagIsOpen ) { m_os << "/>"; m_tagIsOpen = false; } else { m_os << m_indent << ""; } m_os << std::endl; m_tags.pop_back(); return *this; } XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { if( !name.empty() && !attribute.empty() ) m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; return *this; } XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) { if( !name.empty() && attribute && attribute[0] != '\0' ) m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; return *this; } XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; return *this; } XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { if( !text.empty() ){ bool tagWasOpen = m_tagIsOpen; ensureTagClosed(); if( tagWasOpen && indent ) m_os << m_indent; m_os << XmlEncode( text ); m_needsNewline = true; } return *this; } //XmlWriter& XmlWriter::writeComment( std::string const& text ) { // ensureTagClosed(); // m_os << m_indent << ""; // m_needsNewline = true; // return *this; //} //void XmlWriter::writeStylesheetRef( std::string const& url ) { // m_os << "\n"; //} //XmlWriter& XmlWriter::writeBlankLine() { // ensureTagClosed(); // m_os << '\n'; // return *this; //} void XmlWriter::ensureTagClosed() { if( m_tagIsOpen ) { m_os << ">" << std::endl; m_tagIsOpen = false; } } void XmlWriter::writeDeclaration() { m_os << "\n"; } void XmlWriter::newlineIfNecessary() { if( m_needsNewline ) { m_os << std::endl; m_needsNewline = false; } } // ================================================================================================= // End of copy-pasted code from Catch // ================================================================================================= // clang-format on struct XmlReporter : public IReporter { XmlWriter xml; DOCTEST_DECLARE_MUTEX(mutex) // caching pointers/references to objects of these types - safe to do const ContextOptions& opt; const TestCaseData* tc = nullptr; XmlReporter(const ContextOptions& co) : xml(*co.cout) , opt(co) {} void log_contexts() { int num_contexts = get_num_active_contexts(); if(num_contexts) { auto contexts = get_active_contexts(); std::stringstream ss; for(int i = 0; i < num_contexts; ++i) { contexts[i]->stringify(&ss); xml.scopedElement("Info").writeText(ss.str()); ss.str(""); } } } unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } void test_case_start_impl(const TestCaseData& in) { bool open_ts_tag = false; if(tc != nullptr) { // we have already opened a test suite if(std::strcmp(tc->m_test_suite, in.m_test_suite) != 0) { xml.endElement(); open_ts_tag = true; } } else { open_ts_tag = true; // first test case ==> first test suite } if(open_ts_tag) { xml.startElement("TestSuite"); xml.writeAttribute("name", in.m_test_suite); } tc = ∈ xml.startElement("TestCase") .writeAttribute("name", in.m_name) .writeAttribute("filename", skipPathFromFilename(in.m_file.c_str())) .writeAttribute("line", line(in.m_line)) .writeAttribute("description", in.m_description); if(Approx(in.m_timeout) != 0) xml.writeAttribute("timeout", in.m_timeout); if(in.m_may_fail) xml.writeAttribute("may_fail", true); if(in.m_should_fail) xml.writeAttribute("should_fail", true); } // ========================================================================================= // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE // ========================================================================================= void report_query(const QueryData& in) override { test_run_start(); if(opt.list_reporters) { for(auto& curr : getListeners()) xml.scopedElement("Listener") .writeAttribute("priority", curr.first.first) .writeAttribute("name", curr.first.second); for(auto& curr : getReporters()) xml.scopedElement("Reporter") .writeAttribute("priority", curr.first.first) .writeAttribute("name", curr.first.second); } else if(opt.count || opt.list_test_cases) { for(unsigned i = 0; i < in.num_data; ++i) { xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name) .writeAttribute("testsuite", in.data[i]->m_test_suite) .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str())) .writeAttribute("line", line(in.data[i]->m_line)) .writeAttribute("skipped", in.data[i]->m_skip); } xml.scopedElement("OverallResultsTestCases") .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); } else if(opt.list_test_suites) { for(unsigned i = 0; i < in.num_data; ++i) xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]->m_test_suite); xml.scopedElement("OverallResultsTestCases") .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); xml.scopedElement("OverallResultsTestSuites") .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters); } xml.endElement(); } void test_run_start() override { xml.writeDeclaration(); // remove .exe extension - mainly to have the same output on UNIX and Windows std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); #ifdef DOCTEST_PLATFORM_WINDOWS if(binary_name.rfind(".exe") != std::string::npos) binary_name = binary_name.substr(0, binary_name.length() - 4); #endif // DOCTEST_PLATFORM_WINDOWS xml.startElement("doctest").writeAttribute("binary", binary_name); if(opt.no_version == false) xml.writeAttribute("version", DOCTEST_VERSION_STR); // only the consequential ones (TODO: filters) xml.scopedElement("Options") .writeAttribute("order_by", opt.order_by.c_str()) .writeAttribute("rand_seed", opt.rand_seed) .writeAttribute("first", opt.first) .writeAttribute("last", opt.last) .writeAttribute("abort_after", opt.abort_after) .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels) .writeAttribute("case_sensitive", opt.case_sensitive) .writeAttribute("no_throw", opt.no_throw) .writeAttribute("no_skip", opt.no_skip); } void test_run_end(const TestRunStats& p) override { if(tc) // the TestSuite tag - only if there has been at least 1 test case xml.endElement(); xml.scopedElement("OverallResultsAsserts") .writeAttribute("successes", p.numAsserts - p.numAssertsFailed) .writeAttribute("failures", p.numAssertsFailed); xml.startElement("OverallResultsTestCases") .writeAttribute("successes", p.numTestCasesPassingFilters - p.numTestCasesFailed) .writeAttribute("failures", p.numTestCasesFailed); if(opt.no_skipped_summary == false) xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters); xml.endElement(); xml.endElement(); } void test_case_start(const TestCaseData& in) override { test_case_start_impl(in); xml.ensureTagClosed(); } void test_case_reenter(const TestCaseData&) override {} void test_case_end(const CurrentTestCaseStats& st) override { xml.startElement("OverallResultsAsserts") .writeAttribute("successes", st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest) .writeAttribute("failures", st.numAssertsFailedCurrentTest) .writeAttribute("test_case_success", st.testCaseSuccess); if(opt.duration) xml.writeAttribute("duration", st.seconds); if(tc->m_expected_failures) xml.writeAttribute("expected_failures", tc->m_expected_failures); xml.endElement(); xml.endElement(); } void test_case_exception(const TestCaseException& e) override { DOCTEST_LOCK_MUTEX(mutex) xml.scopedElement("Exception") .writeAttribute("crash", e.is_crash) .writeText(e.error_string.c_str()); } void subcase_start(const SubcaseSignature& in) override { xml.startElement("SubCase") .writeAttribute("name", in.m_name) .writeAttribute("filename", skipPathFromFilename(in.m_file)) .writeAttribute("line", line(in.m_line)); xml.ensureTagClosed(); } void subcase_end() override { xml.endElement(); } void log_assert(const AssertData& rb) override { if(!rb.m_failed && !opt.success) return; DOCTEST_LOCK_MUTEX(mutex) xml.startElement("Expression") .writeAttribute("success", !rb.m_failed) .writeAttribute("type", assertString(rb.m_at)) .writeAttribute("filename", skipPathFromFilename(rb.m_file)) .writeAttribute("line", line(rb.m_line)); xml.scopedElement("Original").writeText(rb.m_expr); if(rb.m_threw) xml.scopedElement("Exception").writeText(rb.m_exception.c_str()); if(rb.m_at & assertType::is_throws_as) xml.scopedElement("ExpectedException").writeText(rb.m_exception_type); if(rb.m_at & assertType::is_throws_with) xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string.c_str()); if((rb.m_at & assertType::is_normal) && !rb.m_threw) xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str()); log_contexts(); xml.endElement(); } void log_message(const MessageData& mb) override { DOCTEST_LOCK_MUTEX(mutex) xml.startElement("Message") .writeAttribute("type", failureString(mb.m_severity)) .writeAttribute("filename", skipPathFromFilename(mb.m_file)) .writeAttribute("line", line(mb.m_line)); xml.scopedElement("Text").writeText(mb.m_string.c_str()); log_contexts(); xml.endElement(); } void test_case_skipped(const TestCaseData& in) override { if(opt.no_skipped_summary == false) { test_case_start_impl(in); xml.writeAttribute("skipped", "true"); xml.endElement(); } } }; DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter); void fulltext_log_assert_to_stream(std::ostream& s, const AssertData& rb) { if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) == 0) //!OCLINT bitwise operator in conditional s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) " << Color::None; if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n"; } else if((rb.m_at & assertType::is_throws_as) && (rb.m_at & assertType::is_throws_with)) { //!OCLINT s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" << rb.m_exception_string.c_str() << "\", " << rb.m_exception_type << " ) " << Color::None; if(rb.m_threw) { if(!rb.m_failed) { s << "threw as expected!\n"; } else { s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n"; } } else { s << "did NOT throw at all!\n"; } } else if(rb.m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", " << rb.m_exception_type << " ) " << Color::None << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" : "threw a DIFFERENT exception: ") : "did NOT throw at all!") << Color::Cyan << rb.m_exception << "\n"; } else if(rb.m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" << rb.m_exception_string.c_str() << "\" ) " << Color::None << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : "threw a DIFFERENT exception: ") : "did NOT throw at all!") << Color::Cyan << rb.m_exception << "\n"; } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan << rb.m_exception << "\n"; } else { s << (rb.m_threw ? "THREW exception: " : (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")); if(rb.m_threw) s << rb.m_exception << "\n"; else s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n"; } } // TODO: // - log_message() // - respond to queries // - honor remaining options // - more attributes in tags struct JUnitReporter : public IReporter { XmlWriter xml; DOCTEST_DECLARE_MUTEX(mutex) Timer timer; std::vector deepestSubcaseStackNames; struct JUnitTestCaseData { static std::string getCurrentTimestamp() { // Beware, this is not reentrant because of backward compatibility issues // Also, UTC only, again because of backward compatibility (%z is C++11) time_t rawtime; std::time(&rawtime); auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); std::tm timeInfo; #ifdef DOCTEST_PLATFORM_WINDOWS gmtime_s(&timeInfo, &rawtime); #else // DOCTEST_PLATFORM_WINDOWS gmtime_r(&rawtime, &timeInfo); #endif // DOCTEST_PLATFORM_WINDOWS char timeStamp[timeStampSize]; const char* const fmt = "%Y-%m-%dT%H:%M:%SZ"; std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); return std::string(timeStamp); } struct JUnitTestMessage { JUnitTestMessage(const std::string& _message, const std::string& _type, const std::string& _details) : message(_message), type(_type), details(_details) {} JUnitTestMessage(const std::string& _message, const std::string& _details) : message(_message), type(), details(_details) {} std::string message, type, details; }; struct JUnitTestCase { JUnitTestCase(const std::string& _classname, const std::string& _name) : classname(_classname), name(_name), time(0), failures() {} std::string classname, name; double time; std::vector failures, errors; }; void add(const std::string& classname, const std::string& name) { testcases.emplace_back(classname, name); } void appendSubcaseNamesToLastTestcase(std::vector nameStack) { for(auto& curr: nameStack) if(curr.size()) testcases.back().name += std::string("/") + curr.c_str(); } void addTime(double time) { if(time < 1e-4) time = 0; testcases.back().time = time; totalSeconds += time; } void addFailure(const std::string& message, const std::string& type, const std::string& details) { testcases.back().failures.emplace_back(message, type, details); ++totalFailures; } void addError(const std::string& message, const std::string& details) { testcases.back().errors.emplace_back(message, details); ++totalErrors; } std::vector testcases; double totalSeconds = 0; int totalErrors = 0, totalFailures = 0; }; JUnitTestCaseData testCaseData; // caching pointers/references to objects of these types - safe to do const ContextOptions& opt; const TestCaseData* tc = nullptr; JUnitReporter(const ContextOptions& co) : xml(*co.cout) , opt(co) {} unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } // ========================================================================================= // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE // ========================================================================================= void report_query(const QueryData&) override { xml.writeDeclaration(); } void test_run_start() override { xml.writeDeclaration(); } void test_run_end(const TestRunStats& p) override { // remove .exe extension - mainly to have the same output on UNIX and Windows std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); #ifdef DOCTEST_PLATFORM_WINDOWS if(binary_name.rfind(".exe") != std::string::npos) binary_name = binary_name.substr(0, binary_name.length() - 4); #endif // DOCTEST_PLATFORM_WINDOWS xml.startElement("testsuites"); xml.startElement("testsuite").writeAttribute("name", binary_name) .writeAttribute("errors", testCaseData.totalErrors) .writeAttribute("failures", testCaseData.totalFailures) .writeAttribute("tests", p.numAsserts); if(opt.no_time_in_output == false) { xml.writeAttribute("time", testCaseData.totalSeconds); xml.writeAttribute("timestamp", JUnitTestCaseData::getCurrentTimestamp()); } if(opt.no_version == false) xml.writeAttribute("doctest_version", DOCTEST_VERSION_STR); for(const auto& testCase : testCaseData.testcases) { xml.startElement("testcase") .writeAttribute("classname", testCase.classname) .writeAttribute("name", testCase.name); if(opt.no_time_in_output == false) xml.writeAttribute("time", testCase.time); // This is not ideal, but it should be enough to mimic gtest's junit output. xml.writeAttribute("status", "run"); for(const auto& failure : testCase.failures) { xml.scopedElement("failure") .writeAttribute("message", failure.message) .writeAttribute("type", failure.type) .writeText(failure.details, false); } for(const auto& error : testCase.errors) { xml.scopedElement("error") .writeAttribute("message", error.message) .writeText(error.details); } xml.endElement(); } xml.endElement(); xml.endElement(); } void test_case_start(const TestCaseData& in) override { testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); timer.start(); } void test_case_reenter(const TestCaseData& in) override { testCaseData.addTime(timer.getElapsedSeconds()); testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); deepestSubcaseStackNames.clear(); timer.start(); testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); } void test_case_end(const CurrentTestCaseStats&) override { testCaseData.addTime(timer.getElapsedSeconds()); testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); deepestSubcaseStackNames.clear(); } void test_case_exception(const TestCaseException& e) override { DOCTEST_LOCK_MUTEX(mutex) testCaseData.addError("exception", e.error_string.c_str()); } void subcase_start(const SubcaseSignature& in) override { deepestSubcaseStackNames.push_back(in.m_name); } void subcase_end() override {} void log_assert(const AssertData& rb) override { if(!rb.m_failed) // report only failures & ignore the `success` option return; DOCTEST_LOCK_MUTEX(mutex) std::ostringstream os; os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(") << line(rb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl; fulltext_log_assert_to_stream(os, rb); log_contexts(os); testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str()); } void log_message(const MessageData& mb) override { if(mb.m_severity & assertType::is_warn) // report only failures return; DOCTEST_LOCK_MUTEX(mutex) std::ostringstream os; os << skipPathFromFilename(mb.m_file) << (opt.gnu_file_line ? ":" : "(") << line(mb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl; os << mb.m_string.c_str() << "\n"; log_contexts(os); testCaseData.addFailure(mb.m_string.c_str(), mb.m_severity & assertType::is_check ? "FAIL_CHECK" : "FAIL", os.str()); } void test_case_skipped(const TestCaseData&) override {} void log_contexts(std::ostringstream& s) { int num_contexts = get_num_active_contexts(); if(num_contexts) { auto contexts = get_active_contexts(); s << " logged: "; for(int i = 0; i < num_contexts; ++i) { s << (i == 0 ? "" : " "); contexts[i]->stringify(&s); s << std::endl; } } } }; DOCTEST_REGISTER_REPORTER("junit", 0, JUnitReporter); struct Whitespace { int nrSpaces; explicit Whitespace(int nr) : nrSpaces(nr) {} }; std::ostream& operator<<(std::ostream& out, const Whitespace& ws) { if(ws.nrSpaces != 0) out << std::setw(ws.nrSpaces) << ' '; return out; } struct ConsoleReporter : public IReporter { std::ostream& s; bool hasLoggedCurrentTestStart; std::vector subcasesStack; size_t currentSubcaseLevel; DOCTEST_DECLARE_MUTEX(mutex) // caching pointers/references to objects of these types - safe to do const ContextOptions& opt; const TestCaseData* tc; ConsoleReporter(const ContextOptions& co) : s(*co.cout) , opt(co) {} ConsoleReporter(const ContextOptions& co, std::ostream& ostr) : s(ostr) , opt(co) {} // ========================================================================================= // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE // ========================================================================================= void separator_to_stream() { s << Color::Yellow << "===============================================================================" "\n"; } const char* getSuccessOrFailString(bool success, assertType::Enum at, const char* success_str) { if(success) return success_str; return failureString(at); } Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) { return success ? Color::BrightGreen : (at & assertType::is_warn) ? Color::Yellow : Color::Red; } void successOrFailColoredStringToStream(bool success, assertType::Enum at, const char* success_str = "SUCCESS") { s << getSuccessOrFailColor(success, at) << getSuccessOrFailString(success, at, success_str) << ": "; } void log_contexts() { int num_contexts = get_num_active_contexts(); if(num_contexts) { auto contexts = get_active_contexts(); s << Color::None << " logged: "; for(int i = 0; i < num_contexts; ++i) { s << (i == 0 ? "" : " "); contexts[i]->stringify(&s); s << "\n"; } } s << "\n"; } // this was requested to be made virtual so users could override it virtual void file_line_to_stream(const char* file, int line, const char* tail = "") { s << Color::LightGrey << skipPathFromFilename(file) << (opt.gnu_file_line ? ":" : "(") << (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option << (opt.gnu_file_line ? ":" : "):") << tail; } void logTestStart() { if(hasLoggedCurrentTestStart) return; separator_to_stream(); file_line_to_stream(tc->m_file.c_str(), tc->m_line, "\n"); if(tc->m_description) s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n"; if(tc->m_test_suite && tc->m_test_suite[0] != '\0') s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n"; if(strncmp(tc->m_name, " Scenario:", 11) != 0) s << Color::Yellow << "TEST CASE: "; s << Color::None << tc->m_name << "\n"; for(size_t i = 0; i < currentSubcaseLevel; ++i) { if(subcasesStack[i].m_name[0] != '\0') s << " " << subcasesStack[i].m_name << "\n"; } if(currentSubcaseLevel != subcasesStack.size()) { s << Color::Yellow << "\nDEEPEST SUBCASE STACK REACHED (DIFFERENT FROM THE CURRENT ONE):\n" << Color::None; for(size_t i = 0; i < subcasesStack.size(); ++i) { if(subcasesStack[i].m_name[0] != '\0') s << " " << subcasesStack[i].m_name << "\n"; } } s << "\n"; hasLoggedCurrentTestStart = true; } void printVersion() { if(opt.no_version == false) s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \"" << DOCTEST_VERSION_STR << "\"\n"; } void printIntro() { if(opt.no_intro == false) { printVersion(); s << Color::Cyan << "[doctest] " << Color::None << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; } } void printHelp() { int sizePrefixDisplay = static_cast(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY)); printVersion(); // clang-format off s << Color::Cyan << "[doctest]\n" << Color::None; s << Color::Cyan << "[doctest] " << Color::None; s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n"; s << Color::Cyan << "[doctest] " << Color::None; s << "filter values: \"str1,str2,str3\" (comma separated strings)\n"; s << Color::Cyan << "[doctest]\n" << Color::None; s << Color::Cyan << "[doctest] " << Color::None; s << "filters use wildcards for matching strings\n"; s << Color::Cyan << "[doctest] " << Color::None; s << "something passes a filter if any of the strings in a filter matches\n"; #ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS s << Color::Cyan << "[doctest]\n" << Color::None; s << Color::Cyan << "[doctest] " << Color::None; s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n"; #endif s << Color::Cyan << "[doctest]\n" << Color::None; s << Color::Cyan << "[doctest] " << Color::None; s << "Query flags - the program quits after them. Available:\n\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h " << Whitespace(sizePrefixDisplay*0) << "prints this message\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version " << Whitespace(sizePrefixDisplay*1) << "prints the version\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count " << Whitespace(sizePrefixDisplay*1) << "prints the number of matching tests\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases " << Whitespace(sizePrefixDisplay*1) << "lists all matching tests by name\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites " << Whitespace(sizePrefixDisplay*1) << "lists all matching test suites\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters " << Whitespace(sizePrefixDisplay*1) << "lists all registered reporters\n\n"; // ================================================================================== << 79 s << Color::Cyan << "[doctest] " << Color::None; s << "The available / options/filters are:\n\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case= " << Whitespace(sizePrefixDisplay*1) << "filters tests by their name\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude= " << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their name\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file= " << Whitespace(sizePrefixDisplay*1) << "filters tests by their file\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude= " << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their file\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite= " << Whitespace(sizePrefixDisplay*1) << "filters tests by their test suite\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude= " << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their test suite\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase= " << Whitespace(sizePrefixDisplay*1) << "filters subcases by their name\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude= " << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters= " << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out= " << Whitespace(sizePrefixDisplay*1) << "output filename\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by= " << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n"; s << Whitespace(sizePrefixDisplay*3) << " - [file/suite/name/rand/none]\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed= " << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first= " << Whitespace(sizePrefixDisplay*1) << "the first test passing the filters to\n"; s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last= " << Whitespace(sizePrefixDisplay*1) << "the last test passing the filters to\n"; s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after= " << Whitespace(sizePrefixDisplay*1) << "stop after failed assertions\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels= " << Whitespace(sizePrefixDisplay*1) << "apply filters for the first levels\n"; s << Color::Cyan << "\n[doctest] " << Color::None; s << "Bool options - can be used like flags and true is assumed. Available:\n\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success= " << Whitespace(sizePrefixDisplay*1) << "include successful assertions in output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive= " << Whitespace(sizePrefixDisplay*1) << "filters being treated as case sensitive\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit= " << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration= " << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "m, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "minimal= " << Whitespace(sizePrefixDisplay*1) << "minimal console output (only failures)\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "q, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "quiet= " << Whitespace(sizePrefixDisplay*1) << "no console output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw= " << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode= " << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run= " << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ni, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-intro= " << Whitespace(sizePrefixDisplay*1) << "omit the framework intro in the output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version= " << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors= " << Whitespace(sizePrefixDisplay*1) << "disables colors in output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors= " << Whitespace(sizePrefixDisplay*1) << "use colors even when not in a tty\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks= " << Whitespace(sizePrefixDisplay*1) << "disables breakpoints in debuggers\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip= " << Whitespace(sizePrefixDisplay*1) << "don't skip test cases marked as skip\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line= " << Whitespace(sizePrefixDisplay*1) << ":n: vs (n): for line numbers in output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames= " << Whitespace(sizePrefixDisplay*1) << "only filenames and no paths in output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers= " << Whitespace(sizePrefixDisplay*1) << "0 instead of real line numbers in output\n"; // ================================================================================== << 79 // clang-format on s << Color::Cyan << "\n[doctest] " << Color::None; s << "for more information visit the project documentation\n\n"; } void printRegisteredReporters() { printVersion(); auto printReporters = [this] (const reporterMap& reporters, const char* type) { if(reporters.size()) { s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n"; for(auto& curr : reporters) s << "priority: " << std::setw(5) << curr.first.first << " name: " << curr.first.second << "\n"; } }; printReporters(getListeners(), "listeners"); printReporters(getReporters(), "reporters"); } // ========================================================================================= // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE // ========================================================================================= void report_query(const QueryData& in) override { if(opt.version) { printVersion(); } else if(opt.help) { printHelp(); } else if(opt.list_reporters) { printRegisteredReporters(); } else if(opt.count || opt.list_test_cases) { if(opt.list_test_cases) { s << Color::Cyan << "[doctest] " << Color::None << "listing all test case names\n"; separator_to_stream(); } for(unsigned i = 0; i < in.num_data; ++i) s << Color::None << in.data[i]->m_name << "\n"; separator_to_stream(); s << Color::Cyan << "[doctest] " << Color::None << "unskipped test cases passing the current filters: " << g_cs->numTestCasesPassingFilters << "\n"; } else if(opt.list_test_suites) { s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n"; separator_to_stream(); for(unsigned i = 0; i < in.num_data; ++i) s << Color::None << in.data[i]->m_test_suite << "\n"; separator_to_stream(); s << Color::Cyan << "[doctest] " << Color::None << "unskipped test cases passing the current filters: " << g_cs->numTestCasesPassingFilters << "\n"; s << Color::Cyan << "[doctest] " << Color::None << "test suites with unskipped test cases passing the current filters: " << g_cs->numTestSuitesPassingFilters << "\n"; } } void test_run_start() override { if(!opt.minimal) printIntro(); } void test_run_end(const TestRunStats& p) override { if(opt.minimal && p.numTestCasesFailed == 0) return; separator_to_stream(); s << std::dec; auto totwidth = int(std::ceil(log10(static_cast(std::max(p.numTestCasesPassingFilters, static_cast(p.numAsserts))) + 1))); auto passwidth = int(std::ceil(log10(static_cast(std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast(p.numAsserts - p.numAssertsFailed))) + 1))); auto failwidth = int(std::ceil(log10(static_cast(std::max(p.numTestCasesFailed, static_cast(p.numAssertsFailed))) + 1))); const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0; s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth) << p.numTestCasesPassingFilters << " | " << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None : Color::Green) << std::setw(passwidth) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth) << p.numTestCasesFailed << " failed" << Color::None << " |"; if(opt.no_skipped_summary == false) { const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters; s << " " << (numSkipped == 0 ? Color::None : Color::Yellow) << numSkipped << " skipped" << Color::None; } s << "\n"; s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(totwidth) << p.numAsserts << " | " << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green) << std::setw(passwidth) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth) << p.numAssertsFailed << " failed" << Color::None << " |\n"; s << Color::Cyan << "[doctest] " << Color::None << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green) << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl; } void test_case_start(const TestCaseData& in) override { hasLoggedCurrentTestStart = false; tc = ∈ subcasesStack.clear(); currentSubcaseLevel = 0; } void test_case_reenter(const TestCaseData&) override { subcasesStack.clear(); } void test_case_end(const CurrentTestCaseStats& st) override { if(tc->m_no_output) return; // log the preamble of the test case only if there is something // else to print - something other than that an assert has failed if(opt.duration || (st.failure_flags && st.failure_flags != static_cast(TestCaseFailureReason::AssertFailure))) logTestStart(); if(opt.duration) s << Color::None << std::setprecision(6) << std::fixed << st.seconds << " s: " << tc->m_name << "\n"; if(st.failure_flags & TestCaseFailureReason::Timeout) s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6) << std::fixed << tc->m_timeout << "!\n"; if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) { s << Color::Red << "Should have failed but didn't! Marking it as failed!\n"; } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) { s << Color::Yellow << "Failed as expected so marking it as not failed\n"; } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) { s << Color::Yellow << "Allowed to fail so marking it as not failed\n"; } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) { s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures << " times so marking it as failed!\n"; } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) { s << Color::Yellow << "Failed exactly " << tc->m_expected_failures << " times as expected so marking it as not failed!\n"; } if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) { s << Color::Red << "Aborting - too many failed asserts!\n"; } s << Color::None; // lgtm [cpp/useless-expression] } void test_case_exception(const TestCaseException& e) override { DOCTEST_LOCK_MUTEX(mutex) if(tc->m_no_output) return; logTestStart(); file_line_to_stream(tc->m_file.c_str(), tc->m_line, " "); successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require : assertType::is_check); s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ") << Color::Cyan << e.error_string << "\n"; int num_stringified_contexts = get_num_stringified_contexts(); if(num_stringified_contexts) { auto stringified_contexts = get_stringified_contexts(); s << Color::None << " logged: "; for(int i = num_stringified_contexts; i > 0; --i) { s << (i == num_stringified_contexts ? "" : " ") << stringified_contexts[i - 1] << "\n"; } } s << "\n" << Color::None; } void subcase_start(const SubcaseSignature& subc) override { subcasesStack.push_back(subc); ++currentSubcaseLevel; hasLoggedCurrentTestStart = false; } void subcase_end() override { --currentSubcaseLevel; hasLoggedCurrentTestStart = false; } void log_assert(const AssertData& rb) override { if((!rb.m_failed && !opt.success) || tc->m_no_output) return; DOCTEST_LOCK_MUTEX(mutex) logTestStart(); file_line_to_stream(rb.m_file, rb.m_line, " "); successOrFailColoredStringToStream(!rb.m_failed, rb.m_at); fulltext_log_assert_to_stream(s, rb); log_contexts(); } void log_message(const MessageData& mb) override { if(tc->m_no_output) return; DOCTEST_LOCK_MUTEX(mutex) logTestStart(); file_line_to_stream(mb.m_file, mb.m_line, " "); s << getSuccessOrFailColor(false, mb.m_severity) << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity, "MESSAGE") << ": "; s << Color::None << mb.m_string << "\n"; log_contexts(); } void test_case_skipped(const TestCaseData&) override {} }; DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter); #ifdef DOCTEST_PLATFORM_WINDOWS struct DebugOutputWindowReporter : public ConsoleReporter { DOCTEST_THREAD_LOCAL static std::ostringstream oss; DebugOutputWindowReporter(const ContextOptions& co) : ConsoleReporter(co, oss) {} #define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg) \ void func(type arg) override { \ bool with_col = g_no_colors; \ g_no_colors = false; \ ConsoleReporter::func(arg); \ if(oss.tellp() != std::streampos{}) { \ DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \ oss.str(""); \ } \ g_no_colors = with_col; \ } DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY) DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in) DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in) DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_reenter, const TestCaseData&, in) DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in) DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in) DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in) DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY) DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in) DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in) DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in) }; DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss; #endif // DOCTEST_PLATFORM_WINDOWS // the implementation of parseOption() bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String* value) { // going from the end to the beginning and stopping on the first occurrence from the end for(int i = argc; i > 0; --i) { auto index = i - 1; auto temp = std::strstr(argv[index], pattern); if(temp && (value || strlen(temp) == strlen(pattern))) { //!OCLINT prefer early exits and continue // eliminate matches in which the chars before the option are not '-' bool noBadCharsFound = true; auto curr = argv[index]; while(curr != temp) { if(*curr++ != '-') { noBadCharsFound = false; break; } } if(noBadCharsFound && argv[index][0] == '-') { if(value) { // parsing the value of an option temp += strlen(pattern); const unsigned len = strlen(temp); if(len) { *value = temp; return true; } } else { // just a flag - no value return true; } } } } return false; } // parses an option and returns the string after the '=' character bool parseOption(int argc, const char* const* argv, const char* pattern, String* value = nullptr, const String& defaultVal = String()) { if(value) *value = defaultVal; #ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS // offset (normally 3 for "dt-") to skip prefix if(parseOptionImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX), value)) return true; #endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS return parseOptionImpl(argc, argv, pattern, value); } // locates a flag on the command line bool parseFlag(int argc, const char* const* argv, const char* pattern) { return parseOption(argc, argv, pattern); } // parses a comma separated list of words after a pattern in one of the arguments in argv bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern, std::vector& res) { String filtersString; if(parseOption(argc, argv, pattern, &filtersString)) { // tokenize with "," as a separator, unless escaped with backslash std::ostringstream s; auto flush = [&s, &res]() { auto string = s.str(); if(string.size() > 0) { res.push_back(string.c_str()); } s.str(""); }; bool seenBackslash = false; const char* current = filtersString.c_str(); const char* end = current + strlen(current); while(current != end) { char character = *current++; if(seenBackslash) { seenBackslash = false; if(character == ',' || character == '\\') { s.put(character); continue; } s.put('\\'); } if(character == '\\') { seenBackslash = true; } else if(character == ',') { flush(); } else { s.put(character); } } if(seenBackslash) { s.put('\\'); } flush(); return true; } return false; } enum optionType { option_bool, option_int }; // parses an int/bool option from the command line bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type, int& res) { String parsedValue; if(!parseOption(argc, argv, pattern, &parsedValue)) return false; if(type) { // integer // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse... int theInt = std::atoi(parsedValue.c_str()); if (theInt != 0) { res = theInt; //!OCLINT parameter reassignment return true; } } else { // boolean const char positive[][5] = { "1", "true", "on", "yes" }; // 5 - strlen("true") + 1 const char negative[][6] = { "0", "false", "off", "no" }; // 6 - strlen("false") + 1 // if the value matches any of the positive/negative possibilities for (unsigned i = 0; i < 4; i++) { if (parsedValue.compare(positive[i], true) == 0) { res = 1; //!OCLINT parameter reassignment return true; } if (parsedValue.compare(negative[i], true) == 0) { res = 0; //!OCLINT parameter reassignment return true; } } } return false; } } // namespace Context::Context(int argc, const char* const* argv) : p(new detail::ContextState) { parseArgs(argc, argv, true); if(argc) p->binary_name = argv[0]; } Context::~Context() { if(g_cs == p) g_cs = nullptr; delete p; } void Context::applyCommandLine(int argc, const char* const* argv) { parseArgs(argc, argv); if(argc) p->binary_name = argv[0]; } // parses args void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { using namespace detail; // clang-format off parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file=", p->filters[0]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sf=", p->filters[0]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file-exclude=",p->filters[1]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sfe=", p->filters[1]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite=", p->filters[2]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ts=", p->filters[2]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite-exclude=", p->filters[3]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tse=", p->filters[3]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case=", p->filters[4]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tc=", p->filters[4]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case-exclude=", p->filters[5]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tce=", p->filters[5]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase=", p->filters[6]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sc=", p->filters[6]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase-exclude=", p->filters[7]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=", p->filters[7]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=", p->filters[8]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=", p->filters[8]); // clang-format on int intRes = 0; String strRes; #define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \ if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) || \ parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes)) \ p->var = static_cast(intRes); \ else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) || \ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname)) \ p->var = true; \ else if(withDefaults) \ p->var = default #define DOCTEST_PARSE_INT_OPTION(name, sname, var, default) \ if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_int, intRes) || \ parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_int, intRes)) \ p->var = intRes; \ else if(withDefaults) \ p->var = default #define DOCTEST_PARSE_STR_OPTION(name, sname, var, default) \ if(parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", &strRes, default) || \ parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", &strRes, default) || \ withDefaults) \ p->var = strRes // clang-format off DOCTEST_PARSE_STR_OPTION("out", "o", out, ""); DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file"); DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0); DOCTEST_PARSE_INT_OPTION("first", "f", first, 0); DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX); DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0); DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX); DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("minimal", "m", minimal, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("quiet", "q", quiet, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-intro", "ni", no_intro, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-breaks", "nb", no_breaks, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skip", "ns", no_skip, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC)); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-debug-output", "ndo", no_debug_output, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-time-in-output", "ntio", no_time_in_output, false); // clang-format on if(withDefaults) { p->help = false; p->version = false; p->count = false; p->list_test_cases = false; p->list_test_suites = false; p->list_reporters = false; } if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "help") || parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "h") || parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "?")) { p->help = true; p->exit = true; } if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "version") || parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "v")) { p->version = true; p->exit = true; } if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "count") || parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "c")) { p->count = true; p->exit = true; } if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-cases") || parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ltc")) { p->list_test_cases = true; p->exit = true; } if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-suites") || parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lts")) { p->list_test_suites = true; p->exit = true; } if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-reporters") || parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lr")) { p->list_reporters = true; p->exit = true; } } // allows the user to add procedurally to the filters from the command line void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); } // allows the user to clear all filters from the command line void Context::clearFilters() { for(auto& curr : p->filters) curr.clear(); } // allows the user to override procedurally the bool options from the command line void Context::setOption(const char* option, bool value) { setOption(option, value ? "true" : "false"); } // allows the user to override procedurally the int options from the command line void Context::setOption(const char* option, int value) { setOption(option, toString(value).c_str()); } // allows the user to override procedurally the string options from the command line void Context::setOption(const char* option, const char* value) { auto argv = String("-") + option + "=" + value; auto lvalue = argv.c_str(); parseArgs(1, &lvalue); } // users should query this in their main() and exit the program if true bool Context::shouldExit() { return p->exit; } void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; } void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; } void Context::setCout(std::ostream* out) { p->cout = out; } static class DiscardOStream : public std::ostream { private: class : public std::streambuf { private: // allowing some buffering decreases the amount of calls to overflow char buf[1024]; protected: std::streamsize xsputn(const char_type*, std::streamsize count) override { return count; } int_type overflow(int_type ch) override { setp(std::begin(buf), std::end(buf)); return traits_type::not_eof(ch); } } discardBuf; public: DiscardOStream() : std::ostream(&discardBuf) {} } discardOut; // the main function that does all the filtering and test running int Context::run() { using namespace detail; // save the old context state in case such was setup - for using asserts out of a testing context auto old_cs = g_cs; // this is the current contest g_cs = p; is_running_in_test = true; g_no_colors = p->no_colors; p->resetRunData(); std::fstream fstr; if(p->cout == nullptr) { if(p->quiet) { p->cout = &discardOut; } else if(p->out.size()) { // to a file if specified fstr.open(p->out.c_str(), std::fstream::out); p->cout = &fstr; } else { #ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM // stdout by default p->cout = &std::cout; #else // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM return EXIT_FAILURE; #endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM } } FatalConditionHandler::allocateAltStackMem(); auto cleanup_and_return = [&]() { FatalConditionHandler::freeAltStackMem(); if(fstr.is_open()) fstr.close(); // restore context g_cs = old_cs; is_running_in_test = false; // we have to free the reporters which were allocated when the run started for(auto& curr : p->reporters_currently_used) delete curr; p->reporters_currently_used.clear(); if(p->numTestCasesFailed && !p->no_exitcode) return EXIT_FAILURE; return EXIT_SUCCESS; }; // setup default reporter if none is given through the command line if(p->filters[8].empty()) p->filters[8].push_back("console"); // check to see if any of the registered reporters has been selected for(auto& curr : getReporters()) { if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive)) p->reporters_currently_used.push_back(curr.second(*g_cs)); } // TODO: check if there is nothing in reporters_currently_used // prepend all listeners for(auto& curr : getListeners()) p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs)); #ifdef DOCTEST_PLATFORM_WINDOWS if(isDebuggerActive() && p->no_debug_output == false) p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs)); #endif // DOCTEST_PLATFORM_WINDOWS // handle version, help and no_run if(p->no_run || p->version || p->help || p->list_reporters) { DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData()); return cleanup_and_return(); } std::vector testArray; for(auto& curr : getRegisteredTests()) testArray.push_back(&curr); p->numTestCases = testArray.size(); // sort the collected records if(!testArray.empty()) { if(p->order_by.compare("file", true) == 0) { std::sort(testArray.begin(), testArray.end(), fileOrderComparator); } else if(p->order_by.compare("suite", true) == 0) { std::sort(testArray.begin(), testArray.end(), suiteOrderComparator); } else if(p->order_by.compare("name", true) == 0) { std::sort(testArray.begin(), testArray.end(), nameOrderComparator); } else if(p->order_by.compare("rand", true) == 0) { std::srand(p->rand_seed); // random_shuffle implementation const auto first = &testArray[0]; for(size_t i = testArray.size() - 1; i > 0; --i) { int idxToSwap = std::rand() % (i + 1); const auto temp = first[i]; first[i] = first[idxToSwap]; first[idxToSwap] = temp; } } else if(p->order_by.compare("none", true) == 0) { // means no sorting - beneficial for death tests which call into the executable // with a specific test case in mind - we don't want to slow down the startup times } } std::set testSuitesPassingFilt; bool query_mode = p->count || p->list_test_cases || p->list_test_suites; std::vector queryResults; if(!query_mode) DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY); // invoke the registered functions if they match the filter criteria (or just count them) for(auto& curr : testArray) { const auto& tc = *curr; bool skip_me = false; if(tc.m_skip && !p->no_skip) skip_me = true; if(!matchesAny(tc.m_file.c_str(), p->filters[0], true, p->case_sensitive)) skip_me = true; if(matchesAny(tc.m_file.c_str(), p->filters[1], false, p->case_sensitive)) skip_me = true; if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive)) skip_me = true; if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive)) skip_me = true; if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive)) skip_me = true; if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive)) skip_me = true; if(!skip_me) p->numTestCasesPassingFilters++; // skip the test if it is not in the execution range if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) || (p->first > p->numTestCasesPassingFilters)) skip_me = true; if(skip_me) { if(!query_mode) DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc); continue; } // do not execute the test if we are to only count the number of filter passing tests if(p->count) continue; // print the name of the test and don't execute it if(p->list_test_cases) { queryResults.push_back(&tc); continue; } // print the name of the test suite if not done already and don't execute it if(p->list_test_suites) { if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') { queryResults.push_back(&tc); testSuitesPassingFilt.insert(tc.m_test_suite); p->numTestSuitesPassingFilters++; } continue; } // execute the test if it passes all the filtering { p->currentTest = &tc; p->failure_flags = TestCaseFailureReason::None; p->seconds = 0; // reset atomic counters p->numAssertsFailedCurrentTest_atomic = 0; p->numAssertsCurrentTest_atomic = 0; p->fullyTraversedSubcases.clear(); DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc); p->timer.start(); bool run_test = true; do { // reset some of the fields for subcases (except for the set of fully passed ones) p->reachedLeaf = false; // May not be empty if previous subcase exited via exception. p->subcaseStack.clear(); p->currentSubcaseDepth = 0; p->shouldLogCurrentException = true; // reset stuff for logging with INFO() p->stringifiedContexts.clear(); #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS try { #endif // DOCTEST_CONFIG_NO_EXCEPTIONS // MSVC 2015 diagnoses fatalConditionHandler as unused (because reset() is a static method) DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4101) // unreferenced local variable FatalConditionHandler fatalConditionHandler; // Handle signals // execute the test tc.m_test(); fatalConditionHandler.reset(); DOCTEST_MSVC_SUPPRESS_WARNING_POP #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS } catch(const TestFailureException&) { p->failure_flags |= TestCaseFailureReason::AssertFailure; } catch(...) { DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {translateActiveException(), false}); p->failure_flags |= TestCaseFailureReason::Exception; } #endif // DOCTEST_CONFIG_NO_EXCEPTIONS // exit this loop if enough assertions have failed - even if there are more subcases if(p->abort_after > 0 && p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) { run_test = false; p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts; } if(!p->nextSubcaseStack.empty() && run_test) DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc); if(p->nextSubcaseStack.empty()) run_test = false; } while(run_test); p->finalizeTestCaseData(); DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); p->currentTest = nullptr; // stop executing tests if enough assertions have failed if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after) break; } } if(!query_mode) { DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); } else { QueryData qdata; qdata.run_stats = g_cs; qdata.data = queryResults.data(); qdata.num_data = unsigned(queryResults.size()); DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata); } return cleanup_and_return(); } DOCTEST_DEFINE_INTERFACE(IReporter) int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); } const IContextScope* const* IReporter::get_active_contexts() { return get_num_active_contexts() ? &detail::g_infoContexts[0] : nullptr; } int IReporter::get_num_stringified_contexts() { return detail::g_cs->stringifiedContexts.size(); } const String* IReporter::get_stringified_contexts() { return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr; } namespace detail { void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) { if(isReporter) getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); else getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); } } // namespace detail } // namespace doctest #endif // DOCTEST_CONFIG_DISABLE #ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182 int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); } DOCTEST_MSVC_SUPPRESS_WARNING_POP #endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP DOCTEST_SUPPRESS_COMMON_WARNINGS_POP #endif // DOCTEST_LIBRARY_IMPLEMENTATION #endif // DOCTEST_CONFIG_IMPLEMENT #ifdef DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN #undef WIN32_LEAN_AND_MEAN #undef DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN #endif // DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN #ifdef DOCTEST_UNDEF_NOMINMAX #undef NOMINMAX #undef DOCTEST_UNDEF_NOMINMAX #endif // DOCTEST_UNDEF_NOMINMAX argparse-3.2/test/main.cpp000066400000000000000000000000271474520133400155730ustar00rootroot00000000000000#include argparse-3.2/test/test_actions.cpp000066400000000000000000000124651474520133400173570ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include using doctest::test_suite; TEST_CASE("Users can use default value inside actions" * test_suite("actions")) { argparse::ArgumentParser program("test"); program.add_argument("input").default_value("bar").action( [=](const std::string &value) { static const std::vector choices = {"foo", "bar", "baz"}; if (std::find(choices.begin(), choices.end(), value) != choices.end()) { return value; } return std::string{"bar"}; }); program.parse_args({"test", "fez"}); REQUIRE(program.get("input") == "bar"); } TEST_CASE("Users can add actions that return nothing" * test_suite("actions")) { argparse::ArgumentParser program("test"); bool pressed = false; auto &arg = program.add_argument("button").action( [&](const std::string &) mutable { pressed = true; }); REQUIRE_FALSE(pressed); SUBCASE("action performed") { program.parse_args({"test", "ignored"}); REQUIRE(pressed); } SUBCASE("action performed and nothing overrides the default value") { arg.default_value(42); program.parse_args({"test", "ignored"}); REQUIRE(pressed); REQUIRE(program.get("button") == 42); } } class Image { public: int w = 0, h = 0; void resize(std::string_view geometry) { std::stringstream s; s << geometry; s >> w; s.ignore(); s >> h; } static auto create(int w, int h, std::string_view format) -> Image { auto factor = [=] { if (format == "720p") return std::min(1280. / w, 720. / h); else if (format == "1080p") return std::min(1920. / w, 1080. / h); else return 1.; }(); return {static_cast(w * factor), static_cast(h * factor)}; } }; TEST_CASE("Users can bind arguments to actions" * test_suite("actions")) { argparse::ArgumentParser program("test"); GIVEN("an default initialized object bounded by reference") { Image img; program.add_argument("--size").action(&Image::resize, std::ref(img)); WHEN("provided no command-line arguments") { program.parse_args({"test"}); THEN("the object is not updated") { REQUIRE(img.w == 0); REQUIRE(img.h == 0); } } WHEN("provided command-line arguments") { program.parse_args({"test", "--size", "320x98"}); THEN("the object is updated accordingly") { REQUIRE(img.w == 320); REQUIRE(img.h == 98); } } } GIVEN("a command-line option waiting for the last argument in its action") { program.add_argument("format").action(Image::create, 400, 300); WHEN("provided such an argument") { program.parse_args({"test", "720p"}); THEN("the option object is created as if providing all arguments") { auto img = program.get("format"); REQUIRE(img.w == 960); REQUIRE(img.h == 720); } } WHEN("provided a different argument") { program.parse_args({"test", "1080p"}); THEN("a different option object is created") { auto img = program.get("format"); REQUIRE(img.w == 1440); REQUIRE(img.h == 1080); } } } } TEST_CASE("Users can use actions on nargs=ANY arguments" * test_suite("actions")) { argparse::ArgumentParser program("sum"); int result = 0; program.add_argument("all") .nargs(argparse::nargs_pattern::any) .action( [](int &sum, std::string const &value) { sum += std::stoi(value); }, std::ref(result)); program.parse_args({"sum", "42", "100", "-3", "-20"}); REQUIRE(result == 119); } TEST_CASE("Users can use actions on remaining arguments" * test_suite("actions")) { argparse::ArgumentParser program("concat"); std::string result = ""; program.add_argument("all").remaining().action( [](std::string &sum, const std::string &value) { sum += value; }, std::ref(result)); program.parse_args({"concat", "a", "-b", "-c", "--d"}); REQUIRE(result == "a-b-c--d"); } TEST_CASE("Users can run actions on parameterless optional arguments" * test_suite("actions")) { argparse::ArgumentParser program("test"); GIVEN("a flag argument with a counting action") { int count = 0; program.add_argument("-V", "--verbose") .action([&](const auto &) { ++count; }) .append() .default_value(false) .implicit_value(true) .nargs(0); WHEN("the flag is repeated") { program.parse_args({"test", "-VVVV"}); THEN("the count increments once per use") { REQUIRE(program.get("-V")); REQUIRE(count == 4); } } } } TEST_CASE("Users can add multiple actions and they are all run" * test_suite("actions")) { argparse::ArgumentParser program("test"); GIVEN("a flag argument with two counting actions") { int count = 0; program.add_argument("-V", "--verbose") .action([&](const auto &) { ++count; }) .action([&](const auto &) { ++count; }) .append() .default_value(false) .implicit_value(true) .nargs(0); WHEN("the flag is parsed") { program.parse_args({"test", "-V"}); THEN("the count increments twice") { REQUIRE(program.get("-V")); REQUIRE(count == 2); } } } } argparse-3.2/test/test_append.cpp000066400000000000000000000027361474520133400171660ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include #include using doctest::test_suite; TEST_CASE("Simplest .append" * test_suite("append")) { argparse::ArgumentParser program("test"); program.add_argument("--dir").append(); program.parse_args({"test", "--dir", "./Docs"}); std::string result{program.get("--dir")}; REQUIRE(result == "./Docs"); } TEST_CASE("Two parameter .append" * test_suite("append")) { argparse::ArgumentParser program("test"); program.add_argument("--dir").append(); program.parse_args({"test", "--dir", "./Docs", "--dir", "./Src"}); auto result{program.get>("--dir")}; REQUIRE(result.at(0) == "./Docs"); REQUIRE(result.at(1) == "./Src"); } TEST_CASE("Two int .append" * test_suite("append")) { argparse::ArgumentParser program("test"); program.add_argument("--factor").append().scan<'i', int>(); program.parse_args({"test", "--factor", "2", "--factor", "5"}); auto result{program.get>("--factor")}; REQUIRE(result.at(0) == 2); REQUIRE(result.at(1) == 5); } TEST_CASE("Default value with .append" * test_suite("append")) { std::vector expected{"./Src", "./Imgs"}; argparse::ArgumentParser program("test"); program.add_argument("--dir").default_value(expected).append(); program.parse_args({"test"}); auto result{program.get>("--dir")}; REQUIRE(result == expected); } argparse-3.2/test/test_as_container.cpp000066400000000000000000000035641474520133400203640ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include using doctest::test_suite; TEST_CASE("Get argument with .at()" * test_suite("as_container")) { argparse::ArgumentParser program("test"); auto &dir_arg = program.add_argument("--dir"); program.at("--dir").default_value(std::string("/home/user")); SUBCASE("and default value") { program.parse_args({"test"}); REQUIRE(&(program.at("--dir")) == &dir_arg); REQUIRE(program["--dir"] == std::string("/home/user")); REQUIRE(program.at("--dir") == std::string("/home/user")); } SUBCASE("and user-supplied value") { program.parse_args({"test", "--dir", "/usr/local/database"}); REQUIRE(&(program.at("--dir")) == &dir_arg); REQUIRE(program["--dir"] == std::string("/usr/local/database")); REQUIRE(program.at("--dir") == std::string("/usr/local/database")); } SUBCASE("with unknown argument") { program.parse_args({"test"}); REQUIRE_THROWS_WITH_AS(program.at("--folder"), "No such argument: --folder", std::logic_error); } } TEST_CASE("Get subparser with .at()" * test_suite("as_container")) { argparse::ArgumentParser program("test"); argparse::ArgumentParser walk_cmd("walk"); auto &speed = walk_cmd.add_argument("speed"); program.add_subparser(walk_cmd); SUBCASE("and its argument") { program.parse_args({"test", "walk", "4km/h"}); REQUIRE(&(program.at("walk")) == &walk_cmd); REQUIRE(&(program.at("walk").at("speed")) == &speed); REQUIRE(program.at("walk").is_used("speed")); } SUBCASE("with unknown command") { program.parse_args({"test"}); REQUIRE_THROWS_WITH_AS(program.at("fly"), "No such subparser: fly", std::logic_error); } } argparse-3.2/test/test_bool_operator.cpp000066400000000000000000000044461474520133400205650ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include using doctest::test_suite; TEST_CASE("ArgumentParser in bool context" * test_suite("argument_parser")) { argparse::ArgumentParser program("test"); program.add_argument("cases").remaining(); program.parse_args({"test"}); REQUIRE_FALSE(program); program.parse_args({"test", "one", "two"}); REQUIRE(program); } TEST_CASE("With subparsers in bool context" * test_suite("argument_parser")) { argparse::ArgumentParser program("test"); argparse::ArgumentParser cmd_fly("fly"); cmd_fly.add_argument("plane"); argparse::ArgumentParser cmd_soar("soar"); cmd_soar.add_argument("direction"); program.add_subparser(cmd_fly); program.add_subparser(cmd_soar); program.parse_args({"test", "fly", "glider"}); REQUIRE(program); REQUIRE(cmd_fly); REQUIRE_FALSE(cmd_soar); } TEST_CASE("Parsers remain false with unknown arguments" * test_suite("argument_parser")) { argparse::ArgumentParser program("test"); argparse::ArgumentParser cmd_build("build"); cmd_build.add_argument("--file").nargs(1); argparse::ArgumentParser cmd_run("run"); cmd_run.add_argument("--file").nargs(1); program.add_subparser(cmd_build); program.add_subparser(cmd_run); auto unknowns = program.parse_known_args({"test", "badger", "--add-meal", "grubs"}); REQUIRE_FALSE(program); REQUIRE_FALSE(cmd_build); REQUIRE_FALSE(cmd_run); } TEST_CASE("Multi-level parsers match subparser bool" * test_suite("argument_parser")) { argparse::ArgumentParser program("test"); argparse::ArgumentParser cmd_cook("cook"); cmd_cook.add_argument("--temperature"); argparse::ArgumentParser cmd_cook_boil("boil"); cmd_cook_boil.add_argument("--rate"); argparse::ArgumentParser cmd_cook_boil_stir("stir"); cmd_cook_boil_stir.add_argument("--rate"); argparse::ArgumentParser cmd_wash("wash"); program.add_subparser(cmd_cook); cmd_cook.add_subparser(cmd_cook_boil); cmd_cook_boil.add_subparser(cmd_cook_boil_stir); program.add_subparser(cmd_wash); auto unknowns = program.parse_known_args( {"test", "cook", "boil", "stir", "--rate", "fast"}); REQUIRE(program); REQUIRE(cmd_cook); REQUIRE(cmd_cook_boil); REQUIRE(cmd_cook_boil_stir); REQUIRE_FALSE(cmd_wash); } argparse-3.2/test/test_choices.cpp000066400000000000000000000163361474520133400173350ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include using doctest::test_suite; TEST_CASE("Parse argument that is provided zero choices" * test_suite("choices")) { argparse::ArgumentParser program("test"); REQUIRE_THROWS_WITH_AS(program.add_argument("color").choices(), "Zero choices provided", std::runtime_error); } TEST_CASE("Parse argument that is in the fixed number of allowed choices" * test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("color").choices("red", "green", "blue"); program.parse_args({"test", "red"}); } TEST_CASE("Parse argument that is in the fixed number of allowed choices, with " "other positional argument" * test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("--input") .default_value(std::string{"baz"}) .choices("foo", "bar", "baz"); program.add_argument("--value").scan<'i', int>().default_value(0); REQUIRE_NOTHROW( program.parse_args({"test", "--input", "foo", "--value", "1"})); REQUIRE(program.get("--input") == "foo"); REQUIRE(program.get("--value") == 1); } TEST_CASE( "Parse nargs argument that is in the fixed number of allowed choices, with " "other positional argument" * test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("--input") .default_value(std::string{"baz"}) .choices("foo", "bar", "baz") .nargs(2); program.add_argument("--value").scan<'i', int>().default_value(0); REQUIRE_NOTHROW( program.parse_args({"test", "--input", "foo", "bar", "--value", "1"})); REQUIRE((program.get>("--input") == std::vector{"foo", "bar"})); REQUIRE(program.get("--value") == 1); } TEST_CASE("Parse argument that is in the fixed number of allowed choices, with " "other positional argument (reversed)" * test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("--input") .default_value(std::string{"baz"}) .choices("foo", "bar", "baz"); program.add_argument("--value").scan<'i', int>().default_value(0); REQUIRE_NOTHROW( program.parse_args({"test", "--value", "1", "--input", "foo"})); REQUIRE(program.get("--input") == "foo"); REQUIRE(program.get("--value") == 1); } TEST_CASE( "Parse nargs argument that is in the fixed number of allowed choices, with " "other positional argument (reversed)" * test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("--input") .default_value(std::string{"baz"}) .choices("foo", "bar", "baz") .nargs(2); program.add_argument("--value").scan<'i', int>().default_value(0); REQUIRE_NOTHROW( program.parse_args({"test", "--value", "1", "--input", "foo", "bar"})); REQUIRE((program.get>("--input") == std::vector{"foo", "bar"})); REQUIRE(program.get("--value") == 1); } TEST_CASE("Parse argument that is in the fixed number of allowed choices, with " "invalid default" * test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("color").default_value("yellow").choices("red", "green", "blue"); REQUIRE_THROWS_WITH_AS( program.parse_args({"test"}), "Invalid default value \"yellow\" - allowed options: {red, green, blue}", std::runtime_error); } TEST_CASE("Parse invalid argument that is not in the fixed number of allowed " "choices" * test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("color").choices("red", "green", "blue"); REQUIRE_THROWS_WITH_AS( program.parse_args({"test", "red2"}), "Invalid argument \"red2\" - allowed options: {red, green, blue}", std::runtime_error); } TEST_CASE( "Parse multiple arguments that are in the fixed number of allowed choices" * test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("color").nargs(2).choices("red", "green", "blue"); program.parse_args({"test", "red", "green"}); } TEST_CASE("Parse multiple arguments one of which is not in the fixed number of " "allowed choices" * test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("color").nargs(2).choices("red", "green", "blue"); REQUIRE_THROWS_WITH_AS( program.parse_args({"test", "red", "green2"}), "Invalid argument \"green2\" - allowed options: {red, green, blue}", std::runtime_error); } TEST_CASE("Parse multiple arguments that are in the fixed number of allowed " "INTEGER choices" * test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("indices").nargs(2).choices(1, 2, 3, 4, 5); program.parse_args({"test", "1", "2"}); } TEST_CASE("Parse multiple arguments that are not in fixed number of allowed " "INTEGER choices" * test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("indices").nargs(2).choices(1, 2, 3, 4, 5); REQUIRE_THROWS_WITH_AS( program.parse_args({"test", "6", "7"}), "Invalid argument \"6\" - allowed options: {1, 2, 3, 4, 5}", std::runtime_error); } TEST_CASE("Parse multiple arguments that are in range of allowed " "INTEGER choices (Min Range case)" * test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("indices").nargs(1, 3).choices(1, 2, 3, 4, 5); REQUIRE_NOTHROW(program.parse_args({"test", "1"})); REQUIRE(program.get>("indices") == std::vector{"1"}); } TEST_CASE("Parse multiple arguments that are in range of allowed choices (In " "Range case)" * test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("--foo"); program.add_argument("--bar").nargs(1, 3).choices("a", "b", "c"); REQUIRE_NOTHROW( program.parse_args({"test", "--bar", "a", "b", "--foo", "x"})); REQUIRE(program.get>("--bar") == std::vector{"a", "b"}); REQUIRE(program.get("--foo") == "x"); } TEST_CASE("Parse multiple arguments that are in range of allowed " "INTEGER choices (Max Range case)" * test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("indices").nargs(2, 3).choices(1, 2, 3, 4, 5); REQUIRE_NOTHROW(program.parse_args({"test", "3", "4", "5"})); REQUIRE(program.get>("indices") == std::vector{"3", "4", "5"}); } TEST_CASE("Parse multiple arguments that are not in range of allowed choices" * test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("--foo"); program.add_argument("--bar").nargs(1, 3).choices("a", "b", "c"); REQUIRE_THROWS_WITH_AS( program.parse_args({"test", "--bar", "d", "--foo", "x"}), "Invalid argument \"d\" - allowed options: {a, b, c}", std::runtime_error); } argparse-3.2/test/test_compound_arguments.cpp000066400000000000000000000065651474520133400216340ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include using doctest::test_suite; TEST_CASE("Parse compound toggle arguments with implicit values" * test_suite("compound_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("-a").flag(); program.add_argument("-u").flag(); program.add_argument("-x").flag(); program.parse_args({"./test.exe", "-aux"}); REQUIRE(program.get("-a") == true); REQUIRE(program.get("-u") == true); REQUIRE(program.get("-x") == true); } TEST_CASE("Parse compound toggle arguments with implicit values and nargs" * test_suite("compound_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("-a").flag(); program.add_argument("-b").flag(); program.add_argument("-c").nargs(2).scan<'g', float>(); program.add_argument("--input_files").nargs(3); program.parse_args({"./test.exe", "-abc", "3.14", "2.718", "--input_files", "a.txt", "b.txt", "c.txt"}); REQUIRE(program.get("-a") == true); REQUIRE(program.get("-b") == true); auto c = program.get>("-c"); REQUIRE(c.size() == 2); REQUIRE(c[0] == 3.14f); REQUIRE(c[1] == 2.718f); auto input_files = program.get>("--input_files"); REQUIRE(input_files.size() == 3); REQUIRE(input_files[0] == "a.txt"); REQUIRE(input_files[1] == "b.txt"); REQUIRE(input_files[2] == "c.txt"); } TEST_CASE("Parse compound toggle arguments with implicit values and nargs and " "other positional arguments" * test_suite("compound_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("numbers").nargs(3).scan<'i', int>(); program.add_argument("-a").flag(); program.add_argument("-b").flag(); program.add_argument("-c").nargs(2).scan<'g', float>(); program.add_argument("--input_files").nargs(3); REQUIRE_THROWS( program.parse_args({"./test.exe", "1", "-abc", "3.14", "2.718", "2", "--input_files", "a.txt", "b.txt", "c.txt", "3"})); } TEST_CASE("Parse out-of-order compound arguments" * test_suite("compound_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("-a").flag(); program.add_argument("-b").flag(); program.add_argument("-c").nargs(2).scan<'g', float>(); program.parse_args({"./main", "-cab", "3.14", "2.718"}); auto a = program.get("-a"); // true auto b = program.get("-b"); // true auto c = program.get>("-c"); // {3.14f, 2.718f} REQUIRE(a == true); REQUIRE(b == true); REQUIRE(program["-c"] == std::vector{3.14f, 2.718f}); } TEST_CASE("Parse out-of-order compound arguments. Second variation" * test_suite("compound_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("-a").flag(); program.add_argument("-b").flag(); program.add_argument("-c") .nargs(2) .default_value(std::vector{0.0f, 0.0f}) .scan<'g', float>(); program.parse_args({"./main", "-cb"}); auto a = program.get("-a"); auto b = program.get("-b"); auto c = program.get>("-c"); REQUIRE(a == false); REQUIRE(b == true); REQUIRE(program["-c"] == std::vector{0.0f, 0.0f}); } argparse-3.2/test/test_container_arguments.cpp000066400000000000000000000052601474520133400217610ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include using doctest::test_suite; TEST_CASE("Parse vector of arguments" * test_suite("vector")) { argparse::ArgumentParser program("test"); program.add_argument("input").nargs(2); program.parse_args({"test", "rocket.mesh", "thrust_profile.csv"}); auto inputs = program.get>("input"); REQUIRE(inputs.size() == 2); REQUIRE(inputs[0] == "rocket.mesh"); REQUIRE(inputs[1] == "thrust_profile.csv"); } TEST_CASE("Parse list of arguments" * test_suite("vector")) { argparse::ArgumentParser program("test"); program.add_argument("input").nargs(2); program.parse_args({"test", "rocket.mesh", "thrust_profile.csv"}); auto inputs = program.get>("input"); REQUIRE(inputs.size() == 2); REQUIRE(testutility::get_from_list(inputs, 0) == "rocket.mesh"); REQUIRE(testutility::get_from_list(inputs, 1) == "thrust_profile.csv"); } TEST_CASE("Parse list of arguments with default values" * test_suite("vector")) { argparse::ArgumentParser program("test"); program.add_argument("--input") .default_value(std::list{1, 2, 3, 4, 5}) .nargs(5); program.parse_args({"test"}); auto inputs = program.get>("--input"); REQUIRE(inputs.size() == 5); REQUIRE(testutility::get_from_list(inputs, 0) == 1); REQUIRE(testutility::get_from_list(inputs, 1) == 2); REQUIRE(testutility::get_from_list(inputs, 2) == 3); REQUIRE(testutility::get_from_list(inputs, 3) == 4); REQUIRE(testutility::get_from_list(inputs, 4) == 5); REQUIRE(program["--input"] == std::list{1, 2, 3, 4, 5}); } TEST_CASE("Parse list of arguments and save in an object" * test_suite("vector")) { struct ConfigManager { std::vector files; void add_file(const std::string &file) { files.push_back(file); } }; ConfigManager config_manager; argparse::ArgumentParser program("test"); program.add_argument("--input_files") .nargs(2) .action([&](const std::string &value) { config_manager.add_file(value); return value; }); program.parse_args({"test", "--input_files", "config.xml", "system.json"}); auto file_args = program.get>("--input_files"); REQUIRE(file_args.size() == 2); REQUIRE(file_args[0] == "config.xml"); REQUIRE(file_args[1] == "system.json"); REQUIRE(config_manager.files.size() == 2); REQUIRE(config_manager.files[0] == "config.xml"); REQUIRE(config_manager.files[1] == "system.json"); REQUIRE(program["--input_files"] == std::vector{"config.xml", "system.json"}); } argparse-3.2/test/test_default_args.cpp000066400000000000000000000023551474520133400203540ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include #include #include using doctest::test_suite; TEST_CASE("Include all default arguments" * test_suite("default_args")) { argparse::ArgumentParser parser("test"); auto help_msg{parser.help().str()}; REQUIRE(help_msg.find("shows help message") != std::string::npos); REQUIRE(help_msg.find("prints version information") != std::string::npos); } TEST_CASE("Do not include default arguments" * test_suite("default_args")) { argparse::ArgumentParser parser("test", "1.0", argparse::default_arguments::none); parser.parse_args({"test"}); REQUIRE_THROWS_AS(parser.get("--help"), std::logic_error); REQUIRE_THROWS_AS(parser.get("--version"), std::logic_error); } TEST_CASE("Do not exit on default arguments" * test_suite("default_args")) { argparse::ArgumentParser parser("test", "1.0", argparse::default_arguments::all, false); std::stringstream buf; std::streambuf *saved_cout_buf = std::cout.rdbuf(buf.rdbuf()); parser.parse_args({"test", "--help"}); std::cout.rdbuf(saved_cout_buf); REQUIRE(parser.is_used("--help")); } argparse-3.2/test/test_default_value.cpp000066400000000000000000000056031474520133400205330ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include using doctest::test_suite; TEST_CASE("Use a 'string' default value" * test_suite("default_value")) { argparse::ArgumentParser program("test"); SUBCASE("Use a const char[] default value") { program.add_argument("--arg").default_value("array of char"); REQUIRE_NOTHROW(program.parse_args({"test"})); REQUIRE(program.get("--arg") == std::string("array of char")); } SUBCASE("Use a std::string default value") { program.add_argument("--arg").default_value(std::string("string object")); REQUIRE_NOTHROW(program.parse_args({"test"})); REQUIRE(program.get("--arg") == std::string("string object")); } } TEST_CASE("Use a default value with flag arguments" * test_suite("default_value")) { argparse::ArgumentParser program("test"); program.add_argument("-inc_chr", "--include_chromes") .help(std::string{"only process the anchor whose one of the end is " "contained on the specified " "chromatin, used ',' to split."}) .default_value("all"); program.add_argument("-l").flag(); program.add_argument("-o").flag(); program.add_argument("filename"); SUBCASE("Leading optional argument with default_value") { REQUIRE_NOTHROW(program.parse_args({"test", "-inc_chr", "-lo", "my.log"})); REQUIRE(program.get("-inc_chr") == std::string{"all"}); } SUBCASE("Trailing optional argument with default_value") { REQUIRE_NOTHROW(program.parse_args({"test", "-lo", "my.log", "-inc_chr"})); REQUIRE(program.get("-inc_chr") == std::string{"all"}); } } TEST_CASE("Position of the argument with default value") { argparse::ArgumentParser program("test"); program.add_argument("-g").default_value("the_default_value"); program.add_argument("-s"); SUBCASE("Arg with default value not passed") { REQUIRE_NOTHROW(program.parse_args({"test", "-s", "./src"})); REQUIRE(program.get("-g") == std::string("the_default_value")); REQUIRE(program.get("-s") == std::string("./src")); } SUBCASE("Arg with default value passed last") { REQUIRE_NOTHROW(program.parse_args({"test", "-s", "./src", "-g"})); REQUIRE(program.get("-g") == std::string("the_default_value")); REQUIRE(program.get("-s") == std::string("./src")); } SUBCASE("Arg with default value passed before last") { REQUIRE_NOTHROW(program.parse_args({"test", "-g", "-s", "./src"})); REQUIRE(program.get("-g") == std::string("the_default_value")); REQUIRE(program.get("-s") == std::string("./src")); } SUBCASE("Arg with default value replaces the value if given") { REQUIRE_NOTHROW( program.parse_args({"test", "-g", "a_different_value", "-s", "./src"})); REQUIRE(program.get("-g") == std::string("a_different_value")); REQUIRE(program.get("-s") == std::string("./src")); } }argparse-3.2/test/test_equals_form.cpp000066400000000000000000000030201474520133400202170ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include #include #include using doctest::test_suite; TEST_CASE("Basic --value=value" * test_suite("equals_form")) { argparse::ArgumentParser parser("test"); parser.add_argument("--long"); parser.parse_args({"test", "--long=value"}); std::string result{parser.get("--long")}; REQUIRE(result == "value"); } TEST_CASE("Fallback = in regular option name" * test_suite("equals_form")) { argparse::ArgumentParser parser("test"); parser.add_argument("--long=mislead"); parser.parse_args({"test", "--long=mislead", "value"}); std::string result{parser.get("--long=mislead")}; REQUIRE(result == "value"); } TEST_CASE("Duplicate =-named and standard" * test_suite("equals_form")) { argparse::ArgumentParser parser("test"); parser.add_argument("--long=mislead"); parser.add_argument("--long").default_value(std::string{"NO_VALUE"}); parser.parse_args({"test", "--long=mislead", "value"}); std::string result{parser.get("--long=mislead")}; REQUIRE(result == "value"); std::string result2{parser.get("--long")}; REQUIRE(result2 == "NO_VALUE"); } TEST_CASE("Basic --value=value with nargs(2)" * test_suite("equals_form")) { argparse::ArgumentParser parser("test"); parser.add_argument("--long").nargs(2); parser.parse_args({"test", "--long=value1", "value2"}); REQUIRE((parser.get>("--long") == std::vector{"value1", "value2"})); } argparse-3.2/test/test_error_reporting.cpp000066400000000000000000000073661474520133400211450ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include #include #include using doctest::test_suite; TEST_CASE("Missing optional argument name" * test_suite("error_reporting")) { argparse::ArgumentParser parser("test"); parser.add_argument("-a"); parser.add_argument("-b"); SUBCASE("Good case") { REQUIRE_NOTHROW(parser.parse_args({"test", "-a", "1", "-b", "2"})); } SUBCASE("Bad case") { REQUIRE_THROWS_WITH_AS( parser.parse_args({"test", "-a", "1", "2"}), "Zero positional arguments expected, did you mean -b VAR", std::runtime_error); } SUBCASE("Bad case 2") { REQUIRE_THROWS_WITH_AS( parser.parse_args({"test", "1", "2"}), "Zero positional arguments expected, did you mean -a VAR", std::runtime_error); } } TEST_CASE("Missing optional argument name (some flag arguments)" * test_suite("error_reporting")) { argparse::ArgumentParser parser("test"); parser.add_argument("-a").flag(); parser.add_argument("-b").flag(); parser.add_argument("-c"); parser.add_argument("-d"); SUBCASE("Good case") { REQUIRE_NOTHROW(parser.parse_args({"test", "-a", "-b", "-c", "2"})); } SUBCASE("Bad case") { REQUIRE_THROWS_WITH_AS( parser.parse_args({"test", "-a", "-b", "2"}), "Zero positional arguments expected, did you mean -c VAR", std::runtime_error); } SUBCASE("Bad case 2") { REQUIRE_THROWS_WITH_AS( parser.parse_args({"test", "-abc", "1", "2"}), "Zero positional arguments expected, did you mean -d VAR", std::runtime_error); } } TEST_CASE("Missing optional argument name (multiple names)" * test_suite("error_reporting")) { argparse::ArgumentParser parser("test"); parser.add_argument("-a", "--number-of-apples"); parser.add_argument("-b"); SUBCASE("Bad case 2") { REQUIRE_THROWS_WITH_AS(parser.parse_args({"test", "1", "2"}), "Zero positional arguments expected, did you mean " "-a/--number-of-apples VAR", std::runtime_error); } } TEST_CASE("Missing optional argument name with other positional arguments" * test_suite("error_reporting")) { argparse::ArgumentParser parser("test"); parser.add_argument("-a"); parser.add_argument("-b"); parser.add_argument("c"); SUBCASE("Good case") { REQUIRE_NOTHROW(parser.parse_args({"test", "-a", "1", "-b", "2", "3"})); } SUBCASE("Bad case") { REQUIRE_THROWS_WITH_AS( parser.parse_args({"test", "-a", "1", "2", "3", "4"}), "Maximum number of positional arguments exceeded, failed to parse '3'", std::runtime_error); } } TEST_CASE("Detect unknown subcommand" * test_suite("error_reporting")) { argparse::ArgumentParser program("git"); argparse::ArgumentParser log_command("log"); argparse::ArgumentParser notes_command("notes"); argparse::ArgumentParser add_command("add"); program.add_subparser(log_command); program.add_subparser(notes_command); program.add_subparser(add_command); SUBCASE("Typo for 'notes'") { REQUIRE_THROWS_WITH_AS(program.parse_args({"git", "tote"}), "Failed to parse 'tote', did you mean 'notes'", std::runtime_error); } SUBCASE("Typo for 'add'") { REQUIRE_THROWS_WITH_AS(program.parse_args({"git", "bad"}), "Failed to parse 'bad', did you mean 'add'", std::runtime_error); } SUBCASE("Typo for 'log'") { REQUIRE_THROWS_WITH_AS(program.parse_args({"git", "logic"}), "Failed to parse 'logic', did you mean 'log'", std::runtime_error); } }argparse-3.2/test/test_get.cpp000066400000000000000000000033341474520133400164710ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include using doctest::test_suite; TEST_CASE("Getting a simple argument" * test_suite("ArgumentParser::get")) { argparse::ArgumentParser program("test"); program.add_argument("-s", "--stuff"); REQUIRE_NOTHROW(program.parse_args({"test", "-s", "./src"})); REQUIRE(program.get("--stuff") == "./src"); } TEST_CASE("Skipped call to parse_args" * test_suite("ArgumentParser::get")) { argparse::ArgumentParser program("test"); program.add_argument("stuff"); REQUIRE_THROWS_WITH_AS(program.get("stuff"), "Nothing parsed, no arguments are available.", std::logic_error); } TEST_CASE("Missing argument" * test_suite("ArgumentParser::get")) { argparse::ArgumentParser program("test"); program.add_argument("-s", "--stuff"); REQUIRE_NOTHROW(program.parse_args({"test"})); REQUIRE_THROWS_WITH_AS(program.get("--stuff"), "No value provided for '--stuff'.", std::logic_error); } TEST_CASE("Implicit argument" * test_suite("ArgumentParser::get")) { argparse::ArgumentParser program("test"); program.add_argument("-s", "--stuff").nargs(1); REQUIRE_NOTHROW(program.parse_args({"test"})); REQUIRE_THROWS_WITH_AS(program.get("--stuff"), "No value provided for '--stuff'.", std::logic_error); } TEST_CASE("Mismatched type for argument" * test_suite("ArgumentParser::get")) { argparse::ArgumentParser program("test"); program.add_argument("-s", "--stuff"); // as default type, a std::string REQUIRE_NOTHROW(program.parse_args({"test", "-s", "321"})); REQUIRE_THROWS_AS(program.get("--stuff"), std::bad_any_cast); } argparse-3.2/test/test_help.cpp000066400000000000000000000227111474520133400166420ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include #include using doctest::test_suite; TEST_CASE("Users can format help message" * test_suite("help")) { argparse::ArgumentParser program("test"); SUBCASE("Simple arguments") { program.add_argument("input").help("positional input"); program.add_argument("-c").help("optional input"); } SUBCASE("Default values") { program.add_argument("-a").default_value(42); program.add_argument("-b").default_value(4.4e-7); program.add_argument("-c") .default_value(std::vector{1, 2, 3, 4, 5}) .nargs(5); program.add_argument("-d").default_value("I am a string"); program.add_argument("-e").default_value(std::optional{}); program.add_argument("-f").default_value(false); } std::ostringstream s; s << program; REQUIRE_FALSE(s.str().empty()); auto msg = program.help().str(); REQUIRE(msg == s.str()); } TEST_CASE("Users can override the help options" * test_suite("help")) { GIVEN("a program that meant to take -h as a normal option") { argparse::ArgumentParser program("test"); program.add_argument("input"); program.add_argument("-h").implicit_value('h').default_value('x'); WHEN("provided -h without fulfilling other requirements") { THEN("validation fails") { REQUIRE_THROWS_AS(program.parse_args({"test", "-h"}), std::runtime_error); } } WHEN("provided arguments to all parameters") { program.parse_args({"test", "-h", "some input"}); THEN("these parameters get their values") { REQUIRE(program["-h"] == 'h'); REQUIRE(program.get("input") == "some input"); } } } } TEST_CASE("Users can disable default -h/--help" * test_suite("help")) { argparse::ArgumentParser program("test", "1.0", argparse::default_arguments::version); REQUIRE_THROWS_AS(program.parse_args({"test", "-h"}), std::runtime_error); } TEST_CASE("Users can replace default -h/--help" * test_suite("help")) { argparse::ArgumentParser program("test", "1.0", argparse::default_arguments::version); std::stringstream buffer; program.add_argument("-h", "--help") .action([&](const auto &) { buffer << program; }) .default_value(false) .implicit_value(true) .nargs(0); REQUIRE(buffer.str().empty()); program.parse_args({"test", "--help"}); REQUIRE_FALSE(buffer.str().empty()); } TEST_CASE("Multiline help message alignment") { // '#' is used at the beginning of each help message line to simplify testing. // It is important to ensure that this character doesn't appear elsewhere in // the test case. Default arguments (e.g., -h/--help, -v/--version) are not // included in this test. argparse::ArgumentParser program("program"); program.add_argument("INPUT1").help( "#This is the first line of help message.\n" "#And this is the second line of help message."); program.add_argument("program_input2").help("#There is only one line."); program.add_argument("-p", "--prog_input3") .help( R"(#Lorem ipsum dolor sit amet, consectetur adipiscing elit. #Sed ut perspiciatis unde omnis iste natus error sit voluptatem #accusantium doloremque laudantium, totam rem aperiam...)"); program.add_argument("--verbose").flag(); std::ostringstream stream; stream << program; std::istringstream iss(stream.str()); auto help_message_start = std::string::npos; std::string line; while (std::getline(iss, line)) { // Find the position of '#', which indicates the start of the help message // line auto pos = line.find('#'); if (pos == std::string::npos) { continue; } if (help_message_start == std::string::npos) { help_message_start = pos; } else { REQUIRE(pos == help_message_start); } } // Make sure we have at least one help message REQUIRE(help_message_start != -1); } TEST_CASE("Exclusive arguments, only") { argparse::ArgumentParser program("program"); auto &group = program.add_mutually_exclusive_group(); group.add_argument("-a").flag(); group.add_argument("-b").flag(); REQUIRE(program.usage() == "Usage: program [--help] [--version] [[-a]|[-b]]"); } TEST_CASE("Exclusive arguments, several groups") { argparse::ArgumentParser program("program"); auto &group = program.add_mutually_exclusive_group(); group.add_argument("-a").flag(); group.add_argument("-b").flag(); auto &group2 = program.add_mutually_exclusive_group(); group2.add_argument("-c").flag(); group2.add_argument("-d").flag(); REQUIRE(program.usage() == "Usage: program [--help] [--version] [[-a]|[-b]] [[-c]|[-d]]"); } TEST_CASE("Exclusive arguments, several groups, in between arg") { argparse::ArgumentParser program("program"); auto &group = program.add_mutually_exclusive_group(); group.add_argument("-a").flag(); group.add_argument("-b").flag(); program.add_argument("-X").flag(); auto &group2 = program.add_mutually_exclusive_group(); group2.add_argument("-c").flag(); group2.add_argument("-d").flag(); REQUIRE(program.usage() == "Usage: program [--help] [--version] [[-a]|[-b]] [-X] [[-c]|[-d]]"); } TEST_CASE("Argument repeatable") { argparse::ArgumentParser program("program"); program.add_argument("-a").flag().append(); REQUIRE(program.usage() == "Usage: program [--help] [--version] [-a]..."); std::ostringstream s; s << program; // std::cout << "DEBUG:" << s.str() << std::endl; REQUIRE(s.str().find(" -a [may be repeated]") != std::string::npos); } TEST_CASE("Argument with nargs(2) and metavar ") { argparse::ArgumentParser program("program"); program.add_argument("-foo").metavar(" ").nargs(2); REQUIRE(program.usage() == "Usage: program [--help] [--version] [-foo ]"); } TEST_CASE("add_group help") { argparse::ArgumentParser program("program"); program.add_argument("-a").flag().help("help_a"); program.add_group("Advanced options"); program.add_argument("-b").flag().help("help_b"); REQUIRE(program.usage() == "Usage: program [--help] [--version] [-a] [-b]"); std::ostringstream s; s << program; // std::cout << "DEBUG:" << s.str() << std::endl; REQUIRE(s.str().find( " -a help_a \n" "\n" "Advanced options (detailed usage):\n" " -b help_b") != std::string::npos); } TEST_CASE("multiline usage, several groups") { argparse::ArgumentParser program("program"); program.set_usage_max_line_width(80); program.add_argument("-a").flag().help("help_a"); program.add_group("Advanced options"); program.add_argument("-b").flag().help("help_b"); // std::cout << "DEBUG:" << program.usage() << std::endl; REQUIRE(program.usage() == "Usage: program [--help] [--version] [-a]\n" "\n" "Advanced options:\n" " [-b]"); } TEST_CASE("multiline usage, no break on mutex") { argparse::ArgumentParser program("program"); program.set_usage_max_line_width(80); program.set_usage_break_on_mutex(); program.add_argument("--quite-long-option-name").flag(); auto &group = program.add_mutually_exclusive_group(); group.add_argument("-a").flag(); group.add_argument("-b").flag(); program.add_argument("-c").flag(); program.add_argument("--another-one").flag(); program.add_argument("-d").flag(); program.add_argument("--yet-another-long-one").flag(); program.add_argument("--will-go-on-new-line").flag(); // std::cout << "DEBUG:" << program.usage() << std::endl; REQUIRE(program.usage() == "Usage: program [--help] [--version] [--quite-long-option-name]\n" " [[-a]|[-b]]\n" " [-c] [--another-one] [-d] [--yet-another-long-one]\n" " [--will-go-on-new-line]"); } TEST_CASE("multiline usage, break on mutex") { argparse::ArgumentParser program("program"); program.set_usage_max_line_width(80); program.add_argument("--quite-long-option-name").flag(); auto &group = program.add_mutually_exclusive_group(); group.add_argument("-a").flag(); group.add_argument("-b").flag(); program.add_argument("-c").flag(); program.add_argument("--another-one").flag(); program.add_argument("-d").flag(); program.add_argument("--yet-another-long-one").flag(); program.add_argument("--will-go-on-new-line").flag(); program.add_usage_newline(); program.add_argument("--on-a-dedicated-line").flag(); // std::cout << "DEBUG:" << program.usage() << std::endl; REQUIRE(program.usage() == "Usage: program [--help] [--version] [--quite-long-option-name] [[-a]|[-b]] [-c]\n" " [--another-one] [-d] [--yet-another-long-one]\n" " [--will-go-on-new-line]\n" " [--on-a-dedicated-line]"); } TEST_CASE("multiline usage, single arg that is larger than the max width") { argparse::ArgumentParser program("program"); program.set_usage_max_line_width(80); program.add_argument("--lots-of-choices").metavar(""); // std::cout << "DEBUG:" << program.usage() << std::endl; REQUIRE(program.usage() == "Usage: program [--help] [--version]\n" " [--lots-of-choices ]"); } argparse-3.2/test/test_hidden_alias.cpp000066400000000000000000000010011474520133400203030ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include using doctest::test_suite; TEST_CASE("Test setting a hidden alias for an argument" * test_suite("hidden_alias")) { argparse::ArgumentParser program("test"); auto &arg = program.add_argument("--suppress").flag(); program.add_hidden_alias_for(arg, "--supress"); // old misspelled alias program.parse_args({"./test.exe", "--supress"}); REQUIRE(program.get("--suppress") == true); } argparse-3.2/test/test_hidden_argument.cpp000066400000000000000000000024641474520133400210520ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include using doctest::test_suite; TEST_CASE("Test setting a hidden argument" * test_suite("hidden_argument")) { argparse::ArgumentParser program("program"); program.add_argument("--hidden").flag().hidden(); program.add_argument("--regular").flag(); program.add_argument("regular_positional"); // only makes sense if last and optional... program.add_argument("hidden_positional").nargs(0, 1).hidden(); program.parse_args({"./test.exe", "--hidden", "--regular", "regular_positional_val", "hidden_positional_val"}); REQUIRE(program.get("--hidden") == true); REQUIRE(program.get("--regular") == true); REQUIRE(program.get("regular_positional") == "regular_positional_val"); REQUIRE(program.get("hidden_positional") == "hidden_positional_val"); REQUIRE(program.usage() == "Usage: program [--help] [--version] [--regular] regular_positional"); std::ostringstream s; s << program; // std::cout << "DEBUG:" << s.str() << std::endl; REQUIRE(s.str().find("hidden") == std::string::npos); REQUIRE(s.str().find("--regular") != std::string::npos); REQUIRE(s.str().find("regular_positional") != std::string::npos); } argparse-3.2/test/test_invalid_arguments.cpp000066400000000000000000000022701474520133400214230ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include using doctest::test_suite; TEST_CASE("Parse unknown optional argument" * test_suite("compound_arguments")) { argparse::ArgumentParser bfm("bfm"); bfm.add_argument("-l", "--load").help("load a VMM into the kernel"); bfm.add_argument("-x", "--start") .default_value(false) .implicit_value(true) .help("start a previously loaded VMM"); bfm.add_argument("-d", "--dump") .default_value(false) .implicit_value(true) .help("output the contents of the VMM's debug buffer"); bfm.add_argument("-s", "--stop") .default_value(false) .implicit_value(true) .help("stop a previously started VMM"); bfm.add_argument("-u", "--unload") .default_value(false) .implicit_value(true) .help("unload a previously loaded VMM"); bfm.add_argument("-m", "--mem") .default_value(64ULL) .scan<'u', unsigned long long>() .help("memory in MB to give the VMM when loading"); REQUIRE_THROWS_WITH_AS(bfm.parse_args({"./test.exe", "-om"}), "Unknown argument: -om", std::runtime_error); } argparse-3.2/test/test_is_used.cpp000066400000000000000000000013601474520133400173420ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include using doctest::test_suite; TEST_CASE("User-supplied argument" * test_suite("is_used")) { argparse::ArgumentParser program("test"); program.add_argument("--dir").default_value(std::string("/")); program.parse_args({"test", "--dir", "/home/user"}); REQUIRE(program.get("--dir") == "/home/user"); REQUIRE(program.is_used("--dir") == true); } TEST_CASE("Not user-supplied argument" * test_suite("is_used")) { argparse::ArgumentParser program("test"); program.add_argument("--dir").default_value(std::string("/")); program.parse_args({"test"}); REQUIRE(program.get("--dir") == "/"); REQUIRE(program.is_used("--dir") == false); } argparse-3.2/test/test_issue_37.cpp000066400000000000000000000025701474520133400173540ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include using doctest::test_suite; TEST_CASE("Issues with implicit values #37" * test_suite("implicit_values")) { argparse::ArgumentParser m_bfm("test"); m_bfm.add_argument("-l", "--load").help("load a VMM into the kernel"); m_bfm.add_argument("-u", "--unload") .default_value(false) .implicit_value(true) .help("unload a previously loaded VMM"); m_bfm.add_argument("-x", "--start") .default_value(false) .implicit_value(true) .help("start a previously loaded VMM"); m_bfm.add_argument("-s", "--stop") .default_value(false) .implicit_value(true) .help("stop a previously started VMM"); m_bfm.add_argument("-d", "--dump") .default_value(false) .implicit_value(true) .help("output the contents of the VMM's debug buffer"); m_bfm.add_argument("-m", "--mem") .default_value(100) .required() .scan<'u', unsigned long long>() .help("memory in MB to give the VMM when loading"); m_bfm.parse_args({"test", "-l", "blah", "-d", "-u"}); REQUIRE(m_bfm.get("--load") == "blah"); REQUIRE(m_bfm.get("-l") == "blah"); REQUIRE(m_bfm.get("-u") == true); REQUIRE(m_bfm.get("-d") == true); REQUIRE(m_bfm.get("-s") == false); REQUIRE(m_bfm.get("--unload") == true); } argparse-3.2/test/test_mutually_exclusive_group.cpp000066400000000000000000000052031474520133400230660ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include using doctest::test_suite; TEST_CASE("Create mutually exclusive group with 2 arguments" * test_suite("mutex_args")) { argparse::ArgumentParser program("test"); auto &group = program.add_mutually_exclusive_group(); group.add_argument("--first"); group.add_argument("--second"); REQUIRE_THROWS_WITH_AS( program.parse_args({"test", "--first", "1", "--second", "2"}), "Argument '--second VAR' not allowed with '--first VAR'", std::runtime_error); } TEST_CASE( "Create mutually exclusive group with 2 arguments with required flag" * test_suite("mutex_args")) { argparse::ArgumentParser program("test"); auto &group = program.add_mutually_exclusive_group(true); group.add_argument("--first"); group.add_argument("--second"); REQUIRE_THROWS_WITH_AS( program.parse_args({"test"}), "One of the arguments '--first VAR' or '--second VAR' is required", std::runtime_error); } TEST_CASE( "Create mutually exclusive group with 3 arguments with required flag" * test_suite("mutex_args")) { argparse::ArgumentParser program("test"); auto &group = program.add_mutually_exclusive_group(true); group.add_argument("--first"); group.add_argument("--second"); group.add_argument("--third"); REQUIRE_THROWS_WITH_AS(program.parse_args({"test"}), "One of the arguments '--first VAR' or '--second VAR' " "or '--third VAR' is required", std::runtime_error); } TEST_CASE("Create mutually exclusive group with 3 arguments" * test_suite("mutex_args")) { argparse::ArgumentParser program("test"); auto &group = program.add_mutually_exclusive_group(); group.add_argument("--first"); group.add_argument("--second"); group.add_argument("--third"); REQUIRE_THROWS_WITH_AS( program.parse_args({"test", "--first", "1", "--third", "2"}), "Argument '--third VAR' not allowed with '--first VAR'", std::runtime_error); } TEST_CASE("Create two mutually exclusive groups" * test_suite("mutex_args")) { argparse::ArgumentParser program("test"); auto &group_1 = program.add_mutually_exclusive_group(); group_1.add_argument("--first"); group_1.add_argument("--second"); group_1.add_argument("--third"); auto &group_2 = program.add_mutually_exclusive_group(); group_2.add_argument("-a"); group_2.add_argument("-b"); REQUIRE_THROWS_WITH_AS( program.parse_args({"test", "--first", "1", "-a", "2", "-b", "3"}), "Argument '-b VAR' not allowed with '-a VAR'", std::runtime_error); }argparse-3.2/test/test_negative_numbers.cpp000066400000000000000000000227121474520133400212500ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include using doctest::test_suite; TEST_CASE("Parse negative integer" * test_suite("positional_arguments")) { argparse::ArgumentParser program; program.add_argument("--verbose", "-v") .help("enable verbose logging") .default_value(false) .implicit_value(true); program.add_argument("number").help("Input number").scan<'i', int>(); program.parse_args({"./main", "-1"}); REQUIRE(program.get("number") == -1); } TEST_CASE("Parse negative integers into a vector" * test_suite("positional_arguments")) { argparse::ArgumentParser program; program.add_argument("--verbose", "-v") .help("enable verbose logging") .default_value(false) .implicit_value(true); program.add_argument("number").help("Input number").nargs(3).scan<'i', int>(); program.parse_args({"./main", "-1", "-2", "3"}); REQUIRE(program["number"] == std::vector{-1, -2, 3}); } TEST_CASE("Parse negative float" * test_suite("positional_arguments")) { argparse::ArgumentParser program; program.add_argument("--verbose", "-v") .help("enable verbose logging") .default_value(false) .implicit_value(true); program.add_argument("number").help("Input number").scan<'g', float>(); program.parse_args({"./main", "-1.0"}); REQUIRE(program.get("number") == -1.0); } TEST_CASE("Parse negative floats into a vector" * test_suite("positional_arguments")) { argparse::ArgumentParser program; program.add_argument("--verbose", "-v") .help("enable verbose logging") .default_value(false) .implicit_value(true); program.add_argument("number") .help("Input number") .nargs(3) .scan<'g', double>(); program.parse_args({"./main", "-1.001", "-2.002", "3.003"}); REQUIRE(program["number"] == std::vector{-1.001, -2.002, 3.003}); } TEST_CASE("Parse numbers in E notation" * test_suite("positional_arguments")) { argparse::ArgumentParser program; program.add_argument("--verbose", "-v") .help("enable verbose logging") .default_value(false) .implicit_value(true); program.add_argument("number").help("Input number").scan<'g', double>(); program.parse_args({"./main", "-1.2e3"}); REQUIRE(program.get("number") == -1200.0); } TEST_CASE("Parse numbers in E notation (capital E)" * test_suite("positional_arguments")) { argparse::ArgumentParser program; program.add_argument("--verbose", "-v") .help("enable verbose logging") .default_value(false) .implicit_value(true); program.add_argument("number").help("Input number").scan<'g', double>(); program.parse_args({"./main", "-1.32E4"}); REQUIRE(program.get("number") == -13200.0); } TEST_CASE("Recognize negative decimal numbers" * test_suite("positional_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("positional"); SUBCASE("zero") { REQUIRE_NOTHROW(program.parse_args({"test", "-0"})); } SUBCASE("not a decimal") { REQUIRE_THROWS_AS(program.parse_args({"test", "-00"}), std::runtime_error); } SUBCASE("looks like an octal") { REQUIRE_THROWS_AS(program.parse_args({"test", "-003"}), std::runtime_error); } SUBCASE("nonzero-digit") { REQUIRE_NOTHROW(program.parse_args({"test", "-9"})); } SUBCASE("nonzero-digit digit-sequence") { REQUIRE_NOTHROW(program.parse_args({"test", "-92180"})); } SUBCASE("zero dot") { REQUIRE_NOTHROW(program.parse_args({"test", "-0."})); } SUBCASE("nonzero-digit dot") { REQUIRE_NOTHROW(program.parse_args({"test", "-8."})); } SUBCASE("nonzero-digit digit-sequence dot") { REQUIRE_NOTHROW(program.parse_args({"test", "-200."})); } SUBCASE("integer-part dot") { REQUIRE_NOTHROW(program.parse_args({"test", "-003."})); } SUBCASE("dot digit-sequence") { REQUIRE_NOTHROW(program.parse_args({"test", "-.0927"})); } SUBCASE("not a single dot") { REQUIRE_THROWS_AS(program.parse_args({"test", "-."}), std::runtime_error); } SUBCASE("not a single e") { REQUIRE_THROWS_AS(program.parse_args({"test", "-e"}), std::runtime_error); } SUBCASE("not dot e") { REQUIRE_THROWS_AS(program.parse_args({"test", "-.e"}), std::runtime_error); } SUBCASE("integer-part exponent-part without sign") { REQUIRE_NOTHROW(program.parse_args({"test", "-1e32"})); } SUBCASE("integer-part exponent-part with positive sign") { REQUIRE_NOTHROW(program.parse_args({"test", "-1e+32"})); } SUBCASE("integer-part exponent-part with negative sign") { REQUIRE_NOTHROW(program.parse_args({"test", "-00e-0"})); } SUBCASE("missing mantissa") { REQUIRE_THROWS_AS(program.parse_args({"test", "-e32"}), std::runtime_error); } SUBCASE("missing mantissa but with positive sign") { REQUIRE_THROWS_AS(program.parse_args({"test", "-e+7"}), std::runtime_error); } SUBCASE("missing mantissa but with negative sign") { REQUIRE_THROWS_AS(program.parse_args({"test", "-e-1"}), std::runtime_error); } SUBCASE("nothing after e followed by zero") { REQUIRE_THROWS_AS(program.parse_args({"test", "-0e"}), std::runtime_error); } SUBCASE("nothing after e followed by integer-part") { REQUIRE_THROWS_AS(program.parse_args({"test", "-13e"}), std::runtime_error); } SUBCASE("integer-part dot exponent-part without sign") { REQUIRE_NOTHROW(program.parse_args({"test", "-18.e0"})); } SUBCASE("integer-part dot exponent-part with positive sign") { REQUIRE_NOTHROW(program.parse_args({"test", "-18.e+92"})); } SUBCASE("integer-part dot exponent-part with negative sign") { REQUIRE_NOTHROW(program.parse_args({"test", "-0.e-92"})); } SUBCASE("nothing after e followed by integer-part dot") { REQUIRE_THROWS_AS(program.parse_args({"test", "-13.e"}), std::runtime_error); } SUBCASE("dot digit-sequence exponent-part without sign") { REQUIRE_NOTHROW(program.parse_args({"test", "-.023e0"})); } SUBCASE("dot digit-sequence exponent-part with positive sign") { REQUIRE_NOTHROW(program.parse_args({"test", "-.2e+92"})); } SUBCASE("dot digit-sequence exponent-part with negative sign") { REQUIRE_NOTHROW(program.parse_args({"test", "-.71564e-92"})); } SUBCASE("nothing after e in fractional-part") { REQUIRE_THROWS_AS(program.parse_args({"test", "-.283e"}), std::runtime_error); } SUBCASE("exponent-part followed by only a dot") { REQUIRE_THROWS_AS(program.parse_args({"test", "-.e3"}), std::runtime_error); } SUBCASE("exponent-part followed by only a dot but with positive sign") { REQUIRE_THROWS_AS(program.parse_args({"test", "-.e+3"}), std::runtime_error); } SUBCASE("exponent-part followed by only a dot but with negative sign") { REQUIRE_THROWS_AS(program.parse_args({"test", "-.e-3"}), std::runtime_error); } SUBCASE("integer-part dot digit-sequence exponent-part without sign") { REQUIRE_NOTHROW(program.parse_args({"test", "-02.023e4000"})); } SUBCASE("integer-part dot digit-sequence exponent-part with positive sign") { REQUIRE_NOTHROW(program.parse_args({"test", "-3.239e+76"})); } SUBCASE("integer-part dot digit-sequence exponent-part with negative sign") { REQUIRE_NOTHROW(program.parse_args({"test", "-238237.0e-2"})); } SUBCASE("nothing after e") { REQUIRE_THROWS_AS(program.parse_args({"test", "-3.14e"}), std::runtime_error); } SUBCASE("nothing after e and positive sign") { REQUIRE_THROWS_AS(program.parse_args({"test", "-2.17e+"}), std::runtime_error); } SUBCASE("nothing after e and negative sign") { REQUIRE_THROWS_AS(program.parse_args({"test", "-13.6e-"}), std::runtime_error); } SUBCASE("more than one sign present in exponent-part") { REQUIRE_THROWS_AS(program.parse_args({"test", "-13.6e+-23"}), std::runtime_error); } SUBCASE("sign at wrong position") { REQUIRE_THROWS_AS(program.parse_args({"test", "-3.6e23+"}), std::runtime_error); } SUBCASE("more than one exponent-part") { REQUIRE_THROWS_AS(program.parse_args({"test", "-3.6e2e9"}), std::runtime_error); } SUBCASE("more than one fractional-part") { REQUIRE_THROWS_AS(program.parse_args({"test", "-3.6.3"}), std::runtime_error); } SUBCASE("number has its own sign") { REQUIRE_THROWS_AS(program.parse_args({"test", "-+42"}), std::runtime_error); } SUBCASE("looks like hexadecimal integer") { REQUIRE_THROWS_AS(program.parse_args({"test", "-0x0"}), std::runtime_error); } SUBCASE("looks like hexadecimal floating-point") { REQUIRE_THROWS_AS(program.parse_args({"test", "-0x27.8p1"}), std::runtime_error); } SUBCASE("looks like hexadecimal floating-point without prefix") { REQUIRE_THROWS_AS(program.parse_args({"test", "-3.8p1"}), std::runtime_error); } SUBCASE("Richard's pp-number") { REQUIRE_THROWS_AS(program.parse_args({"test", "-0x1e+2"}), std::runtime_error); } SUBCASE("Infinity") { REQUIRE_THROWS_AS(program.parse_args({"test", "-inf"}), std::runtime_error); REQUIRE_THROWS_AS(program.parse_args({"test", "-INFINITY"}), std::runtime_error); } SUBCASE("NaN") { REQUIRE_THROWS_AS(program.parse_args({"test", "-nan"}), std::runtime_error); REQUIRE_THROWS_AS(program.parse_args({"test", "-NAN"}), std::runtime_error); } } argparse-3.2/test/test_optional_arguments.cpp000066400000000000000000000153311474520133400216240ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include using doctest::test_suite; TEST_CASE("Parse toggle arguments with default value" * test_suite("optional_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("--verbose", "-v") .default_value(false) .implicit_value(true); program.parse_args({"./test.exe"}); REQUIRE(program.get("--verbose") == false); REQUIRE(program["--verbose"] == false); } TEST_CASE("Argument '-' is not an optional argument" * test_suite("optional_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("input"); program.parse_args({"./test.exe", "-"}); REQUIRE(program.get("input") == "-"); } TEST_CASE("Argument '-' is not an optional argument but '-l' is" * test_suite("optional_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("-l").flag(); program.add_argument("input"); program.parse_args({"./test.exe", "-l", "-"}); REQUIRE(program.get("-l") == true); REQUIRE(program.get("input") == "-"); } TEST_CASE("Argument '-l' is an optional argument but '-' is not" * test_suite("optional_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("-l").flag(); program.add_argument("input"); program.parse_args({"./test.exe", "-", "-l"}); REQUIRE(program.get("-l") == true); REQUIRE(program.get("input") == "-"); } TEST_CASE("Parse toggle arguments with implicit value" * test_suite("optional_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("--verbose").flag(); program.parse_args({"./test.exe", "--verbose"}); REQUIRE(program.get("--verbose") == true); REQUIRE(program["--verbose"] == true); REQUIRE(program["--verbose"] != false); } TEST_CASE("Parse multiple toggle arguments with implicit values" * test_suite("optional_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("-a").flag(); program.add_argument("-u").flag(); program.add_argument("-x").flag(); program.parse_args({"./test.exe", "-a", "-x"}); REQUIRE(program.get("-a") == true); REQUIRE(program.get("-u") == false); REQUIRE(program.get("-x") == true); } TEST_CASE("Parse optional arguments of many values" * test_suite("optional_arguments")) { GIVEN("a program that accepts an optional argument of many values") { argparse::ArgumentParser program("test"); program.add_argument("-i").remaining().scan<'i', int>(); WHEN("provided no argument") { THEN("the program accepts it but gets nothing") { REQUIRE_NOTHROW(program.parse_args({"test"})); REQUIRE_THROWS_AS(program.get>("-i"), std::logic_error); } } WHEN("provided remaining arguments follow the option") { program.parse_args({"test", "-i", "-42", "8", "100", "300"}); THEN("the optional parameter consumes all of them") { auto inputs = program.get>("-i"); REQUIRE(inputs.size() == 4); REQUIRE(inputs[0] == -42); REQUIRE(inputs[1] == 8); REQUIRE(inputs[2] == 100); REQUIRE(inputs[3] == 300); } } } } TEST_CASE("Parse 2 optional arguments of many values" * test_suite("optional_arguments")) { GIVEN("a program that accepts 2 optional arguments of many values") { argparse::ArgumentParser program("test"); program.add_argument("-i") .nargs(argparse::nargs_pattern::any) .scan<'i', int>(); program.add_argument("-s").nargs(argparse::nargs_pattern::any); WHEN("provided no argument") { THEN("the program accepts it and gets empty container") { REQUIRE_NOTHROW(program.parse_args({"test"})); auto i = program.get>("-i"); REQUIRE(i.size() == 0); auto s = program.get>("-s"); REQUIRE(s.size() == 0); } } WHEN("provided 2 options with many arguments") { program.parse_args({"test", "-i", "-42", "8", "100", "300", "-s", "ok", "this", "works"}); THEN("the optional parameter consumes each arguments") { auto i = program.get>("-i"); REQUIRE(i.size() == 4); REQUIRE(i[0] == -42); REQUIRE(i[1] == 8); REQUIRE(i[2] == 100); REQUIRE(i[3] == 300); auto s = program.get>("-s"); REQUIRE(s.size() == 3); REQUIRE(s[0] == "ok"); REQUIRE(s[1] == "this"); REQUIRE(s[2] == "works"); } } } } TEST_CASE("Parse an optional argument of many values" " and a positional argument of many values" * test_suite("optional_arguments")) { GIVEN("a program that accepts an optional argument of many values" " and a positional argument of many values") { argparse::ArgumentParser program("test"); program.add_argument("-s").nargs(argparse::nargs_pattern::any); program.add_argument("input").nargs(argparse::nargs_pattern::any); WHEN("provided no argument") { program.parse_args({"test"}); THEN("the program accepts it and gets empty containers") { auto s = program.get>("-s"); REQUIRE(s.size() == 0); auto input = program.get>("input"); REQUIRE(input.size() == 0); } } WHEN("provided many arguments followed by an option with many arguments") { program.parse_args({"test", "foo", "bar", "-s", "ok", "this", "works"}); THEN("the parameters consume each arguments") { auto s = program.get>("-s"); REQUIRE(s.size() == 3); REQUIRE(s[0] == "ok"); REQUIRE(s[1] == "this"); REQUIRE(s[2] == "works"); auto input = program.get>("input"); REQUIRE(input.size() == 2); REQUIRE(input[0] == "foo"); REQUIRE(input[1] == "bar"); } } } } TEST_CASE("Parse arguments of different types" * test_suite("optional_arguments")) { using namespace std::literals; argparse::ArgumentParser program("test"); program.add_argument("--this-argument-is-longer-than-any-sso-buffer-that-" "makes-sense-unless-your-cache-line-is-this-long"s); REQUIRE_NOTHROW(program.parse_args({"test"})); program.add_argument("-string"s, "-string-view"sv, "-builtin") .default_value(false) .implicit_value(true); program.parse_args({"test", "-string-view"}); REQUIRE(program["-string"sv] == true); REQUIRE(program["-string-view"] == true); REQUIRE(program["-builtin"s] == true); } argparse-3.2/test/test_parent_parsers.cpp000066400000000000000000000024471474520133400207460ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include using doctest::test_suite; TEST_CASE("Add parent parsers" * test_suite("parent_parsers")) { argparse::ArgumentParser parent_parser("main"); parent_parser.add_argument("--verbose") .default_value(false) .implicit_value(true); argparse::ArgumentParser child_parser("foo"); child_parser.add_parents(parent_parser); child_parser.parse_args({"./main", "--verbose"}); REQUIRE(child_parser["--verbose"] == true); REQUIRE(parent_parser["--verbose"] == false); } TEST_CASE("Add parent to multiple parent parsers" * test_suite("parent_parsers")) { argparse::ArgumentParser parent_parser("main"); parent_parser.add_argument("--parent").default_value(0).scan<'i', int>(); argparse::ArgumentParser foo_parser("foo"); foo_parser.add_argument("foo"); foo_parser.add_parents(parent_parser); foo_parser.parse_args({"./main", "--parent", "2", "XXX"}); REQUIRE(foo_parser["--parent"] == 2); REQUIRE(foo_parser["foo"] == std::string("XXX")); REQUIRE(parent_parser["--parent"] == 0); argparse::ArgumentParser bar_parser("bar"); bar_parser.add_argument("--bar"); bar_parser.parse_args({"./main", "--bar", "YYY"}); REQUIRE(bar_parser["--bar"] == std::string("YYY")); } argparse-3.2/test/test_parse_args.cpp000066400000000000000000000175471474520133400200530ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include using doctest::test_suite; TEST_CASE("Missing argument" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--config").nargs(1); REQUIRE_THROWS_WITH_AS(program.parse_args({"test", "--config"}), "Too few arguments for '--config'.", std::runtime_error); } TEST_CASE("Missing argument, not last" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--config").nargs(1); program.add_argument("--foo"); REQUIRE_THROWS_WITH_AS(program.parse_args({"test", "--config", "--foo"}), "Too few arguments for '--config'.", std::runtime_error); } TEST_CASE("Parse a string argument with value" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--config"); program.parse_args({"test", "--config", "config.yml"}); REQUIRE(program.get("--config") == "config.yml"); } TEST_CASE("Parse a string argument with default value" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--config").default_value(std::string("foo.yml")); program.parse_args({"test", "--config"}); REQUIRE(program.get("--config") == "foo.yml"); } TEST_CASE("Parse a string argument without default value" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--config"); WHEN("no value provided") { program.parse_args({"test"}); THEN("the option is nullopt") { auto opt = program.present("--config"); REQUIRE_FALSE(opt); REQUIRE(opt == std::nullopt); } } WHEN("a value is provided") { program.parse_args({"test", "--config", ""}); THEN("the option has a value") { auto opt = program.present("--config"); REQUIRE(opt); REQUIRE(opt->empty()); } } } TEST_CASE("Parse an int argument with value" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--count").scan<'i', int>(); program.parse_args({"test", "--count", "5"}); REQUIRE(program.get("--count") == 5); } TEST_CASE("Parse an int argument with default value" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--count").default_value(2).scan<'i', int>(); program.parse_args({"test", "--count"}); REQUIRE(program.get("--count") == 2); } TEST_CASE("Parse a float argument with value" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--ratio").scan<'g', float>(); program.parse_args({"test", "--ratio", "5.6645"}); REQUIRE(program.get("--ratio") == 5.6645f); } TEST_CASE("Parse a float argument with default value" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--ratio").default_value(3.14f).scan<'g', float>(); program.parse_args({"test", "--ratio"}); REQUIRE(program.get("--ratio") == 3.14f); } TEST_CASE("Parse a double argument with value" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--ratio").scan<'g', double>(); program.parse_args({"test", "--ratio", "5.6645"}); REQUIRE(program.get("--ratio") == 5.6645); } TEST_CASE("Parse a double argument with default value" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--ratio").default_value(3.14).scan<'g', double>(); program.parse_args({"test", "--ratio"}); REQUIRE(program.get("--ratio") == 3.14); } TEST_CASE("Parse a vector of integer arguments" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--vector").nargs(5).scan<'i', int>(); program.parse_args({"test", "--vector", "1", "2", "3", "4", "5"}); auto vector = program.get>("--vector"); REQUIRE(vector.size() == 5); REQUIRE(vector[0] == 1); REQUIRE(vector[1] == 2); REQUIRE(vector[2] == 3); REQUIRE(vector[3] == 4); REQUIRE(vector[4] == 5); } TEST_CASE("Parse a vector of float arguments" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--vector").nargs(5).scan<'g', float>(); program.parse_args({"test", "--vector", "1.1", "2.2", "3.3", "4.4", "5.5"}); auto vector = program.get>("--vector"); REQUIRE(vector.size() == 5); REQUIRE(vector[0] == 1.1f); REQUIRE(vector[1] == 2.2f); REQUIRE(vector[2] == 3.3f); REQUIRE(vector[3] == 4.4f); REQUIRE(vector[4] == 5.5f); } TEST_CASE("Parse a vector of float without default value" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--vector").scan<'g', float>().nargs(3); WHEN("no value is provided") { program.parse_args({"test"}); THEN("the option is nullopt") { auto opt = program.present>("--vector"); REQUIRE_FALSE(opt.has_value()); REQUIRE(opt == std::nullopt); } } WHEN("a value is provided") { program.parse_args({"test", "--vector", ".3", "1.3", "6"}); THEN("the option has a value") { auto opt = program.present>("--vector"); REQUIRE(opt.has_value()); auto &&vec = opt.value(); REQUIRE(vec.size() == 3); REQUIRE(vec[0] == .3f); REQUIRE(vec[1] == 1.3f); REQUIRE(vec[2] == 6.f); } } } TEST_CASE("Parse a vector of double arguments" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--vector").nargs(5).scan<'g', double>(); program.parse_args({"test", "--vector", "1.1", "2.2", "3.3", "4.4", "5.5"}); auto vector = program.get>("--vector"); REQUIRE(vector.size() == 5); REQUIRE(vector[0] == 1.1); REQUIRE(vector[1] == 2.2); REQUIRE(vector[2] == 3.3); REQUIRE(vector[3] == 4.4); REQUIRE(vector[4] == 5.5); } TEST_CASE("Parse a vector of string arguments" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--vector").nargs(5); program.parse_args({"test", "--vector", "abc", "def", "ghi", "jkl", "mno"}); auto vector = program.get>("--vector"); REQUIRE(vector.size() == 5); REQUIRE(vector[0] == "abc"); REQUIRE(vector[1] == "def"); REQUIRE(vector[2] == "ghi"); REQUIRE(vector[3] == "jkl"); REQUIRE(vector[4] == "mno"); } TEST_CASE("Parse a vector of character arguments" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--vector") .nargs(5) .action([](const std::string &value) { return value[0]; }); program.parse_args({"test", "--vector", "a", "b", "c", "d", "e"}); auto vector = program.get>("--vector"); REQUIRE(vector.size() == 5); REQUIRE(vector[0] == 'a'); REQUIRE(vector[1] == 'b'); REQUIRE(vector[2] == 'c'); REQUIRE(vector[3] == 'd'); REQUIRE(vector[4] == 'e'); } TEST_CASE("Parse a vector of string arguments and construct objects" * test_suite("parse_args")) { class Foo { public: Foo(const std::string &value) : m_value(value) {} std::string m_value; }; argparse::ArgumentParser program("test"); program.add_argument("--vector") .nargs(5) .action([](const std::string &value) { return Foo(value); }); program.parse_args({"test", "--vector", "abc", "def", "ghi", "jkl", "mno"}); auto vector = program.get>("--vector"); REQUIRE(vector.size() == 5); REQUIRE(vector[0].m_value == Foo("abc").m_value); REQUIRE(vector[1].m_value == Foo("def").m_value); REQUIRE(vector[2].m_value == Foo("ghi").m_value); REQUIRE(vector[3].m_value == Foo("jkl").m_value); REQUIRE(vector[4].m_value == Foo("mno").m_value); } argparse-3.2/test/test_parse_known_args.cpp000066400000000000000000000065601474520133400212600ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include #include using doctest::test_suite; TEST_CASE("Parse unknown optional and positional arguments without exceptions" * test_suite("parse_known_args")) { argparse::ArgumentParser program("test"); program.add_argument("--foo").implicit_value(true).default_value(false); program.add_argument("bar"); SUBCASE("Parse unknown optional and positional arguments") { auto unknown_args = program.parse_known_args({"test", "--foo", "--badger", "BAR", "spam"}); REQUIRE((unknown_args == std::vector{"--badger", "spam"})); REQUIRE(program.get("--foo") == true); REQUIRE(program.get("bar") == std::string{"BAR"}); } SUBCASE("Parse unknown compound arguments") { auto unknown_args = program.parse_known_args({"test", "-jc", "BAR"}); REQUIRE((unknown_args == std::vector{"-jc"})); REQUIRE(program.get("--foo") == false); REQUIRE(program.get("bar") == std::string{"BAR"}); } } TEST_CASE("Parse unknown optional and positional arguments in subparsers " "without exceptions" * test_suite("parse_known_args")) { argparse::ArgumentParser program("test"); program.add_argument("--output"); argparse::ArgumentParser command_1("add"); command_1.add_argument("file").nargs(2); argparse::ArgumentParser command_2("clean"); command_2.add_argument("--fullclean") .default_value(false) .implicit_value(true); program.add_subparser(command_1); program.add_subparser(command_2); SUBCASE("Parse unknown optional argument") { auto unknown_args = program.parse_known_args({"test", "add", "--badger", "BAR", "spam"}); REQUIRE(program.is_subcommand_used("add") == true); REQUIRE((command_1.get>("file") == std::vector{"BAR", "spam"})); REQUIRE((unknown_args == std::vector{"--badger"})); } SUBCASE("Parse unknown positional argument") { auto unknown_args = program.parse_known_args({"test", "add", "FOO", "BAR", "spam"}); REQUIRE(program.is_subcommand_used("add") == true); REQUIRE((command_1.get>("file") == std::vector{"FOO", "BAR"})); REQUIRE((unknown_args == std::vector{"spam"})); } SUBCASE("Parse unknown positional and optional arguments") { auto unknown_args = program.parse_known_args( {"test", "add", "--verbose", "FOO", "5", "BAR", "-jn", "spam"}); REQUIRE(program.is_subcommand_used("add") == true); REQUIRE((command_1.get>("file") == std::vector{"FOO", "5"})); REQUIRE((unknown_args == std::vector{"--verbose", "BAR", "-jn", "spam"})); } SUBCASE("Parse unknown positional and optional arguments 2") { auto unknown_args = program.parse_known_args({"test", "clean", "--verbose", "FOO", "5", "BAR", "--fullclean", "-jn", "spam"}); REQUIRE(program.is_subcommand_used("clean") == true); REQUIRE(command_2.get("--fullclean") == true); REQUIRE((unknown_args == std::vector{"--verbose", "FOO", "5", "BAR", "-jn", "spam"})); } } argparse-3.2/test/test_positional_arguments.cpp000066400000000000000000000243031474520133400221570ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include using doctest::test_suite; TEST_CASE("Parse positional arguments" * test_suite("positional_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("input"); program.add_argument("output"); program.parse_args({"test", "rocket.mesh", "thrust_profile.csv"}); REQUIRE(program.get("input") == "rocket.mesh"); REQUIRE(program.get("output") == "thrust_profile.csv"); } TEST_CASE("Missing expected positional argument" * test_suite("positional_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("input"); REQUIRE_THROWS_WITH_AS(program.parse_args({"test"}), "input: 1 argument(s) expected. 0 provided.", std::runtime_error); } TEST_CASE("Parse positional arguments with fixed nargs" * test_suite("positional_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("input"); program.add_argument("output").nargs(2); program.parse_args( {"test", "rocket.mesh", "thrust_profile.csv", "output.mesh"}); REQUIRE(program.get("input") == "rocket.mesh"); auto outputs = program.get>("output"); REQUIRE(outputs.size() == 2); REQUIRE(outputs[0] == "thrust_profile.csv"); REQUIRE(outputs[1] == "output.mesh"); } TEST_CASE("Parse positional arguments with optional arguments" * test_suite("positional_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("input"); program.add_argument("output").nargs(2); program.add_argument("--num_iterations").scan<'i', int>(); program.parse_args({"test", "rocket.mesh", "--num_iterations", "15", "thrust_profile.csv", "output.mesh"}); REQUIRE(program.get("--num_iterations") == 15); REQUIRE(program.get("input") == "rocket.mesh"); auto outputs = program.get>("output"); REQUIRE(outputs.size() == 2); REQUIRE(outputs[0] == "thrust_profile.csv"); REQUIRE(outputs[1] == "output.mesh"); } TEST_CASE("Parse positional arguments with optional arguments in the middle" * test_suite("positional_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("input"); program.add_argument("output").nargs(2); program.add_argument("--num_iterations").scan<'i', int>(); REQUIRE_THROWS( program.parse_args({"test", "rocket.mesh", "thrust_profile.csv", "--num_iterations", "15", "output.mesh"})); } TEST_CASE("Parse positional nargs=1..2 arguments" * test_suite("positional_arguments")) { GIVEN("a program that accepts an optional argument and nargs=1..2 positional " "arguments") { argparse::ArgumentParser program("test"); program.add_argument("-o"); program.add_argument("input").nargs(1, 2); WHEN("provided no argument") { THEN("the program does not accept it") { REQUIRE_THROWS(program.parse_args({"test"})); } } WHEN("provided 1 argument") { THEN("the program accepts it") { REQUIRE_NOTHROW(program.parse_args({"test", "a.c"})); auto inputs = program.get>("input"); REQUIRE(inputs.size() == 1); REQUIRE(inputs[0] == "a.c"); } } WHEN("provided 2 arguments") { THEN("the program accepts it") { REQUIRE_NOTHROW(program.parse_args({"test", "a.c", "b.c"})); auto inputs = program.get>("input"); REQUIRE(inputs.size() == 2); REQUIRE(inputs[0] == "a.c"); REQUIRE(inputs[1] == "b.c"); } } WHEN("provided 3 arguments") { THEN("the program does not accept it") { REQUIRE_THROWS(program.parse_args({"test", "a.c", "b.c", "main.c"})); } } WHEN("provided an optional followed by positional arguments") { program.parse_args({"test", "-o", "a.out", "a.c", "b.c"}); THEN("the optional parameter consumes an argument") { using namespace std::literals; REQUIRE(program["-o"] == "a.out"s); auto inputs = program.get>("input"); REQUIRE(inputs.size() == 2); REQUIRE(inputs[0] == "a.c"); REQUIRE(inputs[1] == "b.c"); } } WHEN("provided an optional preceded by positional arguments") { program.parse_args({"test", "a.c", "b.c", "-o", "a.out"}); THEN("the optional parameter consumes an argument") { using namespace std::literals; REQUIRE(program["-o"] == "a.out"s); auto inputs = program.get>("input"); REQUIRE(inputs.size() == 2); REQUIRE(inputs[0] == "a.c"); REQUIRE(inputs[1] == "b.c"); } } WHEN("provided an optional in between positional arguments") { THEN("the program does not accept it") { REQUIRE_THROWS( program.parse_args({"test", "a.c", "-o", "a.out", "b.c"})); } } } } TEST_CASE("Parse positional nargs=ANY arguments" * test_suite("positional_arguments")) { GIVEN("a program that accepts an optional argument and nargs=ANY positional " "arguments") { argparse::ArgumentParser program("test"); program.add_argument("-o"); program.add_argument("input").nargs(argparse::nargs_pattern::any); WHEN("provided no argument") { THEN("the program accepts it and gets empty container") { REQUIRE_NOTHROW(program.parse_args({"test"})); auto inputs = program.get>("input"); REQUIRE(inputs.size() == 0); } } WHEN("provided an optional followed by positional arguments") { program.parse_args({"test", "-o", "a.out", "a.c", "b.c", "main.c"}); THEN("the optional parameter consumes an argument") { using namespace std::literals; REQUIRE(program["-o"] == "a.out"s); auto inputs = program.get>("input"); REQUIRE(inputs.size() == 3); REQUIRE(inputs[0] == "a.c"); REQUIRE(inputs[1] == "b.c"); REQUIRE(inputs[2] == "main.c"); } } WHEN("provided an optional preceded by positional arguments") { program.parse_args({"test", "a.c", "b.c", "main.c", "-o", "a.out"}); THEN("the optional parameter consumes an argument") { using namespace std::literals; REQUIRE(program["-o"] == "a.out"s); auto inputs = program.get>("input"); REQUIRE(inputs.size() == 3); REQUIRE(inputs[0] == "a.c"); REQUIRE(inputs[1] == "b.c"); REQUIRE(inputs[2] == "main.c"); } } } } TEST_CASE("Parse remaining arguments deemed positional" * test_suite("positional_arguments")) { GIVEN("a program that accepts an optional argument and remaining arguments") { argparse::ArgumentParser program("test"); program.add_argument("-o"); program.add_argument("input").remaining(); WHEN("provided no argument") { THEN("the program accepts it but gets nothing") { REQUIRE_NOTHROW(program.parse_args({"test"})); REQUIRE_THROWS_AS(program.get>("input"), std::logic_error); } } WHEN("provided an optional followed by remaining arguments") { program.parse_args({"test", "-o", "a.out", "a.c", "b.c", "main.c"}); THEN("the optional parameter consumes an argument") { using namespace std::literals; REQUIRE(program["-o"] == "a.out"s); auto inputs = program.get>("input"); REQUIRE(inputs.size() == 3); REQUIRE(inputs[0] == "a.c"); REQUIRE(inputs[1] == "b.c"); REQUIRE(inputs[2] == "main.c"); } } WHEN("provided remaining arguments including optional arguments") { program.parse_args({"test", "a.c", "b.c", "main.c", "-o", "a.out"}); THEN("the optional argument is deemed remaining") { REQUIRE_THROWS_AS(program.get("-o"), std::logic_error); auto inputs = program.get>("input"); REQUIRE(inputs.size() == 5); REQUIRE(inputs[0] == "a.c"); REQUIRE(inputs[1] == "b.c"); REQUIRE(inputs[2] == "main.c"); REQUIRE(inputs[3] == "-o"); REQUIRE(inputs[4] == "a.out"); } } } } TEST_CASE("Reversed order nargs is not allowed" * test_suite("positional_arguments")) { argparse::ArgumentParser program("test"); REQUIRE_THROWS_AS(program.add_argument("output").nargs(2, 1), std::logic_error); } TEST_CASE("Square a number" * test_suite("positional_arguments")) { argparse::ArgumentParser program; program.add_argument("--verbose", "-v") .help("enable verbose logging") .default_value(false) .implicit_value(true); program.add_argument("square") .help("display a square of a given number") .action( [](const std::string &value) { return pow(std::stoi(value), 2); }); program.parse_args({"./main", "15"}); REQUIRE(program.get("square") == 225); } TEST_CASE("At_least_one_followed_by_exactly_one" * test_suite("positional_arguments")) { GIVEN("a program that accepts a positional argument with at_least_one cardinality followed by another positional argument with 1:1") { argparse::ArgumentParser program; std::vector at_least_one; program.add_argument("at_least_one") .nargs(argparse::nargs_pattern::at_least_one) .store_into(at_least_one); std::string exactly_one; program.add_argument("exactly_one") .store_into(exactly_one); WHEN("provided one, two") { THEN("parse_args works") { program.parse_args({"./main", "one", "two"}); REQUIRE(at_least_one == std::vector{"one"}); REQUIRE(exactly_one == "two"); } } WHEN("provided one, two, three") { THEN("parse_args works") { program.parse_args({"./main", "one", "two", "three"}); REQUIRE(at_least_one == std::vector{"one", "two"}); REQUIRE(exactly_one == "three"); } } WHEN("provided one, two") { THEN("parse_args throws") { REQUIRE_THROWS(program.parse_args({"./main", "one"})); } } } } argparse-3.2/test/test_prefix_chars.cpp000066400000000000000000000026471474520133400203750ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include using doctest::test_suite; TEST_CASE("Parse with custom prefix chars" * test_suite("prefix_chars")) { argparse::ArgumentParser program("test"); program.set_prefix_chars("-+"); program.add_argument("+f"); program.add_argument("++bar"); program.parse_args({"test", "+f", "X", "++bar", "Y"}); REQUIRE(program.get("+f") == "X"); REQUIRE(program.get("++bar") == "Y"); } TEST_CASE("Parse with custom Windows-style prefix chars" * test_suite("prefix_chars")) { argparse::ArgumentParser program("dir"); program.set_prefix_chars("/"); program.add_argument("/A").nargs(1); program.add_argument("/B").flag(); program.add_argument("/C").flag(); program.parse_args({"dir", "/A", "D", "/B", "/C"}); REQUIRE(program.get("/A") == "D"); REQUIRE(program.get("/B") == true); } TEST_CASE("Parse with custom Windows-style prefix chars and assign chars" * test_suite("prefix_chars")) { argparse::ArgumentParser program("dir"); program.set_prefix_chars("/"); program.set_assign_chars(":="); program.add_argument("/A").nargs(1); program.add_argument("/B").nargs(1); program.add_argument("/C").flag(); program.parse_args({"dir", "/A:D", "/B=Boo", "/C"}); REQUIRE(program.get("/A") == "D"); REQUIRE(program.get("/B") == "Boo"); REQUIRE(program.get("/C") == true); }argparse-3.2/test/test_repr.cpp000066400000000000000000000036031474520133400166610ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; import argparse.details; #else #include #endif #include #include #include #include using doctest::test_suite; TEST_CASE("Test bool representation" * test_suite("repr")) { REQUIRE(argparse::details::repr(true) == "true"); REQUIRE(argparse::details::repr(false) == "false"); } TEST_CASE_TEMPLATE("Test built-in int types representation" * test_suite("repr"), T, char, short, int, long long, unsigned char, unsigned, unsigned long long) { std::stringstream ss; T v = 42; ss << v; REQUIRE(argparse::details::repr(v) == ss.str()); } TEST_CASE_TEMPLATE("Test built-in float types representation" * test_suite("repr"), T, float, double, long double) { std::stringstream ss; T v = static_cast(0.3333333333); ss << v; REQUIRE(argparse::details::repr(v) == ss.str()); } TEST_CASE_TEMPLATE("Test container representation" * test_suite("repr"), T, std::vector, std::list, std::set) { T empty; T one = {42}; T small = {1, 2, 3}; T big = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; REQUIRE(argparse::details::repr(empty) == "{}"); REQUIRE(argparse::details::repr(one) == "{42}"); REQUIRE(argparse::details::repr(small) == "{1 2 3}"); REQUIRE(argparse::details::repr(big) == "{1 2 3 4...15}"); } TEST_CASE_TEMPLATE("Test string representation" * test_suite("repr"), T, char const *, std::string, std::string_view) { T empty = ""; T str = "A A A#"; REQUIRE(argparse::details::repr(empty) == "\"\""); REQUIRE(argparse::details::repr(str) == "\"A A A#\""); } TEST_CASE("Test unknown representation" * test_suite("repr")) { struct TestClass {}; REQUIRE(argparse::details::repr(TestClass{}) == ""); } argparse-3.2/test/test_required_arguments.cpp000066400000000000000000000044321474520133400216170ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include using doctest::test_suite; TEST_CASE( "Parse required arguments which are not set and don't have default value" * test_suite("required_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("--output", "-o").required(); REQUIRE_THROWS(program.parse_args({"./main"})); } TEST_CASE("Parse required arguments which are set as empty value and don't " "have default value" * test_suite("required_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("--output", "-o").required(); REQUIRE_THROWS(program.parse_args({"./main", "-o"})); } TEST_CASE("Parse required arguments which are set as some value and don't have " "default value" * test_suite("required_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("--output", "-o").required(); program.parse_args({"./main", "-o", "filename"}); REQUIRE(program.get("--output") == "filename"); REQUIRE(program.get("-o") == "filename"); } TEST_CASE("Parse required arguments which are not set and have default value" * test_suite("required_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("--output", "-o") .required() .default_value(std::string("filename")); program.parse_args({"./main"}); REQUIRE(program.get("--output") == "filename"); REQUIRE(program.get("-o") == "filename"); } TEST_CASE( "Parse required arguments which are set as empty and have default value" * test_suite("required_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("--output", "-o") .required() .default_value(std::string("filename")); REQUIRE_THROWS(program.parse_args({"./main", "-o"})); } TEST_CASE("Parse required arguments which are set as some value and have " "default value" * test_suite("required_arguments")) { argparse::ArgumentParser program("test"); program.add_argument("--output", "-o") .required() .default_value(std::string("filename")); program.parse_args({"./main", "-o", "anotherfile"}); REQUIRE(program.get("--output") == "anotherfile"); REQUIRE(program.get("-o") == "anotherfile"); } argparse-3.2/test/test_scan.cpp000066400000000000000000000362101474520133400166350ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include using doctest::test_suite; TEST_CASE_TEMPLATE("Parse a decimal integer argument" * test_suite("scan"), T, int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t, uint64_t) { argparse::ArgumentParser program("test"); program.add_argument("-n").scan<'d', T>(); SUBCASE("zero") { program.parse_args({"test", "-n", "0"}); REQUIRE(program.get("-n") == 0); } SUBCASE("non-negative") { program.parse_args({"test", "-n", "5"}); REQUIRE(program.get("-n") == 5); } SUBCASE("negative") { if constexpr (std::is_signed_v) { program.parse_args({"test", "-n", "-128"}); REQUIRE(program.get("-n") == -128); } else { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-135"}), std::invalid_argument); } } SUBCASE("left-padding is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", " 32"}), std::invalid_argument); } SUBCASE("right-padding is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "12 "}), std::invalid_argument); } SUBCASE("plus sign is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+12"}), std::invalid_argument); } SUBCASE("does not fit") { REQUIRE_THROWS_AS( program.parse_args({"test", "-n", "987654321987654321987654321"}), std::range_error); } } TEST_CASE_TEMPLATE("Parse an octal integer argument" * test_suite("scan"), T, uint8_t, uint16_t, uint32_t, uint64_t) { argparse::ArgumentParser program("test"); program.add_argument("-n").scan<'o', T>(); SUBCASE("zero") { program.parse_args({"test", "-n", "0"}); REQUIRE(program.get("-n") == 0); } SUBCASE("with octal base") { program.parse_args({"test", "-n", "066"}); REQUIRE(program.get("-n") == 066); } SUBCASE("minus sign produces an optional argument") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-003"}), std::runtime_error); } SUBCASE("plus sign is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+012"}), std::invalid_argument); } SUBCASE("does not fit") { REQUIRE_THROWS_AS( program.parse_args({"test", "-n", "02000000000000000000001"}), std::range_error); } } TEST_CASE_TEMPLATE("Parse a hexadecimal integer argument" * test_suite("scan"), T, uint8_t, uint16_t, uint32_t, uint64_t) { argparse::ArgumentParser program("test"); program.add_argument("-n").scan<'X', T>(); SUBCASE("with hex digit") { program.parse_args({"test", "-n", "0x1a"}); REQUIRE(program.get("-n") == 0x1a); } SUBCASE("minus sign produces an optional argument") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0x1"}), std::runtime_error); } SUBCASE("plus sign is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+0x1a"}), std::invalid_argument); } SUBCASE("does not fit") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0XFFFFFFFFFFFFFFFF1"}), std::range_error); } SUBCASE("with hex digit without prefix") { program.parse_args({"test", "-n", "1a"}); REQUIRE(program.get("-n") == 0x1a); } SUBCASE("minus sign without prefix produces an optional argument") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-1"}), std::invalid_argument); } SUBCASE("plus sign without prefix is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+1a"}), std::invalid_argument); } SUBCASE("without prefix does not fit") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "FFFFFFFFFFFFFFFF1"}), std::range_error); } } TEST_CASE("Parse multiple hex numbers without prefix" * test_suite("scan")) { argparse::ArgumentParser program("test"); program.add_argument("-x", "--hex") .help("bytes in hex separated by spaces") .nargs(1, std::numeric_limits::max()) .scan<'x', uint8_t>(); REQUIRE_NOTHROW( program.parse_args({"test", "-x", "f2", "b2", "10", "80", "64"})); const auto &input_bytes = program.get>("-x"); REQUIRE((input_bytes == std::vector{0xf2, 0xb2, 0x10, 0x80, 0x64})); } TEST_CASE_TEMPLATE("Parse integer argument of any format" * test_suite("scan"), T, int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t, uint64_t) { argparse::ArgumentParser program("test"); program.add_argument("-n").scan<'i', T>(); SUBCASE("zero") { program.parse_args({"test", "-n", "0"}); REQUIRE(program.get("-n") == 0); } SUBCASE("octal") { program.parse_args({"test", "-n", "077"}); REQUIRE(program.get("-n") == 077); } SUBCASE("no negative octal") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0777"}), std::runtime_error); } SUBCASE("hex") { program.parse_args({"test", "-n", "0X2c"}); REQUIRE(program.get("-n") == 0X2c); } SUBCASE("no negative hex") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0X2A"}), std::runtime_error); } SUBCASE("decimal") { program.parse_args({"test", "-n", "98"}); REQUIRE(program.get("-n") == 98); } SUBCASE("negative decimal") { if constexpr (std::is_signed_v) { program.parse_args({"test", "-n", "-39"}); REQUIRE(program.get("-n") == -39); } else { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-39"}), std::invalid_argument); } } SUBCASE("left-padding is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "\t32"}), std::invalid_argument); } SUBCASE("right-padding is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "32\n"}), std::invalid_argument); } SUBCASE("plus sign is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+670"}), std::invalid_argument); } } TEST_CASE_TEMPLATE("Parse a binary argument" * test_suite("scan"), T, uint8_t, uint16_t, uint32_t, uint64_t) { argparse::ArgumentParser program("test"); program.add_argument("-n").scan<'b', T>(); SUBCASE("with binary digit") { program.parse_args({"test", "-n", "0b101"}); REQUIRE(program.get("-n") == 0b101); } SUBCASE("minus sign produces an optional argument") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0b101"}), std::runtime_error); } SUBCASE("plus sign is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+0b101"}), std::invalid_argument); } SUBCASE("does not fit") { REQUIRE_THROWS_AS( program.parse_args( {"test", "-n", "0b111111111111111111111111111111111111111111111111111111111111111" "11111111111111111111111111111111111111111111111111111111111111111" "11111111111111111111111111111111111111111111111111111111111111111" "11111111111111111111111111111111111111111111111111111111111111111" "11111111111111111111111111111111111111111111111111111111111111111" "11111111111111111111111111111111111111111111111111111111111111111" "11111111111111111111111111111111111111111111111111111111111111111" "11111111111111111111111111111111111111111111111111111111111111111" "11111111111111111111111111111111111111111111111111111111111111111" "11111111111111111111111111111111111111111111111111111111111111111" "11111111111111111111111111111111111111111111111111111111111111111" "11111111111111111111111111111111111111111111111111111111111111111" "11111111111111111111111111111111111111111111111111111111111111111" "1111111111111111111111111111111111111111111111111111111111111111" "1"}), std::range_error); } } #define FLOAT_G(t, literal) \ ([] { \ if constexpr (std::is_same_v) \ return literal##f; \ else if constexpr (std::is_same_v) \ return literal; \ else if constexpr (std::is_same_v) \ return literal##l; \ }()) TEST_CASE_TEMPLATE("Parse floating-point argument of general format" * test_suite("scan"), T, float, double, long double) { argparse::ArgumentParser program("test"); program.add_argument("-n").scan<'g', T>(); SUBCASE("zero") { program.parse_args({"test", "-n", "0"}); REQUIRE(program.get("-n") == 0.); } SUBCASE("non-negative") { program.parse_args({"test", "-n", "3.14"}); REQUIRE(program.get("-n") == FLOAT_G(T, 3.14)); } SUBCASE("negative") { program.parse_args({"test", "-n", "-0.12"}); REQUIRE(program.get("-n") == FLOAT_G(T, -0.12)); } SUBCASE("left-padding is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "\t.32"}), std::invalid_argument); } SUBCASE("right-padding is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", ".32\n"}), std::invalid_argument); } SUBCASE("plus sign is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+.12"}), std::invalid_argument); } SUBCASE("plus sign after padding is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", " +.12"}), std::invalid_argument); } SUBCASE("hexfloat is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0x1a.3p+1"}), std::invalid_argument); } SUBCASE("does not fit") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "1.3e+5000"}), std::range_error); } } TEST_CASE_TEMPLATE("Parse hexadecimal floating-point argument" * test_suite("scan"), T, float, double, long double) { argparse::ArgumentParser program("test"); program.add_argument("-n").scan<'a', T>(); SUBCASE("zero") { // binary-exponent-part is not optional in C++ grammar program.parse_args({"test", "-n", "0x0"}); REQUIRE(program.get("-n") == 0x0.p0); } SUBCASE("non-negative") { program.parse_args({"test", "-n", "0x1a.3p+1"}); REQUIRE(program.get("-n") == 0x1a.3p+1); } SUBCASE("minus sign produces an optional argument") { // XXX may worth a fix REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0x0.12p1"}), std::runtime_error); } SUBCASE("plus sign is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+0x1p0"}), std::invalid_argument); } SUBCASE("general format is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "3.14"}), std::invalid_argument); } } TEST_CASE_TEMPLATE("Parse floating-point argument of scientific format" * test_suite("scan"), T, float, double, long double) { argparse::ArgumentParser program("test"); program.add_argument("-n").scan<'e', T>(); SUBCASE("zero") { program.parse_args({"test", "-n", "0e0"}); REQUIRE(program.get("-n") == 0e0); } SUBCASE("non-negative") { program.parse_args({"test", "-n", "3.14e-1"}); REQUIRE(program.get("-n") == FLOAT_G(T, 3.14e-1)); } SUBCASE("negative") { program.parse_args({"test", "-n", "-0.12e+1"}); REQUIRE(program.get("-n") == FLOAT_G(T, -0.12e+1)); } SUBCASE("plus sign is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+.12e+1"}), std::invalid_argument); } SUBCASE("fixed format is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "3.14"}), std::invalid_argument); } SUBCASE("hexfloat is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0x1.33p+0"}), std::invalid_argument); } SUBCASE("does not fit") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "1.3e+5000"}), std::range_error); } } TEST_CASE_TEMPLATE("Parse floating-point argument of fixed format" * test_suite("scan"), T, float, double, long double) { argparse::ArgumentParser program("test"); program.add_argument("-n").scan<'f', T>(); SUBCASE("zero") { program.parse_args({"test", "-n", ".0"}); REQUIRE(program.get("-n") == .0); } SUBCASE("non-negative") { program.parse_args({"test", "-n", "3.14"}); REQUIRE(program.get("-n") == FLOAT_G(T, 3.14)); } SUBCASE("negative") { program.parse_args({"test", "-n", "-0.12"}); REQUIRE(program.get("-n") == FLOAT_G(T, -0.12)); } SUBCASE("plus sign is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+.12"}), std::invalid_argument); } SUBCASE("scientific format is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "3.14e+0"}), std::invalid_argument); } SUBCASE("hexfloat is not allowed") { REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0x1.33p+0"}), std::invalid_argument); } } TEST_CASE("Test that scan also works with a custom action" * test_suite("scan")) { GIVEN("an argument with scan followed by a custom action") { argparse::ArgumentParser program("test"); int res; program.add_argument("--int").scan<'i', int>().action([&](const auto &s) {res = std::stoi(s);}); WHEN("the argument is parsed") { SUBCASE("with a valid value") { program.parse_args({"./test.exe", "--int", "3"}); THEN("the value is stored") { REQUIRE(res == 3); } } SUBCASE("with an invalid value") { REQUIRE_THROWS_AS(program.parse_args({"./test.exe", "--int", "XXX"}), std::invalid_argument); } } } GIVEN("an argument with a custom action followed by scan") { argparse::ArgumentParser program("test"); int res; program.add_argument("--int").action([&](const auto &s) {res = std::stoi(s);}).scan<'i', int>(); WHEN("the argument is parsed") { SUBCASE("with a valid value") { program.parse_args({"./test.exe", "--int", "3"}); THEN("the value is stored") { REQUIRE(res == 3); } } SUBCASE("with an invalid value") { REQUIRE_THROWS_AS(program.parse_args({"./test.exe", "--int", "XXX"}), std::invalid_argument); } } } } argparse-3.2/test/test_store_into.cpp000066400000000000000000000243021474520133400200750ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include using doctest::test_suite; TEST_CASE("Test store_into(bool), flag not specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); bool flag; program.add_argument("--flag").store_into(flag); program.parse_args({"./test.exe"}); REQUIRE(flag == false); } TEST_CASE("Test store_into(bool), flag specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); bool flag; program.add_argument("--flag").store_into(flag); program.parse_args({"./test.exe", "--flag"}); REQUIRE(flag == true); } // int cases TEST_CASE("Test store_into(int), no default value, non specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); int res = -1; program.add_argument("--int-opt").store_into(res); program.parse_args({"./test.exe"}); REQUIRE(res == -1); } TEST_CASE("Test store_into(int), default value, non specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); int res = -1; program.add_argument("--int-opt").default_value(3).store_into(res); program.parse_args({"./test.exe"}); REQUIRE(res == 3); } TEST_CASE("Test store_into(int), default value, specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); int res = -1; program.add_argument("--int-opt").default_value(3).store_into(res); program.parse_args({"./test.exe", "--int-opt", "5"}); REQUIRE(res == 5); } // integral cases TEST_CASE("Test store_into(uint8_t), no default value, non specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); uint8_t res = 55; program.add_argument("--int-opt").store_into(res); program.parse_args({"./test.exe"}); REQUIRE(res == 55); } TEST_CASE("Test store_into(uint8_t), default value, non specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); uint8_t res = 55; program.add_argument("--int-opt").default_value((uint8_t)3).store_into(res); program.parse_args({"./test.exe"}); REQUIRE(res == 3); } TEST_CASE("Test store_into(uint8_t), default value, specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); uint8_t res = 55; program.add_argument("--int-opt").default_value((uint8_t)3).store_into(res); program.parse_args({"./test.exe", "--int-opt", "5"}); REQUIRE(res == 5); } // Double cases TEST_CASE("Test store_into(double), no default value, non specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); double res = -1; program.add_argument("--double-opt").store_into(res); program.parse_args({"./test.exe"}); REQUIRE(res == -1); } TEST_CASE("Test store_into(double), default value, non specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); double res = -1; program.add_argument("--double-opt").default_value(3.5).store_into(res); program.parse_args({"./test.exe"}); REQUIRE(res == 3.5); } TEST_CASE("Test store_into(double), default value, specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); double res = -1; program.add_argument("--double-opt").default_value(3.5).store_into(res); program.parse_args({"./test.exe", "--double-opt", "5.5"}); REQUIRE(res == 5.5); } TEST_CASE("Test store_into(string), no default value, non specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); std::string res = "init"; program.add_argument("--str-opt").store_into(res); program.parse_args({"./test.exe"}); REQUIRE(res == "init"); } TEST_CASE("Test store_into(string), default value, non specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); std::string res; program.add_argument("--str-opt").default_value("default").store_into(res); program.parse_args({"./test.exe"}); REQUIRE(res == "default"); } TEST_CASE("Test store_into(string), default value, specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); std::string res; program.add_argument("--str-opt").default_value("default").store_into(res); program.parse_args({"./test.exe", "--str-opt", "foo"}); REQUIRE(res == "foo"); } TEST_CASE("Test store_into(vector of string), no default value, non specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); std::vector res; program.add_argument("--strvector-opt").append().store_into(res); program.parse_args({"./test.exe"}); REQUIRE(res == std::vector{}); } TEST_CASE("Test store_into(vector of string), default value, non specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); std::vector res; program.add_argument("--strvector-opt").append().default_value( std::vector{"a", "b"}).store_into(res); program.parse_args({"./test.exe"}); REQUIRE(res == std::vector{"a", "b"}); } TEST_CASE("Test store_into(vector of string), default value, specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); std::vector res; program.add_argument("--strvector-opt").append().default_value( std::vector{"a", "b"}).store_into(res); program.parse_args({"./test.exe", "--strvector-opt", "foo", "--strvector-opt", "bar"}); REQUIRE(res == std::vector{"foo", "bar"}); } TEST_CASE("Test store_into(vector of string), default value, multi valued, specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); std::vector res; program.add_argument("--strvector-opt").nargs(2).default_value( std::vector{"a", "b"}).store_into(res); program.parse_args({"./test.exe", "--strvector-opt", "foo", "bar"}); REQUIRE(res == std::vector{"foo", "bar"}); } TEST_CASE("Test store_into(vector of int), no default value, non specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); std::vector res; program.add_argument("--intvector-opt").append().store_into(res); program.parse_args({"./test.exe"}); REQUIRE(res == std::vector{}); } TEST_CASE("Test store_into(vector of int), default value, non specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); std::vector res; program.add_argument("--intvector-opt").append().default_value( std::vector{1, 2}).store_into(res); program.parse_args({"./test.exe"}); REQUIRE(res == std::vector{1, 2}); } TEST_CASE("Test store_into(vector of int), default value, specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); std::vector res; program.add_argument("--intvector-opt").append().default_value( std::vector{1, 2}).store_into(res); program.parse_args({"./test.exe", "--intvector-opt", "3", "--intvector-opt", "4"}); REQUIRE(res == std::vector{3, 4}); } TEST_CASE("Test store_into(vector of int), default value, multi valued, specified" * test_suite("store_into")) { argparse::ArgumentParser program("test"); std::vector res; program.add_argument("--intvector-opt").nargs(2).default_value( std::vector{1, 2}).store_into(res); program.parse_args({"./test.exe", "--intvector-opt", "3", "4"}); REQUIRE(res == std::vector{3, 4}); } TEST_CASE("Test store_into(set of int), default value, multi valued, specified" * test_suite("store_into")) { { argparse::ArgumentParser program("test"); std::set res; program.add_argument("--intset-opt").nargs(2).default_value( std::set{1, 2}).store_into(res); program.parse_args({"./test.exe", "--intset-opt", "3", "4"}); REQUIRE(res == std::set{3, 4}); } { argparse::ArgumentParser program("test"); std::set res; program.add_argument("--intset-opt").nargs(2).default_value( std::set{1, 2}).store_into(res); program.parse_args({"./test.exe"}); REQUIRE(res == std::set{1, 2}); } } TEST_CASE("Test store_into(set of string), default value, multi valued, specified" * test_suite("store_into")) { { argparse::ArgumentParser program("test"); std::set res; program.add_argument("--stringset-opt").nargs(2).default_value( std::set{"1", "2"}).store_into(res); program.parse_args({"./test.exe", "--stringset-opt", "3", "4"}); REQUIRE(res == std::set{"3", "4"}); } { argparse::ArgumentParser program("test"); std::set res; program.add_argument("--stringset-opt").nargs(2).default_value( std::set{"1", "2"}).store_into(res); program.parse_args({"./test.exe"}); REQUIRE(res == std::set{"1", "2"}); } } TEST_CASE("Test store_into(int) still works with a custom action" * test_suite("store_into")) { GIVEN("an argument with store_into followed by a custom action ") { argparse::ArgumentParser program("test"); int res; std::string string_res; program.add_argument("--int").store_into(res).action([&](const auto &s) {string_res.append(s);}); WHEN("the argument is parsed") { program.parse_args({"./test.exe", "--int", "3"}); THEN("the value is stored and the action was executed") { REQUIRE(res == 3); REQUIRE(string_res == "3"); } } } GIVEN("an argument with a custom action followed by store_into") { argparse::ArgumentParser program("test"); int res; std::string string_res; program.add_argument("--int").action([&](const auto &s) {string_res.append(s);}).store_into(res); WHEN("the argument is parsed") { program.parse_args({"./test.exe", "--int", "3"}); THEN("the value is stored and the action was executed") { REQUIRE(res == 3); REQUIRE(string_res == "3"); } } } } argparse-3.2/test/test_stringstream.cpp000066400000000000000000000007441474520133400204360ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include #include #include using doctest::test_suite; TEST_CASE("Get Version String" * test_suite("stringstream")) { std::stringstream os; argparse::ArgumentParser program("test", "1.0", argparse::default_arguments::all, false, os); program.parse_args({"test", "--version"}); REQUIRE(os.str() == "1.0\n"); }argparse-3.2/test/test_subparsers.cpp000066400000000000000000000247321474520133400201100ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include #include #include using doctest::test_suite; TEST_CASE("Add subparsers" * test_suite("subparsers")) { argparse::ArgumentParser program("test"); program.add_argument("--output"); argparse::ArgumentParser command_1("add"); command_1.add_argument("file").nargs(2); argparse::ArgumentParser command_2("clean"); program.add_subparser(command_1); program.add_subparser(command_2); program.parse_args({"test", "--output", "thrust_profile.csv"}); REQUIRE(program.is_subcommand_used("add") == false); REQUIRE(program.get("--output") == "thrust_profile.csv"); } TEST_CASE("Parse subparser command" * test_suite("subparsers")) { argparse::ArgumentParser program("test"); program.add_argument("--output"); argparse::ArgumentParser command_1("add"); command_1.add_argument("file").nargs(2); argparse::ArgumentParser command_2("clean"); command_2.add_argument("--fullclean") .default_value(false) .implicit_value(true); program.add_subparser(command_1); program.add_subparser(command_2); SUBCASE("command 1") { program.parse_args({"test", "add", "file1.txt", "file2.txt"}); REQUIRE(program.is_subcommand_used("add") == true); REQUIRE(command_1.is_used("file")); REQUIRE((command_1.get>("file") == std::vector{"file1.txt", "file2.txt"})); } SUBCASE("command 2") { program.parse_args({"test", "clean", "--fullclean"}); REQUIRE(program.is_subcommand_used("clean") == true); REQUIRE(command_2.get("--fullclean") == true); } } TEST_CASE("Parse subparser command with optional argument" * test_suite("subparsers")) { argparse::ArgumentParser program("test"); program.add_argument("--verbose").flag(); argparse::ArgumentParser command_1("add"); command_1.add_argument("file"); argparse::ArgumentParser command_2("clean"); command_2.add_argument("--fullclean") .default_value(false) .implicit_value(true); program.add_subparser(command_1); program.add_subparser(command_2); SUBCASE("Optional argument BEFORE subcommand") { program.parse_args({"test", "--verbose", "clean", "--fullclean"}); REQUIRE(program.is_subcommand_used("clean") == true); REQUIRE(program.get("--verbose") == true); REQUIRE(command_2.get("--fullclean") == true); } SUBCASE("Optional argument AFTER subcommand") { REQUIRE_THROWS_WITH_AS( program.parse_args({"test", "clean", "--fullclean", "--verbose"}), "Unknown argument: --verbose", std::runtime_error); } } TEST_CASE("Parse subparser command with parent parser" * test_suite("subparsers")) { argparse::ArgumentParser program("test"); argparse::ArgumentParser parent("parent"); parent.add_argument("--verbose").flag(); program.add_parents(parent); argparse::ArgumentParser command_1("add"); command_1.add_argument("file"); argparse::ArgumentParser command_2("clean"); command_2.add_argument("--fullclean") .default_value(false) .implicit_value(true); program.add_subparser(command_1); program.add_subparser(command_2); SUBCASE("Optional argument BEFORE subcommand") { program.parse_args({"test", "--verbose", "clean", "--fullclean"}); REQUIRE(program.is_subcommand_used("clean") == true); REQUIRE(program.get("--verbose") == true); REQUIRE(command_2.get("--fullclean") == true); } SUBCASE("Optional argument AFTER subcommand") { REQUIRE_THROWS_WITH_AS( program.parse_args({"test", "clean", "--fullclean", "--verbose"}), "Unknown argument: --verbose", std::runtime_error); } } TEST_CASE("Parse git commands" * test_suite("subparsers")) { argparse::ArgumentParser program("git"); argparse::ArgumentParser add_command("add"); add_command.add_argument("files").remaining(); argparse::ArgumentParser commit_command("commit"); commit_command.add_argument("-a").flag(); commit_command.add_argument("-m"); argparse::ArgumentParser catfile_command("cat-file"); catfile_command.add_argument("-t"); catfile_command.add_argument("-p"); argparse::ArgumentParser submodule_command("submodule"); argparse::ArgumentParser submodule_update_command("update"); submodule_update_command.add_argument("--init") .default_value(false) .implicit_value(true); submodule_update_command.add_argument("--recursive") .default_value(false) .implicit_value(true); submodule_command.add_subparser(submodule_update_command); program.add_subparser(add_command); program.add_subparser(commit_command); program.add_subparser(catfile_command); program.add_subparser(submodule_command); SUBCASE("git add") { program.parse_args({"git", "add", "main.cpp", "foo.hpp", "foo.cpp"}); REQUIRE(program.is_subcommand_used("add") == true); REQUIRE((add_command.get>("files") == std::vector{"main.cpp", "foo.hpp", "foo.cpp"})); } SUBCASE("git commit") { program.parse_args({"git", "commit", "-am", "Initial commit"}); REQUIRE(program.is_subcommand_used("commit") == true); REQUIRE(commit_command.get("-a") == true); REQUIRE(commit_command.get("-m") == std::string{"Initial commit"}); } SUBCASE("git cat-file -t") { program.parse_args({"git", "cat-file", "-t", "3739f5"}); REQUIRE(program.is_subcommand_used("cat-file") == true); REQUIRE(catfile_command.get("-t") == std::string{"3739f5"}); } SUBCASE("git cat-file -p") { program.parse_args({"git", "cat-file", "-p", "3739f5"}); REQUIRE(program.is_subcommand_used("cat-file") == true); REQUIRE(catfile_command.get("-p") == std::string{"3739f5"}); } SUBCASE("git submodule update") { program.parse_args({"git", "submodule", "update"}); REQUIRE(program.is_subcommand_used("submodule") == true); REQUIRE(submodule_command.is_subcommand_used("update") == true); } SUBCASE("git submodule update --init") { program.parse_args({"git", "submodule", "update", "--init"}); REQUIRE(program.is_subcommand_used("submodule") == true); REQUIRE(submodule_command.is_subcommand_used("update") == true); REQUIRE(submodule_update_command.get("--init") == true); REQUIRE(submodule_update_command.get("--recursive") == false); } SUBCASE("git submodule update --recursive") { program.parse_args({"git", "submodule", "update", "--recursive"}); REQUIRE(program.is_subcommand_used("submodule") == true); REQUIRE(submodule_command.is_subcommand_used("update") == true); REQUIRE(submodule_update_command.get("--recursive") == true); } SUBCASE("git submodule update --init --recursive") { program.parse_args({"git", "submodule", "update", "--init", "--recursive"}); REQUIRE(program.is_subcommand_used("submodule") == true); REQUIRE(submodule_command.is_subcommand_used("update") == true); REQUIRE(submodule_update_command.get("--init") == true); REQUIRE(submodule_update_command.get("--recursive") == true); } } TEST_CASE("Check is_subcommand_used after parse" * test_suite("subparsers")) { argparse::ArgumentParser command_1("add"); argparse::ArgumentParser command_2("clean"); command_2.add_argument("--fullclean") .default_value(false) .implicit_value(true); argparse::ArgumentParser program("test"); program.add_subparser(command_1); program.add_subparser(command_2); SUBCASE("command 1") { program.parse_args({"test", "add"}); REQUIRE(program.is_subcommand_used("add") == true); REQUIRE(program.is_subcommand_used(command_1) == true); REQUIRE(program.is_subcommand_used("clean") == false); REQUIRE(program.is_subcommand_used(command_2) == false); } SUBCASE("command 2") { program.parse_args({"test", "clean", "--fullclean"}); REQUIRE(program.is_subcommand_used("add") == false); REQUIRE(program.is_subcommand_used(command_1) == false); REQUIRE(program.is_subcommand_used("clean") == true); REQUIRE(program.is_subcommand_used(command_2) == true); } SUBCASE("none") { program.parse_args({"test"}); REQUIRE(program.is_subcommand_used("add") == false); REQUIRE(program.is_subcommand_used(command_1) == false); REQUIRE(program.is_subcommand_used("clean") == false); REQUIRE(program.is_subcommand_used(command_2) == false); } } static bool contains(const std::string &haystack, const std::string &needle) { return haystack.find(needle) != std::string::npos; } TEST_CASE("Check set_suppress" * test_suite("subparsers")) { argparse::ArgumentParser command("cmd"); command.add_argument("arg").remaining(); argparse::ArgumentParser program("test"); program.add_subparser(command); SUBCASE("help message contain info if subcommand not suppressed") { command.set_suppress(false); REQUIRE(contains(program.help().str(), "Subcommands") == true); REQUIRE(contains(program.help().str(), "cmd") == true); } SUBCASE("help message does not contain info if subcommand suppressed") { command.set_suppress(true); REQUIRE(contains(program.help().str(), "Subcommands") == false); REQUIRE(contains(program.help().str(), "cmd") == false); } SUBCASE("help message contain info if not all subcommands suppressed") { argparse::ArgumentParser command_2("command_2"); program.add_subparser(command_2); command.set_suppress(true); command_2.set_suppress(false); REQUIRE(contains(program.help().str(), "Subcommands") == true); REQUIRE(contains(program.help().str(), "cmd") == false); REQUIRE(contains(program.help().str(), "command_2") == true); } } TEST_CASE("Help of subparsers" * test_suite("subparsers")) { argparse::ArgumentParser program("test"); argparse::ArgumentParser command_1("add", "1.0", argparse::default_arguments::version); std::stringstream buffer; command_1.add_argument("--help") .action([&](const auto &) { buffer << command_1; }) .default_value(false) .implicit_value(true) .nargs(0); program.add_subparser(command_1); REQUIRE(command_1.usage() == "Usage: test add [--version] [--help]"); REQUIRE(buffer.str().empty()); program.parse_args({"test", "add", "--help"}); REQUIRE(buffer.str() == "Usage: test add [--version] [--help]\n" "\n" "Optional arguments:\n" " -v, --version prints version information and exits \n" " --help \n"); } argparse-3.2/test/test_utility.hpp000066400000000000000000000006361474520133400174240ustar00rootroot00000000000000#ifndef ARGPARSE_TEST_UTILITY_HPP #define ARGPARSE_TEST_UTILITY_HPP #include namespace testutility { // Get value at index from std::list template T get_from_list(const std::list& aList, size_t aIndex) { if (aList.size() > aIndex) { auto tIterator = aList.begin(); std::advance(tIterator, aIndex); return *tIterator; } return T(); } } #endif //ARGPARSE_TEST_UTILITY_HPP argparse-3.2/test/test_version.cpp000066400000000000000000000025141474520133400173760ustar00rootroot00000000000000#ifdef WITH_MODULE import argparse; #else #include #endif #include #include using doctest::test_suite; TEST_CASE("Users can print version and exit" * test_suite("version") * doctest::skip()) { argparse::ArgumentParser program("cli-test", "1.9.0"); program.add_argument("-d", "--dir").required(); program.parse_args({"test", "--version"}); REQUIRE(program.get("--version") == "1.9.0"); } TEST_CASE("Users can disable default -v/--version" * test_suite("version")) { argparse::ArgumentParser program("test", "1.0", argparse::default_arguments::help); REQUIRE_THROWS_WITH_AS(program.parse_args({"test", "--version"}), "Unknown argument: --version", std::runtime_error); } TEST_CASE("Users can replace default -v/--version" * test_suite("version")) { std::string version{"3.1415"}; argparse::ArgumentParser program("test", version, argparse::default_arguments::help); std::stringstream buffer; program.add_argument("-v", "--version") .action([&](const auto &) { buffer << version; }) .default_value(true) .implicit_value(false) .nargs(0); REQUIRE(buffer.str().empty()); program.parse_args({"test", "--version"}); REQUIRE_FALSE(buffer.str().empty()); } argparse-3.2/tools/000077500000000000000000000000001474520133400143255ustar00rootroot00000000000000argparse-3.2/tools/build.bat000066400000000000000000000003031474520133400161100ustar00rootroot00000000000000call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat" cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DARGPARSE_BUILD_TESTS=ON ninja -C build argparse-3.2/tools/build.sh000066400000000000000000000001471474520133400157620ustar00rootroot00000000000000#!/bin/sh cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DARGPARSE_BUILD_TESTS=ON ninja -C build argparse-3.2/xmake.lua000066400000000000000000000043711474520133400150020ustar00rootroot00000000000000set_xmakever("2.8.2") set_project("argparse") set_version("3.2.0", { build = "%Y%m%d%H%M" }) option("enable_module") option("enable_std_import", { defines = "ARGPARSE_MODULE_USE_STD_MODULE" }) option("enable_tests") option("enable_samples") add_cxxflags( "-Wall", "-Wno-long-long", "-pedantic", "-Wsign-conversion", "-Wshadow", "-Wconversion", { toolsets = { "clang", "gcc" } } ) add_cxxflags("cl::/W4") if is_plat("windows") then add_defines("_CRT_SECURE_NO_WARNINGS") end target("argparse", function() set_languages("c++17") set_kind("headeronly") if get_config("enable_module") then set_languages("c++20") set_kind("static") -- static atm because of a XMake bug, headeronly doesn't generate package module metadata end add_options("enable_std_import") add_includedirs("include", { public = true }) add_headerfiles("include/argparse/argparse.hpp") if get_config("enable_module") then add_files("module/argparse.cppm", { install = true }) end end) if get_config("enable_tests") then target("argparse_tests", function() set_kind("binary") set_languages("c++17") set_basename("tests") add_includedirs("test") add_files("test/main.cpp", { defines = { "DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN" } }) add_files("test/**.cpp") add_deps("argparse") end) if get_config("enable_module") then target("argparse_module_tests", function() set_kind("binary") set_languages("c++20") set_basename("module_tests") add_defines("WITH_MODULE") add_includedirs("test") add_files("test/main.cpp", { defines = { "DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN" } }) add_files("test/**.cpp") add_files("test/argparse_details.cppm") add_deps("argparse") end) end end if get_config("enable_samples") then for _, sample_file in ipairs(os.files("samples/*.cpp")) do target(path.basename(sample_file), function() set_kind("binary") set_languages("c++17") add_files(sample_file) set_policy("build.c++.modules", false) add_deps("argparse") end) end end