1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-05-01 13:13:53 +00:00

fix: issue found by fuzzing (#846)

* Add the beginnings of a fuzzing system for CLI11.  This commit adds the fuzzing code, a simple test, and two fixes to issues(seg faults) found by the initial round of fuzzing.  It also adds a few tests and coverage issues uncovered in the process of developing the fuzz tests. As a side effect adjusts some of the azure tests to specify the vmImage which was being changed on azure.

* update license to match rest of code base

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Philip Top 2023-03-09 12:02:49 -08:00 committed by GitHub
parent fc9cea6d76
commit 0a615f854d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 605 additions and 27 deletions

View File

@ -4,3 +4,4 @@ ignore:
- "book"
- "docs"
- "test_package"
- "fuzz"

2
.github/codecov.yml vendored
View File

@ -1,6 +1,6 @@
codecov:
notify:
after_n_builds: 4
after_n_builds: 8
coverage:
status:
project:

54
.github/workflows/fuzz.yml vendored Normal file
View File

@ -0,0 +1,54 @@
name: Fuzz
on:
workflow_dispatch:
push:
branches:
- main
- v*
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
quick_fuzz1:
name: quickfuzz1
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Configure
run: |
cmake -S . -B build \
-DCMAKE_CXX_STANDARD=17 \
-DCLI11_SINGLE_FILE_TESTS=OFF \
-DCLI11_BUILD_EXAMPLES=OFF \
-DCLI11_FUZZ_TARGET=ON \
-DCLI11_BUILD_TESTS=OFF \
-DCLI11_BUILD_DOCS=OFF \
-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_COMPILER_FORCED=ON \
-DCMAKE_CXX_FLAGS="-g -O1 -fsanitize=fuzzer,undefined,address"
- name: Build
run: cmake --build build -j4
- name: Test
run: |
cd build
make QUICK_CLI11_APP_FUZZ
- name: Test2
run: |
cd build
make QUICK_CLI11_FILE_FUZZ
- name: artifacts
if: failure()
uses: actions/upload-artifact@v3
with:
name: file_failure
path: ./build/fuzz/cli11_*_fail_artifact.txt

View File

@ -48,7 +48,7 @@ jobs:
- name: Prepare coverage
run: |
lcov --directory . --capture --output-file coverage.info
lcov --remove coverage.info '*/tests/*' '*/examples/*' '/usr/*' '*/book/*' --output-file coverage.info
lcov --remove coverage.info '*/tests/*' '*/examples/*' '/usr/*' '*/book/*' '*/fuzz/*' --output-file coverage.info
lcov --list coverage.info
working-directory: build

View File

@ -1,3 +1,4 @@
exclude: ^(.github/workflows/|docs/img/)
ci:
autoupdate_commit_msg: "chore(deps): pre-commit.ci autoupdate"
autofix_commit_msg: "style: pre-commit.ci fixes"

View File

@ -132,6 +132,9 @@ endif()
include(CLI11Warnings)
# build the fuzzing example or fuzz entry point
add_subdirectory(fuzz)
add_subdirectory(src)
# Allow tests to be run on CUDA

View File

@ -28,7 +28,6 @@ jobs:
- bash: cpplint --counting=detailed --recursive examples include/CLI tests
displayName: Checking against google style guide
# TODO: Fix macOS error and windows warning in c++17 mode
- job: Native
strategy:
matrix:
@ -38,13 +37,13 @@ jobs:
vmImage: "ubuntu-latest"
cli11.precompile: ON
macOS17:
vmImage: "macOS-latest"
vmImage: "macOS-12"
cli11.std: 17
macOS11:
vmImage: "macOS-latest"
vmImage: "macOS-11"
cli11.std: 11
macOS11PC:
vmImage: "macOS-latest"
vmImage: "macOS-11"
cli11.std: 11
cli11.precompile: ON
Windows17:

48
fuzz/CMakeLists.txt Normal file
View File

@ -0,0 +1,48 @@
# Copyright (c) 2017-2023, 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(CMAKE_CXX_STANDARD GREATER 16)
if(CLI11_FUZZ_TARGET)
add_executable(cli11_app_fuzzer cli11_app_fuzz.cpp fuzzApp.cpp fuzzApp.hpp)
target_link_libraries(cli11_app_fuzzer PUBLIC CLI11)
set_property(TARGET cli11_app_fuzzer PROPERTY FOLDER "Tests")
add_executable(cli11_file_fuzzer cli11_file_fuzz.cpp fuzzApp.cpp fuzzApp.hpp)
target_link_libraries(cli11_file_fuzzer PUBLIC CLI11)
set_property(TARGET cli11_file_fuzzer PROPERTY FOLDER "Tests")
if(NOT CLI11_FUZZ_ARTIFACT_PATH)
set(CLI11_FUZZ_ARTIFACT_PATH ${PROJECT_BINARY_DIR}/fuzz)
endif()
if(NOT CLI11_FUZZ_TIME)
set(CLI11_FUZZ_TIME 360)
endif()
add_custom_target(
QUICK_CLI11_APP_FUZZ
COMMAND ${CMAKE_COMMAND} -E make_directory corp
COMMAND
cli11_app_fuzzer corp -max_total_time=${CLI11_FUZZ_TIME} -max_len=2048
-dict=${CMAKE_CURRENT_SOURCE_DIR}/fuzz_dictionary1.txt
-exact_artifact_path=${CLI11_FUZZ_ARTIFACT_PATH}/cli11_app_fail_artifact.txt)
add_custom_target(
QUICK_CLI11_FILE_FUZZ
COMMAND ${CMAKE_COMMAND} -E make_directory corp
COMMAND
cli11_file_fuzzer corp -max_total_time=${CLI11_FUZZ_TIME} -max_len=2048
-dict=${CMAKE_CURRENT_SOURCE_DIR}/fuzz_dictionary2.txt
-exact_artifact_path=${CLI11_FUZZ_ARTIFACT_PATH}/cli11_file_fail_artifact.txt)
else()
if(CLI11_BUILD_EXAMPLES)
add_executable(cli11Fuzz fuzzCommand.cpp fuzzApp.cpp fuzzApp.hpp)
target_link_libraries(cli11Fuzz PUBLIC CLI11)
set_property(TARGET cli11Fuzz PROPERTY FOLDER "Examples")
endif()
endif()
endif()

30
fuzz/cli11_app_fuzz.cpp Normal file
View File

@ -0,0 +1,30 @@
// Copyright (c) 2017-2023, 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 "fuzzApp.hpp"
#include <CLI/CLI.hpp>
#include <cstring>
#include <exception>
#include <string>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
if(Size == 0) {
return 0;
}
std::string parseString(reinterpret_cast<const char *>(Data), Size);
CLI::FuzzApp fuzzdata;
auto app = fuzzdata.generateApp();
try {
app->parse(parseString);
} catch(const CLI::ParseError &e) {
//(app)->exit(e);
// this just indicates we caught an error known by CLI
}
return 0; // Non-zero return values are reserved for future use.
}

