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 }