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