31
fuzz/cli11_file_fuzz.cpp Normal file
View File

@ -0,0 +1,31 @@
// Copyright (c) 2017-2023, 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 "fuzzApp.hpp"
#include <CLI/CLI.hpp>
#include <cstring>
#include <exception>
#include <sstream>
#include <string>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
if(Size == 0) {
return 0;
}
std::string parseString(reinterpret_cast<const char *>(Data), Size);
std::stringstream out(parseString);
CLI::FuzzApp fuzzdata;
auto app = fuzzdata.generateApp();
try {
app->parse_from_stream(out);
} catch(const CLI::ParseError &e) {
(app)->exit(e);
// this just indicates we caught an error known by CLI
}
return 0; // Non-zero return values are reserved for future use.
}

85
fuzz/fuzzApp.cpp Normal file
View File

@ -0,0 +1,85 @@
// Copyright (c) 2017-2023, 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 "fuzzApp.hpp"
namespace CLI {
/*
int32_t val32{0};
int16_t val16{0};
int8_t val8{0};
int64_t val64{0};
uint32_t uval32{0};
uint16_t uval16{0};
uint8_t uval8{0};
uint64_t uval64{0};
std::atomic<int64_t> atomicval64{0};
std::atomic<uint64_t> atomicuval64{0};
double v1{0};
float v2{0};
std::vector<double> vv1;
std::vector<std::string> vstr;
std::vector<std::vector<double>> vecvecd;
std::vector<std::vector<std::string>> vvs;
std::optional<double> od1;
std::optional<std::string> ods;
std::pair<double, std::string> p1;
std::pair<std::vector<double>, std::string> p2;
std::tuple<int64_t, uint16_t, std::optional<double>> t1;
std::tuple<std::tuple<std::tuple<std::string, double, std::vector<int>>,std::string, double>,std::vector<int>,
std::optional<std::string>> tcomplex; std::string_view vstrv;
bool flag1{false};
int flagCnt{0};
std::atomic<bool> flagAtomic{false};
*/
std::shared_ptr<CLI::App> FuzzApp::generateApp() {
auto fApp = std::make_shared<CLI::App>("fuzzing App", "fuzzer");
fApp->set_config("--config");
fApp->add_flag("-a,--flag");
fApp->add_flag("-b,--flag2", flag1);
fApp->add_flag("-c{34},--flag3{1}", flagCnt)->disable_flag_override();
fApp->add_flag("-e,--flagA", flagAtomic);
fApp->add_option("-d,--opt1", val8);
fApp->add_option("--opt2", val16);
fApp->add_option("--opt3", val32);
fApp->add_option("--opt4", val64);
fApp->add_option("--opt5", uval8);
fApp->add_option("--opt6", uval16);
fApp->add_option("--opt7", uval32);
fApp->add_option("--opt8", uval64);
fApp->add_option("--aopt1", atomicval64);
fApp->add_option("--aopt2", atomicuval64);
fApp->add_option("--dopt1", v1);
fApp->add_option("--dopt2", v2);
fApp->add_option("--vopt1", vv1);
fApp->add_option("--vopt2", vvs);
fApp->add_option("--vopt3", vstr);
fApp->add_option("--vopt4", vecvecd);
fApp->add_option("--oopt1", od1);
fApp->add_option("--oopt2", ods);
fApp->add_option("--tup1", p1);
fApp->add_option("--tup2", t1);
fApp->add_option("--tup4", tcomplex);
fApp->add_option("--dwrap", dwrap);
fApp->add_option("--iwrap", iwrap);
return fApp;
}
} // namespace CLI

