1 /* 2 * Copyright (c) 2015, 2018, 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 package optionsvalidation; 24 25 import com.sun.tools.attach.VirtualMachine; 26 import com.sun.tools.attach.AttachOperationFailedException; 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.HashSet; 30 import java.util.List; 31 import java.util.Set; 32 import jdk.test.lib.management.DynamicVMOption; 33 import jdk.test.lib.process.OutputAnalyzer; 34 import jdk.test.lib.process.ProcessTools; 35 import jdk.test.lib.dcmd.CommandExecutor; 36 import jdk.test.lib.dcmd.JMXExecutor; 37 import jdk.test.lib.Platform; 38 import sun.tools.attach.HotSpotVirtualMachine; 39 40 import static optionsvalidation.JVMOptionsUtils.failedMessage; 41 import static optionsvalidation.JVMOptionsUtils.GCType; 42 import static optionsvalidation.JVMOptionsUtils.printOutputContent; 43 import static optionsvalidation.JVMOptionsUtils.VMType; 44 45 public abstract class JVMOption { 46 47 /** 48 * Executor for JCMD 49 */ 50 private final static CommandExecutor executor = new JMXExecutor(); 51 52 /** 53 * Name of the tested parameter 54 */ 55 protected String name; 56 57 /** 58 * Range is defined for option inside VM 59 */ 60 protected boolean withRange; 61 62 /** 63 * Test valid min range value and additional small values 64 */ 65 protected boolean testMinRange; 66 67 /** 68 * Test valid max range value and additional big values 69 */ 70 protected boolean testMaxRange; 71 72 private Set<Integer> allowedExitCodes; 73 74 /** 75 * Prepend string which added before testing option to the command line 76 */ 77 private final List<String> prepend; 78 private final StringBuilder prependString; 79 80 protected JVMOption() { 81 this.prepend = new ArrayList<>(); 82 prependString = new StringBuilder(); 83 allowedExitCodes = new HashSet<>(); 84 allowedExitCodes.add(0); 85 allowedExitCodes.add(1); 86 withRange = false; 87 testMinRange = true; 88 testMaxRange = true; 89 } 90 91 /** 92 * Create JVM Option with given type and name. 93 * 94 * @param type type: "intx", "size_t", "uintx", "uint64_t" or "double" 95 * @param name name of the option 96 * @return created JVMOption 97 */ 98 static JVMOption createVMOption(String type, String name) { 99 JVMOption parameter; 100 101 switch (type) { 102 case "int": 103 case "intx": 104 case "size_t": 105 case "uint": 106 case "uintx": 107 case "uint64_t": 108 parameter = new IntJVMOption(name, type); 109 break; 110 case "double": 111 parameter = new DoubleJVMOption(name); 112 break; 113 default: 114 throw new Error("Expected only \"int\", \"intx\", \"size_t\", " 115 + "\"uint\", \"uintx\", \"uint64_t\", or \"double\" " 116 + "option types! Got " + type + " type!"); 117 } 118 119 return parameter; 120 } 121 122 /** 123 * Add passed options to the prepend options of the option. Prepend options 124 * will be added before testing option to the command line. 125 * 126 * @param options array of prepend options 127 */ 128 public final void addPrepend(String... options) { 129 String toAdd; 130 131 for (String option : options) { 132 if (option.startsWith("-")) { 133 toAdd = option; 134 } else { 135 /* Add "-" before parameter name */ 136 toAdd = "-" + option; 137 138 } 139 prepend.add(toAdd); 140 prependString.append(toAdd).append(" "); 141 } 142 } 143 144 /** 145 * Get name of the option 146 * 147 * @return name of the option 148 */ 149 final String getName() { 150 return name; 151 } 152 153 /** 154 * Mark this option as option which range is defined inside VM 155 */ 156 final void optionWithRange() { 157 withRange = true; 158 } 159 160 /** 161 * Exclude testing of min range value for this option 162 */ 163 public final void excludeTestMinRange() { 164 testMinRange = false; 165 } 166 167 /** 168 * Exclude testing of max range value for this option 169 */ 170 public final void excludeTestMaxRange() { 171 testMaxRange = false; 172 } 173 174 public final void setAllowedExitCodes(Integer... allowedExitCodes) { 175 this.allowedExitCodes.addAll(Arrays.asList(allowedExitCodes)); 176 } 177 178 /** 179 * Set new minimum option value 180 * 181 * @param min new minimum value 182 */ 183 abstract void setMin(String min); 184 185 /** 186 * Get string with minimum value of the option 187 * 188 * @return string with minimum value of the option 189 */ 190 abstract String getMin(); 191 192 /** 193 * Set new maximum option value 194 * 195 * @param max new maximum value 196 */ 197 abstract void setMax(String min); 198 199 /** 200 * Get string with maximum value of the option 201 * 202 * @return string with maximum value of the option 203 */ 204 abstract String getMax(); 205 206 /** 207 * Return list of strings with valid option values which used for testing 208 * using jcmd, attach and etc. 209 * 210 * @return list of strings which contain valid values for option 211 */ 212 protected abstract List<String> getValidValues(); 213 214 /** 215 * Return list of strings with invalid option values which used for testing 216 * using jcmd, attach and etc. 217 * 218 * @return list of strings which contain invalid values for option 219 */ 220 protected abstract List<String> getInvalidValues(); 221 222 /** 223 * Return expected error message for option with value "value" when it used 224 * on command line with passed value 225 * 226 * @param value option value 227 * @return expected error message 228 */ 229 protected abstract String getErrorMessageCommandLine(String value); 230 231 /** 232 * Testing writeable option using DynamicVMOption isValidValue and 233 * isInvalidValue methods 234 * 235 * @return number of failed tests 236 */ 237 public int testDynamic() { 238 DynamicVMOption option = new DynamicVMOption(name); 239 int failedTests = 0; 240 String origValue; 241 242 if (option.isWriteable()) { 243 244 System.out.println("Testing " + name + " option dynamically by DynamicVMOption"); 245 246 origValue = option.getValue(); 247 248 for (String value : getValidValues()) { 249 if (!option.isValidValue(value)) { 250 failedMessage(String.format("Option %s: Valid value \"%s\" is invalid", name, value)); 251 failedTests++; 252 } 253 } 254 255 for (String value : getInvalidValues()) { 256 if (option.isValidValue(value)) { 257 failedMessage(String.format("Option %s: Invalid value \"%s\" is valid", name, value)); 258 failedTests++; 259 } 260 } 261 262 option.setValue(origValue); 263 } 264 265 return failedTests; 266 } 267 268 /** 269 * Testing writeable option using Jcmd 270 * 271 * @return number of failed tests 272 */ 273 public int testJcmd() { 274 DynamicVMOption option = new DynamicVMOption(name); 275 int failedTests = 0; 276 OutputAnalyzer out; 277 String origValue; 278 279 if (option.isWriteable()) { 280 281 System.out.println("Testing " + name + " option dynamically by jcmd"); 282 283 origValue = option.getValue(); 284 285 for (String value : getValidValues()) { 286 out = executor.execute(String.format("VM.set_flag %s %s", name, value), true); 287 288 if (out.getOutput().contains(name + " error")) { 289 failedMessage(String.format("Option %s: Can not change " 290 + "option to valid value \"%s\" via jcmd", name, value)); 291 printOutputContent(out); 292 failedTests++; 293 } 294 } 295 296 for (String value : getInvalidValues()) { 297 out = executor.execute(String.format("VM.set_flag %s %s", name, value), true); 298 299 if (!out.getOutput().contains(name + " error")) { 300 failedMessage(String.format("Option %s: Error not reported for " 301 + "option when it chagned to invalid value \"%s\" via jcmd", name, value)); 302 printOutputContent(out); 303 failedTests++; 304 } 305 } 306 307 option.setValue(origValue); 308 } 309 310 return failedTests; 311 } 312 313 private boolean setFlagAttach(HotSpotVirtualMachine vm, String flagName, String flagValue) throws Exception { 314 boolean result; 315 316 try { 317 vm.setFlag(flagName, flagValue); 318 result = true; 319 } catch (AttachOperationFailedException e) { 320 result = false; 321 } 322 323 return result; 324 } 325 326 /** 327 * Testing writeable option using attach method 328 * 329 * @return number of failed tests 330 * @throws Exception if an error occurred while attaching to the target JVM 331 */ 332 public int testAttach() throws Exception { 333 DynamicVMOption option = new DynamicVMOption(name); 334 int failedTests = 0; 335 String origValue; 336 337 if (option.isWriteable()) { 338 339 System.out.println("Testing " + name + " option dynamically via attach"); 340 341 origValue = option.getValue(); 342 343 HotSpotVirtualMachine vm = (HotSpotVirtualMachine) VirtualMachine.attach(String.valueOf(ProcessTools.getProcessId())); 344 345 for (String value : getValidValues()) { 346 if (!setFlagAttach(vm, name, value)) { 347 failedMessage(String.format("Option %s: Can not change option to valid value \"%s\" via attach", name, value)); 348 failedTests++; 349 } 350 } 351 352 for (String value : getInvalidValues()) { 353 if (setFlagAttach(vm, name, value)) { 354 failedMessage(String.format("Option %s: Option changed to invalid value \"%s\" via attach", name, value)); 355 failedTests++; 356 } 357 } 358 359 vm.detach(); 360 361 option.setValue(origValue); 362 } 363 364 return failedTests; 365 } 366 367 /** 368 * Run java with passed parameter and check the result depending on the 369 * 'valid' parameter 370 * 371 * @param param tested parameter passed to the JVM 372 * @param valid indicates whether the JVM should fail or not 373 * @return true - if test passed 374 * @throws Exception if java process can not be started 375 */ 376 private boolean runJavaWithParam(String optionValue, boolean valid) throws Exception { 377 int exitCode = 0; 378 boolean result = true; 379 String errorMessage = null; 380 String explicitGC = null; 381 List<String> runJava = new ArrayList<>(); 382 OutputAnalyzer out = null; 383 384 if (VMType != null) { 385 runJava.add(VMType); 386 } 387 388 // Run with a small heap to avoid excessive execution time 389 long max = Runtime.getRuntime().maxMemory() / 1024 / 1024; 390 if (max > 1024) { 391 runJava.add("-Xmx1024m"); 392 } 393 394 if (Platform.isDebugBuild()) { 395 // Avoid excessive execution time. 396 runJava.add("-XX:-ZapUnusedHeapArea"); 397 } 398 399 if (GCType != null && 400 !(prepend.contains("-XX:+UseConcMarkSweepGC") || 401 prepend.contains("-XX:+UseSerialGC") || 402 prepend.contains("-XX:+UseParallelGC") || 403 prepend.contains("-XX:+UseG1GC"))) { 404 explicitGC = GCType; 405 } 406 407 if (explicitGC != null) { 408 runJava.add(explicitGC); 409 } 410 411 runJava.addAll(prepend); 412 runJava.add(optionValue); 413 runJava.add(JVMStartup.class.getName()); 414 415 out = new OutputAnalyzer(ProcessTools.createJavaProcessBuilder(runJava.toArray(new String[0])).start()); 416 417 exitCode = out.getExitValue(); 418 String exitCodeString = null; 419 if (exitCode != 0) { 420 exitCodeString = exitCode + " [0x" + Integer.toHexString(exitCode).toUpperCase() + "]"; 421 } 422 423 if (out.getOutput().contains("A fatal error has been detected by the Java Runtime Environment")) { 424 /* Always consider "fatal error" in output as fail */ 425 errorMessage = "JVM output reports a fatal error. JVM exited with code " + exitCodeString + "!"; 426 } else if (out.getStderr().contains("Ignoring option " + name)) { 427 // Watch for newly obsoleted, but not yet removed, flags 428 System.out.println("SKIPPED: Ignoring test result for obsolete flag " + name); 429 } else if (valid == true) { 430 if (!allowedExitCodes.contains(exitCode)) { 431 errorMessage = "JVM exited with unexpected error code = " + exitCodeString; 432 } else if ((exitCode != 0) && (out.getOutput().isEmpty() == true)) { 433 errorMessage = "JVM exited with error(exitcode == " + exitCodeString + "), but with empty stdout and stderr. " + 434 "Description of error is needed!"; 435 } else if (out.getOutput().contains("is outside the allowed range")) { 436 errorMessage = "JVM output contains \"is outside the allowed range\""; 437 } 438 } else { 439 // valid == false 440 String value = optionValue.substring(optionValue.lastIndexOf("=") + 1); 441 String errorMessageCommandLineValue = getErrorMessageCommandLine(value); 442 if (exitCode == 0) { 443 errorMessage = "JVM successfully exit"; 444 } else if (exitCode != 1) { 445 errorMessage = "JVM exited with code " + exitCodeString + " which does not equal to 1"; 446 } else if (!out.getOutput().contains(errorMessageCommandLineValue)) { 447 errorMessage = "JVM output does not contain expected output \"" + errorMessageCommandLineValue + "\""; 448 } 449 } 450 451 if (errorMessage != null) { 452 String fullOptionString = String.format("%s %s %s %s", 453 VMType == null ? "" : VMType, explicitGC == null ? "" : explicitGC, prependString.toString(), optionValue).trim().replaceAll(" +", " "); 454 failedMessage(name, fullOptionString, valid, errorMessage); 455 printOutputContent(out); 456 result = false; 457 } 458 459 System.out.println(""); 460 461 return result; 462 } 463 464 /** 465 * Construct option string with passed value 466 * 467 * @param value parameter value 468 * @return string containing option with passed value 469 */ 470 private String constructOption(String value) { 471 return "-XX:" + name + "=" + value; 472 } 473 474 /** 475 * Return list of strings which contain options with valid values which can 476 * be used for testing on command line 477 * 478 * @return list of strings which contain options with valid values 479 */ 480 private List<String> getValidCommandLineOptions() { 481 List<String> validParameters = new ArrayList<>(); 482 483 for (String value : getValidValues()) { 484 validParameters.add(constructOption(value)); 485 } 486 487 return validParameters; 488 } 489 490 /** 491 * Return list of strings which contain options with invalid values which 492 * can be used for testing on command line 493 * 494 * @return list of strings which contain options with invalid values 495 */ 496 private List<String> getInvalidCommandLineOptions() { 497 List<String> invalidParameters = new ArrayList<>(); 498 499 for (String value : getInvalidValues()) { 500 invalidParameters.add(constructOption(value)); 501 } 502 503 return invalidParameters; 504 } 505 506 /** 507 * Perform test of the parameter. Call java with valid option values and 508 * with invalid option values. 509 * 510 * @return number of failed tests 511 * @throws Exception if java process can not be started 512 */ 513 public int testCommandLine() throws Exception { 514 ProcessBuilder pb; 515 int failed = 0; 516 List<String> optionValuesList; 517 518 optionValuesList = getValidCommandLineOptions(); 519 520 if (optionValuesList.isEmpty() != true) { 521 System.out.println("Testing valid " + name + " values."); 522 for (String optionValid : optionValuesList) { 523 if (runJavaWithParam(optionValid, true) == false) { 524 failed++; 525 } 526 } 527 } 528 529 optionValuesList = getInvalidCommandLineOptions(); 530 531 if (optionValuesList.isEmpty() != true) { 532 System.out.println("Testing invalid " + name + " values."); 533 534 for (String optionInvalid : optionValuesList) { 535 if (runJavaWithParam(optionInvalid, false) == false) { 536 failed++; 537 } 538 } 539 } 540 541 /* return number of failed tests for this option */ 542 return failed; 543 } 544 545 }