1/*
2 * Copyright (c) 2008, 2014, 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
24import java.io.OutputStream;
25import java.io.InputStream;
26import java.lang.annotation.ElementType;
27import java.lang.annotation.Retention;
28import java.lang.annotation.RetentionPolicy;
29import java.lang.annotation.Target;
30import java.lang.reflect.Method;
31import java.util.regex.Pattern;
32import java.io.StringWriter;
33import java.io.PrintWriter;
34import java.util.Set;
35import java.io.BufferedReader;
36import java.io.File;
37import java.io.FileFilter;
38import java.io.FileNotFoundException;
39import java.io.FileOutputStream;
40import java.io.IOException;
41import java.io.InputStreamReader;
42import java.io.PrintStream;
43import java.nio.charset.Charset;
44import java.nio.file.attribute.BasicFileAttributes;
45import java.nio.file.Files;
46import java.nio.file.FileVisitResult;
47import java.nio.file.SimpleFileVisitor;
48import java.nio.file.Path;
49import java.util.ArrayList;
50import java.util.List;
51import java.util.Locale;
52import java.util.Map;
53import java.util.Arrays;
54import javax.tools.JavaCompiler;
55import javax.tools.ToolProvider;
56
57import static java.nio.file.StandardCopyOption.*;
58import static java.nio.file.StandardOpenOption.*;
59
60/**
61 * This class provides some common utilities for the launcher tests.
62 */
63public class TestHelper {
64    // commonly used jtreg constants
65    static final File TEST_CLASSES_DIR;
66    static final File TEST_SOURCES_DIR;
67
68    static final String JAVAHOME = System.getProperty("java.home");
69    static final String JAVA_BIN;
70    static final String JAVA_LIB;
71    static final String javaCmd;
72    static final String javawCmd;
73    static final String javacCmd;
74    static final String jarCmd;
75    static final boolean haveServerVM;
76    static final boolean haveClientVM;
77
78    static final JavaCompiler compiler;
79
80    static final boolean debug = Boolean.getBoolean("TestHelper.Debug");
81    static final boolean isWindows =
82            System.getProperty("os.name", "unknown").startsWith("Windows");
83    static final boolean isMacOSX =
84            System.getProperty("os.name", "unknown").contains("OS X");
85    static final boolean is64Bit =
86            System.getProperty("sun.arch.data.model").equals("64");
87    static final boolean is32Bit =
88            System.getProperty("sun.arch.data.model").equals("32");
89    static final boolean isSolaris =
90            System.getProperty("os.name", "unknown").startsWith("SunOS");
91    static final boolean isLinux =
92            System.getProperty("os.name", "unknown").startsWith("Linux");
93    static final boolean isAIX =
94            System.getProperty("os.name", "unknown").startsWith("AIX");
95    static final String LIBJVM = isWindows
96                        ? "jvm.dll"
97                        : "libjvm" + (isMacOSX ? ".dylib" : ".so");
98
99    static final boolean isSparc = System.getProperty("os.arch").startsWith("sparc");
100
101    // make a note of the golden default locale
102    static final Locale DefaultLocale = Locale.getDefault();
103
104    static final String JAVA_FILE_EXT  = ".java";
105    static final String CLASS_FILE_EXT = ".class";
106    static final String JAR_FILE_EXT   = ".jar";
107    static final String EXE_FILE_EXT   = ".exe";
108    static final String JLDEBUG_KEY     = "_JAVA_LAUNCHER_DEBUG";
109    static final String EXPECTED_MARKER = "TRACER_MARKER:About to EXEC";
110    static final String TEST_PREFIX     = "###TestError###: ";
111
112    static int testExitValue = 0;
113
114    static {
115        String tmp = System.getProperty("test.classes", null);
116        if (tmp == null) {
117            throw new Error("property test.classes not defined ??");
118        }
119        TEST_CLASSES_DIR = new File(tmp).getAbsoluteFile();
120
121        tmp = System.getProperty("test.src", null);
122        if (tmp == null) {
123            throw new Error("property test.src not defined ??");
124        }
125        TEST_SOURCES_DIR = new File(tmp).getAbsoluteFile();
126
127        if (is64Bit && is32Bit) {
128            throw new RuntimeException("arch model cannot be both 32 and 64 bit");
129        }
130        if (!is64Bit && !is32Bit) {
131            throw new RuntimeException("arch model is not 32 or 64 bit ?");
132        }
133        compiler = ToolProvider.getSystemJavaCompiler();
134
135        File binDir = new File(JAVAHOME, "bin");
136        JAVA_BIN = binDir.getAbsolutePath();
137        File libDir = new File(JAVAHOME, "lib");
138        JAVA_LIB = libDir.getAbsolutePath();
139
140        File javaCmdFile = (isWindows)
141                ? new File(binDir, "java.exe")
142                : new File(binDir, "java");
143        javaCmd = javaCmdFile.getAbsolutePath();
144        if (!javaCmdFile.canExecute()) {
145            throw new RuntimeException("java <" + TestHelper.javaCmd +
146                    "> must exist and should be executable");
147        }
148
149        File javacCmdFile = (isWindows)
150                ? new File(binDir, "javac.exe")
151                : new File(binDir, "javac");
152        javacCmd = javacCmdFile.getAbsolutePath();
153
154        File jarCmdFile = (isWindows)
155                ? new File(binDir, "jar.exe")
156                : new File(binDir, "jar");
157        jarCmd = jarCmdFile.getAbsolutePath();
158        if (!jarCmdFile.canExecute()) {
159            throw new RuntimeException("java <" + TestHelper.jarCmd +
160                    "> must exist and should be executable");
161        }
162
163        if (isWindows) {
164            File javawCmdFile = new File(binDir, "javaw.exe");
165            javawCmd = javawCmdFile.getAbsolutePath();
166            if (!javawCmdFile.canExecute()) {
167                throw new RuntimeException("java <" + javawCmd +
168                        "> must exist and should be executable");
169            }
170        } else {
171            javawCmd = null;
172        }
173
174        if (!javacCmdFile.canExecute()) {
175            throw new RuntimeException("java <" + javacCmd +
176                    "> must exist and should be executable");
177        }
178
179        haveClientVM = haveVmVariant("client");
180        haveServerVM = haveVmVariant("server");
181    }
182    private static boolean haveVmVariant(String type) {
183        if (isWindows) {
184            File vmDir = new File(JAVA_BIN, type);
185            File jvmFile = new File(vmDir, LIBJVM);
186            return jvmFile.exists();
187        } else {
188            File vmDir = new File(JAVA_LIB, type);
189            File vmArchDir = new File(vmDir, getJreArch());
190            File jvmFile = new File(vmArchDir, LIBJVM);
191            return jvmFile.exists();
192        }
193    }
194    void run(String[] args) throws Exception {
195        int passed = 0, failed = 0;
196        final Pattern p = (args != null && args.length > 0)
197                ? Pattern.compile(args[0])
198                : null;
199        for (Method m : this.getClass().getDeclaredMethods()) {
200            boolean selected = (p == null)
201                    ? m.isAnnotationPresent(Test.class)
202                    : p.matcher(m.getName()).matches();
203            if (selected) {
204                try {
205                    m.invoke(this, (Object[]) null);
206                    System.out.println(m.getName() + ": OK");
207                    passed++;
208                    System.out.printf("Passed: %d, Failed: %d, ExitValue: %d%n",
209                                      passed, failed, testExitValue);
210                } catch (Throwable ex) {
211                    System.out.printf("Test %s failed: %s %n", m, ex);
212                    System.out.println("----begin detailed exceptions----");
213                    ex.printStackTrace(System.out);
214                    for (Throwable t : ex.getSuppressed()) {
215                        t.printStackTrace(System.out);
216                    }
217                    System.out.println("----end detailed exceptions----");
218                    failed++;
219                }
220            }
221        }
222        System.out.printf("Total: Passed: %d, Failed %d%n", passed, failed);
223        if (failed > 0) {
224            throw new RuntimeException("Tests failed: " + failed);
225        }
226        if (passed == 0 && failed == 0) {
227            throw new AssertionError("No test(s) selected: passed = " +
228                    passed + ", failed = " + failed + " ??????????");
229        }
230    }
231
232    /*
233     * usually the jre/lib/arch-name is the same as os.arch, except for x86.
234     */
235    static String getJreArch() {
236        String arch = System.getProperty("os.arch");
237        return arch.equals("x86") ? "i386" : arch;
238    }
239    static String getArch() {
240        return System.getProperty("os.arch");
241    }
242    static File getClassFile(File javaFile) {
243        String s = javaFile.getAbsolutePath().replace(JAVA_FILE_EXT, CLASS_FILE_EXT);
244        return new File(s);
245    }
246
247    static File getJavaFile(File classFile) {
248        String s = classFile.getAbsolutePath().replace(CLASS_FILE_EXT, JAVA_FILE_EXT);
249        return new File(s);
250    }
251
252    static String baseName(File f) {
253        String s = f.getName();
254        return s.substring(0, s.indexOf("."));
255    }
256
257    /*
258     * A convenience method to create a jar with jar file name and defs
259     */
260    static void createJar(File jarName, String... mainDefs)
261            throws FileNotFoundException{
262        createJar(null, jarName, new File("Foo"), mainDefs);
263    }
264
265    /*
266     * A convenience method to create a java file, compile and jar it up, using
267     * the sole class file name in the jar, as the Main-Class attribute value.
268     */
269    static void createJar(File jarName, File mainClass, String... mainDefs)
270            throws FileNotFoundException {
271            createJar(null, jarName, mainClass, mainDefs);
272    }
273
274    /*
275     * A convenience method to compile java files.
276     */
277    static void compile(String... compilerArgs) {
278        if (compiler.run(null, null, null, compilerArgs) != 0) {
279            String sarg = "";
280            for (String x : compilerArgs) {
281                sarg.concat(x + " ");
282            }
283            throw new Error("compilation failed: " + sarg);
284        }
285    }
286
287    /*
288     * A generic jar file creator to create a java file, compile it
289     * and jar it up, a specific Main-Class entry name in the
290     * manifest can be specified or a null to use the sole class file name
291     * as the Main-Class attribute value.
292     */
293    static void createJar(String mEntry, File jarName, File mainClass,
294            String... mainDefs) throws FileNotFoundException {
295        if (jarName.exists()) {
296            jarName.delete();
297        }
298        try (PrintStream ps = new PrintStream(new FileOutputStream(mainClass + ".java"))) {
299            ps.println("public class Foo {");
300            if (mainDefs != null) {
301                for (String x : mainDefs) {
302                    ps.println(x);
303                }
304            }
305            ps.println("}");
306        }
307
308        String compileArgs[] = {
309            mainClass + ".java"
310        };
311        if (compiler.run(null, null, null, compileArgs) != 0) {
312            throw new RuntimeException("compilation failed " + mainClass + ".java");
313        }
314        if (mEntry == null) {
315            mEntry = mainClass.getName();
316        }
317        String jarArgs[] = {
318            (debug) ? "cvfe" : "cfe",
319            jarName.getAbsolutePath(),
320            mEntry,
321            mainClass.getName() + ".class"
322        };
323        createJar(jarArgs);
324    }
325
326   static void createJar(String... args) {
327        List<String> cmdList = new ArrayList<>();
328        cmdList.add(jarCmd);
329        cmdList.addAll(Arrays.asList(args));
330        doExec(cmdList.toArray(new String[cmdList.size()]));
331   }
332
333   static void copyStream(InputStream in, OutputStream out) throws IOException {
334        byte[] buf = new byte[8192];
335        int n = in.read(buf);
336        while (n > 0) {
337            out.write(buf, 0, n);
338            n = in.read(buf);
339        }
340    }
341
342   static void copyFile(File src, File dst) throws IOException {
343        Path parent = dst.toPath().getParent();
344        if (parent != null) {
345            Files.createDirectories(parent);
346        }
347        Files.copy(src.toPath(), dst.toPath(), COPY_ATTRIBUTES, REPLACE_EXISTING);
348    }
349
350    /**
351     * Attempt to create a file at the given location. If an IOException
352     * occurs then back off for a moment and try again. When a number of
353     * attempts fail, give up and throw an exception.
354     */
355    void createAFile(File aFile, List<String> contents) throws IOException {
356        IOException cause = null;
357        for (int attempts = 0; attempts < 10; attempts++) {
358            try {
359                Files.write(aFile.getAbsoluteFile().toPath(), contents,
360                    Charset.defaultCharset(), CREATE, TRUNCATE_EXISTING, WRITE);
361                if (cause != null) {
362                    /*
363                     * report attempts and errors that were encountered
364                     * for diagnostic purposes
365                     */
366                    System.err.println("Created batch file " +
367                                        aFile + " in " + (attempts + 1) +
368                                        " attempts");
369                    System.err.println("Errors encountered: " + cause);
370                    cause.printStackTrace();
371                }
372                return;
373            } catch (IOException ioe) {
374                if (cause != null) {
375                    // chain the exceptions so they all get reported for diagnostics
376                    cause.addSuppressed(ioe);
377                } else {
378                    cause = ioe;
379                }
380            }
381
382            try {
383                Thread.sleep(500);
384            } catch (InterruptedException ie) {
385                if (cause != null) {
386                    // cause should alway be non-null here
387                    ie.addSuppressed(cause);
388                }
389                throw new RuntimeException("Interrupted while creating batch file", ie);
390            }
391        }
392        throw new RuntimeException("Unable to create batch file", cause);
393    }
394
395    static void createFile(File outFile, List<String> content) throws IOException {
396        Files.write(outFile.getAbsoluteFile().toPath(), content,
397                Charset.defaultCharset(), CREATE_NEW);
398    }
399
400    static void recursiveDelete(File target) throws IOException {
401        if (!target.exists()) {
402            return;
403        }
404        Files.walkFileTree(target.toPath(), new SimpleFileVisitor<Path>() {
405            @Override
406            public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
407                try {
408                    Files.deleteIfExists(dir);
409                } catch (IOException ex) {
410                    System.out.println("Error: could not delete: " + dir.toString());
411                    System.out.println(ex.getMessage());
412                    return FileVisitResult.TERMINATE;
413                }
414                return FileVisitResult.CONTINUE;
415            }
416            @Override
417            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
418                try {
419                    Files.deleteIfExists(file);
420                } catch (IOException ex) {
421                    System.out.println("Error: could not delete: " + file.toString());
422                    System.out.println(ex.getMessage());
423                    return FileVisitResult.TERMINATE;
424                }
425                return FileVisitResult.CONTINUE;
426            }
427        });
428    }
429
430    static TestResult doExec(String...cmds) {
431        return doExec(null, null, cmds);
432    }
433
434    static TestResult doExec(Map<String, String> envToSet, String...cmds) {
435        return doExec(envToSet, null, cmds);
436    }
437    /*
438     * A method which executes a java cmd and returns the results in a container
439     */
440    static TestResult doExec(Map<String, String> envToSet,
441                             Set<String> envToRemove, String...cmds) {
442        String cmdStr = "";
443        for (String x : cmds) {
444            cmdStr = cmdStr.concat(x + " ");
445        }
446        ProcessBuilder pb = new ProcessBuilder(cmds);
447        Map<String, String> env = pb.environment();
448        if (envToRemove != null) {
449            for (String key : envToRemove) {
450                env.remove(key);
451            }
452        }
453        if (envToSet != null) {
454            env.putAll(envToSet);
455        }
456        BufferedReader rdr = null;
457        try {
458            List<String> outputList = new ArrayList<>();
459            pb.redirectErrorStream(true);
460            Process p = pb.start();
461            rdr = new BufferedReader(new InputStreamReader(p.getInputStream()));
462            String in = rdr.readLine();
463            while (in != null) {
464                outputList.add(in);
465                in = rdr.readLine();
466            }
467            p.waitFor();
468            p.destroy();
469
470            return new TestHelper.TestResult(cmdStr, p.exitValue(), outputList,
471                    env, new Throwable("current stack of the test"));
472        } catch (Exception ex) {
473            ex.printStackTrace();
474            throw new RuntimeException(ex.getMessage());
475        }
476    }
477
478    static FileFilter createFilter(final String extension) {
479        return new FileFilter() {
480            @Override
481            public boolean accept(File pathname) {
482                String name = pathname.getName();
483                if (name.endsWith(extension)) {
484                    return true;
485                }
486                return false;
487            }
488        };
489    }
490
491    static boolean isEnglishLocale() {
492        return Locale.getDefault().getLanguage().equals("en");
493    }
494
495    /*
496     * A class to encapsulate the test results and stuff, with some ease
497     * of use methods to check the test results.
498     */
499    static class TestResult {
500        PrintWriter status;
501        StringWriter sw;
502        int exitValue;
503        List<String> testOutput;
504        Map<String, String> env;
505        Throwable t;
506        boolean testStatus;
507
508        public TestResult(String str, int rv, List<String> oList,
509                Map<String, String> env, Throwable t) {
510            sw = new StringWriter();
511            status = new PrintWriter(sw);
512            status.println("Executed command: " + str + "\n");
513            exitValue = rv;
514            testOutput = oList;
515            this.env = env;
516            this.t = t;
517            testStatus = true;
518        }
519
520        void appendError(String x) {
521            testStatus = false;
522            testExitValue++;
523            status.println(TEST_PREFIX + x);
524        }
525
526        void indentStatus(String x) {
527            status.println("  " + x);
528        }
529
530        void checkNegative() {
531            if (exitValue == 0) {
532                appendError("test must not return 0 exit value");
533            }
534        }
535
536        void checkPositive() {
537            if (exitValue != 0) {
538                appendError("test did not return 0 exit value");
539            }
540        }
541
542        boolean isOK() {
543            return exitValue == 0;
544        }
545
546        boolean isZeroOutput() {
547            if (!testOutput.isEmpty()) {
548                appendError("No message from cmd please");
549                return false;
550            }
551            return true;
552        }
553
554        boolean isNotZeroOutput() {
555            if (testOutput.isEmpty()) {
556                appendError("Missing message");
557                return false;
558            }
559            return true;
560        }
561
562        @Override
563        public String toString() {
564            status.println("++++Begin Test Info++++");
565            status.println("Test Status: " + (testStatus ? "PASS" : "FAIL"));
566            status.println("++++Test Environment++++");
567            for (String x : env.keySet()) {
568                indentStatus(x + "=" + env.get(x));
569            }
570            status.println("++++Test Output++++");
571            for (String x : testOutput) {
572                indentStatus(x);
573            }
574            status.println("++++Test Stack Trace++++");
575            status.println(t.toString());
576            for (StackTraceElement e : t.getStackTrace()) {
577                indentStatus(e.toString());
578            }
579            status.println("++++End of Test Info++++");
580            status.flush();
581            String out = sw.toString();
582            status.close();
583            return out;
584        }
585
586        boolean contains(String str) {
587            for (String x : testOutput) {
588                if (x.contains(str)) {
589                    return true;
590                }
591            }
592            appendError("string <" + str + "> not found");
593            return false;
594        }
595
596        boolean notContains(String str) {
597            for (String x : testOutput) {
598                if (x.contains(str)) {
599                    appendError("string <" + str + "> found");
600                    return false;
601                }
602            }
603            return true;
604        }
605
606        boolean matches(String regexToMatch) {
607            for (String x : testOutput) {
608                if (x.matches(regexToMatch)) {
609                    return true;
610                }
611            }
612            appendError("regex <" + regexToMatch + "> not matched");
613            return false;
614        }
615
616        boolean notMatches(String regexToMatch) {
617            for (String x : testOutput) {
618                if (!x.matches(regexToMatch)) {
619                    return true;
620                }
621            }
622            appendError("regex <" + regexToMatch + "> matched");
623            return false;
624        }
625    }
626    /**
627    * Indicates that the annotated method is a test method.
628    */
629    @Retention(RetentionPolicy.RUNTIME)
630    @Target(ElementType.METHOD)
631    public @interface Test {}
632}
633