92
fuzz/fuzzApp.hpp Normal file
View File

@ -0,0 +1,92 @@
// Copyright (c) 2017-2023, 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 <atomic>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
namespace CLI {
class intWrapper64 {
public:
intWrapper64() = default;
explicit intWrapper64(int64_t v) : val(v){};
CLI11_NODISCARD int64_t value() const { return val; }
private:
int64_t val{0};
};
class doubleWrapper {
public:
doubleWrapper() = default;
explicit doubleWrapper(double v) : val(v){};
CLI11_NODISCARD double value() const { return val; }
private:
double val{0.0};
};
class FuzzApp {
public:
FuzzApp() = default;
std::shared_ptr<CLI::App> generateApp();
int32_t val32{0};
int16_t val16{0};
int8_t val8{0};
int64_t val64{0};
uint32_t uval32{0};
uint16_t uval16{0};
uint8_t uval8{0};
uint64_t uval64{0};
std::atomic<int64_t> atomicval64{0};
std::atomic<uint64_t> atomicuval64{0};
double v1{0};
float v2{0};
std::vector<double> vv1{};
std::vector<std::string> vstr{};
std::vector<std::vector<double>> vecvecd{};
std::vector<std::vector<std::string>> vvs{};
std::optional<double> od1{};
std::optional<std::string> ods{};
std::pair<double, std::string> p1{};
std::pair<std::vector<double>, std::string> p2{};
std::tuple<int64_t, uint16_t, std::optional<double>> t1{};
std::tuple<std::tuple<std::tuple<std::string, double, std::vector<int>>, std::string, double>,
std::vector<int>,
std::optional<std::string>>
tcomplex{};
std::tuple<std::tuple<std::tuple<std::string, double, std::vector<int>>, std::string, double>,
std::vector<int>,
std::optional<std::string>>
tcomplex2{};
std::string_view vstrv = "";
bool flag1{false};
int flagCnt{0};
std::atomic<bool> flagAtomic{false};
intWrapper64 iwrap{0};
doubleWrapper dwrap{0.0};
};
} // namespace CLI

