Skip to content

Commit

Permalink
Scala debugger working without JDT debugger
Browse files Browse the repository at this point in the history
Reworked the Scala debugger so it doesn't use the JDT debugger to
communicate with the VM.
A new launch delegate is added to the Scala application launch configuration type.
It creates a ScalaDebugTarget from the JDI connection to the vm.
Added a breakpoint manager, to create and delete the JDI requests needed.
Added a JDI event dispatcher, to pull the events from the JDI event queue and forwarding
them to the interested parties.
The management of the events and of the state of the debug elements is base on Scala actors.
Removed the debug preference page
Improved the access modifiers

Fix #1001130
  • Loading branch information
Luc Bourlier committed Jul 13, 2012
1 parent a8d0572 commit d494b18
Show file tree
Hide file tree
Showing 35 changed files with 1,337 additions and 557 deletions.
Expand Up @@ -22,11 +22,6 @@ class ScalaDebugComputeDetailTest {

var session: ScalaDebugTestSession = null

@Before
def setScalaDebugMode() {
ScalaDebugPlugin.plugin.getPreferenceStore.setValue(DebugPreferencePage.P_ENABLE, true)
}

@Before
def refreshBinaryFiles() {
project.underlying.build(IncrementalProjectBuilder.CLEAN_BUILD, new NullProgressMonitor)
Expand Down
Expand Up @@ -18,11 +18,6 @@ class ScalaDebugResumeTest {

var session: ScalaDebugTestSession = null

@Before
def setScalaDebugMode() {
ScalaDebugPlugin.plugin.getPreferenceStore.setValue(DebugPreferencePage.P_ENABLE, true)
}

@Before
def refreshBinaryFiles() {
project.underlying.build(IncrementalProjectBuilder.CLEAN_BUILD, new NullProgressMonitor)
Expand Down
Expand Up @@ -23,7 +23,6 @@ class ScalaDebugSteppingTest {
@Before
def initializeTests() {
if (!initialized) {
ScalaDebugPlugin.plugin.getPreferenceStore.setValue(DebugPreferencePage.P_ENABLE, true)
project.underlying.build(IncrementalProjectBuilder.CLEAN_BUILD, new NullProgressMonitor)
project.underlying.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, new NullProgressMonitor)
initialized = true
Expand Down
Expand Up @@ -11,6 +11,17 @@ import org.eclipse.debug.core.model.DebugElement
import scala.tools.eclipse.debug.model.ScalaDebugElement
import org.eclipse.jdt.internal.debug.core.model.JDIDebugElement

object EclipseDebugEvent {
def unapply(event: DebugEvent): Option[(Int, DebugElement)] = {
event.getSource match {
case debugElement: DebugElement =>
Some(event.getKind, debugElement)
case _ =>
None
}
}
}

class ScalaDebugTestSession(launchConfigurationFile: IFile) extends IDebugEventSetListener {

object State extends Enumeration {
Expand Down Expand Up @@ -100,13 +111,11 @@ class ScalaDebugTestSession(launchConfigurationFile: IFile) extends IDebugEventS
var debugTarget: ScalaDebugTarget = null
var currentStackFrame: ScalaStackFrame = null

// if is not Scala 2.0, it is Scala 2.09
lazy val isScala210 = debugTarget.javaTarget.getVM.classesByName("scala.ScalaObject").isEmpty

def runToLine(typeName: String, breakpointLine: Int) {
assertThat("Bad state before runToBreakpoint", state, anyOf(is(NOT_LAUNCHED), is(SUSPENDED)))

val breakpoint = JDIDebugModel.createLineBreakpoint(ResourcesPlugin.getWorkspace.getRoot, typeName, breakpointLine, -1, -1, -1, true, null)
waitForBreakpointsToBeInstalled

if (state eq NOT_LAUNCHED) {
launch()
Expand Down Expand Up @@ -177,18 +186,29 @@ class ScalaDebugTestSession(launchConfigurationFile: IFile) extends IDebugEventS
val launchConfiguration = DebugPlugin.getDefault.getLaunchManager.getLaunchConfiguration(launchConfigurationFile)
launchConfiguration.launch(ILaunchManager.DEBUG_MODE, null)
}

/**
* Breakpoints are set asynchronously. It is fine in the UI, but it create timing problems
* while running test.
* This method make sure there are no outstanding requests
*/
private def waitForBreakpointsToBeInstalled() {
if (state ne NOT_LAUNCHED) {
debugTarget.breakpointManager.waitForAllCurrentEvents
}
}

// -----

/**
* Check that all threads have a suspended count of 0, except the one of the current thread which should be 1
* Check that all threads have a suspended count of 0, except the one of the current thread, which should be 1
*/
def checkThreadsState() {
assertEquals("Bad state before checkThreadsState", SUSPENDED, state)

val currentThread = currentStackFrame.stackFrame.thread
import scala.collection.JavaConverters._
debugTarget.javaTarget.getVM.allThreads.asScala.foreach(thread =>
debugTarget.virtualMachine.allThreads.asScala.foreach(thread =>
assertEquals("Wrong suspended count", if (thread == currentThread) 1 else 0, thread.suspendCount))
}

Expand Down
Expand Up @@ -41,7 +41,7 @@ class ScalaDebugModelPresentationTest {
when(jdiThread.threadGroup).thenReturn(jdiThreadGroup)
when(jdiThreadGroup.name).thenReturn("not system")

val scalaThread = new ScalaThread(null, jdiThread)
val scalaThread = ScalaThread(null, jdiThread)

assertEquals("Bad display name for Scala thread", "Thread [thread name]", modelPres.getText(scalaThread))
}
Expand All @@ -54,7 +54,7 @@ class ScalaDebugModelPresentationTest {
when(jdiThread.threadGroup).thenReturn(jdiThreadGroup)
when(jdiThreadGroup.name).thenReturn("system")

val scalaThread = new ScalaThread(null, jdiThread)
val scalaThread = ScalaThread(null, jdiThread)

assertEquals("Bad display name for Scala system thread", "Deamon System Thread [system thread name]", modelPres.getText(scalaThread))
}
Expand Down
Expand Up @@ -10,47 +10,59 @@ import java.util.ArrayList
import org.eclipse.debug.core.DebugPlugin
import org.junit.Before
import com.sun.jdi.event.ThreadStartEvent
import com.sun.jdi.request.EventRequestManager
import com.sun.jdi.request.ThreadStartRequest
import com.sun.jdi.request.ThreadDeathRequest
import org.eclipse.debug.core.Launch
import com.sun.jdi.event.EventQueue

class ScalaDebugTargetTest {

@Before
def initializeDebugPlugin() {
if (DebugPlugin.getDefault == null) {
new DebugPlugin
}
}

@Test
def threadNotTwiceInList() {
val THREAD_NAME= "thread name"

val debugTarget= createDebugTarget

val THREAD_NAME = "thread name"
val debugTarget = createDebugTarget()

val event = mock(classOf[ThreadStartEvent])
val thread = mock(classOf[ThreadReference])
when(event.thread).thenReturn(thread)
when(thread.name).thenReturn(THREAD_NAME)
debugTarget.handleEvent(event, null, false, null)
val threads1= debugTarget.getThreads

debugTarget.eventActor !? event

val threads1 = debugTarget.getThreads
assertEquals("Wrong number of threads", 1, threads1.length)
assertEquals("Wrong thread name", THREAD_NAME, threads1(0).getName)

// a second start event should not result in a duplicate entry
debugTarget.handleEvent(event, null, false, null)
val threads2= debugTarget.getThreads
debugTarget.eventActor !? event

val threads2 = debugTarget.getThreads
assertEquals("Wrong number of threads", 1, threads2.length)
assertEquals("Wrong thread name", THREAD_NAME, threads2(0).getName)
}


/**
* Create a debug target with most of the JDI implementation mocked
*/
def createDebugTarget(): ScalaDebugTarget = {
val javaDebugTarget= mock(classOf[JDIDebugTarget])
val vm= mock(classOf[VirtualMachine])
when(javaDebugTarget.getVM).thenReturn(vm)
when(vm.allThreads).thenReturn(new ArrayList[ThreadReference]())
new ScalaDebugTarget(javaDebugTarget, null, null)
val virtualMachine = mock(classOf[VirtualMachine])
when(virtualMachine.allThreads).thenReturn(new ArrayList[ThreadReference]())
val eventRequestManager = mock(classOf[EventRequestManager])
when(virtualMachine.eventRequestManager).thenReturn(eventRequestManager)
when(virtualMachine.eventQueue).thenReturn(mock(classOf[EventQueue]))
val threadStartRequest = mock(classOf[ThreadStartRequest])
when(eventRequestManager.createThreadStartRequest).thenReturn(threadStartRequest)
val threadDeathRequest = mock(classOf[ThreadDeathRequest])
when(eventRequestManager.createThreadDeathRequest).thenReturn(threadDeathRequest)
ScalaDebugTarget(virtualMachine, mock(classOf[Launch]), null)
}

}
}
Expand Up @@ -39,7 +39,7 @@ class ScalaThreadTest {
val jdiThreadGroup = createThreadGroup();
when(jdiThread.threadGroup).thenReturn(jdiThreadGroup)

val thread = new ScalaThread(null, jdiThread)
val thread = ScalaThread(null, jdiThread)

assertEquals("Bad thread name", "some test string", thread.getName)
}
Expand All @@ -52,7 +52,7 @@ class ScalaThreadTest {
val jdiThreadGroup = createThreadGroup();
when(jdiThread.threadGroup).thenReturn(jdiThreadGroup)

val thread = new ScalaThread(null, jdiThread)
val thread = ScalaThread(null, jdiThread)

assertEquals("Bad thread name on VMDisconnectedException", "<disconnected>", thread.getName)
}
Expand All @@ -65,7 +65,7 @@ class ScalaThreadTest {
val jdiThreadGroup = createThreadGroup();
when(jdiThread.threadGroup).thenReturn(jdiThreadGroup)

val thread = new ScalaThread(null, jdiThread)
val thread = ScalaThread(null, jdiThread)

assertEquals("Bad thread name", "<garbage collected>", thread.getName)
}
Expand Down
Expand Up @@ -7,6 +7,9 @@
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="1"/>
</listAttribute>
<mapAttribute key="org.eclipse.debug.core.preferred_launchers">
<mapEntry key="[debug]" value="scala.application.new"/>
</mapAttribute>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="stepping.AnonFunOnListString"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="debug"/>
</launchConfiguration>
Expand Up @@ -2,11 +2,14 @@
<launchConfiguration type="scala.application">
<stringAttribute key="bad_container_name" value="/debug/f"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/debug"/>
<listEntry value="/debug/src/stepping/ForComprehensionListInt.scala"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
<listEntry value="1"/>
</listAttribute>
<mapAttribute key="org.eclipse.debug.core.preferred_launchers">
<mapEntry key="[debug]" value="scala.application.new"/>
</mapAttribute>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="stepping.ForComprehensionListInt"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="debug"/>
</launchConfiguration>
Expand Up @@ -7,6 +7,9 @@
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="1"/>
</listAttribute>
<mapAttribute key="org.eclipse.debug.core.preferred_launchers">
<mapEntry key="[debug]" value="scala.application.new"/>
</mapAttribute>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="stepping.ForComprehensionListIntOptimized"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="debug"/>
</launchConfiguration>
Expand Up @@ -7,6 +7,9 @@
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="1"/>
</listAttribute>
<mapAttribute key="org.eclipse.debug.core.preferred_launchers">
<mapEntry key="[debug]" value="scala.application.new"/>
</mapAttribute>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="stepping.ForComprehensionListObject"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="debug"/>
</launchConfiguration>
Expand Up @@ -7,6 +7,9 @@
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="1"/>
</listAttribute>
<mapAttribute key="org.eclipse.debug.core.preferred_launchers">
<mapEntry key="[debug]" value="scala.application.new"/>
</mapAttribute>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="stepping.ForComprehensionListString"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="debug"/>
</launchConfiguration>
Expand Up @@ -7,6 +7,9 @@
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="1"/>
</listAttribute>
<mapAttribute key="org.eclipse.debug.core.preferred_launchers">
<mapEntry key="[debug]" value="scala.application.new"/>
</mapAttribute>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="stepping.ForComprehensionListString2"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="debug"/>
</launchConfiguration>
Expand Up @@ -7,6 +7,9 @@
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="1"/>
</listAttribute>
<mapAttribute key="org.eclipse.debug.core.preferred_launchers">
<mapEntry key="[debug]" value="scala.application.new"/>
</mapAttribute>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="stepping.SimpleStepping"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="debug"/>
</launchConfiguration>
Expand Up @@ -6,6 +6,9 @@
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="1"/>
</listAttribute>
<mapAttribute key="org.eclipse.debug.core.preferred_launchers">
<mapEntry key="[debug]" value="scala.application.new"/>
</mapAttribute>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="debug.Variables"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="debug"/>
</launchConfiguration>
28 changes: 13 additions & 15 deletions org.scala-ide.sdt.debug/plugin.xml
@@ -1,21 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<extension
point="org.eclipse.ui.preferencePages">
<page
category="org.scala-ide.sdt.core.preferences"
class="scala.tools.eclipse.debug.DebugPreferencePage"
id="org.scala-ide.sdt.debug.preferences"
name="Debug">
</page>
</extension>
<extension
point="org.eclipse.core.runtime.preferences">
<initializer
class="scala.tools.eclipse.debug.DebugPreferenceInitializer">
</initializer>
</extension>
<extension
point="org.eclipse.debug.ui.debugModelPresentations">
<debugModelPresentation
Expand All @@ -36,4 +21,17 @@
modelIdentifier="org.scala-ide.sdt.debug">
</logicalStructureProvider>
</extension>
<extension
point="org.eclipse.debug.core.launchDelegates">
<launchDelegate
delegate="scala.tools.eclipse.launching.ScalaApplicationLaunchConfigurationDelegate"
delegateDescription="The Scala JVM Launcher supports debugging of local Scala using the new Scala debugger"
id="scala.application.new"
modes="debug"
name="Scala Application (new debugger)"
sourceLocatorId="org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector"
sourcePathComputerId="org.eclipse.jdt.launching.sourceLookup.javaSourcePathComputer"
type="scala.application">
</launchDelegate>
</extension>
</plugin>

0 comments on commit d494b18

Please sign in to comment.