blob: b13a30a6f79c0321dedc48efb61cdd33c3572baa [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.api.debugger.jpda;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
import org.netbeans.api.debugger.*;
import java.io.*;
import java.util.*;
import java.net.URLClassLoader;
import java.net.URL;
import java.beans.PropertyChangeEvent;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import com.sun.jdi.connect.*;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.Bootstrap;
//import org.netbeans.api.java.classpath.ClassPath;
import junit.framework.Test;
import junit.framework.TestCase;
import org.netbeans.api.java.classpath.ClassPath;
//import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.netbeans.api.java.queries.SourceForBinaryQuery;
import org.netbeans.junit.NbModuleSuite;
import org.netbeans.junit.NbModuleSuite.Configuration;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
/**
* Contains support functionality for unit tests.
*
* @author Maros Sandor
*/
public class JPDASupport implements DebuggerManagerListener {
private static final boolean verbose = false;
private static final DateFormat df = new SimpleDateFormat("kk:mm:ss.SSS");
private static DebuggerManager dm = DebuggerManager.getDebuggerManager ();
private JPDADebugger jpdaDebugger;
private DebuggerEngine debuggerEngine;
private Object [] debuggerStartLock = new Object[1];
private Object [] stepLock = new Object[1];
private Object STATE_LOCK = new Object ();
private JPDASupport (JPDADebugger jpdaDebugger) {
this.jpdaDebugger = jpdaDebugger;
jpdaDebugger.addPropertyChangeListener (this);
DebuggerEngine[] de = dm.getDebuggerEngines ();
int i, k = de.length;
for (i = 0; i < k; i++)
if (de [i].lookupFirst (null, JPDADebugger.class) == jpdaDebugger) {
debuggerEngine = de [i];
break;
}
}
public static Test createTestSuite(Class<? extends TestCase> clazz) {
Configuration suiteConfiguration = NbModuleSuite.createConfiguration(clazz);
suiteConfiguration = suiteConfiguration.clusters(".*").enableModules(".*java.source.*").gui(false);
if (!(ClassLoader.getSystemClassLoader() instanceof URLClassLoader)) {
//when running on JDK 9+, to make the com.sun.jdi package dependency work, we need to make getPackage("com.sun.jdi") work
//for system CL's parent (which otherwise happily loads the VirtualMachineManager class,
//but won't return the package from getPackage due to JDK "specialty":
suiteConfiguration = suiteConfiguration.parentClassLoader(new ClassLoader(ClassLoader.getSystemClassLoader().getParent()) {
@Override
protected Package getPackage(String pack) {
if ("com.sun.jdi".equals(pack)) {
try {
return loadClass("com.sun.jdi.VirtualMachineManager").getPackage();
} catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
}
return super.getPackage(pack);
}
});
}
//suiteConfiguration = suiteConfiguration.reuseUserDir(false);
return NbModuleSuite.create(suiteConfiguration);
}
// starting methods ........................................................
// public static JPDASupport listen (String mainClass)
// throws IOException, IllegalConnectorArgumentsException,
// DebuggerStartException {
// VirtualMachineManager vmm = Bootstrap.virtualMachineManager ();
// List lconnectors = vmm.listeningConnectors ();
// ListeningConnector connector = null;
// for (Iterator i = lconnectors.iterator (); i.hasNext ();) {
// ListeningConnector lc = (ListeningConnector) i.next ();
// Transport t = lc.transport ();
// if (t != null && t.name ().equals ("dt_socket")) {
// connector = lc;
// break;
// }
// }
// if (connector == null)
// throw new RuntimeException
// ("No listening socket connector available");
//
// Map args = connector.defaultArguments ();
// String address = connector.startListening (args);
// String localhostAddres;
// try
// {
// int port = Integer.parseInt
// (address.substring (address.indexOf (':') + 1));
// localhostAddres = "localhost:" + port;
// Connector.IntegerArgument portArg =
// (Connector.IntegerArgument) args.get("port");
// portArg.setValue(port);
// } catch (Exception e) {
// // this address format is not known, use default
// localhostAddres = address;
// }
//
// JPDADebugger jpdaDebugger = JPDADebugger.listen
// (connector, args, createServices ());
// if (jpdaDebugger == null)
// throw new DebuggerStartException ("JPDA jpdaDebugger was not started");
// Process process = launchVM (mainClass, localhostAddres, false);
// ProcessIO pio = new ProcessIO (process);
// pio.go ();
// return new JPDASupport (jpdaDebugger);
// }
public static JPDASupport attach (String mainClass) throws IOException,
DebuggerStartException {
return attach(mainClass, null);
}
public static JPDASupport attach (String mainClass, String[] args) throws IOException,
DebuggerStartException {
return attach(mainClass, args, new File[0]);
}
public static JPDASupport attach (String mainClass, String[] args, File[] classPath) throws IOException,
DebuggerStartException {
Process process = launchVM (mainClass, args, classPath, "", true);
String line = readLine (process.getInputStream ());
int port = Integer.parseInt (line.substring (line.lastIndexOf (':') + 1).trim ());
ProcessIO pio = new ProcessIO (process);
pio.go ();
VirtualMachineManager vmm = Bootstrap.virtualMachineManager();
List aconnectors = vmm.attachingConnectors();
AttachingConnector connector = null;
for (Iterator i = aconnectors.iterator(); i.hasNext();) {
AttachingConnector ac = (AttachingConnector) i.next();
Transport t = ac.transport ();
if (t != null && t.name().equals("dt_socket")) {
connector = ac;
break;
}
}
if (connector == null)
throw new RuntimeException
("No attaching socket connector available");
JPDADebugger jpdaDebugger = JPDADebugger.attach (
"localhost",
port,
createServices ()
);
return new JPDASupport (jpdaDebugger);
}
// public interface ........................................................
public void doContinue () {
if (jpdaDebugger.getState () != JPDADebugger.STATE_STOPPED)
throw new IllegalStateException ();
debuggerEngine.getActionsManager ().doAction
(ActionsManager.ACTION_CONTINUE);
}
public void stepOver () {
step (ActionsManager.ACTION_STEP_OVER);
}
public void stepInto () {
step (ActionsManager.ACTION_STEP_INTO);
}
public void stepOut () {
step (ActionsManager.ACTION_STEP_OUT);
}
public void step (Object action) {
if (jpdaDebugger.getState () != JPDADebugger.STATE_STOPPED)
throw new IllegalStateException ();
debuggerEngine.getActionsManager ().doAction (action);
waitState (JPDADebugger.STATE_STOPPED);
}
public void stepAsynch (final Object actionAsynch, final ActionsManagerListener al) {
if (jpdaDebugger.getState () != JPDADebugger.STATE_STOPPED)
throw new IllegalStateException ();
debuggerEngine.getActionsManager().addActionsManagerListener(
new ActionsManagerListener() {
public void actionPerformed(Object action) {
if (action != actionAsynch) return ;
al.actionPerformed(action);
debuggerEngine.getActionsManager().removeActionsManagerListener(this);
}
public void actionStateChanged(Object action, boolean enabled) {
}
}
);
debuggerEngine.getActionsManager ().postAction (actionAsynch);
}
public void doFinish () {
if (jpdaDebugger == null) return;
debuggerEngine.getActionsManager ().
doAction (ActionsManager.ACTION_KILL);
waitState (JPDADebugger.STATE_DISCONNECTED);
}
public void waitState (int state) {
synchronized (STATE_LOCK) {
while ( jpdaDebugger.getState () != state &&
jpdaDebugger.getState () != JPDADebugger.STATE_DISCONNECTED
) {
try {
STATE_LOCK.wait ();
} catch (InterruptedException ex) {
ex.printStackTrace ();
}
}
}
}
/*public void waitState (int state) {
synchronized (STATE_LOCK) {
int ds = jpdaDebugger.getState ();
System.err.println("JPDASupport.waitState("+state+"): ds = "+ds+", jpdaDebugger = "+jpdaDebugger);
while ( ds != state &&
ds != JPDADebugger.STATE_DISCONNECTED
) {
try {
STATE_LOCK.wait ();
} catch (InterruptedException ex) {
ex.printStackTrace ();
}
ds = jpdaDebugger.getState ();
System.err.println("JPDASupport.waitState("+state+"): new ds = "+ds+", jpdaDebugger = "+jpdaDebugger);
}
System.err.println("JPDASupport.waitState("+state+"): state reached.");
}
}*/
public JPDADebugger getDebugger() {
return jpdaDebugger;
}
/**
* Remove all non-hidden breakpoints.
*/
public static void removeAllBreakpoints () {
Breakpoint[] bs = DebuggerManager.getDebuggerManager ().
getBreakpoints ();
int i, k = bs.length;
for (i = 0; i < k; i++) {
if (!(bs[i] instanceof JPDABreakpoint && ((JPDABreakpoint) bs[i]).isHidden())) {
DebuggerManager.getDebuggerManager ().removeBreakpoint (bs [i]);
}
}
}
// other methods ...........................................................
private static Object[] createServices () {
try {
Map map = new HashMap ();
String sourceRoot = System.getProperty ("test.dir.src");
URL sourceUrl = new File(sourceRoot).toURI().toURL();
String sourceUrlStr = sourceUrl.toString() + "/";
sourceUrl = new URL(sourceUrlStr);
ClassPath cp = ClassPathSupport.createClassPath (new URL[] {
sourceUrl
});
map.put ("sourcepath", cp);
map.put ("baseDir", new File(sourceRoot).getParentFile());
return new Object[] { map };
} catch (MalformedURLException ex) {
//System.err.println("MalformedURLException: sourceRoot = '"+sourceRoot+"'.");
ex.printStackTrace();
return new Object[] {};
}
}
private static String readLine (InputStream in) throws IOException {
StringBuffer sb = new StringBuffer();
for (;;) {
int c = in.read();
if (c == -1) throw new EOFException();
if (c == 0x0D) {
c = in.read();
if (c != 0x0A) sb.append((char)0x0D);
}
if (c == 0x0A) return sb.toString();
sb.append((char)c);
}
}
private static Process launchVM (
String mainClass,
String[] args,
File[] extraCP,
String connectorAddress,
boolean server
) throws IOException {
String cp = getClassPath(extraCP);
//System.err.println("CP = "+cp);
String [] cmdArray = new String [] {
System.getProperty ("java.home") + File.separatorChar +
"bin" + File.separatorChar + "java",
"-agentlib:jdwp=transport=" + "dt_socket" + ",address=" +
connectorAddress + ",suspend=y,server=" +
(server ? "y" : "n"),
"-classpath",
cp.substring(0, cp.length() -1),
mainClass
};
if (args != null && args.length > 0) {
String[] arr = new String[cmdArray.length + args.length];
System.arraycopy(cmdArray, 0, arr, 0, cmdArray.length);
System.arraycopy(args, 0, arr, cmdArray.length, args.length);
cmdArray = arr;
}
return Runtime.getRuntime ().exec (cmdArray);
}
private static String getClassPath(File[] extraCP) {
StringBuilder cp = new StringBuilder (200);
ClassLoader cl = JPDASupport.class.getClassLoader ();
if (cl instanceof URLClassLoader) {
URLClassLoader ucl = (URLClassLoader) cl;
URL [] urls = ucl.getURLs ();
for (int i = 0; i < urls.length; i++) {
URL url = urls [i];
cp.append (url.getPath ());
cp.append (File.pathSeparatorChar);
}
} else if (cl.getClass().getName().indexOf("org.netbeans.ModuleManager$SystemClassLoader") >= 0) {
Class jarClassLoaderClass = cl.getClass().getSuperclass();
try {
java.lang.reflect.Field sourcesField = jarClassLoaderClass.getDeclaredField("sources");
sourcesField.setAccessible(true);
Object[] sources = (Object[]) sourcesField.get(cl);
for (int i = 0; i < sources.length; i++) {
Method getURL = sources[i].getClass().getMethod("getURL");
getURL.setAccessible(true);
URL url = (URL) getURL.invoke(sources[i]);
cp.append (url.getPath ());
cp.append (File.pathSeparatorChar);
}
} catch (Exception ex) {
throw new RuntimeException("Problem retrieving class path from class loader: "+cl, ex);
}
} else {
throw new RuntimeException("Unsupported class loader: "+cl);
}
for (File f : extraCP) {
cp.append(f.getPath());
cp.append(File.pathSeparatorChar);
}
return cp.toString();
}
public String toString () {
switch (jpdaDebugger.getState ()) {
case JPDADebugger.STATE_DISCONNECTED:
return "Debugger finished.";
case JPDADebugger.STATE_RUNNING:
return "Debugger running.";
case JPDADebugger.STATE_STARTING:
return "Debugger starting.";
case JPDADebugger.STATE_STOPPED:
CallStackFrame f = jpdaDebugger.getCurrentCallStackFrame ();
return "Debugger stopped: " +
f.getClassName () + "." +
f.getMethodName () + ":" +
f.getLineNumber (null);
}
return super.toString ();
}
// DebuggerListener ........................................................
public Breakpoint[] initBreakpoints() {
return new Breakpoint[0];
}
public void breakpointAdded(Breakpoint breakpoint) {
}
public void breakpointRemoved(Breakpoint breakpoint) {
}
public void initWatches() {
}
public void watchAdded(Watch watch) {
}
public void watchRemoved(Watch watch) {
}
public void sessionAdded(Session session) {
}
public void sessionRemoved(Session session) {
}
public void propertyChange (PropertyChangeEvent evt) {
if (evt.getSource() instanceof JPDADebugger) {
JPDADebugger dbg = (JPDADebugger) evt.getSource();
if (JPDADebugger.PROP_STATE.equals(evt.getPropertyName())) {
synchronized (STATE_LOCK) {
STATE_LOCK.notifyAll ();
}
if (jpdaDebugger.getState () == JPDADebugger.STATE_DISCONNECTED)
jpdaDebugger.removePropertyChangeListener (this);
}
}
}
// TODO: Include check of these call in the test suite
public void engineAdded (DebuggerEngine debuggerEngine) {
}
// TODO: Include check of these call in the test suite
public void engineRemoved (DebuggerEngine debuggerEngine) {
}
// innerclasses ............................................................
private static class ProcessIO {
private Process p;
public ProcessIO(Process p) {
this.p = p;
}
public void go() {
InputStream out = p.getInputStream();
InputStream err = p.getErrorStream();
new SimplePipe(System.out, out).start();
new SimplePipe(System.out, err).start();
}
}
private static class SimplePipe extends Thread {
private OutputStream out;
private InputStream in;
public SimplePipe(OutputStream out, InputStream in) {
this.out = out;
this.in = in;
setDaemon(true);
}
public void run() {
byte [] buffer = new byte[1024];
int n;
try {
while ((n = in.read(buffer)) != -1) {
out.write(buffer, 0, n);
}
} catch (IOException e) {
} finally {
try {
out.close();
in.close();
} catch (IOException e) {
}
}
System.out.println("PIO QUIT");
}
}
}