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