1 /* 2 * Copyright (c) 2016, 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 /* 25 * @test 26 * @bug 8156499 27 * @summary Test image creation from Multi-Release JAR 28 * @author Steve Drach 29 * @library /test/lib 30 * @modules java.base/jdk.internal.jimage 31 * java.base/jdk.internal.module 32 * jdk.compiler 33 * jdk.jartool 34 * jdk.jlink 35 * jdk.zipfs 36 * @build jdk.test.lib.Utils 37 * jdk.test.lib.Asserts 38 * jdk.test.lib.JDKToolFinder 39 * jdk.test.lib.JDKToolLauncher 40 * jdk.test.lib.Platform 41 * jdk.test.lib.process.* 42 * @run testng JLinkMultiReleaseJarTest 43 */ 44 45 import java.io.ByteArrayInputStream; 46 import java.io.IOException; 47 import java.lang.invoke.MethodHandle; 48 import java.lang.invoke.MethodHandles; 49 import java.lang.invoke.MethodType; 50 import java.lang.module.ModuleDescriptor; 51 import java.nio.file.Files; 52 import java.nio.file.Path; 53 import java.nio.file.Paths; 54 import java.nio.file.StandardCopyOption; 55 import java.util.Arrays; 56 import java.util.Set; 57 import java.util.jar.JarFile; 58 import java.util.spi.ToolProvider; 59 import java.util.stream.Collectors; 60 import java.util.stream.Stream; 61 62 import jdk.internal.jimage.BasicImageReader; 63 import jdk.test.lib.process.ProcessTools; 64 import jdk.test.lib.process.OutputAnalyzer; 65 66 import org.testng.Assert; 67 import org.testng.annotations.BeforeClass; 68 import org.testng.annotations.Test; 69 70 public class JLinkMultiReleaseJarTest { 71 private static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar") 72 .orElseThrow(() -> new RuntimeException("jar tool not found")); 73 private static final ToolProvider JAVAC_TOOL = ToolProvider.findFirst("javac") 74 .orElseThrow(() -> new RuntimeException("javac tool not found")); 75 private static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink") 76 .orElseThrow(() -> new RuntimeException("jlink tool not found")); 77 78 private final Path userdir = Paths.get(System.getProperty("user.dir", ".")); 79 private final Path javahome = Paths.get(System.getProperty("java.home")); 80 private final Path jmodsdir = javahome.resolve("jmods"); 81 82 private final String pathsep = System.getProperty("path.separator"); 83 84 private byte[] resource = (Runtime.version().major() + " resource file").getBytes(); 85 86 @BeforeClass 87 public void initialize() throws IOException { 88 Path srcdir = Paths.get(System.getProperty("test.src")); 89 90 // create class files from source 91 Path base = srcdir.resolve("base"); 92 Path basemods = userdir.resolve("basemods"); 93 javac(base, basemods, base.toString()); 94 95 Path rt = srcdir.resolve("rt"); 96 Path rtmods = userdir.resolve("rtmods"); 97 javac(rt, rtmods, rt.toString()); 98 99 // create resources in basemods and rtmods 100 Path dest = basemods.resolve("m1").resolve("resource.txt"); 101 byte[] text = "base resource file".getBytes(); 102 ByteArrayInputStream is = new ByteArrayInputStream(text); 103 Files.copy(is, dest); 104 105 dest = rtmods.resolve("m1").resolve("resource.txt"); 106 is = new ByteArrayInputStream(resource); 107 Files.copy(is, dest); 108 109 // build multi-release jar file with different module-infos 110 String[] args = { 111 "-cf", "m1.jar", 112 "-C", basemods.resolve("m1").toString(), ".", 113 "--release ", String.valueOf(JarFile.runtimeVersion().major()), 114 "-C", rtmods.resolve("m1").toString(), "." 115 }; 116 JAR_TOOL.run(System.out, System.err, args); 117 118 // now move the module-info that requires logging to temporary place 119 Files.move(rtmods.resolve("m1").resolve("module-info.class"), 120 userdir.resolve("module-info.class")); 121 122 // and build another jar 123 args[1] = "m1-no-logging.jar"; 124 JAR_TOOL.run(System.out, System.err, args); 125 126 // replace the no logging module-info with the logging module-info 127 Files.move(userdir.resolve("module-info.class"), 128 basemods.resolve("m1").resolve("module-info.class"), 129 StandardCopyOption.REPLACE_EXISTING); 130 131 // and build another jar 132 args[1] = "m1-logging.jar"; 133 JAR_TOOL.run(System.out, System.err, args); 134 } 135 136 private void javac(Path source, Path destination, String srcpath) throws IOException { 137 String[] args = Stream.concat( 138 Stream.of("-d", destination.toString(), "--module-source-path", srcpath), 139 Files.walk(source) 140 .map(Path::toString) 141 .filter(s -> s.endsWith(".java")) 142 ).toArray(String[]::new); 143 int rc = JAVAC_TOOL.run(System.out, System.err, args); 144 Assert.assertEquals(rc, 0); 145 } 146 147 @Test 148 public void basicTest() throws Throwable { 149 if (ignoreTest()) return; 150 151 // use jlink to build image from multi-release jar 152 jlink("m1.jar", "myimage"); 153 154 // validate image 155 Path jimage = userdir.resolve("myimage").resolve("lib").resolve("modules"); 156 try (BasicImageReader reader = BasicImageReader.open(jimage)) { 157 158 // do we have the right entry names? 159 Set<String> names = Arrays.stream(reader.getEntryNames()) 160 .filter(n -> n.startsWith("/m1")) 161 .collect(Collectors.toSet()); 162 Assert.assertEquals(names, Set.of( 163 "/m1/module-info.class", 164 "/m1/p/Main.class", 165 "/m1/p/Type.class", 166 "/m1/q/PublicClass.class", 167 "/m1/META-INF/MANIFEST.MF", 168 "/m1/resource.txt")); 169 170 // do we have the right module-info.class? 171 byte[] b = reader.getResource("/m1/module-info.class"); 172 Set<String> requires = ModuleDescriptor 173 .read(new ByteArrayInputStream(b)) 174 .requires() 175 .stream() 176 .map(mdr -> mdr.name()) 177 .filter(nm -> !nm.equals("java.base")) 178 .collect(Collectors.toSet()); 179 Assert.assertEquals(requires, Set.of("java.logging")); 180 181 // do we have the right resource? 182 b = reader.getResource("/m1/resource.txt"); 183 Assert.assertEquals(b, resource); 184 185 // do we have the right class? 186 b = reader.getResource("/m1/p/Main.class"); 187 Class<?> clazz = (new ByteArrayClassLoader()).loadClass("p.Main", b); 188 MethodHandle getVersion = MethodHandles.lookup() 189 .findVirtual(clazz, "getVersion", MethodType.methodType(int.class)); 190 int version = (int) getVersion.invoke(clazz.getConstructor().newInstance()); 191 Assert.assertEquals(version, JarFile.runtimeVersion().major()); 192 } 193 } 194 195 @Test 196 public void noLoggingTest() throws Throwable { 197 if (ignoreTest()) return; 198 199 jlink("m1-no-logging.jar", "no-logging-image"); 200 runImage("no-logging-image", false); 201 } 202 203 @Test 204 public void loggingTest() throws Throwable { 205 if (ignoreTest()) return; 206 207 jlink("m1-logging.jar", "logging-image"); 208 runImage("logging-image", true); 209 210 } 211 212 // java.base.jmod must exist for this test to make sense 213 private boolean ignoreTest() { 214 if (Files.isRegularFile(jmodsdir.resolve("java.base.jmod"))) { 215 return false; 216 } 217 System.err.println("Test skipped. NO jmods/java.base.jmod"); 218 return true; 219 } 220 221 222 private void jlink(String jar, String image) { 223 String args = "--output " + image + " --add-modules m1 --module-path " + 224 jar + pathsep + jmodsdir.toString(); 225 int exitCode = JLINK_TOOL.run(System.out, System.err, args.split(" +")); 226 Assert.assertEquals(exitCode, 0); 227 } 228 229 public void runImage(String image, boolean expected) throws Throwable { 230 Path java = Paths.get(image, "bin", "java"); 231 OutputAnalyzer oa = ProcessTools.executeProcess(java.toString(), "-m", "m1/p.Main"); 232 String sout = oa.getStdout(); 233 boolean actual = sout.contains("logging found"); 234 Assert.assertEquals(actual, expected); 235 System.out.println(sout); 236 System.err.println(oa.getStderr()); 237 Assert.assertEquals(oa.getExitValue(), 0); 238 } 239 240 private static class ByteArrayClassLoader extends ClassLoader { 241 public Class<?> loadClass(String name, byte[] bytes) { 242 return defineClass(name, bytes, 0, bytes.length); 243 } 244 } 245 }