Skip to content

Commit

Permalink
Add option to run everything in the current file
Browse files Browse the repository at this point in the history
This will enable the possibility to run/debug without specifying the configuration in `launch.json`

Some further work can be done on the disocery so that we would be able to automatically feel new configurations for the user.
  • Loading branch information
tgodzik committed Nov 29, 2021
1 parent db3ae66 commit e82cba6
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 10 deletions.
17 changes: 9 additions & 8 deletions docs/integrations/debug-adapter-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,15 @@ a-b-key9=value 9 # will be ignored

This option works a bit different than the other two param shapes as you don't
specify a test or main class, but rather a `runType` of either `"run"`,
`"testFile"`, or `"testTarget"` and a file URI representing your current location.
`"run"` will automatically find any main method in the build target that belongs
to the URI that was sent in. If multiple are found, you will be given the choice
of which to run. The `"testFile"` option will check for any test classes in your
current file and run them. Similarly, `"testTarget"` will run all test classes
found in the build target that the URI belongs to. The `"args"`, `"jvmOptions"`,
`"env"`, and `"envFile"` are all valid keys that can be sent as well with the
same format as above.
`"runOrTestFile"`, `"testFile"`, or `"testTarget"` and a file URI representing
your current location. `"run"` will automatically find any main method in the
build target that belongs to the URI that was sent in. If multiple are found,
you will be given the choice of which to run. `"runOrTestFile"` will try to find
a main or test class in your current file and run them. The `"testFile"` option
will check for any test classes in your current file and run them. Similarly,
`"testTarget"` will run all test classes found in the build target that the URI
belongs to. The `"args"`, `"jvmOptions"`, `"env"`, and `"envFile"` are all valid
keys that can be sent as well with the same format as above.

```json
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package scala.meta.internal.metals.config
object RunType {
sealed trait RunType
case object Run extends RunType
case object RunFile extends RunType
case object TestFile extends RunType
case object TestTarget extends RunType

def fromString(string: String): Option[RunType] = {
string match {
case "run" => Some(Run)
case "runOrTestFile" => Some(RunFile)
case "testFile" => Some(TestFile)
case "testTarget" => Some(TestTarget)
case _ => None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import java.util.Collections.singletonList
import java.util.concurrent.TimeUnit
import java.{util => ju}

import scala.collection.concurrent.TrieMap
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.concurrent.Promise
Expand Down Expand Up @@ -275,6 +276,51 @@ class DebugProvider(
}
}

private def resolveInFile(
buildTarget: BuildTargetIdentifier,
classes: TrieMap[
BuildTargetClasses.Symbol,
ScalaMainClass
],
testClasses: TrieMap[
BuildTargetClasses.Symbol,
BuildTargetClasses.ClassName
],
params: DebugDiscoveryParams
)(implicit ec: ExecutionContext) = {
val path = params.path.toAbsolutePath
semanticdbs
.textDocument(path)
.documentIncludingStale
.fold[Future[DebugSessionParams]] {
Future.failed(SemanticDbNotFoundException)
} { textDocument =>
lazy val tests = for {
symbolInfo <- textDocument.symbols
symbol = symbolInfo.symbol
testClass <- testClasses.get(symbol)
} yield testClass
val mains = for {
symbolInfo <- textDocument.symbols
symbol = symbolInfo.symbol
mainClass <- classes.get(symbol)
} yield mainClass
if (mains.nonEmpty) {
verifyMain(buildTarget, mains.toList, params)
} else if (tests.nonEmpty) {
Future(
new b.DebugSessionParams(
singletonList(buildTarget),
b.DebugSessionParamsDataKind.SCALA_TEST_SUITES,
tests.asJava.toJson
)
)
} else {
Future.failed(NoRunOptionException)
}
}
}

/**
* Given fully unresolved params this figures out the runType that was passed
* in and then discovers either the main methods for the build target the
Expand All @@ -288,7 +334,7 @@ class DebugProvider(
val buildTargetO = buildTargets.inverseSources(path)

lazy val mainClasses = (bti: BuildTargetIdentifier) =>
buildTargetClasses.classesOf(bti).mainClasses.values.toList
buildTargetClasses.classesOf(bti).mainClasses

lazy val testClasses = (bti: BuildTargetIdentifier) =>
buildTargetClasses.classesOf(bti).testClasses
Expand All @@ -301,7 +347,9 @@ class DebugProvider(
case (None, _) =>
Future.failed(RunType.UnknownRunTypeException(params.runType))
case (Some(Run), Some(target)) =>
verifyMain(target, mainClasses(target), params)
verifyMain(target, mainClasses(target).values.toList, params)
case (Some(RunFile), Some(target)) =>
resolveInFile(target, mainClasses(target), testClasses(target), params)
case (Some(TestFile), Some(target)) if testClasses(target).isEmpty =>
Future.failed(
NoTestsFoundException("file", path.toString())
Expand Down Expand Up @@ -629,6 +677,10 @@ case object WorkspaceErrorsException
extends Exception(
s"Cannot run class, since the workspace has errors."
)
case object NoRunOptionException
extends Exception(
s"There is nothing to run in the current file."
)
case object SemanticDbNotFoundException
extends Exception(
"Build misconfiguration. No semanticdb can be found for you file, please check the doctor."
Expand Down
66 changes: 66 additions & 0 deletions tests/unit/src/test/scala/tests/DebugDiscoverySuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,72 @@ class DebugDiscoverySuite
} yield assertNoDiff(output, "oranges are nice")
}

test("run-file-main") {
for {
_ <- initialize(
s"""/metals.json
|{
| "a": {}
|}
|/${mainPath}
|package a
|object Main {
| def main(args: Array[String]) = {
| print("oranges are nice")
| System.exit(0)
| }
|}
|""".stripMargin
)
_ <- server.didOpen(mainPath)
_ <- server.waitFor(TimeUnit.SECONDS.toMillis(10))
debugger <- server.startDebuggingUnresolved(
new DebugDiscoveryParams(
server.toPath(mainPath).toURI.toString,
"runOrTestFile"
).toJson
)
_ <- debugger.initialize
_ <- debugger.launch
_ <- debugger.configurationDone
_ <- debugger.shutdown
output <- debugger.allOutput
} yield assertNoDiff(output, "oranges are nice")
}

test("run-file-test") {
for {
_ <- initialize(
s"""/metals.json
|{
| "a": {
| "libraryDependencies":["org.scalatest::scalatest:3.0.5"]
| }
|}
|/${fooPath}
|package a
|class Foo extends org.scalatest.FunSuite {
| test("foo") {}
|}
|""".stripMargin
)
_ <- server.didOpen(fooPath)
_ <- server.didSave(fooPath)(identity)
_ <- server.waitFor(TimeUnit.SECONDS.toMillis(10))
debugger <- server.startDebuggingUnresolved(
new DebugDiscoveryParams(
server.toPath(fooPath).toURI.toString,
"runOrTestFile"
).toJson
)
_ <- debugger.initialize
_ <- debugger.launch
_ <- debugger.configurationDone
_ <- debugger.shutdown
output <- debugger.allOutput
} yield assert(output.contains("All tests in a.Foo passed"))
}

test("run-multiple") {
for {
_ <- initialize(
Expand Down

0 comments on commit e82cba6

Please sign in to comment.