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  * Note: to debug the new workflow, run jtreg with -vmoption:-DCDSAppTester.split.new.workflow=true
 40  * This will run the new workflow in two separate processes that you can rerun easily inside a debugger.
 41  * Also, the log files are easier to read.
 42 */
 43 abstract public class CDSAppTester {
 44     private final String name;
 45     private final String classListFile;
 46     private final String classListFileLog;
 47     private final String aotConfigurationFile;
 48     private final String aotConfigurationFileLog;
 49     private final String staticArchiveFile;
 50     private final String staticArchiveFileLog;
 51     private final String aotCacheFile;
 52     private final String aotCacheFileLog;
 53     private final String dynamicArchiveFile;
 54     private final String dynamicArchiveFileLog;
 55     private final String cdsFile;        // new workflow: -XX:CacheDataStore=<foo>.cds
 56     private final String cdsFileLog;
 57     private final String cdsFilePreImage;        // new workflow: -XX:CacheDataStore=<foo>.cds
 58     private final String cdsFilePreImageLog;
 59     private final String tempBaseArchiveFile;
 60     private int numProductionRuns = 0;
 61     private String whiteBoxJar = null;
 62     private boolean inOneStepTraining = false;
 63 
 64     /**
 65      * All files created in the CDS/AOT workflow will be name + extension. E.g.
 66      * - name.aot
 67      * - name.aotconfig
 68      * - name.classlist
 69      * - name.jsa
 70      */
 71     public CDSAppTester(String name) {
 72         if (CDSTestUtils.DYNAMIC_DUMP) {
 73             throw new SkippedException("Tests based on CDSAppTester should be excluded when -Dtest.dynamic.cds.archive is specified");
 74         }
 75 
 76         this.name = name;
 77         classListFile = name() + ".classlist";
 78         classListFileLog = logFileName(classListFile);
 79         aotConfigurationFile = name() + ".aotconfig";
 80         aotConfigurationFileLog = logFileName(aotConfigurationFile);
 81         staticArchiveFile = name() + ".static.jsa";
 82         staticArchiveFileLog = logFileName(staticArchiveFile);
 83         aotCacheFile = name() + ".aot";
 84         aotCacheFileLog = logFileName(aotCacheFile);;
 85         dynamicArchiveFile = name() + ".dynamic.jsa";
 86         dynamicArchiveFileLog = logFileName(dynamicArchiveFile);
 87         cdsFile = name() + ".cds";
 88         cdsFileLog = logFileName(cdsFile);
 89         cdsFilePreImage = cdsFile + ".preimage";
 90         cdsFilePreImageLog = logFileName(cdsFilePreImage);
 91         tempBaseArchiveFile = name() + ".temp-base.jsa";
 92     }
 93 
 94     private String productionRunLog() {
 95         if (numProductionRuns == 0) {
 96             return logFileName(name() + ".production");
 97         } else {
 98             return logFileName(name() + ".production." + numProductionRuns);
 99         }
100     }
101 
102     private static String logFileName(String file) {
103         file = file.replace("\"", "%22");
104         file = file.replace("'", "%27");
105         return file + ".log";
106     }
107 
108     private enum Workflow {
109         STATIC,        // classic -Xshare:dump workflow
110         DYNAMIC,       // classic -XX:ArchiveClassesAtExit
111         AOT,           // JEP 483 Ahead-of-Time Class Loading & Linking
112         LEYDEN,        // The new "one step training workflow" -- see JDK-8320264
113     }
114 
115     public enum RunMode {
116         TRAINING,       // -XX:DumpLoadedClassList OR {-XX:AOTMode=record -XX:AOTConfiguration}
117         TRAINING0,      // LEYDEN only
118         TRAINING1,      // LEYDEN only (assembly phase, app logic not executed)
119         DUMP_STATIC,    // -Xshare:dump
120         DUMP_DYNAMIC,   // -XX:ArchiveClassesArExit
121         ASSEMBLY,       // JEP 483 (assembly phase, app logic not executed)
122         PRODUCTION;     // Running with the CDS archive produced from the above steps
123 
124         public boolean isStaticDump() {
125             return this == DUMP_STATIC;
126         }
127         public boolean isProductionRun() {
128             return this == PRODUCTION;
129         }
130 
131         // When <code>CDSAppTester::checkExecution(out, runMode)</code> is called, has the application been
132         // executed? If so, <code>out</code> should contain logs printed by the application's own logic.
133         public boolean isApplicationExecuted() {
134             return (this != TRAINING1) && (this != ASSEMBLY) && (this != DUMP_STATIC);
135         }
136     }
137 
138     public boolean isDumping(RunMode runMode) {
139         if (isStaticWorkflow()) {
140             return runMode == RunMode.DUMP_STATIC;
141         } else if (isDynamicWorkflow()) {
142             return runMode == RunMode.DUMP_DYNAMIC;
143         } else if (isAOTWorkflow()) {
144             return runMode == RunMode.TRAINING || runMode == RunMode.ASSEMBLY;
145         } else {
146             return runMode == RunMode.TRAINING || runMode == RunMode.TRAINING0 || runMode == RunMode.TRAINING1;
147         }
148     }
149 
150     public final String name() {
151         return this.name;
152     }
153 
154     // optional
155     public String[] vmArgs(RunMode runMode) {
156         return new String[0];
157     }
158 
159     // optional
160     public String classpath(RunMode runMode) {
161         return null;
162     }
163 
164     // optional
165     public String modulepath(RunMode runMode) {
166         return null;
167     }
168 
169     // must override
170     // main class, followed by arguments to the main class
171     abstract public String[] appCommandLine(RunMode runMode);
172 
173     // optional
174     public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {}
175 
176     private Workflow workflow;
177     private boolean checkExitValue = true;
178 
179     public final void setCheckExitValue(boolean b) {
180         checkExitValue = b;
181     }
182 
183     public final void useWhiteBox(String whiteBoxJar) {
184         this.whiteBoxJar = whiteBoxJar;
185     }
186 
187     public final boolean isStaticWorkflow() {
188         return workflow == Workflow.STATIC;
189     }
190 
191     public final boolean isDynamicWorkflow() {
192         return workflow == Workflow.DYNAMIC;
193     }
194 
195     public final boolean isAOTWorkflow() {
196         return workflow == Workflow.AOT;
197     }
198 
199     public final boolean isLeydenWorkflow() {
200         return workflow == Workflow.LEYDEN;
201     }
202 
203     private String logToFile(String logFile, String... logTags) {
204         StringBuilder sb = new StringBuilder("-Xlog:");
205         String prefix = "";
206         for (String tag : logTags) {
207             sb.append(prefix);
208             sb.append(tag);
209             prefix = ",";
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                                                              "cds=debug",
279                                                              "cds+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                                                              "cds=debug",
293                                                              "cds+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:cds",
335                                                    "-Xlog:cds+heap=error",
336                                                    "-XX:AOTMode=create",
337                                                    "-XX:AOTConfiguration=" + aotConfigurationFile,
338                                                    "-XX:AOTCache=" + aotCacheFile,
339                                                    logToFile(aotCacheFileLog,
340                                                              "cds=debug",
341                                                              "cds+class=debug",
342                                                              "cds+heap=warning",
343                                                              "cds+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:cds",
384                                             "-XX:ArchiveClassesAtExit=" + dynamicArchiveFile,
385                                             logToFile(dynamicArchiveFileLog,
386                                                       "cds=debug",
387                                                       "cds+class=debug",
388                                                       "cds+resolve=debug",
389                                                       "class+load=debug"));
390           cmdLine = addCommonVMArgs(runMode, cmdLine);
391         }
392         if (baseArchive != null) {
393             cmdLine = StringArrayUtils.concat(cmdLine, "-XX:SharedArchiveFile=" + baseArchive);
394         }
395         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
396         return executeAndCheck(cmdLine, runMode, dynamicArchiveFile, dynamicArchiveFileLog);
397     }
398 
399     private String trainingLog(String file) {
400         return logToFile(file,
401                          "cds=debug",
402                          "cds+class=debug",
403                          "cds+heap=warning",
404                          "cds+resolve=debug");
405     }
406 
407     // normal training workflow (main JVM process spawns child process)
408     private OutputAnalyzer trainingRun() throws Exception {
409         RunMode runMode = RunMode.TRAINING;
410         File f = new File(cdsFile);
411         f.delete();
412         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
413                                                    "-XX:+AOTClassLinking",
414                                                    "-XX:+ArchiveDynamicProxies",
415                                                  //"-XX:+ArchiveReflectionData",
416                                                    "-XX:CacheDataStore=" + cdsFile,
417                                                    "-cp", classpath(runMode),
418                                                    // Use PID to distinguish the logs of the training process
419                                                    // and the forked final image dump process.
420                                                    "-Xlog:cds::uptime,level,tags,pid",
421                                                    trainingLog(cdsFileLog));
422         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
423         OutputAnalyzer out =  executeAndCheck(cmdLine, runMode, cdsFile, cdsFileLog);
424         listOutputFile(cdsFileLog + ".0"); // the preimage dump
425         return out;
426     }
427 
428     // "split" training workflow (launch the two processes manually, for easier debugging);
429     private OutputAnalyzer trainingRun0() throws Exception {
430         RunMode runMode = RunMode.TRAINING0;
431         File f = new File(cdsFile);
432         f.delete();
433         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
434                                                    "-XX:+UnlockDiagnosticVMOptions",
435                                                    "-XX:+CDSManualFinalImage",
436                                                    "-XX:+AOTClassLinking",
437                                                    "-XX:+ArchiveDynamicProxies",
438                                                  //"-XX:+ArchiveReflectionData",
439                                                    "-XX:CacheDataStore=" + cdsFile,
440                                                    "-cp", classpath(runMode),
441                                                    trainingLog(cdsFilePreImageLog));
442         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
443         return executeAndCheck(cmdLine, runMode, cdsFilePreImage, cdsFilePreImageLog);
444     }
445     private OutputAnalyzer trainingRun1() throws Exception {
446         RunMode runMode = RunMode.TRAINING1;
447         File f = new File(cdsFile);
448         f.delete();
449         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
450                                                    "-XX:+UnlockDiagnosticVMOptions",
451                                                    "-XX:+AOTClassLinking",
452                                                    "-XX:+ArchiveDynamicProxies",
453                                                  //"-XX:+ArchiveReflectionData",
454                                                    "-XX:CacheDataStore=" + cdsFile,
455                                                    "-XX:CDSPreimage=" + cdsFilePreImage,
456                                                    "-cp", classpath(runMode),
457                                                    trainingLog(cdsFileLog));
458         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
459         return executeAndCheck(cmdLine, runMode, cdsFile, cdsFileLog);
460     }
461 
462     private OutputAnalyzer productionRun() throws Exception {
463         return productionRun(null, null);
464     }
465 
466     public OutputAnalyzer productionRun(String[] extraVmArgs) throws Exception {
467         return productionRun(extraVmArgs, null);
468     }
469 
470     // After calling run(String[]), you can call this method to run the app again, with the AOTCache
471     // using different args to the VM and application.
472     public OutputAnalyzer productionRun(String[] extraVmArgs, String[] extraAppArgs) throws Exception {
473         RunMode runMode = RunMode.PRODUCTION;
474         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
475                                                    "-XX:+UnlockDiagnosticVMOptions",
476                                                    "-XX:VerifyArchivedFields=2", // make sure archived heap objects are good.
477                                                    logToFile(productionRunLog(), "cds"));
478         cmdLine = addCommonVMArgs(runMode, cmdLine);
479 
480         if (isStaticWorkflow()) {
481             cmdLine = StringArrayUtils.concat(cmdLine, "-Xshare:on", "-XX:SharedArchiveFile=" + staticArchiveFile);
482         } else if (isDynamicWorkflow()) {
483             cmdLine = StringArrayUtils.concat(cmdLine, "-Xshare:on", "-XX:SharedArchiveFile=" + dynamicArchiveFile);
484        } else if (isAOTWorkflow()) {
485             cmdLine = StringArrayUtils.concat(cmdLine, "-XX:AOTMode=on", "-XX:AOTCache=" + aotCacheFile);
486         } else {
487             cmdLine = StringArrayUtils.concat(cmdLine, "-XX:CacheDataStore=" + cdsFile);
488         }
489 
490         if (extraVmArgs != null) {
491             cmdLine = StringArrayUtils.concat(cmdLine, extraVmArgs);
492         }
493 
494         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
495 
496         if (extraAppArgs != null) {
497             cmdLine = StringArrayUtils.concat(cmdLine, extraAppArgs);
498         }
499 
500         OutputAnalyzer out = executeAndCheck(cmdLine, runMode, productionRunLog());
501         numProductionRuns ++;
502         return out;
503     }
504 
505     public void run(String... args) throws Exception {
506         String err = "Must have at least one command line argument of the following: ";
507         String prefix = "";
508         for (Workflow wf : Workflow.values()) {
509             err += prefix;
510             err += wf;
511             prefix = ", ";
512         }
513         if (args.length < 1) {
514             throw new RuntimeException(err);
515         } else {
516             if (args[0].equals("STATIC")) {
517                 runStaticWorkflow();
518             } else if (args[0].equals("DYNAMIC")) {
519                 runDynamicWorkflow();
520             } else if (args[0].equals("AOT")) {
521                 runAOTWorkflow(args);
522             } else if (args[0].equals("LEYDEN")) {
523                 runLeydenWorkflow(false);
524             } else if (args[0].equals("LEYDEN_TRAINONLY")) {
525                 runLeydenWorkflow(true);
526             } else {
527                 throw new RuntimeException(err);
528             }
529         }
530     }
531 
532     public void runStaticWorkflow() throws Exception {
533         this.workflow = Workflow.STATIC;
534         createClassList();
535         dumpStaticArchive();
536         productionRun();
537     }
538 
539     public void runDynamicWorkflow() throws Exception {
540         this.workflow = Workflow.DYNAMIC;
541         dumpDynamicArchive();
542         productionRun();
543     }
544 
545     // See JEP 483
546     public void runAOTWorkflow(String... args) throws Exception {
547         this.workflow = Workflow.AOT;
548         if (args.length > 1 && args[1].equals("--onestep-training")) {
549             try {
550                 inOneStepTraining = true;
551                 createAOTCacheOneStep();
552             } finally {
553                 inOneStepTraining = false;
554             }
555         } else {
556             recordAOTConfiguration();
557             createAOTCache();
558         }
559         productionRun();
560     }
561 
562     private void runLeydenWorkflow(boolean trainOnly) throws Exception {
563         this.workflow = Workflow.LEYDEN;
564         if (System.getProperty("CDSAppTester.split.new.workflow") != null) {
565             trainingRun0();
566             trainingRun1();
567         } else {
568             trainingRun();
569         }
570         if (!trainOnly) {
571             productionRun();
572         }
573     }
574 }