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 }