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