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.bug;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  
22  import java.io.PrintWriter;
23  import java.io.StringWriter;
24  import java.sql.ParameterMetaData;
25  import java.sql.Types;
26  
27  import org.apache.commons.cli.HelpFormatter;
28  import org.apache.commons.cli.Option;
29  import org.apache.commons.cli.OptionGroup;
30  import org.apache.commons.cli.Options;
31  import org.junit.jupiter.api.BeforeEach;
32  import org.junit.jupiter.api.Test;
33  
34  public class BugCLI162Test {
35      /** Constant for the line separator. */
36      private static final String CR = System.lineSeparator();
37  
38      // Constants used for options
39      private static final String OPT = "-";
40  
41      private static final String OPT_COLUMN_NAMES = "l";
42  
43      private static final String OPT_CONNECTION = "c";
44  
45      private static final String OPT_DESCRIPTION = "e";
46  
47      private static final String OPT_DRIVER = "d";
48  
49      private static final String OPT_DRIVER_INFO = "n";
50  
51      private static final String OPT_FILE_BINDING = "b";
52  
53      private static final String OPT_FILE_JDBC = "j";
54  
55      private static final String OPT_FILE_SFMD = "f";
56  
57      private static final String OPT_HELP = "h";
58  
59      private static final String OPT_HELP_LONG = "help";
60  
61      private static final String OPT_INTERACTIVE = "i";
62  
63      private static final String OPT_JDBC_TO_SFMD = "2";
64  
65      private static final String OPT_JDBC_TO_SFMD_L = "jdbc2sfmd";
66  
67      private static final String OPT_METADATA = "m";
68  
69      private static final String OPT_PARAM_MODES_INT = "o";
70  
71      private static final String OPT_PARAM_MODES_NAME = "O";
72  
73      private static final String OPT_PARAM_NAMES = "a";
74  
75      private static final String OPT_PARAM_TYPES_INT = "y";
76  
77      private static final String OPT_PARAM_TYPES_NAME = "Y";
78  
79      private static final String OPT_PASSWORD = "p";
80  
81      private static final String OPT_PASSWORD_L = "password";
82  
83      private static final String OPT_SQL = "s";
84  
85      private static final String OPT_SQL_L = "sql";
86  
87      private static final String OPT_STACK_TRACE = "t";
88  
89      private static final String OPT_TIMING = "g";
90  
91      private static final String OPT_TRIM_L = "trim";
92  
93      private static final String OPT_USER = "u";
94  
95      private static final String OPT_WRITE_TO_FILE = "w";
96  
97      private static final String PMODE_IN = "IN";
98  
99      private static final String PMODE_INOUT = "INOUT";
100 
101     private static final String PMODE_OUT = "OUT";
102 
103     private static final String PMODE_UNK = "Unknown";
104 
105     private static final String PMODES = PMODE_IN + ", " + PMODE_INOUT + ", " + PMODE_OUT + ", " + PMODE_UNK;
106 
107     // @formatter:off
108     private static final String EXPECTED = "usage: org.apache.commons.cli.bug.BugCLI162Test" + CR +
109             " -2,--jdbc2sfmd <arg>        Converts the JDBC file in the first argument" + CR +
110             "                             to an SMFD file specified in the second" + CR +
111             "                             argument." + CR +
112             " -a,--paramNames <arg>       Parameter XML names; default names are" + CR +
113             "                             param1, param2, etc. Example: -a \"pname1" + CR +
114             "                             pname2\"" + CR +
115             " -b,--jdbc <arg>             Writes a JDBC binding node file for the given" + CR +
116             "                             SQL" + CR +
117             " -c,--url <arg>              Connection URL" + CR +
118             " -d,--driver <arg>           JDBC driver class name" + CR +
119             " -e,--description <arg>      SFMD description. A default description is" + CR +
120             "                             used if omited. Example: -e \"Runs such and" + CR +
121             "                             such\"" + CR +
122             " -f,--sfmd <arg>             Writes a SFMD file for the given SQL" + CR +
123             " -g,--printTiming            Prints timing information" + CR +
124             " -h,--help                   Prints help and quits" + CR +
125             " -i,--interactive            Runs in interactive mode, reading and writing" + CR +
126             "                             from the console, 'go' or '/' sends a" + CR +
127             "                             statement" + CR +
128             " -j,--node <arg>             Writes a JDBC node file for the given SQL" + CR +
129             "                             (internal debugging)" + CR +
130             " -l,--columnNames <arg>      Column XML names; default names column" + CR +
131             "                             labels. Example: -l \"cname1 cname2\"" + CR +
132             " -m,--printMetaData          Prints metadata information" + CR +
133             " -n,--info                   Prints driver information and properties. If" + CR +
134             "                             -c is not specified, all drivers on the" + CR +
135             "                             classpath are displayed." + CR +
136             " -o,--paramModes <arg>       Parameters modes (1=IN, 2=INOUT, 4=OUT," + CR +
137             "                             0=Unknown). -o and -O are mutually exclusive." + CR +
138             "                             Example for 2 parameters, OUT and IN: -o \"4" + CR +
139             "                             1\"" + CR +
140             " -O,--paramModeNames <arg>   Parameters mode names (IN, INOUT, OUT," + CR +
141             "                             Unknown). -o and -O are mutually exclusive." + CR +
142             "                             Example for 2 parameters, OUT and IN: -O \"OUT" + CR +
143             "                             IN\"" + CR +
144             " -p,--password <arg>         The database password for the user specified" + CR +
145             "                             with the -u option. You can obfuscate the" + CR +
146             "                             password with" + CR +
147             "                             org.mortbay.jetty.security.Password, see" + CR +
148             "                             https://docs.codehaus.org/display/JETTY/Secur" + CR +
149             "                             ing+Passwords" + CR +
150             " -s,--sql <arg>              Runs SQL or {call stored_procedure(?, ?)} or" + CR +
151             "                             {?=call function(?, ?)}" + CR +
152             " -t,--printStack             Prints stack traces on errors" + CR +
153             "    --trim <arg>             Trims leading and trailing spaces from all" + CR +
154             "                             column values. Column XML names can be" + CR +
155             "                             optionally specified to set which columns to" + CR +
156             "                             trim." + CR +
157             " -u,--user <arg>             A database user name" + CR +
158             " -w,--outfile <arg>          Writes the SQL output to the given file" + CR +
159             " -y,--paramTypes <arg>       Parameter types from java.sql.Types. -y and" + CR +
160             "                             -Y are mutually exclusive. Example: -y \"-10" + CR +
161             "                             12\"" + CR +
162             " -Y,--paramTypeNames <arg>   Parameter java.sql.Types names. -y and -Y are" + CR +
163             "                             mutually exclusive. Example: -Y \"CURSOR" + CR +
164             "                             VARCHAR\"" + CR;
165     // @formatter:on
166 
167     private HelpFormatter formatter;
168 
169     private StringWriter sw;
170 
171     @BeforeEach
172     public void setUp() {
173         formatter = new HelpFormatter();
174         sw = new StringWriter();
175     }
176 
177     @Test
178     public void testInfiniteLoop() {
179         final Options options = new Options();
180         options.addOption("h", "help", false, "This is a looooong description");
181         // used to hang & crash
182         formatter.printHelp(new PrintWriter(sw), 20, "app", null, options, HelpFormatter.DEFAULT_LEFT_PAD, HelpFormatter.DEFAULT_DESC_PAD, null);
183 
184         //@formatter:off
185         final String expected = "usage: app" + CR +
186                 " -h,--help   This is" + CR +
187                 "             a" + CR +
188                 "             looooon" + CR +
189                 "             g" + CR +
190                 "             descrip" + CR +
191                 "             tion" + CR;
192         //@formatter:on
193         assertEquals(expected, sw.toString());
194     }
195 
196     @Test
197     public void testLongLineChunking() {
198         final Options options = new Options();
199         //@formatter:off
200         options.addOption("x", "extralongarg", false,
201                           "This description has ReallyLongValuesThatAreLongerThanTheWidthOfTheColumns " +
202                           "and also other ReallyLongValuesThatAreHugerAndBiggerThanTheWidthOfTheColumnsBob, " +
203                           "yes. ");
204         //@formatter:on
205         formatter.printHelp(new PrintWriter(sw), 35, this.getClass().getName(), "Header", options, 0, 5, "Footer");
206         //@formatter:off
207         final String expected = "usage:" + CR +
208                           "       org.apache.commons.cli.bug.B" + CR +
209                           "       ugCLI162Test" + CR +
210                           "Header" + CR +
211                           "-x,--extralongarg     This" + CR +
212                           "                      description" + CR +
213                           "                      has" + CR +
214                           "                      ReallyLongVal" + CR +
215                           "                      uesThatAreLon" + CR +
216                           "                      gerThanTheWid" + CR +
217                           "                      thOfTheColumn" + CR +
218                           "                      s and also" + CR +
219                           "                      other" + CR +
220                           "                      ReallyLongVal" + CR +
221                           "                      uesThatAreHug" + CR +
222                           "                      erAndBiggerTh" + CR +
223                           "                      anTheWidthOfT" + CR +
224                           "                      heColumnsBob," + CR +
225                           "                      yes." + CR +
226                           "Footer" + CR;
227         //@formatter:on
228         assertEquals(expected, sw.toString(), "Long arguments did not split as expected");
229     }
230 
231     @Test
232     public void testLongLineChunkingIndentIgnored() {
233         final Options options = new Options();
234         options.addOption("x", "extralongarg", false, "This description is Long.");
235         formatter.printHelp(new PrintWriter(sw), 22, this.getClass().getName(), "Header", options, 0, 5, "Footer");
236         //@formatter:off
237         final String expected = "usage:" + CR +
238                           "       org.apache.comm" + CR +
239                           "       ons.cli.bug.Bug" + CR +
240                           "       CLI162Test" + CR +
241                           "Header" + CR +
242                           "-x,--extralongarg" + CR +
243                           " This description is" + CR +
244                           " Long." + CR +
245                           "Footer" + CR;
246         //@formatter:on
247         assertEquals(expected, sw.toString(), "Long arguments did not split as expected");
248     }
249 
250     @Test
251     public void testPrintHelpLongLines() {
252         // Options build
253         final Options commandLineOptions;
254         commandLineOptions = new Options();
255         commandLineOptions.addOption(OPT_HELP, OPT_HELP_LONG, false, "Prints help and quits");
256         commandLineOptions.addOption(OPT_DRIVER, "driver", true, "JDBC driver class name");
257         //@formatter:off
258         commandLineOptions.addOption(OPT_DRIVER_INFO, "info", false, "Prints driver information and properties. If "
259             + OPT
260             + OPT_CONNECTION
261             + " is not specified, all drivers on the classpath are displayed.");
262         //@formatter:on
263         commandLineOptions.addOption(OPT_CONNECTION, "url", true, "Connection URL");
264         commandLineOptions.addOption(OPT_USER, "user", true, "A database user name");
265         //@formatter:off
266         commandLineOptions
267                 .addOption(
268                         OPT_PASSWORD,
269                         OPT_PASSWORD_L,
270                         true,
271                         "The database password for the user specified with the "
272                             + OPT
273                             + OPT_USER
274                             + " option. You can obfuscate the password with org.mortbay.jetty.security.Password,"
275                             + " see https://docs.codehaus.org/display/JETTY/Securing+Passwords");
276         //@formatter:on
277         commandLineOptions.addOption(OPT_SQL, OPT_SQL_L, true, "Runs SQL or {call stored_procedure(?, ?)} or {?=call function(?, ?)}");
278         commandLineOptions.addOption(OPT_FILE_SFMD, "sfmd", true, "Writes a SFMD file for the given SQL");
279         commandLineOptions.addOption(OPT_FILE_BINDING, "jdbc", true, "Writes a JDBC binding node file for the given SQL");
280         commandLineOptions.addOption(OPT_FILE_JDBC, "node", true, "Writes a JDBC node file for the given SQL (internal debugging)");
281         commandLineOptions.addOption(OPT_WRITE_TO_FILE, "outfile", true, "Writes the SQL output to the given file");
282         commandLineOptions.addOption(OPT_DESCRIPTION, "description", true,
283             "SFMD description. A default description is used if omited. Example: " + OPT + OPT_DESCRIPTION + " \"Runs such and such\"");
284         commandLineOptions.addOption(OPT_INTERACTIVE, "interactive", false,
285             "Runs in interactive mode, reading and writing from the console, 'go' or '/' sends a statement");
286         commandLineOptions.addOption(OPT_TIMING, "printTiming", false, "Prints timing information");
287         commandLineOptions.addOption(OPT_METADATA, "printMetaData", false, "Prints metadata information");
288         commandLineOptions.addOption(OPT_STACK_TRACE, "printStack", false, "Prints stack traces on errors");
289         //@formatter:off
290         Option option = new Option(OPT_COLUMN_NAMES, "columnNames", true, "Column XML names; default names column labels. Example: "
291             + OPT
292             + OPT_COLUMN_NAMES
293             + " \"cname1 cname2\"");
294         //@formatter:on
295         commandLineOptions.addOption(option);
296         //@formatter:off
297         option = new Option(OPT_PARAM_NAMES, "paramNames", true, "Parameter XML names; default names are param1, param2, etc. Example: "
298             + OPT
299             + OPT_PARAM_NAMES
300             + " \"pname1 pname2\"");
301         //@formatter:on
302         commandLineOptions.addOption(option);
303         //
304         final OptionGroup pOutTypesOptionGroup = new OptionGroup();
305         final String pOutTypesOptionGroupDoc = OPT + OPT_PARAM_TYPES_INT + " and " + OPT + OPT_PARAM_TYPES_NAME + " are mutually exclusive.";
306         final String typesClassName = Types.class.getName();
307         //@formatter:off
308         option = new Option(OPT_PARAM_TYPES_INT, "paramTypes", true, "Parameter types from "
309             + typesClassName
310             + ". "
311             + pOutTypesOptionGroupDoc
312             + " Example: "
313             + OPT
314             + OPT_PARAM_TYPES_INT
315             + " \"-10 12\"");
316         //@formatter:on
317         commandLineOptions.addOption(option);
318         //@formatter:off
319         option = new Option(OPT_PARAM_TYPES_NAME, "paramTypeNames", true, "Parameter "
320             + typesClassName
321             + " names. "
322             + pOutTypesOptionGroupDoc
323             + " Example: "
324             + OPT
325             + OPT_PARAM_TYPES_NAME
326             + " \"CURSOR VARCHAR\"");
327         //@formatter:on
328         commandLineOptions.addOption(option);
329         commandLineOptions.addOptionGroup(pOutTypesOptionGroup);
330         //
331         final OptionGroup modesOptionGroup = new OptionGroup();
332         final String modesOptionGroupDoc = OPT + OPT_PARAM_MODES_INT + " and " + OPT + OPT_PARAM_MODES_NAME + " are mutually exclusive.";
333         //@formatter:off
334         option = new Option(OPT_PARAM_MODES_INT, "paramModes", true, "Parameters modes ("
335             + ParameterMetaData.parameterModeIn
336             + "=IN, "
337             + ParameterMetaData.parameterModeInOut
338             + "=INOUT, "
339             + ParameterMetaData.parameterModeOut
340             + "=OUT, "
341             + ParameterMetaData.parameterModeUnknown
342             + "=Unknown"
343             + "). "
344             + modesOptionGroupDoc
345             + " Example for 2 parameters, OUT and IN: "
346             + OPT
347             + OPT_PARAM_MODES_INT
348             + " \""
349             + ParameterMetaData.parameterModeOut
350             + " "
351             + ParameterMetaData.parameterModeIn
352             + "\"");
353         //@formatter:on
354         modesOptionGroup.addOption(option);
355         //@formatter:off
356         option = new Option(OPT_PARAM_MODES_NAME, "paramModeNames", true, "Parameters mode names ("
357             + PMODES
358             + "). "
359             + modesOptionGroupDoc
360             + " Example for 2 parameters, OUT and IN: "
361             + OPT
362             + OPT_PARAM_MODES_NAME
363             + " \""
364             + PMODE_OUT
365             + " "
366             + PMODE_IN
367             + "\"");
368         //@formatter:on
369         modesOptionGroup.addOption(option);
370         commandLineOptions.addOptionGroup(modesOptionGroup);
371         option = new Option(null, OPT_TRIM_L, true,
372             "Trims leading and trailing spaces from all column values. Column XML names can be optionally specified to set which columns to trim.");
373         option.setOptionalArg(true);
374         commandLineOptions.addOption(option);
375         option = new Option(OPT_JDBC_TO_SFMD, OPT_JDBC_TO_SFMD_L, true,
376             "Converts the JDBC file in the first argument to an SMFD file specified in the second argument.");
377         option.setArgs(2);
378         commandLineOptions.addOption(option);
379 
380         formatter.printHelp(new PrintWriter(sw), HelpFormatter.DEFAULT_WIDTH, this.getClass().getName(), null, commandLineOptions,
381             HelpFormatter.DEFAULT_LEFT_PAD, HelpFormatter.DEFAULT_DESC_PAD, null);
382         //@formatter:on
383         assertEquals(EXPECTED, sw.toString());
384     }
385 
386 }