1 /*
   2  * Copyright (c) 2014, 2015, 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 
  24 package jdk.test.lib.cli;
  25 
  26 import java.util.List;
  27 import java.util.ArrayList;
  28 import java.util.Collections;
  29 import java.util.function.BooleanSupplier;
  30 
  31 import jdk.test.lib.*;
  32 
  33 /**
  34  * Base class for command line option tests.
  35  */
  36 public abstract class CommandLineOptionTest {
  37     public static final String UNLOCK_DIAGNOSTIC_VM_OPTIONS
  38             = "-XX:+UnlockDiagnosticVMOptions";
  39     public static final String UNLOCK_EXPERIMENTAL_VM_OPTIONS
  40             = "-XX:+UnlockExperimentalVMOptions";
  41     protected static final String UNRECOGNIZED_OPTION_ERROR_FORMAT
  42             = "Unrecognized VM option '[+-]?%s(=.*)?'";
  43     protected static final String EXPERIMENTAL_OPTION_ERROR_FORMAT
  44             = "VM option '%s' is experimental and must be enabled via "
  45             + "-XX:\\+UnlockExperimentalVMOptions.";
  46     protected static final String DIAGNOSTIC_OPTION_ERROR_FORMAT
  47             = " VM option '%s' is diagnostic and must be enabled via "
  48             + "-XX:\\+UnlockDiagnosticVMOptions.";
  49     private static final String PRINT_FLAGS_FINAL_FORMAT = "%s\\s*:?=\\s*%s";
  50 
  51     /**
  52      * Verifies that JVM startup behavior matches our expectations.
  53      *
  54      * @param option an option that should be passed to JVM
  55      * @param expectedMessages an array of patterns that should occur
  56      *                          in JVM output. If {@code null} then
  57      *                          JVM output could be empty.
  58      * @param unexpectedMessages an array of patterns that should not
  59      *                           occur in JVM output. If {@code null} then
  60      *                           JVM output could be empty.
  61      * @param exitErrorMessage message that will be shown if exit code is not
  62      *                           as expected.
  63      * @param wrongWarningMessage message that will be shown if warning
  64      *                           messages are not as expected.
  65      * @param exitCode expected exit code.
  66      * @throws Throwable if verification fails or some other issues occur.
  67      */
  68     public static void verifyJVMStartup(String option,
  69             String expectedMessages[], String unexpectedMessages[],
  70             String exitErrorMessage, String wrongWarningMessage,
  71             ExitCode exitCode) throws Throwable {
  72         CommandLineOptionTest.verifyJVMStartup(expectedMessages,
  73                 unexpectedMessages, exitErrorMessage,
  74                 wrongWarningMessage, exitCode, false, option);
  75     }
  76 
  77     /**
  78      * Verifies that JVM startup behavior matches our expectations.
  79      *
  80      * @param expectedMessages an array of patterns that should occur
  81      *                         in JVM output. If {@code null} then
  82      *                         JVM output could be empty.
  83      * @param unexpectedMessages an array of patterns that should not
  84      *                           occur in JVM output. If {@code null} then
  85      *                           JVM output could be empty.
  86      * @param exitErrorMessage message that will be shown if exit code is not
  87      *                           as expected.
  88      * @param wrongWarningMessage message that will be shown if warning
  89      *                           messages are not as expected.
  90      * @param exitCode expected exit code.
  91      * @param addTestVMOptions if {@code true} then test VM options will be
  92      *                         passed to VM.
  93      * @param options options that should be passed to VM in addition to mode
  94      *                flag.
  95      * @throws Throwable if verification fails or some other issues occur.
  96      */
  97     public static void verifyJVMStartup(String expectedMessages[],
  98             String unexpectedMessages[], String exitErrorMessage,
  99             String wrongWarningMessage, ExitCode exitCode,
 100             boolean addTestVMOptions, String... options)
 101                     throws Throwable {
 102         List<String> finalOptions = new ArrayList<>();
 103         if (addTestVMOptions) {
 104             Collections.addAll(finalOptions, Utils.getTestJavaOpts());
 105         }
 106         Collections.addAll(finalOptions, options);
 107         finalOptions.add("-version");
 108 
 109         ProcessBuilder processBuilder
 110                 = ProcessTools.createJavaProcessBuilder(finalOptions.toArray(
 111                 new String[finalOptions.size()]));
 112         OutputAnalyzer outputAnalyzer
 113                 = new OutputAnalyzer(processBuilder.start());
 114 
 115         try {
 116                 outputAnalyzer.shouldHaveExitValue(exitCode.value);
 117         } catch (RuntimeException e) {
 118             String errorMessage = String.format(
 119                     "JVM process should have exit value '%d'.%n%s",
 120                     exitCode.value, exitErrorMessage);
 121             throw new AssertionError(errorMessage, e);
 122         }
 123 
 124 
 125         if (expectedMessages != null) {
 126             for (String expectedMessage : expectedMessages) {
 127                 try {
 128                     outputAnalyzer.shouldMatch(expectedMessage);
 129                 } catch (RuntimeException e) {
 130                     String errorMessage = String.format(
 131                             "Expected message not found: '%s'.%n%s",
 132                             expectedMessage, wrongWarningMessage);
 133                     throw new AssertionError(errorMessage, e);
 134                 }
 135             }
 136         }
 137 
 138         if (unexpectedMessages != null) {
 139             for (String unexpectedMessage : unexpectedMessages) {
 140                 try {
 141                     outputAnalyzer.shouldNotMatch(unexpectedMessage);
 142                 } catch (RuntimeException e) {
 143                     String errorMessage = String.format(
 144                             "Unexpected message found: '%s'.%n%s",
 145                             unexpectedMessage, wrongWarningMessage);
 146                     throw new AssertionError(errorMessage, e);
 147                 }
 148             }
 149         }
 150     }
 151 
 152     /**
 153      * Verifies that JVM startup behavior matches our expectations when type
 154      * of newly started VM is the same as the type of current.
 155      *
 156      * @param expectedMessages an array of patterns that should occur
 157      *                         in JVM output. If {@code null} then
 158      *                         JVM output could be empty.
 159      * @param unexpectedMessages an array of patterns that should not
 160      *                           occur in JVM output. If {@code null} then
 161      *                           JVM output could be empty.
 162      * @param exitErrorMessage Message that will be shown if exit value is not
 163      *                           as expected.
 164      * @param wrongWarningMessage message that will be shown if warning
 165      *                           messages are not as expected.
 166      * @param exitCode expected exit code.
 167      * @param options options that should be passed to VM in addition to mode
 168      *                flag.
 169      * @throws Throwable if verification fails or some other issues occur.
 170      */
 171     public static void verifySameJVMStartup(String expectedMessages[],
 172             String unexpectedMessages[], String exitErrorMessage,
 173             String wrongWarningMessage, ExitCode exitCode, String... options)
 174             throws Throwable {
 175         List<String> finalOptions = new ArrayList<>();
 176         finalOptions.add(CommandLineOptionTest.getVMTypeOption());
 177         Collections.addAll(finalOptions, options);
 178 
 179         CommandLineOptionTest.verifyJVMStartup(expectedMessages,
 180                 unexpectedMessages, exitErrorMessage,
 181                 wrongWarningMessage, exitCode, false,
 182                 finalOptions.toArray(new String[finalOptions.size()]));
 183     }
 184 
 185     /**
 186      * Verifies that value of specified JVM option is the same as
 187      * expected value.
 188      * This method filter out option with {@code optionName}
 189      * name from test java options.
 190      *
 191      * @param optionName a name of tested option.
 192      * @param expectedValue expected value of tested option.
 193      * @param optionErrorString message will be shown if option value is not as
 194      *                         expected.
 195      * @param additionalVMOpts additional options that should be
 196      *                         passed to JVM.
 197      * @throws Throwable if verification fails or some other issues occur.
 198      */
 199     public static void verifyOptionValue(String optionName,
 200             String expectedValue, String optionErrorString,
 201             String... additionalVMOpts) throws Throwable {
 202         verifyOptionValue(optionName, expectedValue,  optionErrorString,
 203                 true, additionalVMOpts);
 204     }
 205 
 206     /**
 207      * Verifies that value of specified JVM option is the same as
 208      * expected value.
 209      * This method filter out option with {@code optionName}
 210      * name from test java options.
 211      *
 212      * @param optionName a name of tested option.
 213      * @param expectedValue expected value of tested option.
 214      * @param addTestVmOptions if {@code true}, then test VM options
 215      *                         will be used.
 216      * @param optionErrorString message will be shown if option value is not as
 217      *                         expected.
 218      * @param additionalVMOpts additional options that should be
 219      *                         passed to JVM.
 220      * @throws Throwable if verification fails or some other issues
 221      *                          occur.
 222      */
 223     public static void verifyOptionValue(String optionName,
 224             String expectedValue, String optionErrorString,
 225             boolean addTestVmOptions, String... additionalVMOpts)
 226                     throws Throwable {
 227         List<String> vmOpts = new ArrayList<>();
 228 
 229         if (addTestVmOptions) {
 230             Collections.addAll(vmOpts,
 231                                Utils.getFilteredTestJavaOpts(optionName));
 232         }
 233         Collections.addAll(vmOpts, additionalVMOpts);
 234         Collections.addAll(vmOpts, "-XX:+PrintFlagsFinal", "-version");
 235 
 236         ProcessBuilder processBuilder = ProcessTools.createJavaProcessBuilder(
 237                 vmOpts.toArray(new String[vmOpts.size()]));
 238 
 239         OutputAnalyzer outputAnalyzer
 240                 = new OutputAnalyzer(processBuilder.start());
 241 
 242         try {
 243             outputAnalyzer.shouldHaveExitValue(0);
 244         } catch (RuntimeException e) {
 245             String errorMessage = String.format(
 246                     "JVM should start with option '%s' without errors.",
 247                     optionName);
 248             throw new AssertionError(errorMessage, e);
 249         }
 250         try {
 251         outputAnalyzer.shouldMatch(String.format(
 252                 CommandLineOptionTest.PRINT_FLAGS_FINAL_FORMAT,
 253                 optionName, expectedValue));
 254         } catch (RuntimeException e) {
 255             String errorMessage =  String.format(
 256                     "Option '%s' is expected to have '%s' value%n%s",
 257                     optionName, expectedValue,
 258                     optionErrorString);
 259             throw new AssertionError(errorMessage, e);
 260         }
 261     }
 262 
 263     /**
 264      * Verifies that value of specified JVM when type of newly started VM
 265      * is the same as the type of current.
 266      * This method filter out option with {@code optionName}
 267      * name from test java options.
 268      * Only mode flag will be passed to VM in addition to
 269      * {@code additionalVMOpts}
 270      *
 271      * @param optionName name of tested option.
 272      * @param expectedValue expected value of tested option.
 273      * @param optionErrorString message to show if option has another value
 274      * @param additionalVMOpts additional options that should be
 275      *                         passed to JVM.
 276      * @throws Throwable if verification fails or some other issues occur.
 277      */
 278     public static void verifyOptionValueForSameVM(String optionName,
 279             String expectedValue, String optionErrorString,
 280             String... additionalVMOpts) throws Throwable {
 281         List<String> finalOptions = new ArrayList<>();
 282         finalOptions.add(CommandLineOptionTest.getVMTypeOption());
 283         Collections.addAll(finalOptions, additionalVMOpts);
 284 
 285         CommandLineOptionTest.verifyOptionValue(optionName, expectedValue,
 286                 optionErrorString, false,
 287                 finalOptions.toArray(new String[finalOptions.size()]));
 288     }
 289 
 290     /**
 291      * Prepares boolean command line flag with name {@code name} according
 292      * to it's {@code value}.
 293      *
 294      * @param name the name of option to be prepared
 295      * @param value the value of option
 296      * @return prepared command line flag
 297      */
 298     public static String prepareBooleanFlag(String name, boolean value) {
 299         return String.format("-XX:%c%s", (value ? '+' : '-'), name);
 300     }
 301 
 302     /**
 303      * Prepares numeric command line flag with name {@code name} by setting
 304      * it's value to {@code value}.
 305      *
 306      * @param name the name of option to be prepared
 307      * @param value the value of option
 308      * @return prepared command line flag
 309      */
 310     public static String prepareNumericFlag(String name, Number value) {
 311         return String.format("-XX:%s=%s", name, value.toString());
 312     }
 313 
 314     /**
 315      * Returns message that should occur in VM output if option
 316      * {@code optionName} if unrecognized.
 317      *
 318      * @param optionName the name of option for which message should be returned
 319      * @return message saying that option {@code optionName} is unrecognized
 320      */
 321     public static String getUnrecognizedOptionErrorMessage(String optionName) {
 322         return String.format(
 323                 CommandLineOptionTest.UNRECOGNIZED_OPTION_ERROR_FORMAT,
 324                 optionName);
 325     }
 326 
 327     /**
 328      * Returns message that should occur in VM output if option
 329      * {@code optionName} is experimental and
 330      * -XX:+UnlockExperimentalVMOptions was not passed to VM.
 331      *
 332      * @param optionName the name of option for which message should be returned
 333      * @return message saying that option {@code optionName} is experimental
 334      */
 335     public static String getExperimentalOptionErrorMessage(String optionName) {
 336         return String.format(
 337                 CommandLineOptionTest.EXPERIMENTAL_OPTION_ERROR_FORMAT,
 338                 optionName);
 339     }
 340 
 341     /**
 342      * Returns message that should occur in VM output if option
 343      * {@code optionName} is diagnostic and -XX:+UnlockDiagnosticVMOptions
 344      * was not passed to VM.
 345      *
 346      * @param optionName the name of option for which message should be returned
 347      * @return message saying that option {@code optionName} is diganostic
 348      */
 349     public static String getDiagnosticOptionErrorMessage(String optionName) {
 350         return String.format(
 351                 CommandLineOptionTest.DIAGNOSTIC_OPTION_ERROR_FORMAT,
 352                 optionName);
 353     }
 354 
 355     /**
 356      * @return option required to start a new VM with the same type as current.
 357      * @throws RuntimeException when VM type is unknown.
 358      */
 359     private static String getVMTypeOption() {
 360         if (Platform.isServer()) {
 361             return "-server";
 362         } else if (Platform.isClient()) {
 363             return "-client";
 364         } else if (Platform.isMinimal()) {
 365             return "-minimal";
 366         } else if (Platform.isGraal()) {
 367             return "-graal";
 368         }
 369         throw new RuntimeException("Unknown VM mode.");
 370     }
 371 
 372     private final BooleanSupplier predicate;
 373 
 374     /**
 375      * Constructs new CommandLineOptionTest that will be executed only if
 376      * predicate {@code predicate} return {@code true}.
 377      * @param predicate a predicate responsible for test's preconditions check.
 378      */
 379     public CommandLineOptionTest(BooleanSupplier predicate) {
 380         this.predicate = predicate;
 381     }
 382 
 383     /**
 384      * Runs command line option test.
 385      */
 386     public final void test() throws Throwable {
 387         if (predicate.getAsBoolean()) {
 388             runTestCases();
 389         }
 390     }
 391 
 392     /**
 393      * @throws Throwable if some issue happened during test cases execution.
 394      */
 395     protected abstract void runTestCases() throws Throwable;
 396 }