1 /*
  2  * Copyright (c) 2020, 2022, 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 /**
 26  * @test
 27  * @requires vm.cds & !vm.graal.enabled
 28  * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds
 29  * @run driver OptimizeModuleHandlingTest
 30  * @summary test module path changes for optimization of
 31  *          module handling.
 32  *
 33  */
 34 
 35 import java.io.File;
 36 import java.nio.file.Files;
 37 import java.nio.file.Path;
 38 import java.nio.file.Paths;
 39 
 40 import jdk.test.lib.cds.CDSTestUtils;
 41 import jdk.test.lib.process.OutputAnalyzer;
 42 
 43 public class OptimizeModuleHandlingTest {
 44 
 45     private static final Path USER_DIR = Paths.get(CDSTestUtils.getOutputDir());
 46 
 47     private static final String TEST_SRC = System.getProperty("test.src");
 48 
 49     private static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
 50     private static final Path MODS_DIR = Paths.get("mody");
 51 
 52     // the module name of the test module
 53     private static final String MAIN_MODULE = "com.bars";
 54     private static final String TEST_MODULE = "com.foos";
 55 
 56     // the module main class
 57     private static final String MAIN_CLASS = "com.bars.Main";
 58     private static final String TEST_CLASS = "com.foos.Test";
 59 
 60     private static String PATH_LIBS = "modylibs";
 61     private static Path libsDir = null;
 62     private static Path mainJar = null;
 63     private static Path testJar = null;
 64 
 65     private static String CLASS_FOUND_MESSAGE = "com.foos.Test found";
 66     private static String CLASS_NOT_FOUND_MESSAGE = "java.lang.ClassNotFoundException: com.foos.Test";
 67     private static String OPTIMIZE_ENABLED = "optimized module handling: enabled";
 68     private static String OPTIMIZE_DISABLED = "optimized module handling: disabled";
 69     private static String MAIN_FROM_JAR = "class,load.*com.bars.Main.*[.]jar";
 70     private static String MAIN_FROM_CDS = "class,load.*com.bars.Main.*shared objects file";
 71     private static String TEST_FROM_JAR = "class,load.*com.foos.Test.*[.]jar";
 72     private static String TEST_FROM_CDS = "class,load.*com.foos.Test.*shared objects file";
 73     private static String MAP_FAILED  = "Unable to use shared archive";
 74     private static String PATH_SEPARATOR = File.pathSeparator;
 75 
 76     public static void buildTestModule() throws Exception {
 77 
 78         // javac -d mods/$TESTMODULE src/$TESTMODULE/**
 79         JarBuilder.compileModule(SRC_DIR.resolve(TEST_MODULE),
 80                                  MODS_DIR.resolve(TEST_MODULE),
 81                                  null);
 82 
 83         // javac -d mods/$TESTMODULE --module-path MOD_DIR src/$TESTMODULE/**
 84         JarBuilder.compileModule(SRC_DIR.resolve(MAIN_MODULE),
 85                                  MODS_DIR.resolve(MAIN_MODULE),
 86                                  MODS_DIR.toString());
 87 
 88         libsDir = Files.createTempDirectory(USER_DIR, PATH_LIBS);
 89         mainJar = libsDir.resolve(MAIN_MODULE + ".jar");
 90         testJar = libsDir.resolve(TEST_MODULE + ".jar");
 91 
 92         // modylibs contains both modules com.foos.jar, com.bars.jar
 93         // build com.foos.jar
 94         String classes = MODS_DIR.resolve(TEST_MODULE).toString();
 95         JarBuilder.createModularJar(testJar.toString(), classes, TEST_CLASS);
 96 
 97         // build com.bars.jar
 98         classes = MODS_DIR.resolve(MAIN_MODULE).toString();
 99         JarBuilder.createModularJar(mainJar.toString(), classes, MAIN_CLASS);
100     }
101 
102     public static void main(String... args) throws Exception {
103         runWithModulePath();
104         runWithJarPath();
105     }
106 
107     private static void tty(String... args) {
108         for (String s : args) {
109             System.out.print(s + " ");
110         }
111         System.out.print("\n");
112     }
113 
114     public static void runWithModulePath(String... extraRuntimeArgs) throws Exception {
115         // compile the modules and create the modular jar files
116         buildTestModule();
117         String appClasses[] = {MAIN_CLASS, TEST_CLASS};
118         // create an archive with the classes in the modules built in the
119         // previous step
120         OutputAnalyzer output = TestCommon.createArchive(
121                                         null, appClasses,
122                                         "--module-path",
123                                         libsDir.toString(),
124                                         "-m", MAIN_MODULE,
125                                         "-XX:-PreloadSharedClasses");
126         TestCommon.checkDump(output);
127 
128         // following 1 - 4 test with CDS off
129         tty("1. run with CDS off");
130         TestCommon.execOff( "-p", libsDir.toString(),
131                             "-m", MAIN_MODULE)
132             .shouldHaveExitValue(0)
133             .shouldNotContain(OPTIMIZE_ENABLED)
134             .shouldContain(CLASS_FOUND_MESSAGE);
135         tty("2. run with CDS off, without module path");
136         TestCommon.execOff("-cp",
137                            mainJar.toString(),
138                            MAIN_CLASS)
139             .shouldHaveExitValue(0)
140             .shouldContain(CLASS_NOT_FOUND_MESSAGE);
141         tty("3. run with CDS off, but with full jars in path");
142         TestCommon.execOff( "-cp", mainJar.toString() + PATH_SEPARATOR + testJar.toString(),
143                             MAIN_CLASS)
144             .shouldHaveExitValue(0)
145             .shouldNotContain(OPTIMIZE_ENABLED)
146             .shouldContain(CLASS_FOUND_MESSAGE);
147         tty("4. run with CDS off, only main jar on path, but given moudle path");
148         TestCommon.execOff( "-cp", mainJar.toString(),
149                             "--module-path", libsDir.toString(),
150                             "--add-modules", TEST_MODULE,
151                             MAIN_CLASS)
152             .shouldHaveExitValue(0)
153             .shouldNotContain(OPTIMIZE_ENABLED)
154             .shouldContain(CLASS_FOUND_MESSAGE);
155 
156         // Following 5 - 10 test with CDS on
157         tty("5. run with CDS on, with module path");
158         String prefix[] = {"-Djava.class.path=", "-Xlog:cds", "-Xlog:class+load"};
159         TestCommon.runWithModules(prefix,
160                                  null,               // --upgrade-module-path
161                                  libsDir.toString(), // --module-path
162                                  MAIN_MODULE)        // -m
163             .assertNormalExit(out -> {
164                 out.shouldNotContain(OPTIMIZE_ENABLED)
165                    .shouldContain(OPTIMIZE_DISABLED)
166                    .shouldMatch(MAIN_FROM_CDS)       // // archived Main class is for module only
167                    .shouldContain(CLASS_FOUND_MESSAGE);
168             });
169         tty("6. run with CDS on, with module paths set correctly");
170         TestCommon.run("-Xlog:cds",
171                        "-Xlog:class+load",
172                        "-p", libsDir.toString(),
173                        "-m", MAIN_MODULE)
174             .assertNormalExit(out -> {
175                 out.shouldContain(CLASS_FOUND_MESSAGE)
176                    .shouldMatch(MAIN_FROM_CDS)
177                    .shouldMatch(TEST_FROM_CDS)
178                    .shouldContain(OPTIMIZE_DISABLED)
179                    .shouldNotContain(OPTIMIZE_ENABLED);
180             });
181         tty("7. run with CDS on, with jar on path");
182         TestCommon.run("-Xlog:cds",
183                        "-Xlog:class+load",
184                        "-cp", mainJar.toString() + PATH_SEPARATOR + testJar.toString(),
185                        MAIN_CLASS)
186             .assertNormalExit(out -> {
187                 out.shouldContain(CLASS_FOUND_MESSAGE)
188                    .shouldMatch(MAIN_FROM_JAR)
189                    .shouldMatch(TEST_FROM_JAR)
190                    .shouldContain(OPTIMIZE_DISABLED)
191                    .shouldNotContain(OPTIMIZE_ENABLED);
192             });
193 
194         tty("8. run with CDS on, with --module-path, with jar should fail");
195         TestCommon.run("-Xlog:cds",
196                        "-Xlog:class+load",
197                        "-p", libsDir.toString(),
198                        "-cp", mainJar.toString(),
199                        MAIN_CLASS)
200             .assertNormalExit(out -> {
201                 out.shouldContain(CLASS_NOT_FOUND_MESSAGE)
202                    .shouldMatch(MAIN_FROM_JAR)
203                    .shouldNotContain(OPTIMIZE_ENABLED);
204             });
205         tty("9. run with CDS on, with com.foos on --module-path, with main jar on cp should pass");
206         TestCommon.run("-Xlog:cds",
207                        "-Xlog:class+load",
208                        "--module-path", libsDir.toString(),
209                        "--add-modules", TEST_MODULE,
210                        "-cp", mainJar.toString(),
211                        MAIN_CLASS)
212             .assertNormalExit(out -> {
213                 out.shouldContain(CLASS_FOUND_MESSAGE)
214                    .shouldMatch(MAIN_FROM_JAR)
215                    .shouldMatch(TEST_FROM_CDS)
216                    .shouldNotContain(OPTIMIZE_ENABLED);
217             });
218         tty("10. run with CDS on, --module-path, with -Xbootclasspath/a: .");
219         TestCommon.run("-Xlog:cds",
220                        "-Xbootclasspath/a:", ".",
221                        "--module-path", libsDir.toString(),
222                        MAIN_CLASS)
223             .assertAbnormalExit(out -> {
224                 out.shouldNotContain(CLASS_FOUND_MESSAGE)
225                    .shouldContain(OPTIMIZE_DISABLED)           // mapping info
226                    .shouldContain("Error: Could not find or load main class .");
227             });
228         tty("11. run with CDS on, --module-path, with -Xbootclasspath/a:.");
229         TestCommon.run("-Xlog:cds",
230                        "-Xbootclasspath/a:.",
231                        "--module-path", libsDir.toString(),
232                        MAIN_CLASS)
233             .assertAbnormalExit(out -> {
234                 out.shouldNotContain(CLASS_FOUND_MESSAGE)
235                    .shouldContain(OPTIMIZE_DISABLED)           // mapping info
236                    .shouldContain("shared class paths mismatch");
237             });
238     }
239 
240     public static void runWithJarPath(String... extraRuntimeArgs) throws Exception {
241         // compile the modules and create the modular jar files
242         buildTestModule();
243         String appClasses[] = {MAIN_CLASS, TEST_CLASS};
244         // create an archive with the classes in the modules built in the
245         // previous step
246         OutputAnalyzer output = TestCommon.createArchive(
247                                     testJar.toString() + PATH_SEPARATOR + mainJar.toString(),
248                                     appClasses, "-XX:-PreloadSharedClasses");
249         TestCommon.checkDump(output);
250 
251         // tests 1 - 4 test with CDS off are same as with module archive.
252         tty("tests 1 - 4 test with CDS off are same as with module archive, skipped");
253 
254         // Following 5 - 9 test with CDS on
255         tty("5. run with CDS on, with module path");
256         String prefix[] = {"-Djava.class.path=", "-Xlog:cds"};
257         TestCommon.runWithModules(prefix,
258                                   null,               // --upgrade-module-path
259                                   libsDir.toString(), // --module-path
260                                   MAIN_MODULE)        // -m
261             .assertAbnormalExit(out -> {
262                 out.shouldContain(MAP_FAILED)
263                    .shouldNotContain(OPTIMIZE_ENABLED)
264                    .shouldNotContain(CLASS_FOUND_MESSAGE);
265             });
266         tty("6. run with CDS on, with module paths set correctly");
267         TestCommon.run("-Xlog:cds",
268                        "-p", libsDir.toString(),
269                        "-m", MAIN_MODULE)
270             .assertAbnormalExit(out -> {
271                 out.shouldContain(MAP_FAILED)
272                    .shouldNotContain(CLASS_FOUND_MESSAGE)
273                    .shouldNotContain(OPTIMIZE_ENABLED);
274             });
275         tty("7. run with CDS on, with jar on path");
276         TestCommon.run("-Xlog:cds",
277                        "-Xlog:class+load",
278                        "-cp", testJar.toString() + PATH_SEPARATOR + mainJar.toString(),
279                        MAIN_CLASS)
280             .assertNormalExit(out -> {
281                 out.shouldMatch(MAIN_FROM_CDS)
282                    .shouldMatch(TEST_FROM_CDS)
283                    .shouldContain(CLASS_FOUND_MESSAGE)
284                    .shouldContain(OPTIMIZE_ENABLED);
285             });
286         tty("8. run with CDS on, with --module-path, with jars on classpath should run but not optimized");
287         TestCommon.run("-Xlog:cds",
288                        "-Xlog:class+load",
289                        "-p", libsDir.toString(),
290                        "-cp", testJar.toString() + PATH_SEPARATOR + mainJar.toString(),
291                        "--add-modules=com.bars",         // Main/Test from jars
292                        MAIN_CLASS)
293             .assertNormalExit(out -> {
294                 out.shouldMatch(MAIN_FROM_JAR)
295                    .shouldMatch(TEST_FROM_JAR)
296                    .shouldContain(CLASS_FOUND_MESSAGE)
297                    .shouldNotContain(OPTIMIZE_ENABLED);
298             });
299         tty("9. run with CDS on,  with main jar only on classpath should not pass");
300         TestCommon.run("-Xlog:cds",
301                        "-cp", mainJar.toString(),
302                        MAIN_CLASS)
303             .assertAbnormalExit(out -> {
304                 out.shouldContain(MAP_FAILED)
305                    .shouldNotContain(CLASS_FOUND_MESSAGE)
306                    .shouldNotContain(CLASS_NOT_FOUND_MESSAGE)
307                    .shouldNotContain(OPTIMIZE_ENABLED)
308                    .shouldNotContain(OPTIMIZE_DISABLED);
309             });
310         tty("10. run with CDS on,  with main/test jars on classpath also with -Xbootclasspath/a:  should pass");
311         TestCommon.run("-Xlog:cds",
312                        "-cp", mainJar.toString() + PATH_SEPARATOR + testJar.toString(),
313                        "-Xbootclasspath/a:", ".",
314                        MAIN_CLASS)
315             .assertAbnormalExit(out -> {
316                 out.shouldNotContain(CLASS_FOUND_MESSAGE)
317                    .shouldNotContain(CLASS_NOT_FOUND_MESSAGE)
318                    .shouldNotContain(OPTIMIZE_ENABLED)
319                    .shouldContain(MAP_FAILED);
320             });
321 
322         // Dump an archive with -Xbootclasspath/a
323         output = TestCommon.createArchive(
324                                 testJar.toString(),
325                                 appClasses,
326                                 "-Xbootclasspath/a:" + mainJar.toString());
327         TestCommon.checkDump(output);
328         tty("11. run with CDS on,  with test jar on classpath and with main jar on -Xbootclasspath/a:  should pass");
329         TestCommon.run("-Xlog:cds",
330                        "-cp", testJar.toString(),
331                        "-Xbootclasspath/a:" + mainJar.toString(),
332                        MAIN_CLASS)
333             .assertNormalExit(out -> {
334                 out.shouldNotContain(CLASS_FOUND_MESSAGE)
335                    .shouldNotContain(CLASS_NOT_FOUND_MESSAGE)
336                    .shouldContain(OPTIMIZE_ENABLED);
337             });
338         tty("12. run with CDS on,  with main jar on classpath and with test jar on -Xbootclasspath/a:  should not pass due to class paths mismatch");
339         TestCommon.run("-Xlog:cds",
340                        "-cp", mainJar.toString(),
341                        "-Xbootclasspath/a:" + testJar.toString(),
342                        MAIN_CLASS)
343             .assertAbnormalExit(out -> {
344                 out.shouldNotContain(CLASS_FOUND_MESSAGE)
345                    .shouldNotContain(CLASS_NOT_FOUND_MESSAGE)
346                    .shouldNotContain(OPTIMIZE_ENABLED)
347                    .shouldContain(MAP_FAILED);
348             });
349 
350         // Skip the following test for dynamic CDS archive because the current
351         // dynamic dump test utililty does not support empty -cp with a classlist.
352         // (see createArchive(CDSOptions opts) in TestCommon.java)
353         if (!CDSTestUtils.isDynamicArchive()) {
354             // Dump an archive with only -Xbootclasspath/a
355             output = TestCommon.createArchive(
356                                     null,
357                                     appClasses,
358                                     "-Xbootclasspath/a:" + mainJar.toString());
359             TestCommon.checkDump(output);
360             tty("13. run with CDS on,  with the same -Xbootclasspath/a as dump time and adding a -cp with test.jar:  should pass");
361             TestCommon.run("-Xlog:cds,class+load",
362                            "-cp", testJar.toString(),
363                            "-Xbootclasspath/a:" + mainJar.toString(),
364                            MAIN_CLASS)
365                 .assertNormalExit(out -> {
366                     out.shouldMatch(MAIN_FROM_CDS)
367                        .shouldContain(OPTIMIZE_ENABLED);
368             });
369         }
370     }
371 }