1 /* 2 * Copyright (c) 2019, 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.File; 25 import java.io.IOException; 26 import java.io.OutputStream; 27 import java.lang.invoke.MethodHandle; 28 import java.lang.invoke.MethodHandles; 29 import java.lang.module.ModuleDescriptor; 30 import java.lang.reflect.Method; 31 import java.nio.file.FileSystems; 32 import java.nio.file.Files; 33 import java.nio.file.Path; 34 import java.util.ArrayList; 35 import java.util.LinkedList; 36 import java.util.List; 37 import java.util.jar.Attributes; 38 import java.util.jar.JarEntry; 39 import java.util.jar.JarOutputStream; 40 import java.util.jar.Manifest; 41 import java.util.stream.Collectors; 42 import java.util.stream.Stream; 43 44 import jdk.internal.module.ModuleInfoWriter; 45 import jdk.test.lib.JDKToolFinder; 46 import jdk.test.lib.process.ProcessTools; 47 import jdk.test.lib.util.JarUtils; 48 49 /* 50 * @test 51 * @bug 8205654 52 * @summary Unit test for sun.tools.ProcessHelper class. The test launches Java processes with different Java options 53 * and checks that sun.tools.ProcessHelper.getMainClass(pid) method returns a correct main class. return a . 54 * 55 * @requires (os.family == "linux" | os.family == "freebsd") 56 * @library /test/lib 57 * @modules jdk.jcmd/sun.tools.common:+open 58 * java.base/jdk.internal.module 59 * @build test.TestProcess 60 * @run main/othervm TestProcessHelper 61 */ 62 public class TestProcessHelper { 63 64 private static final String TEST_PROCESS_MAIN_CLASS_NAME = "TestProcess"; 65 private static final String TEST_PROCESS_MAIN_CLASS_PACKAGE = "test"; 66 private static final String TEST_PROCESS_MAIN_CLASS = TEST_PROCESS_MAIN_CLASS_PACKAGE + "." 67 + TEST_PROCESS_MAIN_CLASS_NAME; 68 private static final Path TEST_CLASSES = FileSystems.getDefault().getPath(System.getProperty("test.classes")); 69 private static final Path USER_DIR = FileSystems.getDefault().getPath(System.getProperty("user.dir", ".")); 70 private static final Path TEST_MODULES = USER_DIR.resolve("testmodules"); 71 private static final String JAVA_PATH = JDKToolFinder.getJDKTool("java"); 72 private static final Path TEST_CLASS = TEST_CLASSES.resolve(TEST_PROCESS_MAIN_CLASS_PACKAGE) 73 .resolve(TEST_PROCESS_MAIN_CLASS_NAME + ".class"); 74 75 private static final String[] CP_OPTIONS = {"-cp", "-classpath", "--class-path"}; 76 private static final String[][] VM_ARGS = {{}, {"-Dtest1=aaa"}, {"-Dtest1=aaa", "-Dtest2=bbb ccc"}}; 77 private static final String[][] ARGS = {{}, {"param1"}, {"param1", "param2"}}; 78 private static final String[] MP_OPTIONS = {"-p", "--module-path"}; 79 private static final String[] MODULE_OPTIONS = {"-m", "--module", "--module="}; 80 private static final String JAR_OPTION = "-jar"; 81 private static final String MODULE_NAME = "module1"; 82 private static final String[][] EXTRA_MODULAR_OPTIONS = {null, 83 {"--add-opens", "java.base/java.net=ALL-UNNAMED"}, 84 {"--add-exports", "java.base/java.net=ALL-UNNAMED"}, 85 {"--add-reads", "java.base/java.net=ALL-UNNAMED"}, 86 {"--add-modules", "java.management"}, 87 {"--limit-modules", "java.management"}, 88 {"--upgrade-module-path", "test"}}; 89 90 private static final String[] PATCH_MODULE_OPTIONS = {"--patch-module", null}; 91 92 private static final MethodHandle MH_GET_MAIN_CLASS = resolveMainClassMH(); 93 resolveMainClassMH()94 private static MethodHandle resolveMainClassMH() { 95 try { 96 Method getMainClassMethod = Class 97 .forName("sun.tools.common.ProcessHelper") 98 .getDeclaredMethod("getMainClass", String.class); 99 getMainClassMethod.setAccessible(true); 100 return MethodHandles.lookup().unreflect(getMainClassMethod); 101 } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException e) { 102 throw new RuntimeException(e); 103 } 104 } 105 callGetMainClass(Process p)106 private static String callGetMainClass(Process p) { 107 try { 108 return (String)MH_GET_MAIN_CLASS.invoke(Long.toString(p.pid())); 109 } catch (Throwable e) { 110 throw new RuntimeException(e); 111 } 112 113 } 114 main(String[] args)115 public static void main(String[] args) throws Exception { 116 new TestProcessHelper().runTests(); 117 } 118 runTests()119 public void runTests() throws Exception { 120 testClassPath(); 121 testJar(); 122 testModule(); 123 } 124 125 // Test Java processes that are started with -classpath, -cp, or --class-path options 126 // and with different combinations of VM and program args. testClassPath()127 private void testClassPath() throws Exception { 128 for (String cp : CP_OPTIONS) { 129 for (String[] vma : VM_ARGS) { 130 for (String[] arg : ARGS) { 131 for (String[] modularOptions : EXTRA_MODULAR_OPTIONS) { 132 List<String> cmd = new LinkedList<>(); 133 cmd.add(JAVA_PATH); 134 cmd.add(cp); 135 cmd.add(TEST_CLASSES.toAbsolutePath().toString()); 136 for (String v : vma) { 137 cmd.add(v); 138 } 139 if (modularOptions != null) { 140 cmd.add(modularOptions[0]); 141 cmd.add(modularOptions[1]); 142 } 143 cmd.add(TEST_PROCESS_MAIN_CLASS); 144 for (String a : arg) { 145 cmd.add(a); 146 } 147 testProcessHelper(cmd, TEST_PROCESS_MAIN_CLASS); 148 } 149 } 150 } 151 } 152 } 153 154 // Test Java processes that are started with -jar option 155 // and with different combinations of VM and program args. testJar()156 private void testJar() throws Exception { 157 File jarFile = prepareJar(); 158 for (String[] vma : VM_ARGS) { 159 for (String[] arg : ARGS) { 160 List<String> cmd = new LinkedList<>(); 161 cmd.add(JAVA_PATH); 162 for (String v : vma) { 163 cmd.add(v); 164 } 165 cmd.add(JAR_OPTION); 166 cmd.add(jarFile.getAbsolutePath()); 167 for (String a : arg) { 168 cmd.add(a); 169 } 170 testProcessHelper(cmd, jarFile.getAbsolutePath()); 171 } 172 } 173 174 } 175 176 // Test Java processes that are started with -m or --module options 177 // and with different combination of VM and program args. testModule()178 private void testModule() throws Exception { 179 prepareModule(); 180 for (String mp : MP_OPTIONS) { 181 for (String m : MODULE_OPTIONS) { 182 for (String[] vma : VM_ARGS) { 183 for (String[] arg : ARGS) { 184 for(String patchModuleOption : PATCH_MODULE_OPTIONS) { 185 List<String> cmd = new LinkedList<>(); 186 cmd.add(JAVA_PATH); 187 cmd.add(mp); 188 cmd.add(TEST_MODULES.toAbsolutePath().toString()); 189 if (patchModuleOption != null) { 190 cmd.add(patchModuleOption); 191 cmd.add(MODULE_NAME + "=" + TEST_MODULES.toAbsolutePath().toString()); 192 } 193 for (String v : vma) { 194 cmd.add(v); 195 } 196 if (m.endsWith("=")) { 197 cmd.add(m + MODULE_NAME + "/" + TEST_PROCESS_MAIN_CLASS); 198 } else { 199 cmd.add(m); 200 cmd.add(MODULE_NAME + "/" + TEST_PROCESS_MAIN_CLASS); 201 } 202 for (String a : arg) { 203 cmd.add(a); 204 } 205 testProcessHelper(cmd, MODULE_NAME + "/" + TEST_PROCESS_MAIN_CLASS); 206 } 207 } 208 } 209 } 210 } 211 } 212 checkMainClass(Process p, String expectedMainClass)213 private void checkMainClass(Process p, String expectedMainClass) { 214 String mainClass = callGetMainClass(p); 215 // getMainClass() may return null, e.g. due to timing issues. 216 // Attempt some limited retries. 217 if (mainClass == null) { 218 System.err.println("Main class returned by ProcessHelper was null."); 219 // sleep time doubles each round, altogether, wait no longer than 1 sec 220 final int MAX_RETRIES = 10; 221 int retrycount = 0; 222 long sleepms = 1; 223 while (retrycount < MAX_RETRIES && mainClass == null) { 224 System.err.println("Retry " + retrycount + ", sleeping for " + sleepms + "ms."); 225 try { 226 Thread.sleep(sleepms); 227 } catch (InterruptedException e) { 228 // ignore 229 } 230 mainClass = callGetMainClass(p); 231 retrycount++; 232 sleepms *= 2; 233 } 234 } 235 p.destroyForcibly(); 236 if (!expectedMainClass.equals(mainClass)) { 237 throw new RuntimeException("Main class is wrong: " + mainClass); 238 } 239 } 240 testProcessHelper(List<String> args, String expectedValue)241 private void testProcessHelper(List<String> args, String expectedValue) throws Exception { 242 ProcessBuilder pb = new ProcessBuilder(args); 243 String cmd = pb.command().stream().collect(Collectors.joining(" ")); 244 System.out.println("Starting the process:" + cmd); 245 Process p = ProcessTools.startProcess("test", pb); 246 if (!p.isAlive()) { 247 throw new RuntimeException("Cannot start the process: " + cmd); 248 } 249 checkMainClass(p, expectedValue); 250 } 251 prepareJar()252 private File prepareJar() throws Exception { 253 Path jarFile = USER_DIR.resolve("testprocess.jar"); 254 Manifest manifest = createManifest(); 255 JarUtils.createJarFile(jarFile, manifest, TEST_CLASSES, TEST_CLASS); 256 return jarFile.toFile(); 257 } 258 prepareModule()259 private void prepareModule() throws Exception { 260 TEST_MODULES.toFile().mkdirs(); 261 Path moduleJar = TEST_MODULES.resolve("mod1.jar"); 262 ModuleDescriptor md = createModuleDescriptor(); 263 createModuleJarFile(moduleJar, md, TEST_CLASSES, TEST_CLASS); 264 } 265 createManifest()266 private Manifest createManifest() { 267 Manifest manifest = new Manifest(); 268 manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); 269 manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, TEST_PROCESS_MAIN_CLASS); 270 return manifest; 271 } 272 createModuleDescriptor()273 private ModuleDescriptor createModuleDescriptor() { 274 ModuleDescriptor.Builder builder 275 = ModuleDescriptor.newModule(MODULE_NAME).requires("java.base"); 276 return builder.build(); 277 } 278 createModuleJarFile(Path jarfile, ModuleDescriptor md, Path dir, Path... files)279 private static void createModuleJarFile(Path jarfile, ModuleDescriptor md, Path dir, Path... files) 280 throws IOException { 281 282 Path parent = jarfile.getParent(); 283 if (parent != null) { 284 Files.createDirectories(parent); 285 } 286 287 List<Path> entries = findAllRegularFiles(dir, files); 288 289 try (OutputStream out = Files.newOutputStream(jarfile); 290 JarOutputStream jos = new JarOutputStream(out)) { 291 if (md != null) { 292 JarEntry je = new JarEntry("module-info.class"); 293 jos.putNextEntry(je); 294 ModuleInfoWriter.write(md, jos); 295 jos.closeEntry(); 296 } 297 298 for (Path entry : entries) { 299 String name = toJarEntryName(entry); 300 jos.putNextEntry(new JarEntry(name)); 301 Files.copy(dir.resolve(entry), jos); 302 jos.closeEntry(); 303 } 304 } 305 } 306 toJarEntryName(Path file)307 private static String toJarEntryName(Path file) { 308 Path normalized = file.normalize(); 309 return normalized.subpath(0, normalized.getNameCount()) 310 .toString() 311 .replace(File.separatorChar, '/'); 312 } 313 findAllRegularFiles(Path dir, Path[] files)314 private static List<Path> findAllRegularFiles(Path dir, Path[] files) throws IOException { 315 List<Path> entries = new ArrayList<>(); 316 for (Path file : files) { 317 try (Stream<Path> stream = Files.find(dir.resolve(file), Integer.MAX_VALUE, 318 (p, attrs) -> attrs.isRegularFile())) { 319 stream.map(dir::relativize) 320 .forEach(entries::add); 321 } 322 } 323 return entries; 324 } 325 326 } 327