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