Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(290)

Side by Side Diff: pkg/scheduled_test/lib/scheduled_process.dart

Issue 812253002: Delete a bunch of packages that are now on GitHub. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Un-delete http Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/scheduled_test/lib/descriptor.dart ('k') | pkg/scheduled_test/lib/scheduled_server.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 library scheduled_test.scheduled_process;
6
7 import 'dart:async';
8 import 'dart:convert';
9 import 'dart:io';
10
11 import 'package:stack_trace/stack_trace.dart';
12
13 import 'scheduled_stream.dart';
14 import 'scheduled_test.dart';
15 import 'src/utils.dart';
16 import 'src/value_future.dart';
17
18 /// A class representing a [Process] that is scheduled to run in the course of
19 /// the test. This class allows actions on the process to be scheduled
20 /// synchronously. All operations on this class are scheduled.
21 ///
22 /// Before running the test, either [shouldExit] or [kill] must be called on
23 /// this to ensure that the process terminates when expected. Note that [kill]
24 /// is using SIGKILL, to ensure the process is killed on Mac OS X (an early
25 /// SIGTERM on Mac OS X may be ignored).
26 ///
27 /// If the test fails, this will automatically print out any stdout and stderr
28 /// from the process to aid debugging.
29 class ScheduledProcess {
30 /// A description of the process. Used for error reporting.
31 String get description => _description;
32 String _description;
33
34 /// Whether a description was passed explicitly by the user.
35 bool _explicitDescription;
36
37 /// The encoding used for the process's input and output streams.
38 final Encoding _encoding;
39
40 /// The process that's scheduled to run.
41 ValueFuture<Process> _process;
42
43 /// A fork of [_stdout] that records the standard output of the process. Used
44 /// for debugging information.
45 Stream<String> _stdoutLog;
46
47 /// A line-by-line view of the standard output stream of the process.
48 ScheduledStream<String> get stdout => _stdout;
49 ScheduledStream<String> _stdout;
50
51 /// A canceller that controls both [_stdout] and [_stdoutLog].
52 StreamCanceller _stdoutCanceller;
53
54 /// A fork of [_stderr] that records the standard error of the process. Used
55 /// for debugging information.
56 Stream<String> _stderrLog;
57
58 /// A line-by-line view of the standard error stream of the process.
59 ScheduledStream<String> get stderr => _stderr;
60 ScheduledStream<String> _stderr;
61
62 /// A canceller that controls both [_stderr] and [_stderrLog].
63 StreamCanceller _stderrCanceller;
64
65 /// The exit code of the process that's scheduled to run. This will naturally
66 /// only complete once the process has terminated.
67 ValueFuture<int> _exitCode;
68
69 /// Whether the user has scheduled the end of this process by calling either
70 /// [shouldExit] or [kill].
71 bool get _endScheduled => _scheduledExitTask != null;
72
73 /// The task where this process is scheduled to exit -- either by waiting to
74 /// exit ([shouldExit]) or by killing the process ([kill]).
75 ///
76 /// It's legal for the process to exit before this task runs. This can happen
77 /// for example if there's still standard output to read from the process
78 /// after it exits.
79 Task _scheduledExitTask;
80
81 /// The task during which the process actually exited.
82 Task _actualExitTask;
83
84 /// Schedules a process to start. [executable], [arguments],
85 /// [workingDirectory], and [environment] have the same meaning as for
86 /// [Process.start]. [description] is a string description of this process; it
87 /// defaults to the command-line invocation. [encoding] is the [Encoding] that
88 /// will be used for the process's input and output.
89 ///
90 /// [executable], [arguments], [workingDirectory], and [environment] may be
91 /// either a [Future] or a concrete value. If any are [Future]s, the process
92 /// won't start until the [Future]s have completed. In addition, [arguments]
93 /// may be a [List] containing a mix of strings and [Future]s.
94 ScheduledProcess.start(executable, arguments,
95 {workingDirectory, environment, String description,
96 Encoding encoding: UTF8})
97 : _encoding = encoding,
98 _explicitDescription = description != null,
99 _description = description {
100 assert(currentSchedule.state == ScheduleState.SET_UP);
101
102 _updateDescription(executable, arguments);
103
104 _scheduleStartProcess(executable, arguments, workingDirectory, environment);
105
106 _scheduleExceptionCleanup();
107
108 var stdoutWithCanceller = _lineStreamWithCanceller(
109 _process.then((p) => Chain.track(p.stdout)));
110 _stdoutCanceller = stdoutWithCanceller.last;
111 _stdoutLog = stdoutWithCanceller.first;
112
113 var stderrWithCanceller = _lineStreamWithCanceller(
114 _process.then((p) => Chain.track(p.stderr)));
115 _stderrCanceller = stderrWithCanceller.last;
116 _stderrLog = stderrWithCanceller.first;
117
118 _stdout = new ScheduledStream<String>(stdoutStream());
119 _stderr = new ScheduledStream<String>(stderrStream());
120 }
121
122 /// Updates [_description] to reflect [executable] and [arguments], which are
123 /// the same values as in [start].
124 void _updateDescription(executable, arguments) {
125 if (_explicitDescription) return;
126 if (executable is Future) {
127 _description = "future process";
128 } else if (arguments is Future || arguments.any((e) => e is Future)) {
129 _description = executable;
130 } else {
131 _description = "$executable ${arguments.map((a) => '"$a"').join(' ')}";
132 }
133 }
134
135 /// Schedules the process to start and sets [_process].
136 void _scheduleStartProcess(executable,
137 arguments,
138 workingDirectory,
139 environment) {
140 var exitCodeCompleter = new Completer();
141 _exitCode = new ValueFuture(exitCodeCompleter.future);
142
143 _process = new ValueFuture(schedule(() {
144 if (!_endScheduled) {
145 throw new StateError("Scheduled process '$description' must "
146 "have shouldExit() or kill() called before the test is run.");
147 }
148
149 _handleExit(exitCodeCompleter);
150
151 return Future.wait([
152 new Future.sync(() => executable),
153 awaitObject(arguments),
154 new Future.sync(() => workingDirectory),
155 new Future.sync(() => environment)
156 ]).then((results) {
157 executable = results[0];
158 arguments = results[1];
159 workingDirectory = results[2];
160 environment = results[3];
161 _updateDescription(executable, arguments);
162 return Chain.track(
163 Process.start(executable,
164 arguments,
165 workingDirectory: workingDirectory,
166 environment: environment)).then((process) {
167 process.stdin.encoding = UTF8;
168 return process;
169 });
170 });
171 }, "starting process '$description'"));
172 }
173
174 /// Listens for [_process] to exit and passes the exit code to
175 /// [exitCodeCompleter]. If the process completes earlier than expected, an
176 /// exception will be signaled to the schedule.
177 void _handleExit(Completer exitCodeCompleter) {
178 // We purposefully avoid using wrapFuture here. If an error occurs while a
179 // process is running, we want the schedule to move to the onException
180 // queue where the process will be killed, rather than blocking the tasks
181 // queue waiting for the process to exit.
182 _process.then((p) => Chain.track(p.exitCode)).then((exitCode) {
183 _actualExitTask = currentSchedule.currentTask;
184 exitCodeCompleter.complete(exitCode);
185 });
186 }
187
188 /// Converts a stream of byte lists to a stream of lines and returns that
189 /// along with a [StreamCanceller] controlling it.
190 Pair<Stream<String>, StreamCanceller> _lineStreamWithCanceller(
191 Future<Stream<List<int>>> streamFuture) {
192 // Ignore errors from the future. They'll be reported through [schedule].
193 streamFuture = streamFuture.catchError((_) => new Stream.fromIterable([]));
194 return streamWithCanceller(futureStream(streamFuture)
195 .handleError(currentSchedule.signalError)
196 .map((chunk) {
197 // Whenever the process produces any sort of output, reset the schedule's
198 // timer.
199 currentSchedule.heartbeat();
200 return chunk;
201 })
202 .transform(_encoding.decoder)
203 .transform(new LineSplitter()));
204 }
205
206 /// Schedule an exception handler that will clean up the process and provide
207 /// debug information if an error occurs.
208 void _scheduleExceptionCleanup() {
209 currentSchedule.onException.schedule(() {
210 _stdoutCanceller();
211 _stderrCanceller();
212
213 if (!_process.hasValue) return null;
214
215 var killedPrematurely = false;
216 if (!_exitCode.hasValue) {
217 killedPrematurely = true;
218 _process.value.kill(ProcessSignal.SIGKILL);
219 // Ensure that the onException queue waits for the process to actually
220 // exit after being killed.
221 wrapFuture(_process.value.exitCode, "waiting for process "
222 "'$description' to die");
223 }
224
225 return Future.wait([
226 _stdoutLog.toList(),
227 _stderrLog.toList()
228 ]).then((results) {
229 var stdout = results[0].join("\n");
230 var stderr = results[1].join("\n");
231
232 var exitDescription;
233 if (killedPrematurely) {
234 exitDescription = "Process was killed prematurely.";
235 } else {
236 exitDescription = "Process exited with exit code ${_exitCode.value}";
237 if (_actualExitTask != _scheduledExitTask) {
238 var taskString = _actualExitTask.toString();
239 if (taskString.contains("\n")) {
240 exitDescription += " in task:\n${prefixLines(taskString)}";
241 } else {
242 exitDescription += " in task $taskString";
243 }
244 }
245 exitDescription += ".";
246 }
247
248 currentSchedule.addDebugInfo(
249 "Results of running '$description':\n"
250 "$exitDescription\n"
251 "Standard output:\n"
252 "${prefixLines(stdout)}\n"
253 "Standard error:\n"
254 "${prefixLines(stderr)}");
255 });
256 }, "cleaning up process '$description'");
257 }
258
259 /// Returns a stream that will emit anything the process emits via the
260 /// process's standard output from now on.
261 ///
262 /// This stream will be independent from any other methods that deal with
263 /// standard output, including other calls to [stdoutStream].
264 ///
265 /// This can be overridden by subclasses to return a derived standard output
266 /// stream. This stream will then be used for [stdout] and [stderr].
267 Stream<String> stdoutStream() {
268 var pair = tee(_stdoutLog);
269 _stdoutLog = pair.first;
270 return pair.last;
271 }
272
273 /// Returns a stream that will emit anything the process emits via the
274 /// process's standard error from now on.
275 ///
276 /// This stream will be independent from any other methods that deal with
277 /// standard error, including other calls to [stderrStream].
278 Stream<String> stderrStream() {
279 var pair = tee(_stderrLog);
280 _stderrLog = pair.first;
281 return pair.last;
282 }
283
284 /// Writes [line] to the process as stdin.
285 void writeLine(String line) {
286 schedule(() {
287 return _process.then((p) => p.stdin.writeln('$line'));
288 }, "writing '$line' to stdin for process '$description'");
289 }
290
291 /// Closes the process's stdin stream.
292 void closeStdin() {
293 schedule(() => _process.then((p) => p.stdin.close()),
294 "closing stdin for process '$description'");
295 }
296
297 /// Kills the process, and waits until it's dead.
298 void kill() {
299 if (_endScheduled) {
300 throw new StateError("shouldExit() or kill() already called.");
301 }
302
303 schedule(() {
304 return _process
305 .then((p) => p.kill(ProcessSignal.SIGKILL))
306 .then((_) => _exitCode);
307 }, "waiting for process '$description' to die");
308 _scheduledExitTask = currentSchedule.tasks.contents.last;
309 }
310
311 /// Waits for the process to exit, and verifies that the exit code matches
312 /// [expectedExitCode] (if given).
313 void shouldExit([int expectedExitCode]) {
314 if (_endScheduled) {
315 throw new StateError("shouldExit() or kill() already called.");
316 }
317
318 schedule(() {
319 return _exitCode.then((exitCode) {
320 if (expectedExitCode != null) {
321 expect(exitCode, equals(expectedExitCode));
322 }
323 });
324 }, "waiting for process '$description' to exit");
325 _scheduledExitTask = currentSchedule.tasks.contents.last;
326 }
327 }
OLDNEW
« no previous file with comments | « pkg/scheduled_test/lib/descriptor.dart ('k') | pkg/scheduled_test/lib/scheduled_server.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698