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