[
https://issues.apache.org/jira/browse/CLI-346?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Ruiqi Dong updated CLI-346:
---------------------------
Affects Version/s: 1.10.0
> Options.addOptionGroup() silently overwrites existing options while
> addOptions() throws exception for duplicates
> ----------------------------------------------------------------------------------------------------------------
>
> Key: CLI-346
> URL: https://issues.apache.org/jira/browse/CLI-346
> Project: Commons CLI
> Issue Type: Bug
> Components: Options definition
> Affects Versions: 1.10.0
> Reporter: Ruiqi Dong
> Priority: Major
> Original Estimate: 0.5h
> Remaining Estimate: 0.5h
>
> The {{Options.addOptionGroup()}} method silently overwrites existing options
> when adding an {{OptionGroup}} that contains options with keys already
> present in the {{Options}} instance. This behavior is inconsistent with
> {{{}Options.addOptions(){}}}, which throws an {{IllegalArgumentException}}
> when attempting to add duplicate options. This inconsistency violates the
> principle of least surprise and can lead to subtle bugs where previously
> configured options are unintentionally overwritten without any warning.
>
> {*}Impact{*}:
> * Can cause unexpected behavior in applications using Commons CLI
> * Previously required options may become optional without warning
> * Configuration may be silently lost
> * Debugging such issues is difficult due to lack of error messages
>
> *Test Case:*
>
> {code:java}
> @Test
> void testAddOptionGroupShouldThrowExceptionForDuplicateOption() {
> // Arrange
> Options options = new Options();
>
> // Add an existing option
> Option existingOption = new Option("f", "file", true, "Input file");
> existingOption.setRequired(true);
> options.addOption(existingOption);
>
> // Create an OptionGroup with the same option key
> OptionGroup group = new OptionGroup();
> Option duplicateOption = new Option("f", "file", true, "Config file");
> group.addOption(duplicateOption);
>
> // Act & Assert
> // This test expects addOptionGroup to throw exception like addOptions
> does
> // BUT IT FAILS - demonstrating the bug
> IllegalArgumentException exception = assertThrows(
> IllegalArgumentException.class,
> () -> options.addOptionGroup(group),
> "addOptionGroup should throw IllegalArgumentException for duplicate
> option key, " +
> "consistent with addOptions() behavior"
> );
>
> // This assertion would verify the exception message (if exception was
> thrown)
> assertTrue(exception.getMessage().contains("f") ||
> exception.getMessage().contains("duplicate") ||
> exception.getMessage().contains("Duplicate"),
> "Exception message should mention the duplicate key");
> }{code}
> {*}Test Failure Output{*}{*}:{*}
> {code:java}
> [ERROR]
> org.apache.commons.cli.OptionsTest.testAddOptionGroupShouldThrowExceptionForDuplicateOption
> -- Time elapsed: 0.001 s <<< FAILURE!
> org.opentest4j.AssertionFailedError: addOptionGroup should throw
> IllegalArgumentException for duplicate option key, consistent with
> addOptions() behavior ==> Expected java.lang.IllegalArgumentException to be
> thrown, but nothing was thrown.
> at
> org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:152)
> at
> org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:73)
> at
> org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:39)
> at org.junit.jupiter.api.Assertions.assertThrows(Assertions.java:3153)
> at
> org.apache.commons.cli.OptionsTest.testAddOptionGroupShouldThrowExceptionForDuplicateOption(OptionsTest.java:46)
> at java.lang.reflect.Method.invoke(Method.java:498)
> at java.util.ArrayList.forEach(ArrayList.java:1259)
> at java.util.ArrayList.forEach(ArrayList.java:1259) {code}
>
> {*}Suggested Fix{*}:
> Modify {{addOptionGroup()}} to check for existing options before adding,
> making it consistent with {{{}addOptions(){}}}:
> {code:java}
> public Options addOptionGroup(final OptionGroup group) {
> // Check for duplicates first - consistent with addOptions() behavior
> for (final Option option : group.getOptions()) {
> if (hasOption(option.getKey())) {
> throw new IllegalArgumentException("Option '" + option.getKey() +
> "' already exists");
> }
> }
>
> // Existing logic
> if (group.isRequired()) {
> requiredOpts.add(group);
> }
> for (final Option option : group.getOptions()) {
> option.setRequired(false);
> addOption(option);
> optionGroups.put(option.getKey(), group);
> }
> return this;
> } {code}
--
This message was sent by Atlassian Jira
(v8.20.10#820010)