1 /*
   2  * Copyright (c) 2013, 2014, 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 com.oracle.java.testlibrary;
  25 
  26 import static com.oracle.java.testlibrary.Asserts.assertTrue;
  27 import java.io.BufferedReader;
  28 import java.io.File;
  29 import java.io.FileReader;
  30 import java.io.IOException;
  31 import java.lang.reflect.Field;
  32 import java.net.InetAddress;
  33 import java.net.ServerSocket;
  34 import java.net.UnknownHostException;
  35 import java.util.ArrayList;
  36 import java.util.Arrays;
  37 import java.util.Collections;
  38 import java.util.List;
  39 import java.util.Random;
  40 import java.util.function.BooleanSupplier;
  41 import java.util.regex.Matcher;
  42 import java.util.regex.Pattern;
  43 import sun.misc.Unsafe;
  44 
  45 /**
  46  * Common library for various test helper functions.
  47  */
  48 public final class Utils {
  49 
  50     /**
  51      * Returns the sequence used by operating system to separate lines.
  52      */
  53     public static final String NEW_LINE = System.getProperty("line.separator");
  54 
  55     /**
  56      * Returns the value of 'test.vm.opts'system property.
  57      */
  58     public static final String VM_OPTIONS = System.getProperty("test.vm.opts", "").trim();
  59 
  60     /**
  61      * Returns the value of 'test.java.opts'system property.
  62      */
  63     public static final String JAVA_OPTIONS = System.getProperty("test.java.opts", "").trim();
  64 
  65     private static Unsafe unsafe = null;
  66 
  67     /**
  68      * Defines property name for seed value.
  69      */
  70     public static final String SEED_PROPERTY_NAME = "com.oracle.java.testlibrary.random.seed";
  71 
  72     /* (non-javadoc)
  73      * Random generator with (or without) predefined seed. Depends on
  74      * "com.oracle.java.testlibrary.random.seed" property value.
  75      */
  76     private static volatile Random RANDOM_GENERATOR;
  77 
  78     /**
  79      * Contains the seed value used for {@link java.util.Random} creation.
  80      */
  81     public static final long SEED = Long.getLong(SEED_PROPERTY_NAME, new Random().nextLong());
  82     /**
  83     * Returns the value of 'test.timeout.factor' system property
  84     * converted to {@code double}.
  85     */
  86     public static final double TIMEOUT_FACTOR;
  87     static {
  88         String toFactor = System.getProperty("test.timeout.factor", "1.0");
  89         TIMEOUT_FACTOR = Double.parseDouble(toFactor);
  90     }
  91 
  92     /**
  93     * Returns the value of 'test.iterations.count' system property
  94     * converted to {@code int}.
  95     */
  96     public static final int ITERATIONS_COUNT = 
  97             Integer.getInteger("com.oracle.java.testlibrary.iterations", 1);
  98     
  99     private Utils() {
 100         // Private constructor to prevent class instantiation
 101     }
 102 
 103     /**
 104      * Returns the list of VM options.
 105      *
 106      * @return List of VM options
 107      */
 108     public static List<String> getVmOptions() {
 109         return Arrays.asList(safeSplitString(VM_OPTIONS));
 110     }
 111 
 112     /**
 113      * Returns the list of VM options with -J prefix.
 114      *
 115      * @return The list of VM options with -J prefix
 116      */
 117     public static List<String> getForwardVmOptions() {
 118         String[] opts = safeSplitString(VM_OPTIONS);
 119         for (int i = 0; i < opts.length; i++) {
 120             opts[i] = "-J" + opts[i];
 121         }
 122         return Arrays.asList(opts);
 123     }
 124 
 125     /**
 126      * Returns the 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 An array of options, or an empty array if no opptions.
 129      */
 130     public static String[] getTestJavaOpts() {
 131         List<String> opts = new ArrayList<String>();
 132         Collections.addAll(opts, safeSplitString(VM_OPTIONS));
 133         Collections.addAll(opts, safeSplitString(JAVA_OPTIONS));
 134         return opts.toArray(new String[0]);
 135     }
 136 
 137     /**
 138      * Returns the default JTReg arguments for a jvm running a test without
 139      * options that matches regular expressions in {@code filters}.
 140      * This is the combination of JTReg arguments test.vm.opts and test.java.opts.
 141      * @param filters Regular expressions used to filter out options.
 142      * @return An array of options, or an empty array if no options.
 143      */
 144     public static String[] getFilteredTestJavaOpts(String... filters) {
 145         String options[] = getTestJavaOpts();
 146 
 147         if (filters.length == 0) {
 148             return options;
 149         }
 150 
 151         List<String> filteredOptions = new ArrayList<String>(options.length);
 152         Pattern patterns[] = new Pattern[filters.length];
 153         for (int i = 0; i < filters.length; i++) {
 154             patterns[i] = Pattern.compile(filters[i]);
 155         }
 156 
 157         for (String option : options) {
 158             boolean matched = false;
 159             for (int i = 0; i < patterns.length && !matched; i++) {
 160                 Matcher matcher = patterns[i].matcher(option);
 161                 matched = matcher.find();
 162             }
 163             if (!matched) {
 164                 filteredOptions.add(option);
 165             }
 166         }
 167 
 168         return filteredOptions.toArray(new String[filteredOptions.size()]);
 169     }
 170 
 171     /**
 172      * Combines given arguments with default JTReg arguments for a jvm running a test.
 173      * This is the combination of JTReg arguments test.vm.opts and test.java.opts
 174      * @return The combination of JTReg test java options and user args.
 175      */
 176     public static String[] addTestJavaOpts(String... userArgs) {
 177         List<String> opts = new ArrayList<String>();
 178         Collections.addAll(opts, getTestJavaOpts());
 179         Collections.addAll(opts, userArgs);
 180         return opts.toArray(new String[0]);
 181     }
 182 
 183     /**
 184      * Splits a string by white space.
 185      * Works like String.split(), but returns an empty array
 186      * if the string is null or empty.
 187      */
 188     private static String[] safeSplitString(String s) {
 189         if (s == null || s.trim().isEmpty()) {
 190             return new String[] {};
 191         }
 192         return s.trim().split("\\s+");
 193     }
 194 
 195     /**
 196      * @return The full command line for the ProcessBuilder.
 197      */
 198     public static String getCommandLine(ProcessBuilder pb) {
 199         StringBuilder cmd = new StringBuilder();
 200         for (String s : pb.command()) {
 201             cmd.append(s).append(" ");
 202         }
 203         return cmd.toString();
 204     }
 205 
 206     /**
 207      * Returns the free port on the local host.
 208      * The function will spin until a valid port number is found.
 209      *
 210      * @return The port number
 211      * @throws InterruptedException if any thread has interrupted the current thread
 212      * @throws IOException if an I/O error occurs when opening the socket
 213      */
 214     public static int getFreePort() throws InterruptedException, IOException {
 215         int port = -1;
 216 
 217         while (port <= 0) {
 218             Thread.sleep(100);
 219 
 220             ServerSocket serverSocket = null;
 221             try {
 222                 serverSocket = new ServerSocket(0);
 223                 port = serverSocket.getLocalPort();
 224             } finally {
 225                 serverSocket.close();
 226             }
 227         }
 228 
 229         return port;
 230     }
 231 
 232     /**
 233      * Returns the name of the local host.
 234      *
 235      * @return The host name
 236      * @throws UnknownHostException if IP address of a host could not be determined
 237      */
 238     public static String getHostname() throws UnknownHostException {
 239         InetAddress inetAddress = InetAddress.getLocalHost();
 240         String hostName = inetAddress.getHostName();
 241 
 242         assertTrue((hostName != null && !hostName.isEmpty()),
 243                 "Cannot get hostname");
 244 
 245         return hostName;
 246     }
 247 
 248     /**
 249      * Uses "jcmd -l" to search for a jvm pid. This function will wait
 250      * forever (until jtreg timeout) for the pid to be found.
 251      * @param key Regular expression to search for
 252      * @return The found pid.
 253      */
 254     public static int waitForJvmPid(String key) throws Throwable {
 255         final long iterationSleepMillis = 250;
 256         System.out.println("waitForJvmPid: Waiting for key '" + key + "'");
 257         System.out.flush();
 258         while (true) {
 259             int pid = tryFindJvmPid(key);
 260             if (pid >= 0) {
 261                 return pid;
 262             }
 263             Thread.sleep(iterationSleepMillis);
 264         }
 265     }
 266 
 267     /**
 268      * Searches for a jvm pid in the output from "jcmd -l".
 269      *
 270      * Example output from jcmd is:
 271      * 12498 sun.tools.jcmd.JCmd -l
 272      * 12254 /tmp/jdk8/tl/jdk/JTwork/classes/com/sun/tools/attach/Application.jar
 273      *
 274      * @param key A regular expression to search for.
 275      * @return The found pid, or -1 if Enot found.
 276      * @throws Exception If multiple matching jvms are found.
 277      */
 278     public static int tryFindJvmPid(String key) throws Throwable {
 279         OutputAnalyzer output = null;
 280         try {
 281             JDKToolLauncher jcmdLauncher = JDKToolLauncher.create("jcmd");
 282             jcmdLauncher.addToolArg("-l");
 283             output = ProcessTools.executeProcess(jcmdLauncher.getCommand());
 284             output.shouldHaveExitValue(0);
 285 
 286             // Search for a line starting with numbers (pid), follwed by the key.
 287             Pattern pattern = Pattern.compile("([0-9]+)\\s.*(" + key + ").*\\r?\\n");
 288             Matcher matcher = pattern.matcher(output.getStdout());
 289 
 290             int pid = -1;
 291             if (matcher.find()) {
 292                 pid = Integer.parseInt(matcher.group(1));
 293                 System.out.println("findJvmPid.pid: " + pid);
 294                 if (matcher.find()) {
 295                     throw new Exception("Found multiple JVM pids for key: " + key);
 296                 }
 297             }
 298             return pid;
 299         } catch (Throwable t) {
 300             System.out.println(String.format("Utils.findJvmPid(%s) failed: %s", key, t));
 301             throw t;
 302         }
 303     }
 304 
 305     /**
 306      * Returns file content as a list of strings
 307      *
 308      * @param file File to operate on
 309      * @return List of strings
 310      * @throws IOException
 311      */
 312     public static List<String> fileAsList(File file) throws IOException {
 313         assertTrue(file.exists() && file.isFile(),
 314                 file.getAbsolutePath() + " does not exist or not a file");
 315         List<String> output = new ArrayList<>();
 316         try (BufferedReader reader = new BufferedReader(new FileReader(file.getAbsolutePath()))) {
 317             while (reader.ready()) {
 318                 output.add(reader.readLine().replace(NEW_LINE, ""));
 319             }
 320         }
 321         return output;
 322     }
 323 
 324     /**
 325      * @return Unsafe instance.
 326      */
 327     public static synchronized Unsafe getUnsafe() {
 328         if (unsafe == null) {
 329             try {
 330                 Field f = Unsafe.class.getDeclaredField("theUnsafe");
 331                 f.setAccessible(true);
 332                 unsafe = (Unsafe) f.get(null);
 333             } catch (NoSuchFieldException | IllegalAccessException e) {
 334                 throw new RuntimeException("Unable to get Unsafe instance.", e);
 335             }
 336         }
 337         return unsafe;
 338     }
 339     private static final char[] hexArray = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
 340 
 341     /**
 342      * Returns hex view of byte array
 343      *
 344      * @param bytes byte array to process
 345      * @return Space separated hexadecimal string representation of bytes
 346      */
 347 
 348     public static String toHexString(byte[] bytes) {
 349         char[] hexView = new char[bytes.length * 3];
 350         int i = 0;
 351         for (byte b : bytes) {
 352             hexView[i++] = hexArray[(b >> 4) & 0x0F];
 353             hexView[i++] = hexArray[b & 0x0F];
 354             hexView[i++] = ' ';
 355         }
 356         return new String(hexView);
 357     }
 358 
 359     /**
 360      * Returns {@link java.util.Random} generator initialized with particular seed.
 361      * The seed could be provided via system property {@link Utils#SEED_PROPERTY_NAME}
 362      * In case no seed is provided, the method uses a random number.
 363      * The used seed printed to stdout.
 364      * @return {@link java.util.Random} generator with particular seed.
 365      */
 366     public static Random getRandomInstance() {
 367         if (RANDOM_GENERATOR == null) {
 368             synchronized (Utils.class) {
 369                 if (RANDOM_GENERATOR == null) {
 370                     RANDOM_GENERATOR = new Random(SEED);
 371                     System.out.printf("For random generator using seed: %d%n", SEED);
 372                     System.out.printf("To re-run test with same seed value please add \"-D%s=%d\" to command line.%n", SEED_PROPERTY_NAME, SEED);
 373                 }
 374             }
 375         }
 376         return RANDOM_GENERATOR;
 377     }
 378     
 379     /**
 380      * Wait for condition to be true
 381      *
 382      * @param condition, a condition to wait for, using default sleep time 100
 383      */
 384     public static final void waitForCondition(BooleanSupplier condition) {
 385         waitForCondition(condition, 100);
 386     }
 387 
 388     /**
 389      * Wait until timeout for condition to be true
 390      *
 391      * @param condition, a condition to wait for
 392      * @param sleepTime a time to sleep value in milliseconds
 393      */
 394     public static final void waitForCondition(BooleanSupplier condition,
 395             long sleepTime) {
 396         while (!condition.getAsBoolean()) {
 397             try {
 398                 Thread.sleep(sleepTime);
 399             } catch (InterruptedException e) {
 400                 Thread.currentThread().interrupt();
 401                 throw new Error(e);
 402             }
 403         }
 404     }
 405 }