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 }