@@ -59,16 +59,19 @@ trait RunnerOrchestration {
59
59
def runMain (classPath : String , toolArgs : ToolArgs )(implicit summaryReport : SummaryReporting ): Status =
60
60
monitor.runMain(classPath)
61
61
62
+ /** Each method of Debuggee can be called only once, in the order of definition.*/
62
63
trait Debuggee :
63
- /** the jdi port to connect the debugger */
64
- def jdiPort : Int
64
+ /** read the jdi port to connect the debugger */
65
+ def readJdiPort () : Int
65
66
/** start the main method in the background */
66
67
def launch (): Unit
68
+ /** wait until the end of the main method */
69
+ def exit (): Status
67
70
68
71
/** Provide a Debuggee for debugging the Test class's main method
69
- * @param f the debugging flow: set breakpoints, launch main class, pause, step, evaluate etc
72
+ * @param f the debugging flow: set breakpoints, launch main class, pause, step, evaluate, exit etc
70
73
*/
71
- def debugMain (classPath : String )(f : Debuggee => Unit )(implicit summaryReport : SummaryReporting ): Status =
74
+ def debugMain (classPath : String )(f : Debuggee => Unit )(implicit summaryReport : SummaryReporting ): Unit =
72
75
assert(debugMode, " debugMode is disabled" )
73
76
monitor.debugMain(classPath)(f)
74
77
@@ -92,14 +95,31 @@ trait RunnerOrchestration {
92
95
def runMain (classPath : String )(implicit summaryReport : SummaryReporting ): Status =
93
96
withRunner(_.runMain(classPath))
94
97
95
- def debugMain (classPath : String )(f : Debuggee => Unit )(implicit summaryReport : SummaryReporting ): Status =
98
+ def debugMain (classPath : String )(f : Debuggee => Unit )(implicit summaryReport : SummaryReporting ): Unit =
96
99
withRunner(_.debugMain(classPath)(f))
97
100
98
- // A JVM process and its JDI port for debugging, if debugMode is enabled.
99
- private class RunnerProcess (p : Process , val jdiPort : Option [Int ]):
100
- val stdout = new BufferedReader (new InputStreamReader (p.getInputStream(), StandardCharsets .UTF_8 ))
101
- val stdin = new PrintStream (p.getOutputStream(), /* autoFlush = */ true )
101
+ private class RunnerProcess (p : Process ):
102
+ private val stdout = new BufferedReader (new InputStreamReader (p.getInputStream(), StandardCharsets .UTF_8 ))
103
+ private val stdin = new PrintStream (p.getOutputStream(), /* autoFlush = */ true )
104
+
105
+ def readLine (): String =
106
+ stdout.readLine() match
107
+ case s " Listening for transport dt_socket at address: $port" =>
108
+ throw new IOException (
109
+ s " Unexpected transport dt_socket message. " +
110
+ " The port is going to be lost and no debugger will be able to connect."
111
+ )
112
+ case line => line
113
+
114
+ def printLine (line : String ): Unit = stdin.println(line)
115
+
116
+ def getJdiPort (): Int =
117
+ stdout.readLine() match
118
+ case s " Listening for transport dt_socket at address: $port" => port.toInt
119
+ case line => throw new IOException (s " Failed getting JDI port of child JVM: got $line" )
120
+
102
121
export p .{exitValue , isAlive , destroy }
122
+ end RunnerProcess
103
123
104
124
private class Runner (private var process : RunnerProcess ):
105
125
@@ -111,7 +131,7 @@ trait RunnerOrchestration {
111
131
*/
112
132
def isAlive : Boolean =
113
133
try { process.exitValue(); false }
114
- catch { case _ : IllegalThreadStateException => true }
134
+ catch case _ : IllegalThreadStateException => true
115
135
116
136
/** Destroys the underlying process and kills IO streams */
117
137
def kill (): Unit =
@@ -123,51 +143,50 @@ trait RunnerOrchestration {
123
143
assert(process ne null , " Runner was killed and then reused without setting a new process" )
124
144
awaitStatusOrRespawn(startMain(classPath))
125
145
126
- def debugMain (classPath : String )(f : Debuggee => Unit ): Status =
146
+ def debugMain (classPath : String )(f : Debuggee => Unit ): Unit =
127
147
assert(process ne null , " Runner was killed and then reused without setting a new process" )
128
- assert(process.jdiPort.isDefined, " Runner has not been started in debug mode" )
129
148
130
- var mainFuture : Future [Status ] = null
131
149
val debuggee = new Debuggee :
132
- def jdiPort : Int = process.jdiPort.get
133
- def launch (): Unit =
134
- mainFuture = startMain(classPath)
150
+ var mainFuture : Future [Status ] = null
151
+ def readJdiPort (): Int = process.getJdiPort()
152
+ def launch (): Unit = mainFuture = startMain(classPath)
153
+ def exit (): Status =
154
+ awaitStatusOrRespawn(mainFuture)
135
155
136
156
try f(debuggee)
137
- catch case debugFailure : Throwable =>
138
- if mainFuture != null then awaitStatusOrRespawn(mainFuture)
139
- throw debugFailure
140
-
141
- assert(mainFuture ne null , " main method not started by debugger" )
142
- awaitStatusOrRespawn(mainFuture)
157
+ catch case e : Throwable =>
158
+ // if debugging failed it is safer to respawn a new process
159
+ respawn()
160
+ throw e
143
161
end debugMain
144
162
145
163
private def startMain (classPath : String ): Future [Status ] =
146
164
// pass classpath to running process
147
- process.stdin.println (classPath)
165
+ process.printLine (classPath)
148
166
149
167
// Create a future reading the object:
150
168
Future :
151
169
val sb = new StringBuilder
152
170
153
- var childOutput : String = process.stdout. readLine()
171
+ var childOutput : String = process.readLine()
154
172
155
173
// Discard all messages until the test starts
156
174
while (childOutput != ChildJVMMain .MessageStart && childOutput != null )
157
- childOutput = process.stdout. readLine()
158
- childOutput = process.stdout. readLine()
175
+ childOutput = process.readLine()
176
+ childOutput = process.readLine()
159
177
160
178
while childOutput != ChildJVMMain .MessageEnd && childOutput != null do
161
179
sb.append(childOutput).append(System .lineSeparator)
162
- childOutput = process.stdout. readLine()
180
+ childOutput = process.readLine()
163
181
164
- if ( process.isAlive() && childOutput != null ) Success (sb.toString)
182
+ if process.isAlive() && childOutput != null then Success (sb.toString)
165
183
else Failure (sb.toString)
166
184
end startMain
167
185
168
186
// wait status of the main class execution, respawn if failure or timeout
169
187
private def awaitStatusOrRespawn (future : Future [Status ]): Status =
170
- val status = try Await .result(future, maxDuration)
188
+ val status =
189
+ try Await .result(future, maxDuration)
171
190
catch case _ : TimeoutException => Timeout
172
191
// handle failures
173
192
status match
@@ -178,13 +197,13 @@ trait RunnerOrchestration {
178
197
// Makes the encapsulating RunnerMonitor spawn a new runner
179
198
private def respawn (): Unit =
180
199
process.destroy()
181
- process = createProcess
200
+ process = createProcess()
182
201
end Runner
183
202
184
203
/** Create a process which has the classpath of the `ChildJVMMain` and the
185
204
* scala library.
186
205
*/
187
- private def createProcess : RunnerProcess = {
206
+ private def createProcess () : RunnerProcess =
188
207
val url = classOf [ChildJVMMain ].getProtectionDomain.getCodeSource.getLocation
189
208
val cp = Paths .get(url.toURI).toString + JFile .pathSeparator + Properties .scalaLibrary
190
209
val javaBin = Paths .get(sys.props(" java.home" ), " bin" , " java" ).toString
@@ -196,15 +215,7 @@ trait RunnerOrchestration {
196
215
.redirectInput(ProcessBuilder .Redirect .PIPE )
197
216
.redirectOutput(ProcessBuilder .Redirect .PIPE )
198
217
.start()
199
-
200
- val jdiPort = Option .when(debugMode):
201
- val reader = new BufferedReader (new InputStreamReader (process.getInputStream, StandardCharsets .UTF_8 ))
202
- reader.readLine() match
203
- case s " Listening for transport dt_socket at address: $port" => port.toInt
204
- case line => throw new IOException (s " Failed getting JDI port of child JVM: got $line" )
205
-
206
- RunnerProcess (process, jdiPort)
207
- }
218
+ RunnerProcess (process)
208
219
209
220
private val freeRunners = mutable.Queue .empty[Runner ]
210
221
private val busyRunners = mutable.Set .empty[Runner ]
@@ -213,7 +224,7 @@ trait RunnerOrchestration {
213
224
while (freeRunners.isEmpty && busyRunners.size >= numberOfSlaves) wait()
214
225
215
226
val runner =
216
- if (freeRunners.isEmpty) new Runner (createProcess)
227
+ if (freeRunners.isEmpty) new Runner (createProcess() )
217
228
else freeRunners.dequeue()
218
229
busyRunners += runner
219
230
0 commit comments