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