1 /*
2  * Copyright (c) 2013, 2018, 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.testlibrary;
25 
26 import static jdk.testlibrary.Asserts.assertTrue;
27 
28 import java.io.BufferedReader;
29 import java.io.File;
30 import java.io.FileReader;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.OutputStream;
34 import java.net.InetAddress;
35 import java.net.ServerSocket;
36 import java.net.UnknownHostException;
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.Arrays;
40 import java.util.LinkedList;
41 import java.nio.file.Paths;
42 import java.util.Collections;
43 import java.util.Objects;
44 import java.util.regex.Pattern;
45 import java.util.regex.Matcher;
46 import java.util.concurrent.TimeUnit;
47 import java.util.function.Function;
48 import java.nio.file.Files;
49 
50 /**
51  * Common library for various test helper functions.
52  */
53 public final class Utils {
54 
55     /**
56      * Returns the sequence used by operating system to separate lines.
57      */
58     public static final String NEW_LINE = System.getProperty("line.separator");
59 
60     /**
61      * Returns the value of 'test.vm.opts'system property.
62      */
63     public static final String VM_OPTIONS = System.getProperty("test.vm.opts", "").trim();
64 
65     /**
66      * Returns the value of 'test.java.opts'system property.
67      */
68     public static final String JAVA_OPTIONS = System.getProperty("test.java.opts", "").trim();
69 
70 
71     /**
72     * Returns the value of 'test.timeout.factor' system property
73     * converted to {@code double}.
74     */
75     public static final double TIMEOUT_FACTOR;
76     static {
77         String toFactor = System.getProperty("test.timeout.factor", "1.0");
78         TIMEOUT_FACTOR = Double.parseDouble(toFactor);
79     }
80 
81     /**
82     * Returns the value of JTREG default test timeout in milliseconds
83     * converted to {@code long}.
84     */
85     public static final long DEFAULT_TEST_TIMEOUT = TimeUnit.SECONDS.toMillis(120);
86 
87     private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
88     private static final int DEFAULT_BUFFER_SIZE = 8192;
89 
Utils()90     private Utils() {
91         // Private constructor to prevent class instantiation
92     }
93 
94     /**
95      * Returns the list of VM options.
96      *
97      * @return List of VM options
98      */
getVmOptions()99     public static List<String> getVmOptions() {
100         return Arrays.asList(safeSplitString(VM_OPTIONS));
101     }
102 
103     /**
104      * Returns the list of VM options with -J prefix.
105      *
106      * @return The list of VM options with -J prefix
107      */
getForwardVmOptions()108     public static List<String> getForwardVmOptions() {
109         String[] opts = safeSplitString(VM_OPTIONS);
110         for (int i = 0; i < opts.length; i++) {
111             opts[i] = "-J" + opts[i];
112         }
113         return Arrays.asList(opts);
114     }
115 
116     /**
117      * Returns the default JTReg arguments for a jvm running a test.
118      * This is the combination of JTReg arguments test.vm.opts and test.java.opts.
119      * @return An array of options, or an empty array if no opptions.
120      */
getTestJavaOpts()121     public static String[] getTestJavaOpts() {
122         List<String> opts = new ArrayList<String>();
123         Collections.addAll(opts, safeSplitString(VM_OPTIONS));
124         Collections.addAll(opts, safeSplitString(JAVA_OPTIONS));
125         return opts.toArray(new String[0]);
126     }
127 
128     /**
129      * Combines given arguments with default JTReg arguments for a jvm running a test.
130      * This is the combination of JTReg arguments test.vm.opts and test.java.opts
131      * @return The combination of JTReg test java options and user args.
132      */
addTestJavaOpts(String... userArgs)133     public static String[] addTestJavaOpts(String... userArgs) {
134         List<String> opts = new ArrayList<String>();
135         Collections.addAll(opts, getTestJavaOpts());
136         Collections.addAll(opts, userArgs);
137         return opts.toArray(new String[0]);
138     }
139 
140     /**
141      * Removes any options specifying which GC to use, for example "-XX:+UseG1GC".
142      * Removes any options matching: -XX:(+/-)Use*GC
143      * Used when a test need to set its own GC version. Then any
144      * GC specified by the framework must first be removed.
145      * @return A copy of given opts with all GC options removed.
146      */
147     private static final Pattern useGcPattern = Pattern.compile("\\-XX\\:[\\+\\-]Use.+GC");
removeGcOpts(List<String> opts)148     public static List<String> removeGcOpts(List<String> opts) {
149         List<String> optsWithoutGC = new ArrayList<String>();
150         for (String opt : opts) {
151             if (useGcPattern.matcher(opt).matches()) {
152                 System.out.println("removeGcOpts: removed " + opt);
153             } else {
154                 optsWithoutGC.add(opt);
155             }
156         }
157         return optsWithoutGC;
158     }
159 
160     /**
161      * Splits a string by white space.
162      * Works like String.split(), but returns an empty array
163      * if the string is null or empty.
164      */
safeSplitString(String s)165     private static String[] safeSplitString(String s) {
166         if (s == null || s.trim().isEmpty()) {
167             return new String[] {};
168         }
169         return s.trim().split("\\s+");
170     }
171 
172     /**
173      * @return The full command line for the ProcessBuilder.
174      */
getCommandLine(ProcessBuilder pb)175     public static String getCommandLine(ProcessBuilder pb) {
176         StringBuilder cmd = new StringBuilder();
177         for (String s : pb.command()) {
178             cmd.append(s).append(" ");
179         }
180         return cmd.toString();
181     }
182 
183     /**
184      * Returns the free port on the local host.
185      * The function will spin until a valid port number is found.
186      *
187      * @return The port number
188      * @throws InterruptedException if any thread has interrupted the current thread
189      * @throws IOException if an I/O error occurs when opening the socket
190      */
getFreePort()191     public static int getFreePort() throws InterruptedException, IOException {
192         int port = -1;
193 
194         while (port <= 0) {
195             Thread.sleep(100);
196 
197             ServerSocket serverSocket = null;
198             try {
199                 serverSocket = new ServerSocket(0);
200                 port = serverSocket.getLocalPort();
201             } finally {
202                 serverSocket.close();
203             }
204         }
205 
206         return port;
207     }
208 
209     /**
210      * Returns the name of the local host.
211      *
212      * @return The host name
213      * @throws UnknownHostException if IP address of a host could not be determined
214      */
getHostname()215     public static String getHostname() throws UnknownHostException {
216         InetAddress inetAddress = InetAddress.getLocalHost();
217         String hostName = inetAddress.getHostName();
218 
219         assertTrue((hostName != null && !hostName.isEmpty()),
220                 "Cannot get hostname");
221 
222         return hostName;
223     }
224 
225     /**
226      * Uses "jcmd -l" to search for a jvm pid. This function will wait
227      * forever (until jtreg timeout) for the pid to be found.
228      * @param key Regular expression to search for
229      * @return The found pid.
230      */
waitForJvmPid(String key)231     public static int waitForJvmPid(String key) throws Throwable {
232         final long iterationSleepMillis = 250;
233         System.out.println("waitForJvmPid: Waiting for key '" + key + "'");
234         System.out.flush();
235         while (true) {
236             int pid = tryFindJvmPid(key);
237             if (pid >= 0) {
238                 return pid;
239             }
240             Thread.sleep(iterationSleepMillis);
241         }
242     }
243 
244     /**
245      * Searches for a jvm pid in the output from "jcmd -l".
246      *
247      * Example output from jcmd is:
248      * 12498 sun.tools.jcmd.JCmd -l
249      * 12254 /tmp/jdk8/tl/jdk/JTwork/classes/com/sun/tools/attach/Application.jar
250      *
251      * @param key A regular expression to search for.
252      * @return The found pid, or -1 if Enot found.
253      * @throws Exception If multiple matching jvms are found.
254      */
tryFindJvmPid(String key)255     public static int tryFindJvmPid(String key) throws Throwable {
256         OutputAnalyzer output = null;
257         try {
258             JDKToolLauncher jcmdLauncher = JDKToolLauncher.create("jcmd");
259             jcmdLauncher.addToolArg("-l");
260             output = ProcessTools.executeProcess(jcmdLauncher.getCommand());
261             output.shouldHaveExitValue(0);
262 
263             // Search for a line starting with numbers (pid), follwed by the key.
264             Pattern pattern = Pattern.compile("([0-9]+)\\s.*(" + key + ").*\\r?\\n");
265             Matcher matcher = pattern.matcher(output.getStdout());
266 
267             int pid = -1;
268             if (matcher.find()) {
269                 pid = Integer.parseInt(matcher.group(1));
270                 System.out.println("findJvmPid.pid: " + pid);
271                 if (matcher.find()) {
272                     throw new Exception("Found multiple JVM pids for key: " + key);
273                 }
274             }
275             return pid;
276         } catch (Throwable t) {
277             System.out.println(String.format("Utils.findJvmPid(%s) failed: %s", key, t));
278             throw t;
279         }
280     }
281 
282     /**
283      * Returns file content as a list of strings
284      *
285      * @param file File to operate on
286      * @return List of strings
287      * @throws IOException
288      */
fileAsList(File file)289     public static List<String> fileAsList(File file) throws IOException {
290         assertTrue(file.exists() && file.isFile(),
291                 file.getAbsolutePath() + " does not exist or not a file");
292         List<String> output = new ArrayList<>();
293         try (BufferedReader reader = new BufferedReader(new FileReader(file.getAbsolutePath()))) {
294             while (reader.ready()) {
295                 output.add(reader.readLine().replace(NEW_LINE, ""));
296             }
297         }
298         return output;
299     }
300 
301     /**
302      * Helper method to read all bytes from InputStream
303      *
304      * @param is InputStream to read from
305      * @return array of bytes
306      * @throws IOException
307      */
readAllBytes(InputStream is)308     public static byte[] readAllBytes(InputStream is) throws IOException {
309         byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
310         int capacity = buf.length;
311         int nread = 0;
312         int n;
313         for (;;) {
314             // read to EOF which may read more or less than initial buffer size
315             while ((n = is.read(buf, nread, capacity - nread)) > 0)
316                 nread += n;
317 
318             // if the last call to read returned -1, then we're done
319             if (n < 0)
320                 break;
321 
322             // need to allocate a larger buffer
323             if (capacity <= MAX_BUFFER_SIZE - capacity) {
324                 capacity = capacity << 1;
325             } else {
326                 if (capacity == MAX_BUFFER_SIZE)
327                     throw new OutOfMemoryError("Required array size too large");
328                 capacity = MAX_BUFFER_SIZE;
329             }
330             buf = Arrays.copyOf(buf, capacity);
331         }
332         return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
333     }
334 
335     /**
336      * Adjusts the provided timeout value for the TIMEOUT_FACTOR
337      * @param tOut the timeout value to be adjusted
338      * @return The timeout value adjusted for the value of "test.timeout.factor"
339      *         system property
340      */
adjustTimeout(long tOut)341     public static long adjustTimeout(long tOut) {
342         return Math.round(tOut * Utils.TIMEOUT_FACTOR);
343     }
344 
345     /**
346      * Interface same as java.lang.Runnable but with
347      * method {@code run()} able to throw any Throwable.
348      */
349     public static interface ThrowingRunnable {
run()350         void run() throws Throwable;
351     }
352 
353     /**
354      * Filters out an exception that may be thrown by the given
355      * test according to the given filter.
356      *
357      * @param test - method that is invoked and checked for exception.
358      * @param filter - function that checks if the thrown exception matches
359      *                 criteria given in the filter's implementation.
360      * @return - exception that matches the filter if it has been thrown or
361      *           {@code null} otherwise.
362      * @throws Throwable - if test has thrown an exception that does not
363      *                     match the filter.
364      */
filterException(ThrowingRunnable test, Function<Throwable, Boolean> filter)365     public static Throwable filterException(ThrowingRunnable test,
366             Function<Throwable, Boolean> filter) throws Throwable {
367         try {
368             test.run();
369         } catch (Throwable t) {
370             if (filter.apply(t)) {
371                 return t;
372             } else {
373                 throw t;
374             }
375         }
376         return null;
377     }
378 
379     private static final int BUFFER_SIZE = 1024;
380 
381     /**
382      * Reads all bytes from the input stream and writes the bytes to the
383      * given output stream in the order that they are read. On return, the
384      * input stream will be at end of stream. This method does not close either
385      * stream.
386      * <p>
387      * This method may block indefinitely reading from the input stream, or
388      * writing to the output stream. The behavior for the case where the input
389      * and/or output stream is <i>asynchronously closed</i>, or the thread
390      * interrupted during the transfer, is highly input and output stream
391      * specific, and therefore not specified.
392      * <p>
393      * If an I/O error occurs reading from the input stream or writing to the
394      * output stream, then it may do so after some bytes have been read or
395      * written. Consequently the input stream may not be at end of stream and
396      * one, or both, streams may be in an inconsistent state. It is strongly
397      * recommended that both streams be promptly closed if an I/O error occurs.
398      *
399      * @param  in the input stream, non-null
400      * @param  out the output stream, non-null
401      * @return the number of bytes transferred
402      * @throws IOException if an I/O error occurs when reading or writing
403      * @throws NullPointerException if {@code in} or {@code out} is {@code null}
404      *
405      */
transferTo(InputStream in, OutputStream out)406     public static long transferTo(InputStream in, OutputStream out)
407             throws IOException  {
408         long transferred = 0;
409         byte[] buffer = new byte[BUFFER_SIZE];
410         int read;
411         while ((read = in.read(buffer, 0, BUFFER_SIZE)) >= 0) {
412             out.write(buffer, 0, read);
413             transferred += read;
414         }
415         return transferred;
416     }
417 
418     // Parses the specified source file for "@{id} breakpoint" tags and returns
419     // list of the line numbers containing the tag.
420     // Example:
421     //   System.out.println("BP is here");  // @1 breakpoint
parseBreakpoints(String filePath, int id)422     public static List<Integer> parseBreakpoints(String filePath, int id) {
423         final String pattern = "@" + id + " breakpoint";
424         int lineNum = 1;
425         List<Integer> result = new LinkedList<>();
426         try {
427             for (String line: Files.readAllLines(Paths.get(filePath))) {
428                 if (line.contains(pattern)) {
429                     result.add(lineNum);
430                 }
431                 lineNum++;
432             }
433         } catch (IOException ex) {
434             throw new RuntimeException("failed to parse " + filePath, ex);
435         }
436         return result;
437     }
438 
439     // gets full test source path for the given test filename
getTestSourcePath(String fileName)440     public static String getTestSourcePath(String fileName) {
441         return Paths.get(System.getProperty("test.src")).resolve(fileName).toString();
442     }
443 }
444