24
fuzz/fuzzCommand.cpp Normal file
View File

@ -0,0 +1,24 @@
// Copyright (c) 2017-2023, 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 "fuzzApp.hpp"
#include <CLI/CLI.hpp>
#include <iostream>
int main(int argc, char **argv) {
CLI::FuzzApp fuzzdata;
auto app = fuzzdata.generateApp();
try {
app->parse(argc, argv);
} catch(const CLI::ParseError &e) {
(app)->exit(e);
// this just indicates we caught an error known by CLI
}
return 0;
}

34
fuzz/fuzz_dictionary1.txt Normal file
View File

@ -0,0 +1,34 @@
###### Recommended dictionary.
"-a"
"-b"
"-c"
"-d"
"-e"
"--flag1"
"--flag"
"--flag2"
"--flagA"
"--opt1"
"--opt2"
"--opt3"
"--opt4"
"--opt5"
"--opt6"
"--opt7"
"--opt8"
"--opt9"
"--aopt1"
"--aopt2"
"--dopt1"
"--dopt2"
"--vopt1"
"--vopt2"
"--vopt3"
"--vopt4"
"--oopt1"
"--oopt2"
"--tup1"
"--tup2"
"--tup4"
"--dwrap"
"--iwrap"

37
fuzz/fuzz_dictionary2.txt Normal file
View File

@ -0,0 +1,37 @@
###### Recommended dictionary.
"a"
"b"
"c"
"d"
"e"
"flag1"
"flag"
"flag2"
"flagA"
"opt1"
"opt2"
"opt3"
"opt4"
"opt5"
"opt6"
"opt7"
"opt8"
"opt9"
"aopt1"
"aopt2"
"dopt1"
"dopt2"
"vopt1"
"vopt2"
"vopt3"
"vopt4"
"="
"oopt1"
"oopt2"
"tup1"
"tup2"
"tup4"
"tup2"
"tup4"
"dwrap"
"iwrap"

View File

@ -1239,9 +1239,17 @@ bool lexical_assign(const std::string &input, AssignTo &output) {
output = 0;
return true;
}
int val = 0;
int val{0};
if(lexical_cast(input, val)) {
#if defined(__clang__)
/* on some older clang compilers */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-conversion"
#endif
output = val;
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
return true;
}
return false;
@ -1296,11 +1304,13 @@ template <typename AssignTo,
detail::enabler> = detail::dummy>
bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
// the remove const is to handle pair types coming from a container
typename std::remove_const<typename std::tuple_element<0, ConvertTo>::type>::type v1;
typename std::tuple_element<1, ConvertTo>::type v2;
bool retval = lexical_assign<decltype(v1), decltype(v1)>(strings[0], v1);
using FirstType = typename std::remove_const<typename std::tuple_element<0, ConvertTo>::type>::type;
using SecondType = typename std::tuple_element<1, ConvertTo>::type;
FirstType v1;
SecondType v2;
bool retval = lexical_assign<FirstType, FirstType>(strings[0], v1);
if(strings.size() > 1) {
retval = retval && lexical_assign<decltype(v2), decltype(v2)>(strings[1], v2);
retval = retval && lexical_assign<SecondType, SecondType>(strings[1], v2);
}
if(retval) {
output = AssignTo{v1, v2};
@ -1460,7 +1470,7 @@ tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) {
std::size_t index{subtype_count_min<ConvertTo>::value};
const std::size_t mx_count{subtype_count<ConvertTo>::value};
const std::size_t mx{(std::max)(mx_count, strings.size())};
const std::size_t mx{(std::min)(mx_count, strings.size() - 1)};
while(index < mx) {
if(is_separator(strings[index])) {
@ -1470,7 +1480,11 @@ tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) {
}
bool retval = lexical_conversion<AssignTo, ConvertTo>(
std::vector<std::string>(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index)), output);
strings.erase(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index) + 1);
if(strings.size() > index) {
strings.erase(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index) + 1);
} else {
strings.clear();
}
return retval;
}

