This is an automated email from the ASF dual-hosted git repository.
dmeden pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new ba17b02fef ArgParser: apply default values after dependency validation
(#12934)
ba17b02fef is described below
commit ba17b02fef0661088e58c5af1c26bb3a9ea60679
Author: Damian Meden <[email protected]>
AuthorDate: Thu Mar 5 14:31:01 2026 +0100
ArgParser: apply default values after dependency validation (#12934)
validate_dependencies() incorrectly triggers on options that have
default values but were not explicitly passed by the user.
The root cause is that append_option_data() populates default values
into the Arguments map before validate_dependencies() runs. When
validate_dependencies() calls ret.get(key) for an option with a
default, the lookup finds the entry and sets _is_called = true,
making the option appear "used" even though the user never specified
it on the command line.
Fix by extracting the default-value loop into apply_option_defaults()
and calling it after validate_dependencies() in parse().
---
include/tscore/ArgParser.h | 2 +
src/tscore/ArgParser.cc | 14 ++++++-
src/tscore/unit_tests/test_ArgParser.cc | 69 +++++++++++++++++++++++++++++++++
3 files changed, 84 insertions(+), 1 deletion(-)
diff --git a/include/tscore/ArgParser.h b/include/tscore/ArgParser.h
index a4bd5916fe..fdb6086eba 100644
--- a/include/tscore/ArgParser.h
+++ b/include/tscore/ArgParser.h
@@ -226,6 +226,8 @@ public:
void validate_mutex_groups(Arguments &ret) const;
// Helper method to validate option dependencies
void validate_dependencies(Arguments &ret) const;
+ // Helper method to apply default values for options not explicitly set
+ void apply_option_defaults(Arguments &ret) const;
// The command name and help message
std::string _name;
std::string _description;
diff --git a/src/tscore/ArgParser.cc b/src/tscore/ArgParser.cc
index 65298317a6..90c3c2f5e4 100644
--- a/src/tscore/ArgParser.cc
+++ b/src/tscore/ArgParser.cc
@@ -725,7 +725,14 @@ ArgParser::Command::append_option_data(Arguments &ret,
AP_StrVec &args, int inde
help_message(std::to_string(_option_list.at(it.first).arg_num) + "
arguments expected by " + it.first);
}
}
- // put in the default value of options
+}
+
+// Apply default values for options not explicitly set by the user.
+// This must be called AFTER validate_dependencies() so that default values
+// (e.g. --timeout "0") don't falsely trigger dependency checks.
+void
+ArgParser::Command::apply_option_defaults(Arguments &ret) const
+{
for (const auto &it : _option_list) {
if (!it.second.default_value.empty() && ret.get(it.second.key).empty()) {
std::istringstream ss(it.second.default_value);
@@ -771,6 +778,11 @@ ArgParser::Command::parse(Arguments &ret, AP_StrVec &args)
// Validate option dependencies
validate_dependencies(ret);
+
+ // Apply default values after validation so that defaults don't
+ // trigger dependency checks (e.g. --timeout with default "0"
+ // should not require --monitor when not explicitly used).
+ apply_option_defaults(ret);
}
if (command_called) {
diff --git a/src/tscore/unit_tests/test_ArgParser.cc
b/src/tscore/unit_tests/test_ArgParser.cc
index 672a702a28..0a32502e96 100644
--- a/src/tscore/unit_tests/test_ArgParser.cc
+++ b/src/tscore/unit_tests/test_ArgParser.cc
@@ -148,3 +148,72 @@ TEST_CASE("Invoke test", "[invoke]")
parsed_data.invoke();
REQUIRE(global == 2);
}
+
+TEST_CASE("Case sensitive short options", "[parse]")
+{
+ ts::ArgParser cs_parser;
+ cs_parser.add_global_usage("test_prog [--SWITCH]");
+
+ // Add a command with two options that differ only in case: -t and -T
+ ts::ArgParser::Command &cmd = cs_parser.add_command("process", "process
data");
+ cmd.add_option("--tag", "-t", "a label", "", 1, "");
+ cmd.add_option("--threshold", "-T", "a numeric value", "", 1, "100");
+
+ ts::Arguments parsed;
+
+ // Use lowercase -t: should set "tag" only
+ const char *argv1[] = {"test_prog", "process", "-t", "my_tag", nullptr};
+ parsed = cs_parser.parse(argv1);
+ REQUIRE(parsed.get("tag") == true);
+ REQUIRE(parsed.get("tag").value() == "my_tag");
+ // threshold should still have its default
+ REQUIRE(parsed.get("threshold").value() == "100");
+
+ // Use uppercase -T: should set "threshold" only
+ const char *argv2[] = {"test_prog", "process", "-T", "200", nullptr};
+ parsed = cs_parser.parse(argv2);
+ REQUIRE(parsed.get("threshold") == true);
+ REQUIRE(parsed.get("threshold").value() == "200");
+ // tag should be empty (no default)
+ REQUIRE(parsed.get("tag").value() == "");
+
+ // Use both -t and -T together
+ const char *argv3[] = {"test_prog", "process", "-t", "foo", "-T", "500",
nullptr};
+ parsed = cs_parser.parse(argv3);
+ REQUIRE(parsed.get("tag") == true);
+ REQUIRE(parsed.get("tag").value() == "foo");
+ REQUIRE(parsed.get("threshold") == true);
+ REQUIRE(parsed.get("threshold").value() == "500");
+}
+
+TEST_CASE("with_required does not trigger on default values", "[parse]")
+{
+ ts::ArgParser parser;
+ parser.add_global_usage("test_prog [OPTIONS]");
+
+ ts::ArgParser::Command &cmd = parser.add_command("scan", "scan targets");
+ cmd.add_option("--tag", "-t", "a label", "", 1, "");
+ cmd.add_option("--verbose", "-v", "enable verbose output");
+ cmd.add_option("--threshold", "-T", "a numeric value", "", 1,
"100").with_required("--verbose");
+
+ // -t alone should NOT trigger --threshold's dependency on --verbose.
+ // The default value "100" for --threshold must not count as "explicitly
used".
+ const char *argv1[] = {"test_prog", "scan", "-t", "my_tag", nullptr};
+ ts::Arguments parsed = parser.parse(argv1);
+ REQUIRE(parsed.get("tag").value() == "my_tag");
+ // threshold default should still be applied after validation
+ REQUIRE(parsed.get("threshold").value() == "100");
+
+ // -T with -v should work fine
+ const char *argv2[] = {"test_prog", "scan", "-T", "200", "-v", nullptr};
+ parsed = parser.parse(argv2);
+ REQUIRE(parsed.get("threshold").value() == "200");
+ REQUIRE(parsed.get("verbose") == true);
+
+ // -t and -T together with -v should work
+ const char *argv3[] = {"test_prog", "scan", "-t", "foo", "-T", "300", "-v",
nullptr};
+ parsed = parser.parse(argv3);
+ REQUIRE(parsed.get("tag").value() == "foo");
+ REQUIRE(parsed.get("threshold").value() == "300");
+ REQUIRE(parsed.get("verbose") == true);
+}