| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library scheduled_test.scheduled_process; | 5 library scheduled_test.scheduled_process; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:convert'; | 8 import 'dart:convert'; |
| 9 import 'dart:io'; | 9 import 'dart:io'; |
| 10 | 10 |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 58 | 58 |
| 59 /// A canceller that controls both [_stderr] and [_stderrLog]. | 59 /// A canceller that controls both [_stderr] and [_stderrLog]. |
| 60 StreamCanceller _stderrCanceller; | 60 StreamCanceller _stderrCanceller; |
| 61 | 61 |
| 62 /// The exit code of the process that's scheduled to run. This will naturally | 62 /// The exit code of the process that's scheduled to run. This will naturally |
| 63 /// only complete once the process has terminated. | 63 /// only complete once the process has terminated. |
| 64 ValueFuture<int> _exitCode; | 64 ValueFuture<int> _exitCode; |
| 65 | 65 |
| 66 /// Whether the user has scheduled the end of this process by calling either | 66 /// Whether the user has scheduled the end of this process by calling either |
| 67 /// [shouldExit] or [kill]. | 67 /// [shouldExit] or [kill]. |
| 68 var _endScheduled = false; | 68 bool get _endScheduled => _scheduledExitTask != null; |
| 69 | 69 |
| 70 /// The task that runs immediately before this process is scheduled to end. If | 70 /// The task where this process is scheduled to exit -- either by waiting to |
| 71 /// the process ends during this task, we treat that as expected. | 71 /// exit ([shouldExit]) or by killing the process ([kill]). |
| 72 Task _taskBeforeEnd; | 72 /// |
| 73 /// It's legal for the process to exit before this task runs. This can happen |
| 74 /// for example if there's still standard output to read from the process |
| 75 /// after it exits. |
| 76 Task _scheduledExitTask; |
| 73 | 77 |
| 74 /// Whether the process is expected to terminate at this point. | 78 /// The task during which the process actually exited. |
| 75 var _endExpected = false; | 79 Task _actualExitTask; |
| 76 | 80 |
| 77 /// Schedules a process to start. [executable], [arguments], | 81 /// Schedules a process to start. [executable], [arguments], |
| 78 /// [workingDirectory], and [environment] have the same meaning as for | 82 /// [workingDirectory], and [environment] have the same meaning as for |
| 79 /// [Process.start]. [description] is a string description of this process; it | 83 /// [Process.start]. [description] is a string description of this process; it |
| 80 /// defaults to the command-line invocation. [encoding] is the [Encoding] that | 84 /// defaults to the command-line invocation. [encoding] is the [Encoding] that |
| 81 /// will be used for the process's input and output. | 85 /// will be used for the process's input and output. |
| 82 /// | 86 /// |
| 83 /// [executable], [arguments], [workingDirectory], and [environment] may be | 87 /// [executable], [arguments], [workingDirectory], and [environment] may be |
| 84 /// either a [Future] or a concrete value. If any are [Future]s, the process | 88 /// either a [Future] or a concrete value. If any are [Future]s, the process |
| 85 /// won't start until the [Future]s have completed. In addition, [arguments] | 89 /// won't start until the [Future]s have completed. In addition, [arguments] |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 166 | 170 |
| 167 /// Listens for [_process] to exit and passes the exit code to | 171 /// Listens for [_process] to exit and passes the exit code to |
| 168 /// [exitCodeCompleter]. If the process completes earlier than expected, an | 172 /// [exitCodeCompleter]. If the process completes earlier than expected, an |
| 169 /// exception will be signaled to the schedule. | 173 /// exception will be signaled to the schedule. |
| 170 void _handleExit(Completer exitCodeCompleter) { | 174 void _handleExit(Completer exitCodeCompleter) { |
| 171 // We purposefully avoid using wrapFuture here. If an error occurs while a | 175 // We purposefully avoid using wrapFuture here. If an error occurs while a |
| 172 // process is running, we want the schedule to move to the onException | 176 // process is running, we want the schedule to move to the onException |
| 173 // queue where the process will be killed, rather than blocking the tasks | 177 // queue where the process will be killed, rather than blocking the tasks |
| 174 // queue waiting for the process to exit. | 178 // queue waiting for the process to exit. |
| 175 _process.then((p) => Chain.track(p.exitCode)).then((exitCode) { | 179 _process.then((p) => Chain.track(p.exitCode)).then((exitCode) { |
| 176 if (_endExpected) { | 180 _actualExitTask = currentSchedule.currentTask; |
| 177 exitCodeCompleter.complete(exitCode); | 181 exitCodeCompleter.complete(exitCode); |
| 178 return; | |
| 179 } | |
| 180 | |
| 181 wrapFuture(pumpEventQueue().then((_) { | |
| 182 if (currentSchedule.currentTask != _taskBeforeEnd) return null; | |
| 183 // If we're one task before the end was scheduled, wait for that task | |
| 184 // to complete and pump the event queue so that _endExpected will be | |
| 185 // set. | |
| 186 return _taskBeforeEnd.result.then((_) => pumpEventQueue()); | |
| 187 }).then((_) { | |
| 188 exitCodeCompleter.complete(exitCode); | |
| 189 | |
| 190 if (!_endExpected) { | |
| 191 fail("Process '$description' ended earlier than scheduled " | |
| 192 "with exit code $exitCode."); | |
| 193 } | |
| 194 }), "waiting to reach shouldExit() or kill() for process " | |
| 195 "'$description'"); | |
| 196 }); | 182 }); |
| 197 } | 183 } |
| 198 | 184 |
| 199 /// Converts a stream of bytes to a stream of lines and returns that along | 185 /// Converts a stream of bytes to a stream of lines and returns that along |
| 200 /// with a [StreamCanceller] controlling it. | 186 /// with a [StreamCanceller] controlling it. |
| 201 Pair<Stream<String>, StreamCanceller> _lineStreamWithCanceller( | 187 Pair<Stream<String>, StreamCanceller> _lineStreamWithCanceller( |
| 202 Future<Stream<List<int>>> streamFuture) { | 188 Future<Stream<List<int>>> streamFuture) { |
| 203 return streamWithCanceller(futureStream(streamFuture) | 189 return streamWithCanceller(futureStream(streamFuture) |
| 204 .handleError(currentSchedule.signalError) | 190 .handleError(currentSchedule.signalError) |
| 205 .map((chunk) { | 191 .map((chunk) { |
| (...skipping 11 matching lines...) Expand all Loading... |
| 217 void _scheduleExceptionCleanup() { | 203 void _scheduleExceptionCleanup() { |
| 218 currentSchedule.onException.schedule(() { | 204 currentSchedule.onException.schedule(() { |
| 219 _stdoutCanceller(); | 205 _stdoutCanceller(); |
| 220 _stderrCanceller(); | 206 _stderrCanceller(); |
| 221 | 207 |
| 222 if (!_process.hasValue) return null; | 208 if (!_process.hasValue) return null; |
| 223 | 209 |
| 224 var killedPrematurely = false; | 210 var killedPrematurely = false; |
| 225 if (!_exitCode.hasValue) { | 211 if (!_exitCode.hasValue) { |
| 226 killedPrematurely = true; | 212 killedPrematurely = true; |
| 227 _endExpected = true; | |
| 228 _process.value.kill(ProcessSignal.SIGKILL); | 213 _process.value.kill(ProcessSignal.SIGKILL); |
| 229 // Ensure that the onException queue waits for the process to actually | 214 // Ensure that the onException queue waits for the process to actually |
| 230 // exit after being killed. | 215 // exit after being killed. |
| 231 wrapFuture(_process.value.exitCode, "waiting for process " | 216 wrapFuture(_process.value.exitCode, "waiting for process " |
| 232 "'$description' to die"); | 217 "'$description' to die"); |
| 233 } | 218 } |
| 234 | 219 |
| 235 return Future.wait([ | 220 return Future.wait([ |
| 236 _stdoutLog.toList(), | 221 _stdoutLog.toList(), |
| 237 _stderrLog.toList() | 222 _stderrLog.toList() |
| 238 ]).then((results) { | 223 ]).then((results) { |
| 239 var stdout = results[0].join("\n"); | 224 var stdout = results[0].join("\n"); |
| 240 var stderr = results[1].join("\n"); | 225 var stderr = results[1].join("\n"); |
| 241 | 226 |
| 242 var exitDescription = killedPrematurely | 227 var exitDescription; |
| 243 ? "Process was killed prematurely." | 228 if (killedPrematurely) { |
| 244 : "Process exited with exit code ${_exitCode.value}."; | 229 exitDescription = "Process was killed prematurely."; |
| 230 } else { |
| 231 exitDescription = "Process exited with exit code ${_exitCode.value}"; |
| 232 if (_actualExitTask != _scheduledExitTask) { |
| 233 var taskString = _actualExitTask.toString(); |
| 234 if (taskString.contains("\n")) { |
| 235 exitDescription += " in task:\n${prefixLines(taskString)}"; |
| 236 } else { |
| 237 exitDescription += " in task $taskString"; |
| 238 } |
| 239 } |
| 240 exitDescription += "."; |
| 241 } |
| 242 |
| 245 currentSchedule.addDebugInfo( | 243 currentSchedule.addDebugInfo( |
| 246 "Results of running '$description':\n" | 244 "Results of running '$description':\n" |
| 247 "$exitDescription\n" | 245 "$exitDescription\n" |
| 248 "Standard output:\n" | 246 "Standard output:\n" |
| 249 "${prefixLines(stdout)}\n" | 247 "${prefixLines(stdout)}\n" |
| 250 "Standard error:\n" | 248 "Standard error:\n" |
| 251 "${prefixLines(stderr)}"); | 249 "${prefixLines(stderr)}"); |
| 252 }); | 250 }); |
| 253 }, "cleaning up process '$description'"); | 251 }, "cleaning up process '$description'"); |
| 254 } | 252 } |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 322 schedule(() => _process.then((p) => p.stdin.close()), | 320 schedule(() => _process.then((p) => p.stdin.close()), |
| 323 "closing stdin for process '$description'"); | 321 "closing stdin for process '$description'"); |
| 324 } | 322 } |
| 325 | 323 |
| 326 /// Kills the process, and waits until it's dead. | 324 /// Kills the process, and waits until it's dead. |
| 327 void kill() { | 325 void kill() { |
| 328 if (_endScheduled) { | 326 if (_endScheduled) { |
| 329 throw new StateError("shouldExit() or kill() already called."); | 327 throw new StateError("shouldExit() or kill() already called."); |
| 330 } | 328 } |
| 331 | 329 |
| 332 _endScheduled = true; | |
| 333 _taskBeforeEnd = currentSchedule.tasks.contents.last; | |
| 334 schedule(() { | 330 schedule(() { |
| 335 _endExpected = true; | |
| 336 return _process | 331 return _process |
| 337 .then((p) => p.kill(ProcessSignal.SIGKILL)) | 332 .then((p) => p.kill(ProcessSignal.SIGKILL)) |
| 338 .then((_) => _exitCode); | 333 .then((_) => _exitCode); |
| 339 }, "waiting for process '$description' to die"); | 334 }, "waiting for process '$description' to die"); |
| 335 _scheduledExitTask = currentSchedule.tasks.contents.last; |
| 340 } | 336 } |
| 341 | 337 |
| 342 /// Waits for the process to exit, and verifies that the exit code matches | 338 /// Waits for the process to exit, and verifies that the exit code matches |
| 343 /// [expectedExitCode] (if given). | 339 /// [expectedExitCode] (if given). |
| 344 void shouldExit([int expectedExitCode]) { | 340 void shouldExit([int expectedExitCode]) { |
| 345 if (_endScheduled) { | 341 if (_endScheduled) { |
| 346 throw new StateError("shouldExit() or kill() already called."); | 342 throw new StateError("shouldExit() or kill() already called."); |
| 347 } | 343 } |
| 348 | 344 |
| 349 _endScheduled = true; | |
| 350 _taskBeforeEnd = currentSchedule.tasks.contents.last; | |
| 351 schedule(() { | 345 schedule(() { |
| 352 _endExpected = true; | |
| 353 return _exitCode.then((exitCode) { | 346 return _exitCode.then((exitCode) { |
| 354 if (expectedExitCode != null) { | 347 if (expectedExitCode != null) { |
| 355 expect(exitCode, equals(expectedExitCode)); | 348 expect(exitCode, equals(expectedExitCode)); |
| 356 } | 349 } |
| 357 }); | 350 }); |
| 358 }, "waiting for process '$description' to exit"); | 351 }, "waiting for process '$description' to exit"); |
| 352 _scheduledExitTask = currentSchedule.tasks.contents.last; |
| 359 } | 353 } |
| 360 } | 354 } |
| OLD | NEW |