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