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