1 /*
   2  * Copyright (c) 2013, 2016, 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 jdk.testlibrary;
  25 
  26 import static jdk.testlibrary.Asserts.assertTrue;
  27 
  28 import java.io.BufferedReader;
  29 import java.io.File;
  30 import java.io.FileReader;
  31 import java.io.IOException;
  32 import java.io.InputStream;
  33 import java.io.OutputStream;
  34 import java.net.InetAddress;
  35 import java.net.ServerSocket;
  36 import java.net.UnknownHostException;
  37 import java.util.ArrayList;
  38 import java.util.List;
  39 import java.util.Arrays;
  40 import java.util.Collections;
  41 import java.util.Objects;
  42 import java.util.regex.Pattern;
  43 import java.util.regex.Matcher;
  44 import java.util.concurrent.TimeUnit;
  45 import java.util.function.Function;
  46 
  47 /**
  48  * Common library for various test helper functions.
  49  */
  50 public final class Utils {
  51 
  52     /**
  53      * Returns the sequence used by operating system to separate lines.
  54      */
  55     public static final String NEW_LINE = System.getProperty("line.separator");
  56 
  57     /**
  58      * Returns the value of 'test.vm.opts'system property.
  59      */
  60     public static final String VM_OPTIONS = System.getProperty("test.vm.opts", "").trim();
  61 
  62     /**
  63      * Returns the value of 'test.java.opts'system property.
  64      */
  65     public static final String JAVA_OPTIONS = System.getProperty("test.java.opts", "").trim();
  66 
  67 
  68     /**
  69     * Returns the value of 'test.timeout.factor' system property
  70     * converted to {@code double}.
  71     */
  72     public static final double TIMEOUT_FACTOR;
  73     static {
  74         String toFactor = System.getProperty("test.timeout.factor", "1.0");
  75         TIMEOUT_FACTOR = Double.parseDouble(toFactor);
  76     }
  77 
  78     /**
  79     * Returns the value of JTREG default test timeout in milliseconds
  80     * converted to {@code long}.
  81     */
  82     public static final long DEFAULT_TEST_TIMEOUT = TimeUnit.SECONDS.toMillis(120);
  83 
  84     private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
  85     private static final int DEFAULT_BUFFER_SIZE = 8192;
  86 
  87     private Utils() {
  88         // Private constructor to prevent class instantiation
  89     }
  90 
  91     /**
  92      * Returns the list of VM options.
  93      *
  94      * @return List of VM options
  95      */
  96     public static List<String> getVmOptions() {
  97         return Arrays.asList(safeSplitString(VM_OPTIONS));
  98     }
  99 
 100     /**
 101      * Returns the list of VM options with -J prefix.
 102      *
 103      * @return The list of VM options with -J prefix
 104      */
 105     public static List<String> getForwardVmOptions() {
 106         String[] opts = safeSplitString(VM_OPTIONS);
 107         for (int i = 0; i < opts.length; i++) {
 108             opts[i] = "-J" + opts[i];
 109         }
 110         return Arrays.asList(opts);
 111     }
 112 
 113     /**
 114      * Returns the default JTReg arguments for a jvm running a test.
 115      * This is the combination of JTReg arguments test.vm.opts and test.java.opts.
 116      * @return An array of options, or an empty array if no opptions.
 117      */
 118     public static String[] getTestJavaOpts() {
 119         List<String> opts = new ArrayList<String>();
 120         Collections.addAll(opts, safeSplitString(VM_OPTIONS));
 121         Collections.addAll(opts, safeSplitString(JAVA_OPTIONS));
 122         return opts.toArray(new String[0]);
 123     }
 124 
 125     /**
 126      * Combines given arguments with default JTReg arguments for a jvm running a test.
 127      * This is the combination of JTReg arguments test.vm.opts and test.java.opts
 128      * @return The combination of JTReg test java options and user args.
 129      */
 130     public static String[] addTestJavaOpts(String... userArgs) {
 131         List<String> opts = new ArrayList<String>();
 132         Collections.addAll(opts, getTestJavaOpts());
 133         Collections.addAll(opts, userArgs);
 134         return opts.toArray(new String[0]);
 135     }
 136 
 137     /**
 138      * Removes any options specifying which GC to use, for example "-XX:+UseG1GC".
 139      * Removes any options matching: -XX:(+/-)Use*GC
 140      * Used when a test need to set its own GC version. Then any
 141      * GC specified by the framework must first be removed.
 142      * @return A copy of given opts with all GC options removed.
 143      */
 144     private static final Pattern useGcPattern = Pattern.compile("\\-XX\\:[\\+\\-]Use.+GC");
 145     public static List<String> removeGcOpts(List<String> opts) {
 146         List<String> optsWithoutGC = new ArrayList<String>();
 147         for (String opt : opts) {
 148             if (useGcPattern.matcher(opt).matches()) {
 149                 System.out.println("removeGcOpts: removed " + opt);
 150             } else {
 151                 optsWithoutGC.add(opt);
 152             }
 153         }
 154         return optsWithoutGC;
 155     }
 156 
 157     /**
 158      * Splits a string by white space.
 159      * Works like String.split(), but returns an empty array
 160      * if the string is null or empty.
 161      */
 162     private static String[] safeSplitString(String s) {
 163         if (s == null || s.trim().isEmpty()) {
 164             return new String[] {};
 165         }
 166         return s.trim().split("\\s+");
 167     }
 168 
 169     /**
 170      * @return The full command line for the ProcessBuilder.
 171      */
 172     public static String getCommandLine(ProcessBuilder pb) {
 173         StringBuilder cmd = new StringBuilder();
 174         for (String s : pb.command()) {
 175             cmd.append(s).append(" ");
 176         }
 177         return cmd.toString();
 178     }
 179 
 180     /**
 181      * Returns the free port on the local host.
 182      * The function will spin until a valid port number is found.
 183      *
 184      * @return The port number
 185      * @throws InterruptedException if any thread has interrupted the current thread
 186      * @throws IOException if an I/O error occurs when opening the socket
 187      */
 188     public static int getFreePort() throws InterruptedException, IOException {
 189         int port = -1;
 190 
 191         while (port <= 0) {
 192             Thread.sleep(100);
 193 
 194             ServerSocket serverSocket = null;
 195             try {
 196                 serverSocket = new ServerSocket(0);
 197                 port = serverSocket.getLocalPort();
 198             } finally {
 199                 serverSocket.close();
 200             }
 201         }
 202 
 203         return port;
 204     }
 205 
 206     /**
 207      * Returns the name of the local host.
 208      *
 209      * @return The host name
 210      * @throws UnknownHostException if IP address of a host could not be determined
 211      */
 212     public static String getHostname() throws UnknownHostException {
 213         InetAddress inetAddress = InetAddress.getLocalHost();
 214         String hostName = inetAddress.getHostName();
 215 
 216         assertTrue((hostName != null && !hostName.isEmpty()),
 217                 "Cannot get hostname");
 218 
 219         return hostName;
 220     }
 221 
 222     /**
 223      * Uses "jcmd -l" to search for a jvm pid. This function will wait
 224      * forever (until jtreg timeout) for the pid to be found.
 225      * @param key Regular expression to search for
 226      * @return The found pid.
 227      */
 228     public static int waitForJvmPid(String key) throws Throwable {
 229         final long iterationSleepMillis = 250;
 230         System.out.println("waitForJvmPid: Waiting for key '" + key + "'");
 231         System.out.flush();
 232         while (true) {
 233             int pid = tryFindJvmPid(key);
 234             if (pid >= 0) {
 235                 return pid;
 236             }
 237             Thread.sleep(iterationSleepMillis);
 238         }
 239     }
 240 
 241     /**
 242      * Searches for a jvm pid in the output from "jcmd -l".
 243      *
 244      * Example output from jcmd is:
 245      * 12498 sun.tools.jcmd.JCmd -l
 246      * 12254 /tmp/jdk8/tl/jdk/JTwork/classes/com/sun/tools/attach/Application.jar
 247      *
 248      * @param key A regular expression to search for.
 249      * @return The found pid, or -1 if Enot found.
 250      * @throws Exception If multiple matching jvms are found.
 251      */
 252     public static int tryFindJvmPid(String key) throws Throwable {
 253         OutputAnalyzer output = null;
 254         try {
 255             JDKToolLauncher jcmdLauncher = JDKToolLauncher.create("jcmd");
 256             jcmdLauncher.addToolArg("-l");
 257             output = ProcessTools.executeProcess(jcmdLauncher.getCommand());
 258             output.shouldHaveExitValue(0);
 259 
 260             // Search for a line starting with numbers (pid), follwed by the key.
 261             Pattern pattern = Pattern.compile("([0-9]+)\\s.*(" + key + ").*\\r?\\n");
 262             Matcher matcher = pattern.matcher(output.getStdout());
 263 
 264             int pid = -1;
 265             if (matcher.find()) {
 266                 pid = Integer.parseInt(matcher.group(1));
 267                 System.out.println("findJvmPid.pid: " + pid);
 268                 if (matcher.find()) {
 269                     throw new Exception("Found multiple JVM pids for key: " + key);
 270                 }
 271             }
 272             return pid;
 273         } catch (Throwable t) {
 274             System.out.println(String.format("Utils.findJvmPid(%s) failed: %s", key, t));
 275             throw t;
 276         }
 277     }
 278 
 279     /**
 280      * Returns file content as a list of strings
 281      *
 282      * @param file File to operate on
 283      * @return List of strings
 284      * @throws IOException
 285      */
 286     public static List<String> fileAsList(File file) throws IOException {
 287         assertTrue(file.exists() && file.isFile(),
 288                 file.getAbsolutePath() + " does not exist or not a file");
 289         List<String> output = new ArrayList<>();
 290         try (BufferedReader reader = new BufferedReader(new FileReader(file.getAbsolutePath()))) {
 291             while (reader.ready()) {
 292                 output.add(reader.readLine().replace(NEW_LINE, ""));
 293             }
 294         }
 295         return output;
 296     }
 297 
 298     /**
 299      * Helper method to read all bytes from InputStream
 300      *
 301      * @param is InputStream to read from
 302      * @return array of bytes
 303      * @throws IOException
 304      */
 305     public static byte[] readAllBytes(InputStream is) throws IOException {
 306         byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
 307         int capacity = buf.length;
 308         int nread = 0;
 309         int n;
 310         for (;;) {
 311             // read to EOF which may read more or less than initial buffer size
 312             while ((n = is.read(buf, nread, capacity - nread)) > 0)
 313                 nread += n;
 314 
 315             // if the last call to read returned -1, then we're done
 316             if (n < 0)
 317                 break;
 318 
 319             // need to allocate a larger buffer
 320             if (capacity <= MAX_BUFFER_SIZE - capacity) {
 321                 capacity = capacity << 1;
 322             } else {
 323                 if (capacity == MAX_BUFFER_SIZE)
 324                     throw new OutOfMemoryError("Required array size too large");
 325                 capacity = MAX_BUFFER_SIZE;
 326             }
 327             buf = Arrays.copyOf(buf, capacity);
 328         }
 329         return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
 330     }
 331 
 332     /**
 333      * Adjusts the provided timeout value for the TIMEOUT_FACTOR
 334      * @param tOut the timeout value to be adjusted
 335      * @return The timeout value adjusted for the value of "test.timeout.factor"
 336      *         system property
 337      */
 338     public static long adjustTimeout(long tOut) {
 339         return Math.round(tOut * Utils.TIMEOUT_FACTOR);
 340     }
 341 
 342     /**
 343      * Interface same as java.lang.Runnable but with
 344      * method {@code run()} able to throw any Throwable.
 345      */
 346     public static interface ThrowingRunnable {
 347         void run() throws Throwable;
 348     }
 349 
 350     /**
 351      * Filters out an exception that may be thrown by the given
 352      * test according to the given filter.
 353      *
 354      * @param test - method that is invoked and checked for exception.
 355      * @param filter - function that checks if the thrown exception matches
 356      *                 criteria given in the filter's implementation.
 357      * @return - exception that matches the filter if it has been thrown or
 358      *           {@code null} otherwise.
 359      * @throws Throwable - if test has thrown an exception that does not
 360      *                     match the filter.
 361      */
 362     public static Throwable filterException(ThrowingRunnable test,
 363             Function<Throwable, Boolean> filter) throws Throwable {
 364         try {
 365             test.run();
 366         } catch (Throwable t) {
 367             if (filter.apply(t)) {
 368                 return t;
 369             } else {
 370                 throw t;
 371             }
 372         }
 373         return null;
 374     }
 375 
 376     private static final int BUFFER_SIZE = 1024;
 377 
 378     /**
 379      * Reads all bytes from the input stream and writes the bytes to the
 380      * given output stream in the order that they are read. On return, the
 381      * input stream will be at end of stream. This method does not close either
 382      * stream.
 383      * <p>
 384      * This method may block indefinitely reading from the input stream, or
 385      * writing to the output stream. The behavior for the case where the input
 386      * and/or output stream is <i>asynchronously closed</i>, or the thread
 387      * interrupted during the transfer, is highly input and output stream
 388      * specific, and therefore not specified.
 389      * <p>
 390      * If an I/O error occurs reading from the input stream or writing to the
 391      * output stream, then it may do so after some bytes have been read or
 392      * written. Consequently the input stream may not be at end of stream and
 393      * one, or both, streams may be in an inconsistent state. It is strongly
 394      * recommended that both streams be promptly closed if an I/O error occurs.
 395      *
 396      * @param  in the input stream, non-null
 397      * @param  out the output stream, non-null
 398      * @return the number of bytes transferred
 399      * @throws IOException if an I/O error occurs when reading or writing
 400      * @throws NullPointerException if {@code in} or {@code out} is {@code null}
 401      *
 402      */
 403     public static long transferTo(InputStream in, OutputStream out)
 404             throws IOException  {
 405         long transferred = 0;
 406         byte[] buffer = new byte[BUFFER_SIZE];
 407         int read;
 408         while ((read = in.read(buffer, 0, BUFFER_SIZE)) >= 0) {
 409             out.write(buffer, 0, read);
 410             transferred += read;
 411         }
 412         return transferred;
 413     }
 414 }