Skip to content

Commit ee7f7c7

Browse files
committed
Introduce debugMode in RunnerOrchestration
debugMode allows to connect to runner processes with JDI
1 parent 8d7b74a commit ee7f7c7

File tree

3 files changed

+41
-18
lines changed

3 files changed

+41
-18
lines changed

compiler/test/dotty/tools/debug/DebugTests.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ object DebugTests extends ParallelTesting:
2424
def testFilter = Properties.testsFilter
2525
def updateCheckFiles: Boolean = Properties.testsUpdateCheckfile
2626
def failedTests = TestReporter.lastRunFailedTests
27+
override def debugMode = true
2728

2829
implicit val summaryReport: SummaryReporting = new SummaryReport
2930

@@ -40,7 +41,7 @@ object DebugTests extends ParallelTesting:
4041

4142
private def verifyDebug(dir: JFile, testSource: TestSource, warnings: Int, reporters: Seq[TestReporter], logger: LoggedRunnable) =
4243
if Properties.testsNoRun then addNoRunWarning()
43-
else runMain(testSource.runClassPath, testSource.allToolArgs) match
44+
else runMain(testSource.runClassPath) match
4445
case Success(output) => ()
4546
case Failure(output) =>
4647
if output == "" then

compiler/test/dotty/tools/vulpix/ParallelTesting.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -929,7 +929,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
929929

