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 }