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

Side by Side Diff: pkg/watcher/test/utils.dart

Issue 129473003: Use stream matchers to unflake the mac OS watcher tests. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 11 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
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, 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 watcher.test.utils; 5 library watcher.test.utils;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:collection'; 8 import 'dart:collection';
9 import 'dart:io'; 9 import 'dart:io';
10 10
11 import 'package:path/path.dart' as p; 11 import 'package:path/path.dart' as p;
12 import 'package:scheduled_test/scheduled_stream.dart';
12 import 'package:scheduled_test/scheduled_test.dart'; 13 import 'package:scheduled_test/scheduled_test.dart';
13 import 'package:unittest/compact_vm_config.dart'; 14 import 'package:unittest/compact_vm_config.dart';
14 import 'package:watcher/watcher.dart'; 15 import 'package:watcher/watcher.dart';
15 import 'package:watcher/src/stat.dart'; 16 import 'package:watcher/src/stat.dart';
16 import 'package:watcher/src/utils.dart'; 17 import 'package:watcher/src/utils.dart';
17 18
18 // TODO(nweiz): remove this when issue 15042 is fixed. 19 // TODO(nweiz): remove this when issue 15042 is fixed.
19 import 'package:watcher/src/directory_watcher/mac_os.dart'; 20 import 'package:watcher/src/directory_watcher/mac_os.dart';
20 21
21 /// The path to the temporary sandbox created for each test. All file 22 /// The path to the temporary sandbox created for each test. All file
22 /// operations are implicitly relative to this directory. 23 /// operations are implicitly relative to this directory.
23 String _sandboxDir; 24 String _sandboxDir;
24 25
25 /// The [DirectoryWatcher] being used for the current scheduled test. 26 /// The [DirectoryWatcher] being used for the current scheduled test.
26 DirectoryWatcher _watcher; 27 DirectoryWatcher _watcher;
27 28
28 /// The index in [_watcher]'s event stream for the next event. When event
29 /// expectations are set using [expectEvent] (et. al.), they use this to
30 /// expect a series of events in order.
31 var _nextEvent = 0;
32
33 /// The mock modification times (in milliseconds since epoch) for each file. 29 /// The mock modification times (in milliseconds since epoch) for each file.
34 /// 30 ///
35 /// The actual file system has pretty coarse granularity for file modification 31 /// The actual file system has pretty coarse granularity for file modification
36 /// times. This means using the real file system requires us to put delays in 32 /// times. This means using the real file system requires us to put delays in
37 /// the tests to ensure we wait long enough between operations for the mod time 33 /// the tests to ensure we wait long enough between operations for the mod time
38 /// to be different. 34 /// to be different.
39 /// 35 ///
40 /// Instead, we'll just mock that out. Each time a file is written, we manually 36 /// Instead, we'll just mock that out. Each time a file is written, we manually
41 /// increment the mod time for that file instantly. 37 /// increment the mod time for that file instantly.
42 Map<String, int> _mockFileModificationTimes; 38 Map<String, int> _mockFileModificationTimes;
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
104 // Wait until the scan is finished so that we don't miss changes to files 100 // Wait until the scan is finished so that we don't miss changes to files
105 // that could occur before the scan completes. 101 // that could occur before the scan completes.
106 if (waitForReady != false) { 102 if (waitForReady != false) {
107 schedule(() => watcher.ready, "wait for watcher to be ready"); 103 schedule(() => watcher.ready, "wait for watcher to be ready");
108 } 104 }
109 105
110 return watcher; 106 return watcher;
111 } 107 }
112 108
113 /// The stream of events from the watcher started with [startWatcher]. 109 /// The stream of events from the watcher started with [startWatcher].
114 Stream _watcherEvents; 110 ScheduledStream<WatchEvent> _watcherEvents;
115 111
116 /// Creates a new [DirectoryWatcher] that watches a temporary directory and 112 /// Creates a new [DirectoryWatcher] that watches a temporary directory and
117 /// starts monitoring it for events. 113 /// starts monitoring it for events.
118 /// 114 ///
119 /// If [dir] is provided, watches a subdirectory in the sandbox with that name. 115 /// If [dir] is provided, watches a subdirectory in the sandbox with that name.
120 void startWatcher({String dir}) { 116 void startWatcher({String dir}) {
121 var testCase = currentTestCase.description; 117 var testCase = currentTestCase.description;
122 if (MacOSDirectoryWatcher.logDebugInfo) { 118 if (MacOSDirectoryWatcher.logDebugInfo) {
123 print("starting watcher for $testCase"); 119 print("starting watcher for $testCase");
124 } 120 }
125 121
126 // We want to wait until we're ready *after* we subscribe to the watcher's 122 // We want to wait until we're ready *after* we subscribe to the watcher's
127 // events. 123 // events.
128 _watcher = createWatcher(dir: dir, waitForReady: false); 124 _watcher = createWatcher(dir: dir, waitForReady: false);
129 125
130 // Schedule [_watcher.events.listen] so that the watcher doesn't start 126 // Schedule [_watcher.events.listen] so that the watcher doesn't start
131 // watching [dir] before it exists. Expose [_watcherEvents] immediately so 127 // watching [dir] before it exists. Expose [_watcherEvents] immediately so
132 // that it can be accessed synchronously after this. 128 // that it can be accessed synchronously after this.
133 _watcherEvents = futureStream(schedule(() { 129 _watcherEvents = new ScheduledStream(futureStream(schedule(() {
134 var allEvents = new Queue();
135 var subscription = _watcher.events.listen(allEvents.add,
136 onError: currentSchedule.signalError);
137
138 currentSchedule.onComplete.schedule(() { 130 currentSchedule.onComplete.schedule(() {
139 if (MacOSDirectoryWatcher.logDebugInfo) { 131 if (MacOSDirectoryWatcher.logDebugInfo) {
140 print("stopping watcher for $testCase"); 132 print("stopping watcher for $testCase");
141 } 133 }
142 134
143 var numEvents = _nextEvent;
144 subscription.cancel();
145 _nextEvent = 0;
146 _watcher = null; 135 _watcher = null;
136 if (!_closePending) _watcherEvents.close();
147 137
148 // If there are already errors, don't add this to the output and make 138 // If there are already errors, don't add this to the output and make
149 // people think it might be the root cause. 139 // people think it might be the root cause.
150 if (currentSchedule.errors.isEmpty) { 140 if (currentSchedule.errors.isEmpty) {
151 expect(allEvents, hasLength(numEvents)); 141 _watcherEvents.expect(isDone);
152 } else {
153 currentSchedule.addDebugInfo("Events fired:\n${allEvents.join('\n')}");
154 } 142 }
155 }, "reset watcher"); 143 }, "reset watcher");
156 144
157 return _watcher.events; 145 return _watcher.events;
158 }, "create watcher"), broadcast: true); 146 }, "create watcher"), broadcast: true));
159 147
160 schedule(() => _watcher.ready, "wait for watcher to be ready"); 148 schedule(() => _watcher.ready, "wait for watcher to be ready");
161 } 149 }
162 150
163 /// A future set by [inAnyOrder] that will complete to the set of events that 151 /// Whether an event to close [_watcherEvents] has been scheduled.
164 /// occur in the [inAnyOrder] block. 152 bool _closePending = false;
165 Future<Set<WatchEvent>> _unorderedEventFuture;
166 153
167 /// Runs [block] and allows multiple [expectEvent] calls in that block to match 154 /// Schedule closing the directory watcher stream after the event queue has been
168 /// events in any order. 155 /// pumped.
169 void inAnyOrder(block()) { 156 ///
170 var oldFuture = _unorderedEventFuture; 157 /// This is necessary when events are allowed to occur, but don't have to occur,
158 /// at the end of a test. Otherwise, if they don't occur, the test will wait
159 /// indefinitely because they might in the future and because the watcher is
160 /// normally only closed after the test completes.
161 void startClosingEventStream() {
162 schedule(() {
163 _closePending = true;
164 pumpEventQueue().then((_) => _watcherEvents.close()).whenComplete(() {
165 _closePending = false;
166 });
167 }, 'start closing event stream');
168 }
169
170 /// A list of [StreamMatcher]s that have been collected using
171 /// [_collectStreamMatcher].
172 List<StreamMatcher> _collectedStreamMatchers;
173
174 /// Collects all stream matchers that are registered within [block] into a
175 /// single stream matcher.
176 ///
177 /// The returned matcher will match each of the collected matchers in order.
178 StreamMatcher _collectStreamMatcher(block()) {
179 var oldStreamMatchers = _collectedStreamMatchers;
180 _collectedStreamMatchers = new List<StreamMatcher>();
171 try { 181 try {
172 var firstEvent = _nextEvent;
173 var completer = new Completer();
174 _unorderedEventFuture = completer.future;
175 block(); 182 block();
176 183 return inOrder(_collectedStreamMatchers);
177 _watcherEvents.skip(firstEvent).take(_nextEvent - firstEvent).toSet()
178 .then(completer.complete, onError: completer.completeError);
179 currentSchedule.wrapFuture(_unorderedEventFuture,
180 "waiting for ${_nextEvent - firstEvent} events");
181 } finally { 184 } finally {
182 _unorderedEventFuture = oldFuture; 185 _collectedStreamMatchers = oldStreamMatchers;
183 } 186 }
184 } 187 }
185 188
186 /// Expects that the next set of event will be a change of [type] on [path]. 189 /// Either add [streamMatcher] as an expectation to [_watcherEvents], or collect
190 /// it with [_collectStreamMatcher].
187 /// 191 ///
188 /// Multiple calls to [expectEvent] require that the events are received in that 192 /// [streamMatcher] can be a [StreamMatcher], a [Matcher], or a value.
189 /// order unless they're called in an [inAnyOrder] block. 193 void _expectOrCollect(streamMatcher) {
190 void expectEvent(ChangeType type, String path) { 194 if (_collectedStreamMatchers != null) {
191 if (_unorderedEventFuture != null) { 195 _collectedStreamMatchers.add(new StreamMatcher.wrap(streamMatcher));
192 // Assign this to a local variable since it will be un-assigned by the time 196 } else {
193 // the scheduled callback runs. 197 _watcherEvents.expect(streamMatcher);
194 var future = _unorderedEventFuture; 198 }
199 }
195 200
196 expect( 201 /// Expects that [matchers] will match emitted events in any order.
197 schedule(() => future, "should fire $type event on $path"), 202 ///
198 completion(contains(isWatchEvent(type, path)))); 203 /// [matchers] may be [Matcher]s or values, but not [StreamMatcher]s.
199 } else { 204 void inAnyOrder(Iterable matchers) {
200 var future = currentSchedule.wrapFuture( 205 matchers = matchers.toSet();
201 _watcherEvents.elementAt(_nextEvent), 206 _expectOrCollect(nextValues(matchers.length, unorderedMatches(matchers)));
202 "waiting for $type event on $path"); 207 }
203 208
204 expect( 209 /// Expects that the expectations established in either [block1] or [block2]
205 schedule(() => future, "should fire $type event on $path"), 210 /// will match the emitted events.
206 completion(isWatchEvent(type, path))); 211 ///
207 } 212 /// If both blocks match, the one that consumed more events will be used.
208 _nextEvent++; 213 void allowEither(block1(), block2()) {
214 _expectOrCollect(either(
215 _collectStreamMatcher(block1), _collectStreamMatcher(block2)));
216 }
217
218 /// Allows the expectations established in [block] to match the emitted events.
219 ///
220 /// If the expectations in [block] don't match, no error will be raised and no
221 /// events will be consumed. If this is used at the end of a test,
222 /// [startClosingEventStream] should be called before it.
223 void allowEvents(block()) {
224 _expectOrCollect(allow(_collectStreamMatcher(block)));
209 } 225 }
210 226
211 /// Returns a matcher that matches a [WatchEvent] with the given [type] and 227 /// Returns a matcher that matches a [WatchEvent] with the given [type] and
212 /// [path]. 228 /// [path].
213 Matcher isWatchEvent(ChangeType type, String path) { 229 Matcher isWatchEvent(ChangeType type, String path) {
214 return predicate((e) { 230 return predicate((e) {
215 return e is WatchEvent && e.type == type && 231 return e is WatchEvent && e.type == type &&
216 e.path == p.join(_sandboxDir, p.normalize(path)); 232 e.path == p.join(_sandboxDir, p.normalize(path));
217 }, "is $type $path"); 233 }, "is $type $path");
218 } 234 }
219 235
220 void expectAddEvent(String path) => expectEvent(ChangeType.ADD, path); 236 /// Returns a [Matcher] that matches a [WatchEvent] for an add event for [path].
221 void expectModifyEvent(String path) => expectEvent(ChangeType.MODIFY, path); 237 Matcher isAddEvent(String path) => isWatchEvent(ChangeType.ADD, path);
222 void expectRemoveEvent(String path) => expectEvent(ChangeType.REMOVE, path); 238
239 /// Returns a [Matcher] that matches a [WatchEvent] for a modification event for
240 /// [path].
241 Matcher isModifyEvent(String path) => isWatchEvent(ChangeType.MODIFY, path);
242
243 /// Returns a [Matcher] that matches a [WatchEvent] for a removal event for
244 /// [path].
245 Matcher isRemoveEvent(String path) => isWatchEvent(ChangeType.REMOVE, path);
246
247 /// Expects that the next event emitted will be for an add event for [path].
248 void expectAddEvent(String path) =>
249 _expectOrCollect(isWatchEvent(ChangeType.ADD, path));
250
251 /// Expects that the next event emitted will be for a modification event for
252 /// [path].
253 void expectModifyEvent(String path) =>
254 _expectOrCollect(isWatchEvent(ChangeType.MODIFY, path));
255
256 /// Expects that the next event emitted will be for a removal event for [path].
257 void expectRemoveEvent(String path) =>
258 _expectOrCollect(isWatchEvent(ChangeType.REMOVE, path));
259
260 /// Consumes an add event for [path] if one is emitted at this point in the
261 /// schedule, but doesn't throw an error if it isn't.
262 ///
263 /// If this is used at the end of a test, [startClosingEventStream] should be
264 /// called before it.
265 void allowAddEvent(String path) =>
266 _expectOrCollect(allow(isWatchEvent(ChangeType.ADD, path)));
267
268 /// Consumes a modification event for [path] if one is emitted at this point in
269 /// the schedule, but doesn't throw an error if it isn't.
270 ///
271 /// If this is used at the end of a test, [startClosingEventStream] should be
272 /// called before it.
273 void allowModifyEvent(String path) =>
274 _expectOrCollect(allow(isWatchEvent(ChangeType.MODIFY, path)));
275
276 /// Consumes a removal event for [path] if one is emitted at this point in the
277 /// schedule, but doesn't throw an error if it isn't.
278 ///
279 /// If this is used at the end of a test, [startClosingEventStream] should be
280 /// called before it.
281 void allowRemoveEvent(String path) =>
282 _expectOrCollect(allow(isWatchEvent(ChangeType.REMOVE, path)));
223 283
224 /// Schedules writing a file in the sandbox at [path] with [contents]. 284 /// Schedules writing a file in the sandbox at [path] with [contents].
225 /// 285 ///
226 /// If [contents] is omitted, creates an empty file. If [updatedModified] is 286 /// If [contents] is omitted, creates an empty file. If [updatedModified] is
227 /// `false`, the mock file modification time is not changed. 287 /// `false`, the mock file modification time is not changed.
228 void writeFile(String path, {String contents, bool updateModified}) { 288 void writeFile(String path, {String contents, bool updateModified}) {
229 if (contents == null) contents = ""; 289 if (contents == null) contents = "";
230 if (updateModified == null) updateModified = true; 290 if (updateModified == null) updateModified = true;
231 291
232 schedule(() { 292 schedule(() {
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
292 /// Schedules deleting a directory in the sandbox at [path]. 352 /// Schedules deleting a directory in the sandbox at [path].
293 void deleteDir(String path) { 353 void deleteDir(String path) {
294 schedule(() { 354 schedule(() {
295 new Directory(p.join(_sandboxDir, path)).deleteSync(recursive: true); 355 new Directory(p.join(_sandboxDir, path)).deleteSync(recursive: true);
296 }, "delete directory $path"); 356 }, "delete directory $path");
297 } 357 }
298 358
299 /// Runs [callback] with every permutation of non-negative [i], [j], and [k] 359 /// Runs [callback] with every permutation of non-negative [i], [j], and [k]
300 /// less than [limit]. 360 /// less than [limit].
301 /// 361 ///
362 /// Returns a set of all values returns by [callback].
363 ///
302 /// [limit] defaults to 3. 364 /// [limit] defaults to 3.
303 void withPermutations(callback(int i, int j, int k), {int limit}) { 365 Set withPermutations(callback(int i, int j, int k), {int limit}) {
304 if (limit == null) limit = 3; 366 if (limit == null) limit = 3;
367 var results = new Set();
305 for (var i = 0; i < limit; i++) { 368 for (var i = 0; i < limit; i++) {
306 for (var j = 0; j < limit; j++) { 369 for (var j = 0; j < limit; j++) {
307 for (var k = 0; k < limit; k++) { 370 for (var k = 0; k < limit; k++) {
308 callback(i, j, k); 371 results.add(callback(i, j, k));
309 } 372 }
310 } 373 }
311 } 374 }
375 return results;
312 } 376 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698