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