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      * Start VM with given options and values.
 265      * Generates command line option flags from
 266      * {@code optionNames} and {@code optionValues}.
 267      *
 268      * @param optionNames names of options to pass in
 269      * @param optionValues  values of option
 270      * @param additionalVMOpts additional options that should be
 271      *                         passed to JVM.
 272      * @return output from vm process
 273      */
 274     public static OutputAnalyzer startVMWithOptions(String[] optionNames,
 275             String[] optionValues,
 276             String... additionalVMOpts) throws Throwable {
 277         List<String> vmOpts = new ArrayList<>();
 278         if (optionNames == null || optionValues == null || optionNames.length != optionValues.length) {
 279             throw new IllegalArgumentException("optionNames and/or optionValues");
 280         }
 281 
 282         for (int i = 0; i < optionNames.length; i++) {
 283           vmOpts.add(prepareFlag(optionNames[i], optionValues[i]));
 284         }
 285         Collections.addAll(vmOpts, additionalVMOpts);
 286         Collections.addAll(vmOpts, "-version");
 287 
 288         ProcessBuilder processBuilder = ProcessTools.createJavaProcessBuilder(
 289                 vmOpts.toArray(new String[vmOpts.size()]));
 290 
 291         return new OutputAnalyzer(processBuilder.start());
 292     }
 293 
 294     /**
 295      * Verifies from the output that values of specified JVM options were the same as
 296      * expected values.
 297      *
 298      * @param outputAnalyzer search output for expect options and values.
 299      * @param optionNames names of tested options.
 300      * @param expectedValues expected values of tested options.
 301      * @throws Throwable if verification fails or some other issues occur.
 302      */
 303     public static void verifyOptionValuesFromOutput(OutputAnalyzer outputAnalyzer,
 304             String[] optionNames,
 305             String[] expectedValues) throws Throwable {
 306         outputAnalyzer.shouldHaveExitValue(0);
 307         for (int i = 0; i < optionNames.length; i++) {
 308           outputAnalyzer.shouldMatch(String.format(
 309                 CommandLineOptionTest.PRINT_FLAGS_FINAL_FORMAT,
 310                 optionNames[i], expectedValues[i]));
 311         }
 312     }
 313 
 314    /**
 315      * Verifies that value of specified JVM options are the same as
 316      * expected values.
 317      * Generates command line option flags from
 318      * {@code optionNames} and {@code expectedValues}.
 319      *
 320      * @param optionNames names of tested options.
 321      * @param expectedValues expected values of tested options.
 322      * @throws Throwable if verification fails or some other issues occur.
 323      */
 324     public static void verifyOptionValues(String[] optionNames,
 325             String[] expectedValues) throws Throwable {
 326        OutputAnalyzer outputAnalyzer = startVMWithOptions(optionNames, expectedValues, "-XX:+PrintFlagsFinal");
 327        verifyOptionValuesFromOutput(outputAnalyzer, optionNames, expectedValues);
 328     }
 329 
 330     /**
 331      * Verifies that value of specified JVM when type of newly started VM
 332      * is the same as the type of current.
 333      * This method filter out option with {@code optionName}
 334      * name from test java options.
 335      * Only mode flag will be passed to VM in addition to
 336      * {@code additionalVMOpts}
 337      *
 338      * @param optionName name of tested option.
 339      * @param expectedValue expected value of tested option.
 340      * @param optionErrorString message to show if option has another value
 341      * @param additionalVMOpts additional options that should be
 342      *                         passed to JVM.
 343      * @throws Throwable if verification fails or some other issues occur.
 344      */
 345     public static void verifyOptionValueForSameVM(String optionName,
 346             String expectedValue, String optionErrorString,
 347             String... additionalVMOpts) throws Throwable {
 348         List<String> finalOptions = new ArrayList<>();
 349         finalOptions.add(CommandLineOptionTest.getVMTypeOption());
 350         Collections.addAll(finalOptions, additionalVMOpts);
 351 
 352         CommandLineOptionTest.verifyOptionValue(optionName, expectedValue,
 353                 optionErrorString, false,
 354                 finalOptions.toArray(new String[finalOptions.size()]));
 355     }
 356 
 357     /**
 358      * Prepares boolean command line flag with name {@code name} according
 359      * to it's {@code value}.
 360      *
 361      * @param name the name of option to be prepared
 362      * @param value the value of option
 363      * @return prepared command line flag
 364      */
 365     public static String prepareBooleanFlag(String name, boolean value) {
 366         return String.format("-XX:%c%s", (value ? '+' : '-'), name);
 367     }
 368 
 369     /**
 370      * Prepares numeric command line flag with name {@code name} by setting
 371      * it's value to {@code value}.
 372      *
 373      * @param name the name of option to be prepared
 374      * @param value the value of option
 375      * @return prepared command line flag
 376      */
 377     public static String prepareNumericFlag(String name, Number value) {
 378         return String.format("-XX:%s=%s", name, value.toString());
 379     }
 380 
 381     /**
 382      * Prepares generic command line flag with name {@code name} by setting
 383      * it's value to {@code value}.
 384      *
 385      * @param name the name of option to be prepared
 386      * @param value the value of option ("+" or "-" can be used instead of "true" or "false")
 387      * @return prepared command line flag
 388      */
 389     public static String prepareFlag(String name, String value) {
 390         if (value.equals("+") || value.equalsIgnoreCase("true")) {
 391           return "-XX:+" + name;
 392       } else if (value.equals("-") || value.equalsIgnoreCase("false")) {
 393         return "-XX:-" + name;
 394       } else {
 395         return "-XX:" + name + "=" + value;
 396       }
 397     }
 398 
 399     /**
 400      * Returns message that should occur in VM output if option
 401      * {@code optionName} if unrecognized.
 402      *
 403      * @param optionName the name of option for which message should be returned
 404      * @return message saying that option {@code optionName} is unrecognized
 405      */
 406     public static String getUnrecognizedOptionErrorMessage(String optionName) {
 407         return String.format(
 408                 CommandLineOptionTest.UNRECOGNIZED_OPTION_ERROR_FORMAT,
 409                 optionName);
 410     }
 411 
 412     /**
 413      * Returns message that should occur in VM output if option
 414      * {@code optionName} is experimental and
 415      * -XX:+UnlockExperimentalVMOptions was not passed to VM.
 416      *
 417      * @param optionName the name of option for which message should be returned
 418      * @return message saying that option {@code optionName} is experimental
 419      */
 420     public static String getExperimentalOptionErrorMessage(String optionName) {
 421         return String.format(
 422                 CommandLineOptionTest.EXPERIMENTAL_OPTION_ERROR_FORMAT,
 423                 optionName);
 424     }
 425 
 426     /**
 427      * Returns message that should occur in VM output if option
 428      * {@code optionName} is diagnostic and -XX:+UnlockDiagnosticVMOptions
 429      * was not passed to VM.
 430      *
 431      * @param optionName the name of option for which message should be returned
 432      * @return message saying that option {@code optionName} is diganostic
 433      */
 434     public static String getDiagnosticOptionErrorMessage(String optionName) {
 435         return String.format(
 436                 CommandLineOptionTest.DIAGNOSTIC_OPTION_ERROR_FORMAT,
 437                 optionName);
 438     }
 439 
 440     /**
 441      * @return option required to start a new VM with the same type as current.
 442      * @throws RuntimeException when VM type is unknown.
 443      */
 444     private static String getVMTypeOption() {
 445         if (Platform.isServer()) {
 446             return "-server";
 447         } else if (Platform.isClient()) {
 448             return "-client";
 449         } else if (Platform.isMinimal()) {
 450             return "-minimal";
 451         } else if (Platform.isGraal()) {
 452             return "-graal";
 453         }
 454         throw new RuntimeException("Unknown VM mode.");
 455     }
 456 
 457     private final BooleanSupplier predicate;
 458 
 459     /**
 460      * Constructs new CommandLineOptionTest that will be executed only if
 461      * predicate {@code predicate} return {@code true}.
 462      * @param predicate a predicate responsible for test's preconditions check.
 463      */
 464     public CommandLineOptionTest(BooleanSupplier predicate) {
 465         this.predicate = predicate;
 466     }
 467 
 468     /**
 469      * Runs command line option test.
 470      */
 471     public final void test() throws Throwable {
 472         if (predicate.getAsBoolean()) {
 473             runTestCases();
 474         }
 475     }
 476 
 477     /**
 478      * @throws Throwable if some issue happened during test cases execution.
 479      */
 480     protected abstract void runTestCases() throws Throwable;
 481 }