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