1 /*
   2  * Copyright (c) 2013, 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 package jdk.test.lib;
  25 
  26 import java.io.File;
  27 import static jdk.test.lib.Asserts.assertTrue;
  28 import java.io.IOException;
  29 import java.lang.reflect.Field;
  30 import java.net.InetAddress;
  31 import java.net.MalformedURLException;
  32 import java.net.ServerSocket;
  33 import java.net.URL;
  34 import java.net.URLClassLoader;
  35 import java.net.UnknownHostException;
  36 import java.nio.file.Files;
  37 import java.nio.file.Path;
  38 import java.nio.file.Paths;
  39 import java.util.ArrayList;
  40 import java.util.Arrays;
  41 import java.util.Collection;
  42 import java.util.Collections;
  43 import java.util.Iterator;
  44 import java.util.Map;
  45 import java.util.HashMap;
  46 import java.util.List;
  47 import java.util.Random;
  48 import java.util.function.BooleanSupplier;
  49 import java.util.concurrent.TimeUnit;
  50 import java.util.function.Consumer;
  51 import java.util.regex.Matcher;
  52 import java.util.regex.Pattern;
  53 import sun.misc.Unsafe;
  54 
  55 /**
  56  * Common library for various test helper functions.
  57  */
  58 public final class Utils {
  59 
  60     /**
  61      * Returns the value of 'test.class.path' system property.
  62      */
  63     public static final String TEST_CLASS_PATH = System.getProperty("test.class.path", ".");
  64 
  65     /**
  66      * Returns the sequence used by operating system to separate lines.
  67      */
  68     public static final String NEW_LINE = System.getProperty("line.separator");
  69 
  70     /**
  71      * Returns the value of 'test.vm.opts' system property.
  72      */
  73     public static final String VM_OPTIONS = System.getProperty("test.vm.opts", "").trim();
  74 
  75     /**
  76      * Returns the value of 'test.java.opts' system property.
  77      */
  78     public static final String JAVA_OPTIONS = System.getProperty("test.java.opts", "").trim();
  79 
  80     /**
  81      * Returns the value of 'test.src' system property.
  82      */
  83     public static final String TEST_SRC = System.getProperty("test.src", ".").trim();
  84 
  85     private static Unsafe unsafe = null;
  86 
  87     /**
  88      * Defines property name for seed value.
  89      */
  90     public static final String SEED_PROPERTY_NAME = "jdk.test.lib.random.seed";
  91 
  92     /* (non-javadoc)
  93      * Random generator with (or without) predefined seed. Depends on
  94      * "jdk.test.lib.random.seed" property value.
  95      */
  96     private static volatile Random RANDOM_GENERATOR;
  97 
  98     /**
  99      * Contains the seed value used for {@link java.util.Random} creation.
 100      */
 101     public static final long SEED = Long.getLong(SEED_PROPERTY_NAME, new Random().nextLong());
 102     /**
 103     * Returns the value of 'test.timeout.factor' system property
 104     * converted to {@code double}.
 105     */
 106     public static final double TIMEOUT_FACTOR;
 107     static {
 108         String toFactor = System.getProperty("test.timeout.factor", "1.0");
 109         TIMEOUT_FACTOR = Double.parseDouble(toFactor);
 110     }
 111 
 112     /**
 113     * Returns the value of JTREG default test timeout in milliseconds
 114     * converted to {@code long}.
 115     */
 116     public static final long DEFAULT_TEST_TIMEOUT = TimeUnit.SECONDS.toMillis(120);
 117 
 118     private Utils() {
 119         // Private constructor to prevent class instantiation
 120     }
 121 
 122     /**
 123      * Returns the list of VM options.
 124      *
 125      * @return List of VM options
 126      */
 127     public static List<String> getVmOptions() {
 128         return Arrays.asList(safeSplitString(VM_OPTIONS));
 129     }
 130 
 131     /**
 132      * Returns the list of VM options with -J prefix.
 133      *
 134      * @return The list of VM options with -J prefix
 135      */
 136     public static List<String> getForwardVmOptions() {
 137         String[] opts = safeSplitString(VM_OPTIONS);
 138         for (int i = 0; i < opts.length; i++) {
 139             opts[i] = "-J" + opts[i];
 140         }
 141         return Arrays.asList(opts);
 142     }
 143 
 144     /**
 145      * Returns the default JTReg arguments for a jvm running a test.
 146      * This is the combination of JTReg arguments test.vm.opts and test.java.opts.
 147      * @return An array of options, or an empty array if no options.
 148      */
 149     public static String[] getTestJavaOpts() {
 150         List<String> opts = new ArrayList<String>();
 151         Collections.addAll(opts, safeSplitString(VM_OPTIONS));
 152         Collections.addAll(opts, safeSplitString(JAVA_OPTIONS));
 153         return opts.toArray(new String[0]);
 154     }
 155 
 156     /**
 157      * Returns the default JTReg arguments for a jvm running a test without
 158      * options that matches regular expressions in {@code filters}.
 159      * This is the combination of JTReg arguments test.vm.opts and test.java.opts.
 160      * @param filters Regular expressions used to filter out options.
 161      * @return An array of options, or an empty array if no options.
 162      */
 163     public static String[] getFilteredTestJavaOpts(String... filters) {
 164         String options[] = getTestJavaOpts();
 165 
 166         if (filters.length == 0) {
 167             return options;
 168         }
 169 
 170         List<String> filteredOptions = new ArrayList<String>(options.length);
 171         Pattern patterns[] = new Pattern[filters.length];
 172         for (int i = 0; i < filters.length; i++) {
 173             patterns[i] = Pattern.compile(filters[i]);
 174         }
 175 
 176         for (String option : options) {
 177             boolean matched = false;
 178             for (int i = 0; i < patterns.length && !matched; i++) {
 179                 Matcher matcher = patterns[i].matcher(option);
 180                 matched = matcher.find();
 181             }
 182             if (!matched) {
 183                 filteredOptions.add(option);
 184             }
 185         }
 186 
 187         return filteredOptions.toArray(new String[filteredOptions.size()]);
 188     }
 189 
 190     /**
 191      * Combines given arguments with default JTReg arguments for a jvm running a test.
 192      * This is the combination of JTReg arguments test.vm.opts and test.java.opts
 193      * @return The combination of JTReg test java options and user args.
 194      */
 195     public static String[] addTestJavaOpts(String... userArgs) {
 196         List<String> opts = new ArrayList<String>();
 197         Collections.addAll(opts, getTestJavaOpts());
 198         Collections.addAll(opts, userArgs);
 199         return opts.toArray(new String[0]);
 200     }
 201 
 202     /**
 203      * Splits a string by white space.
 204      * Works like String.split(), but returns an empty array
 205      * if the string is null or empty.
 206      */
 207     private static String[] safeSplitString(String s) {
 208         if (s == null || s.trim().isEmpty()) {
 209             return new String[] {};
 210         }
 211         return s.trim().split("\\s+");
 212     }
 213 
 214     /**
 215      * @return The full command line for the ProcessBuilder.
 216      */
 217     public static String getCommandLine(ProcessBuilder pb) {
 218         StringBuilder cmd = new StringBuilder();
 219         for (String s : pb.command()) {
 220             cmd.append(s).append(" ");
 221         }
 222         return cmd.toString();
 223     }
 224 
 225     /**
 226      * Returns the free port on the local host.
 227      * The function will spin until a valid port number is found.
 228      *
 229      * @return The port number
 230      * @throws InterruptedException if any thread has interrupted the current thread
 231      * @throws IOException if an I/O error occurs when opening the socket
 232      */
 233     public static int getFreePort() throws InterruptedException, IOException {
 234         int port = -1;
 235 
 236         while (port <= 0) {
 237             Thread.sleep(100);
 238 
 239             ServerSocket serverSocket = null;
 240             try {
 241                 serverSocket = new ServerSocket(0);
 242                 port = serverSocket.getLocalPort();
 243             } finally {
 244                 serverSocket.close();
 245             }
 246         }
 247 
 248         return port;
 249     }
 250 
 251     /**
 252      * Returns the name of the local host.
 253      *
 254      * @return The host name
 255      * @throws UnknownHostException if IP address of a host could not be determined
 256      */
 257     public static String getHostname() throws UnknownHostException {
 258         InetAddress inetAddress = InetAddress.getLocalHost();
 259         String hostName = inetAddress.getHostName();
 260 
 261         assertTrue((hostName != null && !hostName.isEmpty()),
 262                 "Cannot get hostname");
 263 
 264         return hostName;
 265     }
 266 
 267     /**
 268      * Uses "jcmd -l" to search for a jvm pid. This function will wait
 269      * forever (until jtreg timeout) for the pid to be found.
 270      * @param key Regular expression to search for
 271      * @return The found pid.
 272      */
 273     public static int waitForJvmPid(String key) throws Throwable {
 274         final long iterationSleepMillis = 250;
 275         System.out.println("waitForJvmPid: Waiting for key '" + key + "'");
 276         System.out.flush();
 277         while (true) {
 278             int pid = tryFindJvmPid(key);
 279             if (pid >= 0) {
 280                 return pid;
 281             }
 282             Thread.sleep(iterationSleepMillis);
 283         }
 284     }
 285 
 286     /**
 287      * Searches for a jvm pid in the output from "jcmd -l".
 288      *
 289      * Example output from jcmd is:
 290      * 12498 sun.tools.jcmd.JCmd -l
 291      * 12254 /tmp/jdk8/tl/jdk/JTwork/classes/com/sun/tools/attach/Application.jar
 292      *
 293      * @param key A regular expression to search for.
 294      * @return The found pid, or -1 if not found.
 295      * @throws Exception If multiple matching jvms are found.
 296      */
 297     public static int tryFindJvmPid(String key) throws Throwable {
 298         OutputAnalyzer output = null;
 299         try {
 300             JDKToolLauncher jcmdLauncher = JDKToolLauncher.create("jcmd");
 301             jcmdLauncher.addToolArg("-l");
 302             output = ProcessTools.executeProcess(jcmdLauncher.getCommand());
 303             output.shouldHaveExitValue(0);
 304 
 305             // Search for a line starting with numbers (pid), follwed by the key.
 306             Pattern pattern = Pattern.compile("([0-9]+)\\s.*(" + key + ").*\\r?\\n");
 307             Matcher matcher = pattern.matcher(output.getStdout());
 308 
 309             int pid = -1;
 310             if (matcher.find()) {
 311                 pid = Integer.parseInt(matcher.group(1));
 312                 System.out.println("findJvmPid.pid: " + pid);
 313                 if (matcher.find()) {
 314                     throw new Exception("Found multiple JVM pids for key: " + key);
 315                 }
 316             }
 317             return pid;
 318         } catch (Throwable t) {
 319             System.out.println(String.format("Utils.findJvmPid(%s) failed: %s", key, t));
 320             throw t;
 321         }
 322     }
 323 
 324     /**
 325      * Return the contents of the named file as a single String,
 326      * or null if not found.
 327      * @param filename name of the file to read
 328      * @return String contents of file, or null if file not found.
 329      * @throws  IOException
 330      *          if an I/O error occurs reading from the file or a malformed or
 331      *          unmappable byte sequence is read
 332      */
 333     public static String fileAsString(String filename) throws IOException {
 334         Path filePath = Paths.get(filename);
 335         if (!Files.exists(filePath)) return null;
 336         return new String(Files.readAllBytes(filePath));
 337     }
 338 
 339     /**
 340      * @return Unsafe instance.
 341      */
 342     public static synchronized Unsafe getUnsafe() {
 343         if (unsafe == null) {
 344             try {
 345                 Field f = Unsafe.class.getDeclaredField("theUnsafe");
 346                 f.setAccessible(true);
 347                 unsafe = (Unsafe) f.get(null);
 348             } catch (NoSuchFieldException | IllegalAccessException e) {
 349                 throw new RuntimeException("Unable to get Unsafe instance.", e);
 350             }
 351         }
 352         return unsafe;
 353     }
 354     private static final char[] hexArray = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
 355 
 356     /**
 357      * Returns hex view of byte array
 358      *
 359      * @param bytes byte array to process
 360      * @return Space separated hexadecimal string representation of bytes
 361      */
 362 
 363     public static String toHexString(byte[] bytes) {
 364         char[] hexView = new char[bytes.length * 3];
 365         int i = 0;
 366         for (byte b : bytes) {
 367             hexView[i++] = hexArray[(b >> 4) & 0x0F];
 368             hexView[i++] = hexArray[b & 0x0F];
 369             hexView[i++] = ' ';
 370         }
 371         return new String(hexView);
 372     }
 373 
 374     /**
 375      * Returns {@link java.util.Random} generator initialized with particular seed.
 376      * The seed could be provided via system property {@link Utils#SEED_PROPERTY_NAME}
 377      * In case no seed is provided, the method uses a random number.
 378      * The used seed printed to stdout.
 379      * @return {@link java.util.Random} generator with particular seed.
 380      */
 381     public static Random getRandomInstance() {
 382         if (RANDOM_GENERATOR == null) {
 383             synchronized (Utils.class) {
 384                 if (RANDOM_GENERATOR == null) {
 385                     RANDOM_GENERATOR = new Random(SEED);
 386                     System.out.printf("For random generator using seed: %d%n", SEED);
 387                     System.out.printf("To re-run test with same seed value please add \"-D%s=%d\" to command line.%n", SEED_PROPERTY_NAME, SEED);
 388                 }
 389             }
 390         }
 391         return RANDOM_GENERATOR;
 392     }
 393 
 394     /**
 395      * Returns random element of non empty collection
 396      *
 397      * @param <T> a type of collection element
 398      * @param collection collection of elements
 399      * @return random element of collection
 400      * @throws IllegalArgumentException if collection is empty
 401      */
 402     public static <T> T getRandomElement(Collection<T> collection)
 403             throws IllegalArgumentException {
 404         if (collection.isEmpty()) {
 405             throw new IllegalArgumentException("Empty collection");
 406         }
 407         Random random = getRandomInstance();
 408         int elementIndex = 1 + random.nextInt(collection.size() - 1);
 409         Iterator<T> iterator = collection.iterator();
 410         while (--elementIndex != 0) {
 411             iterator.next();
 412         }
 413         return iterator.next();
 414     }
 415 
 416     /**
 417      * Returns random element of non empty array
 418      *
 419      * @param <T> a type of array element
 420      * @param array array of elements
 421      * @return random element of array
 422      * @throws IllegalArgumentException if array is empty
 423      */
 424     public static <T> T getRandomElement(T[] array)
 425             throws IllegalArgumentException {
 426         if (array == null || array.length == 0) {
 427             throw new IllegalArgumentException("Empty or null array");
 428         }
 429         Random random = getRandomInstance();
 430         return array[random.nextInt(array.length)];
 431     }
 432 
 433     /**
 434      * Wait for condition to be true
 435      *
 436      * @param condition, a condition to wait for
 437      */
 438     public static final void waitForCondition(BooleanSupplier condition) {
 439         waitForCondition(condition, -1L, 100L);
 440     }
 441 
 442     /**
 443      * Wait until timeout for condition to be true
 444      *
 445      * @param condition, a condition to wait for
 446      * @param timeout a time in milliseconds to wait for condition to be true
 447      * specifying -1 will wait forever
 448      * @return condition value, to determine if wait was successful
 449      */
 450     public static final boolean waitForCondition(BooleanSupplier condition,
 451             long timeout) {
 452         return waitForCondition(condition, timeout, 100L);
 453     }
 454 
 455     /**
 456      * Wait until timeout for condition to be true for specified time
 457      *
 458      * @param condition, a condition to wait for
 459      * @param timeout a time in milliseconds to wait for condition to be true,
 460      * specifying -1 will wait forever
 461      * @param sleepTime a time to sleep value in milliseconds
 462      * @return condition value, to determine if wait was successful
 463      */
 464     public static final boolean waitForCondition(BooleanSupplier condition,
 465             long timeout, long sleepTime) {
 466         long startTime = System.currentTimeMillis();
 467         while (!(condition.getAsBoolean() || (timeout != -1L
 468                 && ((System.currentTimeMillis() - startTime) > timeout)))) {
 469             try {
 470                 Thread.sleep(sleepTime);
 471             } catch (InterruptedException e) {
 472                 Thread.currentThread().interrupt();
 473                 throw new Error(e);
 474             }
 475         }
 476         return condition.getAsBoolean();
 477     }
 478 
 479     /**
 480      * Adjusts the provided timeout value for the TIMEOUT_FACTOR
 481      * @param tOut the timeout value to be adjusted
 482      * @return The timeout value adjusted for the value of "test.timeout.factor"
 483      *         system property
 484      */
 485     public static long adjustTimeout(long tOut) {
 486         return Math.round(tOut * Utils.TIMEOUT_FACTOR);
 487     }
 488 
 489     /**
 490      * Ensures a requested class is loaded
 491      * @param aClass class to load
 492      */
 493     public static void ensureClassIsLoaded(Class<?> aClass) {
 494         if (aClass == null) {
 495             throw new Error("Requested null class");
 496         }
 497         try {
 498             Class.forName(aClass.getName(), /* initialize = */ true,
 499                     ClassLoader.getSystemClassLoader());
 500         } catch (ClassNotFoundException e) {
 501             throw new Error("Class not found", e);
 502         }
 503     }
 504     /**
 505      * @param parent a class loader to be the parent for the returned one
 506      * @return an UrlClassLoader with urls made of the 'test.class.path' jtreg
 507      *         property and with the given parent
 508      */
 509     public static URLClassLoader getTestClassPathURLClassLoader(ClassLoader parent) {
 510         URL[] urls = Arrays.stream(TEST_CLASS_PATH.split(File.pathSeparator))
 511                 .map(Paths::get)
 512                 .map(Path::toUri)
 513                 .map(x -> {
 514                     try {
 515                         return x.toURL();
 516                     } catch (MalformedURLException ex) {
 517                         throw new Error("Test issue. JTREG property"
 518                                 + " 'test.class.path'"
 519                                 + " is not defined correctly", ex);
 520                     }
 521                 }).toArray(URL[]::new);
 522         return new URLClassLoader(urls, parent);
 523     }
 524 
 525     /**
 526      * Runs runnable and checks that it throws expected exception. If exceptionException is null it means
 527      * that we expect no exception to be thrown.
 528      * @param runnable what we run
 529      * @param expectedException expected exception
 530      */
 531     public static void runAndCheckException(Runnable runnable, Class<? extends Throwable> expectedException) {
 532         runAndCheckException(runnable, t -> {
 533             if (t == null) {
 534                 if (expectedException != null) {
 535                     throw new AssertionError("Didn't get expected exception " + expectedException.getSimpleName());
 536                 }
 537             } else {
 538                 String message = "Got unexpected exception " + t.getClass().getSimpleName();
 539                 if (expectedException == null) {
 540                     throw new AssertionError(message, t);
 541                 } else if (!expectedException.isAssignableFrom(t.getClass())) {
 542                     message += " instead of " + expectedException.getSimpleName();
 543                     throw new AssertionError(message, t);
 544                 }
 545             }
 546         });
 547     }
 548 
 549     /**
 550      * Runs runnable and makes some checks to ensure that it throws expected exception.
 551      * @param runnable what we run
 552      * @param checkException a consumer which checks that we got expected exception and raises a new exception otherwise
 553      */
 554     public static void runAndCheckException(Runnable runnable, Consumer<Throwable> checkException) {
 555         try {
 556             runnable.run();
 557             checkException.accept(null);
 558         } catch (Throwable t) {
 559             checkException.accept(t);
 560         }
 561     }
 562 
 563     /**
 564      * Converts to VM type signature
 565      *
 566      * @param type Java type to convert
 567      * @return string representation of VM type
 568      */
 569     public static String toJVMTypeSignature(Class<?> type) {
 570         if (type.isPrimitive()) {
 571             if (type == boolean.class) {
 572                 return "Z";
 573             } else if (type == byte.class) {
 574                 return "B";
 575             } else if (type == char.class) {
 576                 return "C";
 577             } else if (type == double.class) {
 578                 return "D";
 579             } else if (type == float.class) {
 580                 return "F";
 581             } else if (type == int.class) {
 582                 return "I";
 583             } else if (type == long.class) {
 584                 return "J";
 585             } else if (type == short.class) {
 586                 return "S";
 587             } else if (type == void.class) {
 588                 return "V";
 589             } else {
 590                 throw new Error("Unsupported type: " + type);
 591             }
 592         }
 593         String result = type.getName().replaceAll("\\.", "/");
 594         if (!type.isArray()) {
 595             return "L" + result + ";";
 596         }
 597         return result;
 598     }
 599 
 600     public static Object[] getNullValues(Class<?>... types) {
 601         Object[] result = new Object[types.length];
 602         int i = 0;
 603         for (Class<?> type : types) {
 604             result[i++] = NULL_VALUES.get(type);
 605         }
 606         return result;
 607     }
 608     private static Map<Class<?>, Object> NULL_VALUES = new HashMap<>();
 609     static {
 610         NULL_VALUES.put(boolean.class, false);
 611         NULL_VALUES.put(byte.class, (byte) 0);
 612         NULL_VALUES.put(short.class, (short) 0);
 613         NULL_VALUES.put(char.class, '\0');
 614         NULL_VALUES.put(int.class, 0);
 615         NULL_VALUES.put(long.class, 0L);
 616         NULL_VALUES.put(float.class, 0.0f);
 617         NULL_VALUES.put(double.class, 0.0d);
 618     }
 619 }
 620