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

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

Issue 12208081: Add a scheduled test library. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: 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
Bob Nystrom 2013/02/08 16:15:37 <3 <3 <3
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
Bob Nystrom 2013/02/08 16:15:37 We'll probably eventually want the ability to wrap
nweiz 2013/02/08 22:14:38 Once that comes up, we can port guardAsync as well
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.
Bob Nystrom 2013/02/08 16:15:37 Maybe throw if the user tries to access this outsi
nweiz 2013/02/08 22:14:38 I think it's useful to allow users to check if a s
166 Schedule get currentSchedule => _currentSchedule;
167 Schedule _currentSchedule;
168
169 /// Creates a new test case with the given description and body. This has the
170 /// same semantics as [unittest.test].
171 void test(String description, void body()) =>
172 _test(description, body, unittest.test);
173
174 /// Creates a new test case with the given description and body that will be the
175 /// only test run in this file. This has the same semantics as
176 /// [unittest.solo_test].
177 void solo_test(String description, void body()) =>
178 _test(description, body, unittest.solo_test);
179
180 void _test(String description, void body(), [Function testFn]) {
Bob Nystrom 2013/02/08 16:15:37 Why is testFn optional?
nweiz 2013/02/08 22:14:38 Good question. Fixed.
181 _ensureInitialized();
182 _ensureSetUpForTopLevel();
183 testFn(description, () {
184 var asyncDone = unittest.expectAsync0(() {});
185 return currentSchedule.run(body).then((_) {
186 // If we got here, the test completed successfully so tell unittest so.
187 asyncDone();
188 }).catchError((e) {
189 if (e is ScheduleError) {
190 unittest.registerException(new ExpectException(e.toString()));
191 } else if (e is AsyncError) {
192 unittest.registerException(e.error, e.stackTrace);
193 } else {
194 unittest.registerException(e);
195 }
196 });
197 });
198 }
199
200 /// Whether or not the tests currently being defined are in a group. This is
201 /// only true when defining tests, not when executing them.
202 bool _inGroup = false;
203
204 /// Creates a new named group of tests. This has the same semantics as
205 /// [unittest.group].
206 void group(String description, void body()) {
207 unittest.group(description, () {
208 var wasInGroup = _inGroup;
209 _inGroup = true;
210 _setUpScheduledTest();
211 body();
212 _inGroup = wasInGroup;
213 });
214 }
215
216 /// Schedules a task, [fn], to run asynchronously as part of the main task queue
217 /// of [currentSchedule]. Tasks will be run in the order they're scheduled. In
Bob Nystrom 2013/02/08 16:15:37 In -> If
nweiz 2013/02/08 22:14:38 Done.
218 /// [fn] returns a [Future], tasks after it won't be run until that [Future]
219 /// completes.
220 ///
221 /// The return value will be completed once the scheduled task has finished
222 /// running. Its return value is the same as the return value of [fn], or the
223 /// value it completes to if it's a [Future].
224 ///
225 /// If [description] is passed, it's used to describe the task for debugging
226 /// purposes when an error occurs.
227 ///
228 /// This function is identical to [currentSchedule.tasks.schedule].
229 Future schedule(fn(), [String description]) =>
230 currentSchedule.tasks.schedule(fn, description);
231
232 /// Register a [setUp] function for a test [group]. This has the same semantics
233 /// as [unittest.setUp]. Tasks may be scheduled using [schedule] within
234 /// [setUpFn], and [currentSchedule] may be accessed as well.
235 ///
236 /// Note that there is no associated [tearDown] function. Instead, tasks should
237 /// be scheduled for [currentSchedule.onComplete] or
238 /// [currentSchedule.onException]. These tasks will be run after each test's
239 /// schedule is completed.
240 void setUp(void setUpFn()) {
241 _setUpScheduledTest(setUpFn);
242 }
243
244 /// Whether [unittest.setUp] has been called in the top level scope.
245 bool _setUpForTopLevel = false;
246
247 /// If we're in the top-level scope (that is, not in any [group]s) and
248 /// [unittest.setUp] hasn't been called yet, call it.
249 void _ensureSetUpForTopLevel() {
250 if (_inGroup || _setUpForTopLevel) return;
251 _setUpScheduledTest();
252 }
253
254 /// Registers callbacks for [unittest.setUp] and [unittest.tearDown] that set up
255 /// and tear down the scheduled test infrastructure.
256 void _setUpScheduledTest([void setUpFn()]) {
257 if (!_inGroup) _setUpForTopLevel = true;
258
259 unittest.setUp(() {
260 if (currentSchedule != null) {
261 throw 'There seems to be another scheduled test "$description" still '
Bob Nystrom 2013/02/08 16:15:37 Throw a StateError.
nweiz 2013/02/08 22:14:38 Done.
262 'running.';
263 }
264 _currentSchedule = new Schedule();
265 if (setUpFn != null) setUpFn();
266 });
267
268 unittest.tearDown(() {
269 _currentSchedule = null;
270 });
271 }
272
273 /// Ensures that the global configuration for `scheduled_test` has been
274 /// initialized.
275 void _ensureInitialized() {
276 unittest.ensureInitialized();
277 unittest.wrapAsync = (f) {
278 if (currentSchedule == null) {
279 throw "Unexpected call to wrapAsync with no current schedule.";
Bob Nystrom 2013/02/08 16:15:37 Throw a StateError.
nweiz 2013/02/08 22:14:38 Done.
280 }
281
282 return currentSchedule.wrapAsync(f);
283 };
284 }
OLDNEW
« no previous file with comments | « no previous file | pkg/scheduled_test/lib/src/schedule.dart » ('j') | pkg/scheduled_test/lib/src/schedule.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698