CommandLineOptionTest.java revision 2553:f7b949f0a845
1/*
2 * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24package jdk.test.lib.cli;
25
26import java.util.List;
27import java.util.ArrayList;
28import java.util.Collections;
29import java.util.function.BooleanSupplier;
30
31import jdk.test.lib.management.InputArguments;
32import jdk.test.lib.process.ExitCode;
33import jdk.test.lib.process.ProcessTools;
34import jdk.test.lib.process.OutputAnalyzer;
35import jdk.test.lib.Platform;
36import jdk.test.lib.Utils;
37
38/**
39 * Base class for command line option tests.
40 */
41public abstract class CommandLineOptionTest {
42    public static final String UNLOCK_DIAGNOSTIC_VM_OPTIONS
43            = "-XX:+UnlockDiagnosticVMOptions";
44    public static final String UNLOCK_EXPERIMENTAL_VM_OPTIONS
45            = "-XX:+UnlockExperimentalVMOptions";
46    protected static final String UNRECOGNIZED_OPTION_ERROR_FORMAT
47            = "Unrecognized VM option '[+-]?%s(=.*)?'";
48    protected static final String EXPERIMENTAL_OPTION_ERROR_FORMAT
49            = "VM option '%s' is experimental and must be enabled via "
50            + "-XX:\\+UnlockExperimentalVMOptions.";
51    protected static final String DIAGNOSTIC_OPTION_ERROR_FORMAT
52            = " VM option '%s' is diagnostic and must be enabled via "
53            + "-XX:\\+UnlockDiagnosticVMOptions.";
54    private static final String PRINT_FLAGS_FINAL_FORMAT = "%s\\s*:?=\\s*%s";
55
56    /**
57     * Verifies that JVM startup behavior matches our expectations.
58     *
59     * @param option an option that should be passed to JVM
60     * @param expectedMessages an array of patterns that should occur
61     *                          in JVM output. If {@code null} then
62     *                          JVM output could be empty.
63     * @param unexpectedMessages an array of patterns that should not
64     *                           occur in JVM output. If {@code null} then
65     *                           JVM output could be empty.
66     * @param exitErrorMessage message that will be shown if exit code is not
67     *                           as expected.
68     * @param wrongWarningMessage message that will be shown if warning
69     *                           messages are not as expected.
70     * @param exitCode expected exit code.
71     * @throws Throwable if verification fails or some other issues occur.
72     */
73    public static void verifyJVMStartup(String option,
74            String expectedMessages[], String unexpectedMessages[],
75            String exitErrorMessage, String wrongWarningMessage,
76            ExitCode exitCode) throws Throwable {
77        CommandLineOptionTest.verifyJVMStartup(expectedMessages,
78                unexpectedMessages, exitErrorMessage,
79                wrongWarningMessage, exitCode, false, option);
80    }
81
82    /**
83     * Verifies that JVM startup behavior matches our expectations.
84     *
85     * @param expectedMessages an array of patterns that should occur
86     *                         in JVM output. If {@code null} then
87     *                         JVM output could be empty.
88     * @param unexpectedMessages an array of patterns that should not
89     *                           occur in JVM output. If {@code null} then
90     *                           JVM output could be empty.
91     * @param exitErrorMessage message that will be shown if exit code is not
92     *                           as expected.
93     * @param wrongWarningMessage message that will be shown if warning
94     *                           messages are not as expected.
95     * @param exitCode expected exit code.
96     * @param addTestVMOptions if {@code true} then test VM options will be
97     *                         passed to VM.
98     * @param options options that should be passed to VM in addition to mode
99     *                flag.
100     * @throws Throwable if verification fails or some other issues occur.
101     */
102    public static void verifyJVMStartup(String expectedMessages[],
103            String unexpectedMessages[], String exitErrorMessage,
104            String wrongWarningMessage, ExitCode exitCode,
105            boolean addTestVMOptions, String... options)
106                    throws Throwable {
107        List<String> finalOptions = new ArrayList<>();
108        if (addTestVMOptions) {
109            Collections.addAll(finalOptions, InputArguments.getVmInputArgs());
110            Collections.addAll(finalOptions, Utils.getTestJavaOpts());
111        }
112        Collections.addAll(finalOptions, options);
113        finalOptions.add("-version");
114
115        ProcessBuilder processBuilder
116                = ProcessTools.createJavaProcessBuilder(finalOptions.toArray(
117                new String[finalOptions.size()]));
118        OutputAnalyzer outputAnalyzer
119                = new OutputAnalyzer(processBuilder.start());
120
121        try {
122                outputAnalyzer.shouldHaveExitValue(exitCode.value);
123        } catch (RuntimeException e) {
124            String errorMessage = String.format(
125                    "JVM process should have exit value '%d'.%n%s",
126                    exitCode.value, exitErrorMessage);
127            throw new AssertionError(errorMessage, e);
128        }
129
130        verifyOutput(expectedMessages, unexpectedMessages,
131                wrongWarningMessage, outputAnalyzer);
132    }
133
134    /**
135     * Verifies that JVM startup behavior matches our expectations.
136     *
137     * @param expectedMessages an array of patterns that should occur in JVM
138     *                         output. If {@code null} then
139     *                         JVM output could be empty.
140     * @param unexpectedMessages an array of patterns that should not occur
141     *                           in JVM output. If {@code null} then
142     *                           JVM output could be empty.
143     * @param wrongWarningMessage message that will be shown if messages are
144     *                            not as expected.
145     * @param outputAnalyzer OutputAnalyzer instance
146     * @throws AssertionError if verification fails.
147     */
148    public static void verifyOutput(String[] expectedMessages,
149            String[] unexpectedMessages, String wrongWarningMessage,
150            OutputAnalyzer outputAnalyzer) {
151        if (expectedMessages != null) {
152            for (String expectedMessage : expectedMessages) {
153                try {
154                    outputAnalyzer.shouldMatch(expectedMessage);
155                } catch (RuntimeException e) {
156                    String errorMessage = String.format(
157                            "Expected message not found: '%s'.%n%s",
158                            expectedMessage, wrongWarningMessage);
159                    throw new AssertionError(errorMessage, e);
160                }
161            }
162        }
163
164        if (unexpectedMessages != null) {
165            for (String unexpectedMessage : unexpectedMessages) {
166                try {
167                    outputAnalyzer.shouldNotMatch(unexpectedMessage);
168                } catch (RuntimeException e) {
169                    String errorMessage = String.format(
170                            "Unexpected message found: '%s'.%n%s",
171                            unexpectedMessage, wrongWarningMessage);
172                    throw new AssertionError(errorMessage, e);
173                }
174            }
175        }
176    }
177
178    /**
179     * Verifies that JVM startup behavior matches our expectations when type
180     * of newly started VM is the same as the type of current.
181     *
182     * @param expectedMessages an array of patterns that should occur
183     *                         in JVM output. If {@code null} then
184     *                         JVM output could be empty.
185     * @param unexpectedMessages an array of patterns that should not
186     *                           occur in JVM output. If {@code null} then
187     *                           JVM output could be empty.
188     * @param exitErrorMessage Message that will be shown if exit value is not
189     *                           as expected.
190     * @param wrongWarningMessage message that will be shown if warning
191     *                           messages are not as expected.
192     * @param exitCode expected exit code.
193     * @param options options that should be passed to VM in addition to mode
194     *                flag.
195     * @throws Throwable if verification fails or some other issues occur.
196     */
197    public static void verifySameJVMStartup(String expectedMessages[],
198            String unexpectedMessages[], String exitErrorMessage,
199            String wrongWarningMessage, ExitCode exitCode, String... options)
200            throws Throwable {
201        List<String> finalOptions = new ArrayList<>();
202        finalOptions.add(CommandLineOptionTest.getVMTypeOption());
203        String extraFlagForEmulated = CommandLineOptionTest.getVMTypeOptionForEmulated();
204        if (extraFlagForEmulated != null) {
205            finalOptions.add(extraFlagForEmulated);
206        }
207        Collections.addAll(finalOptions, options);
208
209        CommandLineOptionTest.verifyJVMStartup(expectedMessages,
210                unexpectedMessages, exitErrorMessage,
211                wrongWarningMessage, exitCode, false,
212                finalOptions.toArray(new String[finalOptions.size()]));
213    }
214
215    /**
216     * Verifies that value of specified JVM option is the same as
217     * expected value.
218     * This method filter out option with {@code optionName}
219     * name from test java options.
220     *
221     * @param optionName a name of tested option.
222     * @param expectedValue expected value of tested option.
223     * @param optionErrorString message will be shown if option value is not as
224     *                         expected.
225     * @param additionalVMOpts additional options that should be
226     *                         passed to JVM.
227     * @throws Throwable if verification fails or some other issues occur.
228     */
229    public static void verifyOptionValue(String optionName,
230            String expectedValue, String optionErrorString,
231            String... additionalVMOpts) throws Throwable {
232        verifyOptionValue(optionName, expectedValue, optionErrorString,
233                true, additionalVMOpts);
234    }
235
236    /**
237     * Verifies that value of specified JVM option is the same as
238     * expected value.
239     * This method filter out option with {@code optionName}
240     * name from test java options.
241     *
242     * @param optionName a name of tested option.
243     * @param expectedValue expected value of tested option.
244     * @param addTestVmOptions if {@code true}, then test VM options
245     *                         will be used.
246     * @param optionErrorString message will be shown if option value is not as
247     *                         expected.
248     * @param additionalVMOpts additional options that should be
249     *                         passed to JVM.
250     * @throws Throwable if verification fails or some other issues
251     *                          occur.
252     */
253    public static void verifyOptionValue(String optionName,
254            String expectedValue, String optionErrorString,
255            boolean addTestVmOptions, String... additionalVMOpts)
256                    throws Throwable {
257        List<String> vmOpts = new ArrayList<>();
258
259        if (addTestVmOptions) {
260            Collections.addAll(vmOpts,
261                               Utils.getFilteredTestJavaOpts(optionName));
262        }
263        Collections.addAll(vmOpts, additionalVMOpts);
264        Collections.addAll(vmOpts, "-XX:+PrintFlagsFinal", "-version");
265
266        ProcessBuilder processBuilder = ProcessTools.createJavaProcessBuilder(
267                vmOpts.toArray(new String[vmOpts.size()]));
268
269        OutputAnalyzer outputAnalyzer
270                = new OutputAnalyzer(processBuilder.start());
271
272        try {
273            outputAnalyzer.shouldHaveExitValue(0);
274        } catch (RuntimeException e) {
275            String errorMessage = String.format(
276                    "JVM should start with option '%s' without errors.",
277                    optionName);
278            throw new AssertionError(errorMessage, e);
279        }
280        verifyOptionValue(optionName, expectedValue, optionErrorString,
281                outputAnalyzer);
282    }
283
284    /**
285     * Verifies that value of specified JVM option is the same as
286     * expected value.
287     *
288     * @param optionName a name of tested option.
289     * @param expectedValue expected value of tested option.
290     * @param optionErrorString message will be shown if option value is not
291     *                          as expected.
292     * @param outputAnalyzer OutputAnalyzer instance
293     * @throws AssertionError if verification fails
294     */
295    public static void verifyOptionValue(String optionName,
296            String expectedValue, String optionErrorString,
297            OutputAnalyzer outputAnalyzer) {
298        try {
299            outputAnalyzer.shouldMatch(String.format(
300                    CommandLineOptionTest.PRINT_FLAGS_FINAL_FORMAT,
301                    optionName, expectedValue));
302        } catch (RuntimeException e) {
303            String errorMessage = String.format(
304                    "Option '%s' is expected to have '%s' value%n%s",
305                    optionName, expectedValue,
306                    optionErrorString);
307            throw new AssertionError(errorMessage, e);
308        }
309    }
310
311    /**
312     * Start VM with given options and values.
313     * Generates command line option flags from
314     * {@code optionNames} and {@code optionValues}.
315     *
316     * @param optionNames names of options to pass in
317     * @param optionValues  values of option
318     * @param additionalVMOpts additional options that should be
319     *                         passed to JVM.
320     * @return output from vm process
321     */
322    public static OutputAnalyzer startVMWithOptions(String[] optionNames,
323            String[] optionValues,
324            String... additionalVMOpts) throws Throwable {
325        List<String> vmOpts = new ArrayList<>();
326        if (optionNames == null || optionValues == null || optionNames.length != optionValues.length) {
327            throw new IllegalArgumentException("optionNames and/or optionValues");
328        }
329
330        for (int i = 0; i < optionNames.length; i++) {
331          vmOpts.add(prepareFlag(optionNames[i], optionValues[i]));
332        }
333        Collections.addAll(vmOpts, additionalVMOpts);
334        Collections.addAll(vmOpts, "-version");
335
336        ProcessBuilder processBuilder = ProcessTools.createJavaProcessBuilder(
337                vmOpts.toArray(new String[vmOpts.size()]));
338
339        return new OutputAnalyzer(processBuilder.start());
340    }
341
342    /**
343     * Verifies from the output that values of specified JVM options were the same as
344     * expected values.
345     *
346     * @param outputAnalyzer search output for expect options and values.
347     * @param optionNames names of tested options.
348     * @param expectedValues expected values of tested options.
349     * @throws Throwable if verification fails or some other issues occur.
350     */
351    public static void verifyOptionValuesFromOutput(OutputAnalyzer outputAnalyzer,
352            String[] optionNames,
353            String[] expectedValues) throws Throwable {
354        outputAnalyzer.shouldHaveExitValue(0);
355        for (int i = 0; i < optionNames.length; i++) {
356          outputAnalyzer.shouldMatch(String.format(
357                CommandLineOptionTest.PRINT_FLAGS_FINAL_FORMAT,
358                optionNames[i], expectedValues[i]));
359        }
360    }
361
362   /**
363     * Verifies that value of specified JVM options are the same as
364     * expected values.
365     * Generates command line option flags from
366     * {@code optionNames} and {@code expectedValues}.
367     *
368     * @param optionNames names of tested options.
369     * @param expectedValues expected values of tested options.
370     * @throws Throwable if verification fails or some other issues occur.
371     */
372    public static void verifyOptionValues(String[] optionNames,
373            String[] expectedValues) throws Throwable {
374       OutputAnalyzer outputAnalyzer = startVMWithOptions(optionNames, expectedValues, "-XX:+PrintFlagsFinal");
375       verifyOptionValuesFromOutput(outputAnalyzer, optionNames, expectedValues);
376    }
377
378    /**
379     * Verifies that value of specified JVM when type of newly started VM
380     * is the same as the type of current.
381     * This method filter out option with {@code optionName}
382     * name from test java options.
383     * Only mode flag will be passed to VM in addition to
384     * {@code additionalVMOpts}
385     *
386     * @param optionName name of tested option.
387     * @param expectedValue expected value of tested option.
388     * @param optionErrorString message to show if option has another value
389     * @param additionalVMOpts additional options that should be
390     *                         passed to JVM.
391     * @throws Throwable if verification fails or some other issues occur.
392     */
393    public static void verifyOptionValueForSameVM(String optionName,
394            String expectedValue, String optionErrorString,
395            String... additionalVMOpts) throws Throwable {
396        List<String> finalOptions = new ArrayList<>();
397        finalOptions.add(CommandLineOptionTest.getVMTypeOption());
398        String extraFlagForEmulated = CommandLineOptionTest.getVMTypeOptionForEmulated();
399        if (extraFlagForEmulated != null) {
400            finalOptions.add(extraFlagForEmulated);
401        }
402        Collections.addAll(finalOptions, additionalVMOpts);
403
404        CommandLineOptionTest.verifyOptionValue(optionName, expectedValue,
405                optionErrorString, false,
406                finalOptions.toArray(new String[finalOptions.size()]));
407    }
408
409    /**
410     * Prepares boolean command line flag with name {@code name} according
411     * to it's {@code value}.
412     *
413     * @param name the name of option to be prepared
414     * @param value the value of option
415     * @return prepared command line flag
416     */
417    public static String prepareBooleanFlag(String name, boolean value) {
418        return String.format("-XX:%c%s", (value ? '+' : '-'), name);
419    }
420
421    /**
422     * Prepares numeric command line flag with name {@code name} by setting
423     * it's value to {@code value}.
424     *
425     * @param name the name of option to be prepared
426     * @param value the value of option
427     * @return prepared command line flag
428     */
429    public static String prepareNumericFlag(String name, Number value) {
430        return String.format("-XX:%s=%s", name, value.toString());
431    }
432
433    /**
434     * Prepares generic command line flag with name {@code name} by setting
435     * it's value to {@code value}.
436     *
437     * @param name the name of option to be prepared
438     * @param value the value of option ("+" or "-" can be used instead of "true" or "false")
439     * @return prepared command line flag
440     */
441    public static String prepareFlag(String name, String value) {
442        if (value.equals("+") || value.equalsIgnoreCase("true")) {
443          return "-XX:+" + name;
444      } else if (value.equals("-") || value.equalsIgnoreCase("false")) {
445        return "-XX:-" + name;
446      } else {
447        return "-XX:" + name + "=" + value;
448      }
449    }
450
451    /**
452     * Returns message that should occur in VM output if option
453     * {@code optionName} if unrecognized.
454     *
455     * @param optionName the name of option for which message should be returned
456     * @return message saying that option {@code optionName} is unrecognized
457     */
458    public static String getUnrecognizedOptionErrorMessage(String optionName) {
459        return String.format(
460                CommandLineOptionTest.UNRECOGNIZED_OPTION_ERROR_FORMAT,
461                optionName);
462    }
463
464    /**
465     * Returns message that should occur in VM output if option
466     * {@code optionName} is experimental and
467     * -XX:+UnlockExperimentalVMOptions was not passed to VM.
468     *
469     * @param optionName the name of option for which message should be returned
470     * @return message saying that option {@code optionName} is experimental
471     */
472    public static String getExperimentalOptionErrorMessage(String optionName) {
473        return String.format(
474                CommandLineOptionTest.EXPERIMENTAL_OPTION_ERROR_FORMAT,
475                optionName);
476    }
477
478    /**
479     * Returns message that should occur in VM output if option
480     * {@code optionName} is diagnostic and -XX:+UnlockDiagnosticVMOptions
481     * was not passed to VM.
482     *
483     * @param optionName the name of option for which message should be returned
484     * @return message saying that option {@code optionName} is diganostic
485     */
486    public static String getDiagnosticOptionErrorMessage(String optionName) {
487        return String.format(
488                CommandLineOptionTest.DIAGNOSTIC_OPTION_ERROR_FORMAT,
489                optionName);
490    }
491
492    /**
493     * @return option required to start a new VM with the same type as current.
494     * @throws RuntimeException when VM type is unknown.
495     */
496    private static String getVMTypeOption() {
497        if (Platform.isServer()) {
498            return "-server";
499        } else if (Platform.isClient()) {
500            return "-client";
501        } else if (Platform.isMinimal()) {
502            return "-minimal";
503        } else if (Platform.isGraal()) {
504            return "-graal";
505        }
506        throw new RuntimeException("Unknown VM mode.");
507    }
508
509    /**
510     * @return addtional VMoptions(Emulated related) required to start a new VM with the same type as current.
511     */
512    private static String getVMTypeOptionForEmulated() {
513        if (Platform.isServer() && !Platform.isEmulatedClient()) {
514            return "-XX:-NeverActAsServerClassMachine";
515        } else if (Platform.isEmulatedClient()) {
516            return "-XX:+NeverActAsServerClassMachine";
517        }
518        return null;
519    }
520
521    private final BooleanSupplier predicate;
522
523    /**
524     * Constructs new CommandLineOptionTest that will be executed only if
525     * predicate {@code predicate} return {@code true}.
526     * @param predicate a predicate responsible for test's preconditions check.
527     */
528    public CommandLineOptionTest(BooleanSupplier predicate) {
529        this.predicate = predicate;
530    }
531
532    /**
533     * Runs command line option test.
534     */
535    public final void test() throws Throwable {
536        if (predicate.getAsBoolean()) {
537            runTestCases();
538        }
539    }
540
541    /**
542     * @throws Throwable if some issue happened during test cases execution.
543     */
544    protected abstract void runTestCases() throws Throwable;
545}
546