930930
private def verifyOutput(checkFile: Option[JFile], dir: JFile, testSource: TestSource, warnings: Int, reporters: Seq[TestReporter], logger: LoggedRunnable) = {
931931
if Properties.testsNoRun then addNoRunWarning()
932-
else runMain(testSource.runClassPath, testSource.allToolArgs) match {
932+
else runMain(testSource.runClassPath) match {
933933
case Success(output) => checkFile match {
934934
case Some(file) if file.exists => diffTest(testSource, file, output.linesIterator.toList, reporters, logger)
935935
case _ =>

compiler/test/dotty/tools/vulpix/RunnerOrchestration.scala

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@ package dotty
22
package tools
33
package vulpix
44

5-
import scala.language.unsafeNulls
6-
7-
import java.io.{ File => JFile, InputStreamReader, BufferedReader, PrintStream }
8-
import java.nio.file.Paths
5+
import java.io.BufferedReader
6+
import java.io.File as JFile
7+
import java.io.IOException
8+
import java.io.InputStreamReader
9+
import java.io.PrintStream
910
import java.nio.charset.StandardCharsets
10-
import java.util.concurrent.atomic.AtomicBoolean
11+
import java.nio.file.Paths
1112
import java.util.concurrent.TimeoutException
12-
13-
import scala.concurrent.duration.Duration
14-
import scala.concurrent.{ Await, Future }
15-
import scala.concurrent.ExecutionContext.Implicits.global
13+
import java.util.concurrent.atomic.AtomicBoolean
1614
import scala.collection.mutable
1715
import scala.compiletime.uninitialized
16+
import scala.concurrent.Await
17+
import scala.concurrent.ExecutionContext.Implicits.global
18+
import scala.concurrent.Future
19+
import scala.concurrent.duration.Duration
20+
import scala.jdk.CollectionConverters.*
21+
import scala.language.unsafeNulls
1822

1923
/** Vulpix spawns JVM subprocesses (`numberOfSlaves`) in order to run tests
2024
* without compromising the main JVM
@@ -48,8 +52,11 @@ trait RunnerOrchestration {
4852
/** Destroy and respawn process after each test */
4953
def safeMode: Boolean
5054

55+
/** Open JDI connection for testing the debugger */
56+
def debugMode: Boolean = false
57+
5158
/** Running a `Test` class's main method from the specified `dir` */
52-
def runMain(classPath: String, toolArgs: ToolArgs)(implicit summaryReport: SummaryReporting): Status =
59+
def runMain(classPath: String)(implicit summaryReport: SummaryReporting): Status =
5360
monitor.runMain(classPath)
5461

5562
/** Kill all processes */
@@ -70,7 +77,7 @@ trait RunnerOrchestration {
7077
def runMain(classPath: String)(implicit summaryReport: SummaryReporting): Status =
7178
withRunner(_.runMain(classPath))
7279

73-
private class Runner(private var process: Process) {
80+
private class Runner(private var process: RunnerProcess) {
7481
private var childStdout: BufferedReader = uninitialized
7582
private var childStdin: PrintStream = uninitialized
7683

@@ -114,7 +121,7 @@ trait RunnerOrchestration {
114121
}
115122

116123
if (childStdin eq null)
117-
childStdin = new PrintStream(process.getOutputStream, /* autoFlush = */ true)
124+
childStdin = new PrintStream(process.getOutputStream(), /* autoFlush = */ true)
118125

119126
// pass file to running process
120127
childStdin.println(classPath)
@@ -124,7 +131,7 @@ trait RunnerOrchestration {
124131
val sb = new StringBuilder
125132

126133
if (childStdout eq null)
127-
childStdout = new BufferedReader(new InputStreamReader(process.getInputStream, StandardCharsets.UTF_8))
134+
childStdout = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))
128135

129136
var childOutput: String = childStdout.readLine()
130137

@@ -138,7 +145,7 @@ trait RunnerOrchestration {
138145
childOutput = childStdout.readLine()
139146
}
140147

141-
if (process.isAlive && childOutput != null) Success(sb.toString)
148+
if (process.isAlive() && childOutput != null) Success(sb.toString)
142149
else Failure(sb.toString)
143150
}
144151

@@ -159,18 +166,33 @@ trait RunnerOrchestration {
159166
}
160167
}
161168

169+
// A Java process and its JDI port for debugging, if debugMode is enabled.
170+
private class RunnerProcess(p: Process, val port: Option[Int]):
171+
export p.*
172+
162173
/** Create a process which has the classpath of the `ChildJVMMain` and the
163174
* scala library.
164175
*/
165-
private def createProcess: Process = {
176+
private def createProcess: RunnerProcess = {
166177
val url = classOf[ChildJVMMain].getProtectionDomain.getCodeSource.getLocation
167178
val cp = Paths.get(url.toURI).toString + JFile.pathSeparator + Properties.scalaLibrary
168179
val javaBin = Paths.get(sys.props("java.home"), "bin", "java").toString
169-
new ProcessBuilder(javaBin, "-Dfile.encoding=UTF-8", "-Duser.language=en", "-Duser.country=US", "-Xmx1g", "-cp", cp, "dotty.tools.vulpix.ChildJVMMain")
180+
val args = Seq("-Dfile.encoding=UTF-8", "-Duser.language=en", "-Duser.country=US", "-Xmx1g", "-cp", cp) ++
181+
(if debugMode then Seq("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,quiet=n") else Seq.empty)
182+
val command = (javaBin +: args) :+ "dotty.tools.vulpix.ChildJVMMain"
183+
val process = new ProcessBuilder(command*)
170184
.redirectErrorStream(true)
171185
.redirectInput(ProcessBuilder.Redirect.PIPE)
172186
.redirectOutput(ProcessBuilder.Redirect.PIPE)
173187
.start()
188+
189+
val jdiPort = Option.when(debugMode):
190+
val reader = new BufferedReader(new InputStreamReader(process.getInputStream, StandardCharsets.UTF_8))
191+
reader.readLine() match
192+
case s"Listening for transport dt_socket at address: $port" => port.toInt
193+
case line => throw new IOException(s"Failed getting JDI port of child JVM: got $line")
194+
195+
RunnerProcess(process, jdiPort)
174196
}
175197

176198
private val freeRunners = mutable.Queue.empty[Runner]

0 commit comments

Comments
 (0)