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