@@ -2,19 +2,23 @@ package dotty
2
2
package tools
3
3
package vulpix
4
4
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
9
10
import java .nio .charset .StandardCharsets
10
- import java .util . concurrent . atomic . AtomicBoolean
11
+ import java .nio . file . Paths
11
12
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
16
14
import scala .collection .mutable
17
15
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
18
22
19
23
/** Vulpix spawns JVM subprocesses (`numberOfSlaves`) in order to run tests
20
24
* without compromising the main JVM
@@ -48,8 +52,11 @@ trait RunnerOrchestration {
48
52
/** Destroy and respawn process after each test */
49
53
def safeMode : Boolean
50
54
55
+ /** Open JDI connection for testing the debugger */
56
+ def debugMode : Boolean = false
57
+
51
58
/** 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 =
53
60
monitor.runMain(classPath)
54
61
55
62
/** Kill all processes */
@@ -70,7 +77,7 @@ trait RunnerOrchestration {
70
77
def runMain (classPath : String )(implicit summaryReport : SummaryReporting ): Status =
71
78
withRunner(_.runMain(classPath))
72
79
73
- private class Runner (private var process : Process ) {
80
+ private class Runner (private var process : RunnerProcess ) {
74
81
private var childStdout : BufferedReader = uninitialized
75
82
private var childStdin : PrintStream = uninitialized
76
83
@@ -114,7 +121,7 @@ trait RunnerOrchestration {
114
121
}
115
122
116
123
if (childStdin eq null )
117
- childStdin = new PrintStream (process.getOutputStream, /* autoFlush = */ true )
124
+ childStdin = new PrintStream (process.getOutputStream() , /* autoFlush = */ true )
118
125
119
126
// pass file to running process
120
127
childStdin.println(classPath)
@@ -124,7 +131,7 @@ trait RunnerOrchestration {
124
131
val sb = new StringBuilder
125
132
126
133
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 ))
128
135
129
136
var childOutput : String = childStdout.readLine()
130
137
@@ -138,7 +145,7 @@ trait RunnerOrchestration {
138
145
childOutput = childStdout.readLine()
139
146
}
140
147
141
- if (process.isAlive && childOutput != null ) Success (sb.toString)
148
+ if (process.isAlive() && childOutput != null ) Success (sb.toString)
142
149
else Failure (sb.toString)
143
150
}
144
151
@@ -159,18 +166,33 @@ trait RunnerOrchestration {
159
166
}
160
167
}
161
168
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
+
162
173
/** Create a process which has the classpath of the `ChildJVMMain` and the
163
174
* scala library.
164
175
*/
165
- private def createProcess : Process = {
176
+ private def createProcess : RunnerProcess = {
166
177
val url = classOf [ChildJVMMain ].getProtectionDomain.getCodeSource.getLocation
167
178
val cp = Paths .get(url.toURI).toString + JFile .pathSeparator + Properties .scalaLibrary
168
179
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* )
170
184
.redirectErrorStream(true )
171
185
.redirectInput(ProcessBuilder .Redirect .PIPE )
172
186
.redirectOutput(ProcessBuilder .Redirect .PIPE )
173
187
.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)
174
196
}
175
197
176
198
private val freeRunners = mutable.Queue .empty[Runner ]
0 commit comments