1 /* 2 * Copyright (c) 2018, 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 lib.jdb; 25 26 import jdk.test.lib.process.OutputAnalyzer; 27 28 import java.io.IOException; 29 import java.nio.file.Files; 30 import java.nio.file.Paths; 31 import java.util.Arrays; 32 import java.util.LinkedList; 33 import java.util.List; 34 import java.util.concurrent.TimeUnit; 35 import java.util.stream.Collectors; 36 37 public abstract class JdbTest { 38 39 public static class LaunchOptions { 40 public final String debuggeeClass; 41 public final List<String> debuggeeOptions = new LinkedList<>(); 42 public String sourceFilename; 43 44 public LaunchOptions(String debuggeeClass) { 45 this.debuggeeClass = debuggeeClass; 46 } 47 public LaunchOptions addDebuggeeOption(String option) { 48 debuggeeOptions.add(option); 49 return this; 50 } 51 public LaunchOptions addDebuggeeOptions(String[] options) { 52 debuggeeOptions.addAll(Arrays.asList(options)); 53 return this; 54 } 55 public LaunchOptions setSourceFilename(String name) { 56 sourceFilename = name; 57 return this; 58 } 59 } 60 61 public JdbTest(LaunchOptions launchOptions) { 62 this.launchOptions = launchOptions; 63 } 64 public JdbTest(String debuggeeClass) { 65 this(new LaunchOptions(debuggeeClass)); 66 } 67 68 // sourceFilename is used by setBreakpoints and redefineClass 69 public JdbTest(String debuggeeClass, String sourceFilename) { 70 this(new LaunchOptions(debuggeeClass).setSourceFilename(sourceFilename)); 71 } 72 73 protected Jdb jdb; 74 protected Debuggee debuggee; 75 private final LaunchOptions launchOptions; 76 77 // returns the whole jdb output as a string 78 public String getJdbOutput() { 79 return jdb == null ? "" : jdb.getJdbOutput(); 80 } 81 82 // returns the whole debuggee output as a string 83 public String getDebuggeeOutput() { 84 return debuggee == null ? "" : debuggee.getOutput(); 85 } 86 87 public void run() { 88 try { 89 setup(); 90 runCases(); 91 } catch (Throwable e) { 92 jdb.log("======================================="); 93 jdb.log("Exception thrown during test execution: " + e.getMessage()); 94 jdb.log("======================================="); 95 throw e; 96 } finally { 97 shutdown(); 98 } 99 } 100 101 protected void setup() { 102 /* run debuggee as: 103 java -agentlib:jdwp=transport=dt_socket,server=n,suspend=y <debuggeeClass> 104 it reports something like : Listening for transport dt_socket at address: 60810 105 after that connect jdb by: 106 jdb -connect com.sun.jdi.SocketAttach:port=60810 107 */ 108 // launch debuggee 109 debuggee = Debuggee.launcher(launchOptions.debuggeeClass) 110 .addOptions(launchOptions.debuggeeOptions) 111 .launch(); 112 113 // launch jdb 114 try { 115 jdb = new Jdb("-connect", "com.sun.jdi.SocketAttach:port=" + debuggee.getAddress()); 116 } catch (Throwable ex) { 117 // terminate debuggee if something went wrong 118 debuggee.shutdown(); 119 throw ex; 120 } 121 // wait while jdb is initialized 122 jdb.waitForPrompt(1, false); 123 } 124 125 protected abstract void runCases(); 126 127 protected void shutdown() { 128 if (jdb != null) { 129 jdb.shutdown(); 130 } 131 // shutdown debuggee 132 if (debuggee != null) { 133 if (!debuggee.waitFor(10, TimeUnit.SECONDS)) { 134 debuggee.shutdown(); 135 } 136 } 137 } 138 139 protected static final String lineSeparator = System.getProperty("line.separator"); 140 141 142 // Parses the specified source file for "@{id} breakpoint" tags and returns 143 // list of the line numbers containing the tag. 144 // Example: 145 // System.out.println("BP is here"); // @1 breakpoint 146 public static List<Integer> parseBreakpoints(String filePath, int id) { 147 final String pattern = "@" + id + " breakpoint"; 148 int lineNum = 1; 149 List<Integer> result = new LinkedList<>(); 150 try { 151 for (String line: Files.readAllLines(Paths.get(filePath))) { 152 if (line.contains(pattern)) { 153 result.add(lineNum); 154 } 155 lineNum++; 156 } 157 } catch (IOException ex) { 158 throw new RuntimeException("failed to parse " + filePath, ex); 159 } 160 return result; 161 } 162 163 // sets breakpoints to the lines parsed by {@code parseBreakpoints} 164 // returns number of the breakpoints set. 165 public static int setBreakpoints(Jdb jdb, String debuggeeClass, String sourcePath, int id) { 166 List<Integer> bps = parseBreakpoints(sourcePath, id); 167 for (int bp : bps) { 168 String reply = jdb.command(JdbCommand.stopAt(debuggeeClass, bp)).stream() 169 .collect(Collectors.joining("\n")); 170 if (reply.contains("Unable to set")) { 171 throw new RuntimeException("jdb failed to set breakpoint at " + debuggeeClass + ":" + bp); 172 } 173 174 } 175 return bps.size(); 176 } 177 178 // sets breakpoints to the lines parsed by {@code parseBreakpoints} 179 // from the file from test source directory. 180 // returns number of the breakpoints set. 181 protected int setBreakpointsFromTestSource(String debuggeeFileName, int id) { 182 return setBreakpoints(jdb, launchOptions.debuggeeClass, 183 getTestSourcePath(debuggeeFileName), id); 184 } 185 186 // sets breakpoints in the class {@code launchOptions.debuggeeClass} 187 // to the lines parsed by {@code parseBreakpoints} 188 // from the file from test source directory specified by {@code launchOptions.sourceFilename}. 189 // returns number of the breakpoints set. 190 protected int setBreakpoints(int id) { 191 verifySourceFilename(); 192 return setBreakpointsFromTestSource(launchOptions.sourceFilename, id); 193 } 194 195 // transforms class with the specified id (see {@code ClassTransformer}) 196 // and executes "redefine" jdb command for {@code launchOptions.debuggeeClass}. 197 // returns reply for the command. 198 protected List<String> redefineClass(int id, String... compilerOptions) { 199 verifySourceFilename(); 200 String transformedClassFile = ClassTransformer.fromTestSource(launchOptions.sourceFilename) 201 .transform(id, launchOptions.debuggeeClass, compilerOptions); 202 return jdb.command(JdbCommand.redefine(launchOptions.debuggeeClass, transformedClassFile)); 203 } 204 205 // gets full test source path for the given test filename 206 public static String getTestSourcePath(String fileName) { 207 return Paths.get(System.getProperty("test.src")).resolve(fileName).toString(); 208 } 209 210 // verifies that sourceFilename is specified in ctor 211 private void verifySourceFilename() { 212 if (launchOptions.sourceFilename == null) { 213 throw new RuntimeException("launchOptions.sourceFilename must be specified."); 214 } 215 } 216 217 protected OutputAnalyzer execCommand(JdbCommand cmd) { 218 List<String> reply = jdb.command(cmd); 219 return new OutputAnalyzer(reply.stream().collect(Collectors.joining(lineSeparator))); 220 } 221 222 // helpers for "eval" jdb command. 223 // executes "eval <expr>" and verifies output contains the specified text 224 protected void evalShouldContain(String expr, String expectedString) { 225 execCommand(JdbCommand.eval(expr)) 226 .shouldContain(expectedString); 227 } 228 // executes "eval <expr>" and verifies output does not contain the specified text 229 protected void evalShouldNotContain(String expr, String unexpectedString) { 230 execCommand(JdbCommand.eval(expr)) 231 .shouldNotContain(unexpectedString); 232 } 233 }