1 /*
2 * Copyright (c) 2017, 2025, 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 package jdk.test.lib.cds;
24
25 import java.io.IOException;
26 import java.io.File;
27 import java.io.FileOutputStream;
28 import java.io.PrintStream;
29 import java.nio.file.Files;
30 import java.nio.file.CopyOption;
31 import java.nio.file.Path;
32 import java.nio.file.Paths;
33 import java.nio.file.StandardCopyOption;
34 import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
35 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
36 import java.text.SimpleDateFormat;
37 import java.util.List;
38 import java.util.ArrayList;
39 import java.util.Date;
40 import jdk.test.lib.JDKToolFinder;
41 import jdk.test.lib.Utils;
42 import jdk.test.lib.process.OutputAnalyzer;
43 import jdk.test.lib.process.ProcessTools;
44 import jtreg.SkippedException;
45
46 // This class contains common test utilities for testing CDS
47 public class CDSTestUtils {
48 public static final String MSG_RANGE_NOT_WITHIN_HEAP =
49 "Unable to allocate region, range is not within java heap.";
50 public static final String MSG_RANGE_ALREADT_IN_USE =
51 "Unable to allocate region, java heap range is already in use.";
52 public static final String MSG_DYNAMIC_NOT_SUPPORTED =
53 "-XX:ArchiveClassesAtExit is unsupported when base CDS archive is not loaded";
54 public static final String MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE =
55 "an object points to a static field that may hold a different value at runtime";
56 public static final boolean DYNAMIC_DUMP = Boolean.getBoolean("test.dynamic.cds.archive");
57
58 public interface Checker {
59 public void check(OutputAnalyzer output) throws Exception;
60 }
61
62 /*
63 * INTRODUCTION
64 *
65 * When testing various CDS functionalities, we need to launch JVM processes
66 * using a "launch method" (such as TestCommon.run), and analyze the results of these
67 * processes.
68 *
69 * While typical jtreg tests would use OutputAnalyzer in such cases, due to the
70 * complexity of CDS failure modes, we have added the CDSTestUtils.Result class
71 * to make the analysis more convenient and less error prone.
72 *
73 * A Java process can end in one of the following 4 states:
74 *
75 * 1: Unexpected error - such as JVM crashing. In this case, the "launch method"
76 * will throw a RuntimeException.
77 * 2: Mapping Failure - this happens when the OS (intermittently) fails to map the
78 * CDS archive, normally caused by Address Space Layout Randomization.
79 * We usually treat this as "pass".
80 * 3: Normal Exit - the JVM process has finished without crashing, and the exit code is 0.
81 * 4: Abnormal Exit - the JVM process has finished without crashing, and the exit code is not 0.
82 *
83 * In most test cases, we need to check the JVM process's output in cases 3 and 4. However, we need
84 * to make sure that our test code is not confused by case 2.
85 *
86 * For example, a JVM process is expected to print the string "Hi" and exit with 0. With the old
87 * CDSTestUtils.runWithArchive API, the test may be written as this:
88 *
89 * OutputAnalyzer out = CDSTestUtils.runWithArchive(args);
90 * out.shouldContain("Hi");
91 *
92 * However, if the JVM process fails with mapping failure, the string "Hi" will not be in the output,
93 * and your test case will fail intermittently.
94 *
95 * Instead, the test case should be written as
96 *
97 * CDSTestUtils.run(args).assertNormalExit("Hi");
98 *
99 * EXAMPLES/HOWTO
100 *
101 * 1. For simple substring matching:
102 *
103 * CDSTestUtils.run(args).assertNormalExit("Hi");
104 * CDSTestUtils.run(args).assertNormalExit("a", "b", "x");
105 * CDSTestUtils.run(args).assertAbnormalExit("failure 1", "failure2");
106 *
107 * 2. For more complex output matching: using Lambda expressions
108 *
109 * CDSTestUtils.run(args)
110 * .assertNormalExit(output -> output.shouldNotContain("this should not be printed");
111 * CDSTestUtils.run(args)
112 * .assertAbnormalExit(output -> {
113 * output.shouldNotContain("this should not be printed");
114 * output.shouldHaveExitValue(123);
115 * });
116 *
117 * 3. Chaining several checks:
118 *
119 * CDSTestUtils.run(args)
120 * .assertNormalExit(output -> output.shouldNotContain("this should not be printed")
121 * .assertNormalExit("should have this", "should have that");
122 *
123 * 4. [Rare use case] if a test sometimes exit normally, and sometimes abnormally:
124 *
125 * CDSTestUtils.run(args)
126 * .ifNormalExit("ths string is printed when exiting with 0")
127 * .ifAbNormalExit("ths string is printed when exiting with 1");
128 *
129 * NOTE: you usually don't want to write your test case like this -- it should always
130 * exit with the same exit code. (But I kept this API because some existing test cases
131 * behave this way -- need to revisit).
132 */
133 public static class Result {
134 private final OutputAnalyzer output;
135 private final CDSOptions options;
136 private final boolean hasNormalExit;
137 private final String CDS_DISABLED = "warning: CDS is disabled when the";
138
139 public Result(CDSOptions opts, OutputAnalyzer out) throws Exception {
140 checkMappingFailure(out);
141 this.options = opts;
142 this.output = out;
143 hasNormalExit = (output.getExitValue() == 0);
144
145 if (hasNormalExit) {
146 if ("on".equals(options.xShareMode) &&
147 output.getStderr().contains("java version") &&
148 !output.getStderr().contains(CDS_DISABLED)) {
149 // "-showversion" is always passed in the command-line by the execXXX methods.
150 // During normal exit, we require that the VM to show that sharing was enabled.
151 output.shouldContain("sharing");
152 }
153 }
154 }
155
156 public Result assertNormalExit(Checker checker) throws Exception {
157 checker.check(output);
158 output.shouldHaveExitValue(0);
159 return this;
160 }
161
162 public Result assertAbnormalExit(Checker checker) throws Exception {
163 checker.check(output);
164 output.shouldNotHaveExitValue(0);
165 return this;
166 }
167
168 // When {--limit-modules, --patch-module, and/or --upgrade-module-path}
169 // are specified, CDS is silently disabled for both -Xshare:auto and -Xshare:on.
170 public Result assertSilentlyDisabledCDS(Checker checker) throws Exception {
171 // this comes from a JVM warning message.
172 output.shouldContain(CDS_DISABLED);
173 checker.check(output);
174 return this;
175 }
176
177 public Result assertSilentlyDisabledCDS(int exitCode, String... matches) throws Exception {
178 return assertSilentlyDisabledCDS((out) -> {
179 out.shouldHaveExitValue(exitCode);
180 checkMatches(out, matches);
181 });
182 }
183
184 public Result ifNormalExit(Checker checker) throws Exception {
185 if (hasNormalExit) {
186 checker.check(output);
187 }
188 return this;
189 }
190
191 public Result ifAbnormalExit(Checker checker) throws Exception {
192 if (!hasNormalExit) {
193 checker.check(output);
194 }
195 return this;
196 }
197
198 public Result ifNoMappingFailure(Checker checker) throws Exception {
199 checker.check(output);
200 return this;
201 }
202
203
204 public Result assertNormalExit(String... matches) throws Exception {
205 checkMatches(output, matches);
206 output.shouldHaveExitValue(0);
207 return this;
208 }
209
210 public Result assertAbnormalExit(String... matches) throws Exception {
211 checkMatches(output, matches);
212 output.shouldNotHaveExitValue(0);
213 return this;
214 }
215 }
216
217 // A number to be included in the filename of the stdout and the stderr output file.
218 static int logCounter = 0;
219
220 private static int getNextLogCounter() {
221 return logCounter++;
222 }
223
224 // By default, stdout of child processes are logged in files such as
225 // <testname>-0000-exec.stdout. If you want to also include the stdout
226 // inside jtr files, you can override this in the jtreg command line like
227 // "jtreg -Dtest.cds.copy.child.stdout=true ...."
228 public static final boolean copyChildStdoutToMainStdout =
229 Boolean.getBoolean("test.cds.copy.child.stdout");
230
231 public static final String UnableToMapMsg =
232 "Unable to map shared archive: test did not complete";
233
234 // Create bootstrap CDS archive,
235 // use extra JVM command line args as a prefix.
236 // For CDS tests specifying prefix makes more sense than specifying suffix, since
237 // normally there are no classes or arguments to classes, just "-version"
238 // To specify suffix explicitly use CDSOptions.addSuffix()
239 public static OutputAnalyzer createArchive(String... cliPrefix)
240 throws Exception {
241 return createArchive((new CDSOptions()).addPrefix(cliPrefix));
242 }
243
244 // Create bootstrap CDS archive
245 public static OutputAnalyzer createArchive(CDSOptions opts)
246 throws Exception {
247
248 startNewArchiveName();
249
250 ArrayList<String> cmd = new ArrayList<String>();
251
252 for (String p : opts.prefix) cmd.add(p);
253
254 cmd.add("-Xshare:dump");
255 cmd.add("-Xlog:cds,aot+hashtables");
256 if (opts.archiveName == null)
257 opts.archiveName = getDefaultArchiveName();
258 cmd.add("-XX:SharedArchiveFile=" + opts.archiveName);
259
260 if (opts.classList != null) {
261 File classListFile = makeClassList(opts.classList);
262 cmd.add("-XX:ExtraSharedClassListFile=" + classListFile.getPath());
263 }
264
265 for (String s : opts.suffix) cmd.add(s);
266
267 String[] cmdLine = cmd.toArray(new String[cmd.size()]);
268 ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(cmdLine);
269 return executeAndLog(pb, "dump");
270 }
271
272 public static boolean isDynamicArchive() {
273 return DYNAMIC_DUMP;
274 }
275
276 // check result of 'dump-the-archive' operation, that is "-Xshare:dump"
277 public static OutputAnalyzer checkDump(OutputAnalyzer output, String... extraMatches)
278 throws Exception {
279
280 if (!DYNAMIC_DUMP) {
281 output.shouldContain("Loading classes to share");
282 } else {
283 output.shouldContain("Written dynamic archive 0x");
284 }
285 output.shouldHaveExitValue(0);
286 //output.shouldNotContain(MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE); // FIXME -- leyden+JEP483 merge
287
288 for (String match : extraMatches) {
289 output.shouldContain(match);
290 }
291
292 return output;
293 }
294
295 // check result of dumping base archive
296 public static OutputAnalyzer checkBaseDump(OutputAnalyzer output) throws Exception {
297 output.shouldContain("Loading classes to share");
298 output.shouldHaveExitValue(0);
299 //output.shouldNotContain(MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE); // FIXME -- leyden+JEP483 merge
300 return output;
301 }
302
303 // A commonly used convenience methods to create an archive and check the results
304 // Creates an archive and checks for errors
305 public static OutputAnalyzer createArchiveAndCheck(CDSOptions opts)
306 throws Exception {
307 return checkDump(createArchive(opts));
308 }
309
310
311 public static OutputAnalyzer createArchiveAndCheck(String... cliPrefix)
312 throws Exception {
313 return checkDump(createArchive(cliPrefix));
314 }
315
316
317 // This method should be used to check the output of child VM for common exceptions.
318 // Most of CDS tests deal with child VM processes for creating and using the archive.
319 // However exceptions that occur in the child process do not automatically propagate
320 // to the parent process. This mechanism aims to improve the propagation
321 // of exceptions and common errors.
322 // Exception e argument - an exception to be re-thrown if none of the common
323 // exceptions match. Pass null if you wish not to re-throw any exception.
324 public static void checkCommonExecExceptions(OutputAnalyzer output, Exception e)
325 throws Exception {
326 if (output.getStdout().contains("https://bugreport.java.com/bugreport/crash.jsp")) {
327 throw new RuntimeException("Hotspot crashed");
328 }
329 if (output.getStdout().contains("TEST FAILED")) {
330 throw new RuntimeException("Test Failed");
331 }
332 if (output.getOutput().contains("Unable to unmap shared space")) {
333 throw new RuntimeException("Unable to unmap shared space");
334 }
335
336 // Special case -- sometimes Xshare:on fails because it failed to map
337 // at given address. This behavior is platform-specific, machine config-specific
338 // and can be random (see ASLR).
339 checkMappingFailure(output);
340
341 if (e != null) {
342 throw e;
343 }
344 }
345
346 public static void checkCommonExecExceptions(OutputAnalyzer output) throws Exception {
347 checkCommonExecExceptions(output, null);
348 }
349
350
351 // Check the output for indication that mapping of the archive failed.
352 // Performance note: this check seems to be rather costly - searching the entire
353 // output stream of a child process for multiple strings. However, it is necessary
354 // to detect this condition, a failure to map an archive, since this is not a real
355 // failure of the test or VM operation, and results in a test being "skipped".
356 // Suggestions to improve:
357 // 1. VM can designate a special exit code for such condition.
358 // 2. VM can print a single distinct string indicating failure to map an archive,
359 // instead of utilizing multiple messages.
360 // These are suggestions to improve testibility of the VM. However, implementing them
361 // could also improve usability in the field.
362 private static String hasUnableToMapMessage(OutputAnalyzer output) {
363 String outStr = output.getOutput();
364 if ((output.getExitValue() == 1)) {
365 if (outStr.contains(MSG_RANGE_NOT_WITHIN_HEAP)) {
366 return MSG_RANGE_NOT_WITHIN_HEAP;
367 }
368 if (outStr.contains(MSG_DYNAMIC_NOT_SUPPORTED)) {
369 return MSG_DYNAMIC_NOT_SUPPORTED;
370 }
371 }
372
373 return null;
374 }
375
376 public static boolean isUnableToMap(OutputAnalyzer output) {
377 return hasUnableToMapMessage(output) != null;
378 }
379
380 public static void checkMappingFailure(OutputAnalyzer out) throws SkippedException {
381 String match = hasUnableToMapMessage(out);
382 if (match != null) {
383 throw new SkippedException(UnableToMapMsg + ": " + match);
384 }
385 }
386
387 public static Result run(String... cliPrefix) throws Exception {
388 CDSOptions opts = new CDSOptions();
389 opts.setArchiveName(getDefaultArchiveName());
390 opts.addPrefix(cliPrefix);
391 return new Result(opts, runWithArchive(opts));
392 }
393
394 public static Result run(CDSOptions opts) throws Exception {
395 return new Result(opts, runWithArchive(opts));
396 }
397
398 // Dump a classlist using the -XX:DumpLoadedClassList option.
399 public static Result dumpClassList(String classListName, String... cli)
400 throws Exception {
401 CDSOptions opts = (new CDSOptions())
402 .setUseVersion(false)
403 .setXShareMode("auto")
404 .addPrefix("-XX:DumpLoadedClassList=" + classListName)
405 .addSuffix(cli);
406 Result res = run(opts).assertNormalExit();
407 return res;
408 }
409
410 // Execute JVM with CDS archive, specify command line args suffix
411 public static OutputAnalyzer runWithArchive(String... cliPrefix)
412 throws Exception {
413
414 return runWithArchive( (new CDSOptions())
415 .setArchiveName(getDefaultArchiveName())
416 .addPrefix(cliPrefix) );
417 }
418
419 // Enable basic verification (VerifyArchivedFields=1, no side effects) for all CDS
420 // tests to make sure the archived heap objects are mapped/loaded properly.
421 public static void addVerifyArchivedFields(ArrayList<String> cmd) {
422 cmd.add("-XX:+UnlockDiagnosticVMOptions");
423 cmd.add("-XX:VerifyArchivedFields=1");
424 }
425
426 // Execute JVM with CDS archive, specify CDSOptions
427 public static OutputAnalyzer runWithArchive(CDSOptions opts)
428 throws Exception {
429
430 ArrayList<String> cmd = new ArrayList<String>();
431 cmd.addAll(opts.prefix);
432 cmd.add("-Xshare:" + opts.xShareMode);
433 cmd.add("-Dtest.timeout.factor=" + Utils.TIMEOUT_FACTOR);
434
435 if (!opts.useSystemArchive) {
436 if (opts.archiveName == null)
437 opts.archiveName = getDefaultArchiveName();
438 cmd.add("-XX:SharedArchiveFile=" + opts.archiveName);
439 }
440
441 if (opts.useVersion)
442 cmd.add("-version");
443
444 for (String s : opts.suffix) cmd.add(s);
445
446 String[] cmdLine = cmd.toArray(new String[cmd.size()]);
447 ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(cmdLine);
448 return executeAndLog(pb, "exec");
449 }
450
451
452 // A commonly used convenience methods to create an archive and check the results
453 // Creates an archive and checks for errors
454 public static OutputAnalyzer runWithArchiveAndCheck(CDSOptions opts) throws Exception {
455 return checkExec(runWithArchive(opts));
456 }
457
458
459 public static OutputAnalyzer runWithArchiveAndCheck(String... cliPrefix) throws Exception {
460 return checkExec(runWithArchive(cliPrefix));
461 }
462
463
464 public static OutputAnalyzer checkExec(OutputAnalyzer output,
465 String... extraMatches) throws Exception {
466 CDSOptions opts = new CDSOptions();
467 return checkExec(output, opts, extraMatches);
468 }
469
470
471 // check result of 'exec' operation, that is when JVM is run using the archive
472 public static OutputAnalyzer checkExec(OutputAnalyzer output, CDSOptions opts,
473 String... extraMatches) throws Exception {
474 try {
475 if ("on".equals(opts.xShareMode)) {
476 output.shouldContain("sharing");
477 }
478 output.shouldHaveExitValue(0);
479 } catch (RuntimeException e) {
480 checkCommonExecExceptions(output, e);
481 return output;
482 }
483
484 checkMatches(output, extraMatches);
485 return output;
486 }
487
488
489 public static OutputAnalyzer checkExecExpectError(OutputAnalyzer output,
490 int expectedExitValue,
491 String... extraMatches) throws Exception {
492 checkMappingFailure(output);
493 output.shouldHaveExitValue(expectedExitValue);
494 checkMatches(output, extraMatches);
495 return output;
496 }
497
498 public static OutputAnalyzer checkMatches(OutputAnalyzer output,
499 String... matches) throws Exception {
500 for (String match : matches) {
501 output.shouldContain(match);
502 }
503 return output;
504 }
505
506 private static final String outputDir;
507 private static final File outputDirAsFile;
508
509 static {
510 outputDir = System.getProperty("user.dir", ".");
511 outputDirAsFile = new File(outputDir);
512 }
513
514 public static String getOutputDir() {
515 return outputDir;
516 }
517
518 public static File getOutputDirAsFile() {
519 return outputDirAsFile;
520 }
521
522 // get the file object for the test artifact
523 public static File getTestArtifact(String name, boolean checkExistence) {
524 File file = new File(outputDirAsFile, name);
525
526 if (checkExistence && !file.exists()) {
527 throw new RuntimeException("Cannot find " + file.getPath());
528 }
529
530 return file;
531 }
532
533
534 // create file containing the specified class list
535 public static File makeClassList(String classes[])
536 throws Exception {
537 return makeClassList(testName + "-", classes);
538 }
539
540 // create file containing the specified class list
541 public static File makeClassList(String testCaseName, String classes[])
542 throws Exception {
543
544 File classList = getTestArtifact(testCaseName + "test.classlist", false);
545 FileOutputStream fos = new FileOutputStream(classList);
546 PrintStream ps = new PrintStream(fos);
547
548 addToClassList(ps, classes);
549
550 ps.close();
551 fos.close();
552
553 return classList;
554 }
555
556
557 public static void addToClassList(PrintStream ps, String classes[])
558 throws IOException
559 {
560 if (classes != null) {
561 for (String s : classes) {
562 ps.println(s);
563 }
564 }
565 }
566
567 private static String testName = Utils.TEST_NAME.replace('/', '.');
568
569 private static final SimpleDateFormat timeStampFormat =
570 new SimpleDateFormat("HH'h'mm'm'ss's'SSS");
571
572 private static String defaultArchiveName;
573
574 // Call this method to start new archive with new unique name
575 public static void startNewArchiveName() {
576 defaultArchiveName = testName +
577 timeStampFormat.format(new Date()) + ".jsa";
578 }
579
580 public static String getDefaultArchiveName() {
581 return defaultArchiveName;
582 }
583
584
585 // ===================== FILE ACCESS convenience methods
586 public static File getOutputFile(String name) {
587 return new File(outputDirAsFile, testName + "-" + name);
588 }
589
590 public static String getOutputFileName(String name) {
591 return getOutputFile(name).getName();
592 }
593
594
595 public static File getOutputSourceFile(String name) {
596 return new File(outputDirAsFile, name);
597 }
598
599
600 public static File getSourceFile(String name) {
601 File dir = new File(System.getProperty("test.src", "."));
602 return new File(dir, name);
603 }
604
605 // Check commandline for the last instance of Xshare to see if the process can load
606 // a CDS archive
607 public static boolean isRunningWithArchive(List<String> cmd) {
608 // -Xshare only works for the java executable
609 if (!cmd.get(0).equals(JDKToolFinder.getJDKTool("java")) || cmd.size() < 2) {
610 return false;
611 }
612
613 // -Xshare options are likely at the end of the args list
614 for (int i = cmd.size() - 1; i >= 1; i--) {
615 String s = cmd.get(i);
616 if (s.equals("-Xshare:dump") || s.equals("-Xshare:off")) {
617 return false;
618 }
619 }
620 return true;
621 }
622
623 public static boolean isGCOption(String s) {
624 return s.startsWith("-XX:+Use") && s.endsWith("GC");
625 }
626
627 public static boolean hasGCOption(List<String> cmd) {
628 for (String s : cmd) {
629 if (isGCOption(s)) {
630 return true;
631 }
632 }
633 return false;
634 }
635
636 // Handle and insert test.cds.runtime.options to commandline
637 // The test.cds.runtime.options property is used to inject extra VM options to
638 // subprocesses launched by the CDS test cases using executeAndLog().
639 // The injection applies only to subprocesses that:
640 // - are launched by the standard java launcher (bin/java)
641 // - are not dumping the CDS archive with -Xshare:dump
642 // - do not explicitly disable CDS via -Xshare:off
643 //
644 // The main purpose of this property is to test the runtime loading of
645 // the CDS "archive heap region" with non-default garbage collectors. E.g.,
646 //
647 // jtreg -vmoptions:-Dtest.cds.runtime.options=-XX:+UnlockExperimentalVMOptions,-XX:+UseEpsilonGC \
648 // test/hotspot/jtreg/runtime/cds
649 //
650 // Note that the injection is not applied to -Xshare:dump, so that the CDS archives
651 // will be dumped with G1, which is the only collector that supports dumping
652 // the archive heap region. Similarly, if a UseXxxGC option already exists in the command line,
653 // the UseXxxGC option added in test.cds.runtime.options will be ignored.
654 public static void handleCDSRuntimeOptions(ProcessBuilder pb) {
655 List<String> cmd = pb.command();
656 String jtropts = System.getProperty("test.cds.runtime.options");
657 if (jtropts != null && isRunningWithArchive(cmd)) {
658 // There cannot be multiple GC options in the command line so some
659 // options may be ignored
660 ArrayList<String> cdsRuntimeOpts = new ArrayList<String>();
661 boolean hasGCOption = hasGCOption(cmd);
662 for (String s : jtropts.split(",")) {
663 if (!CDSOptions.disabledRuntimePrefixes.contains(s) &&
664 !(hasGCOption && isGCOption(s))) {
665 cdsRuntimeOpts.add(s);
666 }
667 }
668 pb.command().addAll(1, cdsRuntimeOpts);
669 }
670 }
671
672 // ============================= Logging
673 public static OutputAnalyzer executeAndLog(ProcessBuilder pb, String logName) throws Exception {
674 handleCDSRuntimeOptions(pb);
675 return executeAndLog(pb.start(), logName);
676 }
677
678 public static OutputAnalyzer executeAndLog(Process process, String logName) throws Exception {
679 long started = System.currentTimeMillis();
680
681 OutputAnalyzer output = new OutputAnalyzer(process);
682 String logFileNameStem =
683 String.format("%04d", getNextLogCounter()) + "-" + logName;
684
685 File stdout = getOutputFile(logFileNameStem + ".stdout");
686 File stderr = getOutputFile(logFileNameStem + ".stderr");
687
688 writeFile(stdout, output.getStdout());
689 writeFile(stderr, output.getStderr());
690 System.out.println("[ELAPSED: " + (System.currentTimeMillis() - started) + " ms]");
691 System.out.println("[logging stdout to " + stdout + "]");
692 System.out.println("[logging stderr to " + stderr + "]");
693 System.out.println("[STDERR]\n" + output.getStderr());
694
695 if (copyChildStdoutToMainStdout)
696 System.out.println("[STDOUT]\n" + output.getStdout());
697
698 if (output.getExitValue() != 0 && output.getStdout().contains("A fatal error has been detected")) {
699 throw new RuntimeException("Hotspot crashed");
700 }
701 return output;
702 }
703
704
705 private static void writeFile(File file, String content) throws Exception {
706 FileOutputStream fos = new FileOutputStream(file);
707 PrintStream ps = new PrintStream(fos);
708 ps.print(content);
709 ps.close();
710 fos.close();
711 }
712
713 // Format a line that defines an extra symbol in the file specify by -XX:SharedArchiveConfigFile=<file>
714 public static String formatArchiveConfigSymbol(String symbol) {
715 int refCount = -1; // This is always -1 in the current HotSpot implementation.
716 if (isAsciiPrintable(symbol)) {
717 return symbol.length() + " " + refCount + ": " + symbol;
718 } else {
719 StringBuilder sb = new StringBuilder();
720 int utf8_length = escapeArchiveConfigString(sb, symbol);
721 return utf8_length + " " + refCount + ": " + sb.toString();
722 }
723 }
724
725 // This method generates the same format as HashtableTextDump::put_utf8() in HotSpot,
726 // to be used by -XX:SharedArchiveConfigFile=<file>.
727 private static int escapeArchiveConfigString(StringBuilder sb, String s) {
728 byte arr[];
729 try {
730 arr = s.getBytes("UTF8");
731 } catch (java.io.UnsupportedEncodingException e) {
732 throw new RuntimeException("Unexpected", e);
733 }
734 for (int i = 0; i < arr.length; i++) {
735 char ch = (char)(arr[i] & 0xff);
736 if (isAsciiPrintable(ch)) {
737 sb.append(ch);
738 } else if (ch == '\t') {
739 sb.append("\\t");
740 } else if (ch == '\r') {
741 sb.append("\\r");
742 } else if (ch == '\n') {
743 sb.append("\\n");
744 } else if (ch == '\\') {
745 sb.append("\\\\");
746 } else {
747 String hex = Integer.toHexString(ch);
748 if (ch < 16) {
749 sb.append("\\x0");
750 } else {
751 sb.append("\\x");
752 }
753 sb.append(hex);
754 }
755 }
756
757 return arr.length;
758 }
759
760 private static boolean isAsciiPrintable(String s) {
761 for (int i = 0; i < s.length(); i++) {
762 if (!isAsciiPrintable(s.charAt(i))) {
763 return false;
764 }
765 }
766 return true;
767 }
768
769 private static boolean isAsciiPrintable(char ch) {
770 return ch >= 32 && ch < 127;
771 }
772
773 // JDK utility
774
775 // Do a cheap clone of the JDK. Most files can be sym-linked. However, $JAVA_HOME/bin/java and $JAVA_HOME/lib/.../libjvm.so"
776 // must be copied, because the java.home property is derived from the canonicalized paths of these 2 files.
777 // The jvm.cfg file must be copied because the cds/NonJVMVariantLocation.java
778 // test is testing a CDS archive can be loaded from a non-JVM variant directory.
779 // Set a list of {jvm, "java", "jvm.cfg"} which will be physically copied. If a file needs copied physically, add it to the list.
780 private static String[] phCopied = {System.mapLibraryName("jvm"), "java", "jvm.cfg"};
781 public static void clone(File src, File dst) throws Exception {
782 if (dst.exists()) {
783 if (!dst.isDirectory()) {
784 throw new RuntimeException("Not a directory :" + dst);
785 }
786 } else {
787 if (!dst.mkdir()) {
788 throw new RuntimeException("Cannot create directory: " + dst);
789 }
790 }
791 // final String jvmLib = System.mapLibraryName("jvm");
792 for (String child : src.list()) {
793 if (child.equals(".") || child.equals("..")) {
794 continue;
795 }
796
797 File child_src = new File(src, child);
798 File child_dst = new File(dst, child);
799 if (child_dst.exists()) {
800 throw new RuntimeException("Already exists: " + child_dst);
801 }
802 if (child_src.isFile()) {
803 boolean needPhCopy = false;
804 for (String target : phCopied) {
805 if (child.equals(target)) {
806 needPhCopy = true;
807 break;
808 }
809 }
810 if (needPhCopy) {
811 Files.copy(child_src.toPath(), /* copy data to -> */ child_dst.toPath(),
812 new CopyOption[] { StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES});
813 } else {
814 Files.createSymbolicLink(child_dst.toPath(), /* link to -> */ child_src.toPath());
815 }
816 } else {
817 clone(child_src, child_dst);
818 }
819 }
820 }
821
822 // modulesDir, like $JDK/lib
823 // oldName, module name under modulesDir
824 // newName, new name for oldName
825 public static void rename(File fromFile, File toFile) throws Exception {
826 if (!fromFile.exists()) {
827 throw new RuntimeException(fromFile.getName() + " does not exist");
828 }
829
830 if (toFile.exists()) {
831 throw new RuntimeException(toFile.getName() + " already exists");
832 }
833
834 boolean success = fromFile.renameTo(toFile);
835 if (!success) {
836 throw new RuntimeException("rename file " + fromFile.getName()+ " to " + toFile.getName() + " failed");
837 }
838 }
839
840 public static ProcessBuilder makeBuilder(String... args) throws Exception {
841 System.out.print("[");
842 for (String s : args) {
843 System.out.print(" " + s);
844 }
845 System.out.println(" ]");
846 return new ProcessBuilder(args);
847 }
848
849 public static Path copyFile(String srcFile, String destDir) throws Exception {
850 int idx = srcFile.lastIndexOf(File.separator);
851 String jarName = srcFile.substring(idx + 1);
852 Path srcPath = Paths.get(jarName);
853 Path newPath = Paths.get(destDir);
854 Path newDir;
855 if (!Files.exists(newPath)) {
856 newDir = Files.createDirectories(newPath);
857 } else {
858 newDir = newPath;
859 }
860 Path destPath = newDir.resolve(jarName);
861 Files.copy(srcPath, destPath, REPLACE_EXISTING, COPY_ATTRIBUTES);
862 return destPath;
863 }
864
865 // Some tests were initially written without the knowledge of -XX:+AOTClassLinking. These tests need to
866 // be adjusted if -XX:+AOTClassLinking is specified in jtreg -vmoptions or -javaoptions:
867 public static boolean isAOTClassLinkingEnabled() {
868 return isBooleanVMOptionEnabledInCommandLine("AOTClassLinking");
869 }
870
871 public static boolean isBooleanVMOptionEnabledInCommandLine(String optionName) {
872 String lastMatch = null;
873 String pattern = "^-XX:." + optionName + "$";
874 for (String s : Utils.getTestJavaOpts()) {
875 if (s.matches(pattern)) {
876 lastMatch = s;
877 }
878 }
879 if (lastMatch != null && lastMatch.equals("-XX:+" + optionName)) {
880 return true;
881 }
882 return false;
883 }
884 }