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 
  24 import static jdk.testlibrary.Asserts.assertGreaterThan;
  25 import static jdk.testlibrary.Asserts.assertTrue;
  26 
  27 import java.io.BufferedWriter;
  28 import java.io.File;
  29 import java.io.FileWriter;
  30 import java.io.IOException;
  31 import java.nio.file.Files;
  32 import java.nio.file.Path;
  33 import java.nio.file.Paths;
  34 import java.util.ArrayList;
  35 import java.util.Arrays;
  36 import java.util.List;
  37 
  38 import jdk.test.lib.process.OutputAnalyzer;
  39 import jdk.test.lib.process.ProcessTools;
  40 import jdk.testlibrary.Asserts;
  41 import jdk.testlibrary.JDKToolLauncher;
  42 import jdk.testlibrary.Utils;
  43 
  44 /**
  45  * The helper class for running jps utility and verifying output from it
  46  */
  47 public 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) {
 188         output.shouldHaveExitValue(0);
 189         output.stdoutShouldMatchByLine(regex);
 190         output.stderrShouldNotMatch("[E|e]xception");
 191         output.stderrShouldNotMatch("[E|e]rror");
 192     }
 193 
 194     /**
 195      * Compare jps output with a content in a file line by line
 196      */
 197     public static void verifyOutputAgainstFile(OutputAnalyzer output) throws IOException {
 198         String testSrc = System.getProperty("test.src", "?");
 199         Path path = Paths.get(testSrc, "usage.out");
 200         List<String> fileOutput = Files.readAllLines(path);
 201         List<String> outputAsLines = output.asLines();
 202         assertTrue(outputAsLines.containsAll(fileOutput),
 203                 "The ouput should contain all content of " + path.toAbsolutePath());
 204     }
 205 
 206     public static void runJpsVariants(Long pid, String processName, String fullProcessName, String argument) throws Exception {
 207         System.out.printf("INFO: user.dir:  '%s''\n", System.getProperty("user.dir"));
 208         List<List<JpsHelper.JpsArg>> combinations = JpsHelper.JpsArg.generateCombinations();
 209         for (List<JpsHelper.JpsArg> combination : combinations) {
 210             OutputAnalyzer output = JpsHelper.jps(JpsHelper.JpsArg.asCmdArray(combination));
 211             output.shouldHaveExitValue(0);
 212 
 213             boolean isQuiet = false;
 214             boolean isFull = false;
 215             String pattern;
 216             for (JpsHelper.JpsArg jpsArg : combination) {
 217                 switch (jpsArg) {
 218                 case q:
 219                     // If '-q' is specified output should contain only a list of local VM identifiers:
 220                     // 30673
 221                     isQuiet = true;
 222                     JpsHelper.verifyJpsOutput(output, "^\\d+$");
 223                     output.shouldContain(Long.toString(pid));
 224                     break;
 225                 case l:
 226                     // If '-l' is specified output should contain the full package name for the application's main class
 227                     // or the full path name to the application's JAR file:
 228                     // 30673 /tmp/jtreg/jtreg-workdir/scratch/LingeredAppForJps.jar ...
 229                     isFull = true;
 230                     pattern = "^" + pid + "\\s+" + replaceSpecialChars(fullProcessName) + ".*";
 231                     output.shouldMatch(pattern);
 232                     break;
 233                 case m:
 234                     // If '-m' is specified output should contain the arguments passed to the main method:
 235                     // 30673 LingeredAppForJps lockfilename ...
 236                     pattern = "^" + pid + ".*" + replaceSpecialChars(argument) + ".*";
 237                     output.shouldMatch(pattern);
 238                     break;
 239                 case v:
 240                     // If '-v' is specified output should contain VM arguments:
 241                     // 30673 LingeredAppForJps -Xmx512m -XX:+UseParallelGC -XX:Flags=/tmp/jtreg/jtreg-workdir/scratch/vmflags ...
 242                     for (String vmArg : JpsHelper.getVmArgs()) {
 243                         pattern = "^" + pid + ".*" + replaceSpecialChars(vmArg) + ".*";
 244                         output.shouldMatch(pattern);
 245                     }
 246                     break;
 247                 case V:
 248                     // If '-V' is specified output should contain VM flags:
 249                     // 30673 LingeredAppForJps +DisableExplicitGC ...
 250                     pattern = "^" + pid + ".*" + replaceSpecialChars(JpsHelper.VM_FLAG) + ".*";
 251                     output.shouldMatch(pattern);
 252                     break;
 253                 }
 254 
 255                 if (isQuiet) {
 256                     break;
 257                 }
 258             }
 259 
 260             if (!isQuiet) {
 261                 // Verify output line by line.
 262                 // Output should only contain lines with pids after the first line with pid.
 263                 JpsHelper.verifyJpsOutput(output, "^\\d+\\s+.*");
 264                 if (!isFull) {
 265                     pattern = "^" + pid + "\\s+" + replaceSpecialChars(processName);
 266                     if (combination.isEmpty()) {
 267                         // If no arguments are specified output should only contain
 268                         // pid and process name
 269                         pattern += "$";
 270                     } else {
 271                         pattern += ".*";
 272                     }
 273                     output.shouldMatch(pattern);
 274                 }
 275             }
 276         }
 277     }
 278 
 279     private static String replaceSpecialChars(String str) {
 280         String tmp = str.replace("\\", "\\\\");
 281         tmp = tmp.replace("+", "\\+");
 282         tmp = tmp.replace(".", "\\.");
 283         tmp = tmp.replace("\n", "\\\\n");
 284         tmp = tmp.replace("\r", "\\\\r");
 285         return tmp;
 286     }
 287 }