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