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 }