Commit fd30b5989f5fd94e1bd0c5b5cc3946a6d8b8901f

Authored by Philip Top
Committed by GitHub
1 parent dcbcb472

fix: improve some confusing error situations with config files (#781)

* add some more tests for coverage and fix some confusing error situations with config files.

* style: pre-commit.ci fixes

* fix warning

* ci: fix coverage

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
.github/workflows/tests.yml
@@ -20,6 +20,8 @@ jobs: @@ -20,6 +20,8 @@ jobs:
20 precompile: ["ON", "OFF"] 20 precompile: ["ON", "OFF"]
21 steps: 21 steps:
22 - uses: actions/checkout@v3 22 - uses: actions/checkout@v3
  23 + with:
  24 + fetch-depth: 0
23 25
24 - name: Get LCov 26 - name: Get LCov
25 run: | 27 run: |
@@ -50,12 +52,10 @@ jobs: @@ -50,12 +52,10 @@ jobs:
50 lcov --list coverage.info 52 lcov --list coverage.info
51 working-directory: build 53 working-directory: build
52 54
53 - - name: Upload coverage  
54 - run: |  
55 - curl -Os https://uploader.codecov.io/latest/linux/codecov  
56 - chmod +x codecov  
57 - ./codecov  
58 - working-directory: build 55 + - uses: codecov/codecov-action@v3
  56 + with:
  57 + fail_ci_if_error: true
  58 + working-directory: build
59 59
60 clang-tidy: 60 clang-tidy:
61 name: Clang-Tidy 61 name: Clang-Tidy
include/CLI/impl/App_inl.hpp
@@ -1384,16 +1384,23 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &amp;item, std::size_t @@ -1384,16 +1384,23 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &amp;item, std::size_t
1384 if(op->empty()) { 1384 if(op->empty()) {
1385 1385
1386 if(op->get_expected_min() == 0) { 1386 if(op->get_expected_min() == 0) {
1387 - // Flag parsing  
1388 - auto res = config_formatter_->to_flag(item);  
1389 - res = op->get_flag_value(item.name, res); 1387 + if(item.inputs.size() <= 1) {
  1388 + // Flag parsing
  1389 + auto res = config_formatter_->to_flag(item);
  1390 + res = op->get_flag_value(item.name, res);
1390 1391
1391 - op->add_result(res);  
1392 -  
1393 - } else {  
1394 - op->add_result(item.inputs);  
1395 - op->run_callback(); 1392 + op->add_result(res);
  1393 + return true;
  1394 + }
  1395 + if(static_cast<int>(item.inputs.size()) > op->get_items_expected_max()) {
  1396 + if(op->get_items_expected_max() > 1) {
  1397 + throw ArgumentMismatch::AtMost(item.fullname(), op->get_items_expected_max(), item.inputs.size());
  1398 + }
  1399 + throw ConversionError::TooManyInputsFlag(item.fullname());
  1400 + }
1396 } 1401 }
  1402 + op->add_result(item.inputs);
  1403 + op->run_callback();
1397 } 1404 }
1398 1405
1399 return true; 1406 return true;
tests/AppTest.cpp
@@ -981,7 +981,9 @@ TEST_CASE_METHOD(TApp, &quot;emptyVectorReturn&quot;, &quot;[app]&quot;) { @@ -981,7 +981,9 @@ TEST_CASE_METHOD(TApp, &quot;emptyVectorReturn&quot;, &quot;[app]&quot;) {
981 981
982 std::vector<std::string> strs; 982 std::vector<std::string> strs;
983 std::vector<std::string> strs2; 983 std::vector<std::string> strs2;
  984 + std::vector<std::string> strs3;
984 auto *opt1 = app.add_option("--str", strs)->required()->expected(0, 2); 985 auto *opt1 = app.add_option("--str", strs)->required()->expected(0, 2);
  986 + app.add_option("--str3", strs3)->expected(1, 3);
985 app.add_option("--str2", strs2); 987 app.add_option("--str2", strs2);
986 args = {"--str"}; 988 args = {"--str"};
987 989
@@ -1004,6 +1006,11 @@ TEST_CASE_METHOD(TApp, &quot;emptyVectorReturn&quot;, &quot;[app]&quot;) { @@ -1004,6 +1006,11 @@ TEST_CASE_METHOD(TApp, &quot;emptyVectorReturn&quot;, &quot;[app]&quot;) {
1004 1006
1005 CHECK_NOTHROW(run()); 1007 CHECK_NOTHROW(run());
1006 CHECK(strs.empty()); 1008 CHECK(strs.empty());
  1009 + opt1->required(false);
  1010 + args = {"--str3", "{}"};
  1011 +
  1012 + CHECK_NOTHROW(run());
  1013 + CHECK_FALSE(strs3.empty());
1007 } 1014 }
1008 1015
1009 TEST_CASE_METHOD(TApp, "RequiredOptsDoubleShort", "[app]") { 1016 TEST_CASE_METHOD(TApp, "RequiredOptsDoubleShort", "[app]") {
tests/ConfigFileTest.cpp
@@ -1017,17 +1017,19 @@ TEST_CASE_METHOD(TApp, &quot;TOMLStringVector&quot;, &quot;[config]&quot;) { @@ -1017,17 +1017,19 @@ TEST_CASE_METHOD(TApp, &quot;TOMLStringVector&quot;, &quot;[config]&quot;) {
1017 out << "zero1=[]\n"; 1017 out << "zero1=[]\n";
1018 out << "zero2={}\n"; 1018 out << "zero2={}\n";
1019 out << "zero3={}\n"; 1019 out << "zero3={}\n";
  1020 + out << "zero4=[\"{}\",\"\"]\n";
1020 out << "nzero={}\n"; 1021 out << "nzero={}\n";
1021 out << "one=[\"1\"]\n"; 1022 out << "one=[\"1\"]\n";
1022 out << "two=[\"2\",\"3\"]\n"; 1023 out << "two=[\"2\",\"3\"]\n";
1023 out << "three=[\"1\",\"2\",\"3\"]\n"; 1024 out << "three=[\"1\",\"2\",\"3\"]\n";
1024 } 1025 }
1025 1026
1026 - std::vector<std::string> nzero, zero1, zero2, zero3, one, two, three; 1027 + std::vector<std::string> nzero, zero1, zero2, zero3, zero4, one, two, three;
1027 app.add_option("--zero1", zero1)->required()->expected(0, 99)->default_str("{}"); 1028 app.add_option("--zero1", zero1)->required()->expected(0, 99)->default_str("{}");
1028 app.add_option("--zero2", zero2)->required()->expected(0, 99)->default_val(std::vector<std::string>{}); 1029 app.add_option("--zero2", zero2)->required()->expected(0, 99)->default_val(std::vector<std::string>{});
1029 // if no default is specified the argument results in an empty string 1030 // if no default is specified the argument results in an empty string
1030 app.add_option("--zero3", zero3)->required()->expected(0, 99); 1031 app.add_option("--zero3", zero3)->required()->expected(0, 99);
  1032 + app.add_option("--zero4", zero4)->required()->expected(0, 99);
1031 app.add_option("--nzero", nzero)->required(); 1033 app.add_option("--nzero", nzero)->required();
1032 app.add_option("--one", one)->required(); 1034 app.add_option("--one", one)->required();
1033 app.add_option("--two", two)->required(); 1035 app.add_option("--two", two)->required();
@@ -1038,6 +1040,7 @@ TEST_CASE_METHOD(TApp, &quot;TOMLStringVector&quot;, &quot;[config]&quot;) { @@ -1038,6 +1040,7 @@ TEST_CASE_METHOD(TApp, &quot;TOMLStringVector&quot;, &quot;[config]&quot;) {
1038 CHECK(zero1 == std::vector<std::string>({})); 1040 CHECK(zero1 == std::vector<std::string>({}));
1039 CHECK(zero2 == std::vector<std::string>({})); 1041 CHECK(zero2 == std::vector<std::string>({}));
1040 CHECK(zero3 == std::vector<std::string>({""})); 1042 CHECK(zero3 == std::vector<std::string>({""}));
  1043 + CHECK(zero4 == std::vector<std::string>({"{}"}));
1041 CHECK(nzero == std::vector<std::string>({"{}"})); 1044 CHECK(nzero == std::vector<std::string>({"{}"}));
1042 CHECK(one == std::vector<std::string>({"1"})); 1045 CHECK(one == std::vector<std::string>({"1"}));
1043 CHECK(two == std::vector<std::string>({"2", "3"})); 1046 CHECK(two == std::vector<std::string>({"2", "3"}));
@@ -1735,6 +1738,23 @@ TEST_CASE_METHOD(TApp, &quot;IniFlagDual&quot;, &quot;[config]&quot;) { @@ -1735,6 +1738,23 @@ TEST_CASE_METHOD(TApp, &quot;IniFlagDual&quot;, &quot;[config]&quot;) {
1735 CHECK_THROWS_AS(run(), CLI::ConversionError); 1738 CHECK_THROWS_AS(run(), CLI::ConversionError);
1736 } 1739 }
1737 1740
  1741 +TEST_CASE_METHOD(TApp, "IniVectorMax", "[config]") {
  1742 +
  1743 + TempFile tmpini{"TestIniTmp.ini"};
  1744 +
  1745 + std::vector<std::string> v1;
  1746 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1747 + app.add_option("--vec", v1)->expected(0, 2);
  1748 + app.set_config("--config", tmpini);
  1749 +
  1750 + {
  1751 + std::ofstream out{tmpini};
  1752 + out << "vec=[a,b,c]" << std::endl;
  1753 + }
  1754 +
  1755 + CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
  1756 +}
  1757 +
1738 TEST_CASE_METHOD(TApp, "IniShort", "[config]") { 1758 TEST_CASE_METHOD(TApp, "IniShort", "[config]") {
1739 1759
1740 TempFile tmpini{"TestIniTmp.ini"}; 1760 TempFile tmpini{"TestIniTmp.ini"};
tests/HelpTest.cpp
@@ -974,6 +974,16 @@ TEST_CASE(&quot;THelp: GroupOrder&quot;, &quot;[help]&quot;) { @@ -974,6 +974,16 @@ TEST_CASE(&quot;THelp: GroupOrder&quot;, &quot;[help]&quot;) {
974 CHECK(aee_loc > zee_loc); 974 CHECK(aee_loc > zee_loc);
975 } 975 }
976 976
  977 +TEST_CASE("THelp: GroupNameError", "[help]") {
  978 + CLI::App app;
  979 +
  980 + auto *f1 = app.add_flag("--one");
  981 + auto *f2 = app.add_flag("--two");
  982 +
  983 + CHECK_THROWS_AS(f1->group("evil group name\non two lines"), CLI::IncorrectConstruction);
  984 + CHECK_THROWS_AS(f2->group(std::string(5, '\0')), CLI::IncorrectConstruction);
  985 +}
  986 +
977 TEST_CASE("THelp: ValidatorsText", "[help]") { 987 TEST_CASE("THelp: ValidatorsText", "[help]") {
978 CLI::App app; 988 CLI::App app;
979 989
tests/SetTest.cpp
@@ -492,6 +492,23 @@ TEST_CASE_METHOD(TApp, &quot;FailSet&quot;, &quot;[set]&quot;) { @@ -492,6 +492,23 @@ TEST_CASE_METHOD(TApp, &quot;FailSet&quot;, &quot;[set]&quot;) {
492 CHECK_THROWS_AS(run(), CLI::ValidationError); 492 CHECK_THROWS_AS(run(), CLI::ValidationError);
493 } 493 }
494 494
  495 +TEST_CASE_METHOD(TApp, "shortStringCheck", "[set]") {
  496 +
  497 + std::string choice;
  498 + app.add_option("-q,--quick", choice)->check([](const std::string &v) {
  499 + if(v.size() > 5) {
  500 + return std::string{"string too long"};
  501 + }
  502 + return std::string{};
  503 + });
  504 +
  505 + args = {"--quick", "3"};
  506 + CHECK_NOTHROW(run());
  507 +
  508 + args = {"--quick=hello_goodbye"};
  509 + CHECK_THROWS_AS(run(), CLI::ValidationError);
  510 +}
  511 +
495 TEST_CASE_METHOD(TApp, "FailMutableSet", "[set]") { 512 TEST_CASE_METHOD(TApp, "FailMutableSet", "[set]") {
496 513
497 int choice{0}; 514 int choice{0};