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