8153300: [TESTBUG] Enhance test/testlibrary/ClassFileInstaller.java to support JAR files
authoriklam
Tue, 05 Apr 2016 14:52:12 -0700
changeset 37263 08dec586ed5c
parent 37262 e7b7bc691d7d
child 37265 ff874750de46
8153300: [TESTBUG] Enhance test/testlibrary/ClassFileInstaller.java to support JAR files Reviewed-by: lfoltan, mseledtsov
hotspot/test/runtime/SharedArchiveFile/BootAppendTests.java
hotspot/test/runtime/SharedArchiveFile/SharedStrings.java
hotspot/test/testlibrary/ClassFileInstaller.java
--- a/hotspot/test/runtime/SharedArchiveFile/BootAppendTests.java	Tue Apr 05 11:17:50 2016 -0400
+++ b/hotspot/test/runtime/SharedArchiveFile/BootAppendTests.java	Tue Apr 05 14:52:12 2016 -0700
@@ -27,14 +27,12 @@
  * @library /testlibrary
  * @modules java.base/jdk.internal.misc
  *          java.management
- *          jdk.jartool/sun.tools.jar
  *          jdk.jvmstat/sun.jvmstat.monitor
  * @ignore 8150683
  * @compile javax/sound/sampled/MyClass.jasm
  * @compile org/omg/CORBA/Context.jasm
  * @compile nonjdk/myPackage/MyClass.java
- * @build jdk.test.lib.* LoadClass
- * @run main ClassFileInstaller LoadClass
+ * @build jdk.test.lib.* LoadClass ClassFileInstaller
  * @run main/othervm BootAppendTests
  */
 
@@ -90,11 +88,9 @@
         fos.close();
 
         // build jar files
