CommandLineOptionTest.java revision 2224:2a8815d86b93
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        Collections.addAll(finalOptions, options);
203
204        CommandLineOptionTest.verifyJVMStartup(expectedMessages,
205                unexpectedMessages, exitErrorMessage,
206                wrongWarningMessage, exitCode, false,
207                finalOptions.toArray(new String[finalOptions.size()]));
208    }
209
210    /**
211     * Verifies that value of specified JVM option is the same as
212     * expected value.
213     * This method filter out option with {@code optionName}
214     * name from test java options.
215     *
216     * @param optionName a name of tested option.
217     * @param expectedValue expected value of tested option.
218     * @param optionErrorString message will be shown if option value is not as
219     *                         expected.
220     * @param additionalVMOpts additional options that should be
221     *                         passed to JVM.
222     * @throws Throwable if verification fails or some other issues occur.
223     */
224    public static void verifyOptionValue(String optionName,
225            String expectedValue, String optionErrorString,
226            String... additionalVMOpts) throws Throwable {
227        verifyOptionValue(optionName, expectedValue, optionErrorString,
228                true, additionalVMOpts);
229    }
230
231    /**
232     * Verifies that value of specified JVM option is the same as
233     * expected value.
234     * This method filter out option with {@code optionName}
235     * name from test java options.
236     *
237     * @param optionName a name of tested option.
238     * @param expectedValue expected value of tested option.
239     * @param addTestVmOptions if {@code true}, then test VM options
240     *                         will be used.
241     * @param optionErrorString message will be shown if option value is not as
242     *                         expected.
243     * @param additionalVMOpts additional options that should be
244     *                         passed to JVM.
245     * @throws Throwable if verification fails or some other issues
246     *                          occur.
247     */
248    public static void verifyOptionValue(String optionName,
249            String expectedValue, String optionErrorString,
250            boolean addTestVmOptions, String... additionalVMOpts)
251                    throws Throwable {
252        List<String> vmOpts = new ArrayList<>();
253
254        if (addTestVmOptions) {
255            Collections.addAll(vmOpts,
256                               Utils.getFilteredTestJavaOpts(optionName));
257        }
258        Collections.addAll(vmOpts, additionalVMOpts);
259        Collections.addAll(vmOpts, "-XX:+PrintFlagsFinal", "-version");
260
261        ProcessBuilder processBuilder = ProcessTools.createJavaProcessBuilder(
262                vmOpts.toArray(new String[vmOpts.size()]));
263
264        OutputAnalyzer outputAnalyzer
265                = new OutputAnalyzer(processBuilder.start());
266
267        try {
268            outputAnalyzer.shouldHaveExitValue(0);
269        } catch (RuntimeException e) {
270            String errorMessage = String.format(
271                    "JVM should start with option '%s' without errors.",
272                    optionName);
273            throw new AssertionError(errorMessage, e);
274        }
275        verifyOptionValue(optionName, expectedValue, optionErrorString,
276                outputAnalyzer);
277    }
278
279    /**
280     * Verifies that value of specified JVM option is the same as
281     * expected value.
282     *
283     * @param optionName a name of tested option.
284     * @param expectedValue expected value of tested option.
285     * @param optionErrorString message will be shown if option value is not
286     *                          as expected.
287     * @param outputAnalyzer OutputAnalyzer instance
288     * @throws AssertionError if verification fails
289     */
290    public static void verifyOptionValue(String optionName,
291            String expectedValue, String optionErrorString,
292            OutputAnalyzer outputAnalyzer) {
293        try {
294            outputAnalyzer.shouldMatch(String.format(
295                    CommandLineOptionTest.PRINT_FLAGS_FINAL_FORMAT,
296                    optionName, expectedValue));
297        } catch (RuntimeException e) {
298            String errorMessage = String.format(
299                    "Option '%s' is expected to have '%s' value%n%s",
300                    optionName, expectedValue,
301                    optionErrorString);
302            throw new AssertionError(errorMessage, e);
303        }
304    }
305
306    /**
307     * Start VM with given options and values.
308     * Generates command line option flags from
309     * {@code optionNames} and {@code optionValues}.
310     *
311     * @param optionNames names of options to pass in
312     * @param optionValues  values of option
313     * @param additionalVMOpts additional options that should be
314     *                         passed to JVM.
315     * @return output from vm process
316     */
317    public static OutputAnalyzer startVMWithOptions(String[] optionNames,
318            String[] optionValues,
319            String... additionalVMOpts) throws Throwable {
320        List<String> vmOpts = new ArrayList<>();
321        if (optionNames == null || optionValues == null || optionNames.length != optionValues.length) {
322            throw new IllegalArgumentException("optionNames and/or optionValues");
323        }
324
325        for (int i = 0; i < optionNames.length; i++) {
326          vmOpts.add(prepareFlag(optionNames[i], optionValues[i]));
327        }
328        Collections.addAll(vmOpts, additionalVMOpts);
329        Collections.addAll(vmOpts, "-version");
330
331        ProcessBuilder processBuilder = ProcessTools.createJavaProcessBuilder(
332                vmOpts.toArray(new String[vmOpts.size()]));
333
334        return new OutputAnalyzer(processBuilder.start());
335    }
336
337    /**
338     * Verifies from the output that values of specified JVM options were the same as
339     * expected values.
340     *
341     * @param outputAnalyzer search output for expect options and values.
342     * @param optionNames names of tested options.
343     * @param expectedValues expected values of tested options.
344     * @throws Throwable if verification fails or some other issues occur.
345     */
346    public static void verifyOptionValuesFromOutput(OutputAnalyzer outputAnalyzer,
347            String[] optionNames,
348            String[] expectedValues) throws Throwable {
349        outputAnalyzer.shouldHaveExitValue(0);
350        for (int i = 0; i < optionNames.length; i++) {
351          outputAnalyzer.shouldMatch(String.format(
352                CommandLineOptionTest.PRINT_FLAGS_FINAL_FORMAT,
353                optionNames[i], expectedValues[i]));
354        }
355    }
356
357   /**
358     * Verifies that value of specified JVM options are the same as
359     * expected values.
360     * Generates command line option flags from
361     * {@code optionNames} and {@code expectedValues}.
362     *
363     * @param optionNames names of tested options.
364     * @param expectedValues expected values of tested options.
365     * @throws Throwable if verification fails or some other issues occur.
366     */
367    public static void verifyOptionValues(String[] optionNames,
368            String[] expectedValues) throws Throwable {
369       OutputAnalyzer outputAnalyzer = startVMWithOptions(optionNames, expectedValues, "-XX:+PrintFlagsFinal");
370       verifyOptionValuesFromOutput(outputAnalyzer, optionNames, expectedValues);
371    }
372
373    /**
374     * Verifies that value of specified JVM when type of newly started VM
375     * is the same as the type of current.
376     * This method filter out option with {@code optionName}
377     * name from test java options.
378     * Only mode flag will be passed to VM in addition to
379     * {@code additionalVMOpts}
380     *
381     * @param optionName name of tested option.
382     * @param expectedValue expected value of tested option.
383     * @param optionErrorString message to show if option has another value
384     * @param additionalVMOpts additional options that should be
385     *                         passed to JVM.
386     * @throws Throwable if verification fails or some other issues occur.
387     */
388    public static void verifyOptionValueForSameVM(String optionName,
389            String expectedValue, String optionErrorString,
390            String... additionalVMOpts) throws Throwable {
391        List<String> finalOptions = new ArrayList<>();
392        finalOptions.add(CommandLineOptionTest.getVMTypeOption());
393        Collections.addAll(finalOptions, additionalVMOpts);
394
395        CommandLineOptionTest.verifyOptionValue(optionName, expectedValue,
396                optionErrorString, false,
397                finalOptions.toArray(new String[finalOptions.size()]));
398    }
399
400    /**
401     * Prepares boolean command line flag with name {@code name} according
402     * to it's {@code value}.
403     *
404     * @param name the name of option to be prepared
405     * @param value the value of option
406     * @return prepared command line flag
407     */
408    public static String prepareBooleanFlag(String name, boolean value) {
409        return String.format("-XX:%c%s", (value ? '+' : '-'), name);
410    }
411
412    /**
413     * Prepares numeric command line flag with name {@code name} by setting
414     * it's value to {@code value}.
415     *
416     * @param name the name of option to be prepared
417     * @param value the value of option
418     * @return prepared command line flag
419     */
420    public static String prepareNumericFlag(String name, Number value) {
421        return String.format("-XX:%s=%s", name, value.toString());
422    }
423
424    /**
425     * Prepares generic command line flag with name {@code name} by setting
426     * it's value to {@code value}.
427     *
428     * @param name the name of option to be prepared
429     * @param value the value of option ("+" or "-" can be used instead of "true" or "false")
430     * @return prepared command line flag
431     */
432    public static String prepareFlag(String name, String value) {
433        if (value.equals("+") || value.equalsIgnoreCase("true")) {
434          return "-XX:+" + name;
435      } else if (value.equals("-") || value.equalsIgnoreCase("false")) {
436        return "-XX:-" + name;
437      } else {
438        return "-XX:" + name + "=" + value;
439      }
440    }
441
442    /**
443     * Returns message that should occur in VM output if option
444     * {@code optionName} if unrecognized.
445     *
446     * @param optionName the name of option for which message should be returned
447     * @return message saying that option {@code optionName} is unrecognized
448     */
449    public static String getUnrecognizedOptionErrorMessage(String optionName) {
450        return String.format(
451                CommandLineOptionTest.UNRECOGNIZED_OPTION_ERROR_FORMAT,
452                optionName);
453    }
454
455    /**
456     * Returns message that should occur in VM output if option
457     * {@code optionName} is experimental and
458     * -XX:+UnlockExperimentalVMOptions was not passed to VM.
459     *
460     * @param optionName the name of option for which message should be returned
461     * @return message saying that option {@code optionName} is experimental
462     */
463    public static String getExperimentalOptionErrorMessage(String optionName) {
464        return String.format(
465                CommandLineOptionTest.EXPERIMENTAL_OPTION_ERROR_FORMAT,
466                optionName);
467    }
468
469    /**
470     * Returns message that should occur in VM output if option
471     * {@code optionName} is diagnostic and -XX:+UnlockDiagnosticVMOptions
472     * was not passed to VM.
473     *
474     * @param optionName the name of option for which message should be returned
475     * @return message saying that option {@code optionName} is diganostic
476     */
477    public static String getDiagnosticOptionErrorMessage(String optionName) {
478        return String.format(
479                CommandLineOptionTest.DIAGNOSTIC_OPTION_ERROR_FORMAT,
480                optionName);
481    }
482
483    /**
484     * @return option required to start a new VM with the same type as current.
485     * @throws RuntimeException when VM type is unknown.
486     */
487    private static String getVMTypeOption() {
488        if (Platform.isServer()) {
489            return "-server";
490        } else if (Platform.isClient()) {
491            return "-client";
492        } else if (Platform.isMinimal()) {
493            return "-minimal";
494        } else if (Platform.isGraal()) {
495            return "-graal";
496        }
497        throw new RuntimeException("Unknown VM mode.");
498    }
499
500    private final BooleanSupplier predicate;
501
502    /**
503     * Constructs new CommandLineOptionTest that will be executed only if
504     * predicate {@code predicate} return {@code true}.
505     * @param predicate a predicate responsible for test's preconditions check.
506     */
507    public CommandLineOptionTest(BooleanSupplier predicate) {
508        this.predicate = predicate;
509    }
510
511    /**
512     * Runs command line option test.
513     */
514    public final void test() throws Throwable {
515        if (predicate.getAsBoolean()) {
516            runTestCases();
517        }
518    }
519
520    /**
521     * @throws Throwable if some issue happened during test cases execution.
522     */
523    protected abstract void runTestCases() throws Throwable;
524}
525