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