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 }