1 /*
  2  * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
  3  * Copyright (c) 2018, 2020 SAP SE. All rights reserved.
  4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  5  *
  6  * This code is free software; you can redistribute it and/or modify it
  7  * under the terms of the GNU General Public License version 2 only, as
  8  * published by the Free Software Foundation.
  9  *
 10  * This code is distributed in the hope that it will be useful, but WITHOUT
 11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 13  * version 2 for more details (a copy is included in the LICENSE file that
 14  * accompanied this code).
 15  *
 16  * You should have received a copy of the GNU General Public License version
 17  * 2 along with this work; if not, write to the Free Software Foundation,
 18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 19  *
 20  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 21  * or visit www.oracle.com if you need additional information or have any
 22  * questions.
 23  */
 24 
 25 /**
 26  * @test
 27  * @summary Validate and test -?, -h and --help flags. All tools in the jdk
 28  *          should take the same flags to display the help message. These
 29  *          flags should be documented in the printed help message. The
 30  *          tool should quit without error code after displaying the
 31  *          help message (if there  is no other problem with the command
 32  *          line).
 33  *          Also check that tools that used to accept -help still do
 34  *          so. Test that tools that never accepted -help don't do so
 35  *          in future. I.e., check that the tool returns with the same
 36  *          return code as called with an invalid flag, and does not
 37  *          print anything containing '-help' in that case.
 38  * @compile HelpFlagsTest.java
 39  * @run main HelpFlagsTest
 40  */
 41 
 42 import java.io.File;
 43 
 44 public class HelpFlagsTest extends TestHelper {
 45 
 46     // Tools that should not be tested because a usage message is pointless.
 47     static final String[] TOOLS_NOT_TO_TEST = {
 48         "jaccessinspector", // gui, don't test, win only
 49         "jaccessinspector-32", // gui, don't test, win-32 only
 50         "jaccesswalker",    // gui, don't test, win only
 51         "jaccesswalker-32", // gui, don't test, win-32 only
 52         "jconsole",         // gui, don't test
 53         "servertool",       // none. Shell, don't test.
 54         "javaw",            // don't test, win only
 55         // These shall have a help message that resembles that of
 56         // MIT's tools. Thus -?, -h and --help are supported, but not
 57         // mentioned in the help text.
 58         "kinit",
 59         "klist",
 60         "ktab",
 61         // Oracle proprietary tools without help message.
 62         "javacpl",
 63         "jmc",
 64         "jweblauncher",
 65         "jcontrol",
 66         "ssvagent",
 67         // asprof don't test
 68         "asprof",
 69         "jfrconv"
 70     };
 71 
 72     // Lists which tools support which flags.
 73     private static class ToolHelpSpec {
 74         String toolname;
 75 
 76         // How the flags supposed to be supported are handled.
 77         //
 78         // These flags are supported, i.e.,
 79         // * the tool accepts the flag
 80         // * the tool prints a help message if the flag is specified
 81         // * this help message lists the flag
 82         // * the tool exits with exit code '0'.
 83         boolean supportsQuestionMark;
 84         boolean supportsH;
 85         boolean supportsHelp;
 86 
 87         // One tool returns with exit code != '0'.
 88         int exitcodeOfHelp;
 89 
 90         // How legacy -help is handled.
 91         //
 92         // Tools that so far support -help should still do so, but
 93         // not print documentation about it. Tools that do not
 94         // support -help should not do so in future.
 95         //
 96         // The tools accepts legacy -help. -help should not be
 97         // documented in the usage message.
 98         boolean supportsLegacyHelp;
 99 
100         // Java itself documents -help. -help prints to stderr,
101         // while --help prints to stdout. Leave as is.
102         boolean documentsLegacyHelp;
103 
104         // The exit code of the tool if an invalid argument is passed to it.
105         // An exit code != 0 would be expected, but not all tools handle it
106         // that way.
107         int exitcodeOfWrongFlag;
108 
109         ToolHelpSpec(String n, int q, int h, int hp, int ex1, int l, int dl, int ex2) {
110             toolname = n;
111             supportsQuestionMark = ( q  == 1 ? true : false );
112             supportsH            = ( h  == 1 ? true : false );
113             supportsHelp         = ( hp == 1 ? true : false );
114             exitcodeOfHelp       = ex1;
115 
116             supportsLegacyHelp   = (  l == 1 ? true : false );
117             documentsLegacyHelp  = ( dl == 1 ? true : false );
118             exitcodeOfWrongFlag  = ex2;
119         }
120     }
121 
122     static ToolHelpSpec[] jdkTools = {
123         //               name          -?   -h --help exitcode   -help -help  exitcode
124         //                                            of help          docu   of wrong
125         //                                                             mented flag
126         new ToolHelpSpec("jabswitch",   0,   0,   0,   0,         0,    0,     0),     // /?, prints help message anyways, win only
127         new ToolHelpSpec("jar",         1,   1,   1,   0,         0,    0,     1),     // -?, -h, --help
128         new ToolHelpSpec("jarsigner",   1,   1,   1,   0,         1,    0,     1),     // -?, -h, --help, -help accepted but not documented.
129         new ToolHelpSpec("java",        1,   1,   1,   0,         1,    1,     1),     // -?, -h, --help -help, Documents -help
130         new ToolHelpSpec("javac",       1,   0,   1,   0,         1,    1,     2),     // -?,     --help -help, Documents -help, -h is already taken for "native header output directory".
131         new ToolHelpSpec("javadoc",     1,   1,   1,   0,         1,    1,     1),     // -?, -h, --help -help, Documents -help
132         new ToolHelpSpec("javap",       1,   1,   1,   0,         1,    1,     2),     // -?, -h, --help -help, Documents -help
133         new ToolHelpSpec("javaw",       1,   1,   1,   0,         1,    1,     1),     // -?, -h, --help -help, Documents -help, win only
134         new ToolHelpSpec("jcmd",        1,   1,   1,   0,         1,    0,     1),     // -?, -h, --help, -help accepted but not documented.
135         new ToolHelpSpec("jdb",         1,   1,   1,   0,         1,    1,     0),     // -?, -h, --help -help, Documents -help
136         new ToolHelpSpec("jdeprscan",   1,   1,   1,   0,         0,    0,     1),     // -?, -h, --help
137         new ToolHelpSpec("jdeps",       1,   1,   1,   0,         1,    0,     2),     // -?, -h, --help, -help accepted but not documented.
138         new ToolHelpSpec("jfr",         1,   1,   1,   0,         0,    0,     2),     // -?, -h, --help
139         new ToolHelpSpec("jhsdb",       0,   0,   0,   0,         0,    0,     0),     // none, prints help message anyways.
140         new ToolHelpSpec("jimage",      1,   1,   1,   0,         0,    0,     2),     // -?, -h, --help
141         new ToolHelpSpec("jinfo",       1,   1,   1,   0,         1,    1,     1),     // -?, -h, --help -help, Documents -help
142         new ToolHelpSpec("jjs",         0,   1,   1, 100,         0,    0,   100),     //     -h, --help, return code 100
143         new ToolHelpSpec("jlink",       1,   1,   1,   0,         0,    0,     2),     // -?, -h, --help
144         new ToolHelpSpec("jmap",        1,   1,   1,   0,         1,    0,     1),     // -?, -h, --help, -help accepted but not documented.
145         new ToolHelpSpec("jmod",        1,   1,   1,   0,         1,    0,     2),     // -?, -h, --help, -help accepted but not documented.
146         new ToolHelpSpec("jnativescan", 1,   1,   1,   0,         1,    0,     1),     // -?, -h, --help, -help accepted but not documented.
147         new ToolHelpSpec("jps",         1,   1,   1,   0,         1,    1,     1),     // -?, -h, --help -help, Documents -help
148         new ToolHelpSpec("jrunscript",  1,   1,   1,   0,         1,    1,     7),     // -?, -h, --help -help, Documents -help
149         new ToolHelpSpec("jshell",      1,   1,   1,   0,         1,    0,     1),     // -?, -h, --help, -help accepted but not documented.
150         new ToolHelpSpec("jstack",      1,   1,   1,   0,         1,    1,     1),     // -?, -h, --help -help, Documents -help
151         new ToolHelpSpec("jstat",       1,   1,   1,   0,         1,    1,     1),     // -?, -h, --help -help, Documents -help
152         new ToolHelpSpec("jstatd",      1,   1,   1,   0,         0,    0,     1),     // -?, -h, --help
153         new ToolHelpSpec("keytool",     1,   1,   1,   0,         1,    0,     1),     // none, prints help message anyways.
154         new ToolHelpSpec("rmiregistry", 0,   0,   0,   0,         0,    0,     1),     // none, prints help message anyways.
155         new ToolHelpSpec("serialver",   0,   0,   0,   0,         0,    0,     1),     // none, prints help message anyways.
156         new ToolHelpSpec("jpackage",    0,   1,   1,   0,         0,    1,     1),     //     -h, --help,
157         new ToolHelpSpec("jwebserver",  1,   1,   1,   0,         0,    1,     1),     // -?, -h, --help
158     };
159 
160     // Returns corresponding object from jdkTools array.
161     static ToolHelpSpec getToolHelpSpec(String tool) {
162         for (ToolHelpSpec x : jdkTools) {
163             if (tool.toLowerCase().equals(x.toolname) ||
164                 tool.toLowerCase().equals(x.toolname + ".exe"))
165                 return x;
166         }
167         return null;
168     }
169 
170     // Check whether 'flag' appears in 'line' as a word of itself. It must not
171     // be a substring of a word, as then similar flags might be matched.
172     // E.g.: --help matches in the documentation of --help-extra.
173     // This works only with english locale, as some tools have translated
174     // usage messages.
175     static boolean findFlagInLine(String line, String flag) {
176         if (line.contains(flag) &&
177             !line.contains("nknown") &&                       // Some tools say 'Unknown option "<flag>"',
178             !line.contains("invalid flag") &&                 // 'invalid flag: <flag>'
179             !line.contains("invalid option") &&               // or 'invalid option: <flag>'. Skip that.
180             !line.contains("FileNotFoundException: -help") && // Special case for idlj.
181             !line.contains("-h requires an argument") &&      // Special case for javac.
182             !line.contains("port argument,")) {               // Special case for rmiregistry.
183             // There might be several appearances of 'flag' in
184             // 'line'. (-h as substring of --help).
185             int flagLen = flag.length();
186             int lineLen = line.length();
187             for (int i = line.indexOf(flag); i >= 0; i = line.indexOf(flag, i+1)) {
188                 // There should be a space before 'flag' in 'line', or it's right at the beginning.
189                 if (i > 0 &&
190                     line.charAt(i-1) != ' ' &&
191                     line.charAt(i-1) != '[' &&  // jarsigner
192                     line.charAt(i-1) != '|' &&  // jstatd
193                     line.charAt(i-1) != '\t') { // jjs
194                     continue;
195                 }
196                 // There should be a space or comma after 'flag' in 'line', or it's just at the end.
197                 int posAfter = i + flagLen;
198                 if (posAfter < lineLen &&
199                     line.charAt(posAfter) != ' ' &&
200                     line.charAt(posAfter) != ',' &&
201                     line.charAt(posAfter) != '[' && // jar
202                     line.charAt(posAfter) != ']' && // jarsigner
203                     line.charAt(posAfter) != ')' && // jfr
204                     line.charAt(posAfter) != '|' && // jstatd
205                     line.charAt(posAfter) != ':' && // jps
206                     line.charAt(posAfter) != '"') { // keytool
207                     continue;
208                 }
209                 return true;
210             }
211         }
212         return false;
213     }
214 
215     static TestResult runToolWithFlag(File f, String flag) {
216         String x = f.getAbsolutePath();
217         TestResult tr = doExec(x, flag);
218         System.out.println("Testing " + f.getName());
219         System.out.println("#> " + x + " " + flag);
220         tr.testOutput.forEach(System.out::println);
221         System.out.println("#> echo $?");
222         System.out.println(tr.exitValue);
223 
224         return tr;
225     }
226 
227     // Checks whether tool supports flag 'flag' and documents it
228     // in the help message.
229     static String testTool(File f, String flag, int exitcode) {
230         String result = "";
231         TestResult tr = runToolWithFlag(f, flag);
232 
233         // Check that the tool accepted the flag.
234         if (exitcode == 0 && !tr.isOK()) {
235             System.out.println("failed");
236             result = "failed: " + f.getName() + " " + flag + " has exit code " + tr.exitValue + ".\n";
237         }
238 
239         // Check there is a help message listing the flag.
240         boolean foundFlag = false;
241         for (String y : tr.testOutput) {
242             if (!foundFlag && findFlagInLine(y, flag)) { // javac
243                 foundFlag = true;
244                 System.out.println("Found documentation of '" + flag + "': '" + y.trim() +"'");
245             }
246         }
247         if (!foundFlag) {
248             result += "failed: " + f.getName() + " does not document " +
249                 flag + " in help message.\n";
250         }
251 
252         if (!result.isEmpty())
253             System.out.println(result);
254 
255         return result;
256     }
257 
258     // Test the tool supports legacy option -help, but does
259     // not document it.
260     static String testLegacyFlag(File f, int exitcode) {
261         String result = "";
262         TestResult tr = runToolWithFlag(f, "-help");
263 
264         // Check that the tool accepted the flag.
265         if (exitcode == 0 && !tr.isOK()) {
266             System.out.println("failed");
267             result = "failed: " + f.getName() + " -help has exit code " + tr.exitValue + ".\n";
268         }
269 
270         // Check there is _no_ documentation of -help.
271         boolean foundFlag = false;
272         for (String y : tr.testOutput) {
273             if (!foundFlag && findFlagInLine(y, "-help")) {  // javac
274                 foundFlag = true;
275                 System.out.println("Found documentation of '-help': '" + y.trim() +"'");
276             }
277         }
278         if (foundFlag) {
279             result += "failed: " + f.getName() + " does document -help " +
280                 "in help message. This legacy flag should not be documented.\n";
281         }
282 
283         if (!result.isEmpty())
284             System.out.println(result);
285 
286         return result;
287     }
288 
289     // Test that the tool exits with the exit code expected for
290     // invalid flags. In general, one would expect this to be != 0,
291     // but currently a row of tools exit with 0 in this case.
292     // The output should not ask to get help with flag '-help'.
293     static String testInvalidFlag(File f, String flag, int exitcode, boolean documentsLegacyHelp) {
294         String result = "";
295         TestResult tr = runToolWithFlag(f, flag);
296 
297         // Check that the tool did exit with the expected return code.
298         if (!((exitcode == tr.exitValue) ||
299               // Windows reports -1 where unix reports 255.
300               (tr.exitValue < 0 && exitcode == tr.exitValue + 256))) {
301             System.out.println("failed");
302             result = "failed: " + f.getName() + " " + flag + " should not be " +
303                      "accepted. But it has exit code " + tr.exitValue + ".\n";
304         }
305 
306         if (!documentsLegacyHelp) {
307             // Check there is _no_ documentation of -help.
308             boolean foundFlag = false;
309             for (String y : tr.testOutput) {
310                 if (!foundFlag && findFlagInLine(y, "-help")) {  // javac
311                     foundFlag = true;
312                     System.out.println("Found documentation of '-help': '" + y.trim() +"'");
313                 }
314             }
315             if (foundFlag) {
316                 result += "failed: " + f.getName() + " does document -help " +
317                     "in error message. This legacy flag should not be documented.\n";
318             }
319         }
320 
321         if (!result.isEmpty())
322             System.out.println(result);
323 
324         return result;
325     }
326 
327     public static void main(String[] args) {
328         String errorMessage = "";
329 
330         // The test analyses the help messages printed. It assumes englisch
331         // help messages. Thus it only works with english locale.
332         if (!isEnglishLocale()) { return; }
333 
334         for (File f : new File(JAVA_BIN).listFiles(new ToolFilter(TOOLS_NOT_TO_TEST))) {
335             String toolName = f.getName();
336 
337             ToolHelpSpec tool = getToolHelpSpec(toolName);
338             if (tool == null) {
339                 errorMessage += "Tool " + toolName + " not covered by this test. " +
340                     "Add specification to jdkTools array!\n";
341                 continue;
342             }
343 
344             // Test for help flags to be supported.
345             if (tool.supportsQuestionMark == true) {
346                 errorMessage += testTool(f, "-?", tool.exitcodeOfHelp);
347             } else {
348                 System.out.println("Skip " + tool.toolname + ". It does not support -?.");
349             }
350             if (tool.supportsH == true) {
351                 errorMessage += testTool(f, "-h", tool.exitcodeOfHelp);
352             } else {
353                 System.out.println("Skip " + tool.toolname + ". It does not support -h.");
354             }
355             if (tool.supportsHelp == true) {
356                 errorMessage += testTool(f, "--help", tool.exitcodeOfHelp);
357             } else {
358                 System.out.println("Skip " + tool.toolname + ". It does not support --help.");
359             }
360 
361             // Check that the return code listing in jdkTools[] is
362             // correct for an invalid flag.
363             errorMessage += testInvalidFlag(f, "-asdfxgr", tool.exitcodeOfWrongFlag, tool.documentsLegacyHelp);
364 
365             // Test for legacy -help flag.
366             if (!tool.documentsLegacyHelp) {
367                 if (tool.supportsLegacyHelp == true) {
368                     errorMessage += testLegacyFlag(f, tool.exitcodeOfHelp);
369                 } else {
370                     errorMessage += testInvalidFlag(f, "-help", tool.exitcodeOfWrongFlag, false);
371                 }
372             }
373         }
374 
375         if (errorMessage.isEmpty()) {
376             System.out.println("All help string tests: PASS");
377         } else {
378             throw new AssertionError("HelpFlagsTest failed:\n" + errorMessage);
379         }
380     }
381 }