1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * Copyright (c) 2018 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 import java.io.FileFilter;
  44 import java.util.Map;
  45 import java.util.ArrayList;
  46 import java.util.HashMap;
  47 import java.util.List;
  48 import java.util.HashSet;
  49 import java.util.Set;
  50 
  51 
  52 public class HelpFlagsTest extends TestHelper {
  53 
  54     // Tools that should not be tested because a usage message is pointless.
  55     static final String[] TOOLS_NOT_TO_TEST = {
  56         "appletviewer",     // deprecated, don't test
  57         "jaccessinspector", // gui, don't test, win only
  58         "jaccesswalker",    // gui, don't test, win only
  59         "jconsole",         // gui, don't test
  60         "servertool",       // none. Shell, don't test.
  61         "javaw",            // don't test, win only
  62         // These shall have a help message that resembles that of
  63         // MIT's tools. Thus -?, -h and --help are supported, but not
  64         // mentioned in the help text.
  65         "kinit",
  66         "klist",
  67         "ktab",
  68         // Oracle proprietary tools without help message.
  69         "javacpl",
  70         "jmc",
  71         "jweblauncher",
  72         "jcontrol",
  73         "ssvagent"
  74     };
  75 
  76     // Lists which tools support which flags.
  77     private static class ToolHelpSpec {
  78         String toolname;
  79 
  80         // How the flags supposed to be supported are handled.
  81         //
  82         // These flags are supported, i.e.,
  83         // * the tool accepts the flag
  84         // * the tool prints a help message if the flag is specified
  85         // * this help message lists the flag
  86         // * the tool exits with exit code '0'.
  87         boolean supportsQuestionMark;
  88         boolean supportsH;
  89         boolean supportsHelp;
  90 
  91         // One tool returns with exit code != '0'.
  92         int exitcodeOfHelp;
  93 
  94         // How legacy -help is handled.
  95         //
  96         // Tools that so far support -help should still do so, but
  97         // not print documentation about it. Tools that do not
  98         // support -help should not do so in future.
  99         //
 100         // The tools accepts legacy -help. -help should not be
 101         // documented in the usage message.
 102         boolean supportsLegacyHelp;
 103 
 104         // Java itself documents -help. -help prints to stderr,
 105         // while --help prints to stdout. Leave as is.
 106         boolean documentsLegacyHelp;
 107 
 108         // The exit code of the tool if an invalid argument is passed to it.
 109         // An exit code != 0 would be expected, but not all tools handle it
 110         // that way.
 111         int exitcodeOfWrongFlag;
 112 
 113         ToolHelpSpec(String n, int q, int h, int hp, int ex1, int l, int dl, int ex2) {
 114             toolname = n;
 115             supportsQuestionMark = ( q  == 1 ? true : false );
 116             supportsH            = ( h  == 1 ? true : false );
 117             supportsHelp         = ( hp == 1 ? true : false );
 118             exitcodeOfHelp       = ex1;
 119 
 120             supportsLegacyHelp   = (  l == 1 ? true : false );
 121             documentsLegacyHelp  = ( dl == 1 ? true : false );
 122             exitcodeOfWrongFlag  = ex2;
 123         }
 124     }
 125 
 126     static ToolHelpSpec[] jdkTools = {
 127         //               name          -?   -h --help exitcode   -help -help  exitcode
 128         //                                            of help          docu   of wrong
 129         //                                                             mented flag
 130         new ToolHelpSpec("jabswitch",   0,   0,   0,   0,         0,    0,     0),     // /?, prints help message anyways, win only
 131         new ToolHelpSpec("jaotc",       1,   1,   1,   0,         0,    0,     2),     // -?, -h, --help
 132         new ToolHelpSpec("jar",         1,   1,   1,   0,         0,    0,     1),     // -?, -h, --help
 133         new ToolHelpSpec("jarsigner",   1,   1,   1,   0,         1,    0,     1),     // -?, -h, --help, -help accepted but not documented.
 134         new ToolHelpSpec("java",        1,   1,   1,   0,         1,    1,     1),     // -?, -h, --help -help, Documents -help
 135         new ToolHelpSpec("javac",       1,   0,   1,   0,         1,    1,     2),     // -?,     --help -help, Documents -help, -h is already taken for "native header output directory".
 136         new ToolHelpSpec("javadoc",     1,   1,   1,   0,         1,    1,     1),     // -?, -h, --help -help, Documents -help
 137         new ToolHelpSpec("javap",       1,   1,   1,   0,         1,    1,     2),     // -?, -h, --help -help, Documents -help
 138         new ToolHelpSpec("javaw",       1,   1,   1,   0,         1,    1,     1),     // -?, -h, --help -help, Documents -help, win only
 139         new ToolHelpSpec("jcmd",        1,   1,   1,   0,         1,    0,     1),     // -?, -h, --help, -help accepted but not documented.
 140         new ToolHelpSpec("jdb",         1,   1,   1,   0,         1,    1,     0),     // -?, -h, --help -help, Documents -help
 141         new ToolHelpSpec("jdeprscan",   1,   1,   1,   0,         0,    0,     1),     // -?, -h, --help
 142         new ToolHelpSpec("jdeps",       1,   1,   1,   0,         1,    0,     2),     // -?, -h, --help, -help accepted but not documented.
 143         new ToolHelpSpec("jfr",         1,   1,   1,   0,         0,    0,     2),     // -?, -h, --help
 144         new ToolHelpSpec("jhsdb",       0,   0,   0,   0,         0,    0,     0),     // none, prints help message anyways.
 145         new ToolHelpSpec("jimage",      1,   1,   1,   0,         0,    0,     2),     // -?, -h, --help
 146         new ToolHelpSpec("jinfo",       1,   1,   1,   0,         1,    1,     1),     // -?, -h, --help -help, Documents -help
 147         new ToolHelpSpec("jjs",         0,   1,   1, 100,         0,    0,   100),     //     -h, --help, return code 100
 148         new ToolHelpSpec("jlink",       1,   1,   1,   0,         0,    0,     2),     // -?, -h, --help
 149         new ToolHelpSpec("jmap",        1,   1,   1,   0,         1,    0,     1),     // -?, -h, --help, -help accepted but not documented.
 150         new ToolHelpSpec("jmod",        1,   1,   1,   0,         1,    0,     2),     // -?, -h, --help, -help accepted but not documented.
 151         new ToolHelpSpec("jps",         1,   1,   1,   0,         1,    1,     1),     // -?, -h, --help -help, Documents -help
 152         new ToolHelpSpec("jrunscript",  1,   1,   1,   0,         1,    1,     7),     // -?, -h, --help -help, Documents -help
 153         new ToolHelpSpec("jshell",      1,   1,   1,   0,         1,    0,     1),     // -?, -h, --help, -help accepted but not documented.
 154         new ToolHelpSpec("jstack",      1,   1,   1,   0,         1,    1,     1),     // -?, -h, --help -help, Documents -help
 155         new ToolHelpSpec("jstat",       1,   1,   1,   0,         1,    1,     1),     // -?, -h, --help -help, Documents -help
 156         new ToolHelpSpec("jstatd",      1,   1,   1,   0,         0,    0,     1),     // -?, -h, --help
 157         new ToolHelpSpec("keytool",     1,   1,   1,   0,         1,    0,     1),     // none, prints help message anyways.
 158         new ToolHelpSpec("pack200",     1,   1,   1,   0,         1,    0,     2),     // -?, -h, --help, -help accepted but not documented.
 159         new ToolHelpSpec("rmic",        0,   0,   0,   0,         0,    0,     1),     // none, pirnts help message anyways.
 160         new ToolHelpSpec("rmid",        0,   0,   0,   0,         0,    0,     1),     // none, prints help message anyways.
 161         new ToolHelpSpec("rmiregistry", 0,   0,   0,   0,         0,    0,     1),     // none, prints help message anyways.
 162         new ToolHelpSpec("serialver",   0,   0,   0,   0,         0,    0,     1),     // none, prints help message anyways.
 163         new ToolHelpSpec("unpack200",   1,   1,   1,   0,         1,    0,     2),     // -?, -h, --help, -help accepted but not documented.
 164         // Oracle proprietary tools:
 165         new ToolHelpSpec("javapackager",0,   0,   0,   0,         1,    0,   255),     // -help accepted but not documented.
 166     };
 167 
 168     // Returns true if the file is not a tool.
 169     static boolean notATool(String file) {
 170         if (isWindows && !file.endsWith(EXE_FILE_EXT))
 171             return true;
 172         return false;
 173     }
 174 
 175     // Returns true if tool is listed in TOOLS_NOT_TO_TEST.
 176     static boolean dontTestTool(String tool) {
 177         tool = tool.toLowerCase();
 178         for (String x : TOOLS_NOT_TO_TEST) {
 179             if (tool.toLowerCase().startsWith(x))
 180                 return true;
 181         }
 182         return false;
 183     }
 184 
 185     // Returns corresponding object from jdkTools array.
 186     static ToolHelpSpec getToolHelpSpec(String tool) {
 187         for (ToolHelpSpec x : jdkTools) {
 188             if (tool.toLowerCase().equals(x.toolname) ||
 189                 tool.toLowerCase().equals(x.toolname + ".exe"))
 190                 return x;
 191         }
 192         return null;
 193     }
 194 
 195     // Check whether 'flag' appears in 'line' as a word of itself. It must not
 196     // be a substring of a word, as then similar flags might be matched.
 197     // E.g.: --help matches in the documentation of --help-extra.
 198     // This works only with english locale, as some tools have translated
 199     // usage messages.
 200     static boolean findFlagInLine(String line, String flag) {
 201         if (line.contains(flag) &&
 202             !line.contains("nknown") &&                       // Some tools say 'Unknown option "<flag>"',
 203             !line.contains("invalid flag") &&                 // 'invalid flag: <flag>'
 204             !line.contains("invalid option") &&               // or 'invalid option: <flag>'. Skip that.
 205             !line.contains("FileNotFoundException: -help") && // Special case for idlj.
 206             !line.contains("-h requires an argument") &&      // Special case for javac.
 207             !line.contains("port argument,")) {               // Special case for rmiregistry.
 208             // There might be several appearances of 'flag' in
 209             // 'line'. (-h as substring of --help).
 210             int flagLen = flag.length();
 211             int lineLen = line.length();
 212             for (int i = line.indexOf(flag); i >= 0; i = line.indexOf(flag, i+1)) {
 213                 // There should be a space before 'flag' in 'line', or it's right at the beginning.
 214                 if (i > 0 &&
 215                     line.charAt(i-1) != ' ' &&
 216                     line.charAt(i-1) != '[' &&  // jarsigner
 217                     line.charAt(i-1) != '|' &&  // jstatd
 218                     line.charAt(i-1) != '\t') { // jjs
 219                     continue;
 220                 }
 221                 // There should be a space or comma after 'flag' in 'line', or it's just at the end.
 222                 int posAfter = i + flagLen;
 223                 if (posAfter < lineLen &&
 224                     line.charAt(posAfter) != ' ' &&
 225                     line.charAt(posAfter) != ',' &&
 226                     line.charAt(posAfter) != '[' && // jar
 227                     line.charAt(posAfter) != ']' && // jarsigner
 228                     line.charAt(posAfter) != ')' && // jfr
 229                     line.charAt(posAfter) != '|' && // jstatd
 230                     line.charAt(posAfter) != ':' && // jps
 231                     line.charAt(posAfter) != '"') { // keytool
 232                     continue;
 233                 }
 234                 return true;
 235             }
 236         }
 237         return false;
 238     }
 239 
 240     static TestResult runToolWithFlag(File f, String flag) {
 241         String x = f.getAbsolutePath();
 242         TestResult tr = doExec(x, flag);
 243         System.out.println("Testing " + f.getName());
 244         System.out.println("#> " + x + " " + flag);
 245         tr.testOutput.forEach(System.out::println);
 246         System.out.println("#> echo $?");
 247         System.out.println(tr.exitValue);
 248 
 249         return tr;
 250     }
 251 
 252     // Checks whether tool supports flag 'flag' and documents it
 253     // in the help message.
 254     static String testTool(File f, String flag, int exitcode) {
 255         String result = "";
 256         TestResult tr = runToolWithFlag(f, flag);
 257 
 258         // Check that the tool accepted the flag.
 259         if (exitcode == 0 && !tr.isOK()) {
 260             System.out.println("failed");
 261             result = "failed: " + f.getName() + " " + flag + " has exit code " + tr.exitValue + ".\n";
 262         }
 263 
 264         // Check there is a help message listing the flag.
 265         boolean foundFlag = false;
 266         for (String y : tr.testOutput) {
 267             if (!foundFlag && findFlagInLine(y, flag)) { // javac
 268                 foundFlag = true;
 269                 System.out.println("Found documentation of '" + flag + "': '" + y.trim() +"'");
 270             }
 271         }
 272         if (!foundFlag) {
 273             result += "failed: " + f.getName() + " does not document " +
 274                 flag + " in help message.\n";
 275         }
 276 
 277         if (!result.isEmpty())
 278             System.out.println(result);
 279 
 280         return result;
 281     }
 282 
 283     // Test the tool supports legacy option -help, but does
 284     // not document it.
 285     static String testLegacyFlag(File f, int exitcode) {
 286         String result = "";
 287         TestResult tr = runToolWithFlag(f, "-help");
 288 
 289         // Check that the tool accepted the flag.
 290         if (exitcode == 0 && !tr.isOK()) {
 291             System.out.println("failed");
 292             result = "failed: " + f.getName() + " -help has exit code " + tr.exitValue + ".\n";
 293         }
 294 
 295         // Check there is _no_ documentation of -help.
 296         boolean foundFlag = false;
 297         for (String y : tr.testOutput) {
 298             if (!foundFlag && findFlagInLine(y, "-help")) {  // javac
 299                 foundFlag = true;
 300                 System.out.println("Found documentation of '-help': '" + y.trim() +"'");
 301             }
 302         }
 303         if (foundFlag) {
 304             result += "failed: " + f.getName() + " does document -help " +
 305                 "in help message. This legacy flag should not be documented.\n";
 306         }
 307 
 308         if (!result.isEmpty())
 309             System.out.println(result);
 310 
 311         return result;
 312     }
 313 
 314     // Test that the tool exits with the exit code expected for
 315     // invalid flags. In general, one would expect this to be != 0,
 316     // but currently a row of tools exit with 0 in this case.
 317     // The output should not ask to get help with flag '-help'.
 318     static String testInvalidFlag(File f, String flag, int exitcode, boolean documentsLegacyHelp) {
 319         String result = "";
 320         TestResult tr = runToolWithFlag(f, flag);
 321 
 322         // Check that the tool did exit with the expected return code.
 323         if (!((exitcode == tr.exitValue) ||
 324               // Windows reports -1 where unix reports 255.
 325               (tr.exitValue < 0 && exitcode == tr.exitValue + 256))) {
 326             System.out.println("failed");
 327             result = "failed: " + f.getName() + " " + flag + " should not be " +
 328                      "accepted. But it has exit code " + tr.exitValue + ".\n";
 329         }
 330 
 331         if (!documentsLegacyHelp) {
 332             // Check there is _no_ documentation of -help.
 333             boolean foundFlag = false;
 334             for (String y : tr.testOutput) {
 335                 if (!foundFlag && findFlagInLine(y, "-help")) {  // javac
 336                     foundFlag = true;
 337                     System.out.println("Found documentation of '-help': '" + y.trim() +"'");
 338                 }
 339             }
 340             if (foundFlag) {
 341                 result += "failed: " + f.getName() + " does document -help " +
 342                     "in error message. This legacy flag should not be documented.\n";
 343             }
 344         }
 345 
 346         if (!result.isEmpty())
 347             System.out.println(result);
 348 
 349         return result;
 350     }
 351 
 352     public static void main(String[] args) {
 353         String errorMessage = "";
 354 
 355         // The test analyses the help messages printed. It assumes englisch
 356         // help messages. Thus it only works with english locale.
 357         if (!isEnglishLocale()) { return; }
 358 
 359         for (File f : new File(JAVA_BIN).listFiles()) {
 360             String toolName = f.getName();
 361 
 362             if (notATool(toolName)) {
 363                 continue;
 364             }
 365             if (dontTestTool(toolName)) {
 366                 System.out.println("Skipping test of tool " + toolName +
 367                                    ". Tool has no help message.");
 368                 continue;
 369             }
 370 
 371             ToolHelpSpec tool = getToolHelpSpec(toolName);
 372             if (tool == null) {
 373                 errorMessage += "Tool " + toolName + " not covered by this test. " +
 374                     "Add specification to jdkTools array!\n";
 375                 continue;
 376             }
 377 
 378             // Test for help flags to be supported.
 379             if (tool.supportsQuestionMark == true) {
 380                 errorMessage += testTool(f, "-?", tool.exitcodeOfHelp);
 381             } else {
 382                 System.out.println("Skip " + tool.toolname + ". It does not support -?.");
 383             }
 384             if (tool.supportsH == true) {
 385                 errorMessage += testTool(f, "-h", tool.exitcodeOfHelp);
 386             } else {
 387                 System.out.println("Skip " + tool.toolname + ". It does not support -h.");
 388             }
 389             if (tool.supportsHelp == true) {
 390                 errorMessage += testTool(f, "--help", tool.exitcodeOfHelp);
 391             } else {
 392                 System.out.println("Skip " + tool.toolname + ". It does not support --help.");
 393             }
 394 
 395             // Check that the return code listing in jdkTools[] is
 396             // correct for an invalid flag.
 397             errorMessage += testInvalidFlag(f, "-asdfxgr", tool.exitcodeOfWrongFlag, tool.documentsLegacyHelp);
 398 
 399             // Test for legacy -help flag.
 400             if (!tool.documentsLegacyHelp) {
 401                 if (tool.supportsLegacyHelp == true) {
 402                     errorMessage += testLegacyFlag(f, tool.exitcodeOfHelp);
 403                 } else {
 404                     errorMessage += testInvalidFlag(f, "-help", tool.exitcodeOfWrongFlag, false);
 405                 }
 406             }
 407         }
 408 
 409         if (errorMessage.isEmpty()) {
 410             System.out.println("All help string tests: PASS");
 411         } else {
 412             throw new AssertionError("HelpFlagsTest failed:\n" + errorMessage);
 413         }
 414     }
 415 }