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 import java.io.IOException;
  25 import java.io.OutputStream;
  26 import java.io.File;
  27 import java.nio.file.Files;
  28 import java.nio.file.Path;
  29 import java.nio.file.Paths;
  30 import java.util.List;
  31 import java.util.Map;
  32 import java.util.StringJoiner;
  33 import java.util.Arrays;
  34 import java.util.stream.Collectors;
  35 import java.lang.module.ModuleDescriptor;
  36 import jdk.testlibrary.OutputAnalyzer;
  37 import org.testng.annotations.DataProvider;
  38 import org.testng.annotations.Test;
  39 import jdk.internal.module.ModuleInfoWriter;
  40 import static java.lang.module.ModuleDescriptor.Builder;
  41 
  42 /**
  43  * Base class need to be extended by modular test for security.
  44  */
  45 public abstract class ModularTest {
  46 
  47     /**
  48      * Enum represents all supported module types supported in JDK9. i.e.
  49      * EXPLICIT - Modules have module descriptor(module-info.java)
  50      * defining the module.
  51      * AUTO - Are regular jar files but provided in MODULE_PATH instead
  52      * of CLASS_PATH.
  53      * UNNAMED - Are regular jar but provided through CLASS_PATH.
  54      */
  55     public enum MODULE_TYPE {
  56 
  57         EXPLICIT, AUTO, UNNAMED;
  58     }
  59 
  60     public static final String SPACE = " ";
  61     public static final Path SRC = Paths.get(System.getProperty("test.src"));
  62     public static final String DESCRIPTOR = "MetaService";
  63     public static final String MODULAR = "Modular";
  64     public static final String AUTO = "AutoServiceType";
  65     public static final String JAR_EXTN = ".jar";
  66 
  67     /**
  68      * Setup test data for the test.
  69      */
  70     @DataProvider(name = "TestParams")
  71     public Object[][] setUpTestData() {
  72         return getTestInput();
  73     }
  74 
  75     /**
  76      * Test method for TestNG.
  77      */
  78     @Test(dataProvider = "TestParams")
  79     public void runTest(MODULE_TYPE cModuleType, MODULE_TYPE sModuletype,
  80             boolean addMetaDesc, String failureMsgExpected, String[] args)
  81             throws Exception {
  82 
  83         String testName = new StringJoiner("_").add(cModuleType.toString())
  84                 .add(sModuletype.toString()).add(
  85                         (addMetaDesc) ? "WITH_SERVICE" : "NO_SERVICE")
  86                 .toString();
  87 
  88         System.out.format("%nStarting Test case: '%s'", testName);
  89         Path cJarPath = findJarPath(false, cModuleType, false,
  90                 (sModuletype == MODULE_TYPE.EXPLICIT));
  91         Path sJarPath = findJarPath(true, sModuletype, addMetaDesc, false);
  92         System.out.format("%nClient jar path : %s ", cJarPath);
  93         System.out.format("%nService jar path : %s ", sJarPath);
  94         OutputAnalyzer output = executeTestClient(cModuleType, cJarPath,
  95                 sModuletype, sJarPath, args);
  96 
  97         if (output.getExitValue() != 0) {
  98             if (failureMsgExpected != null
  99                     && output.getOutput().contains(failureMsgExpected)) {
 100                 System.out.println("PASS: Test is expected to fail here.");
 101             } else {
 102                 System.out.format("%nUnexpected failure occured with exit code"
 103                         + " '%s'.", output.getExitValue());
 104                 throw new RuntimeException("Unexpected failure occured.");
 105             }
 106         }
 107     }
 108 
 109     /**
 110      * Abstract method need to be implemented by each Test type to provide
 111      * test parameters.
 112      */
 113     public abstract Object[][] getTestInput();
 114 
 115     /**
 116      * Execute the test client to access required service.
 117      */
 118     public abstract OutputAnalyzer executeTestClient(MODULE_TYPE cModuleType,
 119             Path cJarPath, MODULE_TYPE sModuletype, Path sJarPath,
 120             String... args) throws Exception;
 121 
 122     /**
 123      * Find the Jar for service/client based on module type and if service
 124      * descriptor required.
 125      */
 126     public abstract Path findJarPath(boolean service, MODULE_TYPE moduleType,
 127             boolean addMetaDesc, boolean dependsOnServiceModule);
 128 
 129     /**
 130      * Constructs a Java Command line string based on modular structure followed
 131      * by modular client and service.
 132      */
 133     public String[] getJavaCommand(Path modulePath, String classPath,
 134             String clientModuleName, String mainClass,
 135             Map<String, String> vmArgs, String... options) throws IOException {
 136 
 137         final StringJoiner command = new StringJoiner(SPACE, SPACE, SPACE);
 138         vmArgs.forEach((key, value) -> command.add(key + value));
 139         if (modulePath != null) {
 140             command.add("--module-path").add(modulePath.toFile().getCanonicalPath());
 141         }
 142         if (classPath != null && classPath.length() > 0) {
 143             command.add("-cp").add(classPath);
 144         }
 145         if (clientModuleName != null && clientModuleName.length() > 0) {
 146             command.add("-m").add(clientModuleName + "/" + mainClass);
 147         } else {
 148             command.add(mainClass);
 149         }
 150         command.add(Arrays.stream(options).collect(Collectors.joining(SPACE)));
 151         return command.toString().trim().split("[\\s]+");
 152     }
 153 
 154     /**
 155      * Generate ModuleDescriptor object for explicit/auto based client/Service
 156      * modules type.
 157      */
 158     public ModuleDescriptor generateModuleDescriptor(boolean isService,
 159             MODULE_TYPE moduleType, String moduleName, String pkg,
 160             String serviceInterface, String serviceImpl,
 161             String serviceModuleName, List<String> requiredModules,
 162             boolean depends) {
 163 
 164         final Builder builder;
 165         if (moduleType == MODULE_TYPE.EXPLICIT) {
 166             System.out.format(" %nGenerating ModuleDescriptor object");
 167             builder = new Builder(moduleName).exports(pkg);
 168             if (isService && serviceInterface != null && serviceImpl != null) {
 169                 builder.provides(serviceInterface, serviceImpl);
 170             } else {
 171                 if (serviceInterface != null) {
 172                     builder.uses(serviceInterface);
 173                 }
 174                 if (depends) {
 175                     builder.requires(serviceModuleName);
 176                 }
 177             }
 178         } else {
 179             System.out.format(" %nModuleDescriptor object not required.");
 180             return null;
 181         }
 182         requiredModules.stream().forEach(reqMod -> builder.requires(reqMod));
 183         return builder.build();
 184     }
 185 
 186     /**
 187      * Generates service descriptor inside META-INF folder.
 188      */
 189     public boolean createMetaInfServiceDescriptor(
 190             Path serviceDescriptorFile, String serviceImpl) {
 191         boolean created = true;
 192         System.out.format("%nCreating META-INF service descriptor for '%s' "
 193                 + "at path '%s'", serviceImpl, serviceDescriptorFile);
 194         try {
 195             Path parent = serviceDescriptorFile.getParent();
 196             if (parent != null) {
 197                 Files.createDirectories(parent);
 198             }
 199             Files.write(serviceDescriptorFile, serviceImpl.getBytes("UTF-8"));
 200             System.out.println(
 201                     "META-INF service descriptor generated successfully");
 202         } catch (IOException e) {
 203             e.printStackTrace(System.out);
 204             created = false;
 205         }
 206         return created;
 207     }
 208 
 209     /**
 210      * Generate modular/regular jar file.
 211      */
 212     public void generateJar(ModuleDescriptor mDescriptor, Path jar,
 213             Path compilePath) throws IOException {
 214         System.out.format("%nCreating jar file '%s'", jar);
 215         JarUtils.createJarFile(jar, compilePath);
 216         if (mDescriptor != null) {
 217             Path dir = Files.createTempDirectory("tmp");
 218             Path mi = dir.resolve("module-info.class");
 219             try (OutputStream out = Files.newOutputStream(mi)) {
 220                 ModuleInfoWriter.write(mDescriptor, out);
 221             }
 222             System.out.format("%nAdding 'module-info.class' to jar '%s'", jar);
 223             JarUtils.updateJarFile(jar, dir);
 224         }
 225     }
 226 
 227     /**
 228      * Copy pre-generated jar files to the module base path.
 229      */
 230     public void copyJarsToModuleBase(MODULE_TYPE moduleType, Path jar,
 231             Path mBasePath) throws IOException {
 232         if (mBasePath != null) {
 233             Files.createDirectories(mBasePath);
 234         }
 235         if (moduleType != MODULE_TYPE.UNNAMED) {
 236             Path artifactName = mBasePath.resolve(jar.getFileName());
 237             System.out.format("%nCopy jar path: '%s' to module base path: %s",
 238                     jar, artifactName);
 239             Files.copy(jar, artifactName);
 240         }
 241     }
 242 
 243     /**
 244      * Construct class path string.
 245      */
 246     public String buildClassPath(MODULE_TYPE cModuleType,
 247             Path cJarPath, MODULE_TYPE sModuletype,
 248             Path sJarPath) throws IOException {
 249         StringJoiner classPath = new StringJoiner(File.pathSeparator);
 250         classPath.add((cModuleType == MODULE_TYPE.UNNAMED)
 251                 ? cJarPath.toFile().getCanonicalPath() : "");
 252         classPath.add((sModuletype == MODULE_TYPE.UNNAMED)
 253                 ? sJarPath.toFile().getCanonicalPath() : "");
 254         return classPath.toString();
 255     }
 256 
 257     /**
 258      * Construct executable module name for java. It is fixed for explicit
 259      * module type while it is same as jar file name for automated module type.
 260      */
 261     public String getModuleName(MODULE_TYPE moduleType,
 262             Path jarPath, String mName) {
 263         String jarName = jarPath.toFile().getName();
 264         return (moduleType == MODULE_TYPE.EXPLICIT) ? mName
 265                 : ((moduleType == MODULE_TYPE.AUTO) ? jarName.substring(0,
 266                                 jarName.indexOf(JAR_EXTN)) : null);
 267     }
 268 
 269     /**
 270      * Delete all the files inside the base module path.
 271      */
 272     public void cleanModuleBasePath(Path mBasePath) {
 273         Arrays.asList(mBasePath.toFile().listFiles()).forEach(f -> {
 274             System.out.println("delete: " + f);
 275             f.delete();
 276         });
 277     }
 278 
 279 }