This commit is contained in:
2026-09-13 13:30:21 +08:00
commit a6bbd520cf
2744 changed files with 1598795 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
- I../ build / googletest - src / googletest / include / -I../ build / googletest - src / googlemock / include /
File diff suppressed because it is too large Load Diff
+137
View File
@@ -0,0 +1,137 @@
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
cc_binary(
name = "ensure_utf8",
srcs = ["applications/ensure_utf8.cpp"],
deps = ["//:cli11"],
)
cc_binary(
name = "ensure_utf8_twice",
srcs = ["applications/ensure_utf8_twice.cpp"],
deps = ["//:cli11"],
)
cc_library(
name = "catch_main",
srcs = ["main.cpp"],
hdrs = ["catch.hpp"],
defines = ["CLI11_CATCH3"],
deps = ["@catch2//:catch2_main"],
)
cc_test(
name = "AppTest",
srcs = [
"AppTest.cpp",
"app_helper.hpp",
],
data = [
"ensure_utf8",
"ensure_utf8_twice",
],
local_defines = [
'CLI11_ENSURE_UTF8_EXE=\\"$(rootpath ensure_utf8)\\"',
'CLI11_ENSURE_UTF8_TWICE_EXE=\\"$(rootpath ensure_utf8_twice)\\"',
],
deps = [
"catch_main",
"//:cli11",
"@catch2",
],
linkopts = select({
"@rules_cc//cc/compiler:msvc-cl": [],
"@rules_cc//cc/compiler:clang-cl": [],
"//conditions:default": ["-pthread"],
}),
)
cc_test(
name = "TimerTest",
srcs = ["TimerTest.cpp"],
deps = [
"catch_main",
"//:cli11",
"@catch2",
],
linkopts = select({
"@rules_cc//cc/compiler:msvc-cl": [],
"@rules_cc//cc/compiler:clang-cl": [],
"//conditions:default": ["-pthread"],
}),
)
# Verifies there are no unguarded inlines (duplicate symbols across TUs)
[
(
cc_library(
name = "link_test_1" + suffix,
srcs = ["link_test_1.cpp"],
deps = [lib],
),
cc_test(
name = "link_test_2" + suffix,
srcs = ["link_test_2.cpp"],
deps = [
"catch_main",
"link_test_1" + suffix,
lib,
"@catch2",
],
),
)
for suffix, lib in [
("", "//:cli11"),
("_header_only", "//:cli11_header_only"),
]
]
cc_test(
name = "SimpleTestHeaderOnly",
srcs = [
"SimpleTest.cpp",
"app_helper.hpp",
],
deps = [
"catch_main",
"//:cli11_header_only",
"@catch2",
],
)
[
cc_test(
name = test,
srcs = [
test + ".cpp",
"app_helper.hpp",
],
deps = [
"catch_main",
"//:cli11",
"@catch2",
],
)
for test in [
"HelpersTest",
"ConfigFileTest",
"OptionTypeTest",
"SimpleTest",
"SetTest",
"NumericTypeTest",
"TransformTest",
"CreationTest",
"SubcommandTest",
"HelpTest",
"FormatterTest",
"NewParseTest",
"OptionalTest",
"DeprecatedTest",
"StringParseTest",
"ComplexTypeTest",
"TrueFalseTest",
"OptionGroupTest",
"ExtraValidatorsTest",
"localeTest",
"EncodingTest",
]
]
+126
View File
@@ -0,0 +1,126 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>
#include <boost/container/slist.hpp>
#include <boost/container/small_vector.hpp>
#include <boost/container/stable_vector.hpp>
#include <boost/container/static_vector.hpp>
#include <boost/container/vector.hpp>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
TEMPLATE_TEST_CASE("Boost container single",
"[boost][optional]",
(boost::container::small_vector<int, 2>),
(boost::container::small_vector<int, 3>),
boost::container::flat_set<int>,
boost::container::stable_vector<int>,
boost::container::slist<int>) {
TApp tapp;
TestType cv;
CLI::Option *opt = tapp.app.add_option("-v", cv);
tapp.args = {"-v", "1", "-1", "-v", "3", "-v", "-976"};
tapp.run();
CHECK(tapp.app.count("-v") == 4u);
CHECK(cv.size() == 4u);
opt->check(CLI::PositiveNumber.application_index(0));
opt->check((!CLI::PositiveNumber).application_index(1));
CHECK_NOTHROW(tapp.run());
CHECK(cv.size() == 4u);
// v[3] would be negative
opt->check(CLI::PositiveNumber.application_index(3));
CHECK_THROWS_AS(tapp.run(), CLI::ValidationError);
}
using isp = std::pair<int, std::string>;
TEMPLATE_TEST_CASE("Boost container pair",
"[boost][optional]",
boost::container::stable_vector<isp>,
(boost::container::small_vector<isp, 2>),
boost::container::flat_set<isp>,
boost::container::slist<isp>,
boost::container::vector<isp>,
(boost::container::flat_map<int, std::string>)) {
TApp tapp;
TestType cv;
tapp.app.add_option("--dict", cv);
tapp.args = {"--dict", "1", "str1", "--dict", "3", "str3"};
tapp.run();
CHECK(2u == cv.size());
tapp.args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"};
tapp.run();
CHECK(3u == cv.size());
}
using tup_obj = std::tuple<int, std::string, double>;
TEMPLATE_TEST_CASE("Boost container tuple",
"[boost][optional]",
(boost::container::small_vector<tup_obj, 3>),
boost::container::stable_vector<tup_obj>,
boost::container::flat_set<tup_obj>,
boost::container::slist<tup_obj>) {
TApp tapp;
TestType cv;
tapp.app.add_option("--dict", cv);
tapp.args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"};
tapp.run();
CHECK(2u == cv.size());
tapp.args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"};
tapp.run();
CHECK(3u == cv.size());
}
using icontainer1 = boost::container::vector<int>;
using icontainer2 = boost::container::flat_set<int>;
using icontainer3 = boost::container::slist<int>;
TEMPLATE_TEST_CASE("Boost container container",
"[boost][optional]",
std::vector<icontainer1>,
boost::container::slist<icontainer1>,
boost::container::flat_set<icontainer1>,
(boost::container::small_vector<icontainer1, 2>),
std::vector<icontainer2>,
boost::container::slist<icontainer2>,
boost::container::flat_set<icontainer2>,
boost::container::stable_vector<icontainer2>,
(boost::container::static_vector<icontainer2, 10>),
boost::container::slist<icontainer3>,
boost::container::flat_set<icontainer3>,
(boost::container::static_vector<icontainer3, 10>)) {
TApp tapp;
TestType cv;
tapp.app.add_option("--dict", cv);
tapp.args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
tapp.run();
CHECK(2u == cv.size());
tapp.args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict",
"3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"};
tapp.run();
CHECK(4u == cv.size());
}
+434
View File
@@ -0,0 +1,434 @@
# Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
# under NSF AWARD 1414736 and by the respective contributors.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
if(CLI11_SANITIZERS AND ${CMAKE_VERSION} VERSION_GREATER "3.15")
message(STATUS "Using arsenm/sanitizers-cmake")
FetchContent_Declare(
sanitizers
GIT_REPOSITORY https://github.com/arsenm/sanitizers-cmake.git
GIT_SHALLOW 1
GIT_TAG bcb1fc6)
FetchContent_MakeAvailable(sanitizers)
list(APPEND CMAKE_MODULE_PATH "${sanitizers_SOURCE_DIR}/cmake")
find_package(Sanitizers)
if(SANITIZE_ADDRESS)
message(STATUS "You might want to use \"${ASan_WRAPPER}\" to run your program")
endif()
else()
macro(add_sanitizers)
endmacro()
endif()
# Add boost to test boost::optional (currently explicitly requested)"
option(CLI11_BOOST "Turn on boost test (currently may fail with Boost 1.70)" OFF)
if(CLI11_BOOST)
find_package(Boost 1.61 QUIET CONFIG)
if(NOT Boost_FOUND)
find_package(Boost 1.61 QUIET)
endif()
if(NOT Boost_FOUND)
message(FATAL_ERROR "Boost 1.61+ requested via CLI11_BOOST, but not found.")
endif()
endif()
set(boost-optional-def $<$<BOOL:${Boost_FOUND}>:CLI11_BOOST_OPTIONAL>)
set(CLI11_TESTS
HelpersTest
ConfigFileTest
OptionTypeTest
NumericTypeTest
SimpleTest
AppTest
SetTest
TransformTest
CreationTest
SubcommandTest
HelpTest
FormatterTest
NewParseTest
OptionalTest
DeprecatedTest
StringParseTest
ComplexTypeTest
TrueFalseTest
localeTest
OptionGroupTest
ExtraValidatorsTest
EncodingTest)
if(WIN32)
list(APPEND CLI11_TESTS WindowsTest)
endif()
if(CMAKE_CXX_STANDARD GREATER 16)
list(APPEND CLI11_TESTS FuzzFailTest)
endif()
if(Boost_FOUND)
list(APPEND CLI11_TESTS BoostOptionTypeTest)
endif()
set(CLI11_MULTIONLY_TESTS TimerTest)
find_package(Catch2 CONFIG QUIET)
if(Catch2_FOUND)
if(NOT TARGET Catch2::Catch2)
message(FATAL_ERROR "Found Catch2 at ${Catch2_DIR} but targets are missing.")
endif()
message(STATUS "Found Catch2 ${Catch2_VERSION}")
if(Catch2_VERSION VERSION_LESS 3)
add_library(catch_main main.cpp catch.hpp)
target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(catch_main PUBLIC Catch2::Catch2)
else()
add_library(catch_main ALIAS Catch2::Catch2WithMain)
target_compile_definitions(Catch2::Catch2WithMain INTERFACE -DCLI11_CATCH3)
endif()
else()
set(catch2_url https://github.com/philsquared/Catch/releases/download/v2.13.10/catch.hpp)
set(catch2_header "${CMAKE_CURRENT_BINARY_DIR}/catch2/catch.hpp")
set(catch2_hash "3725c0f0a75f376a5005dde31ead0feb8f7da7507644c201b814443de8355170")
if(EXISTS "${catch2_header}")
file(SHA256 "${catch2_header}" catch2_cached_hash)
endif()
if(EXISTS "${catch2_header}" AND catch2_cached_hash STREQUAL catch2_hash)
message(STATUS "Using cached Catch2 header")
else()
message(STATUS "Catch2 not found; downloading Catch2")
# FetchContent would be better, but requires newer CMake.
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/catch2")
file(
DOWNLOAD ${catch2_url} "${catch2_header}"
STATUS status
EXPECTED_HASH SHA256=${catch2_hash})
list(GET status 0 error)
if(error)
message(FATAL_ERROR "Could not download ${catch2_url}, and Catch2 not found on your system.")
endif()
endif()
add_library(catch_main main.cpp catch.hpp)
target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_BINARY_DIR}")
endif()
# Add special target that copies the data directory for tests
file(
GLOB_RECURSE DATA_FILES
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/data/*" "${CMAKE_CURRENT_SOURCE_DIR}/tests/.gitkeep")
foreach(DATA_FILE IN LISTS DATA_FILES)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${DATA_FILE}"
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/${DATA_FILE}"
"${CMAKE_CURRENT_BINARY_DIR}/${DATA_FILE}"
MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/${DATA_FILE}"
VERBATIM)
endforeach()
add_custom_target(cli11_test_data DEPENDS ${DATA_FILES})
set_target_properties(cli11_test_data PROPERTIES FOLDER "Tests/Apps")
# Make a shim if we are building single file tests
if(CLI11_SINGLE_FILE AND CLI11_INSTALL_PACKAGE_TESTS)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test_single/CLI/CLI.hpp" "#include <CLI11.hpp>")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/test_single/CLI/CLI.hpp" DESTINATION include/CLI)
endif()
# Build dependent applications which are launched from test code
set(CLI11_DEPENDENT_APPLICATIONS ensure_utf8 ensure_utf8_twice)
foreach(APP IN LISTS CLI11_DEPENDENT_APPLICATIONS)
add_executable(${APP} applications/${APP}.cpp)
target_include_directories(${APP} PRIVATE ${CMAKE_SOURCE_DIR}/include)
set_target_properties(${APP} PROPERTIES FOLDER "Tests/Apps")
endforeach()
function(add_dependent_application_definitions TARGET)
foreach(APP IN LISTS CLI11_DEPENDENT_APPLICATIONS)
string(TOUPPER ${APP} APP_UPPERCASE)
target_compile_definitions(${TARGET}
PRIVATE CLI11_${APP_UPPERCASE}_EXE="$<TARGET_FILE:${APP}>")
if(WIN32)
target_link_libraries(${APP} PRIVATE shell32)
endif()
endforeach()
endfunction()
# Target must already exist
macro(add_catch_test TESTNAME)
target_link_libraries(${TESTNAME} PUBLIC catch_main)
add_dependencies(${TESTNAME} cli11_test_data)
add_test(${TESTNAME} ${TESTNAME})
set_target_properties(${TESTNAME} PROPERTIES FOLDER "Tests")
if(CLI11_FORCE_LIBCXX)
set_property(
TARGET ${TESTNAME}
APPEND_STRING
PROPERTY LINK_FLAGS -stdlib=libc++)
target_link_libraries(${TESTNAME} PUBLIC c++abi)
endif()
endmacro()
foreach(T IN LISTS CLI11_TESTS)
if(CLI11_CUDA_TESTS)
set_property(SOURCE ${T}.cpp PROPERTY LANGUAGE CUDA)
endif()
add_executable(${T} ${T}.cpp)
add_dependent_application_definitions(${T})
add_sanitizers(${T})
if(NOT CLI11_CUDA_TESTS)
target_link_libraries(${T} PRIVATE CLI11_warnings)
endif()
target_link_libraries(${T} PRIVATE CLI11)
add_catch_test(${T})
if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS)
add_executable(${T}_Single ${T}.cpp)
add_dependent_application_definitions(${T}_Single)
target_link_libraries(${T}_Single PRIVATE CLI11_SINGLE)
add_catch_test(${T}_Single)
set_property(TARGET ${T}_Single PROPERTY FOLDER "Tests Single File")
endif()
endforeach()
foreach(T IN LISTS CLI11_MULTIONLY_TESTS)
add_executable(${T} ${T}.cpp)
add_dependent_application_definitions(${T})
add_sanitizers(${T})
target_link_libraries(${T} PUBLIC CLI11)
add_catch_test(${T})
endforeach()
if(CMAKE_CXX_STANDARD GREATER 16)
set(TEST_FILE_FOLDER ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(FuzzFailTest PUBLIC -DTEST_FILE_FOLDER="${TEST_FILE_FOLDER}")
target_sources(FuzzFailTest PUBLIC ${PROJECT_SOURCE_DIR}/fuzz/fuzzApp.cpp)
if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS)
target_compile_definitions(FuzzFailTest_Single PUBLIC -DTEST_FILE_FOLDER="${TEST_FILE_FOLDER}")
target_sources(FuzzFailTest_Single PUBLIC ${PROJECT_SOURCE_DIR}/fuzz/fuzzApp.cpp)
endif()
# Some platforms need to link to atomic
file(WRITE "${CMAKE_BINARY_DIR}/test_atomic.cpp"
"#include <atomic>\n" "int main() { std::atomic<int64_t> i(0); i++; return 0; }\n")
try_compile(ATOMIC_BUILD_SUCCEEDED "${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/test_atomic.cpp")
if(NOT ATOMIC_BUILD_SUCCEEDED)
target_link_libraries(FuzzFailTest PRIVATE atomic)
if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS)
target_link_libraries(FuzzFailTestSingle PRIVATE atomic)
endif()
endif()
file(REMOVE ${CMAKE_BINARY_DIR}/test_atomic.cpp)
endif()
find_package(Threads)
target_link_libraries(AppTest PRIVATE Threads::Threads)
# Add -Wno-deprecated-declarations to DeprecatedTest
set(no-deprecated-declarations $<$<CXX_COMPILER_ID:MSVC>:/wd4996>
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wno-deprecated-declarations>)
target_compile_options(DeprecatedTest PRIVATE ${no-deprecated-declarations})
if(TARGET DeprecatedTest_Single)
target_compile_options(DeprecatedTest_Single PRIVATE ${no-deprecated-declarations})
endif()
# Link test (build error if inlines missing)
add_library(link_test_1 link_test_1.cpp)
target_link_libraries(link_test_1 PUBLIC CLI11)
set_target_properties(link_test_1 PROPERTIES FOLDER "Tests")
add_executable(link_test_2 link_test_2.cpp)
target_link_libraries(link_test_2 PUBLIC CLI11 link_test_1)
add_catch_test(link_test_2)
if(CLI11_FORCE_LIBCXX)
set_property(
TARGET link_test_1
APPEND_STRING
PROPERTY LINK_FLAGS -stdlib=libc++)
target_link_libraries(link_test_1 PUBLIC c++abi)
endif()
# Add informational printout
add_executable(informational informational.cpp)
target_link_libraries(informational PUBLIC CLI11)
if(CLI11_FORCE_LIBCXX)
set_property(
TARGET informational
APPEND_STRING
PROPERTY LINK_FLAGS "-stdlib=libc++")
target_link_libraries(informational PUBLIC c++abi)
endif()
set_target_properties(informational PROPERTIES FOLDER "Tests/Apps")
# Force this to be in a standard location so CTest can find it
set_target_properties(
informational
PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}"
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}"
RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}"
RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}")
# Adding this printout to CTest
file(WRITE "${PROJECT_BINARY_DIR}/CTestCustom.cmake"
"set(CTEST_CUSTOM_PRE_TEST \"${CMAKE_BINARY_DIR}/informational\")")
target_compile_definitions(informational PRIVATE ${boost-optional-def})
target_compile_definitions(OptionalTest PRIVATE ${boost-optional-def})
if(TARGET Boost::headers)
set(cli11_boost_target Boost::headers)
elseif(TARGET Boost::boost)
set(cli11_boost_target Boost::boost)
endif()
if(DEFINED cli11_boost_target)
message(STATUS "Using Boost target ${cli11_boost_target}")
target_link_libraries(informational PRIVATE ${cli11_boost_target})
target_link_libraries(OptionalTest PRIVATE ${cli11_boost_target})
target_link_libraries(BoostOptionTypeTest PRIVATE ${cli11_boost_target})
if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS)
target_link_libraries(OptionalTest_Single PRIVATE ${cli11_boost_target})
target_link_libraries(BoostOptionTypeTest_Single PRIVATE ${cli11_boost_target})
endif()
message(STATUS "Boost includes=${Boost_INCLUDE_DIRS}")
elseif(Boost_FOUND)
message(STATUS "Using Boost include directories")
target_include_directories(informational PRIVATE ${Boost_INCLUDE_DIRS})
target_include_directories(OptionalTest PRIVATE ${Boost_INCLUDE_DIRS})
target_include_directories(BoostOptionTypeTest PRIVATE ${Boost_INCLUDE_DIRS})
if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS)
target_include_directories(OptionalTest_Single PRIVATE ${Boost_INCLUDE_DIRS})
target_include_directories(BoostOptionTypeTest_Single PRIVATE ${Boost_INCLUDE_DIRS})
endif()
message(STATUS "Boost includes=${Boost_INCLUDE_DIRS}")
elseif(CLI11_BOOST)
message(STATUS "Boost not found, not adding boost tests")
endif()
if(CMAKE_BUILD_TYPE STREQUAL Coverage)
include(CodeCoverage)
setup_target_for_coverage_lcov(
NAME
CLI11_coverage
EXECUTABLE
ctest
DEPENDENCIES
${CLI11_TESTS}
${CLI11_MULTIONLY_TESTS})
endif()
set(CLI11_PACKAGE_SEARCH_LOC "")
# tests of the cmake package and pkg-config package
if(CLI11_INSTALL_PACKAGE_TESTS)
if(NOT MSVC)
set(package_test_command --test-command "${CMAKE_CTEST_COMMAND}")
else() # don't try to run the tests on MSVC since that would require copying the dll's and doing
# some other setup that isn't that important to run on all OS
set(package_test_command)
endif()
if(CMAKE_BUILD_TYPE)
set(CLI11_PACKAGE_TEST_BUILD_TYPE ${CMAKE_BUILD_TYPE})
else()
set(CLI11_PACKAGE_TEST_BUILD_TYPE Release)
endif()
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/find_package_tests")
if(MSVC)
# Tests for other CMake projects including and using CLI11 using find_package
add_test(
NAME find-package-testsA
COMMAND
${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" -A "${CMAKE_GENERATOR_PLATFORM}"
"-DCLI11_DIR=${CMAKE_INSTALL_PREFIX}" "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
"-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}"
"${CMAKE_CURRENT_SOURCE_DIR}/find_package_tests"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/find_package_tests")
else()
add_test(
NAME find-package-testsA
COMMAND
${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" "-DCLI11_DIR=${CMAKE_INSTALL_PREFIX}"
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
"-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}"
"${CMAKE_CURRENT_SOURCE_DIR}/find_package_tests"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/find_package_tests")
endif()
add_test(NAME find-package-testsB
COMMAND ${CMAKE_COMMAND} --build "${CMAKE_CURRENT_BINARY_DIR}/find_package_tests"
--config ${CLI11_PACKAGE_TEST_BUILD_TYPE})
add_test(
NAME find-package-testsC
COMMAND ${CMAKE_CTEST_COMMAND} -C ${CLI11_PACKAGE_TEST_BUILD_TYPE}
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/find_package_tests")
set_property(TEST find-package-testsA PROPERTY LABELS Packaging)
set_property(TEST find-package-testsB PROPERTY LABELS Packaging)
set_property(TEST find-package-testsB PROPERTY DEPENDS find-package-testsA)
set_property(TEST find-package-testsC PROPERTY LABELS Packaging)
set_property(TEST find-package-testsC PROPERTY DEPENDS find-package-testsB)
if(CLI11_MODULE_TESTS)
add_test(
find-package-module
${CMAKE_CTEST_COMMAND}
--build-and-test
"${CMAKE_CURRENT_SOURCE_DIR}/module_test"
"${CMAKE_CURRENT_BINARY_DIR}/module_test"
--build-generator
"Ninja"
--build-options
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
"-DCLI11_DIR=${CMAKE_INSTALL_PREFIX}"
"-DCLI11_MODULES=${CLI11_MODULES}"
"-DCLI11_FORCE_LIBCXX=${CLI11_FORCE_LIBCXX}"
"-DCMAKE_LINKER_TYPE=${CMAKE_LINKER_TYPE}"
"-DCMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS}"
"-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
${package_test_command})
set_property(TEST find-package-module PROPERTY LABELS Packaging)
endif()
if(NOT MSVC)
# Tests for other CMake projects using the package_config files
add_test(
package-config-tests
${CMAKE_CTEST_COMMAND}
--build-and-test
"${CMAKE_CURRENT_SOURCE_DIR}/package_config_tests"
"${CMAKE_CURRENT_BINARY_DIR}/package_config_tests"
--build-generator
"${CMAKE_GENERATOR}"
--build-generator-platform
"${CMAKE_GENERATOR_PLATFORM}"
--build-options
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
"-DCLI11_DIR=${CMAKE_INSTALL_PREFIX}"
${package_test_command})
set_property(TEST package-config-tests PROPERTY LABELS Packaging)
endif()
endif()
+190
View File
@@ -0,0 +1,190 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include <complex>
#include <cstdint>
#include <string>
using cx = std::complex<double>;
CLI::Option *
add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) {
CLI::callback_t fun = [&variable](CLI::results_t res) {
double x = 0, y = 0;
bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y);
if(worked)
variable = cx(x, y);
return worked;
};
CLI::Option *opt = app.add_option(name, fun, description, defaulted);
opt->type_name("COMPLEX")->type_size(2);
if(defaulted) {
std::stringstream out;
out << variable;
opt->default_str(out.str());
}
return opt;
}
TEST_CASE_METHOD(TApp, "AddingComplexParser", "[complex]") {
cx comp{0, 0};
add_option(app, "-c,--complex", comp);
args = {"-c", "1.5", "2.5"};
run();
CHECK(comp.real() == Approx(1.5));
CHECK(comp.imag() == Approx(2.5));
}
TEST_CASE_METHOD(TApp, "DefaultedComplex", "[complex]") {
cx comp{1, 2};
add_option(app, "-c,--complex", comp, "", true);
args = {"-c", "4", "3"};
std::string help = app.help();
CHECK_THAT(help, Contains("1"));
CHECK_THAT(help, Contains("2"));
CHECK(comp.real() == Approx(1));
CHECK(comp.imag() == Approx(2));
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(3));
}
// an example of custom complex number converter that can be used to add new parsing options
#if defined(__has_include)
#if __has_include(<regex>)
// an example of custom converter that can be used to add new parsing options
#define HAS_REGEX_INCLUDE
#endif
#endif
#ifdef HAS_REGEX_INCLUDE
// Gcc 4.8 and older and the corresponding standard libraries have a broken <regex> so this would
// fail. And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to
// simplify compilation and prevent a much more complicated #if expression
#include <regex>
namespace CLI {
namespace detail {
// On MSVC and possibly some other new compilers this can be a free standing function without the template
// specialization but this is compiler dependent
template <> bool lexical_cast<std::complex<double>>(const std::string &input, std::complex<double> &output) {
// regular expression to handle complex numbers of various formats
static const std::regex creg(
R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
std::smatch m;
double x{0.0}, y{0.0};
bool worked = false;
std::regex_search(input, m, creg);
if(m.size() == 9) {
worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y);
if(worked) {
if(*m[5].first == '-') {
y = -y;
}
}
} else {
if((input.back() == 'j') || (input.back() == 'i')) {
auto strval = input.substr(0, input.size() - 1);
CLI::detail::trim(strval);
worked = CLI::detail::lexical_cast(strval, y);
} else {
std::string ival = input;
CLI::detail::trim(ival);
worked = CLI::detail::lexical_cast(ival, x);
}
}
if(worked) {
output = cx{x, y};
}
return worked;
}
} // namespace detail
} // namespace CLI
TEST_CASE_METHOD(TApp, "AddingComplexParserDetail", "[complex]") {
bool skip_tests = false;
try { // check if the library actually supports regex, it is possible to link against a non working regex in the
// standard library
std::smatch m;
std::string input = "1.5+2.5j";
static const std::regex creg(
R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
auto rsearch = std::regex_search(input, m, creg);
if(!rsearch) {
skip_tests = true;
} else {
CHECK(9u == m.size());
}
} catch(...) {
skip_tests = true;
}
static_assert(CLI::detail::is_complex<cx>::value, "complex should register as complex in this situation");
if(!skip_tests) {
cx comp{0, 0};
app.add_option("-c,--complex", comp, "add a complex number option");
args = {"-c", "1.5+2.5j"};
run();
CHECK(comp.real() == Approx(1.5));
CHECK(comp.imag() == Approx(2.5));
args = {"-c", "1.5-2.5j"};
run();
CHECK(comp.real() == Approx(1.5));
CHECK(comp.imag() == Approx(-2.5));
}
}
#endif
// defining a new complex class
class complex_new {
public:
complex_new() = default;
complex_new(double v1, double v2) : val1_{v1}, val2_{v2} {};
CLI11_NODISCARD double real() const { return val1_; }
CLI11_NODISCARD double imag() const { return val2_; }
private:
double val1_{0.0};
double val2_{0.0};
};
TEST_CASE_METHOD(TApp, "newComplex", "[complex]") {
complex_new cval;
static_assert(CLI::detail::is_complex<complex_new>::value, "complex new does not register as a complex type");
static_assert(CLI::detail::classify_object<complex_new>::value == CLI::detail::object_category::complex_number,
"complex new does not result in complex number categorization");
app.add_option("-c,--complex", cval, "add a complex number option");
args = {"-c", "1.5+2.5j"};
run();
CHECK(cval.real() == Approx(1.5));
CHECK(cval.imag() == Approx(2.5));
args = {"-c", "1.5-2.5j"};
run();
CHECK(cval.real() == Approx(1.5));
CHECK(cval.imag() == Approx(-2.5));
}
File diff suppressed because it is too large Load Diff
+876
View File
@@ -0,0 +1,876 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include <algorithm>
#include <cstdlib>
#include <string>
#include <vector>
TEST_CASE_METHOD(TApp, "AddingExistingShort", "[creation]") {
CLI::Option *opt = app.add_flag("-c,--count");
CHECK(std::vector<std::string>({"count"}) == opt->get_lnames());
CHECK(std::vector<std::string>({"c"}) == opt->get_snames());
CHECK_THROWS_AS(app.add_flag("--cat,-c"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "AddingExistingLong", "[creation]") {
app.add_flag("-q,--count");
CHECK_THROWS_AS(app.add_flag("--count,-c"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "AddingExistingShortNoCase", "[creation]") {
app.add_flag("-C,--count")->ignore_case();
CHECK_THROWS_AS(app.add_flag("--cat,-c"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "AddingExistingLongNoCase", "[creation]") {
app.add_flag("-q,--count")->ignore_case();
CHECK_THROWS_AS(app.add_flag("--Count,-c"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "AddingExistingNoCaseReversed", "[creation]") {
app.add_flag("-c,--count")->ignore_case();
CHECK_THROWS_AS(app.add_flag("--cat,-C"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "AddingExistingWithCase", "[creation]") {
app.add_flag("-c,--count");
CHECK_NOTHROW(app.add_flag("--Cat,-C"));
}
TEST_CASE_METHOD(TApp, "AddingExistingShortLong", "[creation]") {
app.add_flag("-c");
CHECK_THROWS_AS(app.add_flag("--c"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "AddingExistingLongShort", "[creation]") {
app.add_flag("--c");
CHECK_THROWS_AS(app.add_option("-c"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "AddingExistingWithCaseAfter", "[creation]") {
auto *count = app.add_flag("-c,--count");
app.add_flag("--Cat,-C");
CHECK_THROWS_AS(count->ignore_case(), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "AddingExistingWithCaseAfter2", "[creation]") {
app.add_flag("-c,--count");
auto *cat = app.add_flag("--Cat,-C");
CHECK_THROWS_AS(cat->ignore_case(), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "AddingExistingWithUnderscoreAfter", "[creation]") {
auto *count = app.add_flag("--underscore");
app.add_flag("--under_score");
CHECK_THROWS_AS(count->ignore_underscore(), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "AddingExistingWithUnderscoreAfter2", "[creation]") {
auto *count = app.add_flag("--under_score");
app.add_flag("--underscore");
CHECK_THROWS_AS(count->ignore_underscore(), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "matchPositional", "[creation]") {
app.add_option("firstoption");
CHECK_THROWS_AS(app.add_option("--firstoption"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "matchPositional2", "[creation]") {
app.add_option("--firstoption");
CHECK_THROWS_AS(app.add_option("firstoption"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "matchPositionalInOptionGroup1", "[creation]") {
auto *g1 = app.add_option_group("group_b");
g1->add_option("--firstoption");
CHECK_THROWS_AS(app.add_option("firstoption"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "matchPositionalInOptionGroup2", "[creation]") {
app.add_option("firstoption");
auto *g1 = app.add_option_group("group_b");
CHECK_THROWS_AS(g1->add_option("--firstoption"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "matchPositionalInOptionGroup3", "[creation]") {
app.add_option("f");
auto *g1 = app.add_option_group("group_b");
CHECK_THROWS_AS(g1->add_option("-f"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "AddingMultipleInfPositionals", "[creation]") {
std::vector<std::string> one, two;
app.add_option("one", one);
app.add_option("two", two);
CHECK_THROWS_AS(run(), CLI::InvalidError);
}
TEST_CASE_METHOD(TApp, "AddingMultipleInfPositionalsSubcom", "[creation]") {
std::vector<std::string> one, two;
CLI::App *below = app.add_subcommand("below");
below->add_option("one", one);
below->add_option("two", two);
CHECK_THROWS_AS(run(), CLI::InvalidError);
}
TEST_CASE_METHOD(TApp, "MultipleSubcomMatching", "[creation]") {
app.add_subcommand("first");
app.add_subcommand("second");
app.add_subcommand("Second");
CHECK_THROWS_AS(app.add_subcommand("first"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "RecoverSubcommands", "[creation]") {
CLI::App *app1 = app.add_subcommand("app1");
CLI::App *app2 = app.add_subcommand("app2");
CLI::App *app3 = app.add_subcommand("app3");
CLI::App *app4 = app.add_subcommand("app4");
CHECK(std::vector<CLI::App *>({app1, app2, app3, app4}) == app.get_subcommands({}));
}
TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithCase", "[creation]") {
app.add_subcommand("first")->ignore_case();
CHECK_THROWS_AS(app.add_subcommand("fIrst"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithCaseFirst", "[creation]") {
app.ignore_case();
app.add_subcommand("first");
CHECK_THROWS_AS(app.add_subcommand("fIrst"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithUnderscore", "[creation]") {
app.add_subcommand("first_option")->ignore_underscore();
CHECK_THROWS_AS(app.add_subcommand("firstoption"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithUnderscoreFirst", "[creation]") {
app.ignore_underscore();
app.add_subcommand("first_option");
CHECK_THROWS_AS(app.add_subcommand("firstoption"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithCaseInplace", "[creation]") {
app.add_subcommand("first");
auto *first = app.add_subcommand("fIrst");
CHECK_THROWS_AS(first->ignore_case(), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithCaseInplace2", "[creation]") {
auto *first = app.add_subcommand("first");
app.add_subcommand("fIrst");
CHECK_THROWS_AS(first->ignore_case(), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithUnderscoreInplace", "[creation]") {
app.add_subcommand("first_option");
auto *first = app.add_subcommand("firstoption");
CHECK_THROWS_AS(first->ignore_underscore(), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithUnderscoreInplace2", "[creation]") {
auto *first = app.add_subcommand("firstoption");
app.add_subcommand("first_option");
CHECK_THROWS_AS(first->ignore_underscore(), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "MultipleSubcomNoMatchingInplace2", "[creation]") {
auto *first = app.add_subcommand("first");
auto *second = app.add_subcommand("second");
CHECK_NOTHROW(first->ignore_case());
CHECK_NOTHROW(second->ignore_case());
}
TEST_CASE_METHOD(TApp, "MultipleSubcomNoMatchingInplaceUnderscore2", "[creation]") {
auto *first = app.add_subcommand("first_option");
auto *second = app.add_subcommand("second_option");
CHECK_NOTHROW(first->ignore_underscore());
CHECK_NOTHROW(second->ignore_underscore());
}
TEST_CASE_METHOD(TApp, "IncorrectConstructionFlagPositional1", "[creation]") {
// This wants to be one line with clang-format
CHECK_THROWS_AS(app.add_flag("cat"), CLI::IncorrectConstruction);
}
TEST_CASE_METHOD(TApp, "IncorrectConstructionFlagPositional2", "[creation]") {
int x{0};
CHECK_THROWS_AS(app.add_flag("cat", x), CLI::IncorrectConstruction);
}
TEST_CASE_METHOD(TApp, "IncorrectConstructionFlagPositional3", "[creation]") {
bool x{false};
CHECK_THROWS_AS(app.add_flag("cat", x), CLI::IncorrectConstruction);
}
TEST_CASE_METHOD(TApp, "IncorrectConstructionNeedsCannotFind", "[creation]") {
auto *cat = app.add_flag("--cat");
CHECK_THROWS_AS(cat->needs("--nothing"), CLI::IncorrectConstruction);
}
TEST_CASE_METHOD(TApp, "IncorrectConstructionExcludesCannotFind", "[creation]") {
auto *cat = app.add_flag("--cat");
CHECK_THROWS_AS(cat->excludes("--nothing"), CLI::IncorrectConstruction);
}
TEST_CASE_METHOD(TApp, "IncorrectConstructionDuplicateNeeds", "[creation]") {
auto *cat = app.add_flag("--cat");
auto *other = app.add_flag("--other");
REQUIRE_NOTHROW(cat->needs(other));
// duplicated needs is redundant but not an error
CHECK_NOTHROW(cat->needs(other));
}
TEST_CASE_METHOD(TApp, "IncorrectConstructionDuplicateNeedsTxt", "[creation]") {
auto *cat = app.add_flag("--cat");
app.add_flag("--other");
REQUIRE_NOTHROW(cat->needs("--other"));
// duplicate needs is redundant but not an error
CHECK_NOTHROW(cat->needs("--other"));
}
// Now allowed
TEST_CASE_METHOD(TApp, "CorrectConstructionDuplicateExcludes", "[creation]") {
auto *cat = app.add_flag("--cat");
auto *other = app.add_flag("--other");
REQUIRE_NOTHROW(cat->excludes(other));
REQUIRE_NOTHROW(other->excludes(cat));
}
// Now allowed
TEST_CASE_METHOD(TApp, "CorrectConstructionDuplicateExcludesTxt", "[creation]") {
auto *cat = app.add_flag("--cat");
auto *other = app.add_flag("--other");
REQUIRE_NOTHROW(cat->excludes("--other"));
REQUIRE_NOTHROW(other->excludes("--cat"));
}
TEST_CASE_METHOD(TApp, "CheckName", "[creation]") {
auto *long1 = app.add_flag("--long1");
auto *long2 = app.add_flag("--Long2");
auto *short1 = app.add_flag("-a");
auto *short2 = app.add_flag("-B");
int x{0}, y{0};
auto *pos1 = app.add_option("pos1", x);
auto *pos2 = app.add_option("pOs2", y);
CHECK(long1->check_name("--long1"));
CHECK(!long1->check_name("--lonG1"));
CHECK(long2->check_name("--Long2"));
CHECK(!long2->check_name("--long2"));
CHECK(short1->check_name("-a"));
CHECK(!short1->check_name("-A"));
CHECK(short2->check_name("-B"));
CHECK(!short2->check_name("-b"));
CHECK(pos1->check_name("pos1"));
CHECK(!pos1->check_name("poS1"));
CHECK(pos2->check_name("pOs2"));
CHECK(!pos2->check_name("pos2"));
}
TEST_CASE_METHOD(TApp, "CheckNameNoCase", "[creation]") {
auto *long1 = app.add_flag("--long1")->ignore_case();
auto *long2 = app.add_flag("--Long2")->ignore_case();
auto *short1 = app.add_flag("-a")->ignore_case();
auto *short2 = app.add_flag("-B")->ignore_case();
int x{0}, y{0};
auto *pos1 = app.add_option("pos1", x)->ignore_case();
auto *pos2 = app.add_option("pOs2", y)->ignore_case();
CHECK(long1->check_name("--long1"));
CHECK(long1->check_name("--lonG1"));
CHECK(long2->check_name("--Long2"));
CHECK(long2->check_name("--long2"));
CHECK(short1->check_name("-a"));
CHECK(short1->check_name("-A"));
CHECK(short2->check_name("-B"));
CHECK(short2->check_name("-b"));
CHECK(pos1->check_name("pos1"));
CHECK(pos1->check_name("poS1"));
CHECK(pos2->check_name("pOs2"));
CHECK(pos2->check_name("pos2"));
}
TEST_CASE_METHOD(TApp, "CheckNameNoUnderscore", "[creation]") {
auto *long1 = app.add_flag("--longoption1")->ignore_underscore();
auto *long2 = app.add_flag("--long_option2")->ignore_underscore();
int x{0}, y{0};
auto *pos1 = app.add_option("pos_option_1", x)->ignore_underscore();
auto *pos2 = app.add_option("posoption2", y)->ignore_underscore();
CHECK(long1->check_name("--long_option1"));
CHECK(long1->check_name("--longoption_1"));
CHECK(long1->check_name("--longoption1"));
CHECK(long1->check_name("--long__opt_ion__1"));
CHECK(long1->check_name("--__l_o_n_g_o_p_t_i_o_n_1"));
CHECK(long2->check_name("--long_option2"));
CHECK(long2->check_name("--longoption2"));
CHECK(long2->check_name("--longoption_2"));
CHECK(long2->check_name("--long__opt_ion__2"));
CHECK(long2->check_name("--__l_o_n_go_p_t_i_o_n_2__"));
CHECK(pos1->check_name("pos_option1"));
CHECK(pos1->check_name("pos_option_1"));
CHECK(pos1->check_name("pos_o_p_t_i_on_1"));
CHECK(pos1->check_name("posoption1"));
CHECK(pos2->check_name("pos_option2"));
CHECK(pos2->check_name("pos_option_2"));
CHECK(pos2->check_name("pos_o_p_t_i_on_2"));
CHECK(pos2->check_name("posoption2"));
}
TEST_CASE_METHOD(TApp, "CheckNameNoCaseNoUnderscore", "[creation]") {
auto *long1 = app.add_flag("--LongoptioN1")->ignore_underscore()->ignore_case();
auto *long2 = app.add_flag("--long_Option2")->ignore_case()->ignore_underscore();
int x{0}, y{0};
auto *pos1 = app.add_option("pos_Option_1", x)->ignore_underscore()->ignore_case();
auto *pos2 = app.add_option("posOption2", y)->ignore_case()->ignore_underscore();
CHECK(long1->check_name("--Long_Option1"));
CHECK(long1->check_name("--lONgoption_1"));
CHECK(long1->check_name("--LongOption1"));
CHECK(long1->check_name("--long__Opt_ion__1"));
CHECK(long1->check_name("--__l_o_N_g_o_P_t_i_O_n_1"));
CHECK(long2->check_name("--long_Option2"));
CHECK(long2->check_name("--LongOption2"));
CHECK(long2->check_name("--longOPTION_2"));
CHECK(long2->check_name("--long__OPT_ion__2"));
CHECK(long2->check_name("--__l_o_n_GO_p_t_i_o_n_2__"));
CHECK(pos1->check_name("POS_Option1"));
CHECK(pos1->check_name("pos_option_1"));
CHECK(pos1->check_name("pos_o_p_t_i_on_1"));
CHECK(pos1->check_name("posoption1"));
CHECK(pos2->check_name("pos_option2"));
CHECK(pos2->check_name("pos_OPTION_2"));
CHECK(pos2->check_name("poS_o_p_T_I_on_2"));
CHECK(pos2->check_name("PosOption2"));
}
TEST_CASE_METHOD(TApp, "PreSpaces", "[creation]") {
int x{0};
auto *myapp = app.add_option(" -a, --long, other", x);
CHECK(myapp->check_lname("long"));
CHECK(myapp->check_sname("a"));
CHECK(myapp->check_name("other"));
}
TEST_CASE_METHOD(TApp, "AllSpaces", "[creation]") {
int x{0};
auto *myapp = app.add_option(" -a , --long , other ", x);
CHECK(myapp->check_lname("long"));
CHECK(myapp->check_sname("a"));
CHECK(myapp->check_name("other"));
}
TEST_CASE_METHOD(TApp, "OptionFromDefaults", "[creation]") {
app.option_defaults()->required();
// Options should remember defaults
int x{0};
auto *opt = app.add_option("--simple", x);
CHECK(opt->get_required());
// Flags cannot be required
auto *flag = app.add_flag("--other");
CHECK(!flag->get_required());
app.option_defaults()->required(false);
auto *opt2 = app.add_option("--simple2", x);
CHECK(!opt2->get_required());
app.option_defaults()->required()->ignore_case();
auto *opt3 = app.add_option("--simple3", x);
CHECK(opt3->get_required());
CHECK(opt3->get_ignore_case());
app.option_defaults()->required()->ignore_underscore();
auto *opt4 = app.add_option("--simple4", x);
CHECK(opt4->get_required());
CHECK(opt4->get_ignore_underscore());
}
TEST_CASE_METHOD(TApp, "OptionFromDefaultsSubcommands", "[creation]") {
// Initial defaults
CHECK(!app.option_defaults()->get_required());
CHECK(CLI::MultiOptionPolicy::Throw == app.option_defaults()->get_multi_option_policy());
CHECK(!app.option_defaults()->get_ignore_case());
CHECK(!app.option_defaults()->get_ignore_underscore());
CHECK(!app.option_defaults()->get_disable_flag_override());
CHECK(app.option_defaults()->get_configurable());
CHECK("OPTIONS" == app.option_defaults()->get_group());
app.option_defaults()
->required()
->multi_option_policy(CLI::MultiOptionPolicy::TakeLast)
->ignore_case()
->ignore_underscore()
->configurable(false)
->disable_flag_override()
->group("Something");
auto *app2 = app.add_subcommand("app2");
CHECK(app2->option_defaults()->get_required());
CHECK(CLI::MultiOptionPolicy::TakeLast == app2->option_defaults()->get_multi_option_policy());
CHECK(app2->option_defaults()->get_ignore_case());
CHECK(app2->option_defaults()->get_ignore_underscore());
CHECK(!app2->option_defaults()->get_configurable());
CHECK(app.option_defaults()->get_disable_flag_override());
CHECK("Something" == app2->option_defaults()->get_group());
}
TEST_CASE_METHOD(TApp, "GetNameCheck", "[creation]") {
int x{0};
auto *a = app.add_flag("--that");
auto *b = app.add_flag("-x");
auto *c = app.add_option("pos", x);
auto *d = app.add_option("one,-o,--other", x);
CHECK("--that" == a->get_name(false, true));
CHECK("-x" == b->get_name(false, true));
CHECK("pos" == c->get_name(false, true));
CHECK("--other" == d->get_name());
CHECK("--other" == d->get_name(false, false));
CHECK("-o,--other" == d->get_name(false, true));
CHECK("one,-o,--other" == d->get_name(true, true));
CHECK("one" == d->get_name(true, false));
// an option with an empty group is hidden and has no name
auto *e = app.add_flag("--hidden")->group("");
CHECK(e->get_name().empty());
CHECK(e->get_name(true, true).empty());
}
TEST_CASE_METHOD(TApp, "SubcommandDefaults", "[creation]") {
// allow_extras, prefix_command, ignore_case, fallthrough, group, min/max subcommand, validate_positionals
// Initial defaults
CHECK(!app.get_allow_extras());
CHECK(!app.get_prefix_command());
CHECK(!app.get_immediate_callback());
CHECK(!app.get_ignore_case());
CHECK(!app.get_ignore_underscore());
#ifdef _WIN32
CHECK(app.get_allow_windows_style_options());
#else
CHECK(!app.get_allow_windows_style_options());
#endif
CHECK(!app.get_fallthrough());
CHECK(!app.get_configurable());
CHECK(!app.get_validate_positionals());
CHECK(app.get_usage().empty());
CHECK(app.get_footer().empty());
CHECK("SUBCOMMANDS" == app.get_group());
CHECK(0u == app.get_require_subcommand_min());
CHECK(0u == app.get_require_subcommand_max());
app.allow_extras();
app.prefix_command();
app.immediate_callback();
app.ignore_case();
app.ignore_underscore();
app.configurable();
#ifdef _WIN32
app.allow_windows_style_options(false);
#else
app.allow_windows_style_options();
#endif
app.fallthrough();
app.validate_positionals();
app.usage("ussy");
app.footer("footy");
app.group("Stuff");
app.require_subcommand(2, 3);
auto *app2 = app.add_subcommand("app2");
// Initial defaults
CHECK(app2->get_allow_extras());
CHECK(app2->get_prefix_command());
CHECK(app2->get_immediate_callback());
CHECK(app2->get_ignore_case());
CHECK(app2->get_ignore_underscore());
#ifdef _WIN32
CHECK(!app2->get_allow_windows_style_options());
#else
CHECK(app2->get_allow_windows_style_options());
#endif
CHECK(app2->get_fallthrough());
CHECK(app2->get_validate_positionals());
CHECK(app2->get_configurable());
CHECK("ussy" == app2->get_usage());
CHECK("footy" == app2->get_footer());
CHECK("Stuff" == app2->get_group());
CHECK(0u == app2->get_require_subcommand_min());
CHECK(3u == app2->get_require_subcommand_max());
}
TEST_CASE_METHOD(TApp, "SubcommandMinMax", "[creation]") {
CHECK(0u == app.get_require_subcommand_min());
CHECK(0u == app.get_require_subcommand_max());
app.require_subcommand();
CHECK(1u == app.get_require_subcommand_min());
CHECK(0u == app.get_require_subcommand_max());
app.require_subcommand(2);
CHECK(2u == app.get_require_subcommand_min());
CHECK(2u == app.get_require_subcommand_max());
app.require_subcommand(0);
CHECK(0u == app.get_require_subcommand_min());
CHECK(0u == app.get_require_subcommand_max());
app.require_subcommand(-2);
CHECK(0u == app.get_require_subcommand_min());
CHECK(2u == app.get_require_subcommand_max());
app.require_subcommand(3, 7);
CHECK(3u == app.get_require_subcommand_min());
CHECK(7u == app.get_require_subcommand_max());
}
TEST_CASE_METHOD(TApp, "GetOptionList", "[creation]") {
int two{0};
auto *flag = app.add_flag("--one");
auto *opt = app.add_option("--two", two);
const CLI::App &const_app = app; // const alias to force use of const-methods
std::vector<const CLI::Option *> opt_list = const_app.get_options();
REQUIRE(static_cast<std::size_t>(3) == opt_list.size());
CHECK(flag == opt_list.at(1));
CHECK(opt == opt_list.at(2));
std::vector<CLI::Option *> nonconst_opt_list = app.get_options();
for(std::size_t i = 0; i < opt_list.size(); ++i) {
CHECK(opt_list.at(i) == nonconst_opt_list.at(i));
}
}
// the const and non-const get_options overloads serve different purposes and therefore do not return the
// same set of options. The const overload is used for help formatting and only merges explicitly merged
// groups ('+'); the non-const overload is used for config generation/subcommand comparison and must mirror
// the parser, which descends into every nameless subcommand (e.g. plain option groups).
TEST_CASE_METHOD(TApp, "GetOptionListOverloadBehavior", "[creation]") {
int val{0};
auto *base = app.add_option("--base", val);
// a regular (non '+') nameless option group: its options are shown separately in help (not merged into
// the parent) but are still parsed as part of the parent, so they must appear in the non-const list
auto *grp = app.add_option_group("plain");
auto *gopt = grp->add_option("--grpopt", val);
// a nameless option group whose name starts with '+' is merged into the parent for both overloads
auto *mgrp = app.add_option_group("+merged");
auto *mopt = mgrp->add_option("--mergedopt", val);
// a plain named subcommand is never merged into the parent option list by either overload
auto *namedsub = app.add_subcommand("named");
auto *subopt = namedsub->add_option("--subopt", val);
const CLI::App &const_app = app;
auto const_opts = const_app.get_options();
auto nonconst_opts = app.get_options();
auto in_const = [&](const CLI::Option *o) {
return std::find(const_opts.begin(), const_opts.end(), o) != const_opts.end();
};
auto in_nonconst = [&](const CLI::Option *o) {
return std::find(nonconst_opts.begin(), nonconst_opts.end(), o) != nonconst_opts.end();
};
// both overloads include the parent option and the explicitly merged '+' group
CHECK(in_const(base));
CHECK(in_nonconst(base));
CHECK(in_const(mopt));
CHECK(in_nonconst(mopt));
// a plain option group is merged for the non-const overload (config) but not the const overload (help)
CHECK(!in_const(gopt));
CHECK(in_nonconst(gopt));
// plain named subcommand options are never merged by either overload
CHECK(!in_const(subopt));
CHECK(!in_nonconst(subopt));
}
TEST_CASE_METHOD(TApp, "GetOptionListFilter", "[creation]") {
int two{0};
auto *flag = app.add_flag("--one");
app.add_option("--two", two);
const CLI::App &const_app = app; // const alias to force use of const-methods
std::vector<const CLI::Option *> opt_listc =
const_app.get_options([](const CLI::Option *opt) { return opt->get_name() == "--one"; });
REQUIRE(static_cast<std::size_t>(1) == opt_listc.size());
CHECK(flag == opt_listc.at(0));
std::vector<CLI::Option *> opt_list =
app.get_options([](const CLI::Option *opt) { return opt->get_name() == "--one"; });
REQUIRE(static_cast<std::size_t>(1) == opt_list.size());
CHECK(flag == opt_list.at(0));
}
TEST_CASE("ValidatorTests: TestValidatorCreation", "[creation]") {
std::function<std::string(std::string &)> op1 = [](std::string &val) {
return (val.size() >= 5) ? std::string{} : val;
};
CLI::Validator V(op1, "", "size");
CHECK("size" == V.get_name());
V.name("harry");
CHECK("harry" == V.get_name());
CHECK(V.get_active());
CHECK("test" == V("test"));
CHECK(V("test5").empty());
CHECK(V.get_description().empty());
V.description("this is a description");
CHECK("this is a description" == V.get_description());
}
TEST_CASE("ValidatorTests: TestValidatorOps", "[creation]") {
std::function<std::string(std::string &)> op1 = [](std::string &val) {
return (val.size() >= 5) ? std::string{} : val;
};
std::function<std::string(std::string &)> op2 = [](std::string &val) {
return (val.size() >= 9) ? std::string{} : val;
};
std::function<std::string(std::string &)> op3 = [](std::string &val) {
return (val.size() < 3) ? std::string{} : val;
};
std::function<std::string(std::string &)> op4 = [](std::string &val) {
return (val.size() <= 9) ? std::string{} : val;
};
CLI::Validator V1(op1, "SIZE >= 5");
CLI::Validator V2(op2, "SIZE >= 9");
CLI::Validator V3(op3, "SIZE < 3");
CLI::Validator V4(op4, "SIZE <= 9");
std::string two(2, 'a');
std::string four(4, 'a');
std::string five(5, 'a');
std::string eight(8, 'a');
std::string nine(9, 'a');
std::string ten(10, 'a');
CHECK(V1(five).empty());
CHECK(!V1(four).empty());
CHECK(V2(nine).empty());
CHECK(!V2(eight).empty());
CHECK(V3(two).empty());
CHECK(!V3(four).empty());
CHECK(V4(eight).empty());
CHECK(!V4(ten).empty());
auto V1a2 = V1 & V2;
CHECK("(SIZE >= 5) AND (SIZE >= 9)" == V1a2.get_description());
CHECK(!V1a2(five).empty());
CHECK(V1a2(nine).empty());
auto V1a4 = V1 & V4;
CHECK("(SIZE >= 5) AND (SIZE <= 9)" == V1a4.get_description());
CHECK(V1a4(five).empty());
CHECK(V1a4(eight).empty());
CHECK(!V1a4(ten).empty());
CHECK(!V1a4(four).empty());
auto V1o3 = V1 | V3;
CHECK("(SIZE >= 5) OR (SIZE < 3)" == V1o3.get_description());
CHECK(V1o3(two).empty());
CHECK(V1o3(eight).empty());
CHECK(V1o3(ten).empty());
CHECK(V1o3(two).empty());
CHECK(!V1o3(four).empty());
auto m1 = V1o3 & V4;
CHECK("((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)" == m1.get_description());
CHECK(m1(two).empty());
CHECK(m1(eight).empty());
CHECK(!m1(ten).empty());
CHECK(m1(two).empty());
CHECK(m1(five).empty());
CHECK(!m1(four).empty());
auto m2 = m1 & V2;
CHECK("(((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)) AND (SIZE >= 9)" == m2.get_description());
CHECK(!m2(two).empty());
CHECK(!m2(eight).empty());
CHECK(!m2(ten).empty());
CHECK(!m2(two).empty());
CHECK(m2(nine).empty());
CHECK(!m2(four).empty());
auto m3 = m2 | V3;
CHECK("((((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)) AND (SIZE >= 9)) OR (SIZE < 3)" == m3.get_description());
CHECK(m3(two).empty());
CHECK(!m3(eight).empty());
CHECK(m3(nine).empty());
CHECK(!m3(four).empty());
auto m4 = V3 | m2;
CHECK("(SIZE < 3) OR ((((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)) AND (SIZE >= 9))" == m4.get_description());
CHECK(m4(two).empty());
CHECK(!m4(eight).empty());
CHECK(m4(nine).empty());
CHECK(!m4(four).empty());
}
TEST_CASE("ValidatorTests: TestValidatorNegation", "[creation]") {
std::function<std::string(std::string &)> op1 = [](std::string &val) {
return (val.size() >= 5) ? std::string{} : val;
};
CLI::Validator V1(op1, "SIZE >= 5", "size");
std::string four(4, 'a');
std::string five(5, 'a');
CHECK(V1(five).empty());
CHECK(!V1(four).empty());
auto V2 = !V1;
CHECK(!V2(five).empty());
CHECK(V2(four).empty());
CHECK("NOT SIZE >= 5" == V2.get_description());
V2.active(false);
CHECK(V2(five).empty());
CHECK(V2(four).empty());
CHECK(V2.get_description().empty());
}
TEST_CASE("ValidatorTests: ValidatorDefaults", "[creation]") {
CLI::Validator V1{};
std::string four(4, 'a');
std::string five(5, 'a');
// make sure this doesn't generate a seg fault or something
CHECK(V1(five).empty());
CHECK(V1(four).empty());
CHECK(V1.get_name().empty());
CHECK(V1.get_description().empty());
CHECK(V1.get_active());
CHECK(V1.get_modifying());
CLI::Validator V2{"check"};
// make sure this doesn't generate a seg fault or something
CHECK(V2(five).empty());
CHECK(V2(four).empty());
CHECK(V2.get_name().empty());
CHECK("check" == V2.get_description());
CHECK(V2.get_active());
CHECK(V2.get_modifying());
// This class only support streaming in, not out
}
class Unstreamable {
private:
int x_{-1};
public:
Unstreamable() = default;
CLI11_NODISCARD int get_x() const { return x_; }
void set_x(int x) { x_ = x; }
};
// this needs to be a different check then the one after the function definition otherwise they conflict
static_assert(!CLI::detail::is_istreamable<Unstreamable, std::istream>::value, "Unstreamable type is streamable");
std::istream &operator>>(std::istream &in, Unstreamable &value) {
int x = 0;
in >> x;
value.set_x(x);
return in;
}
// these need to be different classes otherwise the definitions conflict
static_assert(CLI::detail::is_istreamable<Unstreamable>::value,
"Unstreamable type is still unstreamable and it should be");
TEST_CASE_METHOD(TApp, "MakeUnstreamableOptions", "[creation]") {
Unstreamable value;
app.add_option("--value", value);
// This used to fail to build, since it tries to stream from Unstreamable
app.add_option("--value2", value);
std::vector<Unstreamable> values;
app.add_option("--values", values);
// This used to fail to build, since it tries to stream from Unstreamable
app.add_option("--values2", values);
args = {"--value", "45"};
run();
CHECK(45 == value.get_x());
args = {"--values", "45", "27", "34"};
run();
CHECK(3u == values.size());
CHECK(34 == values[2].get_x());
}
+12
View File
@@ -0,0 +1,12 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
TEST_CASE("Deprecated: Empty", "[deprecated]") {
// No deprecated features at this time.
CHECK(true);
}
+144
View File
@@ -0,0 +1,144 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include <array>
#include <string>
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
#include <filesystem>
#endif // CLI11_HAS_FILESYSTEM
// "abcd"
static const std::string abcd_str = "abcd";
static const std::wstring abcd_wstr = L"abcd";
// "𓂀𓂀𓂀" - 4-byte utf8 characters
static const std::array<uint8_t, 12 + 1> egypt_utf8_codeunits{
{0xF0, 0x93, 0x82, 0x80, 0xF0, 0x93, 0x82, 0x80, 0xF0, 0x93, 0x82, 0x80}};
static const std::string egypt_str(reinterpret_cast<const char *>(egypt_utf8_codeunits.data()));
#ifdef _WIN32
static const std::array<uint16_t, 6 + 1> egypt_utf16_codeunits{{0xD80C, 0xDC80, 0xD80C, 0xDC80, 0xD80C, 0xDC80}};
static const std::wstring egypt_wstr(reinterpret_cast<const wchar_t *>(egypt_utf16_codeunits.data()));
#else
static const std::array<uint32_t, 3 + 1> egypt_utf32_codeunits{{0x00013080, 0x00013080, 0x00013080}};
static const std::wstring egypt_wstr(reinterpret_cast<const wchar_t *>(egypt_utf32_codeunits.data()));
#endif
// "Hello Halló Привет 你好 👩‍🚀❤️" - many languages and complex emojis
static const std::array<uint8_t, 50 + 1> hello_utf8_codeunits{
{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x48, 0x61, 0x6c, 0x6c, 0xc3, 0xb3, 0x20, 0xd0, 0x9f, 0xd1, 0x80,
0xd0, 0xb8, 0xd0, 0xb2, 0xd0, 0xb5, 0xd1, 0x82, 0x20, 0xe4, 0xbd, 0xa0, 0xe5, 0xa5, 0xbd, 0x20, 0xf0,
0x9f, 0x91, 0xa9, 0xe2, 0x80, 0x8d, 0xf0, 0x9f, 0x9a, 0x80, 0xe2, 0x9d, 0xa4, 0xef, 0xb8, 0x8f}};
static const std::string hello_str(reinterpret_cast<const char *>(hello_utf8_codeunits.data()));
#ifdef _WIN32
static const std::array<uint16_t, 29 + 1> hello_utf16_codeunits{
{0x0048, 0x0065, 0x006c, 0x006c, 0x006f, 0x0020, 0x0048, 0x0061, 0x006c, 0x006c,
0x00f3, 0x0020, 0x041f, 0x0440, 0x0438, 0x0432, 0x0435, 0x0442, 0x0020, 0x4f60,
0x597d, 0x0020, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xde80, 0x2764, 0xfe0f}};
static const std::wstring hello_wstr(reinterpret_cast<const wchar_t *>(hello_utf16_codeunits.data()));
#else
static const std::array<uint32_t, 27 + 1> hello_utf32_codeunits{
{0x00000048, 0x00000065, 0x0000006c, 0x0000006c, 0x0000006f, 0x00000020, 0x00000048, 0x00000061, 0x0000006c,
0x0000006c, 0x000000f3, 0x00000020, 0x0000041f, 0x00000440, 0x00000438, 0x00000432, 0x00000435, 0x00000442,
0x00000020, 0x00004f60, 0x0000597d, 0x00000020, 0x0001f469, 0x0000200d, 0x0001f680, 0x00002764, 0x0000fe0f}};
static const std::wstring hello_wstr(reinterpret_cast<const wchar_t *>(hello_utf32_codeunits.data()));
#endif
// #14
TEST_CASE("Encoding: Widen", "[unicode]") {
using CLI::widen;
CHECK(abcd_wstr == widen(abcd_str));
CHECK(egypt_wstr == widen(egypt_str));
CHECK(hello_wstr == widen(hello_str));
CHECK(hello_wstr == widen(hello_str.c_str()));
CHECK(hello_wstr == widen(hello_str.c_str(), hello_str.size()));
#ifdef CLI11_CPP17
CHECK(hello_wstr == widen(std::string_view{hello_str}));
#endif // CLI11_CPP17
}
// str_size must bound the conversion; converting a prefix of a longer buffer must not read past str_size
// (relevant for the non-codecvt path, which is the default under C++26).
TEST_CASE("Encoding: WidenSize", "[unicode]") {
using CLI::widen;
// "Hello " is 6 leading ASCII bytes, mapping 1:1 to 6 wide characters.
const std::string longer = "Hello world";
const std::wstring expected = L"Hello ";
CHECK(expected == widen(longer.c_str(), 6));
// A buffer that is not NUL-terminated at str_size must only read the first str_size bytes.
const std::array<char, 5> not_terminated{{'a', 'b', 'c', 'd', 'e'}};
CHECK(std::wstring(L"abc") == widen(not_terminated.data(), 3));
#ifdef CLI11_CPP17
CHECK(expected == widen(std::string_view{longer}.substr(0, 6)));
#endif // CLI11_CPP17
}
// #14
TEST_CASE("Encoding: Narrow", "[unicode]") {
using CLI::narrow;
CHECK(abcd_str == narrow(abcd_wstr));
CHECK(egypt_str == narrow(egypt_wstr));
CHECK(hello_str == narrow(hello_wstr));
CHECK(hello_str == narrow(hello_wstr.c_str()));
CHECK(hello_str == narrow(hello_wstr.c_str(), hello_wstr.size()));
#ifdef CLI11_CPP17
CHECK(hello_str == narrow(std::wstring_view{hello_wstr}));
#endif // CLI11_CPP17
}
// str_size must bound the conversion; converting a prefix of a longer buffer must not read past str_size
// (relevant for the non-codecvt path, which is the default under C++26).
TEST_CASE("Encoding: NarrowSize", "[unicode]") {
using CLI::narrow;
// "Hello " is 6 leading ASCII wide characters, mapping 1:1 to 6 bytes.
const std::wstring longer = L"Hello world";
const std::string expected = "Hello ";
CHECK(expected == narrow(longer.c_str(), 6));
// A buffer that is not NUL-terminated at str_size must only read the first str_size characters.
const std::array<wchar_t, 5> not_terminated{{L'a', L'b', L'c', L'd', L'e'}};
CHECK(std::string("abc") == narrow(not_terminated.data(), 3));
#ifdef CLI11_CPP17
CHECK(expected == narrow(std::wstring_view{longer}.substr(0, 6)));
#endif // CLI11_CPP17
}
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
// #14
TEST_CASE("Encoding: to_path roundtrip", "[unicode]") {
using std::filesystem::path;
#ifdef _WIN32
std::wstring native_str = CLI::widen(hello_str);
#else
std::string native_str = hello_str;
#endif // _WIN32
CHECK(CLI::to_path(hello_str).native() == native_str);
}
#endif // CLI11_HAS_FILESYSTEM
+814
View File
@@ -0,0 +1,814 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include <cmath>
#include <chrono>
#include <cstdint>
#include <map>
#include <string>
#include <utility>
#include <vector>
TEST_CASE("Validators:basic", "[helpers]") { CHECK(true); }
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
TEST_CASE("Validators: IPValidate1", "[helpers]") {
std::string ip = "1.1.1.1";
CHECK(CLI::ValidIPV4(ip).empty());
ip = "224.255.0.1";
CHECK(CLI::ValidIPV4(ip).empty());
// check that it doesn't work with a trailing .
ip = "224.255.0.1.";
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
ip = "-1.255.0.1";
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
ip = "1.256.0.1";
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
ip = "1.223.0.";
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
ip = "1.256.0.1";
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
ip = "aaa";
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
ip = "1.2.3.abc";
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
ip = "11.22";
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
}
TEST_CASE("Validators: NumberValidator", "[helpers]") {
std::string num = "1.1.1.1";
CHECK_FALSE(CLI::Number(num).empty());
num = "1.7";
CHECK(CLI::Number(num).empty());
num = "10000";
CHECK(CLI::Number(num).empty());
num = "-0.000";
CHECK(CLI::Number(num).empty());
num = "+1.55";
CHECK(CLI::Number(num).empty());
num = "a";
CHECK_FALSE(CLI::Number(num).empty());
}
// Tests positionals validation at the end
TEST_CASE_METHOD(TApp, "PositionalValidation", "[app]") {
std::string options;
std::string foo;
app.add_option("bar", options)->check(CLI::Number.name("valbar"));
// disable the check on foo
app.add_option("foo", foo)->check(CLI::Number.active(false));
app.validate_positionals();
args = {"1", "param1"};
run();
CHECK("1" == options);
CHECK("param1" == foo);
args = {"param1", "1"};
CHECK_NOTHROW(run());
CHECK("1" == options);
CHECK("param1" == foo);
CHECK(nullptr != app.get_option("bar")->get_validator("valbar"));
}
TEST_CASE_METHOD(TApp, "BoundTests", "[transform]") {
double value = NAN;
app.add_option("-s", value)->transform(CLI::Bound(3.4, 5.9));
args = {"-s", "15"};
run();
CHECK(5.9 == value);
args = {"-s", "3.689"};
run();
CHECK(std::stod("3.689") == value);
// value can't be converted to int so it is just ignored
args = {"-s", "abcd"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"-s", "2.5"};
run();
CHECK(3.4 == value);
auto help = app.help();
CHECK(help.find("bounded to") != std::string::npos);
CHECK(help.find("[3.4 - 5.9]") != std::string::npos);
}
TEST_CASE_METHOD(TApp, "typeCheck", "[app]") {
/// Note that this must be a double in Range, too
app.add_option("--one")->check(CLI::TypeValidator<unsigned int>());
args = {"--one=1"};
CHECK_NOTHROW(run());
args = {"--one=-7"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--one=error"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--one=4.568"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "NumberWithUnitCorrectlySplitNumber", "[transform]") {
std::map<std::string, int> mapping{{"a", 10}, {"b", 100}, {"cc", 1000}};
int value = 0;
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
args = {"-n", "42"};
run();
CHECK(42 == value);
args = {"-n", "42a"};
run();
CHECK(420 == value);
args = {"-n", " 42 cc "};
run();
CHECK(42000 == value);
args = {"-n", " -42 cc "};
run();
CHECK(-42000 == value);
}
TEST_CASE_METHOD(TApp, "NumberWithUnitFloatTest", "[transform]") {
std::map<std::string, double> mapping{{"a", 10}, {"b", 100}, {"cc", 1000}};
double value{0.0};
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
args = {"-n", "42"};
run();
CHECK(42 == Approx(value));
args = {"-n", ".5"};
run();
CHECK(.5 == Approx(value));
args = {"-n", "42.5 a"};
run();
CHECK(425 == Approx(value));
args = {"-n", "42.cc"};
run();
CHECK(42000 == Approx(value));
}
TEST_CASE_METHOD(TApp, "NumberWithUnitCaseSensitive", "[transform]") {
std::map<std::string, int> mapping{{"a", 10}, {"A", 100}};
int value{0};
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping, CLI::AsNumberWithUnit::CASE_SENSITIVE));
args = {"-n", "42a"};
run();
CHECK(420 == value);
args = {"-n", "42A"};
run();
CHECK(4200 == value);
}
TEST_CASE_METHOD(TApp, "NumberWithUnitCaseInsensitive", "[transform]") {
std::map<std::string, int> mapping{{"a", 10}, {"B", 100}};
int value{0};
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping, CLI::AsNumberWithUnit::CASE_INSENSITIVE));
args = {"-n", "42a"};
run();
CHECK(420 == value);
args = {"-n", "42A"};
run();
CHECK(420 == value);
args = {"-n", "42b"};
run();
CHECK(4200 == value);
args = {"-n", "42B"};
run();
CHECK(4200 == value);
}
TEST_CASE_METHOD(TApp, "NumberWithUnitMandatoryUnit", "[transform]") {
std::map<std::string, int> mapping{{"a", 10}, {"A", 100}};
int value{0};
app.add_option("-n", value)
->transform(CLI::AsNumberWithUnit(
mapping, CLI::AsNumberWithUnit::UNIT_REQUIRED | CLI::AsNumberWithUnit::CASE_SENSITIVE));
args = {"-n", "42a"};
run();
CHECK(420 == value);
args = {"-n", "42A"};
run();
CHECK(4200 == value);
args = {"-n", "42"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "NumberWithUnitMandatoryUnit2", "[transform]") {
std::map<std::string, int> mapping{{"a", 10}, {"B", 100}};
int value{0};
app.add_option("-n", value)
->transform(CLI::AsNumberWithUnit(
mapping, CLI::AsNumberWithUnit::UNIT_REQUIRED | CLI::AsNumberWithUnit::CASE_INSENSITIVE));
args = {"-n", "42A"};
run();
CHECK(420 == value);
args = {"-n", "42b"};
run();
CHECK(4200 == value);
args = {"-n", "42"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "NumberWithUnitBadMapping", "[transform]") {
CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"A", 100}},
CLI::AsNumberWithUnit::CASE_INSENSITIVE),
CLI::ValidationError);
CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"9", 100}}), CLI::ValidationError);
CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"AA A", 100}}), CLI::ValidationError);
CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"", 100}}), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "NumberWithUnitBadInput", "[transform]") {
std::map<std::string, int> mapping{{"a", 10}, {"b", 100}};
int value{0};
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
args = {"-n", "13 a b"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"-n", "13 c"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"-n", "a"};
// Assume 1.0 unit
CHECK_NOTHROW(run());
args = {"-n", "12.0a"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"-n", "a5"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"-n", ""};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"-n", "13 a-"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "NumberWithUnitIntOverflow", "[transform]") {
std::map<std::string, int> mapping{{"a", 1000000}, {"b", 100}, {"c", 101}};
std::int32_t value = 0;
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
args = {"-n", "1000 a"};
run();
CHECK(1000000000 == value);
args = {"-n", "1000000 a"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"-n", "-1000000 a"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"-n", "21474836 b"};
run();
CHECK(2147483600 == value);
args = {"-n", "21474836 c"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "NumberWithUnitFloatOverflow", "[transform]") {
std::map<std::string, float> mapping{{"a", 2.f}, {"b", 1.f}, {"c", 0.f}};
float value{0.0F};
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
args = {"-n", "3e+38 a"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"-n", "3e+38 b"};
run();
CHECK(3e+38f == Approx(value));
args = {"-n", "3e+38 c"};
run();
CHECK(0.f == Approx(value));
}
TEST_CASE_METHOD(TApp, "AsSizeValue1000_1024", "[transform]") {
std::uint64_t value{0};
app.add_option("-s", value)->transform(CLI::AsSizeValue(true));
args = {"-s", "10240"};
run();
CHECK(10240u == value);
args = {"-s", "1b"};
run();
CHECK(1u == value);
std::uint64_t k_value{1000u};
std::uint64_t ki_value{1024u};
args = {"-s", "1k"};
run();
CHECK(k_value == value);
args = {"-s", "1kb"};
run();
CHECK(k_value == value);
args = {"-s", "1 Kb"};
run();
CHECK(k_value == value);
args = {"-s", "1ki"};
run();
CHECK(ki_value == value);
args = {"-s", "1kib"};
run();
CHECK(ki_value == value);
k_value = 1000ull * 1000u;
ki_value = 1024ull * 1024u;
args = {"-s", "1m"};
run();
CHECK(k_value == value);
args = {"-s", "1mb"};
run();
CHECK(k_value == value);
args = {"-s", "1mi"};
run();
CHECK(ki_value == value);
args = {"-s", "1mib"};
run();
CHECK(ki_value == value);
k_value = 1000ull * 1000u * 1000u;
ki_value = 1024ull * 1024u * 1024u;
args = {"-s", "1g"};
run();
CHECK(k_value == value);
args = {"-s", "1gb"};
run();
CHECK(k_value == value);
args = {"-s", "1gi"};
run();
CHECK(ki_value == value);
args = {"-s", "1gib"};
run();
CHECK(ki_value == value);
k_value = 1000ull * 1000u * 1000u * 1000u;
ki_value = 1024ull * 1024u * 1024u * 1024u;
args = {"-s", "1t"};
run();
CHECK(k_value == value);
args = {"-s", "1tb"};
run();
CHECK(k_value == value);
args = {"-s", "1ti"};
run();
CHECK(ki_value == value);
args = {"-s", "1tib"};
run();
CHECK(ki_value == value);
k_value = 1000ull * 1000u * 1000u * 1000u * 1000u;
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u;
args = {"-s", "1p"};
run();
CHECK(k_value == value);
args = {"-s", "1pb"};
run();
CHECK(k_value == value);
args = {"-s", "1pi"};
run();
CHECK(ki_value == value);
args = {"-s", "1pib"};
run();
CHECK(ki_value == value);
k_value = 1000ull * 1000u * 1000u * 1000u * 1000u * 1000u;
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u * 1024u;
args = {"-s", "1e"};
run();
CHECK(k_value == value);
args = {"-s", "1eb"};
run();
CHECK(k_value == value);
args = {"-s", "1ei"};
run();
CHECK(ki_value == value);
args = {"-s", "1eib"};
run();
CHECK(ki_value == value);
}
TEST_CASE_METHOD(TApp, "duration_test", "[transform]") {
std::chrono::seconds duration{1};
app.option_defaults()->ignore_case();
app.add_option_function<std::size_t>(
"--duration",
[&](size_t a_value) { duration = std::chrono::seconds{a_value}; },
"valid units: sec, min, h, day.")
->capture_default_str()
->transform(CLI::AsNumberWithUnit(
std::map<std::string, std::size_t>{{"sec", 1}, {"min", 60}, {"h", 3600}, {"day", 24 * 3600}}));
CHECK_NOTHROW(app.parse(std::vector<std::string>{"1 day", "--duration"}));
CHECK(std::chrono::seconds(86400) == duration);
}
TEST_CASE_METHOD(TApp, "AsSizeValue1024", "[transform]") {
std::uint64_t value{0};
app.add_option("-s", value)->transform(CLI::AsSizeValue(false));
args = {"-s", "10240"};
run();
CHECK(10240u == value);
args = {"-s", "1b"};
run();
CHECK(1u == value);
std::uint64_t ki_value{1024u};
args = {"-s", "1k"};
run();
CHECK(ki_value == value);
args = {"-s", "1kb"};
run();
CHECK(ki_value == value);
args = {"-s", "1 Kb"};
run();
CHECK(ki_value == value);
args = {"-s", "1ki"};
run();
CHECK(ki_value == value);
args = {"-s", "1kib"};
run();
CHECK(ki_value == value);
ki_value = 1024ull * 1024u;
args = {"-s", "1m"};
run();
CHECK(ki_value == value);
args = {"-s", "1mb"};
run();
CHECK(ki_value == value);
args = {"-s", "1mi"};
run();
CHECK(ki_value == value);
args = {"-s", "1mib"};
run();
CHECK(ki_value == value);
ki_value = 1024ull * 1024u * 1024u;
args = {"-s", "1g"};
run();
CHECK(ki_value == value);
args = {"-s", "1gb"};
run();
CHECK(ki_value == value);
args = {"-s", "1gi"};
run();
CHECK(ki_value == value);
args = {"-s", "1gib"};
run();
CHECK(ki_value == value);
ki_value = 1024ull * 1024u * 1024u * 1024u;
args = {"-s", "1t"};
run();
CHECK(ki_value == value);
args = {"-s", "1tb"};
run();
CHECK(ki_value == value);
args = {"-s", "1ti"};
run();
CHECK(ki_value == value);
args = {"-s", "1tib"};
run();
CHECK(ki_value == value);
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u;
args = {"-s", "1p"};
run();
CHECK(ki_value == value);
args = {"-s", "1pb"};
run();
CHECK(ki_value == value);
args = {"-s", "1pi"};
run();
CHECK(ki_value == value);
args = {"-s", "1pib"};
run();
CHECK(ki_value == value);
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u * 1024u;
args = {"-s", "1e"};
run();
CHECK(ki_value == value);
args = {"-s", "1eb"};
run();
CHECK(ki_value == value);
args = {"-s", "1ei"};
run();
CHECK(ki_value == value);
args = {"-s", "1eib"};
run();
CHECK(ki_value == value);
}
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1)
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
#include <filesystem>
TEST_CASE_METHOD(TApp, "FileExistsForRead", "[validate]") {
std::string myfile{"TestNonFileNotUsed.txt"};
if(std::filesystem::exists(myfile)) {
std::filesystem::remove(myfile);
}
CHECK(!CLI::ReadPermissions(myfile).empty());
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
CHECK(ok);
std::string filename = "Failed";
app.add_option("--file", filename)->check(CLI::ReadPermissions);
args = {"--file", myfile};
run();
CHECK(myfile == filename);
std::filesystem::permissions(std::filesystem::path(myfile), std::filesystem::perms::owner_exec);
#if !defined(_WIN32)
// not sure how to make a file unreadable on windows in this context
CHECK_THROWS_AS(run(), CLI::ValidationError);
#endif
std::filesystem::permissions(std::filesystem::path(myfile), std::filesystem::perms::owner_write);
std::filesystem::remove(myfile);
}
TEST_CASE_METHOD(TApp, "FileExistsForWrite", "[validate]") {
std::string myfile{"TestNonFileNotUsed.txt"};
if(std::filesystem::exists(myfile)) {
std::filesystem::remove(myfile);
}
CHECK(!CLI::WritePermissions(myfile).empty());
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
CHECK(ok);
std::string filename = "Failed";
app.add_option("--file", filename)->check(CLI::WritePermissions);
args = {"--file", myfile};
run();
CHECK(myfile == filename);
std::filesystem::permissions(std::filesystem::path(myfile), std::filesystem::perms::owner_read);
CHECK_THROWS_AS(run(), CLI::ValidationError);
std::remove(myfile.c_str());
}
TEST_CASE_METHOD(TApp, "FileExistsForExec", "[validate]") {
std::string myfile{"TestNonFileNotUsed.txt"};
if(std::filesystem::exists(myfile)) {
std::filesystem::remove(myfile);
}
CHECK(!CLI::ExecPermissions(myfile).empty());
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
CHECK(ok);
std::string filename = "Failed";
app.add_option("--file", filename)->check(CLI::ExecPermissions);
args = {"--file", myfile};
std::filesystem::permissions(std::filesystem::path(myfile),
std::filesystem::perms::owner_exec | std::filesystem::perms::owner_read);
run();
CHECK(myfile == filename);
#if !defined(_WIN32)
std::filesystem::permissions(std::filesystem::path(myfile), std::filesystem::perms::owner_read);
CHECK_THROWS_AS(run(), CLI::ValidationError);
// exec permission not really a thing on windows
#endif
std::remove(myfile.c_str());
}
TEST_CASE_METHOD(TApp, "noPermissionCheck", "[validate]") {
std::string myfile{"TestNonFileNotUsed.txt"};
if(std::filesystem::exists(myfile)) {
std::filesystem::remove(myfile);
}
CHECK(!CLI::detail::PermissionValidator(CLI::detail::Permission::none)(myfile).empty());
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
CHECK(ok);
std::string filename = "Failed";
app.add_option("--file", filename)->check(CLI::detail::PermissionValidator(CLI::detail::Permission::none));
args = {"--file", myfile};
run();
CHECK(myfile == filename);
std::remove(myfile.c_str());
}
TEST_CASE_METHOD(TApp, "FileSizeValidatorMinOnly", "[validate]") {
std::string smallfile{"TestSmallFile.txt"};
std::string largefile{"TestLargeFile.txt"};
if(std::filesystem::exists(smallfile)) {
std::filesystem::remove(smallfile);
}
if(std::filesystem::exists(largefile)) {
std::filesystem::remove(largefile);
}
// Create a small file (1 byte)
{
std::ofstream ofs(smallfile);
ofs << "a";
}
CHECK(std::filesystem::file_size(smallfile) == 1);
// Create a large file (100 bytes)
{
std::ofstream ofs(largefile);
for(int i = 0; i < 100; ++i) {
ofs << "a";
}
}
CHECK(std::filesystem::file_size(largefile) == 100);
// Test min only (at least 50 bytes)
CLI::FileSizeValidator minValidator(50);
CHECK(!minValidator(smallfile).empty()); // smallfile is too small
CHECK(minValidator(largefile).empty()); // largefile is ok
// Test with app
std::string filename = "Failed";
app.add_option("--file", filename)->check(minValidator);
args = {"--file", largefile};
run();
CHECK(largefile == filename);
std::filesystem::remove(smallfile);
std::filesystem::remove(largefile);
}
TEST_CASE_METHOD(TApp, "FileSizeValidatorRange", "[validate]") {
std::string smallfile{"TestSmallFile.txt"};
std::string largefile{"TestLargeFile.txt"};
std::string hugefile{"TestHugeFile.txt"};
std::string nonExistingFile{"FileDoesnotExist.txt"};
if(std::filesystem::exists(smallfile)) {
std::filesystem::remove(smallfile);
}
if(std::filesystem::exists(largefile)) {
std::filesystem::remove(largefile);
}
if(std::filesystem::exists(hugefile)) {
std::filesystem::remove(hugefile);
}
// Create a small file (1 byte)
{
std::ofstream ofs(smallfile);
ofs << "a";
}
CHECK(std::filesystem::file_size(smallfile) == 1);
// Create a large file (100 bytes)
{
std::ofstream ofs(largefile);
for(int i = 0; i < 100; ++i) {
ofs << "a";
}
}
CHECK(std::filesystem::file_size(largefile) == 100);
// Create a huge file (1000 bytes)
{
std::ofstream ofs(hugefile);
for(int i = 0; i < 1000; ++i) {
ofs << "a";
}
}
CHECK(std::filesystem::file_size(hugefile) == 1000);
// Test min and max (between 10 and 200 bytes)
CLI::FileSizeValidator rangeValidator(10, 200);
CHECK(!rangeValidator(smallfile).empty()); // smallfile is too small
CHECK(rangeValidator(largefile).empty()); // largefile is ok
CHECK(!rangeValidator(hugefile).empty()); // too big
CHECK(!rangeValidator(nonExistingFile).empty()); // doesn't exist
// Test with app
std::string filename = "Failed";
app.add_option("--file", filename)->check(rangeValidator);
args = {"--file", largefile};
run();
CHECK(largefile == filename);
std::filesystem::remove(smallfile);
std::filesystem::remove(largefile);
std::filesystem::remove(hugefile);
}
TEST_CASE_METHOD(TApp, "NonEmptyFile", "[validate]") {
std::string emptyfile{"TestEmptyFile.txt"};
std::string nonemptyfile{"TestNonEmptyFile.txt"};
std::string nonExistingFile{"FileDoesnotExist.txt"};
if(std::filesystem::exists(emptyfile)) {
std::filesystem::remove(emptyfile);
}
if(std::filesystem::exists(nonemptyfile)) {
std::filesystem::remove(nonemptyfile);
}
// Create an empty file
{
std::ofstream ofs(emptyfile);
}
CHECK(std::filesystem::file_size(emptyfile) == 0);
// Create a non-empty file
{
std::ofstream ofs(nonemptyfile);
ofs << "content";
}
CHECK(std::filesystem::file_size(nonemptyfile) > 0);
// Test NonEmptyFile
CHECK(!CLI::NonEmptyFile(emptyfile).empty()); // empty file fails
CHECK(CLI::NonEmptyFile(nonemptyfile).empty()); // non-empty file passes
// Test with app
std::string filename = "Failed";
app.add_option("--file", filename)->check(CLI::NonEmptyFile);
args = {"--file", nonemptyfile};
run();
CHECK(nonemptyfile == filename);
std::filesystem::remove(emptyfile);
std::filesystem::remove(nonemptyfile);
// test with a file that doesn't exist
CHECK(!CLI::NonEmptyFile(nonExistingFile).empty());
}
TEST_CASE_METHOD(TApp, "NonEmptyFileFail", "[validate]") {
std::string emptyfile{"TestEmptyFile.txt"};
if(std::filesystem::exists(emptyfile)) {
std::filesystem::remove(emptyfile);
}
// Create an empty file
{
std::ofstream ofs(emptyfile);
}
CHECK(std::filesystem::file_size(emptyfile) == 0);
// Test with app - should fail
std::string filename = "Failed";
app.add_option("--file", filename)->check(CLI::NonEmptyFile);
args = {"--file", emptyfile};
CHECK_THROWS_AS(run(), CLI::ValidationError);
std::filesystem::remove(emptyfile);
}
#endif
#endif
#endif
+394
View File
@@ -0,0 +1,394 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#ifdef CLI11_SINGLE_FILE
#include "CLI11.hpp"
#else
#include "CLI/CLI.hpp"
#endif
#include "catch.hpp"
#include <fstream>
#include <memory>
#include <string>
class SimpleFormatter : public CLI::FormatterBase {
public:
SimpleFormatter() : FormatterBase() {}
std::string make_help(const CLI::App *, std::string, CLI::AppFormatMode) const override {
return "This is really simple";
}
};
TEST_CASE("Formatter: Nothing", "[formatter]") {
CLI::App app{"My prog"};
app.formatter(std::make_shared<SimpleFormatter>());
std::string help = app.help();
CHECK("This is really simple" == help);
}
TEST_CASE("Formatter: NothingLambda", "[formatter]") {
CLI::App app{"My prog"};
app.formatter_fn(
[](const CLI::App *, std::string, CLI::AppFormatMode) { return std::string("This is really simple"); });
std::string help = app.help();
CHECK("This is really simple" == help);
}
TEST_CASE("Formatter: OptCustomize", "[formatter]") {
CLI::App app{"My prog"};
auto optfmt = std::make_shared<CLI::Formatter>();
optfmt->column_width(25);
optfmt->label("REQUIRED", "(MUST HAVE)");
app.formatter(optfmt);
int v{0};
app.add_option("--opt", v, "Something")->required();
std::string help = app.help();
CHECK_THAT(help, Contains("(MUST HAVE)"));
CHECK_THAT(help, Contains("Something"));
CHECK_THAT(help, Contains("--opt INT"));
CHECK_THAT(help, Contains("-h, --help Print"));
}
TEST_CASE("Formatter: OptCustomizeSimple", "[formatter]") {
CLI::App app{"My prog"};
app.get_formatter()->column_width(25);
app.get_formatter()->label("REQUIRED", "(MUST HAVE)");
int v{0};
app.add_option("--opt", v, "Something")->required();
std::string help = app.help();
CHECK_THAT(help, Contains("(MUST HAVE)"));
CHECK_THAT(help, Contains("(MUST HAVE)"));
CHECK_THAT(help, Contains("Something"));
CHECK_THAT(help, Contains("--opt INT"));
CHECK_THAT(help, Contains("-h, --help Print"));
}
TEST_CASE("Formatter: OptCustomizeOptionText", "[formatter]") {
CLI::App app{"My prog"};
app.get_formatter()->column_width(25);
int v{0};
app.add_option("--opt", v, "Something")->option_text("(ARG)");
std::string help = app.help();
CHECK_THAT(help, Contains("(ARG)"));
}
TEST_CASE("Formatter: OptBaseExample", "[formatter]") {
CLI::App app{"My prog"};
app.get_formatter()->column_width(25);
std::string v{};
app.add_option("--opt", v)->default_str("DEFFFF");
int v2{0};
app.add_option("-o,--opt2", v2, "this is a description for opt2");
double v3{0.0};
app.add_option("-f,-n,--opt3,--option-double", v3, "this is a description for option3");
app.add_flag("--flag,!--no_flag", "a flag option with a negative flag as well");
std::string help = app.help();
CHECK_THAT(help, Contains("DEFFFF"));
CHECK_THAT(help, Contains("{false"));
}
TEST_CASE("Formatter: OptDefaults", "[formatter]") {
CLI::App app{"My prog"};
app.get_formatter()->column_width(25);
std::string v{};
app.add_option("--opt", v)->default_str("DEFFFF");
std::string help = app.help();
CHECK_THAT(help, Contains("[DEFFFF]"));
app.get_formatter()->enable_option_defaults(false);
help = app.help();
CHECK_THAT(help, !Contains("[DEFFFF]"));
CHECK(!app.get_formatter()->is_option_defaults_enabled());
}
TEST_CASE("Formatter: OptTypes", "[formatter]") {
CLI::App app{"My prog"};
app.get_formatter()->column_width(25);
std::string v{};
app.add_option("--opt", v);
std::string help = app.help();
CHECK_THAT(help, Contains("TEXT"));
app.get_formatter()->enable_option_type_names(false);
help = app.help();
CHECK_THAT(help, !Contains("TEXT"));
CHECK(!app.get_formatter()->is_option_type_names_enabled());
}
TEST_CASE("Formatter: FalseFlagExample", "[formatter]") {
CLI::App app{"My prog"};
app.get_formatter()->column_width(25);
app.get_formatter()->label("REQUIRED", "(MUST HAVE)");
int v{0};
app.add_flag("--opt,!--no_opt", v, "Something");
bool flag{false};
app.add_flag("!-O,--opt2,--no_opt2{false}", flag, "Something else");
std::string help = app.help();
CHECK_THAT(help, Contains("--no_opt{false}"));
CHECK_THAT(help, Contains("--no_opt2{false}"));
CHECK_THAT(help, Contains("-O{false}"));
}
TEST_CASE("Formatter: FalseFlagExampleDisable", "[formatter]") {
CLI::App app{"My prog"};
app.get_formatter()->column_width(25);
int v{0};
app.add_flag("--opt,!--no_opt", v, "Something");
bool flag{false};
app.add_flag("!-O,--opt2,--no_opt2{false}", flag, "Something else");
std::string help = app.help();
CHECK_THAT(help, Contains("--no_opt{false}"));
CHECK_THAT(help, Contains("--no_opt2{false}"));
CHECK_THAT(help, Contains("-O{false}"));
app.get_formatter()->enable_default_flag_values(false);
CHECK(!app.get_formatter()->is_default_flag_values_enabled());
help = app.help();
CHECK_THAT(help, !Contains("{false}"));
}
TEST_CASE("Formatter: AppCustomize", "[formatter]") {
CLI::App app{"My prog"};
app.add_subcommand("subcom1", "This");
auto appfmt = std::make_shared<CLI::Formatter>();
appfmt->column_width(20);
appfmt->label("Usage", "Run");
app.formatter(appfmt);
app.add_subcommand("subcom2", "That");
std::string help = app.help();
CHECK_THAT(help, Contains("Run: [OPTIONS] [SUBCOMMANDS]\n\n"));
CHECK_THAT(help, Contains("\nSUBCOMMANDS:\n"));
CHECK_THAT(help, Contains(" subcom1 This\n"));
CHECK_THAT(help, Contains(" subcom2 That\n"));
}
TEST_CASE("Formatter: AppCustomizeSimple", "[formatter]") {
CLI::App app{"My prog"};
app.add_subcommand("subcom1", "This");
app.get_formatter()->column_width(20);
app.get_formatter()->label("Usage", "Run");
app.add_subcommand("subcom2", "That");
std::string help = app.help();
CHECK_THAT(help, Contains("Run: [OPTIONS] [SUBCOMMANDS]\n\n"));
CHECK_THAT(help, Contains("\nSUBCOMMANDS:\n"));
CHECK_THAT(help, Contains(" subcom1 This\n"));
CHECK_THAT(help, Contains(" subcom2 That\n"));
}
TEST_CASE("Formatter: AllSub", "[formatter]") {
CLI::App app{"My prog"};
CLI::App *sub = app.add_subcommand("subcom", "This");
sub->add_flag("--insub", "MyFlag");
std::string help = app.help("", CLI::AppFormatMode::All);
CHECK_THAT(help, Contains("--insub"));
CHECK_THAT(help, Contains("subcom"));
}
TEST_CASE("Formatter: AllSubRequired", "[formatter]") {
CLI::App app{"My prog"};
CLI::App *sub = app.add_subcommand("subcom", "This");
sub->add_flag("--insub", "MyFlag");
sub->required();
std::string help = app.help("", CLI::AppFormatMode::All);
CHECK_THAT(help, Contains("--insub"));
CHECK_THAT(help, Contains("subcom"));
CHECK_THAT(help, Contains("REQUIRED"));
}
TEST_CASE("Formatter: NamelessSub", "[formatter]") {
CLI::App app{"My prog"};
CLI::App *sub = app.add_subcommand("", "This subcommand");
sub->add_flag("--insub", "MyFlag");
std::string help = app.help("", CLI::AppFormatMode::Normal);
CHECK_THAT(help, Contains("--insub"));
CHECK_THAT(help, Contains("This subcommand"));
}
TEST_CASE("Formatter: NamelessSubInGroup", "[formatter]") {
CLI::App app{"My prog"};
CLI::App *sub = app.add_subcommand("", "This subcommand");
CLI::App *sub2 = app.add_subcommand("sub2", "subcommand2");
sub->add_flag("--insub", "MyFlag");
int val{0};
sub2->add_option("pos", val, "positional");
sub->group("group1");
sub2->group("group1");
std::string help = app.help("", CLI::AppFormatMode::Normal);
CHECK_THAT(help, Contains("--insub"));
CHECK_THAT(help, Contains("This subcommand"));
CHECK_THAT(help, Contains("group1"));
CHECK_THAT(help, Contains("sub2"));
CHECK(help.find("pos") == std::string::npos);
}
TEST_CASE("Formatter: Footer", "[formatter]") {
CLI::App app{"My prog"};
std::string footer_string{"this is a test of the footer "
"systemsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss to Pr e s "
" e r v e SPA C ES"};
app.footer(footer_string);
app.add_flag("--option", "MyFlag");
app.get_formatter()->footer_paragraph_width(50);
app.get_formatter()->enable_footer_formatting(false);
CHECK(!app.get_formatter()->is_footer_paragraph_formatting_enabled());
std::string help = app.help("", CLI::AppFormatMode::Normal);
CHECK_THAT(help, Contains("is a"));
CHECK_THAT(help, Contains("to Pr e s e r v e SPA C ES"));
CHECK_THAT(help, Contains(footer_string));
help = app.help("", CLI::AppFormatMode::Sub);
CHECK_THAT(help, Contains("is a"));
CHECK_THAT(help, Contains("to Pr e s e r v e SPA C ES"));
CHECK_THAT(help, Contains(footer_string));
app.get_formatter()->enable_footer_formatting(true);
CHECK(app.get_formatter()->is_footer_paragraph_formatting_enabled());
help = app.help("", CLI::AppFormatMode::Normal);
CHECK_THAT(help, !Contains("is a"));
CHECK_THAT(help, !Contains("to Pr e s e r v e SPA C ES"));
CHECK_THAT(help, !Contains(footer_string));
help = app.help("", CLI::AppFormatMode::Sub);
CHECK_THAT(help, !Contains("is a"));
CHECK_THAT(help, !Contains("to Pr e s e r v e SPA C ES"));
CHECK_THAT(help, !Contains(footer_string));
}
TEST_CASE("Formatter: Description", "[formatter]") {
CLI::App app{"My prog"};
std::string desc_string{"this is a test of the footer "
"systemsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss to Pr e s "
"e r v e SPA C ES"};
app.description(desc_string);
app.add_flag("--option", "MyFlag");
app.get_formatter()->description_paragraph_width(50);
app.get_formatter()->enable_description_formatting(false);
CHECK(!app.get_formatter()->is_description_paragraph_formatting_enabled());
std::string help = app.help("", CLI::AppFormatMode::Normal);
CHECK_THAT(help, Contains("is a"));
CHECK_THAT(help, Contains("to Pr e s e r v e SPA C ES"));
CHECK_THAT(help, Contains(desc_string));
help = app.help("", CLI::AppFormatMode::Sub);
CHECK_THAT(help, Contains("is a"));
CHECK_THAT(help, Contains("to Pr e s e r v e SPA C ES"));
CHECK_THAT(help, Contains(desc_string));
app.get_formatter()->enable_description_formatting(true);
CHECK(app.get_formatter()->is_description_paragraph_formatting_enabled());
help = app.help("", CLI::AppFormatMode::Normal);
CHECK_THAT(help, !Contains("is a"));
CHECK_THAT(help, !Contains("to Pr e s e r v e SPA C ES"));
CHECK_THAT(help, !Contains(desc_string));
help = app.help("", CLI::AppFormatMode::Sub);
CHECK_THAT(help, !Contains("is a"));
CHECK_THAT(help, !Contains("to Pr e s e r v e SPA C ES"));
CHECK_THAT(help, !Contains(desc_string));
}
TEST_CASE("Formatter: LongOptionAlignment", "[formatter]") {
CLI::App app{"My prog"};
app.get_formatter()->long_option_alignment_ratio(6 / 30.f);
int v{0};
app.add_option("--opt", v, "Something");
std::string help = app.help();
CHECK_THAT(help, Contains("\n -h, --help Print"));
CHECK_THAT(help, Contains("\n --opt INT Something"));
}
TEST_CASE("Formatter: HiddenPositionalUsage", "[formatter]") {
// a positional in an empty (hidden) group must not render as an empty "[]" in the usage line
CLI::App app{"My prog"};
int v{0};
app.add_option("pos", v, "a hidden positional")->group("");
std::string help = app.help();
CHECK_THAT(help, Contains("Usage:"));
CHECK_THAT(help, !Contains("[]"));
// the hidden positional should not appear in the usage line at all
CHECK_THAT(help, !Contains("pos"));
}
TEST_CASE("Formatter: LongSubcommandName", "[formatter]") {
// a subcommand name that fills the name column must wrap so its description does not glue onto it
CLI::App app{"My prog"};
app.add_subcommand("a-really-really-long-subcommand-name", "subcommand description text");
std::string help = app.help();
// name and description must be separated, not concatenated
CHECK_THAT(help, !Contains("a-really-really-long-subcommand-namesubcommand"));
CHECK_THAT(help, Contains("a-really-really-long-subcommand-name\n"));
CHECK_THAT(help, Contains("subcommand description text"));
}
TEST_CASE("Formatter: DescriptionNoFormattingSpacing", "[formatter]") {
// toggling description formatting off must not introduce an extra blank line before the usage
CLI::App app{"My prog"};
app.description("Short desc");
app.get_formatter()->enable_description_formatting(false);
std::string help = app.help();
CHECK_THAT(help, Contains("Short desc\n\n\nUsage"));
CHECK_THAT(help, !Contains("Short desc\n\n\n\nUsage"));
}
+422
View File
@@ -0,0 +1,422 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "../fuzz/fuzzApp.hpp"
#include "app_helper.hpp"
#include <iostream>
#include <string>
#include <vector>
std::string loadFailureFile(const std::string &type, int index) {
std::string fileName(TEST_FILE_FOLDER "/fuzzFail/");
fileName.append(type);
fileName += std::to_string(index);
std::ifstream crashFile(fileName, std::ios::in | std::ios::binary);
if(crashFile) {
std::vector<char> buffer(std::istreambuf_iterator<char>(crashFile), {});
std::string cdata(buffer.begin(), buffer.end());
return cdata;
}
return std::string{};
}
TEST_CASE("app_fail") {
CLI::FuzzApp fuzzdata;
auto app = fuzzdata.generateApp();
int index = GENERATE(range(1, 4));
std::string optionString;
auto parseData = loadFailureFile("fuzz_app_fail", index);
if(index >= 3 && parseData.size() > 25) {
optionString = parseData.substr(0, 25);
parseData.erase(0, 25);
}
try {
if(!optionString.empty()) {
app->add_option(optionString, fuzzdata.buffer);
}
try {
app->parse(parseData);
} catch(const CLI::ParseError & /*e*/) {
CHECK(true);
}
} catch(const CLI::ConstructionError & /*e*/) {
CHECK(true);
}
}
TEST_CASE("file_fail") {
CLI::FuzzApp fuzzdata;
auto app = fuzzdata.generateApp();
int index = GENERATE(range(1, 10));
auto parseData = loadFailureFile("fuzz_file_fail", index);
std::stringstream out(parseData);
try {
app->parse_from_stream(out);
} catch(const CLI::ParseError & /*e*/) {
CHECK(true);
}
}
TEST_CASE("app_file_gen_fail") {
CLI::FuzzApp fuzzdata;
auto app = fuzzdata.generateApp();
int index = GENERATE(range(1, 41));
std::string optionString, flagString;
auto parseData = loadFailureFile("fuzz_app_file_fail", index);
if(parseData.size() > 25) {
optionString = parseData.substr(0, 25);
parseData.erase(0, 25);
}
if(parseData.size() > 25) {
flagString = parseData.substr(0, 25);
parseData.erase(0, 25);
}
try {
if(!optionString.empty()) {
app->add_option(optionString, fuzzdata.buffer);
}
if(!flagString.empty()) {
app->add_flag(flagString, fuzzdata.intbuffer);
}
try {
app->parse(parseData);
} catch(const CLI::ParseError & /*e*/) {
return;
}
} catch(const CLI::ConstructionError & /*e*/) {
return;
}
std::string configOut = app->config_to_str();
app->clear();
std::stringstream out(configOut);
app->parse_from_stream(out);
}
// this test uses the same tests as above just with a full roundtrip test
TEST_CASE("app_file_roundtrip") {
CLI::FuzzApp fuzzdata;
CLI::FuzzApp fuzzdata2;
auto app = fuzzdata.generateApp();
auto app2 = fuzzdata2.generateApp();
int index = GENERATE(range(1, 41));
// int index = GENERATE(range(8, 9));
std::string optionString, flagString;
auto parseData = loadFailureFile("fuzz_app_file_fail", index);
if(parseData.size() > 25) {
optionString = parseData.substr(0, 25);
parseData.erase(0, 25);
}
if(parseData.size() > 25) {
flagString = parseData.substr(0, 25);
parseData.erase(0, 25);
}
try {
if(!optionString.empty()) {
app->add_option(optionString, fuzzdata.buffer);
app2->add_option(optionString, fuzzdata2.buffer);
}
if(!flagString.empty()) {
app->add_flag(flagString, fuzzdata.intbuffer);
app2->add_flag(flagString, fuzzdata2.intbuffer);
}
try {
app->parse(parseData);
} catch(const CLI::ParseError & /*e*/) {
return;
}
} catch(const CLI::ConstructionError & /*e*/) {
return;
}
std::string configOut = app->config_to_str();
std::stringstream out(configOut);
app2->parse_from_stream(out);
bool result = fuzzdata2.compare(fuzzdata);
/*
if (!result)
{
configOut = app->config_to_str();
result = fuzzdata2.compare(fuzzdata);
}
*/
INFO("Failure in test case " << index);
CHECK(result);
}
// this test uses the same tests as above just with a full roundtrip test
TEST_CASE("app_roundtrip") {
CLI::FuzzApp fuzzdata;
CLI::FuzzApp fuzzdata2;
auto app = fuzzdata.generateApp();
auto app2 = fuzzdata2.generateApp();
int index = GENERATE(range(1, 5));
std::string optionString, flagString;
auto parseData = loadFailureFile("round_trip_fail", index);
if(parseData.size() > 25) {
optionString = parseData.substr(0, 25);
parseData.erase(0, 25);
}
if(parseData.size() > 25) {
flagString = parseData.substr(0, 25);
parseData.erase(0, 25);
}
try {
if(!optionString.empty()) {
app->add_option(optionString, fuzzdata.buffer);
app2->add_option(optionString, fuzzdata2.buffer);
}
if(!flagString.empty()) {
app->add_flag(flagString, fuzzdata.intbuffer);
app2->add_flag(flagString, fuzzdata2.intbuffer);
}
try {
app->parse(parseData);
} catch(const CLI::ParseError & /*e*/) {
return;
}
} catch(const CLI::ConstructionError & /*e*/) {
return;
}
std::string configOut = app->config_to_str();
std::stringstream out(configOut);
app2->parse_from_stream(out);
bool result = fuzzdata2.compare(fuzzdata);
/*
if (!result)
{
configOut = app->config_to_str();
result = fuzzdata2.compare(fuzzdata);
}
*/
CHECK(result);
}
// same as above but just a single test for debugging
TEST_CASE("app_roundtrip_single") {
CLI::FuzzApp fuzzdata;
CLI::FuzzApp fuzzdata2;
auto app = fuzzdata.generateApp();
auto app2 = fuzzdata2.generateApp();
int index = 5;
std::string optionString, flagString;
auto parseData = loadFailureFile("round_trip_fail", index);
if(parseData.size() > 25) {
optionString = parseData.substr(0, 25);
parseData.erase(0, 25);
}
if(parseData.size() > 25) {
flagString = parseData.substr(0, 25);
parseData.erase(0, 25);
}
try {
if(!optionString.empty()) {
app->add_option(optionString, fuzzdata.buffer);
app2->add_option(optionString, fuzzdata2.buffer);
}
if(!flagString.empty()) {
app->add_flag(flagString, fuzzdata.intbuffer);
app2->add_flag(flagString, fuzzdata2.intbuffer);
}
try {
app->parse(parseData);
} catch(const CLI::ParseError & /*e*/) {
return;
}
} catch(const CLI::ConstructionError & /*e*/) {
return;
}
std::string configOut = app->config_to_str();
std::stringstream out(configOut);
app2->parse_from_stream(out);
bool result = fuzzdata2.compare(fuzzdata);
/*
if (!result)
{
configOut = app->config_to_str();
result = fuzzdata2.compare(fuzzdata);
}
*/
CHECK(result);
}
/** test the fuzzer itself to support custom options*/
TEST_CASE("fuzz_config_test1") {
CLI::FuzzApp fuzzdata;
auto app = fuzzdata.generateApp();
std::string config_string = "<option>--new_option</option><flag>--new_flag</flag><vector>--new_vector</vector>";
auto loc = fuzzdata.add_custom_options(app.get(), config_string);
config_string = config_string.substr(loc);
CHECK(config_string.empty());
CHECK(app->get_option_no_throw("--new_option") != nullptr);
CHECK(app->get_option_no_throw("--new_flag") != nullptr);
CHECK(app->get_option_no_throw("--new_vector") != nullptr);
}
/** test the fuzzer itself to support custom options*/
TEST_CASE("fuzz_config_test2") {
CLI::FuzzApp fuzzdata;
auto app = fuzzdata.generateApp();
std::string config_string =
"<option>--new_option</option><flag>--new_flag</flag><vector>--new_vector</vector> --new_flag --new_option 10";
auto loc = fuzzdata.add_custom_options(app.get(), config_string);
config_string = config_string.substr(loc);
CHECK(!config_string.empty());
CHECK(config_string == " --new_flag --new_option 10");
CHECK(app->get_option_no_throw("--new_option") != nullptr);
CHECK(app->get_option_no_throw("--new_flag") != nullptr);
CHECK(app->get_option_no_throw("--new_vector") != nullptr);
}
/** test the fuzzer itself to support custom option modifiers*/
TEST_CASE("fuzz_config_modifier_test1") {
CLI::FuzzApp fuzzdata;
auto app = fuzzdata.generateApp();
std::string config_string = "<option modifiers=R2CG>--new_option</option><flag "
"modifiers=cFg>--new_flag</flag><vector modifiers=35s+>--new_vector</vector>";
auto loc = fuzzdata.add_custom_options(app.get(), config_string);
config_string = config_string.substr(loc);
CHECK(config_string.empty());
auto *opt1 = app->get_option_no_throw("--new_option");
REQUIRE(opt1 != nullptr);
CHECK(opt1->get_required());
CHECK(opt1->get_expected_min() == 2);
CHECK(opt1->get_configurable());
CHECK(opt1->get_ignore_case());
auto *opt2 = app->get_option_no_throw("--new_flag");
REQUIRE(opt2 != nullptr);
CHECK(opt2->get_disable_flag_override());
CHECK(!opt2->get_configurable());
CHECK(!opt2->get_ignore_case());
auto *opt3 = app->get_option_no_throw("--new_vector");
REQUIRE(opt3 != nullptr);
CHECK(opt3->get_expected_min() == 0);
CHECK(opt3->get_expected_max() == 3);
CHECK(opt3->get_multi_option_policy() == CLI::MultiOptionPolicy::Sum);
}
/** test the fuzzer itself to support custom subcommand*/
TEST_CASE("fuzz_config_subcommand") {
CLI::FuzzApp fuzzdata;
auto app = fuzzdata.generateApp();
std::string config_string =
"<subcommand name=sub_custom><option modifiers=R2CG>--new_option</option><flag "
"modifiers=cFg>--new_flag</flag><vector modifiers=35s+>--new_vector</vector></subcommand>";
auto loc = fuzzdata.add_custom_options(app.get(), config_string);
config_string = config_string.substr(loc);
CHECK(config_string.empty());
auto *sub_c = app->get_subcommand("sub_custom");
auto *opt1 = sub_c->get_option_no_throw("--new_option");
REQUIRE(opt1 != nullptr);
CHECK(opt1->get_required());
CHECK(opt1->get_expected_min() == 2);
CHECK(opt1->get_configurable());
CHECK(opt1->get_ignore_case());
auto *opt2 = sub_c->get_option_no_throw("--new_flag");
REQUIRE(opt2 != nullptr);
CHECK(opt2->get_disable_flag_override());
CHECK(!opt2->get_configurable());
CHECK(!opt2->get_ignore_case());
auto *opt3 = sub_c->get_option_no_throw("--new_vector");
REQUIRE(opt3 != nullptr);
CHECK(opt3->get_expected_min() == 0);
CHECK(opt3->get_expected_max() == 3);
CHECK(opt3->get_multi_option_policy() == CLI::MultiOptionPolicy::Sum);
}
// this test enables the custom option creation operation
TEST_CASE("app_roundtrip_custom") {
CLI::FuzzApp fuzzdata;
CLI::FuzzApp fuzzdata2;
auto app = fuzzdata.generateApp();
auto app2 = fuzzdata2.generateApp();
int index = GENERATE(range(1, 25));
auto parseData = loadFailureFile("round_trip_custom", index);
std::size_t pstring_start{0};
pstring_start = fuzzdata.add_custom_options(app.get(), parseData);
INFO("Failure in test case " << index << " file length=" << parseData.size() << " pstring start at "
<< pstring_start);
if(pstring_start > 0) {
app->parse(parseData.substr(pstring_start));
CHECK_NOTHROW(app->help("", CLI::AppFormatMode::All));
} else {
app->parse(parseData);
}
if(fuzzdata.supports_config_file()) {
// should be able to write the config to a file and read from it again
std::string configOut = app->config_to_str();
std::stringstream out(configOut);
if(pstring_start > 0) {
fuzzdata2.add_custom_options(app2.get(), parseData);
}
CHECK_NOTHROW(app2->parse_from_stream(out));
auto result = fuzzdata2.compare(fuzzdata);
if(!result) {
result = fuzzdata.compare(fuzzdata2, true);
std::cout << "\n:parsed:\n" << parseData;
std::cout << "\n:config:\n" << configOut << '\n';
}
CHECK(result);
}
}
// this test
TEST_CASE("app_roundtrip_parse_normal_fail") {
// this is mostly checking that no unexpected errors occur
// like HorribleErrors
CLI::FuzzApp fuzzdata;
auto app = fuzzdata.generateApp();
int index = GENERATE(range(1, 11));
auto parseData = loadFailureFile("parse_fail_check", index);
std::size_t pstring_start{0};
try {
pstring_start = fuzzdata.add_custom_options(app.get(), parseData);
} catch(const CLI::ConstructionError & /*ce*/) {
CHECK(true);
return;
}
INFO("Failure in test case " << index);
try {
if(pstring_start > 0) {
app->parse(parseData.substr(pstring_start));
} else {
app->parse(parseData);
}
} catch(const CLI::HorribleError & /*he*/) {
CHECK(false);
return;
} catch(const CLI::ParseError & /*e*/) {
CHECK(true);
return;
}
try {
// should be able to write the config to a file and read from it again
std::string configOut = app->config_to_str();
app->clear();
std::stringstream out(configOut);
app->parse_from_stream(out);
} catch(const CLI::HorribleError & /*he*/) {
CHECK(false);
return;
} catch(const CLI::ParseError & /*e*/) {
CHECK(false);
return;
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+647
View File
@@ -0,0 +1,647 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include <complex>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
using cx = std::complex<double>;
TEST_CASE_METHOD(TApp, "ComplexOption", "[newparse]") {
cx comp{1, 2};
app.add_option("-c,--complex", comp)->capture_default_str();
args = {"-c", "4", "3"};
std::string help = app.help();
CHECK_THAT(help, Contains("1"));
CHECK_THAT(help, Contains("2"));
CHECK_THAT(help, Contains("COMPLEX"));
CHECK(comp.real() == Approx(1));
CHECK(comp.imag() == Approx(2));
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(3));
}
TEST_CASE_METHOD(TApp, "ComplexFloatOption", "[newparse]") {
std::complex<float> comp{1, 2};
app.add_option("-c,--complex", comp)->capture_default_str();
args = {"-c", "4", "3"};
std::string help = app.help();
CHECK_THAT(help, Contains("1"));
CHECK_THAT(help, Contains("2"));
CHECK_THAT(help, Contains("COMPLEX"));
CHECK(comp.real() == Approx(1));
CHECK(comp.imag() == Approx(2));
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(3));
}
TEST_CASE_METHOD(TApp, "ComplexWithDelimiterOption", "[newparse]") {
cx comp{1, 2};
app.add_option("-c,--complex", comp)->capture_default_str()->delimiter('+');
args = {"-c", "4+3i"};
std::string help = app.help();
CHECK_THAT(help, Contains("1"));
CHECK_THAT(help, Contains("2"));
CHECK_THAT(help, Contains("COMPLEX"));
CHECK(comp.real() == Approx(1));
CHECK(comp.imag() == Approx(2));
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(3));
args = {"-c", "5+-3i"};
run();
CHECK(comp.real() == Approx(5));
CHECK(comp.imag() == Approx(-3));
args = {"-c", "6", "-4i"};
run();
CHECK(comp.real() == Approx(6));
CHECK(comp.imag() == Approx(-4));
}
TEST_CASE_METHOD(TApp, "ComplexIgnoreIOption", "[newparse]") {
cx comp{1, 2};
app.add_option("-c,--complex", comp);
args = {"-c", "4", "3i"};
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(3));
}
TEST_CASE_METHOD(TApp, "ComplexSingleArgOption", "[newparse]") {
cx comp{1, 2};
app.add_option("-c,--complex", comp);
args = {"-c", "4"};
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(0));
args = {"-c", "4-2i"};
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(-2));
args = {"-c", "4+2i"};
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(2));
args = {"-c", "-4+2j"};
run();
CHECK(comp.real() == Approx(-4));
CHECK(comp.imag() == Approx(2));
args = {"-c", "-4.2-2j"};
run();
CHECK(comp.real() == Approx(-4.2));
CHECK(comp.imag() == Approx(-2));
args = {"-c", "-4.2-2.7i"};
run();
CHECK(comp.real() == Approx(-4.2));
CHECK(comp.imag() == Approx(-2.7));
}
TEST_CASE_METHOD(TApp, "ComplexSingleImagOption", "[newparse]") {
cx comp{1, 2};
app.add_option("-c,--complex", comp);
args = {"-c", "4j"};
run();
CHECK(comp.real() == Approx(0));
CHECK(comp.imag() == Approx(4));
args = {"-c", "-4j"};
run();
CHECK(comp.real() == Approx(0));
CHECK(comp.imag() == Approx(-4));
args = {"-c", "-4"};
run();
CHECK(comp.real() == Approx(-4));
CHECK(comp.imag() == Approx(0));
args = {"-c", "+4"};
run();
CHECK(comp.real() == Approx(4));
CHECK(comp.imag() == Approx(0));
}
/// Simple class containing two strings useful for testing lexical cast and conversions
class spair {
public:
spair() = default;
spair(std::string s1, std::string s2) : first(std::move(s1)), second(std::move(s2)) {}
std::string first{};
std::string second{};
};
// Example of a custom converter that can be used to add new parsing options.
// It will be found via argument-dependent lookup, so should be in the same namespace as the `spair` type.
bool lexical_cast(const std::string &input, spair &output) {
auto sep = input.find_first_of(':');
if((sep == std::string::npos) && (sep > 0)) {
return false;
}
output = {input.substr(0, sep), input.substr(sep + 1)};
return true;
}
TEST_CASE_METHOD(TApp, "custom_string_converter", "[newparse]") {
spair val;
app.add_option("-d,--dual_string", val);
args = {"-d", "string1:string2"};
run();
CHECK("string1" == val.first);
CHECK("string2" == val.second);
}
TEST_CASE_METHOD(TApp, "custom_string_converterFail", "[newparse]") {
spair val;
app.add_option("-d,--dual_string", val);
args = {"-d", "string2"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
/// Wrapper with an inconvenient interface
template <class T> class badlywrapped {
public:
badlywrapped() : value() {}
CLI11_NODISCARD T get() const { return value; }
void set(T val) { value = val; }
private:
T value;
};
// Example of a custom converter for a template type.
// It will be found via argument-dependent lookup, so should be in the same namespace as the `badlywrapped` type.
template <class T> bool lexical_cast(const std::string &input, badlywrapped<T> &output) {
// This using declaration lets us use an unqualified call to lexical_cast below. This is important because
// unqualified call finds the proper overload via argument-dependent lookup, and thus it will be able to find
// an overload for `spair` type, which is not in `CLI::detail`.
using CLI::detail::lexical_cast;
T value;
if(!lexical_cast(input, value))
return false;
output.set(value);
return true;
}
TEST_CASE_METHOD(TApp, "custom_string_converter_flag", "[newparse]") {
badlywrapped<bool> val;
std::vector<badlywrapped<bool>> vals;
app.add_flag("-1", val);
app.add_flag("-2", vals);
val.set(false);
args = {"-1"};
run();
CHECK(true == val.get());
args = {"-2", "-2"};
run();
CHECK(2 == vals.size());
CHECK(true == vals[0].get());
CHECK(true == vals[1].get());
}
TEST_CASE_METHOD(TApp, "custom_string_converter_adl", "[newparse]") {
// This test checks that the lexical_cast calls route as expected.
badlywrapped<spair> val;
app.add_option("-d,--dual_string", val);
args = {"-d", "string1:string2"};
run();
CHECK("string1" == val.get().first);
CHECK("string2" == val.get().second);
}
/// Another wrapper to test that specializing CLI::detail::lexical_cast works
struct anotherstring {
anotherstring() = default;
std::string s{};
};
// This is a custom converter done via specializing the CLI::detail::lexical_cast template. This was the recommended
// mechanism for extending the library before, so we need to test it. Don't do this in your code, use
// argument-dependent lookup as outlined in the examples for spair and template badlywrapped.
namespace CLI {
namespace detail {
template <> bool lexical_cast<anotherstring>(const std::string &input, anotherstring &output) {
bool result = lexical_cast(input, output.s);
if(result)
output.s += "!";
return result;
}
} // namespace detail
} // namespace CLI
TEST_CASE_METHOD(TApp, "custom_string_converter_specialize", "[newparse]") {
anotherstring s;
app.add_option("-s", s);
args = {"-s", "something"};
run();
CHECK("something!" == s.s);
}
/// Yet another wrapper to test that overloading lexical_cast with enable_if works.
struct yetanotherstring {
yetanotherstring() = default;
std::string s{};
};
template <class T> struct is_my_lexical_cast_enabled : std::false_type {};
template <> struct is_my_lexical_cast_enabled<yetanotherstring> : std::true_type {};
template <class T, CLI::enable_if_t<is_my_lexical_cast_enabled<T>::value, CLI::detail::enabler> = CLI::detail::dummy>
bool lexical_cast(const std::string &input, T &output) {
output.s = input;
return true;
}
TEST_CASE_METHOD(TApp, "custom_string_converter_adl_enable_if", "[newparse]") {
yetanotherstring s;
app.add_option("-s", s);
args = {"-s", "something"};
run();
CHECK("something" == s.s);
}
/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the
/// option assignments
template <class X> class objWrapper {
public:
objWrapper() = default;
explicit objWrapper(X obj) : val_{std::move(obj)} {};
objWrapper(const objWrapper &ow) = default;
template <class TT> objWrapper(const TT &obj) = delete;
objWrapper &operator=(const objWrapper &) = default;
// noexcept not allowed below by GCC 4.8
objWrapper &operator=(objWrapper &&) = default; // NOLINT(performance-noexcept-move-constructor)
// delete all other assignment operators
template <typename TT> void operator=(TT &&obj) = delete;
CLI11_NODISCARD const X &value() const { return val_; }
private:
X val_{};
};
/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the
/// option assignments
template <class X> class objWrapperRestricted {
public:
objWrapperRestricted() = default;
explicit objWrapperRestricted(int val) : val_{val} {};
objWrapperRestricted(const objWrapperRestricted &) = delete;
objWrapperRestricted(objWrapperRestricted &&) = delete;
objWrapperRestricted &operator=(const objWrapperRestricted &) = delete;
objWrapperRestricted &operator=(objWrapperRestricted &&) = delete;
objWrapperRestricted &operator=(int val) {
val_ = val;
return *this;
}
CLI11_NODISCARD const X &value() const { return val_; }
private:
X val_{};
};
// I think there is a bug with the is_assignable in visual studio 2015 it is fixed in later versions
// so this test will not compile in that compiler
#if !defined(_MSC_VER) || _MSC_VER >= 1910
static_assert(CLI::detail::is_direct_constructible<objWrapper<std::string>, std::string>::value,
"string wrapper isn't properly constructible");
static_assert(!std::is_assignable<objWrapper<std::string>, std::string>::value,
"string wrapper is improperly assignable");
TEST_CASE_METHOD(TApp, "stringWrapper", "[newparse]") {
objWrapper<std::string> sWrapper;
app.add_option("-v", sWrapper);
args = {"-v", "string test"};
run();
CHECK("string test" == sWrapper.value());
}
static_assert(CLI::detail::is_direct_constructible<objWrapper<double>, double>::value,
"double wrapper isn't properly assignable");
static_assert(!CLI::detail::is_direct_constructible<objWrapper<double>, int>::value,
"double wrapper can be assigned from int");
static_assert(!CLI::detail::is_istreamable<objWrapper<double>>::value,
"double wrapper is input streamable and it shouldn't be");
TEST_CASE_METHOD(TApp, "doubleWrapper", "[newparse]") {
objWrapper<double> dWrapper;
app.add_option("-v", dWrapper);
args = {"-v", "2.36"};
run();
CHECK(2.36 == dWrapper.value());
args = {"-v", "thing"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
TEST_CASE_METHOD(TApp, "intWrapperRestricted", "[newparse]") {
objWrapperRestricted<double> dWrapper;
app.add_option("-v", dWrapper);
args = {"-v", "4"};
run();
CHECK(4.0 == dWrapper.value());
args = {"-v", "thing"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-v", ""};
run();
CHECK(0.0 == dWrapper.value());
}
static_assert(CLI::detail::is_direct_constructible<objWrapper<int>, int>::value,
"int wrapper is not constructible from int64");
static_assert(!CLI::detail::is_direct_constructible<objWrapper<int>, double>::value,
"int wrapper is constructible from double");
static_assert(!CLI::detail::is_istreamable<objWrapper<int>>::value,
"int wrapper is input streamable and it shouldn't be");
TEST_CASE_METHOD(TApp, "intWrapper", "[newparse]") {
objWrapper<int> iWrapper;
app.add_option("-v", iWrapper);
args = {"-v", "45"};
run();
CHECK(45 == iWrapper.value());
args = {"-v", "thing"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
static_assert(!CLI::detail::is_direct_constructible<objWrapper<float>, int>::value,
"float wrapper is constructible from int");
static_assert(!CLI::detail::is_direct_constructible<objWrapper<float>, double>::value,
"float wrapper is constructible from double");
static_assert(!CLI::detail::is_istreamable<objWrapper<float>>::value,
"float wrapper is input streamable and it shouldn't be");
TEST_CASE_METHOD(TApp, "floatWrapper", "[newparse]") {
objWrapper<float> iWrapper;
app.add_option<objWrapper<float>, float>("-v", iWrapper);
args = {"-v", "45.3"};
run();
CHECK(45.3f == iWrapper.value());
args = {"-v", "thing"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
#endif
/// simple class to wrap another with a very specific type constructor to test out some of the option assignments
class dobjWrapper {
public:
dobjWrapper() = default;
explicit dobjWrapper(double obj) : dval_{obj} {};
explicit dobjWrapper(int obj) : ival_{obj} {};
CLI11_NODISCARD double dvalue() const { return dval_; }
CLI11_NODISCARD int ivalue() const { return ival_; }
private:
double dval_{0.0};
int ival_{0};
};
TEST_CASE_METHOD(TApp, "dobjWrapper", "[newparse]") {
dobjWrapper iWrapper;
app.add_option("-v", iWrapper);
args = {"-v", "45"};
run();
CHECK(45 == iWrapper.ivalue());
CHECK(0.0 == iWrapper.dvalue());
args = {"-v", "thing"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
iWrapper = dobjWrapper{};
args = {"-v", "45.1"};
run();
CHECK(0 == iWrapper.ivalue());
CHECK(45.1 == iWrapper.dvalue());
}
/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the
/// option assignments
template <class X> class AobjWrapper {
public:
AobjWrapper() = default;
// delete all other constructors
template <class TT> AobjWrapper(TT &&obj) = delete;
// single assignment operator
AobjWrapper &operator=(X val) {
val_ = val;
return *this;
}
// delete all other assignment operators
template <typename TT> void operator=(TT &&obj) = delete;
CLI11_NODISCARD const X &value() const { return val_; }
private:
X val_{};
};
static_assert(std::is_assignable<AobjWrapper<std::uint16_t> &, std::uint16_t>::value,
"AobjWrapper not assignable like it should be ");
TEST_CASE_METHOD(TApp, "uint16Wrapper", "[newparse]") {
AobjWrapper<std::uint16_t> sWrapper;
app.add_option<AobjWrapper<std::uint16_t>, std::uint16_t>("-v", sWrapper);
args = {"-v", "9"};
run();
CHECK(9u == sWrapper.value());
args = {"-v", "thing"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-v", "72456245754"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-v", "-3"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
template <class T> class SimpleWrapper {
public:
SimpleWrapper() = default;
explicit SimpleWrapper(T initial) : val_{std::move(initial)} {};
T &getRef() { return val_; }
using value_type = T;
private:
T val_{};
};
TEST_CASE_METHOD(TApp, "wrapperInt", "[newparse]") {
SimpleWrapper<int> wrap;
app.add_option("--val", wrap);
args = {"--val", "2"};
run();
CHECK(2 == wrap.getRef());
}
TEST_CASE_METHOD(TApp, "wrapperString", "[newparse]") {
SimpleWrapper<std::string> wrap;
app.add_option("--val", wrap);
args = {"--val", "str"};
run();
CHECK("str" == wrap.getRef());
}
TEST_CASE_METHOD(TApp, "wrapperVector", "[newparse]") {
SimpleWrapper<std::vector<int>> wrap;
app.add_option("--val", wrap);
args = {"--val", "1", "2", "3", "4"};
run();
auto v1 = wrap.getRef();
auto v2 = std::vector<int>{1, 2, 3, 4};
CHECK(v2 == v1);
}
TEST_CASE_METHOD(TApp, "wrapperwrapperString", "[newparse]") {
SimpleWrapper<SimpleWrapper<std::string>> wrap;
app.add_option("--val", wrap);
args = {"--val", "arg"};
run();
auto v1 = wrap.getRef().getRef();
const auto *v2 = "arg";
CHECK(v2 == v1);
}
TEST_CASE_METHOD(TApp, "wrapperwrapperVector", "[newparse]") {
SimpleWrapper<SimpleWrapper<std::vector<int>>> wrap;
auto *opt = app.add_option("--val", wrap);
args = {"--val", "1", "2", "3", "4"};
run();
auto v1 = wrap.getRef().getRef();
auto v2 = std::vector<int>{1, 2, 3, 4};
CHECK(v2 == v1);
opt->type_size(0, 5);
args = {"--val"};
run();
CHECK(wrap.getRef().getRef().empty());
args = {"--val", "happy", "sad"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
TEST_CASE_METHOD(TApp, "wrapperComplex", "[newparse]") {
SimpleWrapper<std::complex<double>> wrap;
app.add_option("--val", wrap);
args = {"--val", "1", "2"};
run();
auto &v1 = wrap.getRef();
auto v2 = std::complex<double>{1, 2};
CHECK(v2.real() == v1.real());
CHECK(v2.imag() == v1.imag());
args = {"--val", "1.4-4j"};
run();
v2 = std::complex<double>{1.4, -4};
CHECK(v2.real() == v1.real());
CHECK(v2.imag() == v1.imag());
}
TEST_CASE_METHOD(TApp, "vectorComplex", "[newparse]") {
std::vector<std::complex<double>> vcomplex;
app.add_option("--val", vcomplex);
args = {"--val", "1", "2", "--val", "1.4-4j"};
run();
REQUIRE(2U == vcomplex.size());
CHECK(1.0 == vcomplex[0].real());
CHECK(2.0 == vcomplex[0].imag());
CHECK(1.4 == vcomplex[1].real());
CHECK(-4.0 == vcomplex[1].imag());
}
+416
View File
@@ -0,0 +1,416 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include "catch.hpp"
#include <algorithm>
#include <complex>
#include <cstdint>
#include <cstdlib>
#include <deque>
#include <forward_list>
#include <limits>
#include <list>
#include <map>
#include <queue>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using Catch::Matchers::WithinRel;
TEST_CASE_METHOD(TApp, "doubleFunction", "[optiontype]") {
double res{0.0};
app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
args = {"--val", "-354.356"};
run();
CHECK_THAT(res, WithinRel(300.356));
// get the original value as entered as an integer
CHECK_THAT(app["--val"]->as<float>(), WithinRel(-354.356f));
}
TEST_CASE_METHOD(TApp, "doubleFunctionFail", "[optiontype]") {
double res = NAN;
app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
args = {"--val", "not_double"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
TEST_CASE_METHOD(TApp, "doubleVectorFunction", "[optiontype]") {
std::vector<double> res;
app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
res = val;
std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
});
args = {"--val", "5", "--val", "6", "--val", "7"};
run();
CHECK(3u == res.size());
CHECK_THAT(res[0], WithinRel(10.0));
CHECK_THAT(res[2], WithinRel(12.0));
}
TEST_CASE_METHOD(TApp, "doubleVectorFunctionFail", "[optiontype]") {
std::vector<double> res;
std::string vstring = "--val";
app.add_option_function<std::vector<double>>(vstring, [&res](const std::vector<double> &val) {
res = val;
std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
});
args = {"--val", "five", "--val", "nine", "--val", "7"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
// check that getting the results through the results function generates the same error
CHECK_THROWS_AS(app[vstring]->results(res), CLI::ConversionError);
auto strvec = app[vstring]->as<std::vector<std::string>>();
CHECK(3u == strvec.size());
}
TEST_CASE_METHOD(TApp, "doubleVectorFunctionRunCallbackOnDefault", "[optiontype]") {
std::vector<double> res;
auto *opt = app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
res = val;
std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
});
args = {"--val", "5", "--val", "6", "--val", "7"};
run();
CHECK(3u == res.size());
CHECK(10.0 == res[0]);
CHECK(12.0 == res[2]);
CHECK(!opt->get_run_callback_for_default());
opt->run_callback_for_default();
opt->default_val(std::vector<int>{2, 1, -2});
CHECK(7.0 == res[0]);
CHECK(3.0 == res[2]);
CHECK_THROWS_AS(opt->default_val("this is a string"), CLI::ConversionError);
auto vec = opt->as<std::vector<double>>();
REQUIRE(3U == vec.size());
CHECK(5.0 == vec[0]);
CHECK(7.0 == vec[2]);
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
opt->check(CLI::Number);
opt->run_callback_for_default(false);
CHECK_THROWS_AS(opt->default_val("this is a string"), CLI::ValidationError);
#endif
}
static const std::map<std::string, double> testValuesDouble{
{"3.14159", 3.14159},
{"-3.14159", -3.14159},
{"-3.14159\t", -3.14159},
{"-3.14159 ", -3.14159},
{"+1.0", 1.0},
{"-0.01", -0.01},
{"-.01", -0.01},
{"-.3251", -0.3251},
{"+.3251", 0.3251},
{"5e22", 5e22},
{" 5e22", 5e22},
{" 5e22 ", 5e22},
{"-2E-2", -2e-2},
{"5e+22", 5e22},
{"1e06", 1e6},
{"6.626e-34", 6.626e-34},
{"6.626e+34", 6.626e34},
{"-6.626e-34", -6.626e-34},
{"224_617.445_991", 224617.445991},
{"224'617.445'991", 224617.445991},
{"inf", std::numeric_limits<double>::infinity()},
{"+inf", std::numeric_limits<double>::infinity()},
{"-inf", -std::numeric_limits<double>::infinity()},
{"nan", std::numeric_limits<double>::signaling_NaN()},
{"+nan", std::numeric_limits<double>::signaling_NaN()},
{"-nan", -std::numeric_limits<double>::signaling_NaN()},
};
TEST_CASE_METHOD(TApp, "floatingConversions", "[optiontype]") {
auto test_data = GENERATE(from_range(testValuesDouble));
double val{0};
app.add_option("--val", val);
args = {"--val", test_data.first};
run();
if(std::isnan(test_data.second)) {
CHECK(std::isnan(val));
} else {
CHECK_THAT(val, WithinRel(test_data.second, 1e-11));
}
}
static const std::map<std::string, std::int64_t> testValuesInt{
{"+99", 99},
{"99", 99},
{"-99", -99},
{"-99 ", -99},
{"0xDEADBEEF", 0xDEADBEEF},
{"0xdeadbeef", 0xDEADBEEF},
{"0XDEADBEEF", 0xDEADBEEF},
{"0Xdeadbeef", 0xDEADBEEF},
{"0xdead_beef", 0xDEADBEEF},
{"0xdead'beef", 0xDEADBEEF},
{"0o01234567", 001234567},
{"0o755", 0755},
{"0755", 0755},
{"995862_262", 995862262},
{"995862262", 995862262},
{"-995862275", -995862275},
{"\t-995862275\t", -995862275},
{"-995'862'275", -995862275},
{"0b11010110", 0xD6},
{"0b1101'0110", 0xD6},
{"0B11010110", 0xD6},
{"0B1101'0110", 0xD6},
{"1_2_3_4_5", 12345},
};
TEST_CASE_METHOD(TApp, "intConversions", "[optiontype]") {
auto test_data = GENERATE(from_range(testValuesInt));
std::int64_t val{0};
app.add_option("--val", val);
args = {"--val", test_data.first};
run();
CHECK(val == test_data.second);
}
TEST_CASE_METHOD(TApp, "intConversionsErange", "[optiontype]") {
std::int64_t val{0};
app.add_option("--val", val);
args = {"--val", "0o11545241241415151512312415123125667"};
CHECK_THROWS_AS(run(), CLI::ParseError);
args = {"--val", "0b1011000001101011001100110011111000101010101011111111111111111111111001010111011100"};
CHECK_THROWS_AS(run(), CLI::ParseError);
args = {"--val", "0B1011000001101011001100110011111000101010101011111111111111111111111001010111011100"};
CHECK_THROWS_AS(run(), CLI::ParseError);
}
static const std::map<std::string, std::uint64_t> testValuesUInt{
{"+99", 99},
{"99", 99},
{" 99 ", 99},
{"0xDEADBEEF", 0xDEADBEEF},
{"0xdeadbeef", 0xDEADBEEF},
{"0XDEADBEEF", 0xDEADBEEF},
{"0Xdeadbeef", 0xDEADBEEF},
{"0xdead_beef", 0xDEADBEEF},
{"0xdead'beef", 0xDEADBEEF},
{"0o01234567", 001234567},
{"0o755", 0755},
{"0o755\t", 0755},
{"0755", 0755},
{"995862_262", 995862262},
{"995862262", 995862262},
{"+995862275", +995862275},
{"+995862275 \n\t", +995862275},
{"995'862'275", 995862275},
{"0b11010110", 0xD6},
{"0b1101'0110", 0xD6},
{"0b1101'0110 ", 0xD6},
{"0B11010110", 0xD6},
{"0B1101'0110", 0xD6},
{"1_2_3_4_5", 12345},
};
TEST_CASE_METHOD(TApp, "uintConversions", "[optiontype]") {
auto test_data = GENERATE(from_range(testValuesUInt));
std::uint64_t val{0};
app.add_option("--val", val);
args = {"--val", test_data.first};
run();
CHECK(val == test_data.second);
}
TEST_CASE_METHOD(TApp, "uintConversionsErange", "[optiontype]") {
std::uint64_t val{0};
app.add_option("--val", val);
args = {"--val", "0o11545241241415151512312415123125667"};
CHECK_THROWS_AS(run(), CLI::ParseError);
args = {"--val", "0b1011000001101011001100110011111000101010101011111111111111111111111001010111011100"};
CHECK_THROWS_AS(run(), CLI::ParseError);
args = {"--val", "0B1011000001101011001100110011111000101010101011111111111111111111111001010111011100"};
CHECK_THROWS_AS(run(), CLI::ParseError);
}
TEST_CASE_METHOD(TApp, "CharOption", "[optiontype]") {
char c1{'t'};
app.add_option("-c", c1);
args = {"-c", "g"};
run();
CHECK('g' == c1);
args = {"-c", "1"};
run();
CHECK('1' == c1);
args = {"-c", "77"};
run();
CHECK(77 == c1);
// convert hex for digit
args = {"-c", "0x44"};
run();
CHECK(0x44 == c1);
args = {"-c", "751615654161688126132138844896646748852"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
TEST_CASE_METHOD(TApp, "IntegerOverFlowShort", "[optiontype]") {
std::int16_t A{0};
std::uint16_t B{0};
app.add_option("-a", A);
app.add_option("-b", B);
args = {"-a", "2626254242"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-b", "2626254242"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-b", "-26262"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-b", "-262624262525"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
TEST_CASE_METHOD(TApp, "IntegerOverFlowInt", "[optiontype]") {
int A{0};
unsigned int B{0};
app.add_option("-a", A);
app.add_option("-b", B);
args = {"-a", "262625424225252"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-b", "262625424225252"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-b", "-2626225252"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-b", "-26262426252525252"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
TEST_CASE_METHOD(TApp, "IntegerOverFlowLong", "[optiontype]") {
std::int32_t A{0};
std::uint32_t B{0};
app.add_option("-a", A);
app.add_option("-b", B);
args = {"-a", "1111111111111111111111111111"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-b", "1111111111111111111111111111"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-b", "-2626225252"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-b", "-111111111111111111111111"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
TEST_CASE_METHOD(TApp, "IntegerOverFlowLongLong", "[optiontype]") {
std::int64_t A{0};
std::uint64_t B{0};
app.add_option("-a", A);
app.add_option("-b", B);
args = {"-a", "1111111111111111111111111111111111111111111111111111111111"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-b", "1111111111111111111111111111111111111111111111111111111111"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-b", "-2626225252"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
args = {"-b", "-111111111111111111111111111111111111111111111111111111111"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
// now with tuple support this is possible
TEST_CASE_METHOD(TApp, "floatPair", "[optiontype]") {
std::pair<float, float> custom_opt;
auto *opt = app.add_option("--fp", custom_opt)->delimiter(',');
opt->default_str("3.4,2.7");
args = {"--fp", "12", "1.5"};
run();
CHECK(12.0f == Approx(custom_opt.first));
CHECK(1.5f == Approx(custom_opt.second));
args = {};
opt->force_callback();
run();
CHECK(3.4f == Approx(custom_opt.first));
CHECK(2.7f == Approx(custom_opt.second));
}
// now with tuple support this is possible
TEST_CASE_METHOD(TApp, "doubleVector", "[optiontype]") {
std::vector<double> custom_opt;
app.add_option("--fp", custom_opt);
args = {"--fp", "12.7", "1.5"};
run();
CHECK(12.7 == Approx(custom_opt[0]));
CHECK(1.5 == Approx(custom_opt[1]));
args = {"--fp", "12.7", "-.5"};
run();
CHECK(12.7 == Approx(custom_opt[0]));
CHECK(-0.5 == Approx(custom_opt[1]));
args = {"--fp", "-.7", "+.5"};
run();
CHECK(-0.7 == Approx(custom_opt[0]));
CHECK(0.5 == Approx(custom_opt[1]));
}
+902
View File
@@ -0,0 +1,902 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include <memory>
#include <string>
#include <vector>
using vs_t = std::vector<std::string>;
TEST_CASE_METHOD(TApp, "BasicOptionGroup", "[optiongroup]") {
auto *ogroup = app.add_option_group("clusters");
int res = 0;
ogroup->add_option("--test1", res);
ogroup->add_option("--test2", res);
ogroup->add_option("--test3", res);
args = {"--test1", "5"};
run();
CHECK(5 == res);
CHECK(1u == app.count_all());
}
TEST_CASE_METHOD(TApp, "OptionGroupInvalidNames", "[optiongroup]") {
CHECK_THROWS_AS(app.add_option_group("clusters\ncluster2", "description"), CLI::IncorrectConstruction);
std::string groupName("group1");
groupName += '\0';
groupName.append("group2");
CHECK_THROWS_AS(app.add_option_group(groupName), CLI::IncorrectConstruction);
}
TEST_CASE_METHOD(TApp, "BasicOptionGroupExact", "[optiongroup]") {
auto *ogroup = app.add_option_group("clusters");
int res{0};
ogroup->add_option("--test1", res);
ogroup->add_option("--test2", res);
ogroup->add_option("--test3", res);
int val2{0};
app.add_option("--option", val2);
ogroup->require_option(1);
args = {"--test1", "5"};
run();
CHECK(5 == res);
args = {"--test1", "5", "--test2", "4"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {"--option", "9"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
std::string help = ogroup->help();
auto exactloc = help.find("[Exactly 1");
CHECK(std::string::npos != exactloc);
}
TEST_CASE_METHOD(TApp, "BasicOptionGroupExactTooMany", "[optiongroup]") {
auto *ogroup = app.add_option_group("clusters");
int res{0};
ogroup->add_option("--test1", res);
ogroup->add_option("--test2", res);
ogroup->add_option("--test3", res);
int val2{0};
app.add_option("--option", val2);
ogroup->require_option(10);
args = {"--test1", "5"};
CHECK_THROWS_AS(run(), CLI::InvalidError);
}
TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMax", "[optiongroup]") {
auto *ogroup = app.add_option_group("clusters");
int res{0};
ogroup->add_option("--test1", res);
ogroup->add_option("--test2", res);
ogroup->add_option("--test3", res);
int val2{0};
app.add_option("--option", val2);
ogroup->require_option(1, 1);
args = {"--test1", "5"};
run();
CHECK(5 == res);
args = {"--test1", "5", "--test2", "4"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {"--option", "9"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
std::string help = ogroup->help();
auto exactloc = help.find("[Exactly 1");
CHECK(std::string::npos != exactloc);
}
TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMaxDifferent", "[optiongroup]") {
auto *ogroup = app.add_option_group("clusters");
int res{0};
ogroup->add_option("--test1", res);
ogroup->add_option("--test2", res);
ogroup->add_option("--test3", res);
int val2{0};
app.add_option("--option", val2);
ogroup->require_option(1, 2);
args = {"--test1", "5"};
run();
CHECK(5 == res);
args = {"--test1", "5", "--test2", "4"};
CHECK_NOTHROW(run());
CHECK(2u == app.count_all());
args = {"--option", "9"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {"--test1", "5", "--test2", "4", "--test3=5"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
std::string help = ogroup->help();
auto exactloc = help.find("[Between 1 and 2");
CHECK(std::string::npos != exactloc);
}
TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMaxDifferentReversed", "[optiongroup]") {
auto *ogroup = app.add_option_group("clusters");
int res{0};
ogroup->add_option("--test1", res);
ogroup->add_option("--test2", res);
ogroup->add_option("--test3", res);
int val2{0};
app.add_option("--option", val2);
ogroup->require_option(2, 1);
CHECK(2u == ogroup->get_require_option_min());
CHECK(1u == ogroup->get_require_option_max());
args = {"--test1", "5"};
CHECK_THROWS_AS(run(), CLI::InvalidError);
ogroup->require_option(1, 2);
CHECK_NOTHROW(run());
CHECK(5 == res);
CHECK(1u == ogroup->get_require_option_min());
CHECK(2u == ogroup->get_require_option_max());
args = {"--test1", "5", "--test2", "4"};
CHECK_NOTHROW(run());
args = {"--option", "9"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {"--test1", "5", "--test2", "4", "--test3=5"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
std::string help = ogroup->help();
auto exactloc = help.find("[Between 1 and 2");
CHECK(std::string::npos != exactloc);
}
TEST_CASE_METHOD(TApp, "BasicOptionGroupMax", "[optiongroup]") {
auto *ogroup = app.add_option_group("clusters");
int res{0};
ogroup->add_option("--test1", res);
ogroup->add_option("--test2", res);
ogroup->add_option("--test3", res);
int val2 = 0;
app.add_option("--option", val2);
ogroup->require_option(-2);
args = {"--test1", "5"};
run();
CHECK(5 == res);
args = {"--option", "9"};
CHECK_NOTHROW(run());
args = {"--test1", "5", "--test2", "4", "--test3=5"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
std::string help = ogroup->help();
auto exactloc = help.find("[At most 2");
CHECK(std::string::npos != exactloc);
}
TEST_CASE_METHOD(TApp, "BasicOptionGroupMax1", "[optiongroup]") {
auto *ogroup = app.add_option_group("clusters");
int res{0};
ogroup->add_option("--test1", res);
ogroup->add_option("--test2", res);
ogroup->add_option("--test3", res);
int val2{0};
app.add_option("--option", val2);
ogroup->require_option(-1);
args = {"--test1", "5"};
run();
CHECK(5 == res);
args = {"--option", "9"};
CHECK_NOTHROW(run());
args = {"--test1", "5", "--test2", "4"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
std::string help = ogroup->help();
auto exactloc = help.find("[At most 1");
CHECK(std::string::npos != exactloc);
}
TEST_CASE_METHOD(TApp, "BasicOptionGroupMin", "[optiongroup]") {
auto *ogroup = app.add_option_group("clusters");
int res{0};
ogroup->add_option("--test1", res);
ogroup->add_option("--test2", res);
ogroup->add_option("--test3", res);
int val2{0};
app.add_option("--option", val2);
ogroup->require_option();
args = {"--option", "9"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {"--test1", "5", "--test2", "4", "--test3=5"};
CHECK_NOTHROW(run());
std::string help = ogroup->help();
auto exactloc = help.find("[At least 1");
CHECK(std::string::npos != exactloc);
}
TEST_CASE_METHOD(TApp, "integratedOptionGroup", "[optiongroup]") {
auto *ogroup = app.add_option_group("+clusters");
int res{0};
ogroup->add_option("--test1", res);
ogroup->add_option("--test2", res);
ogroup->add_option("--test3", res);
int val2{0};
app.add_option("--option", val2);
ogroup->require_option();
args = {"--option", "9"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {"--test1", "5", "--test2", "4", "--test3=5"};
CHECK_NOTHROW(run());
auto options = app.get_options();
CHECK(options.size() == 5);
const CLI::App *capp = &app;
auto coptions = capp->get_options();
CHECK(coptions.size() == 5);
std::string help = app.help();
auto exactloc = help.find("clusters");
CHECK(std::string::npos == exactloc);
}
TEST_CASE_METHOD(TApp, "BasicOptionGroupExact2", "[optiongroup]") {
auto *ogroup = app.add_option_group("clusters");
int res{0};
ogroup->add_option("--test1", res);
ogroup->add_option("--test2", res);
ogroup->add_option("--test3", res);
int val2{0};
app.add_option("--option", val2);
ogroup->require_option(2);
args = {"--option", "9"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {"--test1", "5", "--test2", "4", "--test3=5"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {"--test1", "5", "--test3=5"};
CHECK_NOTHROW(run());
std::string help = ogroup->help();
auto exactloc = help.find("[Exactly 2");
CHECK(std::string::npos != exactloc);
}
TEST_CASE_METHOD(TApp, "BasicOptionGroupMin2", "[optiongroup]") {
auto *ogroup = app.add_option_group("clusters");
int res{0};
ogroup->add_option("--test1", res);
ogroup->add_option("--test2", res);
ogroup->add_option("--test3", res);
int val2{0};
app.add_option("--option", val2);
ogroup->require_option(2, 0);
args = {"--option", "9"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {"--test1", "5", "--test2", "4", "--test3=5"};
CHECK_NOTHROW(run());
std::string help = ogroup->help();
auto exactloc = help.find("[At least 2");
CHECK(std::string::npos != exactloc);
}
TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMoved", "[optiongroup]") {
int res{0};
auto *opt1 = app.add_option("--test1", res);
auto *opt2 = app.add_option("--test2", res);
auto *opt3 = app.add_option("--test3", res);
int val2{0};
app.add_option("--option", val2);
auto *ogroup = app.add_option_group("clusters");
ogroup->require_option();
ogroup->add_option(opt1);
ogroup->add_option(opt2);
ogroup->add_option(opt3);
args = {"--option", "9"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {"--test1", "5", "--test2", "4", "--test3=5"};
CHECK_NOTHROW(run());
std::string help = app.help();
auto exactloc = help.find("[At least 1");
auto oloc = help.find("--test1");
CHECK(std::string::npos != exactloc);
CHECK(std::string::npos != oloc);
CHECK(oloc > exactloc);
}
TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMovedAsGroup", "[optiongroup]") {
int res{0};
auto *opt1 = app.add_option("--test1", res);
auto *opt2 = app.add_option("--test2", res);
auto *opt3 = app.add_option("--test3", res);
int val2{0};
app.add_option("--option", val2);
auto *ogroup = app.add_option_group("clusters");
ogroup->require_option();
ogroup->add_options(opt1, opt2, opt3);
CHECK_THROWS_AS(ogroup->add_options(opt1), CLI::OptionNotFound);
args = {"--option", "9"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {"--test1", "5", "--test2", "4", "--test3=5"};
CHECK_NOTHROW(run());
std::string help = app.help();
auto exactloc = help.find("[At least 1");
auto oloc = help.find("--test1");
CHECK(std::string::npos != exactloc);
CHECK(std::string::npos != oloc);
CHECK(oloc > exactloc);
}
TEST_CASE_METHOD(TApp, "BasicOptionGroupAddFailures", "[optiongroup]") {
int res{0};
auto *opt1 = app.add_option("--test1", res);
app.set_config("--config");
int val2{0};
app.add_option("--option", val2);
auto *ogroup = app.add_option_group("clusters");
CHECK_THROWS_AS(ogroup->add_options(app.get_config_ptr()), CLI::OptionAlreadyAdded);
CHECK_THROWS_AS(ogroup->add_options(app.get_help_ptr()), CLI::OptionAlreadyAdded);
auto *sub = app.add_subcommand("sub", "subcommand");
auto *opt2 = sub->add_option("--option2", val2);
CHECK_THROWS_AS(ogroup->add_option(opt2), CLI::OptionNotFound);
CHECK_THROWS_AS(ogroup->add_options(nullptr), CLI::OptionNotFound);
ogroup->add_option(opt1);
auto *opt3 = app.add_option("--test1", res);
CHECK_THROWS_AS(ogroup->add_option(opt3), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "BasicOptionGroupScrewedUpMove", "[optiongroup]") {
int res{0};
auto *opt1 = app.add_option("--test1", res);
auto *opt2 = app.add_option("--test2", res);
int val2{0};
app.add_option("--option", val2);
auto *ogroup = app.add_option_group("clusters");
ogroup->require_option();
auto *ogroup2 = ogroup->add_option_group("clusters2");
CHECK_THROWS_AS(ogroup2->add_options(opt1, opt2), CLI::OptionNotFound);
CLI::Option_group EmptyGroup("description", "new group", nullptr);
CHECK_THROWS_AS(EmptyGroup.add_option(opt2), CLI::OptionNotFound);
CHECK_THROWS_AS(app._move_option(opt2, ogroup2), CLI::OptionNotFound);
}
TEST_CASE_METHOD(TApp, "InvalidOptions", "[optiongroup]") {
auto *ogroup = app.add_option_group("clusters");
CLI::Option *opt = nullptr;
CHECK_THROWS_AS(ogroup->excludes(opt), CLI::OptionNotFound);
CLI::App *app_p = nullptr;
CHECK_THROWS_AS(ogroup->excludes(app_p), CLI::OptionNotFound);
CHECK_THROWS_AS(ogroup->excludes(ogroup), CLI::OptionNotFound);
CHECK_THROWS_AS(ogroup->add_option(opt), CLI::OptionNotFound);
}
TEST_CASE_METHOD(TApp, "OptionGroupInheritedOptionDefaults", "[optiongroup]") {
app.option_defaults()->ignore_case();
auto *ogroup = app.add_option_group("clusters");
int res{0};
ogroup->add_option("--test1", res);
args = {"--Test1", "5"};
run();
CHECK(5 == res);
CHECK(1u == app.count_all());
}
struct ManyGroups : public TApp {
CLI::Option_group *main{nullptr};
CLI::Option_group *g1{nullptr};
CLI::Option_group *g2{nullptr};
CLI::Option_group *g3{nullptr};
std::string name1{};
std::string name2{};
std::string name3{};
std::string val1{};
std::string val2{};
std::string val3{};
ManyGroups(const ManyGroups &) = delete;
ManyGroups &operator=(const ManyGroups &) = delete;
ManyGroups() {
main = app.add_option_group("main", "the main outer group");
g1 = main->add_option_group("g1", "group1 description");
g2 = main->add_option_group("g2", "group2 description");
g3 = main->add_option_group("g3", "group3 description");
g1->add_option("--name1", name1)->required();
g1->add_option("--val1", val1);
g2->add_option("--name2", name2)->required();
g2->add_option("--val2", val2);
g3->add_option("--name3", name3)->required();
g3->add_option("--val3", val3);
}
void remove_required() { // NOLINT(readability-make-member-function-const)
g1->get_option("--name1")->required(false);
g2->get_option("--name2")->required(false);
g3->get_option("--name3")->required(false);
g1->required(false);
g2->required(false);
g3->required(false);
}
};
TEST_CASE_METHOD(ManyGroups, "SingleGroup", "[optiongroup]") {
// only 1 group can be used
main->require_option(1);
args = {"--name1", "test"};
run();
CHECK("test" == name1);
args = {"--name2", "test", "--val2", "tval"};
run();
CHECK("tval" == val2);
args = {"--name1", "test", "--val2", "tval"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
}
TEST_CASE_METHOD(ManyGroups, "getGroup", "[optiongroup]") {
auto *mn = app.get_option_group("main");
CHECK(mn == main);
CHECK_THROWS_AS(app.get_option_group("notfound"), CLI::OptionNotFound);
}
TEST_CASE_METHOD(ManyGroups, "ExcludesGroup", "[optiongroup]") {
// only 1 group can be used
g1->excludes(g2);
g1->excludes(g3);
args = {"--name1", "test"};
run();
CHECK("test" == name1);
args = {"--name1", "test", "--name2", "test2"};
CHECK_THROWS_AS(run(), CLI::ExcludesError);
CHECK(g1->remove_excludes(g2));
CHECK_NOTHROW(run());
CHECK(!g1->remove_excludes(g1));
CHECK(!g1->remove_excludes(g2));
}
TEST_CASE_METHOD(ManyGroups, "NeedsGroup", "[optiongroup]") {
remove_required();
// all groups needed if g1 is used
g1->needs(g2);
g1->needs(g3);
args = {"--name1", "test"};
CHECK_THROWS_AS(run(), CLI::RequiresError);
// other groups should run fine
args = {"--name2", "test2"};
run();
// all three groups should be fine
args = {"--name1", "test", "--name2", "test2", "--name3", "test3"};
CHECK_NOTHROW(run());
}
// test adding an option group with existing subcommands to an app
TEST_CASE_METHOD(TApp, "ExistingSubcommandMatch", "[optiongroup]") {
auto sshared = std::make_shared<CLI::Option_group>("documenting the subcommand", "sub1g", nullptr);
auto *s1 = sshared->add_subcommand("sub1");
auto *o1 = sshared->add_option_group("opt1");
o1->add_subcommand("sub3")->alias("sub4");
app.add_subcommand("sub1");
try {
app.add_subcommand(sshared);
// this should throw the next line should never be reached
CHECK(!true);
} catch(const CLI::OptionAlreadyAdded &oaa) {
CHECK_THAT(oaa.what(), Contains("sub1"));
}
sshared->remove_subcommand(s1);
app.add_subcommand("sub3");
// now check that the subsubcommand overlaps
try {
app.add_subcommand(sshared);
// this should throw the next line should never be reached
CHECK(!true);
} catch(const CLI::OptionAlreadyAdded &oaa) {
CHECK_THAT(oaa.what(), Contains("sub3"));
}
}
TEST_CASE_METHOD(ManyGroups, "SingleGroupError", "[optiongroup]") {
// only 1 group can be used
main->require_option(1);
args = {"--name1", "test", "--name2", "test3"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
}
TEST_CASE_METHOD(ManyGroups, "AtMostOneGroup", "[optiongroup]") {
// only 1 group can be used
main->require_option(0, 1);
args = {"--name1", "test", "--name2", "test3"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {};
CHECK_NOTHROW(run());
}
TEST_CASE_METHOD(ManyGroups, "AtLeastTwoGroups", "[optiongroup]") {
// only 1 group can be used
main->require_option(2, 0);
args = {"--name1", "test", "--name2", "test3"};
run();
args = {"--name1", "test"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
}
TEST_CASE_METHOD(ManyGroups, "BetweenOneAndTwoGroups", "[optiongroup]") {
// only 1 group can be used
main->require_option(1, 2);
args = {"--name1", "test", "--name2", "test3"};
run();
args = {"--name1", "test"};
run();
args = {};
CHECK_THROWS_AS(run(), CLI::RequiredError);
args = {"--name1", "test", "--name2", "test3", "--name3=test3"};
CHECK_THROWS_AS(run(), CLI::RequiredError);
}
TEST_CASE_METHOD(ManyGroups, "RequiredFirst", "[optiongroup]") {
// only 1 group can be used
remove_required();
g1->required();
CHECK(g1->get_required());
CHECK(!g2->get_required());
args = {"--name1", "test", "--name2", "test3"};
run();
args = {"--name2", "test"};
try {
run();
} catch(const CLI::RequiredError &re) {
CHECK_THAT(re.what(), Contains("g1"));
}
args = {"--name1", "test", "--name2", "test3", "--name3=test3"};
CHECK_NOTHROW(run());
}
TEST_CASE_METHOD(ManyGroups, "DisableFirst", "[optiongroup]") {
// only 1 group can be used if remove_required not used
remove_required();
g1->disabled();
CHECK(g1->get_disabled());
CHECK(!g2->get_disabled());
args = {"--name2", "test"};
run();
args = {"--name1", "test", "--name2", "test3"};
CHECK_THROWS_AS(run(), CLI::ExtrasError);
g1->disabled(false);
args = {"--name1", "test", "--name2", "test3", "--name3=test3"};
CHECK_NOTHROW(run());
}
TEST_CASE_METHOD(ManyGroups, "SameSubcommand", "[optiongroup]") {
// only 1 group can be used if remove_required not used
remove_required();
auto *sub1 = g1->add_subcommand("sub1")->disabled();
auto *sub2 = g2->add_subcommand("sub1")->disabled();
auto *sub3 = g3->add_subcommand("sub1");
// so when the subcommands are disabled they can have the same name
sub1->disabled(false);
sub2->disabled(false);
// if they are re-enabled they are not checked for overlap on enabling so they can have the same name
args = {"sub1", "sub1", "sub1"};
run();
CHECK(*sub1);
CHECK(*sub2);
CHECK(*sub3);
auto subs = app.get_subcommands();
CHECK(3u == subs.size());
CHECK(sub1 == subs[0]);
CHECK(sub2 == subs[1]);
CHECK(sub3 == subs[2]);
args = {"sub1", "sub1", "sub1", "sub1"};
// for the 4th and future ones they will route to the first one
run();
CHECK(2u == sub1->count());
CHECK(1u == sub2->count());
CHECK(1u == sub3->count());
// subs should remain the same since the duplicate would not be registered there
subs = app.get_subcommands();
CHECK(3u == subs.size());
CHECK(sub1 == subs[0]);
CHECK(sub2 == subs[1]);
CHECK(sub3 == subs[2]);
}
TEST_CASE_METHOD(ManyGroups, "CallbackOrder", "[optiongroup]") {
// only 1 group can be used if remove_required not used
remove_required();
std::vector<int> callback_order;
g1->callback([&callback_order]() { callback_order.push_back(1); });
g2->callback([&callback_order]() { callback_order.push_back(2); });
main->callback([&callback_order]() { callback_order.push_back(3); });
args = {"--name2", "test"};
run();
CHECK(std::vector<int>({2, 3}) == callback_order);
callback_order.clear();
args = {"--name1", "t2", "--name2", "test"};
g2->immediate_callback();
run();
CHECK(std::vector<int>({2, 1, 3}) == callback_order);
callback_order.clear();
args = {"--name2", "test", "--name1", "t2"};
g2->immediate_callback(false);
run();
CHECK(std::vector<int>({1, 2, 3}) == callback_order);
}
// Test the fallthrough for extra arguments
TEST_CASE_METHOD(ManyGroups, "ExtrasFallDown", "[optiongroup]") {
// only 1 group can be used if remove_required not used
remove_required();
args = {"--test1", "--flag", "extra"};
CHECK_THROWS_AS(run(), CLI::ExtrasError);
main->allow_extras();
CHECK_NOTHROW(run());
CHECK(3u == app.remaining_size(true));
CHECK(3u == main->remaining_size());
std::vector<std::string> extras{"--test1", "--flag", "extra"};
CHECK(extras == app.remaining(true));
CHECK(extras == main->remaining());
app.allow_extras(CLI::ExtrasMode::Ignore);
CHECK_NOTHROW(run());
CHECK(0u == app.remaining_size(false));
CHECK(extras == app.remaining(true));
CHECK(extras == main->remaining());
}
// Test the option Inheritance
TEST_CASE_METHOD(ManyGroups, "Inheritance", "[optiongroup]") {
remove_required();
g1->ignore_case();
g1->ignore_underscore();
auto *t2 = g1->add_subcommand("t2");
args = {"T2", "t_2"};
CHECK(t2->get_ignore_underscore());
CHECK(t2->get_ignore_case());
run();
CHECK(2u == t2->count());
}
TEST_CASE_METHOD(ManyGroups, "Moving", "[optiongroup]") {
remove_required();
auto *mg = app.add_option_group("maing");
mg->add_subcommand(g1);
mg->add_subcommand(g2);
CHECK(mg == g1->get_parent());
CHECK(mg == g2->get_parent());
CHECK(main == g3->get_parent());
}
struct ManyGroupsPreTrigger : public ManyGroups {
std::size_t triggerMain{0u}, trigger1{87u}, trigger2{34u}, trigger3{27u};
ManyGroupsPreTrigger() {
remove_required();
app.preparse_callback([this](std::size_t count) { triggerMain = count; });
g1->preparse_callback([this](std::size_t count) { trigger1 = count; });
g2->preparse_callback([this](std::size_t count) { trigger2 = count; });
g3->preparse_callback([this](std::size_t count) { trigger3 = count; });
}
};
TEST_CASE_METHOD(ManyGroupsPreTrigger, "PreTriggerTestsOptions", "[optiongroup]") {
args = {"--name1", "test", "--name2", "test3"};
run();
CHECK(4u == triggerMain);
CHECK(2u == trigger1);
CHECK(0u == trigger2);
CHECK(27u == trigger3);
args = {"--name1", "test"};
trigger2 = 34u;
run();
CHECK(2u == triggerMain);
CHECK(0u == trigger1);
CHECK(34u == trigger2);
args = {};
run();
CHECK(0u == triggerMain);
args = {"--name1", "test", "--val1", "45", "--name2", "test3", "--name3=test3", "--val2=37"};
run();
CHECK(8u == triggerMain);
CHECK(6u == trigger1);
CHECK(2u == trigger2);
CHECK(1u == trigger3);
}
TEST_CASE_METHOD(ManyGroupsPreTrigger, "PreTriggerTestsPositionals", "[optiongroup]") {
// only 1 group can be used
g1->add_option("pos1");
g2->add_option("pos2");
g3->add_option("pos3");
args = {"pos1"};
run();
CHECK(1u == triggerMain);
CHECK(0u == trigger1);
CHECK(34u == trigger2);
CHECK(27u == trigger3);
args = {"pos1", "pos2"};
run();
CHECK(2u == triggerMain);
CHECK(1u == trigger1);
CHECK(0u == trigger2);
args = {"pos1", "pos2", "pos3"};
run();
CHECK(3u == triggerMain);
CHECK(2u == trigger1);
CHECK(1u == trigger2);
CHECK(0u == trigger3);
}
TEST_CASE_METHOD(ManyGroupsPreTrigger, "PreTriggerTestsSubcommand", "[optiongroup]") {
auto *sub1 = g1->add_subcommand("sub1")->fallthrough();
g2->add_subcommand("sub2")->fallthrough();
g3->add_subcommand("sub3")->fallthrough();
std::size_t subtrigger = 0;
sub1->preparse_callback([&subtrigger](std::size_t count) { subtrigger = count; });
args = {"sub1"};
run();
CHECK(1u == triggerMain);
CHECK(0u == trigger1);
CHECK(34u == trigger2);
CHECK(27u == trigger3);
args = {"sub1", "sub2"};
run();
CHECK(2u == triggerMain);
CHECK(1u == subtrigger);
CHECK(1u == trigger1);
CHECK(0u == trigger2);
args = {"sub2", "sub3", "--name1=test", "sub1"};
run();
CHECK(4u == triggerMain);
CHECK(1u == trigger1);
CHECK(3u == trigger2);
CHECK(1u == trigger3);
// go until the sub1 command is given
}
TEST_CASE_METHOD(ManyGroups, "OptionFind", "[optiongroup]") {
auto *opt_main = app.add_option("--base");
g1->fallthrough();
auto *opt_name = g1->get_option("--base");
CHECK(opt_name == opt_main);
CHECK_THROWS_AS(g1->get_option("--notfound"), CLI::OptionNotFound);
auto const *g1_const = g1;
const auto *opt_name_const = g1_const->get_option("--base");
CHECK(opt_name_const == opt_main);
CHECK_THROWS_AS(g1_const->get_option("--notfound"), CLI::OptionNotFound);
}
// from https://github.com/CLIUtils/CLI11/issues/1355
TEST_CASE_METHOD(TApp, "SubgroupHelpIndentation", "[optiongroup]") {
auto *group = app.add_option_group("group", "common group");
group->add_option_group("subgroup 1", "subgroup 1 description");
group->add_option_group("subgroup 2", "subgroup 2 description");
group->add_option_group("subgroup 3", "subgroup 3 description");
const std::string help = app.help();
CHECK_THAT(help, Contains("[Option Group: group]\n common group"));
CHECK_THAT(help, Contains(" [Option Group: subgroup 1]\n subgroup 1 description"));
CHECK_THAT(help, Contains(" [Option Group: subgroup 2]\n subgroup 2 description"));
CHECK_THAT(help, Contains(" [Option Group: subgroup 3]\n subgroup 3 description"));
}
// from https://github.com/CLIUtils/CLI11/issues/1355
TEST_CASE_METHOD(TApp, "SubgroupHelpIndentationWithOption", "[optiongroup]") {
auto *group = app.add_option_group("group", "common group");
auto *subgroup1 = group->add_option_group("subgroup 1", "subgroup 1 description");
subgroup1->add_option("-a", "option a");
group->add_option_group("subgroup 2", "subgroup 2 description");
group->add_option_group("subgroup 3", "subgroup 3 description");
const std::string help = app.help();
CHECK_THAT(help, Contains(" [Option Group: subgroup 1]\n subgroup 1 description"));
CHECK_THAT(help, Contains(" OPTIONS:\n -a"));
CHECK_THAT(help, Contains(" [Option Group: subgroup 2]\n subgroup 2 description"));
CHECK_THAT(help, Contains(" [Option Group: subgroup 3]\n subgroup 3 description"));
}
// from https://github.com/CLIUtils/CLI11/issues/1315
TEST_CASE_METHOD(TApp, "SubcommandOptionGroupWithFallthrough", "[optiongroup]") {
// code from https://github.com/The0Dev
bool flag{false};
std::string str;
app.add_flag("--flag,!--no-flag", flag, "Enable a flag");
// Disabling this prevents the issue:
app.fallthrough(true);
auto *sub = app.add_subcommand("sub", "Execute a subcommand");
auto *group = sub->add_option_group("GROUP");
// possible failure
CHECK_NOTHROW(group->add_option("-p,--path", str, "An option"));
}
File diff suppressed because it is too large Load Diff
+396
View File
@@ -0,0 +1,396 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include <complex>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
#include "app_helper.hpp"
// You can explicitly enable or disable support
// by defining to 1 or 0. Extra check here to ensure it's in the stdlib too.
// We nest the check for __has_include and it's usage
#ifndef CLI11_STD_OPTIONAL
#ifdef __has_include
#if defined(CLI11_CPP17) && __has_include(<optional>)
#define CLI11_STD_OPTIONAL 1
#else
#define CLI11_STD_OPTIONAL 0
#endif
#else
#define CLI11_STD_OPTIONAL 0
#endif
#endif
#ifndef CLI11_EXPERIMENTAL_OPTIONAL
#define CLI11_EXPERIMENTAL_OPTIONAL 0
#endif
#ifndef CLI11_BOOST_OPTIONAL
#define CLI11_BOOST_OPTIONAL 0
#endif
#if CLI11_BOOST_OPTIONAL
#include <boost/version.hpp>
#if BOOST_VERSION < 106100
#error "This boost::optional version is not supported, use 1.61 or better"
#endif
#endif
#if CLI11_STD_OPTIONAL
#include <optional>
#endif
#if CLI11_EXPERIMENTAL_OPTIONAL
#include <experimental/optional>
#endif
#if CLI11_BOOST_OPTIONAL
#include <boost/optional.hpp>
#include <boost/optional/optional_io.hpp>
#endif
// [CLI11:verbatim]
TEST_CASE("OptionalNoEmpty") { CHECK(1 == 1); }
#if CLI11_STD_OPTIONAL
#ifdef _MSC_VER
// this warning suppresses double to int conversions that are inherent in the test
// on windows. This may be able to removed in the future as the add_option capability
// improves
#pragma warning(disable : 4244)
#endif
TEST_CASE_METHOD(TApp, "StdOptionalTest", "[optional]") {
std::optional<int> opt;
app.add_option("-c,--count", opt);
run();
CHECK(!opt);
args = {"-c", "1"};
run();
CHECK((opt && (1 == *opt)));
args = {"--count", "3"};
run();
CHECK((opt && (3 == *opt)));
}
TEST_CASE_METHOD(TApp, "StdOptionalNulloptDefaultValConsistency", "[optional]") {
std::optional<int> opt_int;
app.add_option("-a", opt_int)->default_val(std::nullopt);
std::optional<std::string> opt_string;
CHECK_FALSE(opt_int.has_value());
CHECK_FALSE(opt_string.has_value());
auto *optargs = app.add_option("-b", opt_string);
optargs->default_val(std::nullopt);
run();
CHECK_FALSE(opt_int.has_value());
CHECK_FALSE(opt_string.has_value());
}
TEST_CASE_METHOD(TApp, "StdOptionalEmptyOptConsistency", "[optional]") {
std::optional<int> opt_int;
app.add_option("-a", opt_int)->default_val(opt_int);
std::optional<std::string> opt_string;
CHECK_FALSE(opt_int.has_value());
CHECK_FALSE(opt_string.has_value());
auto *optargs = app.add_option("-b", opt_string);
optargs->default_val(opt_string);
run();
CHECK_FALSE(opt_int.has_value());
CHECK_FALSE(opt_string.has_value());
}
TEST_CASE_METHOD(TApp, "StdOptionalVectorEmptyDirect", "[optional]") {
std::optional<std::vector<int>> opt;
app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
// app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
run();
CHECK(!opt);
args = {"-v"};
opt = std::vector<int>{4, 3};
run();
CHECK(!opt);
args = {"-v", "1", "4", "5"};
run();
REQUIRE(opt);
std::vector<int> expV{1, 4, 5};
CHECK(expV == *opt);
}
TEST_CASE_METHOD(TApp, "StdOptionalComplexDirect", "[optional]") {
std::optional<std::complex<double>> opt;
app.add_option("-c,--complex", opt)->type_size(0, 2);
run();
CHECK(!opt);
args = {"-c"};
opt = std::complex<double>{4.0, 3.0};
run();
CHECK(!opt);
args = {"-c", "1+2j"};
run();
CHECK(opt);
std::complex<double> val{1, 2};
CHECK(val == *opt);
args = {"-c", "3", "-4"};
run();
CHECK(opt);
std::complex<double> val2{3, -4};
CHECK(val2 == *opt);
}
TEST_CASE_METHOD(TApp, "StdOptionalUint", "[optional]") {
std::optional<std::uint64_t> opt;
app.add_option("-i,--int", opt);
run();
CHECK(!opt);
args = {"-i", "15"};
run();
CHECK((opt && (15U == *opt)));
static_assert(CLI::detail::classify_object<std::optional<std::uint64_t>>::value ==
CLI::detail::object_category::wrapper_value);
}
TEST_CASE_METHOD(TApp, "StdOptionalbool", "[optional]") {
std::optional<bool> opt{};
CHECK(!opt);
app.add_flag("--opt,!--no-opt", opt);
CHECK(!opt);
run();
CHECK(!opt);
args = {"--opt"};
run();
CHECK((opt && *opt));
args = {"--no-opt"};
run();
REQUIRE(opt);
if(opt) {
CHECK_FALSE(*opt);
}
static_assert(CLI::detail::classify_object<std::optional<bool>>::value ==
CLI::detail::object_category::wrapper_value);
}
#ifdef _MSC_VER
#pragma warning(default : 4244)
#endif
#endif
#if CLI11_EXPERIMENTAL_OPTIONAL
TEST_CASE_METHOD(TApp, "ExperimentalOptionalTest", "[optional]") {
std::experimental::optional<int> opt;
app.add_option("-c,--count", opt);
run();
CHECK(!opt);
args = {"-c", "1"};
run();
CHECK(opt);
CHECK(1 == *opt);
args = {"--count", "3"};
run();
CHECK(opt);
CHECK(3 == *opt);
}
#endif
#if CLI11_BOOST_OPTIONAL
TEST_CASE_METHOD(TApp, "BoostOptionalTest", "[optional]") {
boost::optional<int> opt;
app.add_option("-c,--count", opt);
run();
CHECK(!opt);
args = {"-c", "1"};
run();
REQUIRE(opt);
CHECK(1 == *opt);
opt = {};
args = {"--count", "3"};
run();
REQUIRE(opt);
CHECK(3 == *opt);
}
TEST_CASE_METHOD(TApp, "BoostOptionalTestZarg", "[optional]") {
boost::optional<int> opt;
app.add_option("-c,--count", opt)->expected(0, 1);
run();
CHECK(!opt);
args = {"-c", "1"};
run();
REQUIRE(opt);
CHECK(1 == *opt);
opt = {};
args = {"--count"};
run();
CHECK(!opt);
}
TEST_CASE_METHOD(TApp, "BoostOptionalint64Test", "[optional]") {
boost::optional<std::int64_t> opt;
app.add_option("-c,--count", opt);
run();
CHECK(!opt);
args = {"-c", "1"};
run();
REQUIRE(opt);
CHECK(1 == *opt);
opt = {};
args = {"--count", "3"};
run();
REQUIRE(opt);
CHECK(3 == *opt);
}
TEST_CASE_METHOD(TApp, "BoostOptionalStringTest", "[optional]") {
boost::optional<std::string> opt;
app.add_option("-s,--string", opt);
run();
CHECK(!opt);
args = {"-s", "strval"};
run();
REQUIRE(opt);
CHECK("strval" == *opt);
opt = {};
args = {"--string", "strv"};
run();
REQUIRE(opt);
CHECK("strv" == *opt);
}
namespace boost {
using CLI::enums::operator<<;
}
TEST_CASE_METHOD(TApp, "BoostOptionalEnumTest", "[optional]") {
enum class eval : char { val0 = 0, val1 = 1, val2 = 2, val3 = 3, val4 = 4 };
boost::optional<eval> opt, opt2;
auto optptr = app.add_option<decltype(opt), eval>("-v,--val", opt);
app.add_option_no_stream("-e,--eval", opt2);
optptr->capture_default_str();
auto dstring = optptr->get_default_str();
CHECK(dstring.empty());
run();
auto checkOpt = static_cast<bool>(opt);
CHECK_FALSE(checkOpt);
args = {"-v", "3"};
run();
checkOpt = static_cast<bool>(opt);
REQUIRE(checkOpt);
CHECK(*opt == eval::val3);
opt = {};
args = {"--val", "1"};
run();
checkOpt = static_cast<bool>(opt);
REQUIRE(checkOpt);
CHECK(*opt == eval::val1);
}
TEST_CASE_METHOD(TApp, "BoostOptionalVector", "[optional]") {
boost::optional<std::vector<int>> opt;
app.add_option_function<std::vector<int>>(
"-v,--vec", [&opt](const std::vector<int> &v) { opt = v; }, "some vector")
->expected(3);
run();
bool checkOpt = static_cast<bool>(opt);
CHECK(!checkOpt);
args = {"-v", "1", "4", "5"};
run();
checkOpt = static_cast<bool>(opt);
REQUIRE(checkOpt);
std::vector<int> expV{1, 4, 5};
CHECK(expV == *opt);
}
TEST_CASE_METHOD(TApp, "BoostOptionalVectorEmpty", "[optional]") {
boost::optional<std::vector<int>> opt;
app.add_option<decltype(opt), std::vector<int>>("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
// app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
run();
bool checkOpt = static_cast<bool>(opt);
CHECK(!checkOpt);
args = {"-v"};
opt = std::vector<int>{4, 3};
run();
checkOpt = static_cast<bool>(opt);
CHECK(!checkOpt);
args = {"-v", "1", "4", "5"};
run();
checkOpt = static_cast<bool>(opt);
REQUIRE(checkOpt);
std::vector<int> expV{1, 4, 5};
CHECK(expV == *opt);
}
TEST_CASE_METHOD(TApp, "BoostOptionalVectorEmptyDirect", "[optional]") {
boost::optional<std::vector<int>> opt;
app.add_option_no_stream("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
// app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
run();
bool checkOpt = static_cast<bool>(opt);
CHECK(!checkOpt);
args = {"-v"};
opt = std::vector<int>{4, 3};
run();
checkOpt = static_cast<bool>(opt);
CHECK(!checkOpt);
args = {"-v", "1", "4", "5"};
run();
checkOpt = static_cast<bool>(opt);
REQUIRE(checkOpt);
std::vector<int> expV{1, 4, 5};
CHECK(expV == *opt);
}
TEST_CASE_METHOD(TApp, "BoostOptionalComplexDirect", "[optional]") {
boost::optional<std::complex<double>> opt;
app.add_option("-c,--complex", opt)->type_size(0, 2);
run();
CHECK(!opt);
args = {"-c"};
opt = std::complex<double>{4.0, 3.0};
run();
CHECK(!opt);
args = {"-c", "1+2j"};
run();
REQUIRE(opt);
std::complex<double> val{1, 2};
CHECK(val == *opt);
args = {"-c", "3", "-4"};
run();
REQUIRE(opt);
std::complex<double> val2{3, -4};
CHECK(val2 == *opt);
}
#endif
+771
View File
@@ -0,0 +1,771 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
static_assert(CLI::is_shared_ptr<std::shared_ptr<int>>::value == true, "is_shared_ptr should work on shared pointers");
static_assert(CLI::is_shared_ptr<int *>::value == false, "is_shared_ptr should work on pointers");
static_assert(CLI::is_shared_ptr<int>::value == false, "is_shared_ptr should work on non-pointers");
static_assert(CLI::is_shared_ptr<const std::shared_ptr<int>>::value == true,
"is_shared_ptr should work on const shared pointers");
static_assert(CLI::is_shared_ptr<const int *>::value == false, "is_shared_ptr should work on const pointers");
static_assert(CLI::is_shared_ptr<const int &>::value == false, "is_shared_ptr should work on const references");
static_assert(CLI::is_shared_ptr<int &>::value == false, "is_shared_ptr should work on non-const references");
static_assert(CLI::is_copyable_ptr<std::shared_ptr<int>>::value == true,
"is_copyable_ptr should work on shared pointers");
static_assert(CLI::is_copyable_ptr<int *>::value == true, "is_copyable_ptr should work on pointers");
static_assert(CLI::is_copyable_ptr<int>::value == false, "is_copyable_ptr should work on non-pointers");
static_assert(CLI::is_copyable_ptr<const std::shared_ptr<int>>::value == true,
"is_copyable_ptr should work on const shared pointers");
static_assert(CLI::is_copyable_ptr<const int *>::value == true, "is_copyable_ptr should work on const pointers");
static_assert(CLI::is_copyable_ptr<const int &>::value == false, "is_copyable_ptr should work on const references");
static_assert(CLI::is_copyable_ptr<int &>::value == false, "is_copyable_ptr should work on non-const references");
static_assert(CLI::detail::pair_adaptor<std::set<int>>::value == false, "Should not have pairs");
static_assert(CLI::detail::pair_adaptor<std::vector<std::string>>::value == false, "Should not have pairs");
static_assert(CLI::detail::pair_adaptor<std::map<int, int>>::value == true, "Should have pairs");
static_assert(CLI::detail::pair_adaptor<std::vector<std::pair<int, int>>>::value == true, "Should have pairs");
/** just test a set as an option value type */
TEST_CASE_METHOD(TApp, "simpleSet", "[set]") {
std::set<std::string> value;
auto *opt = app.add_option("-s,--set", value);
args = {"-s", "one", "-s", "two", "-s", "one"};
run();
CHECK(app.count("-s") == 3u);
CHECK(opt->count() == 3u);
CHECK(value.size() == 2u);
}
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
TEST_CASE_METHOD(TApp, "SimpleMaps", "[set]") {
int value{0};
std::map<std::string, int> map = {{"one", 1}, {"two", 2}};
auto *opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
args = {"-s", "one"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK(1 == value);
}
TEST_CASE_METHOD(TApp, "StringStringMap", "[set]") {
std::string value;
std::map<std::string, std::string> map = {{"a", "b"}, {"b", "c"}};
app.add_option("-s,--set", value)->transform(CLI::CheckedTransformer(map));
args = {"-s", "a"};
run();
CHECK("b" == value);
args = {"-s", "b"};
run();
CHECK("c" == value);
args = {"-s", "c"};
CHECK("c" == value);
}
TEST_CASE_METHOD(TApp, "StringStringMapNoModify", "[set]") {
std::string value;
std::map<std::string, std::string> map = {{"a", "b"}, {"b", "c"}};
app.add_option("-s,--set", value)->check(CLI::IsMember(map));
args = {"-s", "a"};
run();
CHECK("a" == value);
args = {"-s", "b"};
run();
CHECK("b" == value);
args = {"-s", "c"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "StringStringMapNoModifyMultiple", "[set]") {
std::string value1, value2, value3;
std::map<std::string, std::string> map = {{"a", "b"}, {"b", "c"}};
CLI::Validator_p membership = std::make_shared<CLI::Validator>(CLI::IsMember(map));
app.add_option("--set1", value1)->check(membership);
app.add_option("--set2", value2)->check(membership);
app.add_option("--set3", value3)->check(membership);
args = {"--set1", "a"};
run();
CHECK("a" == value1);
args = {"--set2", "b"};
run();
CHECK("b" == value2);
args = {"--set3", "c"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
// check that the validators are actually the same
CHECK(app.get_option("--set1")->get_validator(0) == app.get_option("--set3")->get_validator(0));
}
enum SimpleEnum { SE_one = 1, SE_two = 2 };
TEST_CASE_METHOD(TApp, "EnumMap", "[set]") {
SimpleEnum value; // NOLINT(cppcoreguidelines-init-variables)
std::map<std::string, SimpleEnum> map = {{"one", SE_one}, {"two", SE_two}};
auto *opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
args = {"-s", "one"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK(SE_one == value);
}
enum class SimpleEnumC { one = 1, two = 2 };
TEST_CASE_METHOD(TApp, "EnumCMap", "[set]") {
SimpleEnumC value; // NOLINT(cppcoreguidelines-init-variables)
std::map<std::string, SimpleEnumC> map = {{"one", SimpleEnumC::one}, {"two", SimpleEnumC::two}};
auto *opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
args = {"-s", "one"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK(SimpleEnumC::one == value);
}
TEST_CASE_METHOD(TApp, "structMap", "[set]") {
struct tstruct {
int val2;
double val3;
std::string v4;
};
std::string struct_name;
std::map<std::string, struct tstruct> map = {{"sone", {4, 32.4, "foo"}}, {"stwo", {5, 99.7, "bar"}}};
auto *opt = app.add_option("-s,--set", struct_name)->check(CLI::IsMember(map));
args = {"-s", "sone"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK("sone" == struct_name);
args = {"-s", "sthree"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "structMapChange", "[set]") {
struct tstruct {
int val2;
double val3;
std::string v4;
};
std::string struct_name;
std::map<std::string, struct tstruct> map = {{"sone", {4, 32.4, "foo"}}, {"stwo", {5, 99.7, "bar"}}};
auto *opt = app.add_option("-s,--set", struct_name)
->transform(CLI::IsMember(map, CLI::ignore_case, CLI::ignore_underscore, CLI::ignore_space));
args = {"-s", "s one"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK("sone" == struct_name);
args = {"-s", "sthree"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"-s", "S_t_w_o"};
run();
CHECK("stwo" == struct_name);
args = {"-s", "S two"};
run();
CHECK("stwo" == struct_name);
}
TEST_CASE_METHOD(TApp, "structMapNoChange", "[set]") {
struct tstruct {
int val2;
double val3;
std::string v4;
};
std::string struct_name;
std::map<std::string, struct tstruct> map = {{"sone", {4, 32.4, "foo"}}, {"stwo", {5, 99.7, "bar"}}};
auto *opt = app.add_option("-s,--set", struct_name)
->check(CLI::IsMember(map, CLI::ignore_case, CLI::ignore_underscore, CLI::ignore_space));
args = {"-s", "SONE"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK("SONE" == struct_name);
args = {"-s", "sthree"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"-s", "S_t_w_o"};
run();
CHECK("S_t_w_o" == struct_name);
args = {"-s", "S two"};
run();
CHECK("S two" == struct_name);
}
TEST_CASE_METHOD(TApp, "NonCopyableMap", "[set]") {
std::string map_name;
std::map<std::string, std::unique_ptr<double>> map;
map["e1"].reset(new double(5.7));
map["e3"].reset(new double(23.8));
auto *opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(&map));
args = {"-s", "e1"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK("e1" == map_name);
args = {"-s", "e45"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "NonCopyableMapWithFunction", "[set]") {
std::string map_name;
std::map<std::string, std::unique_ptr<double>> map;
map["e1"].reset(new double(5.7));
map["e3"].reset(new double(23.8));
auto *opt = app.add_option("-s,--set", map_name)->transform(CLI::IsMember(&map, CLI::ignore_underscore));
args = {"-s", "e_1"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK("e1" == map_name);
args = {"-s", "e45"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "NonCopyableMapNonStringMap", "[set]") {
std::string map_name;
std::map<int, std::unique_ptr<double>> map;
map[4].reset(new double(5.7));
map[17].reset(new double(23.8));
auto *opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(&map));
args = {"-s", "4"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK("4" == map_name);
args = {"-s", "e45"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "CopyableMapMove", "[set]") {
std::string map_name;
std::map<int, double> map;
map[4] = 5.7;
map[17] = 23.8;
auto *opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(std::move(map)));
args = {"-s", "4"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK("4" == map_name);
args = {"-s", "e45"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "SimpleSets", "[set]") {
std::string value;
auto *opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<std::string>({"one", "two", "three"})});
args = {"-s", "one"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK("one" == value);
}
TEST_CASE_METHOD(TApp, "SimpleSetsPtrs", "[set]") {
auto set = std::make_shared<std::set<std::string>>(std::set<std::string>{"one", "two", "three"});
std::string value;
auto *opt = app.add_option("-s,--set", value)->check(CLI::IsMember{set});
args = {"-s", "one"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK("one" == value);
set->insert("four");
args = {"-s", "four"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK("four" == value);
}
TEST_CASE_METHOD(TApp, "SimiShortcutSets", "[set]") {
std::string value;
auto *opt = app.add_option("--set", value)->check(CLI::IsMember({"one", "two", "three"}));
args = {"--set", "one"};
run();
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK("one" == value);
std::string value2;
auto *opt2 = app.add_option("--set2", value2)->transform(CLI::IsMember({"One", "two", "three"}, CLI::ignore_case));
args = {"--set2", "onE"};
run();
CHECK(app.count("--set2") == 1u);
CHECK(opt2->count() == 1u);
CHECK("One" == value2);
std::string value3;
auto *opt3 = app.add_option("--set3", value3)
->transform(CLI::IsMember({"O_ne", "two", "three"}, CLI::ignore_case, CLI::ignore_underscore));
args = {"--set3", "onE"};
run();
CHECK(app.count("--set3") == 1u);
CHECK(opt3->count() == 1u);
CHECK("O_ne" == value3);
}
TEST_CASE_METHOD(TApp, "SetFromCharStarArrayVector", "[set]") {
constexpr const char *names[3]{"one", "two", "three"}; // NOLINT(modernize-avoid-c-arrays)
std::string value;
auto *opt = app.add_option("-s,--set", value)
->check(CLI::IsMember{std::vector<std::string>(std::begin(names), std::end(names))});
args = {"-s", "one"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK("one" == value);
}
TEST_CASE_METHOD(TApp, "OtherTypeSets", "[set]") {
int value{0};
std::vector<int> set = {2, 3, 4};
auto *opt = app.add_option("--set", value)->check(CLI::IsMember(set));
args = {"--set", "3"};
run();
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK(3 == value);
args = {"--set", "5"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
std::vector<int> set2 = {-2, 3, 4};
auto *opt2 = app.add_option("--set2", value)->transform(CLI::IsMember(set2, [](int x) { return std::abs(x); }));
args = {"--set2", "-3"};
run();
CHECK(app.count("--set2") == 1u);
CHECK(opt2->count() == 1u);
CHECK(3 == value);
args = {"--set2", "-3"};
run();
CHECK(app.count("--set2") == 1u);
CHECK(opt2->count() == 1u);
CHECK(3 == value);
args = {"--set2", "2"};
run();
CHECK(app.count("--set2") == 1u);
CHECK(opt2->count() == 1u);
CHECK(-2 == value);
}
TEST_CASE_METHOD(TApp, "NumericalSets", "[set]") {
int value{0};
auto *opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<int>({1, 2, 3})});
args = {"-s", "1"};
run();
CHECK(app.count("-s") == 1u);
CHECK(app.count("--set") == 1u);
CHECK(opt->count() == 1u);
CHECK(1 == value);
}
// Converted original set tests
TEST_CASE_METHOD(TApp, "SetWithDefaults", "[set]") {
int someint{2};
app.add_option("-a", someint)->capture_default_str()->check(CLI::IsMember({1, 2, 3, 4}));
args = {"-a1", "-a2"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "SetWithDefaultsConversion", "[set]") {
int someint{2};
app.add_option("-a", someint)->capture_default_str()->check(CLI::IsMember({1, 2, 3, 4}));
args = {"-a", "hi"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "SetWithDefaultsIC", "[set]") {
std::string someint = "ho";
app.add_option("-a", someint)->capture_default_str()->check(CLI::IsMember({"Hi", "Ho"}));
args = {"-aHi", "-aHo"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "InSet", "[set]") {
std::string choice;
app.add_option("-q,--quick", choice)->check(CLI::IsMember({"one", "two", "three"}));
args = {"--quick", "two"};
run();
CHECK(choice == "two");
args = {"--quick", "four"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "InSetWithDefault", "[set]") {
std::string choice = "one";
app.add_option("-q,--quick", choice)->capture_default_str()->check(CLI::IsMember({"one", "two", "three"}));
run();
CHECK(choice == "one");
args = {"--quick", "two"};
run();
CHECK(choice == "two");
args = {"--quick", "four"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "InCaselessSetWithDefault", "[set]") {
std::string choice = "one";
app.add_option("-q,--quick", choice)
->capture_default_str()
->transform(CLI::IsMember({"one", "two", "three"}, CLI::ignore_case));
run();
CHECK(choice == "one");
args = {"--quick", "tWo"};
run();
CHECK(choice == "two");
args = {"--quick", "four"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "InIntSet", "[set]") {
int choice{0};
app.add_option("-q,--quick", choice)->check(CLI::IsMember({1, 2, 3}));
args = {"--quick", "2"};
run();
CHECK(choice == 2);
args = {"--quick", "4"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "InIntSetWindows", "[set]") {
int choice{0};
app.add_option("-q,--quick", choice)->check(CLI::IsMember({1, 2, 3}));
app.allow_windows_style_options();
args = {"/q", "2"};
run();
CHECK(choice == 2);
args = {"/q", "4"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"/q4"};
CHECK_THROWS_AS(run(), CLI::ExtrasError);
}
TEST_CASE_METHOD(TApp, "FailSet", "[set]") {
int choice{0};
app.add_option("-q,--quick", choice)->check(CLI::IsMember({1, 2, 3}));
args = {"--quick", "3", "--quick=2"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
args = {"--quick=hello"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "shortStringCheck", "[set]") {
std::string choice;
app.add_option("-q,--quick", choice)->check([](const std::string &v) {
if(v.size() > 5) {
return std::string{"string too long"};
}
return std::string{};
});
args = {"--quick", "3"};
CHECK_NOTHROW(run());
args = {"--quick=hello_goodbye"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "FailMutableSet", "[set]") {
int choice{0};
auto vals = std::make_shared<std::set<int>>(std::set<int>{1, 2, 3});
app.add_option("-q,--quick", choice)->check(CLI::IsMember(vals));
app.add_option("-s,--slow", choice)->capture_default_str()->check(CLI::IsMember(vals));
args = {"--quick=hello"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--slow=hello"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "InSetIgnoreCase", "[set]") {
std::string choice;
app.add_option("-q,--quick", choice)->transform(CLI::IsMember({"one", "Two", "THREE"}, CLI::ignore_case));
args = {"--quick", "One"};
run();
CHECK(choice == "one");
args = {"--quick", "two"};
run();
CHECK(choice == "Two");
args = {"--quick", "ThrEE"};
run();
CHECK(choice == "THREE");
args = {"--quick", "four"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--quick=one", "--quick=two"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "InSetIgnoreCaseMutableValue", "[set]") {
std::set<std::string> options{"one", "Two", "THREE"};
std::string choice;
app.add_option("-q,--quick", choice)->transform(CLI::IsMember(&options, CLI::ignore_case));
args = {"--quick", "One"};
run();
CHECK(choice == "one");
args = {"--quick", "two"};
run();
CHECK(choice == "Two");
args = {"--quick", "ThrEE"};
run();
CHECK(choice == "THREE");
options.clear();
args = {"--quick", "ThrEE"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "InSetIgnoreCasePointer", "[set]") {
auto *options = new std::set<std::string>{"one", "Two", "THREE"};
std::string choice;
app.add_option("-q,--quick", choice)->transform(CLI::IsMember(*options, CLI::ignore_case));
args = {"--quick", "One"};
run();
CHECK(choice == "one");
args = {"--quick", "two"};
run();
CHECK(choice == "Two");
args = {"--quick", "ThrEE"};
run();
CHECK(choice == "THREE");
delete options;
args = {"--quick", "ThrEE"};
run();
CHECK(choice == "THREE");
args = {"--quick", "four"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--quick=one", "--quick=two"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "NotInSetIgnoreCasePointer", "[set]") {
auto *options = new std::set<std::string>{"one", "Two", "THREE"};
std::string choice;
app.add_option("-q,--quick", choice)->check(!CLI::IsMember(*options, CLI::ignore_case));
args = {"--quick", "One"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--quick", "four"};
run();
CHECK("four" == choice);
}
TEST_CASE_METHOD(TApp, "InSetIgnoreUnderscore", "[set]") {
std::string choice;
app.add_option("-q,--quick", choice)
->transform(CLI::IsMember({"option_one", "option_two", "optionthree"}, CLI::ignore_underscore));
args = {"--quick", "option_one"};
run();
CHECK(choice == "option_one");
args = {"--quick", "optiontwo"};
run();
CHECK(choice == "option_two");
args = {"--quick", "_option_thr_ee"};
run();
CHECK(choice == "optionthree");
args = {"--quick", "Option4"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--quick=option_one", "--quick=option_two"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
TEST_CASE_METHOD(TApp, "InSetIgnoreCaseUnderscore", "[set]") {
std::string choice;
app.add_option("-q,--quick", choice)
->transform(
CLI::IsMember({"Option_One", "option_two", "OptionThree"}, CLI::ignore_case, CLI::ignore_underscore));
args = {"--quick", "option_one"};
run();
CHECK(choice == "Option_One");
args = {"--quick", "OptionTwo"};
run();
CHECK(choice == "option_two");
args = {"--quick", "_OPTION_thr_ee"};
run();
CHECK(choice == "OptionThree");
args = {"--quick", "Option4"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--quick=option_one", "--quick=option_two"};
CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
}
// #113
TEST_CASE_METHOD(TApp, "AddRemoveSetItems", "[set]") {
std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
std::string type1, type2;
app.add_option("--type1", type1)->check(CLI::IsMember(&items));
app.add_option("--type2", type2)->capture_default_str()->check(CLI::IsMember(&items));
args = {"--type1", "TYPE1", "--type2", "TYPE2"};
run();
CHECK("TYPE1" == type1);
CHECK("TYPE2" == type2);
items.insert("TYPE6");
items.insert("TYPE7");
items.erase("TYPE1");
items.erase("TYPE2");
args = {"--type1", "TYPE6", "--type2", "TYPE7"};
run();
CHECK("TYPE6" == type1);
CHECK("TYPE7" == type2);
args = {"--type1", "TYPE1"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--type2", "TYPE2"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "AddRemoveSetItemsNoCase", "[set]") {
std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
std::string type1, type2;
app.add_option("--type1", type1)->transform(CLI::IsMember(&items, CLI::ignore_case));
app.add_option("--type2", type2)->capture_default_str()->transform(CLI::IsMember(&items, CLI::ignore_case));
args = {"--type1", "TYPe1", "--type2", "TyPE2"};
run();
CHECK("TYPE1" == type1);
CHECK("TYPE2" == type2);
items.insert("TYPE6");
items.insert("TYPE7");
items.erase("TYPE1");
items.erase("TYPE2");
args = {"--type1", "TyPE6", "--type2", "tYPE7"};
run();
CHECK("TYPE6" == type1);
CHECK("TYPE7" == type2);
args = {"--type1", "TYPe1"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--type2", "TYpE2"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
#endif
+36
View File
@@ -0,0 +1,36 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#ifdef CLI11_SINGLE_FILE
#include "CLI11.hpp"
#else
#include "CLI/CLI.hpp"
#endif
#include "catch.hpp"
#include <string>
#include <vector>
using input_t = std::vector<std::string>;
TEST_CASE("Basic: Empty", "[simple]") {
{
CLI::App app;
input_t simpleput;
app.parse(simpleput);
}
{
CLI::App app;
input_t spare = {"spare"};
CHECK_THROWS_AS(app.parse(spare), CLI::ExtrasError);
}
{
CLI::App app;
input_t simpleput;
app.parse(simpleput);
}
}
+118
View File
@@ -0,0 +1,118 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include <cstdio>
#include <sstream>
#include <string>
TEST_CASE_METHOD(TApp, "ExistingExeCheck", "[stringparse]") {
TempFile tmpexe{"existingExe.out"};
std::string str, str2, str3;
app.add_option("-s,--string", str);
app.add_option("-t,--tstr", str2);
app.add_option("-m,--mstr", str3);
{
std::ofstream out{tmpexe};
out << "useless string doesn't matter" << '\n';
}
app.parse(std::string("./") + std::string(tmpexe) +
R"( --string="this is my quoted string" -t 'qstring 2' -m=`"quoted string"`)",
true);
CHECK("this is my quoted string" == str);
CHECK("qstring 2" == str2);
CHECK("\"quoted string\"" == str3);
}
TEST_CASE_METHOD(TApp, "ExistingExeCheckWithSpace", "[stringparse]") {
TempFile tmpexe{"Space File.out"};
std::string str, str2, str3;
app.add_option("-s,--string", str);
app.add_option("-t,--tstr", str2);
app.add_option("-m,--mstr", str3);
{
std::ofstream out{tmpexe};
out << "useless string doesn't matter" << '\n';
}
app.parse(std::string("./") + std::string(tmpexe) +
R"( --string="this is my quoted string" -t 'qstring 2' -m=`"quoted string"`)",
true);
CHECK("this is my quoted string" == str);
CHECK("qstring 2" == str2);
CHECK("\"quoted string\"" == str3);
CHECK(std::string("./") + std::string(tmpexe) == app.get_name());
}
TEST_CASE_METHOD(TApp, "ExistingExeCheckWithLotsOfSpace", "[stringparse]") {
TempFile tmpexe{"this is a weird file.exe"};
std::string str, str2, str3;
app.add_option("-s,--string", str);
app.add_option("-t,--tstr", str2);
app.add_option("-m,--mstr", str3);
{
std::ofstream out{tmpexe};
out << "useless string doesn't matter" << '\n';
}
app.parse(std::string("./") + std::string(tmpexe) +
R"( --string="this is my quoted string" -t 'qstring 2' -m=`"quoted string"`)",
true);
CHECK("this is my quoted string" == str);
CHECK("qstring 2" == str2);
CHECK("\"quoted string\"" == str3);
CHECK(std::string("./") + std::string(tmpexe) == app.get_name());
}
// From GitHub issue #591 https://github.com/CLIUtils/CLI11/issues/591
TEST_CASE_METHOD(TApp, "ProgNameWithSpace", "[stringparse]") {
app.add_flag("--foo");
CHECK_NOTHROW(app.parse("\"Foo Bar\" --foo", true));
CHECK(app["--foo"]->as<bool>());
CHECK(app.get_name() == "Foo Bar");
}
// From GitHub issue #739 https://github.com/CLIUtils/CLI11/issues/739
TEST_CASE_METHOD(TApp, "ProgNameOnly", "[stringparse]") {
app.add_flag("--foo");
CHECK_NOTHROW(app.parse("\"C:\\example.exe\"", true));
CHECK(app.get_name() == "C:\\example.exe");
}
TEST_CASE_METHOD(TApp, "ProgNameWithSpaceEmbeddedQuote", "[stringparse]") {
app.add_flag("--foo");
CHECK_NOTHROW(app.parse("\"Foo\\\" Bar\" --foo", true));
CHECK(app["--foo"]->as<bool>());
CHECK(app.get_name() == "Foo\" Bar");
}
TEST_CASE_METHOD(TApp, "ProgNameWithSpaceSingleQuote", "[stringparse]") {
app.add_flag("--foo");
CHECK_NOTHROW(app.parse(R"('Foo\' Bar' --foo)", true));
CHECK(app["--foo"]->as<bool>());
CHECK(app.get_name() == "Foo' Bar");
}
File diff suppressed because it is too large Load Diff
+69
View File
@@ -0,0 +1,69 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "CLI/Timer.hpp"
#include "catch.hpp"
#include <chrono>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>
TEST_CASE("Timer: MSTimes", "[timer]") {
CLI::Timer timer{"My Timer"};
std::this_thread::sleep_for(std::chrono::milliseconds(123));
std::string output = timer.to_string();
std::string new_output = (timer / 1000000).to_string();
CHECK_THAT(output, Contains("My Timer"));
CHECK_THAT(output, Contains(" ms"));
CHECK_THAT(new_output, Contains(" ns"));
}
/* Takes too long
TEST_CASE("Timer: STimes", "[timer]") {
CLI::Timer timer;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::string output = timer.to_string();
CHECK_THAT (output, Contains(" s"));
}
*/
// Fails on Windows
// TEST_CASE("Timer: UStimes", "[timer]") {
// CLI::Timer timer;
// std::this_thread::sleep_for(std::chrono::microseconds(2));
// std::string output = timer.to_string();
// CHECK_THAT (output, Contains(" ms"));
//}
TEST_CASE("Timer: BigTimer", "[timer]") {
CLI::Timer timer{"My Timer", CLI::Timer::Big};
std::string output = timer.to_string();
CHECK_THAT(output, Contains("Time ="));
CHECK_THAT(output, Contains("-----------"));
}
TEST_CASE("Timer: AutoTimer", "[timer]") {
CLI::AutoTimer timer;
std::string output = timer.to_string();
CHECK_THAT(output, Contains("Timer"));
}
TEST_CASE("Timer: PrintTimer", "[timer]") {
std::stringstream out;
CLI::AutoTimer timer;
out << timer;
std::string output = out.str();
CHECK_THAT(output, Contains("Timer"));
}
TEST_CASE("Timer: TimeItTimer", "[timer]") {
CLI::Timer timer;
std::string output = timer.time_it([]() { std::this_thread::sleep_for(std::chrono::milliseconds(10)); }, .1);
std::cout << output << '\n';
CHECK_THAT(output, Contains("ms"));
}
+712
View File
@@ -0,0 +1,712 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include <cmath>
#include <array>
#include <chrono>
#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#if defined(CLI11_CPP17)
#if defined(__has_include)
#if __has_include(<string_view>)
#include <string_view>
#define CLI11_HAS_STRING_VIEW
#endif
#endif
#endif
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
TEST_CASE_METHOD(TApp, "SimpleTransform", "[transform]") {
int value{0};
auto *opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", std::string("1")}}));
args = {"-s", "one"};
run();
CHECK(app.count("-s") == 1u);
CHECK(opt->count() == 1u);
CHECK(1 == value);
}
TEST_CASE_METHOD(TApp, "SimpleTransformInitList", "[transform]") {
int value{0};
auto *opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", "1"}}));
args = {"-s", "one"};
run();
CHECK(app.count("-s") == 1u);
CHECK(opt->count() == 1u);
CHECK(1 == value);
}
TEST_CASE_METHOD(TApp, "SimpleNumericalTransform", "[transform]") {
int value{0};
auto *opt = app.add_option("-s", value)->transform(CLI::Transformer(CLI::TransformPairs<int>{{"one", 1}}));
args = {"-s", "one"};
run();
CHECK(app.count("-s") == 1u);
CHECK(opt->count() == 1u);
CHECK(1 == value);
}
TEST_CASE_METHOD(TApp, "EnumTransform", "[transform]") {
enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17 };
test_cli value{test_cli::val2};
auto *opt = app.add_option("-s", value)
->transform(CLI::Transformer(CLI::TransformPairs<test_cli>{
{"val1", test_cli::val1}, {"val2", test_cli::val2}, {"val3", test_cli::val3}}));
args = {"-s", "val1"};
run();
CHECK(app.count("-s") == 1u);
CHECK(opt->count() == 1u);
CHECK(test_cli::val1 == value);
args = {"-s", "val2"};
run();
CHECK(test_cli::val2 == value);
args = {"-s", "val3"};
run();
CHECK(test_cli::val3 == value);
args = {"-s", "val4"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
// transformer doesn't do any checking so this still works
args = {"-s", "5"};
run();
CHECK(static_cast<std::int16_t>(5) == static_cast<std::int16_t>(value));
}
TEST_CASE_METHOD(TApp, "EnumCheckedTransform", "[transform]") {
enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17 };
test_cli value{test_cli::val1};
auto *opt = app.add_option("-s", value)
->transform(CLI::CheckedTransformer(CLI::TransformPairs<test_cli>{
{"val1", test_cli::val1}, {"val2", test_cli::val2}, {"val3", test_cli::val3}}));
args = {"-s", "val1"};
run();
CHECK(app.count("-s") == 1u);
CHECK(opt->count() == 1u);
CHECK(test_cli::val1 == value);
args = {"-s", "val2"};
run();
CHECK(test_cli::val2 == value);
args = {"-s", "val3"};
run();
CHECK(test_cli::val3 == value);
args = {"-s", "17"};
run();
CHECK(test_cli::val3 == value);
args = {"-s", "val4"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"-s", "5"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
// from to-mas-kral Issue #1086
TEST_CASE_METHOD(TApp, "EnumCheckedTransformUint8", "[transform]") {
enum class FooType : std::uint8_t { A, B };
auto type = FooType::B;
const std::map<std::string, FooType> foo_map{
{"a", FooType::A},
{"b", FooType::B},
};
app.add_option("-f,--foo", type, "FooType")
->transform(CLI::CheckedTransformer(foo_map, CLI::ignore_case))
->default_val(FooType::A)
->force_callback();
run();
CHECK(type == FooType::A);
}
// from jzakrzewski Issue #330
TEST_CASE_METHOD(TApp, "EnumCheckedDefaultTransform", "[transform]") {
enum class existing : std::int16_t { abort, overwrite, remove };
app.add_option("--existing", "What to do if file already exists in the destination")
->transform(
CLI::CheckedTransformer(std::unordered_map<std::string, existing>{{"abort", existing::abort},
{"overwrite", existing ::overwrite},
{"delete", existing::remove},
{"remove", existing::remove}}))
->default_val("abort");
args = {"--existing", "overwrite"};
run();
CHECK(existing::overwrite == app.get_option("--existing")->as<existing>());
args.clear();
run();
CHECK(existing::abort == app.get_option("--existing")->as<existing>());
}
// test from https://github.com/CLIUtils/CLI11/issues/369 [Jakub Zakrzewski](https://github.com/jzakrzewski)
TEST_CASE_METHOD(TApp, "EnumCheckedDefaultTransformCallback", "[transform]") {
enum class existing : std::int16_t { abort, overwrite, remove };
auto cmd = std::make_shared<CLI::App>("deploys the repository somewhere", "deploy");
cmd->add_option("--existing", "What to do if file already exists in the destination")
->transform(
CLI::CheckedTransformer(std::unordered_map<std::string, existing>{{"abort", existing::abort},
{"overwrite", existing::overwrite},
{"delete", existing::remove},
{"remove", existing::remove}}))
->default_val("abort");
cmd->callback([cmd]() { CHECK(cmd->get_option("--existing")->as<existing>() == existing::abort); });
app.add_subcommand(cmd);
args = {"deploy"};
run();
}
TEST_CASE_METHOD(TApp, "SimpleTransformFn", "[transform]") {
int value{0};
auto *opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", "1"}}, CLI::ignore_case));
args = {"-s", "ONE"};
run();
CHECK(app.count("-s") == 1u);
CHECK(opt->count() == 1u);
CHECK(1 == value);
}
enum class Color { kRed, kBlue };
// operator<< outputs full enum name (standard practice)
inline std::ostream &operator<<(std::ostream &os, Color c) { return os << (c == Color::kRed ? "kRed" : "kBlue"); }
// test from https://github.com/CLIUtils/CLI11/issues/1258 [huweiATgithub](https://github.com/huweiATgithub)
TEST_CASE_METHOD(TApp, "streamTransformCheck", "[transform]") {
std::map<std::string, Color> color_map = {
{"red", Color::kRed}, // User types "red"
{"blue", Color::kBlue} // User types "blue"
};
Color color = Color::kRed;
app.add_option("--color", color)
->transform(CLI::CheckedTransformer(color_map, CLI::ignore_case))
->default_val(Color::kRed); // BUG: Validates "kRed" against {"red", "blue"}
CHECK_NOTHROW(app.parse("")); // Should use default
}
#if defined(CLI11_HAS_STRING_VIEW)
TEST_CASE_METHOD(TApp, "StringViewTransformFn", "[transform]") {
std::string value;
std::map<std::string_view, std::string_view> map = {// key length > std::string().capacity() [SSO length]
{"a-rather-long-argument", "mapped"}};
app.add_option("-s", value)->transform(CLI::CheckedTransformer(map));
args = {"-s", "a-rather-long-argument"};
run();
CHECK("mapped" == value);
}
#endif
TEST_CASE_METHOD(TApp, "SimpleNumericalTransformFn", "[transform]") {
int value{0};
auto *opt =
app.add_option("-s", value)
->transform(CLI::Transformer(std::vector<std::pair<std::string, int>>{{"one", 1}}, CLI::ignore_case));
args = {"-s", "ONe"};
run();
CHECK(app.count("-s") == 1u);
CHECK(opt->count() == 1u);
CHECK(1 == value);
}
TEST_CASE_METHOD(TApp, "SimpleNumericalTransformFnVector", "[transform]") {
std::vector<std::pair<std::string, int>> conversions{{"one", 1}, {"two", 2}};
int value{0};
auto *opt = app.add_option("-s", value)->transform(CLI::Transformer(conversions, CLI::ignore_case));
args = {"-s", "ONe"};
run();
CHECK(app.count("-s") == 1u);
CHECK(opt->count() == 1u);
CHECK(1 == value);
}
TEST_CASE_METHOD(TApp, "SimpleNumericalTransformFnArray", "[transform]") {
std::array<std::pair<std::string, int>, 2> conversions;
conversions[0] = std::make_pair(std::string("one"), 1);
conversions[1] = std::make_pair(std::string("two"), 2);
int value{0};
auto *opt = app.add_option("-s", value)->transform(CLI::Transformer(conversions, CLI::ignore_case));
args = {"-s", "ONe"};
run();
CHECK(app.count("-s") == 1u);
CHECK(opt->count() == 1u);
CHECK(1 == value);
}
#ifdef CLI11_CPP14
// zero copy constexpr array operation with transformer example and test
TEST_CASE_METHOD(TApp, "SimpleNumericalTransformFnconstexprArray", "[transform]") {
constexpr std::pair<const char *, int> p1{"one", 1};
constexpr std::pair<const char *, int> p2{"two", 2};
constexpr std::array<std::pair<const char *, int>, 2> conversions_c{{p1, p2}};
int value{0};
auto *opt = app.add_option("-s", value)->transform(CLI::Transformer(&conversions_c, CLI::ignore_case));
args = {"-s", "ONe"};
run();
CHECK(app.count("-s") == 1u);
CHECK(opt->count() == 1u);
CHECK(1 == value);
args = {"-s", "twO"};
run();
CHECK(app.count("-s") == 1u);
CHECK(opt->count() == 1u);
CHECK(2 == value);
}
#endif
TEST_CASE_METHOD(TApp, "EnumTransformFn", "[transform]") {
enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17 };
test_cli value{test_cli::val2};
auto *opt = app.add_option("-s", value)
->transform(CLI::Transformer(CLI::TransformPairs<test_cli>{{"val1", test_cli::val1},
{"val2", test_cli::val2},
{"val3", test_cli::val3}},
CLI::ignore_case,
CLI::ignore_underscore));
args = {"-s", "val_1"};
run();
CHECK(app.count("-s") == 1u);
CHECK(opt->count() == 1u);
CHECK(test_cli::val1 == value);
args = {"-s", "VAL_2"};
run();
CHECK(test_cli::val2 == value);
args = {"-s", "VAL3"};
run();
CHECK(test_cli::val3 == value);
args = {"-s", "val_4"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
TEST_CASE_METHOD(TApp, "EnumTransformFnMap", "[transform]") {
enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17 };
std::map<std::string, test_cli> map{{"val1", test_cli::val1}, {"val2", test_cli::val2}, {"val3", test_cli::val3}};
test_cli value{test_cli::val3};
auto *opt = app.add_option("-s", value)->transform(CLI::Transformer(map, CLI::ignore_case, CLI::ignore_underscore));
args = {"-s", "val_1"};
run();
CHECK(app.count("-s") == 1u);
CHECK(opt->count() == 1u);
CHECK(test_cli::val1 == value);
args = {"-s", "VAL_2"};
run();
CHECK(test_cli::val2 == value);
args = {"-s", "VAL3"};
run();
CHECK(test_cli::val3 == value);
args = {"-s", "val_4"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
}
TEST_CASE_METHOD(TApp, "EnumTransformFnPtrMap", "[transform]") {
enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17, val4 = 37 };
std::map<std::string, test_cli> map{{"val1", test_cli::val1}, {"val2", test_cli::val2}, {"val3", test_cli::val3}};
test_cli value{test_cli::val2};
auto *opt =
app.add_option("-s", value)->transform(CLI::Transformer(&map, CLI::ignore_case, CLI::ignore_underscore));
args = {"-s", "val_1"};
run();
CHECK(app.count("-s") == 1u);
CHECK(opt->count() == 1u);
CHECK(test_cli::val1 == value);
args = {"-s", "VAL_2"};
run();
CHECK(test_cli::val2 == value);
args = {"-s", "VAL3"};
run();
CHECK(test_cli::val3 == value);
args = {"-s", "val_4"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
map["val4"] = test_cli::val4;
run();
CHECK(test_cli::val4 == value);
}
TEST_CASE_METHOD(TApp, "EnumTransformFnSharedPtrMap", "[transform]") {
enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17, val4 = 37 };
auto map = std::make_shared<std::unordered_map<std::string, test_cli>>();
auto &mp = *map;
mp["val1"] = test_cli::val1;
mp["val2"] = test_cli::val2;
mp["val3"] = test_cli::val3;
test_cli value{test_cli::val2};
auto *opt = app.add_option("-s", value)->transform(CLI::Transformer(map, CLI::ignore_case, CLI::ignore_underscore));
args = {"-s", "val_1"};
run();
CHECK(app.count("-s") == 1u);
CHECK(opt->count() == 1u);
CHECK(test_cli::val1 == value);
args = {"-s", "VAL_2"};
run();
CHECK(test_cli::val2 == value);
args = {"-s", "VAL3"};
run();
CHECK(test_cli::val3 == value);
args = {"-s", "val_4"};
CHECK_THROWS_AS(run(), CLI::ConversionError);
mp["val4"] = test_cli::val4;
run();
CHECK(test_cli::val4 == value);
}
// Test a cascade of transform functions
TEST_CASE_METHOD(TApp, "TransformCascade", "[transform]") {
std::string output;
auto *opt = app.add_option("-s", output);
opt->transform(CLI::Transformer({{"abc", "abcd"}, {"bbc", "bbcd"}, {"cbc", "cbcd"}}, CLI::ignore_case));
opt->transform(
CLI::Transformer({{"ab", "abc"}, {"bc", "bbc"}, {"cb", "cbc"}}, CLI::ignore_case, CLI::ignore_underscore));
opt->transform(CLI::Transformer({{"a", "ab"}, {"b", "bb"}, {"c", "cb"}}, CLI::ignore_case));
opt->check(CLI::IsMember({"abcd", "bbcd", "cbcd"}));
args = {"-s", "abcd"};
run();
CHECK("abcd" == output);
args = {"-s", "Bbc"};
run();
CHECK("bbcd" == output);
args = {"-s", "C_B"};
run();
CHECK("cbcd" == output);
args = {"-s", "A"};
run();
CHECK("abcd" == output);
}
// Test a cascade of transform functions
TEST_CASE_METHOD(TApp, "TransformCascadeDeactivate", "[transform]") {
std::string output;
auto *opt = app.add_option("-s", output);
opt->transform(
CLI::Transformer({{"abc", "abcd"}, {"bbc", "bbcd"}, {"cbc", "cbcd"}}, CLI::ignore_case).name("tform1"));
opt->transform(
CLI::Transformer({{"ab", "abc"}, {"bc", "bbc"}, {"cb", "cbc"}}, CLI::ignore_case, CLI::ignore_underscore)
.name("tform2")
.active(false));
opt->transform(CLI::Transformer({{"a", "ab"}, {"b", "bb"}, {"c", "cb"}}, CLI::ignore_case).name("tform3"));
opt->check(CLI::IsMember({"abcd", "bbcd", "cbcd"}).name("check"));
args = {"-s", "abcd"};
run();
CHECK("abcd" == output);
args = {"-s", "Bbc"};
run();
CHECK("bbcd" == output);
args = {"-s", "C_B"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
auto *validator = opt->get_validator("tform2");
CHECK(!validator->get_active());
CHECK("tform2" == validator->get_name());
validator->active();
CHECK(validator->get_active());
args = {"-s", "C_B"};
run();
CHECK("cbcd" == output);
opt->get_validator("check")->active(false);
args = {"-s", "gsdgsgs"};
run();
CHECK("gsdgsgs" == output);
CHECK_THROWS_AS(opt->get_validator("sdfsdf"), CLI::OptionNotFound);
}
// from GitHub issue: https://github.com/CLIUtils/CLI11/issues/1338
// transform(Transformer(...), "", "") should hide transform help text
// without disabling the transform behavior.
TEST_CASE_METHOD(TApp, "TransformEmptyNameAndDescription", "[transform]") {
std::map<std::string, int> numbers_type_map{{"one", 1}, {"two", 2}, {"three", 3}};
int result_number = 0;
auto *opt = app.add_option("-n,--number", result_number, "My number")
->transform(CLI::Transformer(numbers_type_map), "", "");
args = {"--number", "two"};
REQUIRE_NOTHROW(run());
CHECK(app.count("--number") == 1u);
CHECK(opt->count() == 1u);
CHECK(result_number == 2);
auto help = app.help();
CHECK(help.find("one->1") == std::string::npos);
CHECK(help.find("two->2") == std::string::npos);
CHECK(help.find("three->3") == std::string::npos);
}
// from GitHub issue: https://github.com/CLIUtils/CLI11/issues/1338
// transform(Transformer(...), "", "") should hide transform help text
// without disabling the transform behavior.
TEST_CASE_METHOD(TApp, "TransformEmptyDescription", "[transform]") {
std::map<std::string, int> numbers_type_map{{"one", 1}, {"two", 2}, {"three", 3}};
int result_number = 0;
auto *opt = app.add_option("-n,--number", result_number, "My number")
->transform(CLI::Transformer(numbers_type_map), "", "my number transform");
args = {"--number", "two"};
REQUIRE_NOTHROW(run());
CHECK(app.count("--number") == 1u);
CHECK(opt->count() == 1u);
CHECK(result_number == 2);
auto help = app.help();
CHECK(help.find("one->1") == std::string::npos);
CHECK(help.find("two->2") == std::string::npos);
CHECK(help.find("three->3") == std::string::npos);
}
TEST_CASE_METHOD(TApp, "IntTransformFn", "[transform]") {
std::string value;
app.add_option("-s", value)
->transform(
CLI::CheckedTransformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}, [](int in) { return in - 10; }));
args = {"-s", "25"};
run();
CHECK("5" == value);
args = {"-s", "6"};
run();
CHECK("6" == value);
args = {"-s", "45"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"-s", "val_4"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "IntTransformNonConvertible", "[transform]") {
std::string value;
app.add_option("-s", value)->transform(CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}));
args = {"-s", "15"};
run();
CHECK("5" == value);
args = {"-s", "18"};
run();
CHECK("6" == value);
// value can't be converted to int so it is just ignored
args = {"-s", "abcd"};
run();
CHECK("abcd" == value);
}
TEST_CASE_METHOD(TApp, "IntTransformNonMerge", "[transform]") {
std::string value;
app.add_option("-s", value)
->transform(CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}) &
CLI::Transformer(std::map<int, int>{{25, 5}, {28, 6}, {31, 7}}),
"merge");
args = {"-s", "15"};
run();
CHECK("5" == value);
args = {"-s", "18"};
run();
CHECK("6" == value);
// value can't be converted to int so it is just ignored
args = {"-s", "abcd"};
run();
CHECK("abcd" == value);
args = {"-s", "25"};
run();
CHECK("5" == value);
args = {"-s", "31"};
run();
CHECK("7" == value);
auto help = app.help();
CHECK(help.find("15->5") != std::string::npos);
CHECK(help.find("25->5") != std::string::npos);
auto *validator = app.get_option("-s")->get_validator();
help = validator->get_description();
CHECK(help.find("15->5") != std::string::npos);
CHECK(help.find("25->5") != std::string::npos);
auto *validator2 = app.get_option("-s")->get_validator("merge");
CHECK(validator == validator2);
}
TEST_CASE_METHOD(TApp, "IntTransformMergeWithCustomValidator", "[transform]") {
std::string value;
auto *opt = app.add_option("-s", value)
->transform(CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}) |
CLI::Validator(
[](std::string &element) {
if(element == "frog") {
element = "hops";
}
return std::string{};
},
std::string{}),
"check");
args = {"-s", "15"};
run();
CHECK("5" == value);
args = {"-s", "18"};
run();
CHECK("6" == value);
// value can't be converted to int so it is just ignored
args = {"-s", "frog"};
run();
CHECK("hops" == value);
args = {"-s", "25"};
run();
CHECK("25" == value);
auto help = app.help();
CHECK(help.find("15->5") != std::string::npos);
CHECK(help.find("OR") == std::string::npos);
auto *validator = opt->get_validator("check");
CHECK("check" == validator->get_name());
validator->active(false);
help = app.help();
CHECK(help.find("15->5") == std::string::npos);
}
TEST_CASE_METHOD(TApp, "IntTransformMergeWithCustomSharedValidator", "[transform]") {
std::string value;
CLI::Validator_p ctrans = std::make_shared<CLI::Validator>(
CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}) | CLI::Validator(
[](std::string &element) {
if(element == "frog") {
element = "hops";
}
return std::string{};
},
std::string{}));
ctrans->name("check");
auto *opt = app.add_option("-s", value)->transform(ctrans);
args = {"-s", "15"};
run();
CHECK("5" == value);
args = {"-s", "18"};
run();
CHECK("6" == value);
// value can't be converted to int so it is just ignored
args = {"-s", "frog"};
run();
CHECK("hops" == value);
args = {"-s", "25"};
run();
CHECK("25" == value);
auto help = app.help();
CHECK(help.find("15->5") != std::string::npos);
CHECK(help.find("OR") == std::string::npos);
auto *validator = opt->get_validator("check");
CHECK("check" == validator->get_name());
validator->active(false);
help = app.help();
CHECK(help.find("15->5") == std::string::npos);
}
#endif
static const std::map<std::string, std::string> validValues = {
{"test\\u03C0\\u00e9", from_u8string(u8"test\u03C0\u00E9")},
{"test\\u03C0\\u00e9", from_u8string(u8"test\u73C0\u0057")},
{"test\\U0001F600\\u00E9", from_u8string(u8"test\U0001F600\u00E9")},
{R"("this\nis\na\nfour\tline test")", "this\nis\na\nfour\tline test"},
{"'B\"(\\x35\\xa7\\x46)\"'", std::string{0x35, static_cast<char>(0xa7), 0x46}},
{"B\"(\\x35\\xa7\\x46)\"", std::string{0x35, static_cast<char>(0xa7), 0x46}},
{"test\\ntest", "test\ntest"},
{"\"test\\ntest", "\"test\ntest"},
{R"('this\nis\na\nfour\tline test')", R"(this\nis\na\nfour\tline test)"},
{R"("this\nis\na\nfour\tline test")", "this\nis\na\nfour\tline test"},
{R"(`this\nis\na\nfour\tline test`)", R"(this\nis\na\nfour\tline test)"}};
TEST_CASE_METHOD(TApp, "StringEscapeValid", "[transform]") {
auto test_data = GENERATE(from_range(validValues));
std::string value{};
app.add_option("-n", value)->transform(CLI::EscapedString);
args = {"-n", test_data.first};
run();
CHECK(test_data.second == value);
}
static const std::vector<std::string> invalidValues = {"test\\U0001M600\\u00E9",
"test\\U0001E600\\u00M9",
"test\\U0001E600\\uD8E9",
"test\\U0001E600\\uD8",
"test\\U0001E60",
"test\\qbad"};
TEST_CASE_METHOD(TApp, "StringEscapeInvalid", "[transform]") {
auto test_data = GENERATE(from_range(invalidValues));
std::string value{};
app.add_option("-n", value)->transform(CLI::EscapedString);
args = {"-n", test_data};
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
+30
View File
@@ -0,0 +1,30 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include <string>
TEST_CASE_METHOD(TApp, "True Bool Option", "[bool][flag]") {
// Strings needed here due to MSVC 2015.
auto param = GENERATE(as<std::string>{}, "true", "on", "True", "ON");
bool value{false}; // Not used, but set just in case
app.add_option("-b,--bool", value);
args = {"--bool", param};
run();
CHECK(app.count("--bool") == 1u);
CHECK(value);
}
TEST_CASE_METHOD(TApp, "False Bool Option", "[bool][flag]") {
auto param = GENERATE(as<std::string>{}, "false", "off", "False", "OFF");
bool value{true}; // Not used, but set just in case
app.add_option("-b,--bool", value);
args = {"--bool", param};
run();
CHECK(app.count("--bool") == 1u);
CHECK_FALSE(value);
}
+19
View File
@@ -0,0 +1,19 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include <Windows.h>
// This test verifies that CLI11 still works if
// Windows.h is included. #145
TEST_CASE_METHOD(TApp, "WindowsTestSimple", "[windows]") {
app.add_flag("-c,--count");
args = {"-c"};
run();
CHECK(app.count("-c") == 1u);
CHECK(app.count("--count") == 1u);
}
+132
View File
@@ -0,0 +1,132 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#ifdef CLI11_SINGLE_FILE
#include "CLI11.hpp"
#else
#include "CLI/CLI.hpp"
#endif
#include "catch.hpp"
#include <array>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
using input_t = std::vector<std::string>;
class TApp {
public:
CLI::App app{"My Test Program"};
input_t args{};
virtual ~TApp() = default;
void run() {
// It is okay to re-parse - clear is called automatically before a parse.
input_t newargs = args;
std::reverse(std::begin(newargs), std::end(newargs));
app.parse(newargs);
}
};
inline int fileClear(const std::string &name) { return std::remove(name.c_str()); }
class TempFile {
std::string _name{};
public:
explicit TempFile(std::string name) : _name(std::move(name)) {
if(!CLI::NonexistentPath(_name).empty())
throw std::runtime_error(_name);
}
~TempFile() {
std::remove(_name.c_str()); // Doesn't matter if returns 0 or not
}
operator const std::string &() const { return _name; } // NOLINT(google-explicit-constructor)
CLI11_NODISCARD const char *c_str() const { return _name.c_str(); }
};
inline void put_env(std::string name, std::string value) {
#ifdef _WIN32
_putenv_s(name.c_str(), value.c_str());
#else
setenv(name.c_str(), value.c_str(), 1);
#endif
}
inline void unset_env(std::string name) {
#ifdef _WIN32
_putenv_s(name.c_str(), "");
#else
unsetenv(name.c_str());
#endif
}
/// these are provided for compatibility with the char8_t for C++20 that breaks stuff
inline std::string from_u8string(const std::string &s) { return s; }
inline std::string from_u8string(std::string &&s) { return std::move(s); }
#if defined(__cpp_lib_char8_t)
inline std::string from_u8string(const std::u8string &s) { return std::string(s.begin(), s.end()); }
#elif defined(__cpp_char8_t)
inline std::string from_u8string(const char8_t *s) { return std::string(reinterpret_cast<const char *>(s)); }
#endif
inline void check_identical_files(const char *path1, const char *path2) {
std::string err1 = CLI::ExistingFile(path1);
if(!err1.empty()) {
FAIL("Could not open " << path1 << ": " << err1);
}
std::string err2 = CLI::ExistingFile(path2);
if(!err2.empty()) {
FAIL("Could not open " << path2 << ": " << err2);
}
// open files at the end to compare size first
std::ifstream file1(path1, std::ifstream::ate | std::ifstream::binary);
std::ifstream file2(path2, std::ifstream::ate | std::ifstream::binary);
if(!file1.good()) {
FAIL("File " << path1 << " is corrupted");
}
if(!file2.good()) {
FAIL("File " << path2 << " is corrupted");
}
if(file1.tellg() != file2.tellg()) {
FAIL("Different file sizes:\n " << file1.tellg() << " bytes in " << path1 << "\n " << file2.tellg()
<< " bytes in " << path2);
}
// rewind files
file1.seekg(0);
file2.seekg(0);
static std::array<uint8_t, 10240> buffer1;
static std::array<uint8_t, 10240> buffer2;
for(size_t ibuffer = 0; file1.good(); ++ibuffer) {
// Flawfinder: ignore
file1.read(reinterpret_cast<char *>(buffer1.data()), static_cast<std::streamsize>(buffer1.size()));
// Flawfinder: ignore
file2.read(reinterpret_cast<char *>(buffer2.data()), static_cast<std::streamsize>(buffer2.size()));
for(size_t i = 0; i < static_cast<size_t>(file1.gcount()); ++i) {
if(buffer1[i] != buffer2[i]) {
FAIL(std::hex << std::setfill('0') << "Different bytes at position " << (ibuffer * 10240 + i) << ":\n "
<< "0x" << std::setw(2) << static_cast<int>(buffer1[i]) << " in " << path1 << "\n "
<< "0x" << std::setw(2) << static_cast<int>(buffer2[i]) << " in " << path2);
}
}
}
}
@@ -0,0 +1,35 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include <CLI/CLI.hpp>
#include <cstring>
#include <iostream>
int main(int argc, char **argv) {
CLI::App app{"App description"};
char **original_argv = argv;
argv = app.ensure_utf8(argv);
#ifdef _WIN32
for(int i = 0; i < argc; i++) {
if(std::strcmp(argv[i], original_argv[i]) != 0) {
std::cerr << argv[i] << "\n";
std::cerr << original_argv[i] << "\n";
return i + 1;
}
argv[i][0] = 'x'; // access it to check that it is accessible
}
#else
(void)argc;
if(original_argv != argv) {
return -1;
}
#endif
return 0;
}
@@ -0,0 +1,36 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#include <CLI/CLI.hpp>
#include <cstring>
#include <iostream>
int main(int argc, char **argv) {
CLI::App app{"App description"};
char **original_argv = argv;
argv = app.ensure_utf8(argv);
argv = app.ensure_utf8(argv); // completely useless but works ok
#ifdef _WIN32
for(int i = 0; i < argc; i++) {
if(std::strcmp(argv[i], original_argv[i]) != 0) {
std::cerr << argv[i] << "\n";
std::cerr << original_argv[i] << "\n";
return i + 1;
}
argv[i][0] = 'x'; // access it to check that it is accessible
}
#else
(void)argc;
if(original_argv != argv) {
return -1;
}
#endif
return 0;
}
+36
View File
@@ -0,0 +1,36 @@
// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <string>
#ifdef CLI11_CATCH3
#include <catch2/catch_approx.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/generators/catch_generators_range.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
using Catch::Approx; // NOLINT(google-global-names-in-headers)
using Catch::Generators::from_range; // NOLINT(google-global-names-in-headers)
using Catch::Matchers::Equals; // NOLINT(google-global-names-in-headers)
using Catch::Matchers::WithinRel; // NOLINT(google-global-names-in-headers)
inline auto Contains(const std::string &x) { return Catch::Matchers::ContainsSubstring(x); }
#else
#include <catch2/catch.hpp>
#include <new>
using Catch::Equals; // NOLINT(google-global-names-in-headers)
using Catch::WithinRel; // NOLINT(google-global-names-in-headers)
using Catch::Matchers::Contains; // NOLINT(google-global-names-in-headers)
#endif
+1
View File
@@ -0,0 +1 @@
Hello Halló Привет 你好 👩‍🚀❤️
@@ -0,0 +1,25 @@
# Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
# under NSF AWARD 1414736 and by the respective contributors.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.14...4.2)
project(CLI11-find-package-test)
include(CTest)
if(CLI11_DIR)
set(CMAKE_PREFIX_PATH ${CLI11_DIR})
endif()
# Test the CLI11 CMake package config
find_package(CLI11 2.0 REQUIRED)
# Test the target
add_executable(package-test ../../examples/positional_validation.cpp)
target_link_libraries(package-test CLI11::CLI11)
add_test(NAME package-test1 COMMAND package-test one)
set_property(TEST package-test1 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one")
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
1 c e g0 g0 æß --tup4 N3CLI10ParseErrorE% 0  %% 0 iÿÿÿÿÿÿÿÿÿÿÿÿÿÿwrap $  
Binary file not shown.
@@ -0,0 +1,3 @@
-e-vC
,cÌC
,cÌÛÑÒ
@@ -0,0 +1 @@
=666666666~5Ë5Ë--oo?ptvtup@
Binary file not shown.
@@ -0,0 +1 @@
``'``'######################
@@ -0,0 +1,4 @@
--vB
s
'
sub
@@ -0,0 +1 @@
""K���������"鰫
Binary file not shown.
@@ -0,0 +1,9 @@
--vE
˙˙˙˙˙
#
@@ -0,0 +1 @@
--vD \���� \
@@ -0,0 +1 @@
1-!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ceeecae
@@ -0,0 +1 @@
-c
@@ -0,0 +1 @@
································-é#e,cecb
Binary file not shown.
@@ -0,0 +1 @@
dwrap 'a
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
--vC opCB (3tp"o3#
@@ -0,0 +1,2 @@
--vD � `
-5
@@ -0,0 +1,5 @@
--vB
--vB
����,����-vC
@@ -0,0 +1,4 @@
--vE
-3vE
0)-bb=`',,l
@@ -0,0 +1 @@
`--vM```-````-c`
@@ -0,0 +1 @@
[驎驎驎驎驎驎珉ppt1"wrappt1""\","""\""\","
@@ -0,0 +1 @@
--$,,,,,,,,,,,,,,,,,,,,A,,,,,,,,,,,-$,,,,,,,,,,,,,,,,,,,,A,,,,,,,,,,,,,,,,,,,,,;--svopt2#,,,,-sC
@@ -0,0 +1 @@
-¬,,,,,,,,,,,,,,,,,,,opt1śa
@@ -0,0 +1,2 @@
'''-$�
$
@@ -0,0 +1 @@
" (\\\,"�窿
@@ -0,0 +1 @@
'^^^^^^^\^^^^^^''''''@''i¦
@@ -0,0 +1 @@
"\ "
@@ -0,0 +1 @@
"Ü-t2ÿÿÿÿp'--vopt1'â''e#ÿÿ'â''e
@@ -0,0 +1 @@
ParseErrorEF'' --vo-d{}
@@ -0,0 +1 @@
[--'
@@ -0,0 +1 @@
-ccaaaa
@@ -0,0 +1 @@
config ' '
@@ -0,0 +1,6 @@
Š
atd
ŸVVV-baÒî=
î
�
.-' -
Binary file not shown.
@@ -0,0 +1,3 @@
.br-bN3CLI10ParseErrorEa5
Binary file not shown.
@@ -0,0 +1 @@
=o˙˙˙˙˙˙˙˙˙˙p--2v˙˙t ' Í-č-
+1
View File
@@ -0,0 +1 @@
nflag2=555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555"="
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
"\�"
+1
View File
@@ -0,0 +1 @@
""\"
+1
View File
@@ -0,0 +1 @@
"\uasdwrapヲヲヲ-"ヲヲ-"--confi矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣.矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣矣g
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
--vdtr5=[|
+1
View File
@@ -0,0 +1 @@
[ÏqÏq[]1."È"\ ".saopt1[[]1."È"\ ".saopt1[]
+1
View File
@@ -0,0 +1 @@
vdtr9=[
@@ -0,0 +1 @@
'--sub1.!-vA '
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,7 @@
<flago<o<modifiers=<<x-'o-a</o<<5ţ˙˙ůopt6=8
-'--R-(‹Í sÖlag=opt6=vJ8
-'-- -(‹5 --‹Í --=
--vC=opt6=8
-'-- -pt=on>a-'o-a˙˙ůO<x-lag>ÁÁoptggggggggggggggggggggggg'o-a˙˙ůO<x-lag>ÁÁopt4 » »h-
űűűEv˙
-.v˙˙'ÝflagtupĚ�oop2flag<<x-'g>t</flag>ttfapt2
Binary file not shown.
@@ -0,0 +1,28 @@
"--vA=
vA
-
=--svtup
vA=
-d'=--svt=-
v vA
-
=-
v =--svtup
vA=
-d'=--svtup
vA--svtup
vA=
-d'=--svtup
vA=
-d'
=
-d'
""

Some files were not shown because too many files have changed in this diff Show More