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

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

Issue 12209073: Add a scheduled test library. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Make everything play nicely with the outside world. Created 7 years, 10 months 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
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 // TODO(nweiz): Keep track of and display multiple errors so there's more
6 // visibility into cascading errors.
7 // TODO(nweiz): Add timeouts to scheduled tests.
8 // TODO(nweiz): Add support for calling [schedule] while the schedule is already
9 // running.
10 // TODO(nweiz): Port the non-Pub-specific scheduled test libraries from Pub.
11 /// A package for writing readable tests of asynchronous behavior.
12 ///
13 /// This package works by building up a queue of asynchronous tasks called a
14 /// "schedule", then executing those tasks in order. This allows the tests to
15 /// read like synchronous, linear code, despite executing asynchronously.
16 ///
17 /// The `scheduled_test` package is built on top of `unittest`, and should be
18 /// imported instead of `unittest`. It provides its own version of [group],
19 /// [test], and [setUp], and re-exports most other APIs from unittest.
20 ///
21 /// To schedule a task, call the [schedule] function. For example:
22 ///
23 /// import 'package:scheduled_test/scheduled_test.dart';
24 ///
25 /// void main() {
26 /// test('writing to a file and reading it back should work', () {
27 /// schedule(() {
28 /// // The schedule won't proceed until the returned Future has
29 /// // completed.
30 /// return new File("output.txt").writeAsString("contents");
31 /// });
32 ///
33 /// schedule(() {
34 /// return new File("output.txt").readAsString().then((contents) {
35 /// // The normal unittest matchers can still be used.
36 /// expect(contents, equals("contents"));
37 /// });
38 /// });
39 /// });
40 /// }
41 ///
42 /// ## Setting Up and Tearing Down
43 ///
44 /// The `scheduled_test` package defines its own [setUp] method that works just
45 /// like the one in `unittest`. Tasks can be scheduled in [setUp]; they'll be
46 /// run before the tasks scheduled by tests in that group. [currentSchedule] is
47 /// also set in the [setUp] callback.
48 ///
49 /// This package doesn't have an explicit `tearDown` method. Instead, the
50 /// [currentSchedule.onComplete] and [currentSchedule.onException] task queues
51 /// can have tasks scheduled during [setUp]. For example:
52 ///
53 /// import 'package:scheduled_test/scheduled_test.dart';
54 ///
55 /// void main() {
56 /// var tempDir;
57 /// setUp(() {
58 /// schedule(() {
59 /// return createTempDir().then((dir) {
60 /// tempDir = dir;
61 /// });
62 /// });
63 ///
64 /// currentSchedule.onComplete.schedule(() => deleteDir(tempDir));
65 /// });
66 ///
67 /// // ...
68 /// }
69 ///
70 /// ## Passing Values Between Tasks
71 ///
72 /// It's often useful to use values computed in one task in other tasks that are
73 /// scheduled afterwards. There are two ways to do this. The most
74 /// straightforward is just to define a local variable and assign to it. For
75 /// example:
76 ///
77 /// import 'package:scheduled_test/scheduled_test.dart';
78 ///
79 /// void main() {
80 /// test('computeValue returns 12', () {
81 /// var value;
82 ///
83 /// schedule(() {
84 /// return computeValue().then((computedValue) {
85 /// value = computedValue;
86 /// });
87 /// });
88 ///
89 /// schedule(() => expect(value, equals(12)));
90 /// });
91 /// }
92 ///
93 /// However, this doesn't scale well, especially when you start factoring out
94 /// calls to [schedule] into library methods. For that reason, [schedule]
95 /// returns a [Future] that will complete to the same value as the return
96 /// value of the task. For example:
97 ///
98 /// import 'package:scheduled_test/scheduled_test.dart';
99 ///
100 /// void main() {
101 /// test('computeValue returns 12', () {
102 /// var valueFuture = schedule(() => computeValue());
103 /// schedule(() {
104 /// valueFuture.then((value) => expect(value, equals(12)));
105 /// });
106 /// });
107 /// }
108 ///
109 /// ## Out-of-Band Callbacks
110 ///
111 /// Sometimes your tests will have callbacks that don't fit into the schedule.
112 /// It's important that errors in these callbacks are still registered, though,
113 /// and that [Schedule.onException] and [Schedule.onComplete] still run after
114 /// they finish. When using `unittest`, you wrap these callbacks with
115 /// `expectAsyncN`; when using `scheduled_test`, you use [wrapAsync].
116 ///
117 /// [wrapAsync] has two important functions. First, any errors that occur in it
118 /// will be passed into the [Schedule] instead of causing the whole test to
119 /// crash. They can then be handled by [Schedule.onException] and
120 /// [Schedule.onComplete]. Second, a task queue isn't considered finished until
121 /// all of its [wrapAsync]-wrapped functions have been called. This ensures that
122 /// [Schedule.onException] and [Schedule.onComplete] will always run after all
123 /// the test code in the main queue.
124 ///
125 /// Note that the [completes], [completion], and [throws] matchers use
126 /// [wrapAsync] internally, so they're safe to use in conjunction with scheduled
127 /// tests.
128 ///
129 /// Here's an example of a test using [wrapAsync] to catch errors thrown in the
130 /// callback of a fictional `startServer` function:
131 ///
132 /// import 'package:scheduled_test/scheduled_test.dart';
133 ///
134 /// void main() {
135 /// test('sendRequest sends a request', () {
136 /// startServer(wrapAsync((request) {
137 /// expect(request.body, equals('payload'));
138 /// request.response.close();
139 /// }));
140 ///
141 /// schedule(() => sendRequest('payload'));
142 /// });
143 /// }
144 library scheduled_test;
145
146 import 'dart:async';
147
148 import 'package:unittest/unittest.dart' as unittest;
149
150 import 'src/schedule.dart';
151 import 'src/schedule_error.dart';
152 import 'src/utils.dart';
153
154 export 'package:unittest/matcher.dart';
155 export 'package:unittest/unittest.dart' show
156 config, configure, Configuration, logMessage, expectThrow, fail;
157
158 export 'src/schedule.dart';
159 export 'src/schedule_error.dart';
160 export 'src/task.dart';
161
162 /// The [Schedule] for the current test. This is used to add new tasks and
163 /// inspect the state of the schedule.
164 ///
165 /// This is `null` when there's no test currently running.
166 Schedule get currentSchedule => _currentSchedule;
167 Schedule _currentSchedule;
168
169 /// The user-provided setUp function. This is set for each test during
170 /// `unittest.setUp`.
171 Function _setUpFn;
172
173 /// Creates a new test case with the given description and body. This has the
174 /// same semantics as [unittest.test].
175 void test(String description, void body()) =>
176 _test(description, body, unittest.test);
177
178 /// Creates a new test case with the given description and body that will be the
179 /// only test run in this file. This has the same semantics as
180 /// [unittest.solo_test].
181 void solo_test(String description, void body()) =>
182 _test(description, body, unittest.solo_test);
183
184 void _test(String description, void body(), Function testFn) {
185 _ensureInitialized();
186 _ensureSetUpForTopLevel();
187 testFn(description, () {
188 var asyncDone = unittest.expectAsync0(() {});
189 return currentSchedule.run(() {
190 if (_setUpFn != null) _setUpFn();
191 body();
192 }).then((_) {
193 // If we got here, the test completed successfully so tell unittest so.
194 asyncDone();
195 }).catchError((e) {
196 if (e is ScheduleError) {
197 unittest.registerException(new ExpectException(e.toString()));
198 } else if (e is AsyncError) {
199 unittest.registerException(e.error, e.stackTrace);
200 } else {
201 unittest.registerException(e);
202 }
203 });
204 });
205 }
206
207 /// Whether or not the tests currently being defined are in a group. This is
208 /// only true when defining tests, not when executing them.
209 bool _inGroup = false;
210
211 /// Creates a new named group of tests. This has the same semantics as
212 /// [unittest.group].
213 void group(String description, void body()) {
214 unittest.group(description, () {
215 var wasInGroup = _inGroup;
216 _inGroup = true;
217 _setUpScheduledTest();
218 body();
219 _inGroup = wasInGroup;
220 });
221 }
222
223 /// Schedules a task, [fn], to run asynchronously as part of the main task queue
224 /// of [currentSchedule]. Tasks will be run in the order they're scheduled. If
225 /// [fn] returns a [Future], tasks after it won't be run until that [Future]
226 /// completes.
227 ///
228 /// The return value will be completed once the scheduled task has finished
229 /// running. Its return value is the same as the return value of [fn], or the
230 /// value it completes to if it's a [Future].
231 ///
232 /// If [description] is passed, it's used to describe the task for debugging
233 /// purposes when an error occurs.
234 ///
235 /// This function is identical to [currentSchedule.tasks.schedule].
236 Future schedule(fn(), [String description]) =>
237 currentSchedule.tasks.schedule(fn, description);
238
239 /// Register a [setUp] function for a test [group]. This has the same semantics
240 /// as [unittest.setUp]. Tasks may be scheduled using [schedule] within
241 /// [setUpFn], and [currentSchedule] may be accessed as well.
242 ///
243 /// Note that there is no associated [tearDown] function. Instead, tasks should
244 /// be scheduled for [currentSchedule.onComplete] or
245 /// [currentSchedule.onException]. These tasks will be run after each test's
246 /// schedule is completed.
247 void setUp(void setUpFn()) {
248 _setUpScheduledTest(setUpFn);
249 }
250
251 /// Whether [unittest.setUp] has been called in the top level scope.
252 bool _setUpForTopLevel = false;
253
254 /// If we're in the top-level scope (that is, not in any [group]s) and
255 /// [unittest.setUp] hasn't been called yet, call it.
256 void _ensureSetUpForTopLevel() {
257 if (_inGroup || _setUpForTopLevel) return;
258 _setUpScheduledTest();
259 }
260
261 /// Registers callbacks for [unittest.setUp] and [unittest.tearDown] that set up
262 /// and tear down the scheduled test infrastructure.
263 void _setUpScheduledTest([void setUpFn()]) {
264 if (!_inGroup) _setUpForTopLevel = true;
265
266 unittest.setUp(() {
267 if (currentSchedule != null) {
268 throw new StateError('There seems to be another scheduled test '
269 'still running.');
270 }
271 _currentSchedule = new Schedule();
272 _setUpFn = setUpFn;
273 });
274
275 unittest.tearDown(() {
276 _currentSchedule = null;
277 });
278 }
279
280 /// Ensures that the global configuration for `scheduled_test` has been
281 /// initialized.
282 void _ensureInitialized() {
283 unittest.ensureInitialized();
284 unittest.wrapAsync = (f) {
285 if (currentSchedule == null) {
286 throw new StateError("Unexpected call to wrapAsync with no current "
287 "schedule.");
288 }
289
290 return currentSchedule.wrapAsync(f);
291 };
292 }
OLDNEW
« pkg/pkg.status ('K') | « pkg/pkg.status ('k') | pkg/scheduled_test/lib/src/schedule.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698