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