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