1 /*
  2  * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 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 
 32 /*
 33  * This is a base class used for testing CDS functionalities with complex applications.
 34  * You can define the application by overridding the vmArgs(), classpath() and appCommandLine()
 35  * methods. Application-specific validation checks can be implemented with checkExecution().
 36 */
 37 abstract public class CDSAppTester {
 38     private final String name;
 39     private final String classListFile;
 40     private final String classListFileLog;
 41     private final String staticArchiveFile;
 42     private final String staticArchiveFileLog;
 43     private final String dynamicArchiveFile;
 44     private final String dynamicArchiveFileLog;
 45     private final String productionRunLog;
 46 
 47     public CDSAppTester(String name) {
 48         // Old workflow
 49         this.name = name;
 50         classListFile = name() + ".classlist";
 51         classListFileLog = classListFile + ".log";
 52         staticArchiveFile = name() + ".static.jsa";
 53         staticArchiveFileLog = staticArchiveFile + ".log";
 54         dynamicArchiveFile = name() + ".dynamic.jsa";
 55         dynamicArchiveFileLog = dynamicArchiveFile + ".log";
 56         productionRunLog = name() + ".production.log";
 57     }
 58 
 59     private enum Workflow {
 60         STATIC,        // classic -Xshare:dump workflow
 61         DYNAMIC,       // classic -XX:ArchiveClassesAtExit
 62     }
 63 
 64     public enum RunMode {
 65         CLASSLIST,
 66         DUMP_STATIC,
 67         DUMP_DYNAMIC,
 68         PRODUCTION;
 69 
 70         public boolean isStaticDump() {
 71             return this == DUMP_STATIC;
 72         }
 73         public boolean isProductionRun() {
 74             return this == PRODUCTION;
 75         }
 76     }
 77 
 78     public final String name() {
 79         return this.name;
 80     }
 81 
 82     // optional
 83     public String[] vmArgs(RunMode runMode) {
 84         return new String[0];
 85     }
 86 
 87     // optional
 88     public String classpath(RunMode runMode) {
 89         return null;
 90     }
 91 
 92     // must override
 93     // main class, followed by arguments to the main class
 94     abstract public String[] appCommandLine(RunMode runMode);
 95 
 96     // optional
 97     public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {}
 98 
 99     private Workflow workflow;
100 
101     public final boolean isStaticWorkflow() {
102         return workflow == Workflow.STATIC;
103     }
104 
105     public final boolean isDynamicWorkflow() {
106         return workflow == Workflow.DYNAMIC;
107     }
108 
109     private String logToFile(String logFile, String... logTags) {
110         StringBuilder sb = new StringBuilder("-Xlog:");
111         String prefix = "";
112         for (String tag : logTags) {
113             sb.append(prefix);
114             sb.append(tag);
115             prefix = ",";
116         }
117         sb.append(":file=" + logFile + "::filesize=0");
118         return sb.toString();
119     }
120 
121     private void listOutputFile(String file) {
122         File f = new File(file);
123         if (f.exists()) {
124             System.out.println("[output file: " + file + " " + f.length() + " bytes]");
125         } else {
126             System.out.println("[output file: " + file + " does not exist]");
127         }
128     }
129 
130     private OutputAnalyzer executeAndCheck(String[] cmdLine, RunMode runMode, String... logFiles) throws Exception {
131         ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(cmdLine);
132         Process process = pb.start();
133         OutputAnalyzer output = CDSTestUtils.executeAndLog(process, runMode.toString());
134         for (String logFile : logFiles) {
135             listOutputFile(logFile);
136         }
137         output.shouldHaveExitValue(0);
138         CDSTestUtils.checkCommonExecExceptions(output);
139         checkExecution(output, runMode);
140         return output;
141     }
142 
143     private OutputAnalyzer createClassList() throws Exception {
144         RunMode runMode = RunMode.CLASSLIST;
145         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
146                                                    "-Xshare:off",
147                                                    "-XX:DumpLoadedClassList=" + classListFile,
148                                                    "-cp", classpath(runMode),
149                                                    logToFile(classListFileLog,
150                                                              "class+load=debug"));
151         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
152         return executeAndCheck(cmdLine, runMode, classListFile, classListFileLog);
153     }
154 
155     private OutputAnalyzer dumpStaticArchive() throws Exception {
156         RunMode runMode = RunMode.DUMP_STATIC;
157         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
158                                                    "-Xlog:cds",
159                                                    "-Xlog:cds+heap=error",
160                                                    "-Xshare:dump",
161                                                    "-XX:SharedArchiveFile=" + staticArchiveFile,
162                                                    "-XX:SharedClassListFile=" + classListFile,
163                                                    "-cp", classpath(runMode),
164                                                    logToFile(staticArchiveFileLog,
165                                                              "cds=debug",
166                                                              "cds+class=debug",
167                                                              "cds+heap=warning",
168                                                              "cds+resolve=debug"));
169         return executeAndCheck(cmdLine, runMode, staticArchiveFile, staticArchiveFileLog);
170     }
171 
172     private OutputAnalyzer dumpDynamicArchive() throws Exception {
173         RunMode runMode = RunMode.DUMP_DYNAMIC;
174         String[] cmdLine = new String[0];
175         if (isDynamicWorkflow()) {
176           // "classic" dynamic archive
177           cmdLine = StringArrayUtils.concat(vmArgs(runMode),
178                                             "-Xlog:cds",
179                                             "-XX:ArchiveClassesAtExit=" + dynamicArchiveFile,
180                                             "-cp", classpath(runMode),
181                                             logToFile(dynamicArchiveFileLog,
182                                                       "cds=debug",
183                                                       "cds+class=debug",
184                                                       "cds+resolve=debug",
185                                                       "class+load=debug"));
186         }
187         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
188         return executeAndCheck(cmdLine, runMode, dynamicArchiveFile, dynamicArchiveFileLog);
189     }
190 
191     private OutputAnalyzer productionRun() throws Exception {
192         RunMode runMode = RunMode.PRODUCTION;
193         String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
194                                                    "-cp", classpath(runMode),
195                                                    logToFile(productionRunLog, "cds"));
196 
197         if (isStaticWorkflow()) {
198             cmdLine = StringArrayUtils.concat(cmdLine, "-XX:SharedArchiveFile=" + staticArchiveFile);
199         } else if (isDynamicWorkflow()) {
200             cmdLine = StringArrayUtils.concat(cmdLine, "-XX:SharedArchiveFile=" + dynamicArchiveFile);
201         }
202 
203         cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
204         return executeAndCheck(cmdLine, runMode, productionRunLog);
205     }
206 
207     public void run(String args[]) throws Exception {
208         String err = "Must have exactly one command line argument of the following: ";
209         String prefix = "";
210         for (Workflow wf : Workflow.values()) {
211             err += prefix;
212             err += wf;
213             prefix = ", ";
214         }
215         if (args.length != 1) {
216             throw new RuntimeException(err);
217         } else {
218             if (args[0].equals("STATIC")) {
219                 runStaticWorkflow();
220             } else if (args[0].equals("DYNAMIC")) {
221                 runDynamicWorkflow();
222             } else {
223                 throw new RuntimeException(err);
224             }
225         }
226     }
227 
228     private void runStaticWorkflow() throws Exception {
229         this.workflow = Workflow.STATIC;
230         createClassList();
231         dumpStaticArchive();
232         productionRun();
233     }
234 
235     private void runDynamicWorkflow() throws Exception {
236         this.workflow = Workflow.DYNAMIC;
237         dumpDynamicArchive();
238         productionRun();
239     }
240 }