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 behaviour 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      * @throws Throwable if verification fails or some other issues occur.
  63      */
  64     public static void verifyJVMStartup(String option,
  65             String expectedMessages[], String unexpectedMessages[],
  66             ExitCode exitCode) throws Throwable {
  67         CommandLineOptionTest.verifyJVMStartup(expectedMessages,
  68                 unexpectedMessages, exitCode, false, option);
  69     }
  70 
  71     /**
  72      * Verifies that JVM startup behaviour matches our expectations.
  73      *
  74      * @param expectedMessages an array of patterns that should occur
  75      *                         in JVM output. If {@code null} then
  76      *                         JVM output could be empty.
  77      * @param unexpectedMessages an array of patterns that should not
  78      *                           occur in JVM output. If {@code null} then
  79      *                           JVM output could be empty.
  80      * @param exitCode expected exit code.
  81      * @param addTestVMOptions if {@code true} then test VM options will be
  82      *                         passed to VM.
  83      * @param options options that should be passed to VM in addition to mode
  84      *                flag.
  85      * @throws Throwable if verification fails or some other issues occur.
  86      */
  87     public static void verifyJVMStartup(String expectedMessages[],
  88             String unexpectedMessages[], ExitCode exitCode,
  89             boolean addTestVMOptions, String... options) throws Throwable {
  90         List<String> finalOptions = new ArrayList<>();
  91         if (addTestVMOptions) {
  92             Collections.addAll(finalOptions, Utils.getTestJavaOpts());
  93         }
  94         Collections.addAll(finalOptions, options);
  95         finalOptions.add("-version");
  96 
  97         ProcessBuilder processBuilder
  98                 = ProcessTools.createJavaProcessBuilder(finalOptions.toArray(
  99                 new String[finalOptions.size()]));
 100         OutputAnalyzer outputAnalyzer
 101                 = new OutputAnalyzer(processBuilder.start());
 102         outputAnalyzer.shouldHaveExitValue(exitCode.value);
 103 
 104         if (expectedMessages != null) {
 105             for (String expectedMessage : expectedMessages) {
 106                 outputAnalyzer.shouldMatch(expectedMessage);
 107             }
 108         }
 109 
 110         if (unexpectedMessages != null) {
 111             for (String unexpectedMessage : unexpectedMessages) {
 112                 outputAnalyzer.shouldNotMatch(unexpectedMessage);
 113             }
 114         }
 115     }
 116 
 117     /**
 118      * Verifies that JVM startup behaviour matches our expectations when type
 119      * of newly started VM is the same as the type of current.
 120      *
 121      * @param expectedMessages an array of patterns that should occur
 122      *                         in JVM output. If {@code null} then
 123      *                         JVM output could be empty.
 124      * @param unexpectedMessages an array of patterns that should not
 125      *                           occur in JVM output. If {@code null} then
 126      *                           JVM output could be empty.
 127      * @param exitCode expected exit code.
 128      * @param options options that should be passed to VM in addition to mode
 129      *                flag.
 130      * @throws Throwable if verification fails or some other issues occur.
 131      */
 132     public static void verifySameJVMStartup(String expectedMessages[],
 133             String unexpectedMessages[], ExitCode exitCode, String... options)
 134             throws  Throwable {
 135         List<String> finalOptions = new ArrayList<>();
 136         finalOptions.add(CommandLineOptionTest.getVMTypeOption());
 137         Collections.addAll(finalOptions, options);
 138 
 139         CommandLineOptionTest.verifyJVMStartup(expectedMessages,
 140                 unexpectedMessages, exitCode, false,
 141                 finalOptions.toArray(new String[finalOptions.size()]));
 142     }
 143 
 144     /**
 145      * Verifies that value of specified JVM option is the same as
 146      * expected value.
 147      * This method filter out option with {@code optionName}
 148      * name from test java options.
 149      *
 150      * @param optionName a name of tested option.
 151      * @param expectedValue expected value of tested option.
 152      * @param additionalVMOpts additional options that should be
 153      *                         passed to JVM.
 154      * @throws Throwable if verification fails or some other issues occur.
 155      */
 156     public static void verifyOptionValue(String optionName,
 157             String expectedValue, String... additionalVMOpts) throws Throwable {
 158         verifyOptionValue(optionName, expectedValue, true, additionalVMOpts);
 159     }
 160 
 161     /**
 162      * Verifies that value of specified JVM option is the same as
 163      * expected value.
 164      * This method filter out option with {@code optionName}
 165      * name from test java options.
 166      *
 167      * @param optionName a name of tested option.
 168      * @param expectedValue expected value of tested option.
 169      * @param addTestVmOptions if {@code true}, then test VM options
 170      *                         will be used.
 171      * @param additionalVMOpts additional options that should be
 172      *                         passed to JVM.
 173      * @throws Throwable if verification fails or some other issues
 174      *                          occur.
 175      */
 176     public static void verifyOptionValue(String optionName,
 177             String expectedValue, boolean addTestVmOptions,
 178             String... additionalVMOpts) throws Throwable {
 179         List<String> vmOpts = new ArrayList<>();
 180 
 181         if (addTestVmOptions) {
 182             Collections.addAll(vmOpts,
 183                                Utils.getFilteredTestJavaOpts(optionName));
 184         }
 185         Collections.addAll(vmOpts, additionalVMOpts);
 186         Collections.addAll(vmOpts, "-XX:+PrintFlagsFinal", "-version");
 187 
 188         ProcessBuilder processBuilder = ProcessTools.createJavaProcessBuilder(
 189                 vmOpts.toArray(new String[vmOpts.size()]));
 190 
 191         OutputAnalyzer outputAnalyzer
 192                 = new OutputAnalyzer(processBuilder.start());
 193 
 194         outputAnalyzer.shouldHaveExitValue(0);
 195         outputAnalyzer.shouldMatch(String.format(
 196                 CommandLineOptionTest.PRINT_FLAGS_FINAL_FORMAT,
 197                 optionName, expectedValue));
 198     }
 199 
 200     /**
 201      * Verifies that value of specified JVM when type of newly started VM
 202      * is the same as the type of current.
 203      * This method filter out option with {@code optionName}
 204      * name from test java options.
 205      * Only mode flag will be passed to VM in addition to
 206      * {@code additionalVMOpts}
 207      *
 208      * @param optionName name of tested option.
 209      * @param expectedValue expected value of tested option.
 210      * @param additionalVMOpts additional options that should be
 211      *                         passed to JVM.
 212      * @throws Throwable if verification fails or some other issues occur.
 213      */
 214     public static void verifyOptionValueForSameVM(String optionName,
 215             String expectedValue, String... additionalVMOpts) throws Throwable {
 216         List<String> finalOptions = new ArrayList<>();
 217         finalOptions.add(CommandLineOptionTest.getVMTypeOption());
 218         Collections.addAll(finalOptions, additionalVMOpts);
 219 
 220         CommandLineOptionTest.verifyOptionValue(optionName, expectedValue,
 221                 false, finalOptions.toArray(new String[finalOptions.size()]));
 222     }
 223 
 224     /**
 225      * Prepares boolean command line flag with name {@code name} according
 226      * to it's {@code value}.
 227      *
 228      * @param name the name of option to be prepared
 229      * @param value the value of option
 230      * @return prepared command line flag
 231      */
 232     public static String prepareBooleanFlag(String name, boolean value) {
 233         return String.format("-XX:%c%s", (value ? '+' : '-'), name);
 234     }
 235 
 236     /**
 237      * Prepares numeric command line flag with name {@code name} by setting
 238      * it's value to {@code value}.
 239      *
 240      * @param name the name of option to be prepared
 241      * @param value the value of option
 242      * @return prepared command line flag
 243      */
 244     public static String prepareNumericFlag(String name, Number value) {
 245         return String.format("-XX:%s=%s", name, value.toString());
 246     }
 247 
 248     /**
 249      * Returns message that should occur in VM output if option
 250      * {@code optionName} if unrecognized.
 251      *
 252      * @param optionName the name of option for which message should be returned
 253      * @return message saying that option {@code optionName} is unrecognized
 254      */
 255     public static String getUnrecognizedOptionErrorMessage(String optionName) {
 256         return String.format(
 257                 CommandLineOptionTest.UNRECOGNIZED_OPTION_ERROR_FORMAT,
 258                 optionName);
 259     }
 260 
 261     /**
 262      * Returns message that should occur in VM output if option
 263      * {@code optionName} is experimental and
 264      * -XX:+UnlockExperimentalVMOptions was not passed to VM.
 265      *
 266      * @param optionName the name of option for which message should be returned
 267      * @return message saying that option {@code optionName} is experimental
 268      */
 269     public static String getExperimentalOptionErrorMessage(String optionName) {
 270         return String.format(
 271                 CommandLineOptionTest.EXPERIMENTAL_OPTION_ERROR_FORMAT,
 272                 optionName);
 273     }
 274 
 275     /**
 276      * Returns message that should occur in VM output if option
 277      * {@code optionName} is diagnostic and -XX:+UnlockDiagnosticVMOptions
 278      * was not passed to VM.
 279      *
 280      * @param optionName the name of option for which message should be returned
 281      * @return message saying that option {@code optionName} is diganostic
 282      */
 283     public static String getDiagnosticOptionErrorMessage(String optionName) {
 284         return String.format(
 285                 CommandLineOptionTest.DIAGNOSTIC_OPTION_ERROR_FORMAT,
 286                 optionName);
 287     }
 288 
 289     /**
 290      * @return option required to start a new VM with the same type as current.
 291      * @throws RuntimeException when VM type is unknown.
 292      */
 293     private static String getVMTypeOption() {
 294         if (Platform.isServer()) {
 295             return "-server";
 296         } else if (Platform.isClient()) {
 297             return "-client";
 298         } else if (Platform.isMinimal()) {
 299             return "-minimal";
 300         } else if (Platform.isGraal()) {
 301             return "-graal";
 302         }
 303         throw new RuntimeException("Unknown VM mode.");
 304     }
 305 
 306     private final BooleanSupplier predicate;
 307 
 308     /**
 309      * Constructs new CommandLineOptionTest that will be executed only if
 310      * predicate {@code predicate} return {@code true}.
 311      * @param predicate a predicate responsible for test's preconditions check.
 312      */
 313     public CommandLineOptionTest(BooleanSupplier predicate) {
 314         this.predicate = predicate;
 315     }
 316 
 317     /**
 318      * Runs command line option test.
 319      */
 320     public final void test() throws Throwable {
 321         if (predicate.getAsBoolean()) {
 322             runTestCases();
 323         }
 324     }
 325 
 326     /**
 327      * @throws Throwable if some issue happened during test cases execution.
 328      */
 329     protected abstract void runTestCases() throws Throwable;
 330 }