1 /* 2 * Copyright (c) 2014, 2017, 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.management.InputArguments; 32 import jdk.test.lib.process.ExitCode; 33 import jdk.test.lib.process.ProcessTools; 34 import jdk.test.lib.process.OutputAnalyzer; 35 import jdk.test.lib.Platform; 36 import jdk.test.lib.Utils; 37 38 /** 39 * Base class for command line option tests. 40 */ 41 public abstract class CommandLineOptionTest { 42 public static final String UNLOCK_DIAGNOSTIC_VM_OPTIONS 43 = "-XX:+UnlockDiagnosticVMOptions"; 44 public static final String UNLOCK_EXPERIMENTAL_VM_OPTIONS 45 = "-XX:+UnlockExperimentalVMOptions"; 46 protected static final String UNRECOGNIZED_OPTION_ERROR_FORMAT 47 = "Unrecognized VM option '[+-]?%s(=.*)?'"; 48 protected static final String EXPERIMENTAL_OPTION_ERROR_FORMAT 49 = "VM option '%s' is experimental and must be enabled via " 50 + "-XX:\\+UnlockExperimentalVMOptions."; 51 protected static final String DIAGNOSTIC_OPTION_ERROR_FORMAT 52 = " VM option '%s' is diagnostic and must be enabled via " 53 + "-XX:\\+UnlockDiagnosticVMOptions."; 54 private static final String PRINT_FLAGS_FINAL_FORMAT = "%s\\s*:?=\\s*%s"; 55 56 /** 57 * Verifies that JVM startup behavior matches our expectations. 58 * 59 * @param option an option that should be passed to JVM 60 * @param expectedMessages an array of patterns that should occur 61 * in JVM output. If {@code null} then 62 * JVM output could be empty. 63 * @param unexpectedMessages an array of patterns that should not 64 * occur in JVM output. If {@code null} then 65 * JVM output could be empty. 66 * @param exitErrorMessage message that will be shown if exit code is not 67 * as expected. 68 * @param wrongWarningMessage message that will be shown if warning 69 * messages are not as expected. 70 * @param exitCode expected exit code. 71 * @throws Throwable if verification fails or some other issues occur. 72 */ 73 public static void verifyJVMStartup(String option, 74 String expectedMessages[], String unexpectedMessages[], 75 String exitErrorMessage, String wrongWarningMessage, 76 ExitCode exitCode) throws Throwable { 77 CommandLineOptionTest.verifyJVMStartup(expectedMessages, 78 unexpectedMessages, exitErrorMessage, 79 wrongWarningMessage, exitCode, false, option); 80 } 81 82 /** 83 * Verifies that JVM startup behavior matches our expectations. 84 * 85 * @param expectedMessages an array of patterns that should occur 86 * in JVM output. If {@code null} then 87 * JVM output could be empty. 88 * @param unexpectedMessages an array of patterns that should not 89 * occur in JVM output. If {@code null} then 90 * JVM output could be empty. 91 * @param exitErrorMessage message that will be shown if exit code is not 92 * as expected. 93 * @param wrongWarningMessage message that will be shown if warning 94 * messages are not as expected. 95 * @param exitCode expected exit code. 96 * @param addTestVMOptions if {@code true} then test VM options will be 97 * passed to VM. 98 * @param options options that should be passed to VM in addition to mode 99 * flag. 100 * @throws Throwable if verification fails or some other issues occur. 101 */ 102 public static void verifyJVMStartup(String expectedMessages[], 103 String unexpectedMessages[], String exitErrorMessage, 104 String wrongWarningMessage, ExitCode exitCode, 105 boolean addTestVMOptions, String... options) 106 throws Throwable { 107 List<String> finalOptions = new ArrayList<>(); 108 if (addTestVMOptions) { 109 Collections.addAll(finalOptions, InputArguments.getVmInputArgs()); 110 Collections.addAll(finalOptions, Utils.getTestJavaOpts()); 111 } 112 Collections.addAll(finalOptions, options); 113 finalOptions.add("-version"); 114 115 ProcessBuilder processBuilder 116 = ProcessTools.createJavaProcessBuilder(finalOptions.toArray( 117 new String[finalOptions.size()])); 118 OutputAnalyzer outputAnalyzer 119 = new OutputAnalyzer(processBuilder.start()); 120 121 try { 122 outputAnalyzer.shouldHaveExitValue(exitCode.value); 123 } catch (RuntimeException e) { 124 String errorMessage = String.format( 125 "JVM process should have exit value '%d'.%n%s", 126 exitCode.value, exitErrorMessage); 127 throw new AssertionError(errorMessage, e); 128 } 129 130 verifyOutput(expectedMessages, unexpectedMessages, 131 wrongWarningMessage, outputAnalyzer); 132 } 133 134 /** 135 * Verifies that JVM startup behavior matches our expectations. 136 * 137 * @param expectedMessages an array of patterns that should occur in JVM 138 * output. If {@code null} then 139 * JVM output could be empty. 140 * @param unexpectedMessages an array of patterns that should not occur 141 * in JVM output. If {@code null} then 142 * JVM output could be empty. 143 * @param wrongWarningMessage message that will be shown if messages are 144 * not as expected. 145 * @param outputAnalyzer OutputAnalyzer instance 146 * @throws AssertionError if verification fails. 147 */ 148 public static void verifyOutput(String[] expectedMessages, 149 String[] unexpectedMessages, String wrongWarningMessage, 150 OutputAnalyzer outputAnalyzer) { 151 if (expectedMessages != null) { 152 for (String expectedMessage : expectedMessages) { 153 try { 154 outputAnalyzer.shouldMatch(expectedMessage); 155 } catch (RuntimeException e) { 156 String errorMessage = String.format( 157 "Expected message not found: '%s'.%n%s", 158 expectedMessage, wrongWarningMessage); 159 throw new AssertionError(errorMessage, e); 160 } 161 } 162 } 163 164 if (unexpectedMessages != null) { 165 for (String unexpectedMessage : unexpectedMessages) { 166 try { 167 outputAnalyzer.shouldNotMatch(unexpectedMessage); 168 } catch (RuntimeException e) { 169 String errorMessage = String.format( 170 "Unexpected message found: '%s'.%n%s", 171 unexpectedMessage, wrongWarningMessage); 172 throw new AssertionError(errorMessage, e); 173 } 174 } 175 } 176 } 177 178 /** 179 * Verifies that JVM startup behavior matches our expectations when type 180 * of newly started VM is the same as the type of current. 181 * 182 * @param expectedMessages an array of patterns that should occur 183 * in JVM output. If {@code null} then 184 * JVM output could be empty. 185 * @param unexpectedMessages an array of patterns that should not 186 * occur in JVM output. If {@code null} then 187 * JVM output could be empty. 188 * @param exitErrorMessage Message that will be shown if exit value is not 189 * as expected. 190 * @param wrongWarningMessage message that will be shown if warning 191 * messages are not as expected. 192 * @param exitCode expected exit code. 193 * @param options options that should be passed to VM in addition to mode 194 * flag. 195 * @throws Throwable if verification fails or some other issues occur. 196 */ 197 public static void verifySameJVMStartup(String expectedMessages[], 198 String unexpectedMessages[], String exitErrorMessage, 199 String wrongWarningMessage, ExitCode exitCode, String... options) 200 throws Throwable { 201 List<String> finalOptions = new ArrayList<>(); 202 finalOptions.add(CommandLineOptionTest.getVMTypeOption()); 203 String extraFlagForEmulated = CommandLineOptionTest.getVMTypeOptionForEmulated(); 204 if (extraFlagForEmulated != null) { 205 finalOptions.add(extraFlagForEmulated); 206 } 207 Collections.addAll(finalOptions, options); 208 209 CommandLineOptionTest.verifyJVMStartup(expectedMessages, 210 unexpectedMessages, exitErrorMessage, 211 wrongWarningMessage, exitCode, false, 212 finalOptions.toArray(new String[finalOptions.size()])); 213 } 214 215 /** 216 * Verifies that value of specified JVM option is the same as 217 * expected value. 218 * This method filter out option with {@code optionName} 219 * name from test java options. 220 * 221 * @param optionName a name of tested option. 222 * @param expectedValue expected value of tested option. 223 * @param optionErrorString message will be shown if option value is not as 224 * expected. 225 * @param additionalVMOpts additional options that should be 226 * passed to JVM. 227 * @throws Throwable if verification fails or some other issues occur. 228 */ 229 public static void verifyOptionValue(String optionName, 230 String expectedValue, String optionErrorString, 231 String... additionalVMOpts) throws Throwable { 232 verifyOptionValue(optionName, expectedValue, optionErrorString, 233 true, additionalVMOpts); 234 } 235 236 /** 237 * Verifies that value of specified JVM option is the same as 238 * expected value. 239 * This method filter out option with {@code optionName} 240 * name from test java options. 241 * 242 * @param optionName a name of tested option. 243 * @param expectedValue expected value of tested option. 244 * @param addTestVmOptions if {@code true}, then test VM options 245 * will be used. 246 * @param optionErrorString message will be shown if option value is not as 247 * expected. 248 * @param additionalVMOpts additional options that should be 249 * passed to JVM. 250 * @throws Throwable if verification fails or some other issues 251 * occur. 252 */ 253 public static void verifyOptionValue(String optionName, 254 String expectedValue, String optionErrorString, 255 boolean addTestVmOptions, String... additionalVMOpts) 256 throws Throwable { 257 List<String> vmOpts = new ArrayList<>(); 258 259 if (addTestVmOptions) { 260 Collections.addAll(vmOpts, 261 Utils.getFilteredTestJavaOpts(optionName)); 262 } 263 Collections.addAll(vmOpts, additionalVMOpts); 264 Collections.addAll(vmOpts, "-XX:+PrintFlagsFinal", "-version"); 265 266 ProcessBuilder processBuilder = ProcessTools.createJavaProcessBuilder( 267 vmOpts.toArray(new String[vmOpts.size()])); 268 269 OutputAnalyzer outputAnalyzer 270 = new OutputAnalyzer(processBuilder.start()); 271 272 try { 273 outputAnalyzer.shouldHaveExitValue(0); 274 } catch (RuntimeException e) { 275 String errorMessage = String.format( 276 "JVM should start with option '%s' without errors.", 277 optionName); 278 throw new AssertionError(errorMessage, e); 279 } 280 verifyOptionValue(optionName, expectedValue, optionErrorString, 281 outputAnalyzer); 282 } 283 284 /** 285 * Verifies that value of specified JVM option is the same as 286 * expected value. 287 * 288 * @param optionName a name of tested option. 289 * @param expectedValue expected value of tested option. 290 * @param optionErrorString message will be shown if option value is not 291 * as expected. 292 * @param outputAnalyzer OutputAnalyzer instance 293 * @throws AssertionError if verification fails 294 */ 295 public static void verifyOptionValue(String optionName, 296 String expectedValue, String optionErrorString, 297 OutputAnalyzer outputAnalyzer) { 298 try { 299 outputAnalyzer.shouldMatch(String.format( 300 CommandLineOptionTest.PRINT_FLAGS_FINAL_FORMAT, 301 optionName, expectedValue)); 302 } catch (RuntimeException e) { 303 String errorMessage = String.format( 304 "Option '%s' is expected to have '%s' value%n%s", 305 optionName, expectedValue, 306 optionErrorString); 307 throw new AssertionError(errorMessage, e); 308 } 309 } 310 311 /** 312 * Start VM with given options and values. 313 * Generates command line option flags from 314 * {@code optionNames} and {@code optionValues}. 315 * 316 * @param optionNames names of options to pass in 317 * @param optionValues values of option 318 * @param additionalVMOpts additional options that should be 319 * passed to JVM. 320 * @return output from vm process 321 */ 322 public static OutputAnalyzer startVMWithOptions(String[] optionNames, 323 String[] optionValues, 324 String... additionalVMOpts) throws Throwable { 325 List<String> vmOpts = new ArrayList<>(); 326 if (optionNames == null || optionValues == null || optionNames.length != optionValues.length) { 327 throw new IllegalArgumentException("optionNames and/or optionValues"); 328 } 329 330 for (int i = 0; i < optionNames.length; i++) { 331 vmOpts.add(prepareFlag(optionNames[i], optionValues[i])); 332 } 333 Collections.addAll(vmOpts, additionalVMOpts); 334 Collections.addAll(vmOpts, "-version"); 335 336 ProcessBuilder processBuilder = ProcessTools.createJavaProcessBuilder( 337 vmOpts.toArray(new String[vmOpts.size()])); 338 339 return new OutputAnalyzer(processBuilder.start()); 340 } 341 342 /** 343 * Verifies from the output that values of specified JVM options were the same as 344 * expected values. 345 * 346 * @param outputAnalyzer search output for expect options and values. 347 * @param optionNames names of tested options. 348 * @param expectedValues expected values of tested options. 349 * @throws Throwable if verification fails or some other issues occur. 350 */ 351 public static void verifyOptionValuesFromOutput(OutputAnalyzer outputAnalyzer, 352 String[] optionNames, 353 String[] expectedValues) throws Throwable { 354 outputAnalyzer.shouldHaveExitValue(0); 355 for (int i = 0; i < optionNames.length; i++) { 356 outputAnalyzer.shouldMatch(String.format( 357 CommandLineOptionTest.PRINT_FLAGS_FINAL_FORMAT, 358 optionNames[i], expectedValues[i])); 359 } 360 } 361 362 /** 363 * Verifies that value of specified JVM options are the same as 364 * expected values. 365 * Generates command line option flags from 366 * {@code optionNames} and {@code expectedValues}. 367 * 368 * @param optionNames names of tested options. 369 * @param expectedValues expected values of tested options. 370 * @throws Throwable if verification fails or some other issues occur. 371 */ 372 public static void verifyOptionValues(String[] optionNames, 373 String[] expectedValues) throws Throwable { 374 OutputAnalyzer outputAnalyzer = startVMWithOptions(optionNames, expectedValues, "-XX:+PrintFlagsFinal"); 375 verifyOptionValuesFromOutput(outputAnalyzer, optionNames, expectedValues); 376 } 377 378 /** 379 * Verifies that value of specified JVM when type of newly started VM 380 * is the same as the type of current. 381 * This method filter out option with {@code optionName} 382 * name from test java options. 383 * Only mode flag will be passed to VM in addition to 384 * {@code additionalVMOpts} 385 * 386 * @param optionName name of tested option. 387 * @param expectedValue expected value of tested option. 388 * @param optionErrorString message to show if option has another value 389 * @param additionalVMOpts additional options that should be 390 * passed to JVM. 391 * @throws Throwable if verification fails or some other issues occur. 392 */ 393 public static void verifyOptionValueForSameVM(String optionName, 394 String expectedValue, String optionErrorString, 395 String... additionalVMOpts) throws Throwable { 396 List<String> finalOptions = new ArrayList<>(); 397 finalOptions.add(CommandLineOptionTest.getVMTypeOption()); 398 String extraFlagForEmulated = CommandLineOptionTest.getVMTypeOptionForEmulated(); 399 if (extraFlagForEmulated != null) { 400 finalOptions.add(extraFlagForEmulated); 401 } 402 Collections.addAll(finalOptions, additionalVMOpts); 403 404 CommandLineOptionTest.verifyOptionValue(optionName, expectedValue, 405 optionErrorString, false, 406 finalOptions.toArray(new String[finalOptions.size()])); 407 } 408 409 /** 410 * Prepares boolean command line flag with name {@code name} according 411 * to it's {@code value}. 412 * 413 * @param name the name of option to be prepared 414 * @param value the value of option 415 * @return prepared command line flag 416 */ 417 public static String prepareBooleanFlag(String name, boolean value) { 418 return String.format("-XX:%c%s", (value ? '+' : '-'), name); 419 } 420 421 /** 422 * Prepares numeric command line flag with name {@code name} by setting 423 * it's value to {@code value}. 424 * 425 * @param name the name of option to be prepared 426 * @param value the value of option 427 * @return prepared command line flag 428 */ 429 public static String prepareNumericFlag(String name, Number value) { 430 return String.format("-XX:%s=%s", name, value.toString()); 431 } 432 433 /** 434 * Prepares generic command line flag with name {@code name} by setting 435 * it's value to {@code value}. 436 * 437 * @param name the name of option to be prepared 438 * @param value the value of option ("+" or "-" can be used instead of "true" or "false") 439 * @return prepared command line flag 440 */ 441 public static String prepareFlag(String name, String value) { 442 if (value.equals("+") || value.equalsIgnoreCase("true")) { 443 return "-XX:+" + name; 444 } else if (value.equals("-") || value.equalsIgnoreCase("false")) { 445 return "-XX:-" + name; 446 } else { 447 return "-XX:" + name + "=" + value; 448 } 449 } 450 451 /** 452 * Returns message that should occur in VM output if option 453 * {@code optionName} if unrecognized. 454 * 455 * @param optionName the name of option for which message should be returned 456 * @return message saying that option {@code optionName} is unrecognized 457 */ 458 public static String getUnrecognizedOptionErrorMessage(String optionName) { 459 return String.format( 460 CommandLineOptionTest.UNRECOGNIZED_OPTION_ERROR_FORMAT, 461 optionName); 462 } 463 464 /** 465 * Returns message that should occur in VM output if option 466 * {@code optionName} is experimental and 467 * -XX:+UnlockExperimentalVMOptions was not passed to VM. 468 * 469 * @param optionName the name of option for which message should be returned 470 * @return message saying that option {@code optionName} is experimental 471 */ 472 public static String getExperimentalOptionErrorMessage(String optionName) { 473 return String.format( 474 CommandLineOptionTest.EXPERIMENTAL_OPTION_ERROR_FORMAT, 475 optionName); 476 } 477 478 /** 479 * Returns message that should occur in VM output if option 480 * {@code optionName} is diagnostic and -XX:+UnlockDiagnosticVMOptions 481 * was not passed to VM. 482 * 483 * @param optionName the name of option for which message should be returned 484 * @return message saying that option {@code optionName} is diganostic 485 */ 486 public static String getDiagnosticOptionErrorMessage(String optionName) { 487 return String.format( 488 CommandLineOptionTest.DIAGNOSTIC_OPTION_ERROR_FORMAT, 489 optionName); 490 } 491 492 /** 493 * @return option required to start a new VM with the same type as current. 494 * @throws RuntimeException when VM type is unknown. 495 */ 496 private static String getVMTypeOption() { 497 if (Platform.isServer()) { 498 return "-server"; 499 } else if (Platform.isClient()) { 500 return "-client"; 501 } else if (Platform.isMinimal()) { 502 return "-minimal"; 503 } else if (Platform.isGraal()) { 504 return "-graal"; 505 } 506 throw new RuntimeException("Unknown VM mode."); 507 } 508 509 /** 510 * @return addtional VMoptions(Emulated related) required to start a new VM with the same type as current. 511 */ 512 private static String getVMTypeOptionForEmulated() { 513 if (Platform.isServer() && !Platform.isEmulatedClient()) { 514 return "-XX:-NeverActAsServerClassMachine"; 515 } else if (Platform.isEmulatedClient()) { 516 return "-XX:+NeverActAsServerClassMachine"; 517 } 518 return null; 519 } 520 521 private final BooleanSupplier predicate; 522 523 /** 524 * Constructs new CommandLineOptionTest that will be executed only if 525 * predicate {@code predicate} return {@code true}. 526 * @param predicate a predicate responsible for test's preconditions check. 527 */ 528 public CommandLineOptionTest(BooleanSupplier predicate) { 529 this.predicate = predicate; 530 } 531 532 /** 533 * Runs command line option test. 534 */ 535 public final void test() throws Throwable { 536 if (predicate.getAsBoolean()) { 537 runTestCases(); 538 } 539 } 540 541 /** 542 * @throws Throwable if some issue happened during test cases execution. 543 */ 544 protected abstract void runTestCases() throws Throwable; 545 }