1/*
2 * Copyright (c) 2014, 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
24import static jdk.testlibrary.Asserts.assertGreaterThan;
25import static jdk.testlibrary.Asserts.assertTrue;
26
27import java.io.BufferedWriter;
28import java.io.File;
29import java.io.FileWriter;
30import java.io.IOException;
31import java.nio.file.Files;
32import java.nio.file.Path;
33import java.nio.file.Paths;
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.List;
37
38import jdk.testlibrary.Asserts;
39import jdk.testlibrary.JDKToolLauncher;
40import jdk.testlibrary.OutputAnalyzer;
41import jdk.testlibrary.Utils;
42import jdk.testlibrary.ProcessTools;
43
44/**
45 * The helper class for running jps utility and verifying output from it
46 */
47public final class JpsHelper {
48
49    /**
50     * Helper class for handling jps arguments
51     */
52    public enum JpsArg {
53        q,
54        l,
55        m,
56        v,
57        V;
58
59        /**
60         * Generate all possible combinations of {@link JpsArg}
61         * (31 argument combinations and no arguments case)
62         */
63        public static List<List<JpsArg>> generateCombinations() {
64            final int argCount = JpsArg.values().length;
65            // If there are more than 30 args this algorithm will overflow.
66            Asserts.assertLessThan(argCount, 31, "Too many args");
67
68            List<List<JpsArg>> combinations = new ArrayList<>();
69            int combinationCount = (int) Math.pow(2, argCount);
70            for (int currCombo = 0; currCombo < combinationCount; ++currCombo) {
71                List<JpsArg> combination = new ArrayList<>();
72                for (int position = 0; position < argCount; ++position) {
73                    int bit = 1 << position;
74                    if ((bit & currCombo) != 0) {
75                        combination.add(JpsArg.values()[position]);
76                    }
77                }
78                combinations.add(combination);
79            }
80            return combinations;
81        }
82
83        /**
84         *  Return combination of {@link JpsArg} as a String array
85         */
86        public static String[] asCmdArray(List<JpsArg> jpsArgs) {
87            List<String> list = new ArrayList<>();
88            for (JpsArg jpsArg : jpsArgs) {
89                list.add("-" + jpsArg.toString());
90            }
91            return list.toArray(new String[list.size()]);
92        }
93
94    }
95
96    /**
97     * VM arguments to start test application with.
98     * -XX:+UsePerfData is required for running the tests on embedded platforms.
99     */
100    public static final String[] VM_ARGS = {
101        "-XX:+UsePerfData", "-Xmx512m", "-Xlog:gc",
102        "-Dmultiline.prop=value1\nvalue2\r\nvalue3"
103    };
104    /**
105     * VM flag to start test application with
106     */
107    public static final String VM_FLAG = "+DisableExplicitGC";
108
109    private static File vmFlagsFile = null;
110    private static List<String> testVmArgs = null;
111    private static File manifestFile = null;
112
113    /**
114     * Create a file containing VM_FLAG in the working directory
115     */
116    public static File getVmFlagsFile() throws IOException {
117        if (vmFlagsFile == null) {
118            vmFlagsFile = new File("vmflags");
119            try (BufferedWriter output = new BufferedWriter(new FileWriter(vmFlagsFile))) {
120                output.write(VM_FLAG);
121            }
122            vmFlagsFile.deleteOnExit();
123        }
124        return vmFlagsFile;
125    }
126
127    /**
128     * Return a list of VM arguments
129     */
130    public static List<String> getVmArgs() throws IOException {
131        if (testVmArgs == null) {
132            testVmArgs = new ArrayList<>();
133            testVmArgs.addAll(Arrays.asList(VM_ARGS));
134            testVmArgs.add("-XX:Flags=" + getVmFlagsFile().getAbsolutePath());
135        }
136        return testVmArgs;
137    }
138
139    /**
140     * Start jps utility without any arguments
141     */
142    public static OutputAnalyzer jps() throws Exception {
143        return jps(null, null);
144    }
145
146    /**
147     * Start jps utility with tool arguments
148     */
149    public static OutputAnalyzer jps(String... toolArgs) throws Exception {
150        return jps(null, Arrays.asList(toolArgs));
151    }
152
153    /**
154     * Start jps utility with VM args and tool arguments
155     */
156    public static OutputAnalyzer jps(List<String> vmArgs, List<String> toolArgs) throws Exception {
157        JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jps");
158        launcher.addVMArg("-XX:+UsePerfData");
159        if (vmArgs != null) {
160            for (String vmArg : vmArgs) {
161                launcher.addVMArg(vmArg);
162            }
163        }
164        if (toolArgs != null) {
165            for (String toolArg : toolArgs) {
166                launcher.addToolArg(toolArg);
167            }
168        }
169
170        ProcessBuilder processBuilder = new ProcessBuilder(launcher.getCommand());
171        System.out.println(Arrays.toString(processBuilder.command().toArray()).replace(",", ""));
172        OutputAnalyzer output = ProcessTools.executeProcess(processBuilder);
173        System.out.println(output.getOutput());
174
175        return output;
176    }
177
178    /**
179     * Verify jps stdout contains only pids and programs' name information.
180     * jps stderr may contain VM warning messages which will be ignored.
181     *
182     * The output can look like:
183     * 35536 Jps
184     * 35417 Main
185     * 31103 org.eclipse.equinox.launcher_1.3.0.v20120522-1813.jar
186     */
187    public static void verifyJpsOutput(OutputAnalyzer output, String regex) throws Exception {
188        output.shouldHaveExitValue(0);
189        int matchedCount = output.stdoutShouldMatchByLine(regex);
190        assertGreaterThan(matchedCount , 0, "Found no lines matching pattern: " + regex);
191        output.stderrShouldNotMatch("[E|e]xception");
192        output.stderrShouldNotMatch("[E|e]rror");
193    }
194
195    /**
196     * Compare jps output with a content in a file line by line
197     */
198    public static void verifyOutputAgainstFile(OutputAnalyzer output) throws IOException {
199        String testSrc = System.getProperty("test.src", "?");
200        Path path = Paths.get(testSrc, "usage.out");
201        List<String> fileOutput = Files.readAllLines(path);
202        List<String> outputAsLines = output.asLines();
203        assertTrue(outputAsLines.containsAll(fileOutput),
204                "The ouput should contain all content of " + path.toAbsolutePath());
205    }
206
207    public static void runJpsVariants(Long pid, String processName, String fullProcessName, String argument) throws Exception {
208        System.out.printf("INFO: user.dir:  '%s''\n", System.getProperty("user.dir"));
209        List<List<JpsHelper.JpsArg>> combinations = JpsHelper.JpsArg.generateCombinations();
210        for (List<JpsHelper.JpsArg> combination : combinations) {
211            OutputAnalyzer output = JpsHelper.jps(JpsHelper.JpsArg.asCmdArray(combination));
212            output.shouldHaveExitValue(0);
213
214            boolean isQuiet = false;
215            boolean isFull = false;
216            String pattern;
217            for (JpsHelper.JpsArg jpsArg : combination) {
218                switch (jpsArg) {
219                case q:
220                    // If '-q' is specified output should contain only a list of local VM identifiers:
221                    // 30673
222                    isQuiet = true;
223                    JpsHelper.verifyJpsOutput(output, "^\\d+$");
224                    output.shouldContain(Long.toString(pid));
225                    break;
226                case l:
227                    // If '-l' is specified output should contain the full package name for the application's main class
228                    // or the full path name to the application's JAR file:
229                    // 30673 /tmp/jtreg/jtreg-workdir/scratch/LingeredAppForJps.jar ...
230                    isFull = true;
231                    pattern = "^" + pid + "\\s+" + replaceSpecialChars(fullProcessName) + ".*";
232                    output.shouldMatch(pattern);
233                    break;
234                case m:
235                    // If '-m' is specified output should contain the arguments passed to the main method:
236                    // 30673 LingeredAppForJps lockfilename ...
237                    pattern = "^" + pid + ".*" + replaceSpecialChars(argument) + ".*";
238                    output.shouldMatch(pattern);
239                    break;
240                case v:
241                    // If '-v' is specified output should contain VM arguments:
242                    // 30673 LingeredAppForJps -Xmx512m -XX:+UseParallelGC -XX:Flags=/tmp/jtreg/jtreg-workdir/scratch/vmflags ...
243                    for (String vmArg : JpsHelper.getVmArgs()) {
244                        pattern = "^" + pid + ".*" + replaceSpecialChars(vmArg) + ".*";
245                        output.shouldMatch(pattern);
246                    }
247                    break;
248                case V:
249                    // If '-V' is specified output should contain VM flags:
250                    // 30673 LingeredAppForJps +DisableExplicitGC ...
251                    pattern = "^" + pid + ".*" + replaceSpecialChars(JpsHelper.VM_FLAG) + ".*";
252                    output.shouldMatch(pattern);
253                    break;
254                }
255
256                if (isQuiet) {
257                    break;
258                }
259            }
260
261            if (!isQuiet) {
262                // Verify output line by line.
263                // Output should only contain lines with pids after the first line with pid.
264                JpsHelper.verifyJpsOutput(output, "^\\d+\\s+.*");
265                if (!isFull) {
266                    pattern = "^" + pid + "\\s+" + replaceSpecialChars(processName);
267                    if (combination.isEmpty()) {
268                        // If no arguments are specified output should only contain
269                        // pid and process name
270                        pattern += "$";
271                    } else {
272                        pattern += ".*";
273                    }
274                    output.shouldMatch(pattern);
275                }
276            }
277        }
278    }
279
280    private static String replaceSpecialChars(String str) {
281        String tmp = str.replace("\\", "\\\\");
282        tmp = tmp.replace("+", "\\+");
283        tmp = tmp.replace(".", "\\.");
284        tmp = tmp.replace("\n", "\\\\n");
285        tmp = tmp.replace("\r", "\\\\r");
286        return tmp;
287    }
288}
289