@@ -2,19 +2,23 @@ package dotty
22package tools
33package 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
910import java .nio .charset .StandardCharsets
10- import java .util . concurrent . atomic . AtomicBoolean
11+ import java .nio . file . Paths
1112import 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
1614import scala .collection .mutable
1715import 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