JVMOption.java revision 9563:f151ef072683
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 */
23package optionsvalidation;
24
25import com.sun.tools.attach.VirtualMachine;
26import com.sun.tools.attach.AttachOperationFailedException;
27import java.util.ArrayList;
28import java.util.List;
29import jdk.test.lib.DynamicVMOption;
30import jdk.test.lib.OutputAnalyzer;
31import jdk.test.lib.ProcessTools;
32import jdk.test.lib.dcmd.CommandExecutor;
33import jdk.test.lib.dcmd.JMXExecutor;
34import sun.tools.attach.HotSpotVirtualMachine;
35
36import static optionsvalidation.JVMOptionsUtils.failedMessage;
37import static optionsvalidation.JVMOptionsUtils.printOutputContent;
38import static optionsvalidation.JVMOptionsUtils.VMType;
39
40public 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}
504