1 /* 2 * Copyright (c) 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 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.List; 29 import jdk.test.lib.DynamicVMOption; 30 import jdk.test.lib.OutputAnalyzer; 31 import jdk.test.lib.ProcessTools; 32 import jdk.test.lib.dcmd.CommandExecutor; 33 import jdk.test.lib.dcmd.JMXExecutor; 34 import sun.tools.attach.HotSpotVirtualMachine; 35 36 import static optionsvalidation.JVMOptionsUtils.failedMessage; 37 import static optionsvalidation.JVMOptionsUtils.printOutputContent; 38 39 public abstract class JVMOption { 40 41 /* 42 * Executor for JCMD 43 */ 44 private final static CommandExecutor executor = new JMXExecutor(); 45 46 /* 47 * Name of the tested parameter 48 */ 49 protected String name; 50 51 /* 52 * Range is defined for option inside VM 53 */ 54 protected boolean withRange; 55 56 /* Prepend string which added before testing option to the command line */ 57 private final List<String> prepend; 58 private final StringBuilder prependString; 59 60 protected JVMOption() { 61 this.prepend = new ArrayList<>(); 62 prependString = new StringBuilder(); 63 withRange = false; 64 } 65 66 /** 67 * Add passed options to the prepend options of the option. Prepend options 68 * will be added before testing option to the command line. 69 * 70 * @param options Array of prepend options 71 */ 72 public final void addPrepend(String... options) { 73 String toAdd; 74 75 for (String option : options) { 76 if (option.startsWith("-")) { 77 toAdd = option; 78 } else { 79 /* Add "-" before parameter name */ 80 toAdd = "-" + option; 81 82 } 83 prepend.add(toAdd); 84 prependString.append(toAdd).append(" "); 85 } 86 } 87 88 /** 89 * Get name of the option 90 * 91 * @return Name of the option 92 */ 93 final String getName() { 94 return name; 95 } 96 97 /** 98 * Mark this option as option which range is defined inside VM 99 */ 100 final void optionWithRange() { 101 withRange = true; 102 } 103 104 /** 105 * Set new minimum option value 106 * 107 * @param min New minimum value 108 */ 109 abstract void setMin(String min); 110 111 /** 112 * Get string with minimum value of the option 113 * 114 * @return String with minimum value of the option 115 */ 116 abstract String getMin(); 117 118 /** 119 * Set new maximum option value 120 * 121 * @param max New maximum value 122 */ 123 abstract void setMax(String min); 124 125 /** 126 * Get string with maximum value of the option 127 * 128 * @return String with maximum value of the option 129 */ 130 abstract String getMax(); 131 132 /** 133 * Return list of strings which contain options with valid values which used 134 * for testing on command line 135 * 136 * @return List of strings which contain options with valid values 137 */ 138 abstract protected List<String> getValidCommandLineOptions(); 139 140 /** 141 * Return list of strings which contain options with invalid values which 142 * used for testing on command line 143 * 144 * @return List of strings which contain options with invalid values 145 */ 146 abstract protected List<String> getInvalidCommandLineOptions(); 147 148 /** 149 * Return list of strings with valid option values which used for testing 150 * using jcmd, attach and etc. 151 * 152 * @return List of strings which contain valid values for option 153 */ 154 abstract protected List<String> getValidValues(); 155 156 /** 157 * Return list of strings with invalid option values which used for testing 158 * using jcmd, attach and etc. 159 * 160 * @return List of strings which contain invalid values for option 161 */ 162 abstract protected List<String> getInvalidValues(); 163 164 /** 165 * Return expected error message for option with value "value" when it 166 * used on command line with passed value 167 * 168 * @param value Option value 169 * @return Expected error message 170 */ 171 abstract protected String getErrorMessageCommandLine(String value); 172 173 /** 174 * Create JVM Option with given type and name. 175 * 176 * @param type Type: "intx", "size_t", "uintx", "uint64_t" or "double" 177 * @param name Name of the option 178 * @return Created JVMOption 179 */ 180 static JVMOption createVMOption(String type, String name) { 181 JVMOption parameter; 182 183 switch (type) { 184 case "intx": 185 parameter = new IntJVMOption(name, IntJVMOption.IntType.INTX); 186 break; 187 case "size_t": 188 case "uintx": 189 parameter = new IntJVMOption(name, IntJVMOption.IntType.UINTX); 190 break; 191 case "uint64_t": 192 parameter = new IntJVMOption(name, IntJVMOption.IntType.UINT64_T); 193 break; 194 case "double": 195 parameter = new DoubleJVMOption(name); 196 break; 197 default: 198 throw new Error("Expected only \"intx\", \"size_t\", \"uintx\", \"uint64_t\"," 199 + " or \"boolean\" option types! Got " + type + " type!"); 200 } 201 202 return parameter; 203 } 204 205 /** 206 * Testing writeable option using DynamicVMOption isValidValue and 207 * isInvalidValue methods 208 * 209 * @return Number of failed tests 210 */ 211 public int testDynamic() { 212 DynamicVMOption option = new DynamicVMOption(name); 213 int failedTests = 0; 214 String origValue; 215 216 if (option.isWriteable()) { 217 218 System.out.println("Testing " + name + " option dynamically by DynamicVMOption"); 219 220 origValue = option.getValue(); 221 222 for (String value : getValidValues()) { 223 if (!option.isValidValue(value)) { 224 failedMessage(String.format("Option %s: Valid value \"%s\" is invalid", name, value)); 225 failedTests++; 226 } 227 } 228 229 for (String value : getInvalidValues()) { 230 if (option.isValidValue(value)) { 231 failedMessage(String.format("Option %s: Invalid value \"%s\" is valid", name, value)); 232 failedTests++; 233 } 234 } 235 236 option.setValue(origValue); 237 } 238 239 return failedTests; 240 } 241 242 /** 243 * Testing writeable option using Jcmd 244 * 245 * @return Number of failed tests 246 */ 247 public int testJcmd() throws Exception { 248 DynamicVMOption option = new DynamicVMOption(name); 249 int failedTests = 0; 250 OutputAnalyzer out; 251 String origValue; 252 253 if (option.isWriteable()) { 254 255 System.out.println("Testing " + name + " option dynamically by jcmd"); 256 257 origValue = option.getValue(); 258 259 for (String value : getValidValues()) { 260 out = executor.execute(String.format("VM.set_flag %s %s", name, value), true); 261 262 if (out.getOutput().contains(name + " error")) { 263 failedMessage(String.format("Option %s: Can not change " 264 + "option to valid value \"%s\" via jcmd", name, value)); 265 printOutputContent(out); 266 failedTests++; 267 } 268 } 269 270 for (String value : getInvalidValues()) { 271 out = executor.execute(String.format("VM.set_flag %s %s", name, value), true); 272 273 if (!out.getOutput().contains(name + " error")) { 274 failedMessage(String.format("Option %s: Error not reported for " 275 + "option when it chagned to invalid value \"%s\" via jcmd", name, value)); 276 printOutputContent(out); 277 failedTests++; 278 } 279 } 280 281 option.setValue(origValue); 282 } 283 284 return failedTests; 285 } 286 287 private final boolean setFlagAttach(HotSpotVirtualMachine vm, String flagName, String flagValue) throws Exception { 288 boolean result; 289 290 try { 291 vm.setFlag(flagName, flagValue); 292 result = true; 293 } catch (AttachOperationFailedException e) { 294 result = false; 295 } 296 297 return result; 298 } 299 300 /** 301 * Testing writeable option using attach method 302 * 303 * @return Number of failed tests 304 */ 305 public int testAttach() throws Exception { 306 DynamicVMOption option = new DynamicVMOption(name); 307 int failedTests = 0; 308 String origValue; 309 310 if (option.isWriteable()) { 311 312 System.out.println("Testing " + name + " option dynamically via attach"); 313 314 origValue = option.getValue(); 315 316 HotSpotVirtualMachine vm = (HotSpotVirtualMachine) VirtualMachine.attach(String.valueOf(ProcessTools.getProcessId())); 317 318 for (String value : getValidValues()) { 319 if (!setFlagAttach(vm, name, value)) { 320 failedMessage(String.format("Option %s: Can not change option to valid value \"%s\" via attach", name, value)); 321 failedTests++; 322 } 323 } 324 325 for (String value : getInvalidValues()) { 326 if (setFlagAttach(vm, name, value)) { 327 failedMessage(String.format("Option %s: Option changed to invalid value \"%s\" via attach", name, value)); 328 failedTests++; 329 } 330 } 331 332 vm.detach(); 333 334 option.setValue(origValue); 335 } 336 337 return failedTests; 338 } 339 340 /** 341 * Run java with passed parameter and check the result depending on the 342 * 'valid' parameter 343 * 344 * @param param Tested parameter passed to the java 345 * @param valid Indicates, should JVM failed or not 346 * @return true - if test passed 347 * @throws Exception Java process can not be started 348 */ 349 private boolean runJavaWithParam(String optionValue, boolean valid) throws Exception { 350 int returnCode; 351 boolean result = true; 352 String value = optionValue.substring(optionValue.lastIndexOf("=") + 1); 353 String fullOptionString = prependString.toString() + optionValue; 354 List<String> runJava = new ArrayList<>(); 355 OutputAnalyzer out; 356 357 runJava.addAll(prepend); 358 runJava.add(optionValue); 359 runJava.add(JVMOptionsUtils.class.getName()); 360 361 out = new OutputAnalyzer(ProcessTools.createJavaProcessBuilder(true, runJava.toArray(new String[0])).start()); 362 363 returnCode = out.getExitValue(); 364 365 if (out.getOutput().contains("A fatal error has been detected by the Java Runtime Environment")) { 366 /* Always consider "fatal error" in output as fail */ 367 failedMessage(name, fullOptionString, valid, "JVM output reports about fatal error. JVM exit with code " + returnCode + "!"); 368 printOutputContent(out); 369 result = false; 370 } else if (valid == true) { 371 if ((returnCode != 0) && (returnCode != 1)) { 372 failedMessage(name, fullOptionString, valid, "JVM exit with unexpected error code = " + returnCode); 373 printOutputContent(out); 374 result = false; 375 } else if (returnCode == 1 && out.getOutput().isEmpty()) { 376 failedMessage(name, fullOptionString, valid, "JVM exit with error(exitcode == 1)" 377 + ", but with empty stdout and stderr. Description of error is needed!"); 378 result = false; 379 } else if (out.getOutput().contains("is outside the allowed range")) { 380 failedMessage(name, fullOptionString, valid, "JVM output contains \"is outside the allowed range\""); 381 printOutputContent(out); 382 result = false; 383 } 384 } else { 385 // valid == false 386 if (returnCode == 0) { 387 failedMessage(name, fullOptionString, valid, "JVM successfully exit"); 388 result = false; 389 } else if (returnCode != 1) { 390 failedMessage(name, fullOptionString, valid, "JVM exit with code " 391 + returnCode + " which not equal to 1"); 392 result = false; 393 } else if (!out.getOutput().contains(getErrorMessageCommandLine(value))) { 394 failedMessage(name, fullOptionString, valid, "JVM output not contains " 395 + "expected output \"" + getErrorMessageCommandLine(value) + "\""); 396 printOutputContent(out); 397 result = false; 398 } 399 } 400 401 System.out.println(""); 402 403 return result; 404 } 405 406 /** 407 * Perform test of the parameter. Call java with valid option values and 408 * with invalid option values. 409 * 410 * @return Number of failed tests 411 * @throws Exception Java process can not be started 412 */ 413 public int testCommandLine() throws Exception { 414 ProcessBuilder pb; 415 int failed = 0; 416 List<String> optionValuesList; 417 418 optionValuesList = getValidCommandLineOptions(); 419 420 if (optionValuesList.isEmpty() != true) { 421 System.out.println("Testing valid " + name + " values."); 422 for (String optionValid : optionValuesList) { 423 if (runJavaWithParam(optionValid, true) == false) { 424 failed++; 425 } 426 } 427 } 428 429 optionValuesList = getInvalidCommandLineOptions(); 430 431 if (optionValuesList.isEmpty() != true) { 432 System.out.println("Testing invalid " + name + " values."); 433 434 for (String optionInvalid : optionValuesList) { 435 if (runJavaWithParam(optionInvalid, false) == false) { 436 failed++; 437 } 438 } 439 } 440 441 /* return number of failed tests for this option */ 442 return failed; 443 } 444 445 }