-        BasicJarBuilder.build(true, "app", APP_CLASS);
-        appJar = BasicJarBuilder.getTestJar("app.jar");
-        BasicJarBuilder.build("bootAppend",
+        appJar = ClassFileInstaller.writeJar("app.jar", APP_CLASS);
+        bootAppendJar = ClassFileInstaller.writeJar("bootAppend.jar",
             BOOT_APPEND_MODULE_CLASS, BOOT_APPEND_DUPLICATE_MODULE_CLASS, BOOT_APPEND_CLASS);
-        bootAppendJar = BasicJarBuilder.getTestJar("bootAppend.jar");
 
         // dump
         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(
--- a/hotspot/test/runtime/SharedArchiveFile/SharedStrings.java	Tue Apr 05 11:17:50 2016 -0400
+++ b/hotspot/test/runtime/SharedArchiveFile/SharedStrings.java	Tue Apr 05 14:52:12 2016 -0700
@@ -32,23 +32,20 @@
  * @library /testlibrary /test/lib
  * @modules java.base/sun.misc
  *          java.management
- *          jdk.jartool/sun.tools.jar
- * @build SharedStringsWb SharedStrings BasicJarBuilder sun.hotspot.WhiteBox
- * @run main ClassFileInstaller sun.hotspot.WhiteBox
+ * @build SharedStringsWb SharedStrings ClassFileInstaller sun.hotspot.WhiteBox
+ * @run main ClassFileInstaller -jar whitebox.jar sun.hotspot.WhiteBox
  * @run main SharedStrings
  */
 import jdk.test.lib.*;
 
 public class SharedStrings {
     public static void main(String[] args) throws Exception {
-        BasicJarBuilder.build(true, "whitebox", "sun/hotspot/WhiteBox");
-
         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(
             "-XX:+UnlockDiagnosticVMOptions",
             "-XX:SharedArchiveFile=./SharedStrings.jsa",
             "-XX:+PrintSharedSpaces",
             // Needed for bootclasspath match, for CDS to work with WhiteBox API
-            "-Xbootclasspath/a:" + BasicJarBuilder.getTestJar("whitebox.jar"),
+            "-Xbootclasspath/a:" + ClassFileInstaller.getJarPath("whitebox.jar"),
             "-Xshare:dump");
 
         new OutputAnalyzer(pb.start())
@@ -62,7 +59,7 @@
             // these are required modes for shared strings
             "-XX:+UseCompressedOops", "-XX:+UseG1GC",
             // needed for access to white box test API
-            "-Xbootclasspath/a:" + BasicJarBuilder.getTestJar("whitebox.jar"),
+            "-Xbootclasspath/a:" + ClassFileInstaller.getJarPath("whitebox.jar"),
             "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI",
             "-Xshare:on", "-showversion", "SharedStringsWb");
 
--- a/hotspot/test/testlibrary/ClassFileInstaller.java	Tue Apr 05 11:17:50 2016 -0400
+++ b/hotspot/test/testlibrary/ClassFileInstaller.java	Tue Apr 05 14:52:12 2016 -0700
@@ -21,6 +21,10 @@
  * questions.
  */
 
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.io.ByteArrayInputStream;
@@ -28,58 +32,226 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 /**
- * Dump a class file for a class on the class path in the current directory
+ * Dump a class file for a class on the class path in the current directory, or
+ * in the specified JAR file. This class is usually used when you build a class
+ * from a test library, but want to use this class in a sub-process.
+ *
+ * For example, to build the following library class:
+ * test/lib/sun/hotspot/WhiteBox.java
+ *
+ * You would use the following tags:
+ *
+ * @library /test/lib
+ * @build sun.hotspot.WhiteBox
+ *
+ * JTREG would build the class file under
+ * ${JTWork}/classes/test/lib/sun/hotspot/WhiteBox.class
+ *
+ * With you run your main test class using "@run main MyMainClass", JTREG would setup the
+ * -classpath to include "${JTWork}/classes/test/lib/", so MyMainClass would be able to
+ * load the WhiteBox class.
+ *
+ * However, if you run a sub process, and do not wish to use the exact same -classpath,
+ * You can use ClassFileInstaller to ensure that WhiteBox is available in the current
+ * directory of your test:
+ *
+ * @run main ClassFileInstaller sun.hotspot.WhiteBox
+ *
+ * Or, you can use the -jar option to store the class in the specified JAR file. If a relative
+ * path name is given, the JAR file would be relative to the current directory of
+ *
+ * @run main ClassFileInstaller -jar myjar.jar sun.hotspot.WhiteBox
  */
 public class ClassFileInstaller {
     /**
+     * You can enable debug tracing of ClassFileInstaller by running JTREG with
+     * jtreg -DClassFileInstaller.debug=true ... <names of tests>
+     */
+    public static boolean DEBUG = Boolean.getBoolean("ClassFileInstaller.debug");
+
+    /**
      * @param args The names of the classes to dump
      * @throws Exception
      */
     public static void main(String... args) throws Exception {
-        for (String arg : args) {
-            writeClassToDisk(arg);
+        if (args.length > 1 && args[0].equals("-jar")) {
+            if (args.length < 2) {
+                throw new RuntimeException("Usage: ClassFileInstaller <options> <classes>\n" +
+                                           "where possible options include:\n" +
+                                           "  -jar <path>             Write to the JAR file <path>");
+            }
+            writeJar(args[1], null, args, 2, args.length);
+        } else {
+            if (DEBUG) {
+                System.out.println("ClassFileInstaller: Writing to " + System.getProperty("user.dir"));
+            }
+            for (String arg : args) {
+                writeClassToDisk(arg);
+            }
+        }
+    }
+
+    public static class Manifest {
+        private InputStream in;
+
+        private Manifest(InputStream in) {
+            this.in = in;
+        }
+
+        static Manifest fromSourceFile(String fileName) throws Exception {
+            String pathName = System.getProperty("test.src") + File.separator + fileName;
+            return new Manifest(new FileInputStream(pathName));
+        }
+
+        // Example:
+        //  String manifest = "Premain-Class: RedefineClassHelper\n" +
+        //                "Can-Redefine-Classes: true\n";
+        //  ClassFileInstaller.writeJar("redefineagent.jar",
+        //    ClassFileInstaller.Manifest.fromString(manifest),
+        //    "RedefineClassHelper");
+        static Manifest fromString(String manifest) throws Exception {
+            return new Manifest(new ByteArrayInputStream(manifest.getBytes()));
+        }
+
+        public InputStream getInputStream() {
+            return in;
         }
     }
 
+    private static void writeJar(String jarFile, Manifest manifest, String classes[], int from, int to) throws Exception {
+        if (DEBUG) {
+            System.out.println("ClassFileInstaller: Writing to " + getJarPath(jarFile));
+        }
+
+        (new File(jarFile)).delete();
+        FileOutputStream fos = new FileOutputStream(jarFile);
+        ZipOutputStream zos = new ZipOutputStream(fos);
+
+        // The manifest must be the first or second entry. See comments in JarInputStream
+        // constructor and JDK-5046178.
+        if (manifest != null) {
+            writeToDisk(zos, "META-INF/MANIFEST.MF", manifest.getInputStream());
+        }
+
+        for (int i=from; i<to; i++) {
+            writeClassToDisk(zos, classes[i]);
+        }
+
+        zos.close();
+        fos.close();
+    }
+
+    /*
+     * You can call ClassFileInstaller.writeJar() from your main test class instead of
+     * using "@run ClassFileInstaller -jar ...". E.g.,
+     *
+     * String jarPath = ClassFileInstaller.getJarPath("myjar.jar", "sun.hotspot.WhiteBox")
+     *
+     * If you call this API, make sure you build ClassFileInstaller with the following tags:
+     *
+     * @library testlibrary
+     * @build ClassFileInstaller
+     */
+    public static String writeJar(String jarFile, String... classes) throws Exception {
+        writeJar(jarFile, null, classes, 0, classes.length);
+        return getJarPath(jarFile);
+    }
+
+    public static String writeJar(String jarFile, Manifest manifest, String... classes) throws Exception {
+        writeJar(jarFile, manifest, classes, 0, classes.length);
+        return getJarPath(jarFile);
+    }
+
+    /**
+     * This returns the absolute path to the file specified in "@ClassFileInstaller -jar myjar.jar",
+     * In your test program, instead of using the JAR file name directly:
+     *
+     * String jarPath = "myjar.jar";
+     *
+     * you should call this function, like:
+     *
+     * String jarPath = ClassFileInstaller.getJarPath("myjar.jar")
+     *
+     * The reasons are:
+     * (1) Using absolute path makes it easy to cut-and-paste from the JTR file and rerun your
+     *     test in any directory.
+     * (2) In the future, we may make the JAR file name unique to avoid clobbering
+     *     during parallel JTREG execution.
+     *
+     */
+    public static String getJarPath(String jarFileName) {
+        return new File(jarFileName).getAbsolutePath();
+    }
+
     public static void writeClassToDisk(String className) throws Exception {
-        writeClassToDisk(className, "");
+        writeClassToDisk((ZipOutputStream)null, className);
+    }
+    private static void writeClassToDisk(ZipOutputStream zos, String className) throws Exception {
+        writeClassToDisk(zos, className, "");
     }
 
     public static void writeClassToDisk(String className, String prependPath) throws Exception {
+        writeClassToDisk(null, className, prependPath);
+    }
+    private static void writeClassToDisk(ZipOutputStream zos, String className, String prependPath) throws Exception {
         ClassLoader cl = ClassFileInstaller.class.getClassLoader();
 
         // Convert dotted class name to a path to a class file
         String pathName = className.replace('.', '/').concat(".class");
         InputStream is = cl.getResourceAsStream(pathName);
+        if (is == null) {
+            throw new RuntimeException("Failed to find " + pathName);
+        }
         if (prependPath.length() > 0) {
             pathName = prependPath + "/" + pathName;
         }
-        writeToDisk(pathName, is);
+        writeToDisk(zos, pathName, is);
     }
 
     public static void writeClassToDisk(String className, byte[] bytecode) throws Exception {
-        writeClassToDisk(className, bytecode, "");
+        writeClassToDisk(null, className, bytecode);
+    }
+    private static void writeClassToDisk(ZipOutputStream zos, String className, byte[] bytecode) throws Exception {
+        writeClassToDisk(zos, className, bytecode, "");
     }
 
     public static void writeClassToDisk(String className, byte[] bytecode, String prependPath) throws Exception {
+        writeClassToDisk(null, className, bytecode, prependPath);
+    }
+    private static void writeClassToDisk(ZipOutputStream zos, String className, byte[] bytecode, String prependPath) throws Exception {
         // Convert dotted class name to a path to a class file
         String pathName = className.replace('.', '/').concat(".class");
         if (prependPath.length() > 0) {
             pathName = prependPath + "/" + pathName;
         }
-        writeToDisk(pathName, new ByteArrayInputStream(bytecode));
+        writeToDisk(zos, pathName, new ByteArrayInputStream(bytecode));
     }
 
-
-    private static void writeToDisk(String pathName, InputStream is) throws Exception {
-        // Create the class file's package directory
-        Path p = Paths.get(pathName);
-        if (pathName.contains("/")) {
-            Files.createDirectories(p.getParent());
+    private static void writeToDisk(ZipOutputStream zos, String pathName, InputStream is) throws Exception {
+        if (DEBUG) {
+            System.out.println("ClassFileInstaller: Writing " + pathName);
         }
-        // Create the class file
-        Files.copy(is, p, StandardCopyOption.REPLACE_EXISTING);
+        if (zos != null) {
+            ZipEntry ze = new ZipEntry(pathName);
+            zos.putNextEntry(ze);
+            byte[] buf = new byte[1024];
+            int len;
+            while ((len = is.read(buf))>0){
+                zos.write(buf, 0, len);
+            }
+        } else {
+            // Create the class file's package directory
+            Path p = Paths.get(pathName);
+            if (pathName.contains("/")) {
+                Files.createDirectories(p.getParent());
+            }
+            // Create the class file
+            Files.copy(is, p, StandardCopyOption.REPLACE_EXISTING);
+        }
+        is.close();
     }
 }