1 /* 2 * Copyright (c) 2015, 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 * @summary Test custom image builder 27 * @author Andrei Eremeev 28 * @library ../lib 29 * @modules java.base/jdk.internal.jimage 30 * java.base/jdk.internal.module 31 * jdk.jdeps/com.sun.tools.classfile 32 * jdk.jlink/jdk.tools.jlink 33 * jdk.jlink/jdk.tools.jlink.internal 34 * jdk.jlink/jdk.tools.jmod 35 * jdk.jlink/jdk.tools.jimage 36 * @build tests.* 37 * @run testng/othervm CustomImageBuilderTest 38 */ 39 40 import java.io.IOException; 41 import java.io.OutputStream; 42 import java.io.UncheckedIOException; 43 import java.lang.module.ModuleDescriptor; 44 import java.nio.file.Files; 45 import java.nio.file.Path; 46 import java.nio.file.Paths; 47 import java.nio.file.StandardCopyOption; 48 import java.util.Arrays; 49 import java.util.Collections; 50 import java.util.HashSet; 51 import java.util.List; 52 53 import java.util.Set; 54 55 import jdk.internal.module.ModuleInfoWriter; 56 import org.testng.Assert; 57 import org.testng.SkipException; 58 import org.testng.annotations.BeforeClass; 59 import org.testng.annotations.Test; 60 import tests.Helper; 61 import tests.JImageGenerator; 62 import tests.JImageGenerator.JLinkTask; 63 import tests.Result; 64 65 @Test 66 public class CustomImageBuilderTest { 67 68 private static Helper helper; 69 private static Path pluginModulePath; 70 private static Path customPluginJmod; 71 private static Path classes; 72 private static final Path configFile = Paths.get("builder.cfg").toAbsolutePath(); 73 private static final List<String> options = 74 Arrays.asList("custom-image-option-1", "custom-image-option-2"); 75 76 @BeforeClass 77 public static void registerServices() throws IOException { 78 helper = Helper.newHelper(); 79 if (helper == null) { 80 throw new SkipException("Not run"); 81 } 82 helper.generateDefaultModules(); 83 String moduleName = "customplugin"; 84 Path src = Paths.get(System.getProperty("test.src")).resolve(moduleName); 85 classes = helper.getJmodClassesDir().resolve(moduleName); 86 JImageGenerator.compile(src, classes, "-XaddExports:jdk.jlink/jdk.tools.jlink.internal=customplugin"); 87 customPluginJmod = JImageGenerator.getJModTask() 88 .addClassPath(classes) 89 .jmod(helper.getJmodDir().resolve(moduleName + ".jmod")) 90 .create().assertSuccess(); 91 pluginModulePath = customPluginJmod.getParent(); 92 Files.write(configFile, "jdk.jlink.image.builder=custom-image-builder\n".getBytes()); 93 } 94 95 private JLinkTask getJLinkTask() { 96 return JImageGenerator.getJLinkTask() 97 .option("--plugins-configuration") 98 .option(configFile.toString()); 99 } 100 101 public void testHelp() { 102 Result result = JImageGenerator.getJLinkTask() 103 .option("--help") 104 .pluginModulePath(pluginModulePath) 105 .call(); 106 result.assertSuccess(); 107 String message = result.getMessage(); 108 if (!message.contains("Image Builder Name: custom-image-builder\n" + 109 "Image Builder Description: custom-image-builder-description\n" + 110 " --custom-image-option-1 custom-image-option-description")) { 111 System.err.println(result.getMessage()); 112 throw new AssertionError("Custom image builder not found"); 113 } 114 } 115 116 public void testCustomImageBuilder() throws IOException { 117 Path image = getJLinkTask() 118 .modulePath(helper.defaultModulePath()) 119 .output(helper.createNewImageDir("testCustomImageBuilder")) 120 .addMods("leaf1") 121 .pluginModulePath(pluginModulePath) 122 .call().assertSuccess(); 123 checkImageBuilder(image); 124 } 125 126 public void testFirstOption() throws IOException { 127 Path image = getJLinkTask() 128 .modulePath(helper.defaultModulePath()) 129 .output(helper.createNewImageDir("testFirstOption")) 130 .addMods("leaf1") 131 .pluginModulePath(pluginModulePath) 132 .option("--" + options.get(0)) 133 .option("AAAA") 134 .call().assertSuccess(); 135 checkImageBuilder(image, Collections.singletonList(option(options.get(0), "AAAA"))); 136 } 137 138 public void testFirstOptionNoArgs() throws IOException { 139 getJLinkTask() 140 .modulePath(helper.defaultModulePath()) 141 .output(helper.createNewImageDir("testFirstOptionNoArgs")) 142 .addMods("leaf1") 143 .pluginModulePath(pluginModulePath) 144 .option("--" + options.get(0)) 145 .call().assertFailure("Error: no value given for --custom-image-option-1"); 146 } 147 148 public void testSecondOption() throws IOException { 149 Path image = getJLinkTask() 150 .modulePath(helper.defaultModulePath()) 151 .output(helper.createNewImageDir("testSecondOption")) 152 .addMods("leaf1") 153 .pluginModulePath(pluginModulePath) 154 .option("--" + options.get(1)) 155 .call().assertSuccess(); 156 checkImageBuilder(image, Collections.singletonList(option(options.get(1)))); 157 } 158 159 public void testTwoImageBuildersInModule() throws IOException { 160 Result result = JImageGenerator.getJLinkTask() 161 .modulePath(helper.defaultModulePath()) 162 .pluginModulePath(pluginModulePath) 163 .option("--list-plugins") 164 .call(); 165 result.assertSuccess(); 166 String message = result.getMessage(); 167 if (!message.contains("Image Builder Name: custom-image-builder")) { 168 System.err.println(message); 169 throw new AssertionError("List-plugins does not contain custom-image-builder"); 170 } 171 if (!message.contains("Image Builder Name: second-image-builder")) { 172 System.err.println(message); 173 throw new AssertionError("List-plugins does not contain second-image-builder"); 174 } 175 } 176 177 @Test(priority = Integer.MAX_VALUE - 1) 178 public void testTwoPackages() throws IOException { 179 String moduleName = "customplugin_1"; 180 Path src = helper.getJmodSrcDir().resolve(moduleName); 181 copyModule(src); 182 Path jmod = buildModule(moduleName); 183 184 try { 185 JImageGenerator.getJLinkTask() 186 .pluginModulePath(pluginModulePath) 187 .option("--list-plugins") 188 .call().assertFailure("Modules customplugin_1 and customplugin both contain package plugin"); 189 } finally { 190 Files.delete(jmod); 191 } 192 } 193 194 private Path buildModule(String moduleName) throws IOException { 195 Path src = helper.getJmodSrcDir().resolve(moduleName); 196 Path classes = helper.getJmodClassesDir().resolve(moduleName); 197 JImageGenerator.compile(src, classes, "-XaddExports:jdk.jlink/jdk.tools.jlink.internal=customplugin"); 198 199 try (OutputStream out = Files.newOutputStream(classes.resolve("module-info.class"))) { 200 ModuleDescriptor md = new ModuleDescriptor.Builder(moduleName) 201 .requires("java.base") 202 .provides("jdk.tools.jlink.plugins.ImageBuilderProvider", 203 "plugin.CustomImageBuilderProvider").build(); 204 ModuleInfoWriter.write(md, out); 205 } 206 return JImageGenerator.getJModTask() 207 .addClassPath(classes) 208 .jmod(helper.getJmodDir().resolve(moduleName + ".jmod")) 209 .create().assertSuccess(); 210 } 211 212 private void copyModule(Path src) throws IOException { 213 Path customplugin = Paths.get(System.getProperty("test.src")).resolve("customplugin"); 214 Files.walk(customplugin).forEach(p -> { 215 try { 216 Path path = customplugin.relativize(p); 217 Files.copy(p, src.resolve(path)); 218 } catch (IOException e) { 219 throw new UncheckedIOException(e); 220 } 221 }); 222 } 223 224 private void testMalformedModule(String moduleName, ModuleDescriptor md, String expectedError) throws IOException { 225 try (OutputStream out = Files.newOutputStream(classes.resolve("module-info.class"))) { 226 ModuleInfoWriter.write(md, out); 227 } 228 Path jmod = JImageGenerator.getJModTask() 229 .addClassPath(classes) 230 .jmod(helper.getJmodDir().resolve(moduleName + ".jmod")) 231 .create().assertSuccess(); 232 Files.move(jmod, customPluginJmod, StandardCopyOption.REPLACE_EXISTING); 233 getJLinkTask() 234 .modulePath(helper.defaultModulePath()) 235 .output(helper.createNewImageDir(moduleName)) 236 .addMods(moduleName) 237 .pluginModulePath(pluginModulePath) 238 .call().assertFailure(expectedError); 239 } 240 241 @Test(enabled = false, priority = Integer.MAX_VALUE) // run last 242 public void testIllegalAccessError() throws IOException { 243 String moduleName = "testIllegalAccessError"; 244 ModuleDescriptor md = new ModuleDescriptor.Builder(moduleName) 245 .requires("java.base") 246 .provides("jdk.tools.jlink.plugins.ImageBuilderProvider", 247 new HashSet<>(Arrays.asList( 248 "plugin.SameNamedImageBuilderProvider", 249 "plugin.CustomImageBuilderProvider"))).build(); 250 testMalformedModule(moduleName, md, "Error: Multiple ImageBuilderProvider for the name custom-image-builder"); 251 } 252 253 @Test(enabled = false, priority = Integer.MAX_VALUE) // run last 254 public void testTwoImageBuilderWithTheSameName() throws IOException { 255 String moduleName = "testTwoImageBuilderWithTheSameName"; 256 ModuleDescriptor md = new ModuleDescriptor.Builder(moduleName) 257 .requires("java.base") 258 .requires("jdk.jlink") 259 .provides("jdk.tools.jlink.plugins.ImageBuilderProvider", 260 new HashSet<>(Arrays.asList( 261 "plugin.SameNamedImageBuilderProvider", 262 "plugin.CustomImageBuilderProvider"))).build(); 263 testMalformedModule(moduleName, md, 264 "cannot access class jdk.tools.jlink.plugins.ImageBuilderProvider (in module: jdk.jlink)"); 265 } 266 267 private void checkImageBuilder(Path image) throws IOException { 268 checkImageBuilder(image, Collections.emptyList()); 269 } 270 271 private void checkImageBuilder(Path image, List<Option> includes) throws IOException { 272 if (!Files.exists(image.resolve("image.jimage"))) { 273 throw new AssertionError("getJImageOutputStream was not called"); 274 } 275 if (!Files.exists(image.resolve("files.txt"))) { 276 throw new AssertionError("storeFiles was not called"); 277 } 278 Set<String> otherOptions = new HashSet<>(options); 279 for (Option o : includes) { 280 otherOptions.remove(o.option); 281 282 Path file = image.resolve(o.option); 283 Assert.assertTrue(Files.exists(file), "Option was not handled: " + o.option); 284 String content = new String(Files.readAllBytes(file)); 285 Assert.assertEquals(content, o.value, "Option: " + o.option); 286 } 287 for (String o : otherOptions) { 288 Path file = image.resolve(o); 289 Assert.assertTrue(!Files.exists(file), "Option presented in config: " + o); 290 } 291 } 292 293 private static class Option { 294 public final String option; 295 public final String value; 296 297 private Option(String option, String value) { 298 this.option = option; 299 this.value = value; 300 } 301 } 302 303 private static Option option(String option) { 304 return option(option, ""); 305 } 306 307 private static Option option(String option, String value) { 308 return new Option(option, value); 309 } 310 }