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 }