View Javadoc
1   /*
2     Licensed to the Apache Software Foundation (ASF) under one or more
3     contributor license agreements.  See the NOTICE file distributed with
4     this work for additional information regarding copyright ownership.
5     The ASF licenses this file to You under the Apache License, Version 2.0
6     (the "License"); you may not use this file except in compliance with
7     the License.  You may obtain a copy of the License at
8   
9         https://www.apache.org/licenses/LICENSE-2.0
10  
11    Unless required by applicable law or agreed to in writing, software
12    distributed under the License is distributed on an "AS IS" BASIS,
13    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14    See the License for the specific language governing permissions and
15    limitations under the License.
16   */
17  
18  package org.apache.commons.cli;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.util.HashSet;
26  import java.util.Set;
27  import java.util.stream.Stream;
28  
29  import org.apache.commons.cli.DefaultParser.Builder;
30  import org.junit.jupiter.api.BeforeEach;
31  import org.junit.jupiter.api.Disabled;
32  import org.junit.jupiter.api.Test;
33  import org.junit.jupiter.api.extension.ExtensionContext;
34  import org.junit.jupiter.params.ParameterizedTest;
35  import org.junit.jupiter.params.provider.Arguments;
36  import org.junit.jupiter.params.provider.ArgumentsProvider;
37  import org.junit.jupiter.params.provider.ArgumentsSource;
38  
39  class DefaultParserTest extends AbstractParserTestCase {
40  
41      static class ExternalArgumentsProvider implements ArgumentsProvider {
42  
43          @Override
44          public Stream<? extends Arguments> provideArguments(final ExtensionContext context) {
45              return Stream.of(
46                      /* Arguments:
47                       * 1. test case name
48                       * 2. parser
49                       * 3. input string
50                       * 4. expected option value
51                       * 5. checked option
52                       * 6. assertion message
53                       */
54                      Arguments.of(
55                              "Long option quote handling DEFAULT behavior",
56                              DefaultParser.builder().get(),
57                              new String[]{"--bfile", "\"quoted string\""},
58                              "quoted string",
59                              "b",
60                              "Confirm --bfile=\"arg\" strips quotes"
61                      ),
62                      Arguments.of(
63                              "Long option with equals quote handling DEFAULT behavior",
64                              DefaultParser.builder().get(),
65                              new String[]{"--bfile=\"quoted string\""},
66                              "\"quoted string\"",
67                              "b",
68                              "Confirm --bfile=\"arg\" keeps quotes"
69                      ),
70                      Arguments.of(
71                              "Short option quote handling DEFAULT behavior",
72                              DefaultParser.builder().get(),
73                              new String[]{"-b", "\"quoted string\""},
74                              "quoted string",
75                              "b",
76                              "Confirm -b\"arg\" strips quotes"
77                      ),
78                      Arguments.of(
79                              "Short option concatenated quote handling DEFAULT behavior",
80                              DefaultParser.builder().get(),
81                              new String[]{"-b\"quoted string\""},
82                              "\"quoted string\"",
83                              "b",
84                              "Confirm -b\"arg\" keeps quotes"
85                      ),
86                      Arguments.of(
87                              "Long option quote handling WITHOUT strip",
88                              DefaultParser.builder().setStripLeadingAndTrailingQuotes(false).get(),
89                              new String[]{"--bfile", "\"quoted string\""},
90                              "\"quoted string\"",
91                              "b",
92                              "Confirm --bfile \"arg\" keeps quotes"
93                      ),
94                      Arguments.of(
95                              "Long option with equals quote handling WITHOUT strip",
96                              DefaultParser.builder().setStripLeadingAndTrailingQuotes(false).get(),
97                              new String[]{"--bfile=\"quoted string\""},
98                              "\"quoted string\"",
99                              "b",
100                             "Confirm --bfile=\"arg\" keeps quotes"
101                     ),
102                     Arguments.of(
103                             "Short option quote handling WITHOUT strip",
104                             DefaultParser.builder().setStripLeadingAndTrailingQuotes(false).get(),
105                             new String[]{"-b", "\"quoted string\""},
106                             "\"quoted string\"",
107                             "b",
108                             "Confirm -b\"arg\" keeps quotes"
109                     ),
110                     Arguments.of(
111                             "Short option concatenated quote handling WITHOUT strip",
112                             DefaultParser.builder().setStripLeadingAndTrailingQuotes(false).get(),
113                             new String[]{"-b\"quoted string\""},
114                             "\"quoted string\"",
115                             "b",
116                             "Confirm -b\"arg\" keeps quotes"
117                     ),
118                     Arguments.of(
119                             "Long option quote handling WITH strip",
120                             DefaultParser.builder().setStripLeadingAndTrailingQuotes(true).get(),
121                             new String[]{"--bfile", "\"quoted string\""},
122                             "quoted string",
123                             "b",
124                             "Confirm --bfile \"arg\" strips quotes"
125                     ),
126                     Arguments.of(
127                             "Long option With Equals Quote Handling WITH Strip",
128                             DefaultParser.builder().setStripLeadingAndTrailingQuotes(true).get(),
129                             new String[]{"--bfile=\"quoted string\""},
130                             "quoted string",
131                             "b",
132                             "Confirm --bfile=\"arg\" strips quotes"
133                     ),
134                     Arguments.of(
135                             "Short option quote handling WITH strip",
136                             DefaultParser.builder().setStripLeadingAndTrailingQuotes(true).get(),
137                             new String[]{"-b", "\"quoted string\""},
138                             "quoted string",
139                             "b",
140                             "Confirm -b \"arg\" strips quotes"
141                     ),
142                     Arguments.of(
143                             "Short option concatenated quote handling WITH strip",
144                             DefaultParser.builder().setStripLeadingAndTrailingQuotes(true).get(),
145                             new String[]{"-b\"quoted string\""},
146                             "quoted string",
147                             "b",
148                             "Confirm -b\"arg\" strips quotes"
149                     )
150             );
151         }
152     }
153 
154     @Override
155     @BeforeEach
156     public void setUp() {
157         super.setUp();
158         parser = new DefaultParser();
159     }
160 
161     @Test
162     void testBuilder() {
163         // @formatter:off
164         final Builder builder = DefaultParser.builder()
165                 .setStripLeadingAndTrailingQuotes(false)
166                 .setAllowPartialMatching(false)
167                 .setDeprecatedHandler(null);
168         // @formatter:on
169         parser = builder.build();
170         assertEquals(DefaultParser.class, parser.getClass());
171         parser = builder.get();
172         assertEquals(DefaultParser.class, parser.getClass());
173     }
174 
175     @Test
176     void testDeprecated() throws ParseException {
177         final Set<Option> handler = new HashSet<>();
178         parser = DefaultParser.builder().setDeprecatedHandler(handler::add).build();
179         final Option opt1 = Option.builder().option("d1").deprecated().get();
180         // @formatter:off
181         final Option opt2 = Option.builder().option("d2").deprecated(DeprecatedAttributes.builder()
182         .setForRemoval(true)
183         .setSince("1.0")
184         .setDescription("Do this instead.").get()).get();
185         // @formatter:on
186         final Option opt3 = Option.builder().option("a").get();
187         // @formatter:off
188         final CommandLine cl = parser.parse(new Options()
189                 .addOption(opt1)
190                 .addOption(opt2)
191                 .addOption(opt3),
192                 new String[] {"-d1", "-d2", "-a"});
193         // @formatter:on
194         // Trigger handler:
195         assertTrue(cl.hasOption(opt1.getOpt()));
196         assertTrue(cl.hasOption(opt2.getOpt()));
197         assertTrue(cl.hasOption(opt3.getOpt()));
198         // Assert handler was triggered
199         assertTrue(handler.contains(opt1));
200         assertTrue(handler.contains(opt2));
201         assertFalse(handler.contains(opt3));
202     }
203 
204     @Test
205     void testLegacyStopAtNonOption() throws ParseException {
206         final Option a = Option.builder().option("a").longOpt("first-letter").get();
207         final Option b = Option.builder().option("b").longOpt("second-letter").get();
208         final Option c = Option.builder().option("c").longOpt("third-letter").get();
209 
210         final Options options = new Options();
211         options.addOption(a);
212         options.addOption(b);
213         options.addOption(c);
214 
215         final String[] args = {"-a", "-b", "-c", "-d", "arg1", "arg2"}; // -d is rogue option
216 
217         final DefaultParser parser = new DefaultParser();
218 
219         final CommandLine commandLine = parser.parse(options, args, null, true);
220         assertEquals(3, commandLine.getOptions().length);
221         assertEquals(3, commandLine.getArgs().length);
222         assertTrue(commandLine.getArgList().contains("-d"));
223         assertTrue(commandLine.getArgList().contains("arg1"));
224         assertTrue(commandLine.getArgList().contains("arg2"));
225 
226         final UnrecognizedOptionException e = assertThrows(UnrecognizedOptionException.class, () -> parser.parse(options, args, null, false));
227         assertTrue(e.getMessage().contains("-d"));
228     }
229 
230     @Override
231     @Test
232     @Disabled("Test case handled in the parameterized tests as \"DEFAULT behavior\"")
233     void testLongOptionWithEqualsQuoteHandling() throws Exception {
234     }
235 
236     @ParameterizedTest(name = "{index}. {0}")
237     @ArgumentsSource(ExternalArgumentsProvider.class)
238     void testParameterized(final String testName, final CommandLineParser parser, final String[] args, final String expected,
239         final String option, final String message) throws Exception {
240         final CommandLine cl = parser.parse(options, args);
241 
242         assertEquals(expected, cl.getOptionValue(option), message);
243     }
244 
245     @Test
246     void testParseIgnoreHappyPath() throws ParseException {
247         final Option a = Option.builder().option("a").longOpt("first-letter").get();
248         final Option b = Option.builder().option("b").longOpt("second-letter").get();
249         final Option c = Option.builder().option("c").longOpt("third-letter").get();
250         final Option d = Option.builder().option("d").longOpt("fourth-letter").get();
251 
252         final Options baseOptions = new Options();
253         baseOptions.addOption(a);
254         baseOptions.addOption(b);
255         final Options specificOptions = new Options();
256         specificOptions.addOption(a);
257         specificOptions.addOption(b);
258         specificOptions.addOption(c);
259         specificOptions.addOption(d);
260 
261         final String[] args = {"-a", "-b", "-c", "-d", "arg1", "arg2"};
262 
263         final DefaultParser parser = new DefaultParser();
264 
265         final CommandLine baseCommandLine = parser.parse(baseOptions, null, DefaultParser.NonOptionAction.IGNORE, args);
266         assertEquals(2, baseCommandLine.getOptions().length);
267         assertEquals(2, baseCommandLine.getArgs().length);
268         assertTrue(baseCommandLine.hasOption("a"));
269         assertTrue(baseCommandLine.hasOption("b"));
270         assertFalse(baseCommandLine.hasOption("c"));
271         assertFalse(baseCommandLine.hasOption("d"));
272         assertFalse(baseCommandLine.getArgList().contains("-a"));
273         assertFalse(baseCommandLine.getArgList().contains("-b"));
274         assertFalse(baseCommandLine.getArgList().contains("-c"));
275         assertFalse(baseCommandLine.getArgList().contains("-d"));
276         assertTrue(baseCommandLine.getArgList().contains("arg1"));
277         assertTrue(baseCommandLine.getArgList().contains("arg2"));
278 
279         final CommandLine specificCommandLine = parser.parse(specificOptions, null, DefaultParser.NonOptionAction.THROW, args);
280         assertEquals(4, specificCommandLine.getOptions().length);
281         assertEquals(2, specificCommandLine.getArgs().length);
282         assertTrue(specificCommandLine.hasOption("a"));
283         assertTrue(specificCommandLine.hasOption("b"));
284         assertTrue(specificCommandLine.hasOption("c"));
285         assertTrue(specificCommandLine.hasOption("d"));
286         assertFalse(specificCommandLine.getArgList().contains("-a"));
287         assertFalse(specificCommandLine.getArgList().contains("-b"));
288         assertFalse(specificCommandLine.getArgList().contains("-c"));
289         assertFalse(specificCommandLine.getArgList().contains("-d"));
290         assertTrue(specificCommandLine.getArgList().contains("arg1"));
291         assertTrue(specificCommandLine.getArgList().contains("arg2"));
292     }
293 
294     @Test
295     void testParseIgnoreNonHappyPath() throws ParseException {
296         final Option a = Option.builder().option("a").longOpt("first-letter").get();
297         final Option b = Option.builder().option("b").longOpt("second-letter").get();
298         final Option c = Option.builder().option("c").longOpt("third-letter").get();
299 
300         final Options baseOptions = new Options();
301         baseOptions.addOption(a);
302         baseOptions.addOption(b);
303         final Options specificOptions = new Options();
304         specificOptions.addOption(a);
305         specificOptions.addOption(b);
306         specificOptions.addOption(c);
307 
308         final String[] args = {"-a", "-b", "-c", "-d", "arg1", "arg2"}; // -d is rogue option
309 
310         final DefaultParser parser = new DefaultParser();
311 
312         final CommandLine baseCommandLine = parser.parse(baseOptions, null, DefaultParser.NonOptionAction.IGNORE, args);
313         assertEquals(2, baseCommandLine.getOptions().length);
314         assertEquals(2, baseCommandLine.getArgs().length);
315 
316         final UnrecognizedOptionException e = assertThrows(UnrecognizedOptionException.class,
317                 () -> parser.parse(specificOptions, null, DefaultParser.NonOptionAction.THROW, args));
318         assertTrue(e.getMessage().contains("-d"));
319     }
320 
321     @Test
322     void testParseNullOption() throws ParseException {
323         // Edge case
324         assertThrows(NullPointerException.class, () -> new DefaultParser().parse(null, null, DefaultParser.NonOptionAction.IGNORE, "-a"));
325     }
326 
327     @Test
328     void testParseSkipHappyPath() throws ParseException {
329         final Option a = Option.builder().option("a").longOpt("first-letter").get();
330         final Option b = Option.builder().option("b").longOpt("second-letter").get();
331         final Option c = Option.builder().option("c").longOpt("third-letter").get();
332         final Option d = Option.builder().option("d").longOpt("fourth-letter").get();
333 
334         final Options baseOptions = new Options();
335         baseOptions.addOption(a);
336         baseOptions.addOption(b);
337         final Options specificOptions = new Options();
338         specificOptions.addOption(a);
339         specificOptions.addOption(b);
340         specificOptions.addOption(c);
341         specificOptions.addOption(d);
342 
343         final String[] args = {"-a", "-b", "-c", "-d", "arg1", "arg2"};
344 
345         final DefaultParser parser = new DefaultParser();
346 
347         final CommandLine baseCommandLine = parser.parse(baseOptions, null, DefaultParser.NonOptionAction.SKIP, args);
348         assertEquals(2, baseCommandLine.getOptions().length);
349         assertEquals(4, baseCommandLine.getArgs().length);
350         assertTrue(baseCommandLine.hasOption("a"));
351         assertTrue(baseCommandLine.hasOption("b"));
352         assertFalse(baseCommandLine.hasOption("c"));
353         assertFalse(baseCommandLine.hasOption("d"));
354         assertFalse(baseCommandLine.getArgList().contains("-a"));
355         assertFalse(baseCommandLine.getArgList().contains("-b"));
356         assertTrue(baseCommandLine.getArgList().contains("-c"));
357         assertTrue(baseCommandLine.getArgList().contains("-d"));
358         assertTrue(baseCommandLine.getArgList().contains("arg1"));
359         assertTrue(baseCommandLine.getArgList().contains("arg2"));
360 
361         final CommandLine specificCommandLine = parser.parse(specificOptions, null, DefaultParser.NonOptionAction.THROW, args);
362         assertEquals(4, specificCommandLine.getOptions().length);
363         assertEquals(2, specificCommandLine.getArgs().length);
364         assertTrue(specificCommandLine.hasOption("a"));
365         assertTrue(specificCommandLine.hasOption("b"));
366         assertTrue(specificCommandLine.hasOption("c"));
367         assertTrue(specificCommandLine.hasOption("d"));
368         assertFalse(specificCommandLine.getArgList().contains("-a"));
369         assertFalse(specificCommandLine.getArgList().contains("-b"));
370         assertFalse(specificCommandLine.getArgList().contains("-c"));
371         assertFalse(specificCommandLine.getArgList().contains("-d"));
372         assertTrue(specificCommandLine.getArgList().contains("arg1"));
373         assertTrue(specificCommandLine.getArgList().contains("arg2"));
374     }
375 
376     @Test
377     void testParseSkipNonHappyPath() throws ParseException {
378         final Option a = Option.builder().option("a").longOpt("first-letter").get();
379         final Option b = Option.builder().option("b").longOpt("second-letter").get();
380         final Option c = Option.builder().option("c").longOpt("third-letter").get();
381 
382         final Options baseOptions = new Options();
383         baseOptions.addOption(a);
384         baseOptions.addOption(b);
385         final Options specificOptions = new Options();
386         specificOptions.addOption(a);
387         specificOptions.addOption(b);
388         specificOptions.addOption(c);
389 
390         final String[] args = {"-a", "-b", "-c", "-d", "arg1", "arg2"}; // -d is rogue option
391 
392         final DefaultParser parser = new DefaultParser();
393 
394         final CommandLine baseCommandLine = parser.parse(baseOptions, null, DefaultParser.NonOptionAction.SKIP, args);
395         assertEquals(2, baseCommandLine.getOptions().length);
396         assertEquals(4, baseCommandLine.getArgs().length);
397 
398         final UnrecognizedOptionException e = assertThrows(UnrecognizedOptionException.class,
399                 () -> parser.parse(specificOptions, null, DefaultParser.NonOptionAction.THROW, args));
400         assertTrue(e.getMessage().contains("-d"));
401     }
402 
403     @Override
404     @Test
405     @Disabled("Test case handled in the parameterized tests as \"DEFAULT behavior\"")
406     void testShortOptionConcatenatedQuoteHandling() throws Exception {
407     }
408 }