1 /*
  2  * Copyright (c) 2023, 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 
 24 package jdk.test.lib.cds;
 25 
 26 import java.io.File;
 27 import jdk.test.lib.cds.CDSTestUtils;
 28 import jdk.test.lib.process.ProcessTools;
 29 import jdk.test.lib.process.OutputAnalyzer;
 30 import jdk.test.lib.StringArrayUtils;
 31 import jdk.test.whitebox.WhiteBox;
 32 import jtreg.SkippedException;
 33 
 34 /*
 35  * This is a base class used for testing CDS functionalities with complex applications.
 36  * You can define the application by overridding the vmArgs(), classpath() and appCommandLine()
 37  * methods. Application-specific validation checks can be implemented with checkExecution().
 38  *
 39  * The AOT workflow runs with one-step training by default. For debugging purposes, run
 40  * jtreg with -vmoption:-DCDSAppTester.two.step.training=true. This will run -XX:AOTMode=record
 41  * and -XX:AOTMode=record in two separate processes that you can rerun easily inside a debugger.
 42  * Also, the log files are easier to read.
 43  */
 44 abstract public class CDSAppTester {
 45     private final String name;
 46     private final String classListFile;
 47     private final String classListFileLog;
 48     private final String aotConfigurationFile;
 49     private final String aotConfigurationFileLog;
 50     private final String staticArchiveFile;
 51     private final String staticArchiveFileLog;
 52     private final String aotCacheFile;
 53     private final String aotCacheFileLog;
 54     private final String dynamicArchiveFile;
 55     private final String dynamicArchiveFileLog;
 56     private final String cdsFile;        // new workflow: -XX:CacheDataStore=<foo>.cds
 57     private final String cdsFileLog;
 58     private final String cdsFilePreImage;        // new workflow: -XX:CacheDataStore=<foo>.cds
 59     private final String cdsFilePreImageLog;
 60     private final String tempBaseArchiveFile;
 61     private int numProductionRuns = 0;
 62     private String whiteBoxJar = null;
 63     private boolean inOneStepTraining = false;
 64 
 65     /**
 66      * All files created in the CDS/AOT workflow will be name + extension. E.g.
 67      * - name.aot
 68      * - name.aotconfig
 69      * - name.classlist
 70      * - name.jsa
 71      */
 72     public CDSAppTester(String name) {
 73         if (CDSTestUtils.DYNAMIC_DUMP) {
 74             throw new SkippedException("Tests based on CDSAppTester should be excluded when -Dtest.dynamic.cds.archive is specified");
 75         }
 76 
 77         this.name = name;
 78         classListFile = name() + ".classlist";
 79         classListFileLog = logFileName(classListFile);
 80         aotConfigurationFile = name() + ".aotconfig";
 81         aotConfigurationFileLog = logFileName(aotConfigurationFile);
 82         staticArchiveFile = name() + ".static.jsa";
 83         staticArchiveFileLog = logFileName(staticArchiveFile);
 84         aotCacheFile = name() + ".aot";
 85         aotCacheFileLog = logFileName(aotCacheFile);;
 86         dynamicArchiveFile = name() + ".dynamic.jsa";
 87         dynamicArchiveFileLog = logFileName(dynamicArchiveFile);
 88         cdsFile = name() + ".cds";
 89         cdsFileLog = logFileName(cdsFile);
 90         cdsFilePreImage = cdsFile + ".preimage";
 91         cdsFilePreImageLog = logFileName(cdsFilePreImage);
 92         tempBaseArchiveFile = name() + ".temp-base.jsa";
 93     }
 94 
 95     private String productionRunLog() {
 96         if (numProductionRuns == 0) {
 97             return logFileName(name() + ".production");
 98         } else {
 99             return logFileName(name() + ".production." + numProductionRuns);
100         }
101     }
102 
103     private static String logFileName(String file) {
104         file = file.replace("\"", "%22");
105         file = file.replace("'", "%27");
106         return file + ".log";
107     }
108 
109     private enum Workflow {
110         STATIC,        // classic -Xshare:dump workflow
111         DYNAMIC,       // classic -XX:ArchiveClassesAtExit
112         AOT,           // JEP 483 Ahead-of-Time Class Loading & Linking
113         LEYDEN,        // The new "one step training workflow" -- see JDK-8320264
114     }
115 
116     public enum RunMode {
117         TRAINING,       // -XX:DumpLoadedClassList OR {-XX:AOTMode=record -XX:AOTConfiguration}
118         TRAINING0,      // LEYDEN only
119         TRAINING1,      // LEYDEN only (assembly phase, app logic not executed)
120         DUMP_STATIC,    // -Xshare:dump
121         DUMP_DYNAMIC,   // -XX:ArchiveClassesArExit
122         ASSEMBLY,       // JEP 483 (assembly phase, app logic not executed)
123         PRODUCTION;     // Running with the CDS archive produced from the above steps
124 
125         public boolean isStaticDump() {
126             return this == DUMP_STATIC;
127         }
128         public boolean isProductionRun() {
129             return this == PRODUCTION;
130         }
131 
132         // When <code>CDSAppTester::checkExecution(out, runMode)</code> is called, has the application been
133         // executed? If so, <code>out</code> should contain logs printed by the application's own logic.
134         public boolean isApplicationExecuted() {
135             return (this != TRAINING1) && (this != ASSEMBLY) && (this != DUMP_STATIC);
136         }
137     }
138 
139     public boolean isDumping(RunMode runMode) {
140         if (isStaticWorkflow()) {
141             return runMode == RunMode.DUMP_STATIC;
142         } else if (isDynamicWorkflow()) {
143             return runMode == RunMode.DUMP_DYNAMIC;
144         } else if (isAOTWorkflow()) {
145             return runMode == RunMode.TRAINING || runMode == RunMode.ASSEMBLY;
146         } else {
147             return runMode == RunMode.TRAINING || runMode == RunMode.TRAINING0 || runMode == RunMode.TRAINING1;
148         }
149     }
150 
151     public final String name() {
152         return this.name;
153     }
154 
155     // optional
156     public String[] vmArgs(RunMode runMode) {
157         return new String[0];
158     }
159 
160     // optional
161     public String classpath(RunMode runMode) {
162         return null;
163     }
164 
165     // optional
166     public String modulepath(RunMode runMode) {
167         return null;
168     }
169 
170     // must override
171     // main class, followed by arguments to the main class
172     abstract public String[] appCommandLine(RunMode runMode);
173 
174     // optional
175     public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {}
176 
177     private Workflow workflow;
178     private boolean checkExitValue = true;
179 
180     public final void setCheckExitValue(boolean b) {
181         checkExitValue = b;
182     }
183 
184     public final void useWhiteBox(String whiteBoxJar) {
185         this.whiteBoxJar = whiteBoxJar;
186     }
187 
188     public final boolean isStaticWorkflow() {
189         return workflow == Workflow.STATIC;
190     }
191 
192     public final boolean isDynamicWorkflow() {
193         return workflow == Workflow.DYNAMIC;
194     }
195 
196     public final boolean isAOTWorkflow() {
197         return workflow == Workflow.AOT;
198     }
199 
200     public final boolean isLeydenWorkflow() {
201         return workflow == Workflow.LEYDEN;
202     }
203 
204     private String logToFile(String logFile, String... logTags) {
205         StringBuilder sb = new StringBuilder("-Xlog:arguments");
206         String prefix = ",";
207         for (String tag : logTags) {
208             sb.append(prefix);
209             sb.append(tag);
210         }
211         sb.append(":file=" + logFile + "::filesize=0");
212         return sb.toString();
213     }
214 
215     private void listOutputFile(String file) {
216         File f = new File(file);
217         if (f.exists()) {
218             System.out.println("[output file: " + file + " " + f.length() + " bytes]");
219         } else {
220             System.out.println("[output file: " + file + " does not exist]");
221         }
222     }
223 
224     private OutputAnalyzer executeAndCheck(String[] cmdLine, RunMode runMode, String... logFiles) throws Exception {
225         ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(cmdLine);
226         Process process = pb.start();
227         OutputAnalyzer output = CDSTestUtils.executeAndLog(process, runMode.toString());
228         for (String logFile : logFiles) {
229             listOutputFile(logFile);
230         }
231         if (checkExitValue) {
232             output.shouldHaveExitValue(0);
233         }
234         //output.shouldNotContain(CDSTestUtils.MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE); // FIXME -- leyden+JEP483 merge
235         CDSTestUtils.checkCommonExecExceptions(output);
236         checkExecution(output, runMode);
237         return output;
238     }
239 
240     private String[] addCommonVMArgs(RunMode runMode, String[] cmdLine) {
241         cmdLine = addClassOrModulePath(runMode, cmdLine);
242         cmdLine = addWhiteBox(cmdLine);
243         return cmdLine;
244     }
245 
246     private String[] addClassOrModulePath(RunMode runMode, String[] cmdLine) {
247         String cp = classpath(runMode);
248         if (cp == null) {
249             // Override the "-cp ...." added by Jtreg
250             cmdLine = StringArrayUtils.concat(cmdLine, "-Djava.class.path=");
251         } else {
252             cmdLine = StringArrayUtils.concat(cmdLine, "-cp", cp);
253         }
254         String mp = modulepath(runMode);
255         if (mp != null) {
256             cmdLine = StringArrayUtils.concat(cmdLine, "--module-path", mp);
257         }
258         return cmdLine;
259     }
260 
261     private String[] addWhiteBox(String[] cmdLine) {
262         if (whiteBoxJar != null) {
263             cmdLine = StringArrayUtils.concat(cmdLine,
264                                               "-XX:+UnlockDiagnosticVMOptions",
265                                               "-XX:+WhiteBoxAPI",
266                                               "-Xbootclasspath/a:" + whiteBoxJar);
267         }
268         return cmdLine;
269     }
270 
271     private OutputAnalyzer recordAOTConfiguration() throws Exception {
272         RunMode runMode = RunMode.TRAINING;
273         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
274                                                    "-XX:AOTMode=record",
275                                                    "-XX:AOTConfiguration=" + aotConfigurationFile,
276                                                    logToFile(aotConfigurationFileLog,
277                                                              "class+load=debug",
278                                                              "aot=debug",
279                                                              "aot+class=debug"));
280         cmdLine = addCommonVMArgs(runMode, cmdLine);
281         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
282         return executeAndCheck(cmdLine, runMode, aotConfigurationFile, aotConfigurationFileLog);
283     }
284 
285     private OutputAnalyzer createAOTCacheOneStep() throws Exception {
286         RunMode runMode = RunMode.TRAINING;
287         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
288                                                    "-XX:AOTMode=record",
289                                                    "-XX:AOTCacheOutput=" + aotCacheFile,
290                                                    logToFile(aotCacheFileLog,
291                                                              "class+load=debug",
292                                                              "aot=debug",
293                                                              "aot+class=debug"));
294         cmdLine = addCommonVMArgs(runMode, cmdLine);
295         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
296         OutputAnalyzer out =  executeAndCheck(cmdLine, runMode, aotCacheFile, aotCacheFileLog);
297         listOutputFile(aotCacheFileLog + ".0"); // the log file for the training run
298         return out;
299     }
300 
301     private OutputAnalyzer createClassList() throws Exception {
302         RunMode runMode = RunMode.TRAINING;
303         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
304                                                    "-Xshare:off",
305                                                    "-XX:DumpLoadedClassList=" + classListFile,
306                                                    logToFile(classListFileLog,
307                                                              "class+load=debug"));
308         cmdLine = addCommonVMArgs(runMode, cmdLine);
309         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
310         return executeAndCheck(cmdLine, runMode, classListFile, classListFileLog);
311     }
312 
313     private OutputAnalyzer dumpStaticArchive() throws Exception {
314         RunMode runMode = RunMode.DUMP_STATIC;
315         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
316                                                    "-Xlog:cds",
317                                                    "-Xlog:cds+heap=error",
318                                                    "-Xshare:dump",
319                                                    "-XX:SharedArchiveFile=" + staticArchiveFile,
320                                                    "-XX:SharedClassListFile=" + classListFile,
321                                                    logToFile(staticArchiveFileLog,
322                                                              "cds=debug",
323                                                              "cds+class=debug",
324                                                              "cds+heap=warning",
325                                                              "cds+resolve=debug"));
326         cmdLine = addCommonVMArgs(runMode, cmdLine);
327         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
328         return executeAndCheck(cmdLine, runMode, staticArchiveFile, staticArchiveFileLog);
329     }
330 
331     private OutputAnalyzer createAOTCache() throws Exception {
332         RunMode runMode = RunMode.ASSEMBLY;
333         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
334                                                    "-Xlog:aot",
335                                                    "-Xlog:aot+heap=error",
336                                                    "-XX:AOTMode=create",
337                                                    "-XX:AOTConfiguration=" + aotConfigurationFile,
338                                                    "-XX:AOTCache=" + aotCacheFile,
339                                                    logToFile(aotCacheFileLog,
340                                                              "aot=debug",
341                                                              "aot+class=debug",
342                                                              "aot+heap=warning",
343                                                              "aot+resolve=debug"));
344         cmdLine = addCommonVMArgs(runMode, cmdLine);
345         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
346         return executeAndCheck(cmdLine, runMode, aotCacheFile, aotCacheFileLog);
347     }
348 
349     // Creating a dynamic CDS archive (with -XX:ArchiveClassesAtExit=<foo>.jsa) requires that the current
350     // JVM process is using a static archive (which is usually the default CDS archive included in the JDK).
351     // However, if the JDK doesn't include a default CDS archive that's compatible with the set of
352     // VM options used by this test, we need to create a temporary static archive to be used with -XX:ArchiveClassesAtExit.
353     private String getBaseArchiveForDynamicArchive() throws Exception {
354         WhiteBox wb = WhiteBox.getWhiteBox();
355         if (wb.isSharingEnabled()) {
356             // This current JVM is able to use a default CDS archive included by the JDK, so
357             // if we launch a JVM child process (with the same set of options as the current JVM),
358             // that process is also able to use the same default CDS archive for creating
359             // a dynamic archive.
360             return null;
361         } else {
362             // This current JVM is unable to use a default CDS archive, so let's create a temporary
363             // static archive to be used with -XX:ArchiveClassesAtExit.
364             File f = new File(tempBaseArchiveFile);
365             if (!f.exists()) {
366                 CDSOptions opts = new CDSOptions();
367                 opts.setArchiveName(tempBaseArchiveFile);
368                 opts.addSuffix("-Djava.class.path=");
369                 OutputAnalyzer out = CDSTestUtils.createArchive(opts);
370                 CDSTestUtils.checkBaseDump(out);
371             }
372             return tempBaseArchiveFile;
373         }
374     }
375 
376     private OutputAnalyzer dumpDynamicArchive() throws Exception {
377         RunMode runMode = RunMode.DUMP_DYNAMIC;
378         String[] cmdLine = new String[0];
379         String baseArchive = getBaseArchiveForDynamicArchive();
380         if (isDynamicWorkflow()) {
381           // "classic" dynamic archive
382           cmdLine = StringArrayUtils.concat(vmArgs(runMode),
383                                             "-Xlog:aot",
384                                             "-Xlog:cds",
385                                             "-XX:ArchiveClassesAtExit=" + dynamicArchiveFile,
386                                             logToFile(dynamicArchiveFileLog,
387                                                       "cds=debug",
388                                                       "cds+class=debug",
389                                                       "cds+resolve=debug",
390                                                       "class+load=debug"));
391           cmdLine = addCommonVMArgs(runMode, cmdLine);
392         }
393         if (baseArchive != null) {
394             cmdLine = StringArrayUtils.concat(cmdLine, "-XX:SharedArchiveFile=" + baseArchive);
395         }
396         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
397         return executeAndCheck(cmdLine, runMode, dynamicArchiveFile, dynamicArchiveFileLog);
398     }
399 
400     private String trainingLog(String file) {
401         return logToFile(file,
402                          "cds=debug",
403                          "cds+class=debug",
404                          "cds+heap=warning",
405                          "cds+resolve=debug");
406     }
407 
408     // normal training workflow (main JVM process spawns child process)
409     private OutputAnalyzer trainingRun() throws Exception {
410         RunMode runMode = RunMode.TRAINING;
411         File f = new File(cdsFile);
412         f.delete();
413         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
414                                                    "-XX:+AOTClassLinking",
415                                                    "-XX:+ArchiveDynamicProxies",
416                                                  //"-XX:+ArchiveReflectionData",
417                                                    "-XX:CacheDataStore=" + cdsFile,
418                                                    "-cp", classpath(runMode),
419                                                    // Use PID to distinguish the logs of the training process
420                                                    // and the forked final image dump process.
421                                                    "-Xlog:cds::uptime,level,tags,pid",
422                                                    trainingLog(cdsFileLog));
423         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
424         OutputAnalyzer out =  executeAndCheck(cmdLine, runMode, cdsFile, cdsFileLog);
425         listOutputFile(cdsFileLog + ".0"); // the preimage dump
426         return out;
427     }
428 
429     // "split" training workflow (launch the two processes manually, for easier debugging);
430     private OutputAnalyzer trainingRun0() throws Exception {
431         RunMode runMode = RunMode.TRAINING0;
432         File f = new File(cdsFile);
433         f.delete();
434         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
435                                                    "-XX:+UnlockDiagnosticVMOptions",
436                                                    "-XX:+CDSManualFinalImage",
437                                                    "-XX:+AOTClassLinking",
438                                                    "-XX:+ArchiveDynamicProxies",
439                                                  //"-XX:+ArchiveReflectionData",
440                                                    "-XX:CacheDataStore=" + cdsFile,
441                                                    "-cp", classpath(runMode),
442                                                    trainingLog(cdsFilePreImageLog));
443         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
444         return executeAndCheck(cmdLine, runMode, cdsFilePreImage, cdsFilePreImageLog);
445     }
446     private OutputAnalyzer trainingRun1() throws Exception {
447         RunMode runMode = RunMode.TRAINING1;
448         File f = new File(cdsFile);
449         f.delete();
450         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
451                                                    "-XX:+UnlockDiagnosticVMOptions",
452                                                    "-XX:+AOTClassLinking",
453                                                    "-XX:+ArchiveDynamicProxies",
454                                                  //"-XX:+ArchiveReflectionData",
455                                                    "-XX:CacheDataStore=" + cdsFile,
456                                                    "-XX:CDSPreimage=" + cdsFilePreImage,
457                                                    "-cp", classpath(runMode),
458                                                    trainingLog(cdsFileLog));
459         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
460         return executeAndCheck(cmdLine, runMode, cdsFile, cdsFileLog);
461     }
462 
463     public OutputAnalyzer productionRun() throws Exception {
464         return productionRun(null, null);
465     }
466 
467     public OutputAnalyzer productionRun(String[] extraVmArgs) throws Exception {
468         return productionRun(extraVmArgs, null);
469     }
470 
471     // After calling run(String[]), you can call this method to run the app again, with the AOTCache
472     // using different args to the VM and application.
473     public OutputAnalyzer productionRun(String[] extraVmArgs, String[] extraAppArgs) throws Exception {
474         RunMode runMode = RunMode.PRODUCTION;
475         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
476                                                    "-XX:+UnlockDiagnosticVMOptions",
477                                                    "-XX:VerifyArchivedFields=2", // make sure archived heap objects are good.
478                                                    logToFile(productionRunLog(), "aot", "cds"));
479         cmdLine = addCommonVMArgs(runMode, cmdLine);
480 
481         if (isStaticWorkflow()) {
482             cmdLine = StringArrayUtils.concat(cmdLine, "-Xshare:on", "-XX:SharedArchiveFile=" + staticArchiveFile);
483         } else if (isDynamicWorkflow()) {
484             cmdLine = StringArrayUtils.concat(cmdLine, "-Xshare:on", "-XX:SharedArchiveFile=" + dynamicArchiveFile);
485        } else if (isAOTWorkflow()) {
486             cmdLine = StringArrayUtils.concat(cmdLine, "-XX:AOTMode=on", "-XX:AOTCache=" + aotCacheFile);
487         } else {
488             cmdLine = StringArrayUtils.concat(cmdLine, "-XX:CacheDataStore=" + cdsFile);
489         }
490 
491         if (extraVmArgs != null) {
492             cmdLine = StringArrayUtils.concat(cmdLine, extraVmArgs);
493         }
494 
495         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
496 
497         if (extraAppArgs != null) {
498             cmdLine = StringArrayUtils.concat(cmdLine, extraAppArgs);
499         }
500 
501         OutputAnalyzer out = executeAndCheck(cmdLine, runMode, productionRunLog());
502         numProductionRuns ++;
503         return out;
504     }
505 
506     public void run(String... args) throws Exception {
507         String err = "Must have at least one command line argument of the following: ";
508         String prefix = "";
509         for (Workflow wf : Workflow.values()) {
510             err += prefix;
511             err += wf;
512             prefix = ", ";
513         }
514         if (args.length < 1) {
515             throw new RuntimeException(err);
516         } else {
517             if (args[0].equals("STATIC")) {
518                 runStaticWorkflow();
519             } else if (args[0].equals("DYNAMIC")) {
520                 runDynamicWorkflow();
521             } else if (args[0].equals("AOT")) {
522                 runAOTWorkflow(args);
523             } else if (args[0].equals("LEYDEN")) {
524                 runLeydenWorkflow(false);
525             } else if (args[0].equals("LEYDEN_TRAINONLY")) {
526                 runLeydenWorkflow(true);
527             } else {
528                 throw new RuntimeException(err);
529             }
530         }
531     }
532 
533     public void runStaticWorkflow() throws Exception {
534         this.workflow = Workflow.STATIC;
535         createClassList();
536         dumpStaticArchive();
537         productionRun();
538     }
539 
540     public void runDynamicWorkflow() throws Exception {
541         this.workflow = Workflow.DYNAMIC;
542         dumpDynamicArchive();
543         productionRun();
544     }
545 
546     // See JEP 483; stop at the assembly run; do not execute production run
547     public void runAOTAssemblyWorkflow() throws Exception {
548         this.workflow = Workflow.AOT;
549         recordAOTConfiguration();
550         createAOTCache();
551     }
552 
553     // See JEP 483
554     public void runAOTWorkflow(String... args) throws Exception {
555         this.workflow = Workflow.AOT;
556         boolean oneStepTraining = true; // by default use onestep trainning
557 
558         if (System.getProperty("CDSAppTester.two.step.training") != null) {
559             oneStepTraining = false;
560         }
561 
562         if (args.length > 1) {
563             // Tests such as test/hotspot/jtreg/runtime/cds/appcds/aotCache/SpecialCacheNames.java
564             // use --one-step-training or --two-step-training to force a certain training workflow.
565             if (args[1].equals("--one-step-training")) {
566                 oneStepTraining = true;
567             } else if (args[1].equals("--two-step-training")) {
568                 oneStepTraining = false;
569             } else {
570                 throw new RuntimeException("Unknown option: " + args[1]);
571             }
572         }
573 
574         if (oneStepTraining) {
575             try {
576                 inOneStepTraining = true;
577                 createAOTCacheOneStep();
578             } finally {
579                 inOneStepTraining = false;
580             }
581         } else {
582             recordAOTConfiguration();
583             createAOTCache();
584         }
585         productionRun();
586     }
587 
588     private void runLeydenWorkflow(boolean trainOnly) throws Exception {
589         this.workflow = Workflow.LEYDEN;
590         if (System.getProperty("CDSAppTester.split.new.workflow") != null) {
591             trainingRun0();
592             trainingRun1();
593         } else {
594             trainingRun();
595         }
596         if (!trainOnly) {
597             productionRun();
598         }
599     }
600 }