View File

@ -66,20 +66,22 @@ target_include_directories(
CLI11 ${SYSTEM_INCL} ${PUBLIC_OR_INTERFACE} $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
if(CMAKE_VERSION VERSION_LESS 3.8)
# This might not be a complete list
target_compile_features(
CLI11
INTERFACE cxx_lambdas
cxx_nullptr
cxx_override
cxx_range_for
cxx_right_angle_brackets
cxx_strong_enums
cxx_constexpr
cxx_auto_type)
else()
target_compile_features(CLI11 INTERFACE cxx_std_11)
if(CMAKE_CXX_STANDARD LESS 14)
if(CMAKE_VERSION VERSION_LESS 3.8)
# This might not be a complete list
target_compile_features(
CLI11
INTERFACE cxx_lambdas
cxx_nullptr
cxx_override
cxx_range_for
cxx_right_angle_brackets
cxx_strong_enums
cxx_constexpr
cxx_auto_type)
else()
target_compile_features(CLI11 INTERFACE cxx_std_11)
endif()
endif()
if(CLI11_SINGLE_FILE)

View File

@ -56,6 +56,10 @@ 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()
@ -169,6 +173,16 @@ foreach(T IN LISTS CLI11_MULTIONLY_TESTS)
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()
endif()
# Add -Wno-deprecated-declarations to DeprecatedTest
set(no-deprecated-declarations $<$<CXX_COMPILER_ID:MSVC>:/wd4996>
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wno-deprecated-declarations>)

37
tests/FuzzFailTest.cpp Normal file
View File

@ -0,0 +1,37 @@
// Copyright (c) 2017-2023, 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"
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, 3));
auto parseData = loadFailureFile("fuzz_app_fail", index);
try {
app->parse(parseData);
} catch(const CLI::ParseError & /*e*/) {
}
}

View File

@ -305,6 +305,27 @@ template <class X> class objWrapper {
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
@ -347,6 +368,26 @@ TEST_CASE_METHOD(TApp, "doubleWrapper", "[newparse]") {
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");

View File

@ -336,6 +336,36 @@ TEST_CASE_METHOD(TApp, "pair_check", "[optiontype]") {
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "pair_check_string", "[optiontype]") {
std::string myfile{"pair_check_file.txt"};
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
CHECK(ok);
CHECK(CLI::ExistingFile(myfile).empty());
std::pair<std::string, std::string> findex;
auto v0 = CLI::ExistingFile;
v0.application_index(0);
auto v1 = CLI::PositiveNumber;
v1.application_index(1);
app.add_option("--file", findex)->check(v0)->check(v1);
args = {"--file", myfile, "2"};
CHECK_NOTHROW(run());
CHECK(myfile == findex.first);
CHECK("2" == findex.second);
args = {"--file", myfile, "-3"};
CHECK_THROWS_AS(run(), CLI::ValidationError);
args = {"--file", myfile, "2"};
std::remove(myfile.c_str());
CHECK_THROWS_AS(run(), CLI::ValidationError);
}
TEST_CASE_METHOD(TApp, "pair_check_take_first", "[optiontype]") {
std::string myfile{"pair_check_file2.txt"};
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file

Binary file not shown.

View File

@ -0,0 +1 @@
1 c e g0 g0 æß --tup4 N3CLI10ParseErrorE% 0  %% 0 iÿÿÿÿÿÿÿÿÿÿÿÿÿÿwrap $