Skip to content

Commit eb18306

Browse files
committed
improvement: Add location to failure to help mark it in VS Code
1 parent 1081e6e commit eb18306

File tree

9 files changed

+57
-11
lines changed

9 files changed

+57
-11
lines changed

modules/core/src/main/scala/ch/epfl/scala/debugadapter/Debuggee.scala

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package ch.epfl.scala.debugadapter
22

3-
import java.nio.file.Path
4-
import java.io.File
3+
import ch.epfl.scala.debugadapter.internal.SourceLookUpProvider
4+
55
import java.io.Closeable
6+
import java.io.File
7+
import java.nio.file.Path
68

79
trait Debuggee {
810
def name: String
@@ -19,4 +21,10 @@ trait Debuggee {
1921
def classPath: Seq[Path] = classPathEntries.map(_.absolutePath)
2022
def classEntries: Seq[ClassEntry] = classPathEntries ++ javaRuntime
2123
def classPathString: String = classPath.mkString(File.pathSeparator)
24+
25+
@volatile private[debugadapter] var sourceLookUpProvider: Option[SourceLookUpProvider] = None
26+
27+
private[debugadapter] def setSourceLookUpProvider(provider: SourceLookUpProvider): Unit = {
28+
sourceLookUpProvider = Some(provider)
29+
}
2230
}

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/ClassEntryLookUp.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ private class ClassEntryLookUp(
3737
sourceUriToSourceFile: Map[SourceFileKey, SourceFile],
3838
sourceUriToClassFiles: Map[SourceFileKey, Seq[ClassFile]],
3939
classNameToSourceFile: Map[String, SourceFile],
40+
sourceNameToSourceFile: Map[String, Seq[SourceFile]],
4041
missingSourceFileClassFiles: Seq[ClassFile],
4142
val orphanClassFiles: Seq[ClassFile],
4243
logger: Logger
@@ -50,6 +51,10 @@ private class ClassEntryLookUp(
5051
missingSourceFileClassFiles.map(_.fullyQualifiedName)
5152
}
5253

54+
def uriFromFilename(filename: String): Option[URI] = {
55+
sourceNameToSourceFile.get(filename).flatMap(_.headOption).map(_.uri)
56+
}
57+
5358
def classesByScalaName: Map[String, Iterable[String]] =
5459
fullyQualifiedNames.groupBy[String](NameTransformer.scalaClassName)
5560

@@ -257,6 +262,7 @@ private object ClassEntryLookUp {
257262
sourceUriToSourceFile,
258263
sourceUriToClassFiles.toMap,
259264
classNameToSourceFile.toMap,
265+
sourceNameToSourceFile,
260266
missingSourceFileClassFiles.toSeq,
261267
orphanClassFiles.toSeq,
262268
logger

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/DebugSession.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import scala.concurrent.Promise
2222
import scala.util.Failure
2323
import scala.util.Success
2424
import scala.util.Try
25+
import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider
2526

2627
/**
2728
* This debug adapter maintains the lifecycle of the debuggee in separation from JDI.
@@ -152,6 +153,10 @@ private[debugadapter] final class DebugSession private (
152153
val debugConfig =
153154
if (launchArgs.scalaStepFilters == null) config else config.copy(stepFilters = launchArgs.scalaStepFilters)
154155
context.configure(tools, debugConfig)
156+
context.getProvider(classOf[ISourceLookUpProvider]) match {
157+
case provider: SourceLookUpProvider =>
158+
debuggee.setSourceLookUpProvider(provider)
159+
}
155160
// launch request is implemented by spinning up a JVM
156161
// and sending an attach request to the java DapServer
157162
launchedRequests.add(requestId)

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/SourceLookUpProvider.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ private[debugadapter] final class SourceLookUpProvider(
2121
getSourceFileByClassname(fqcn).map(_.toString).orNull
2222
}
2323

24+
def getSourceFileURI(fileName: String): Option[URI] = {
25+
classPathEntries.iterator.map(_.uriFromFilename(fileName)).collectFirst { case Some(value) =>
26+
value
27+
}
28+
}
29+
2430
override def getSourceContents(uri: String): String = {
2531
val sourceUri = URI.create(uri)
2632
sourceUriToClassPathEntry

modules/core/src/main/scala/ch/epfl/scala/debugadapter/testing/TestSuiteEventHandler.scala

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ch.epfl.scala.debugadapter.testing
22

33
import sbt.testing.Status
44
import scala.jdk.CollectionConverters.*
5+
import ch.epfl.scala.debugadapter.internal.SourceLookUpProvider
56

67
trait TestSuiteEventHandler {
78
def handle(testSuiteEvent: TestSuiteEvent): Unit
@@ -25,7 +26,8 @@ object TestSuiteEventHandler {
2526
* Provide a summary of test suite execution based on passed TestSuiteEvent.Results parameter.
2627
*/
2728
def summarizeResults(
28-
testSuiteResult: TestSuiteEvent.Results
29+
testSuiteResult: TestSuiteEvent.Results,
30+
sourceLookUpProviderOpt: Option[SourceLookUpProvider]
2931
): TestSuiteSummary = {
3032
val results = testSuiteResult.events.map { e =>
3133
val name = TestUtils.printSelector(e.selector).getOrElse("")
@@ -35,13 +37,24 @@ object TestSuiteEventHandler {
3537
case Status.Failure =>
3638
val failedMsg =
3739
TestUtils.printThrowable(e.throwable()).getOrElse("")
40+
val location = if (e.throwable().isDefined()) {
41+
val throwable = e.throwable().get.getStackTrace().headOption
42+
for {
43+
stackElement <- throwable
44+
sourceLookUpProvider <- sourceLookUpProviderOpt
45+
uri <- sourceLookUpProvider.getSourceFileURI(stackElement.getFileName())
46+
} yield new SingleTestResult.Location(uri.toString(), stackElement.getLineNumber())
47+
48+
} else {
49+
None
50+
}
3851
val formatted =
3952
TestSuiteEventHandler.formatError(
4053
name,
4154
failedMsg,
4255
indentSize = 0
4356
)
44-
SingleTestResult.Failed(name, e.duration, formatted)
57+
SingleTestResult.Failed(name, e.duration, formatted, location)
4558
case _ =>
4659
SingleTestResult.Skipped(name)
4760
}

modules/core/src/main/scala/ch/epfl/scala/debugadapter/testing/TestSuiteSummary.scala

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,20 @@ object SingleTestResult {
3636
def apply(testName: String): Skipped = new Skipped("skipped", testName)
3737
}
3838

39+
final class Location(
40+
val file: String,
41+
val line: Int
42+
)
43+
3944
final class Failed private (
4045
val kind: String,
4146
val testName: String,
4247
val duration: Long,
43-
val error: String
48+
val error: String,
49+
val location: Option[Location]
4450
) extends SingleTestSummary
4551
object Failed {
46-
def apply(testName: String, duration: Long, error: String): Failed =
47-
new Failed("failed", testName, duration, error)
52+
def apply(testName: String, duration: Long, error: String, location: Option[Location] = None): Failed =
53+
new Failed("failed", testName, duration, error, location)
4854
}
4955
}

modules/sbt-plugin/src/main/scala/ch/epfl/scala/debugadapter/sbtplugin/DebugAdapterPlugin.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,8 +472,8 @@ object DebugAdapterPlugin extends sbt.AutoPlugin {
472472
val address = new DebugServer.Address()
473473
jobService.runInBackground(scope, state) { (logger, _) =>
474474
try {
475-
val debuggee = debuggeeF(logger)
476475
val resolver = resolverF(logger)
476+
val debuggee = debuggeeF(logger)
477477
// if there is a server for this target then close it
478478
debugServers.get(target).foreach(_.close())
479479
val server = DebugServer(debuggee, resolver, new LoggerAdapter(logger), address, config)

modules/sbt-plugin/src/main/scala/ch/epfl/scala/debugadapter/sbtplugin/internal/SbtDebuggee.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ private[debugadapter] final class TestSuitesDebuggee(
6868
s"${getClass.getSimpleName}(${target.uri}, [${tests.mkString(", ")}])"
6969

7070
override def run(listener: DebuggeeListener): CancelableFuture[Unit] = {
71-
val eventHandler = new SbtTestSuiteEventHandler(listener)
71+
val eventHandler = new SbtTestSuiteEventHandler(listener, () => sourceLookUpProvider)
7272

7373
@annotation.tailrec
7474
def receiveLogs(is: ObjectInputStream, os: ObjectOutputStream): Unit = {

modules/sbt-plugin/src/main/scala/ch/epfl/scala/debugadapter/sbtplugin/internal/SbtTestSuiteEventHandler.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@ package ch.epfl.scala.debugadapter.sbtplugin.internal
33
import ch.epfl.scala.debugadapter.DebuggeeListener
44
import ch.epfl.scala.debugadapter.testing.TestSuiteEventHandler
55
import ch.epfl.scala.debugadapter.testing.TestSuiteEvent
6+
import ch.epfl.scala.debugadapter.internal.SourceLookUpProvider
67

78
/**
89
* Extracts information about tests execution and send it to the DebuggeeListener.
910
* Then DebugeeListener forwards it to the DAP client.
1011
*/
11-
class SbtTestSuiteEventHandler(listener: DebuggeeListener) extends TestSuiteEventHandler {
12+
class SbtTestSuiteEventHandler(listener: DebuggeeListener, sourceLookUpProvider: () => Option[SourceLookUpProvider])
13+
extends TestSuiteEventHandler {
1214

1315
def handle(event: TestSuiteEvent): Unit =
1416
event match {
1517
case TestSuiteEvent.Info(s) => listener.out(s)
1618
case TestSuiteEvent.Warn(s) => listener.out(s)
1719
case TestSuiteEvent.Error(s) => listener.err(s)
1820
case results: TestSuiteEvent.Results =>
19-
val testResults = TestSuiteEventHandler.summarizeResults(results)
21+
val testResults = TestSuiteEventHandler.summarizeResults(results, sourceLookUpProvider())
2022
listener.testResult(testResults)
2123
case _ => ()
2224
}

0 commit comments

Comments
 (0)