diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java index 8a09551ceaa0..7c2a2df15a33 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java @@ -74,6 +74,7 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.ui.UIUtil; import com.intellij.xdebugger.XDebugSession; +import com.intellij.xdebugger.impl.XDebugSessionImpl; import com.intellij.xdebugger.impl.actions.XDebuggerActions; import com.sun.jdi.*; import com.sun.jdi.connect.*; @@ -1764,6 +1765,15 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb return mySession.getSearchScope(); } + public void reattach(final DebugEnvironment environment) throws ExecutionException { + ApplicationManager.getApplication().assertIsDispatchThread(); //TODO: remove this requirement + ((XDebugSessionImpl)getXdebugProcess().getSession()).reset(); + myState.set(STATE_INITIAL); + myConnection = environment.getRemoteConnection(); + getManagerThread().restartIfNeeded(); + createVirtualMachine(environment.getSessionName(), environment.isPollConnection()); + } + @Nullable public ExecutionResult attachVirtualMachine(final DebugEnvironment environment, final DebuggerSession session) throws ExecutionException { diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/DebuggerManagerThreadImpl.java b/java/debugger/impl/src/com/intellij/debugger/engine/DebuggerManagerThreadImpl.java index c3862c8e005a..5cf0eda20fdb 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/DebuggerManagerThreadImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/DebuggerManagerThreadImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -251,4 +251,11 @@ public class DebuggerManagerThreadImpl extends InvokeAndWaitThread { } return allEvents; } + + public void reopen() { + myIsClosed = false; + } } diff --git a/java/execution/impl/src/com/intellij/execution/JavaTestFrameworkRunnableState.java b/java/execution/impl/src/com/intellij/execution/JavaTestFrameworkRunnableState.java index 7ae93336a6a6..efd820f7f45e 100644 --- a/java/execution/impl/src/com/intellij/execution/JavaTestFrameworkRunnableState.java +++ b/java/execution/impl/src/com/intellij/execution/JavaTestFrameworkRunnableState.java @@ -201,7 +201,21 @@ public abstract class JavaTestFrameworkRunnableState + diff --git a/plugins/junit/src/META-INF/plugin.xml b/plugins/junit/src/META-INF/plugin.xml index 0ca3bc1d7ed9..c8fc9906c8b6 100644 --- a/plugins/junit/src/META-INF/plugin.xml +++ b/plugins/junit/src/META-INF/plugin.xml @@ -59,6 +59,7 @@ + diff --git a/plugins/junit/src/com/intellij/execution/junit/JUnitDebuggerRunner.java b/plugins/junit/src/com/intellij/execution/junit/JUnitDebuggerRunner.java new file mode 100644 index 000000000000..e9c7b4424306 --- /dev/null +++ b/plugins/junit/src/com/intellij/execution/junit/JUnitDebuggerRunner.java @@ -0,0 +1,111 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed 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 com.intellij.execution.junit; + +import com.intellij.debugger.DebugEnvironment; +import com.intellij.debugger.DebuggerManager; +import com.intellij.debugger.DefaultDebugEnvironment; +import com.intellij.debugger.engine.DebugProcess; +import com.intellij.debugger.engine.DebugProcessImpl; +import com.intellij.debugger.impl.GenericDebuggerRunner; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.JavaTestFrameworkRunnableState; +import com.intellij.execution.configurations.RemoteConnection; +import com.intellij.execution.configurations.RunProfile; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.executors.DefaultDebugExecutor; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.RunContentDescriptor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +/** + * @author egor + */ +public class JUnitDebuggerRunner extends GenericDebuggerRunner { + @Override + public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) { + return DefaultDebugExecutor.EXECUTOR_ID.equals(executorId) && profile instanceof JUnitConfiguration; + } + + @NotNull + @Override + public String getRunnerId() { + return "JUnitDebug"; + } + + @Nullable + @Override + protected RunContentDescriptor createContentDescriptor(@NotNull final RunProfileState state, @NotNull final ExecutionEnvironment environment) + throws ExecutionException { + final RunContentDescriptor res = super.createContentDescriptor(state, environment); + final ServerSocket socket = ((JavaTestFrameworkRunnableState)state).getForkSocket(); + if (socket != null) { + Thread thread = new Thread() { + @Override + public void run() { + try { + final Socket accept = socket.accept(); + try { + DataInputStream stream = new DataInputStream(accept.getInputStream()); + try { + int read = stream.readInt(); + while (read != -1) { + final DebugProcess process = + DebuggerManager.getInstance(environment.getProject()).getDebugProcess(res.getProcessHandler()); + if (process == null) break; + final RemoteConnection connection = new RemoteConnection(true, "127.0.0.1", String.valueOf(read), true); + final DebugEnvironment env = new DefaultDebugEnvironment(environment, state, connection, true); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + try { + ((DebugProcessImpl)process).reattach(env); + accept.getOutputStream().write(0); + } + catch (Exception e) { + e.printStackTrace(); + } + } + }); + read = stream.readInt(); + } + } finally { + stream.close(); + } + } finally { + accept.close(); + } + } + catch (EOFException ignored) {} + catch (IOException e) { + e.printStackTrace(); + } + } + }; + thread.setDaemon(true); + thread.start(); + } + return res; + } +} diff --git a/plugins/junit/src/com/intellij/execution/junit/TestObject.java b/plugins/junit/src/com/intellij/execution/junit/TestObject.java index 102b74c33387..e0d6e3934f84 100644 --- a/plugins/junit/src/com/intellij/execution/junit/TestObject.java +++ b/plugins/junit/src/com/intellij/execution/junit/TestObject.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -479,8 +479,8 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState= 0) { - if (myDebugPort > -1) { - parameter = parameter.substring(0, indexOf) + "transport=dt_socket,server=n,suspend=n,address=" + myDebugPort; - parameters.set(i, parameter); + + int debugAddress = -1; + if (myDebugPort > -1) { + debugAddress = findAvailableSocketPort(); + boolean found = false; + for (int i = 0; i < parameters.size(); i++) { + String parameter = (String)parameters.get(i); + final String debuggerParam = "transport=dt_socket"; + final int indexOf = parameter.indexOf(debuggerParam); + if (indexOf >= 0) { + if (debugAddress > -1) { + parameter = parameter.substring(0, indexOf) + "transport=dt_socket,server=n,suspend=y,address=" + debugAddress; + parameters.set(i, parameter); + found = true; + } + else { + parameters.remove(parameter); + } + break; } - else { - parameters.remove(parameter); - } - break; + } + if (!found) { + parameters.add("-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=" + debugAddress); } } //noinspection SSBasedInspection @@ -296,6 +317,13 @@ public class JUnitForkedStarter { builder.add(listeners); builder.setWorkingDir(workingDir); + if (debugAddress > -1) { + Socket socket = getDebugSocket(); + DataOutputStream stream = new DataOutputStream(socket.getOutputStream()); + stream.writeInt(debugAddress); + int read = socket.getInputStream().read(); + } + final Process exec = builder.createProcess(); final int result = exec.waitFor(); ForkedVMWrapper.readWrapped(testOutputPath, @@ -303,4 +331,28 @@ public class JUnitForkedStarter { JUnitStarter.SM_RUNNER ? ((PrintStream)err) : ((SegmentedOutputStream)err).getPrintStream()); return result; } + + // copied from NetUtils + private static int findAvailableSocketPort() throws IOException { + final ServerSocket serverSocket = new ServerSocket(0); + try { + int port = serverSocket.getLocalPort(); + // workaround for linux : calling close() immediately after opening socket + // may result that socket is not closed + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (serverSocket) { + try { + //noinspection WaitNotInLoop + serverSocket.wait(1); + } + catch (InterruptedException e) { + System.err.println(e); + } + } + return port; + } + finally { + serverSocket.close(); + } + } }