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.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.mockito.Mockito.spy;
26  import static org.mockito.Mockito.when;
27  
28  import java.io.ByteArrayOutputStream;
29  import java.io.IOException;
30  import java.io.OutputStreamWriter;
31  import java.io.PrintWriter;
32  import java.io.StringWriter;
33  import java.io.UncheckedIOException;
34  import java.util.ArrayList;
35  import java.util.List;
36  import java.util.stream.Stream;
37  
38  import org.apache.commons.cli.HelpFormatter.Builder;
39  import org.junit.jupiter.api.Test;
40  import org.junit.jupiter.params.ParameterizedTest;
41  import org.junit.jupiter.params.provider.Arguments;
42  import org.junit.jupiter.params.provider.MethodSource;
43  import org.junit.jupiter.params.provider.ValueSource;
44  
45  /**
46   * Test case for the HelpFormatter class.
47   */
48  class HelpFormatterTest {
49  
50      private static final String EOL = System.lineSeparator();
51  
52      static Stream<Arguments> deprecatedOptionsProvider() {
53          final List<Arguments> lst = new ArrayList<>();
54          Option option = Option.builder("a").longOpt("aaa").desc("dddd dddd dddd")
55          .deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("now")
56                  .setDescription("Why why why").get()).get();
57  
58          HelpFormatter hf = HelpFormatter.builder().get();
59          lst.add(Arguments.of(hf, option, "[Deprecated] dddd dddd dddd"));
60  
61          hf = HelpFormatter.builder().setShowDeprecated(false).get();
62          lst.add(Arguments.of(hf, option, "dddd dddd dddd"));
63  
64          hf = HelpFormatter.builder().setShowDeprecated(true).get();
65          lst.add(Arguments.of(hf, option, "[Deprecated] dddd dddd dddd"));
66  
67          hf = HelpFormatter.builder().setShowDeprecated(o -> String.format("%s [%s]", HelpFormatter.getDescription(o), o.getDeprecated())).get();
68          lst.add(Arguments.of(hf, option, "dddd dddd dddd [Deprecated for removal since now: Why why why]"));
69  
70          option = Option.builder("a").longOpt("aaa")
71          .deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("now")
72                  .setDescription("Why why why").get()).get();
73  
74          hf = HelpFormatter.builder().get();
75          lst.add(Arguments.of(hf, option, "[Deprecated]"));
76  
77          hf = HelpFormatter.builder().setShowDeprecated(false).get();
78          lst.add(Arguments.of(hf, option, ""));
79  
80          hf = HelpFormatter.builder().setShowDeprecated(true).get();
81          lst.add(Arguments.of(hf, option, "[Deprecated]"));
82  
83          hf = HelpFormatter.builder().setShowDeprecated(o -> String.format("%s [%s]", HelpFormatter.getDescription(o), o.getDeprecated())).get();
84          lst.add(Arguments.of(hf, option, "[Deprecated for removal since now: Why why why]"));
85  
86          return lst.stream();
87      }
88  
89      @Test
90      void testAccessors() {
91          final HelpFormatter formatter = new HelpFormatter();
92  
93          formatter.setArgName("argname");
94          assertEquals("argname", formatter.getArgName(), "arg name");
95  
96          formatter.setDescPadding(3);
97          assertEquals(3, formatter.getDescPadding(), "desc padding");
98  
99          formatter.setLeftPadding(7);
100         assertEquals(7, formatter.getLeftPadding(), "left padding");
101 
102         formatter.setLongOptPrefix("~~");
103         assertEquals("~~", formatter.getLongOptPrefix(), "long opt prefix");
104 
105         formatter.setNewLine("\n");
106         assertEquals("\n", formatter.getNewLine(), "new line");
107 
108         formatter.setOptPrefix("~");
109         assertEquals("~", formatter.getOptPrefix(), "opt prefix");
110 
111         formatter.setSyntaxPrefix("-> ");
112         assertEquals("-> ", formatter.getSyntaxPrefix(), "syntax prefix");
113 
114         formatter.setWidth(80);
115         assertEquals(80, formatter.getWidth(), "width");
116     }
117 
118     @Test
119     void testAutomaticUsage() {
120         final HelpFormatter hf = new HelpFormatter();
121         Options options;
122         String expected = "usage: app [-a]";
123         final ByteArrayOutputStream out = new ByteArrayOutputStream();
124         final PrintWriter pw = new PrintWriter(out);
125 
126         options = new Options().addOption("a", false, "aaaa aaaa aaaa aaaa aaaa");
127         hf.printUsage(pw, 60, "app", options);
128         pw.flush();
129         assertEquals(expected, out.toString().trim(), "simple auto usage");
130         out.reset();
131 
132         expected = "usage: app [-a] [-b]";
133         options = new Options().addOption("a", false, "aaaa aaaa aaaa aaaa aaaa").addOption("b", false, "bbb");
134         hf.printUsage(pw, 60, "app", options);
135         pw.flush();
136         assertEquals(expected, out.toString().trim(), "simple auto usage");
137         out.reset();
138     }
139 
140     @Test
141     void testDefaultArgName() {
142         final Option option = Option.builder("f").hasArg().required(true).get();
143 
144         final Options options = new Options();
145         options.addOption(option);
146 
147         final StringWriter out = new StringWriter();
148 
149         final HelpFormatter formatter = new HelpFormatter();
150         formatter.setArgName("argument");
151         formatter.printUsage(new PrintWriter(out), 80, "app", options);
152 
153         assertEquals("usage: app -f <argument>" + EOL, out.toString());
154     }
155 
156     @ParameterizedTest
157     @ValueSource(ints = { -100, -1, 0 })
158     void testDeprecatedFindWrapPosZeroWidth(final int width) {
159         final int pos = new HelpFormatter().findWrapPos("Hello World", width, 0);
160         assertEquals(width, pos);
161     }
162 
163     @ParameterizedTest
164     @ValueSource(ints = { -100, -1, 0 })
165     void testDeprecatedPrintOptionsZeroWidth(final int width) {
166         final Options options = new Options();
167         options.addOption("h", "help", false, "Show help");
168         final StringWriter out = new StringWriter();
169         final PrintWriter pw = new PrintWriter(out);
170         new HelpFormatter().printOptions(pw, width, options, 1, 3);
171         final String result = out.toString();
172         assertNotNull(result);
173     }
174 
175     @Test
176     void testFindWrapPos() {
177         final HelpFormatter hf = new HelpFormatter();
178 
179         String text = "This is a test.";
180         // text width should be max 8; the wrap position is 7
181         assertEquals(7, hf.findWrapPos(text, 8, 0), "wrap position");
182 
183         // starting from 8 must give -1 - the wrap pos is after end
184         assertEquals(-1, hf.findWrapPos(text, 8, 8), "wrap position 2");
185 
186         // words longer than the width are cut
187         text = "aaaa aa";
188         assertEquals(3, hf.findWrapPos(text, 3, 0), "wrap position 3");
189 
190         // last word length is equal to the width
191         text = "aaaaaa aaaaaa";
192         assertEquals(6, hf.findWrapPos(text, 6, 0), "wrap position 4");
193         assertEquals(-1, hf.findWrapPos(text, 6, 7), "wrap position 4");
194 
195         text = "aaaaaa\n aaaaaa";
196         assertEquals(7, hf.findWrapPos(text, 6, 0), "wrap position 5");
197 
198         text = "aaaaaa\t aaaaaa";
199         assertEquals(7, hf.findWrapPos(text, 6, 0), "wrap position 6");
200     }
201 
202     @Test
203     void testHeaderStartingWithLineSeparator0() {
204         // related to Bugzilla #21215
205         final Options options = new Options();
206         final HelpFormatter formatter = new HelpFormatter();
207         final String header = EOL + "Header";
208         final String footer = "Footer";
209         final StringWriter out = new StringWriter();
210         formatter.printHelp(new PrintWriter(out), 80, "foobar", header, options, 2, 2, footer, true);
211         //@formatter:off
212         assertEquals(
213                 "usage: foobar" + EOL +
214                 EOL +
215                 "Header" + EOL +
216                 EOL +
217                 "Footer" + EOL,
218                 out.toString());
219         //@formatter:on
220     }
221 
222     @Test
223     void testHeaderStartingWithLineSeparator1() {
224         // related to Bugzilla #21215
225         final Options options = new Options();
226         final String header = EOL + "Header";
227         final String footer = "Footer";
228         final Builder builder = HelpFormatter.builder();
229         StringWriter out = new StringWriter();
230         builder.setPrintWriter(new PrintWriter(out)).get().printHelp(new PrintWriter(out), 80, "foobar", header, options, 2, 2, footer, true);
231         //@formatter:off
232         assertEquals(
233                 "usage: foobar" + EOL +
234                 "" + EOL +
235                 "Header" + EOL +
236                 "" + EOL +
237                 "Footer" + EOL,
238                 out.toString());
239         //@formatter:on
240         out = new StringWriter();
241         builder.setPrintWriter(new PrintWriter(out)).get().printHelp("foobar", header, options, footer);
242         //@formatter:off
243         assertEquals(
244                 "usage: foobar" + EOL +
245                 EOL +
246                 "Header" + EOL +
247                 EOL +
248                 "Footer" + EOL,
249                 out.toString());
250         //@formatter:on
251         out = new StringWriter();
252         builder.setPrintWriter(new PrintWriter(out)).get().printHelp(80, "foobar", header, options, footer);
253         //@formatter:off
254         assertEquals(
255                 "usage: foobar" + EOL +
256                 EOL +
257                 "Header" + EOL +
258                 EOL +
259                 "Footer" + EOL,
260                 out.toString());
261         //@formatter:on
262         out = new StringWriter();
263         builder.setPrintWriter(new PrintWriter(out)).get().printHelp("foobar", header, options, footer, false);
264         //@formatter:off
265         assertEquals(
266                 "usage: foobar" + EOL +
267                 EOL +
268                 "Header" + EOL +
269                 EOL +
270                 "Footer" + EOL,
271                 out.toString());
272         //@formatter:on
273         out = new StringWriter();
274         builder.setPrintWriter(new PrintWriter(out)).get().printHelp("foobar", header, options, footer, true);
275         //@formatter:off
276         assertEquals(
277                 "usage: foobar" + EOL +
278                 EOL +
279                 "Header" + EOL +
280                 EOL +
281                 "Footer" + EOL,
282                 out.toString());
283         //@formatter:on
284         out = new StringWriter();
285         builder.setPrintWriter(new PrintWriter(out)).get().printHelp("foobar", options, false);
286         //@formatter:off
287         assertEquals(
288                 "usage: foobar" + EOL +
289                 "" + EOL,
290                 out.toString());
291         //@formatter:on
292     }
293 
294     @Test
295     void testHelpWithLongOptSeparator() {
296         final Options options = new Options();
297         options.addOption("f", true, "the file");
298         options.addOption(Option.builder("s").longOpt("size").desc("the size").hasArg().argName("SIZE").get());
299         options.addOption(Option.builder().longOpt("age").desc("the age").hasArg().get());
300 
301         final HelpFormatter formatter = new HelpFormatter();
302         assertEquals(HelpFormatter.DEFAULT_LONG_OPT_SEPARATOR, formatter.getLongOptSeparator());
303         formatter.setLongOptSeparator("=");
304         assertEquals("=", formatter.getLongOptSeparator());
305 
306         final StringWriter out = new StringWriter();
307 
308         formatter.printHelp(new PrintWriter(out), 80, "create", "header", options, 2, 2, "footer");
309 
310         //@formatter:off
311         assertEquals(
312                 "usage: create" + EOL +
313                 "header" + EOL +
314                 "     --age=<arg>    the age" + EOL +
315                 "  -f <arg>          the file" + EOL +
316                 "  -s,--size=<SIZE>  the size" + EOL +
317                 "footer" + EOL,
318                 out.toString());
319         //@formatter:on
320     }
321 
322     @Test
323     void testIndentedHeaderAndFooter() {
324         // related to CLI-207
325         final Options options = new Options();
326         final HelpFormatter formatter = new HelpFormatter();
327         final String header = "  Header1\n  Header2";
328         final String footer = "  Footer1\n  Footer2";
329         final StringWriter out = new StringWriter();
330         formatter.printHelp(new PrintWriter(out), 80, "foobar", header, options, 2, 2, footer, true);
331         //@formatter:off
332         assertEquals(
333                 "usage: foobar" + EOL +
334                 "  Header1" + EOL +
335                 "  Header2" + EOL +
336                 "" + EOL +
337                 "  Footer1" + EOL +
338                 "  Footer2" + EOL,
339                 out.toString());
340         //@formatter:on
341     }
342 
343     @Test
344     void testOptionWithoutShortFormat() {
345         // related to Bugzilla #19383 (CLI-67)
346         final Options options = new Options();
347         options.addOption(new Option("a", "aaa", false, "aaaaaaa"));
348         options.addOption(new Option(null, "bbb", false, "bbbbbbb"));
349         options.addOption(new Option("c", null, false, "ccccccc"));
350 
351         final HelpFormatter formatter = new HelpFormatter();
352         final StringWriter out = new StringWriter();
353         formatter.printHelp(new PrintWriter(out), 80, "foobar", "", options, 2, 2, "", true);
354         //@formatter:off
355         assertEquals(
356                 "usage: foobar [-a] [--bbb] [-c]" + EOL +
357                 "  -a,--aaa  aaaaaaa" + EOL +
358                 "     --bbb  bbbbbbb" + EOL +
359                 "  -c        ccccccc" + EOL,
360                 out.toString());
361         //@formatter:on
362     }
363 
364     @Test
365     void testOptionWithoutShortFormat2() {
366         // related to Bugzilla #27635 (CLI-26)
367         final Option help = new Option("h", "help", false, "print this message");
368         final Option version = new Option("v", "version", false, "print version information");
369         final Option newRun = new Option("n", "new", false, "Create NLT cache entries only for new items");
370         final Option trackerRun = new Option("t", "tracker", false, "Create NLT cache entries only for tracker items");
371         //@formatter:off
372         final Option timeLimit = Option.builder("l")
373         .longOpt("limit")
374         .hasArg()
375         .valueSeparator()
376         .desc("Set time limit for execution, in mintues").get();
377         final Option age = Option.builder("a").longOpt("age")
378         .hasArg()
379         .valueSeparator()
380         .desc("Age (in days) of cache item before being recomputed").get();
381         final Option server = Option.builder("s").longOpt("server")
382         .hasArg()
383         .valueSeparator()
384         .desc("The NLT server address").get();
385         final Option numResults = Option.builder("r").longOpt("results")
386         .hasArg()
387         .valueSeparator()
388         .desc("Number of results per item").get();
389         final Option configFile = Option.builder().longOpt("config")
390         .hasArg()
391         .valueSeparator()
392         .desc("Use the specified configuration file").get();
393         //@formatter:on
394 
395         final Options mOptions = new Options();
396         mOptions.addOption(help);
397         mOptions.addOption(version);
398         mOptions.addOption(newRun);
399         mOptions.addOption(trackerRun);
400         mOptions.addOption(timeLimit);
401         mOptions.addOption(age);
402         mOptions.addOption(server);
403         mOptions.addOption(numResults);
404         mOptions.addOption(configFile);
405 
406         final HelpFormatter formatter = new HelpFormatter();
407         final String eol = System.lineSeparator();
408         final StringWriter out = new StringWriter();
409         formatter.printHelp(new PrintWriter(out), 80, "commandline", "header", mOptions, 2, 2, "footer", true);
410         //@formatter:off
411         assertEquals(
412                 "usage: commandline [-a <arg>] [--config <arg>] [-h] [-l <arg>] [-n] [-r <arg>]" + eol +
413                 "       [-s <arg>] [-t] [-v]" + eol +
414                 "header" + eol +
415                 "  -a,--age <arg>      Age (in days) of cache item before being recomputed" + eol +
416                 "     --config <arg>   Use the specified configuration file" + eol +
417                 "  -h,--help           print this message" + eol +
418                 "  -l,--limit <arg>    Set time limit for execution, in mintues" + eol +
419                 "  -n,--new            Create NLT cache entries only for new items" + eol +
420                 "  -r,--results <arg>  Number of results per item" + eol +
421                 "  -s,--server <arg>   The NLT server address" + eol +
422                 "  -t,--tracker        Create NLT cache entries only for tracker items" + eol +
423                 "  -v,--version        print version information" + eol +
424                 "footer" + eol,
425                 out.toString());
426         //@formatter:on
427     }
428 
429     @ParameterizedTest
430     @MethodSource("deprecatedOptionsProvider")
431     void testPrintDeprecatedOptions(final HelpFormatter hf, final Option option, final String expectedTxt) {
432         final StringBuffer sb = new StringBuffer();
433 
434         final int leftPad = 1;
435         final int descPad = 3;
436         final String lpad = hf.createPadding(leftPad);
437         final String dpad = hf.createPadding(descPad);
438         Options options;
439         final StringBuilder expected = new StringBuilder().append(lpad).append("-a,--aaa");
440 
441         options = new Options().addOption(option);
442         if (expectedTxt.length() > 0) {
443             expected.append(dpad).append(expectedTxt);
444         }
445         hf.renderOptions(sb, 160, options, leftPad, descPad);
446         assertEquals(expected.toString(), sb.toString());
447     }
448 
449     @Test
450     void testPrintHelpNewlineFooter() {
451         final HelpFormatter formatter = new HelpFormatter();
452         final ByteArrayOutputStream out = new ByteArrayOutputStream();
453         final PrintWriter pw = new PrintWriter(out);
454 
455         final Options options = new Options();
456         options.addOption("a", "b");
457 
458         formatter.printHelp(
459             pw,
460             80,
461             "test" + EOL,
462             "header" + EOL,
463             options,
464             0,
465             0,
466             EOL
467         );
468         final String expected = "usage: test" + EOL +
469                           "header" + EOL +
470                           "-ab" + EOL +
471                           EOL;
472         pw.flush();
473         assertEquals(expected, out.toString(), "footer newline");
474     }
475 
476     @Test
477     void testPrintHelpNewlineHeader() {
478         final HelpFormatter formatter = new HelpFormatter();
479         final ByteArrayOutputStream out = new ByteArrayOutputStream();
480         final PrintWriter pw = new PrintWriter(out);
481 
482         final Options options = new Options();
483         options.addOption("a", "b");
484 
485         formatter.printHelp(
486             pw,
487             80,
488             "test" + EOL,
489             EOL,
490             options,
491             0,
492             0,
493             "footer" + EOL
494         );
495         final String expected = "usage: test" + EOL +
496                           EOL +
497                           "-ab" + EOL +
498                           "footer" + EOL;
499         pw.flush();
500         assertEquals(expected, out.toString(), "header newline");
501     }
502 
503     @Test
504     void testPrintHelpWithEmptySyntax() {
505         final HelpFormatter formatter = new HelpFormatter();
506         assertThrows(IllegalArgumentException.class, () -> formatter.printHelp(null, new Options()), "null command line syntax should be rejected");
507         assertThrows(IllegalArgumentException.class, () -> formatter.printHelp(null, new Options(), true), "null command line syntax should be rejected");
508         assertThrows(IllegalArgumentException.class, () -> formatter.printHelp(null, new Options(), false), "null command line syntax should be rejected");
509         assertThrows(IllegalArgumentException.class, () -> formatter.printHelp("", new Options(), true), "null command line syntax should be rejected");
510         assertThrows(IllegalArgumentException.class, () -> formatter.printHelp("", new Options(), false), "null command line syntax should be rejected");
511     }
512 
513     @Test
514     void testPrintHelpWithSince() {
515         final String [] expected = {"usage: Command syntax", "Header", "Options            Since   Description",
516                 "  -n,--no-since    -          Description for n", "  -W,--with-since  1.19.0     Descripton for W", "footer"};
517         final Options options = new Options()
518                 .addOption(Option.builder("W").longOpt("with-since").since("1.19.0").desc("Descripton for W").get())
519                 .addOption(Option.builder("n").longOpt("no-since").desc("Description for n").get());
520 
521         final HelpFormatter formatter = HelpFormatter.builder().setShowSince(true).get();
522         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
523         try (PrintWriter pw = new PrintWriter(new OutputStreamWriter(baos))) {
524             formatter.printHelp(pw, 80, "Command syntax", "Header", options, 2, 5, "footer", false);
525         }
526         assertArrayEquals(expected, baos.toString().split(System.lineSeparator()));
527     }
528 
529     @Test
530     void testPrintOptionGroupUsage() {
531         final OptionGroup optionGroup = new OptionGroup();
532         optionGroup.addOption(Option.builder("a").get());
533         optionGroup.addOption(Option.builder("b").get());
534         optionGroup.addOption(Option.builder("c").get());
535 
536         final Options options = new Options();
537         options.addOptionGroup(optionGroup);
538 
539         final StringWriter out = new StringWriter();
540 
541         final HelpFormatter formatter = new HelpFormatter();
542         formatter.printUsage(new PrintWriter(out), 80, "app", options);
543 
544         assertEquals("usage: app [-a | -b | -c]" + EOL, out.toString());
545     }
546 
547     @Test
548     void testPrintOptions() {
549         final StringBuffer sb = new StringBuffer();
550         final HelpFormatter hf = new HelpFormatter();
551         final int leftPad = 1;
552         final int descPad = 3;
553         final String lpad = hf.createPadding(leftPad);
554         final String dpad = hf.createPadding(descPad);
555         Options options;
556         String expected;
557 
558         options = new Options().addOption("a", false, "aaaa aaaa aaaa aaaa aaaa");
559         expected = lpad + "-a" + dpad + "aaaa aaaa aaaa aaaa aaaa";
560         hf.renderOptions(sb, 60, options, leftPad, descPad);
561         assertEquals(expected, sb.toString(), "simple non-wrapped option");
562 
563         int nextLineTabStop = leftPad + descPad + "-a".length();
564         expected = lpad + "-a" + dpad + "aaaa aaaa aaaa" + EOL + hf.createPadding(nextLineTabStop) + "aaaa aaaa";
565         sb.setLength(0);
566         hf.renderOptions(sb, nextLineTabStop + 17, options, leftPad, descPad);
567         assertEquals(expected, sb.toString(), "simple wrapped option");
568 
569         options = new Options().addOption("a", "aaa", false, "dddd dddd dddd dddd");
570         expected = lpad + "-a,--aaa" + dpad + "dddd dddd dddd dddd";
571         sb.setLength(0);
572         hf.renderOptions(sb, 60, options, leftPad, descPad);
573         assertEquals(expected, sb.toString(), "long non-wrapped option");
574 
575         nextLineTabStop = leftPad + descPad + "-a,--aaa".length();
576         expected = lpad + "-a,--aaa" + dpad + "dddd dddd" + EOL + hf.createPadding(nextLineTabStop) + "dddd dddd";
577         sb.setLength(0);
578         hf.renderOptions(sb, 25, options, leftPad, descPad);
579         assertEquals(expected, sb.toString(), "long wrapped option");
580 
581         options = new Options().addOption("a", "aaa", false, "dddd dddd dddd dddd").addOption("b", false, "feeee eeee eeee eeee");
582         expected = lpad + "-a,--aaa" + dpad + "dddd dddd" + EOL + hf.createPadding(nextLineTabStop) + "dddd dddd" + EOL + lpad + "-b      " + dpad
583             + "feeee eeee" + EOL + hf.createPadding(nextLineTabStop) + "eeee eeee";
584         sb.setLength(0);
585         hf.renderOptions(sb, 25, options, leftPad, descPad);
586         assertEquals(expected, sb.toString(), "multiple wrapped options");
587     }
588 
589     @Test
590     void testPrintOptionWithEmptyArgNameUsage() {
591         final Option option = new Option("f", true, null);
592         option.setArgName("");
593         option.setRequired(true);
594 
595         final Options options = new Options();
596         options.addOption(option);
597 
598         final StringWriter out = new StringWriter();
599 
600         final HelpFormatter formatter = new HelpFormatter();
601         formatter.printUsage(new PrintWriter(out), 80, "app", options);
602 
603         assertEquals("usage: app -f" + EOL, out.toString());
604     }
605 
606     @Test
607     void testPrintRequiredOptionGroupUsage() {
608         final OptionGroup optionGroup = new OptionGroup();
609         optionGroup.addOption(Option.builder("a").get());
610         optionGroup.addOption(Option.builder("b").get());
611         optionGroup.addOption(Option.builder("c").get());
612         optionGroup.setRequired(true);
613 
614         final Options options = new Options();
615         options.addOptionGroup(optionGroup);
616 
617         final StringWriter out = new StringWriter();
618 
619         final HelpFormatter formatter = new HelpFormatter();
620         formatter.printUsage(new PrintWriter(out), 80, "app", options);
621 
622         assertEquals("usage: app -a | -b | -c" + EOL, out.toString());
623     }
624 
625     // uses the test for CLI-131 to implement CLI-155
626     @Test
627     void testPrintSortedUsage() {
628         final Options opts = new Options();
629         opts.addOption(new Option("a", "first"));
630         opts.addOption(new Option("b", "second"));
631         opts.addOption(new Option("c", "third"));
632 
633         final HelpFormatter helpFormatter = new HelpFormatter();
634         helpFormatter.setOptionComparator((opt1, opt2) -> opt2.getKey().compareToIgnoreCase(opt1.getKey()));
635 
636         final StringWriter out = new StringWriter();
637         helpFormatter.printUsage(new PrintWriter(out), 80, "app", opts);
638 
639         assertEquals("usage: app [-c] [-b] [-a]" + EOL, out.toString());
640     }
641 
642     @Test
643     void testPrintSortedUsageWithNullComparator() {
644         final Options opts = new Options();
645         opts.addOption(new Option("c", "first"));
646         opts.addOption(new Option("b", "second"));
647         opts.addOption(new Option("a", "third"));
648 
649         final HelpFormatter helpFormatter = new HelpFormatter();
650         helpFormatter.setOptionComparator(null);
651 
652         final StringWriter out = new StringWriter();
653         helpFormatter.printUsage(new PrintWriter(out), 80, "app", opts);
654 
655         assertEquals("usage: app [-c] [-b] [-a]" + EOL, out.toString());
656     }
657 
658     // This test ensures the options are properly sorted
659     // See https://issues.apache.org/jira/browse/CLI-131
660     @Test
661     void testPrintUsage() {
662         final Option optionA = new Option("a", "first");
663         final Option optionB = new Option("b", "second");
664         final Option optionC = new Option("c", "third");
665         final Options opts = new Options();
666         opts.addOption(optionA);
667         opts.addOption(optionB);
668         opts.addOption(optionC);
669         final HelpFormatter helpFormatter = new HelpFormatter();
670         final ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
671         try (PrintWriter printWriter = new PrintWriter(bytesOut)) {
672             helpFormatter.printUsage(printWriter, 80, "app", opts);
673         }
674         assertEquals("usage: app [-a] [-b] [-c]" + EOL, bytesOut.toString());
675     }
676 
677     @Test
678     void testRenderSince() throws IOException {
679         final String[] expected = {"Options            Since   Description", "  -n,--no-since    -          Description for n",
680             "  -W,--with-since  1.19.0     Descripton for W"};
681         final Options options = new Options()
682                 .addOption(Option.builder("W").longOpt("with-since").since("1.19.0").desc("Descripton for W").get())
683                 .addOption(Option.builder("n").longOpt("no-since").desc("Description for n").get());
684         final HelpFormatter formatter = HelpFormatter.builder().setShowSince(true).get();
685 
686         final StringBuffer sb = new StringBuffer();
687         formatter.renderOptions(sb, 50, options, 2, 5);
688         assertArrayEquals(expected, sb.toString().split(System.lineSeparator()));
689         // check internal exception handling for coverage
690         final HelpFormatter spy = spy(formatter);
691         when(spy.appendOptions(sb, 50, options, 2, 5)).thenThrow(IOException.class);
692         assertThrows(UncheckedIOException.class, () -> spy.renderOptions(sb, 50, options, 2, 5));
693     }
694 
695     @Test
696     void testRenderWrappedTextMultiLine() {
697         // multi line text
698         final int width = 16;
699         final int padding = 0;
700         //@formatter:off
701         final String expected = "aaaa aaaa aaaa" + EOL +
702                                 "aaaaaa" + EOL +
703                                 "aaaaa";
704         //@formatter:on
705 
706         final StringBuffer sb = new StringBuffer();
707         new HelpFormatter().renderWrappedText(sb, width, padding, expected);
708         assertEquals(expected, sb.toString(), "multi line text");
709     }
710 
711     @Test
712     void testRenderWrappedTextMultiLinePadded() {
713         // multi-line padded text
714         final int width = 16;
715         final int padding = 4;
716         //@formatter:off
717         final String text = "aaaa aaaa aaaa" + EOL +
718                       "aaaaaa" + EOL +
719                       "aaaaa";
720         final String expected = "aaaa aaaa aaaa" + EOL +
721                           "    aaaaaa" + EOL +
722                           "    aaaaa";
723         //@formatter:on
724 
725         final StringBuffer sb = new StringBuffer();
726         new HelpFormatter().renderWrappedText(sb, width, padding, text);
727         assertEquals(expected, sb.toString(), "multi-line padded text");
728     }
729 
730     @Test
731     void testRenderWrappedTextSingleLine() throws IOException {
732         // single line text
733         final int width = 12;
734         final int padding = 0;
735         final String text = "This is a test.";
736         final String expected = "This is a" + EOL + "test.";
737 
738         final StringBuffer sb = new StringBuffer();
739         new HelpFormatter().renderWrappedText(sb, width, padding, text);
740         assertEquals(expected, sb.toString(), "single line text");
741         // check internal exception handling for coverage
742         final HelpFormatter spy = spy(new HelpFormatter());
743         when(spy.appendWrappedText(sb, width, padding, text)).thenThrow(IOException.class);
744         assertThrows(UncheckedIOException.class, () -> spy.renderWrappedText(sb, width, padding, text));
745 
746     }
747 
748     @Test
749     void testRenderWrappedTextSingleLinePadded() {
750         // single line padded text
751         final int width = 12;
752         final int padding = 4;
753         final String text = "This is a test.";
754         final String expected = "This is a" + EOL + "    test.";
755 
756         final StringBuffer sb = new StringBuffer();
757         new HelpFormatter().renderWrappedText(sb, width, padding, text);
758         assertEquals(expected, sb.toString(), "single line padded text");
759     }
760 
761     @Test
762     void testRenderWrappedTextSingleLinePadded2() {
763         // single line padded text 2
764         final int width = 53;
765         final int padding = 24;
766         //@formatter:off
767         final String text = "  -p,--period <PERIOD>  PERIOD is time duration of form " +
768                             "DATE[-DATE] where DATE has form YYYY[MM[DD]]";
769         final String expected = "  -p,--period <PERIOD>  PERIOD is time duration of" + EOL +
770                                 "                        form DATE[-DATE] where DATE" + EOL +
771                                 "                        has form YYYY[MM[DD]]";
772         //@formatter:on
773 
774         final StringBuffer sb = new StringBuffer();
775         new HelpFormatter().renderWrappedText(sb, width, padding, text);
776         assertEquals(expected, sb.toString(), "single line padded text 2");
777     }
778 
779     @Test
780     void testRenderWrappedTextWordCut() {
781         final int width = 7;
782         final int padding = 0;
783         final String text = "Thisisatest.";
784         final String expected = "Thisisa" + EOL + "test.";
785 
786         final StringBuffer sb = new StringBuffer();
787         new HelpFormatter().renderWrappedText(sb, width, padding, text);
788         assertEquals(expected, sb.toString(), "cut and wrap");
789     }
790 
791     @Test
792     void testRtrim() {
793         final HelpFormatter formatter = new HelpFormatter();
794 
795         assertNull(formatter.rtrim(null));
796         assertEquals("", formatter.rtrim(""));
797         assertEquals("  foo", formatter.rtrim("  foo  "));
798     }
799 
800     @Test
801     void testUsageWithLongOptSeparator() {
802         final Options options = new Options();
803         options.addOption("f", true, "the file");
804         options.addOption(Option.builder("s").longOpt("size").desc("the size").hasArg().argName("SIZE").get());
805         options.addOption(Option.builder().longOpt("age").desc("the age").hasArg().get());
806 
807         final HelpFormatter formatter = new HelpFormatter();
808         formatter.setLongOptSeparator("=");
809 
810         final StringWriter out = new StringWriter();
811 
812         formatter.printUsage(new PrintWriter(out), 80, "create", options);
813 
814         assertEquals("usage: create [--age=<arg>] [-f <arg>] [-s <SIZE>]", out.toString().trim());
815     }
816 }