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 20 precompile: ["ON", "OFF"]
21 21 steps:
22 22 - uses: actions/checkout@v3
  23 + with:
  24 + fetch-depth: 0
23 25  
24 26 - name: Get LCov
25 27 run: |
... ... @@ -50,12 +52,10 @@ jobs:
50 52 lcov --list coverage.info
51 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 60 clang-tidy:
61 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 1384 if(op->empty()) {
1385 1385  
1386 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 1406 return true;
... ...
tests/AppTest.cpp
... ... @@ -981,7 +981,9 @@ TEST_CASE_METHOD(TApp, &quot;emptyVectorReturn&quot;, &quot;[app]&quot;) {
981 981  
982 982 std::vector<std::string> strs;
983 983 std::vector<std::string> strs2;
  984 + std::vector<std::string> strs3;
984 985 auto *opt1 = app.add_option("--str", strs)->required()->expected(0, 2);
  986 + app.add_option("--str3", strs3)->expected(1, 3);
985 987 app.add_option("--str2", strs2);
986 988 args = {"--str"};
987 989  
... ... @@ -1004,6 +1006,11 @@ TEST_CASE_METHOD(TApp, &quot;emptyVectorReturn&quot;, &quot;[app]&quot;) {
1004 1006  
1005 1007 CHECK_NOTHROW(run());
1006 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 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 1017 out << "zero1=[]\n";
1018 1018 out << "zero2={}\n";
1019 1019 out << "zero3={}\n";
  1020 + out << "zero4=[\"{}\",\"\"]\n";
1020 1021 out << "nzero={}\n";
1021 1022 out << "one=[\"1\"]\n";
1022 1023 out << "two=[\"2\",\"3\"]\n";
1023 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 1028 app.add_option("--zero1", zero1)->required()->expected(0, 99)->default_str("{}");
1028 1029 app.add_option("--zero2", zero2)->required()->expected(0, 99)->default_val(std::vector<std::string>{});
1029 1030 // if no default is specified the argument results in an empty string
1030 1031 app.add_option("--zero3", zero3)->required()->expected(0, 99);
  1032 + app.add_option("--zero4", zero4)->required()->expected(0, 99);
1031 1033 app.add_option("--nzero", nzero)->required();
1032 1034 app.add_option("--one", one)->required();
1033 1035 app.add_option("--two", two)->required();
... ... @@ -1038,6 +1040,7 @@ TEST_CASE_METHOD(TApp, &quot;TOMLStringVector&quot;, &quot;[config]&quot;) {
1038 1040 CHECK(zero1 == std::vector<std::string>({}));
1039 1041 CHECK(zero2 == std::vector<std::string>({}));
1040 1042 CHECK(zero3 == std::vector<std::string>({""}));
  1043 + CHECK(zero4 == std::vector<std::string>({"{}"}));
1041 1044 CHECK(nzero == std::vector<std::string>({"{}"}));
1042 1045 CHECK(one == std::vector<std::string>({"1"}));
1043 1046 CHECK(two == std::vector<std::string>({"2", "3"}));
... ... @@ -1735,6 +1738,23 @@ TEST_CASE_METHOD(TApp, &quot;IniFlagDual&quot;, &quot;[config]&quot;) {
1735 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 1758 TEST_CASE_METHOD(TApp, "IniShort", "[config]") {
1739 1759  
1740 1760 TempFile tmpini{"TestIniTmp.ini"};
... ...
tests/HelpTest.cpp
... ... @@ -974,6 +974,16 @@ TEST_CASE(&quot;THelp: GroupOrder&quot;, &quot;[help]&quot;) {
974 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 987 TEST_CASE("THelp: ValidatorsText", "[help]") {
978 988 CLI::App app;
979 989  
... ...
tests/SetTest.cpp
... ... @@ -492,6 +492,23 @@ TEST_CASE_METHOD(TApp, &quot;FailSet&quot;, &quot;[set]&quot;) {
492 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 512 TEST_CASE_METHOD(TApp, "FailMutableSet", "[set]") {
496 513  
497 514 int choice{0};
... ...