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 }