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

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

Issue 46843003: Wrap Directory.watch on linux for the watcher package. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: code review Created 7 years, 1 month 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/watcher/test/ready_test.dart ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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:io'; 9 import 'dart:io';
9 10
10 import 'package:path/path.dart' as p; 11 import 'package:path/path.dart' as p;
11 import 'package:scheduled_test/scheduled_test.dart'; 12 import 'package:scheduled_test/scheduled_test.dart';
12 import 'package:unittest/compact_vm_config.dart'; 13 import 'package:unittest/compact_vm_config.dart';
13 import 'package:watcher/watcher.dart'; 14 import 'package:watcher/watcher.dart';
14 import 'package:watcher/src/stat.dart'; 15 import 'package:watcher/src/stat.dart';
16 import 'package:watcher/src/utils.dart';
15 17
16 /// The path to the temporary sandbox created for each test. All file 18 /// The path to the temporary sandbox created for each test. All file
17 /// operations are implicitly relative to this directory. 19 /// operations are implicitly relative to this directory.
18 String _sandboxDir; 20 String _sandboxDir;
19 21
20 /// The [DirectoryWatcher] being used for the current scheduled test. 22 /// The [DirectoryWatcher] being used for the current scheduled test.
21 DirectoryWatcher _watcher; 23 DirectoryWatcher _watcher;
22 24
23 /// The index in [_watcher]'s event stream for the next event. When event 25 /// The index in [_watcher]'s event stream for the next event. When event
24 /// expectations are set using [expectEvent] (et. al.), they use this to 26 /// expectations are set using [expectEvent] (et. al.), they use this to
25 /// expect a series of events in order. 27 /// expect a series of events in order.
26 var _nextEvent = 0; 28 var _nextEvent = 0;
27 29
28 /// The mock modification times (in milliseconds since epoch) for each file. 30 /// The mock modification times (in milliseconds since epoch) for each file.
29 /// 31 ///
30 /// The actual file system has pretty coarse granularity for file modification 32 /// The actual file system has pretty coarse granularity for file modification
31 /// times. This means using the real file system requires us to put delays in 33 /// times. This means using the real file system requires us to put delays in
32 /// the tests to ensure we wait long enough between operations for the mod time 34 /// the tests to ensure we wait long enough between operations for the mod time
33 /// to be different. 35 /// to be different.
34 /// 36 ///
35 /// Instead, we'll just mock that out. Each time a file is written, we manually 37 /// Instead, we'll just mock that out. Each time a file is written, we manually
36 /// increment the mod time for that file instantly. 38 /// increment the mod time for that file instantly.
37 Map<String, int> _mockFileModificationTimes; 39 Map<String, int> _mockFileModificationTimes;
38 40
41 typedef DirectoryWatcher WatcherFactory(String directory);
42
43 /// Sets the function used to create the directory watcher.
44 set watcherFactory(WatcherFactory factory) {
45 _watcherFactory = factory;
46 }
47 WatcherFactory _watcherFactory;
48
39 void initConfig() { 49 void initConfig() {
40 useCompactVMConfiguration(); 50 useCompactVMConfiguration();
41 filterStacks = true; 51 filterStacks = true;
42 } 52 }
43 53
44 /// Creates the sandbox directory the other functions in this library use and 54 /// Creates the sandbox directory the other functions in this library use and
45 /// ensures it's deleted when the test ends. 55 /// ensures it's deleted when the test ends.
46 /// 56 ///
47 /// This should usually be called by [setUp]. 57 /// This should usually be called by [setUp].
48 void createSandbox() { 58 void createSandbox() {
49 var dir = Directory.systemTemp.createTempSync('watcher_test_'); 59 var dir = Directory.systemTemp.createTempSync('watcher_test_');
50 _sandboxDir = dir.path; 60 _sandboxDir = dir.path;
51 61
52 _mockFileModificationTimes = new Map<String, int>(); 62 _mockFileModificationTimes = new Map<String, int>();
53 mockGetModificationTime((path) { 63 mockGetModificationTime((path) {
54 path = p.normalize(p.relative(path, from: _sandboxDir)); 64 path = p.normalize(p.relative(path, from: _sandboxDir));
55 65
56 // Make sure we got a path in the sandbox. 66 // Make sure we got a path in the sandbox.
57 assert(p.isRelative(path) && !path.startsWith("..")); 67 assert(p.isRelative(path) && !path.startsWith(".."));
58 68
59 return new DateTime.fromMillisecondsSinceEpoch( 69 var mtime = _mockFileModificationTimes[path];
60 _mockFileModificationTimes[path]); 70 return new DateTime.fromMillisecondsSinceEpoch(mtime == null ? 0 : mtime);
61 }); 71 });
62 72
63 // Delete the sandbox when done. 73 // Delete the sandbox when done.
64 currentSchedule.onComplete.schedule(() { 74 currentSchedule.onComplete.schedule(() {
65 if (_sandboxDir != null) { 75 if (_sandboxDir != null) {
66 new Directory(_sandboxDir).deleteSync(recursive: true); 76 new Directory(_sandboxDir).deleteSync(recursive: true);
67 _sandboxDir = null; 77 _sandboxDir = null;
68 } 78 }
69 79
70 _mockFileModificationTimes = null; 80 _mockFileModificationTimes = null;
71 mockGetModificationTime(null); 81 mockGetModificationTime(null);
72 }, "delete sandbox"); 82 }, "delete sandbox");
73 } 83 }
74 84
75 /// Creates a new [DirectoryWatcher] that watches a temporary directory. 85 /// Creates a new [DirectoryWatcher] that watches a temporary directory.
76 /// 86 ///
77 /// Normally, this will pause the schedule until the watcher is done scanning 87 /// Normally, this will pause the schedule until the watcher is done scanning
78 /// and is polling for changes. If you pass `false` for [waitForReady], it will 88 /// and is polling for changes. If you pass `false` for [waitForReady], it will
79 /// not schedule this delay. 89 /// not schedule this delay.
80 /// 90 ///
81 /// If [dir] is provided, watches a subdirectory in the sandbox with that name. 91 /// If [dir] is provided, watches a subdirectory in the sandbox with that name.
82 DirectoryWatcher createWatcher({String dir, bool waitForReady}) { 92 DirectoryWatcher createWatcher({String dir, bool waitForReady}) {
83 if (dir == null) { 93 if (dir == null) {
84 dir = _sandboxDir; 94 dir = _sandboxDir;
85 } else { 95 } else {
86 dir = p.join(_sandboxDir, dir); 96 dir = p.join(_sandboxDir, dir);
87 } 97 }
88 98
89 // Use a short delay to make the tests run quickly. 99 var watcher = _watcherFactory(dir);
90 _watcher = new DirectoryWatcher(dir,
91 pollingDelay: new Duration(milliseconds: 100));
92 100
93 // Wait until the scan is finished so that we don't miss changes to files 101 // Wait until the scan is finished so that we don't miss changes to files
94 // that could occur before the scan completes. 102 // that could occur before the scan completes.
95 if (waitForReady != false) { 103 if (waitForReady != false) {
96 schedule(() => _watcher.ready, "wait for watcher to be ready"); 104 schedule(() => watcher.ready, "wait for watcher to be ready");
97 } 105 }
98 106
99 currentSchedule.onComplete.schedule(() { 107 return watcher;
100 _nextEvent = 0;
101 _watcher = null;
102 }, "reset watcher");
103
104 return _watcher;
105 } 108 }
106 109
107 /// Expects that the next set of events will all be changes of [type] on 110 /// The stream of events from the watcher started with [startWatcher].
108 /// [paths]. 111 Stream _watcherEvents;
112
113 /// Creates a new [DirectoryWatcher] that watches a temporary directory and
114 /// starts monitoring it for events.
109 /// 115 ///
110 /// Validates that events are delivered for all paths in [paths], but allows 116 /// If [dir] is provided, watches a subdirectory in the sandbox with that name.
111 /// them in any order. 117 void startWatcher({String dir}) {
112 void expectEvents(ChangeType type, Iterable<String> paths) { 118 // We want to wait until we're ready *after* we subscribe to the watcher's
113 var pathSet = paths 119 // events.
114 .map((path) => p.join(_sandboxDir, path)) 120 _watcher = createWatcher(dir: dir, waitForReady: false);
115 .map(p.normalize)
116 .toSet();
117 121
118 // Create an expectation for as many paths as we have. 122 // Schedule [_watcher.events.listen] so that the watcher doesn't start
119 var futures = []; 123 // watching [dir] before it exists. Expose [_watcherEvents] immediately so
124 // that it can be accessed synchronously after this.
125 _watcherEvents = futureStream(schedule(() {
126 var allEvents = new Queue();
127 var subscription = _watcher.events.listen(allEvents.add,
128 onError: currentSchedule.signalError);
120 129
121 for (var i = 0; i < paths.length; i++) { 130 currentSchedule.onComplete.schedule(() {
122 // Immediately create the futures. This ensures we don't register too 131 var numEvents = _nextEvent;
123 // late and drop the event before we receive it. 132 subscription.cancel();
124 var future = _watcher.events.elementAt(_nextEvent++).then((event) { 133 _nextEvent = 0;
125 expect(event.type, equals(type)); 134 _watcher = null;
126 expect(pathSet, contains(event.path));
127 135
128 pathSet.remove(event.path); 136 // If there are already errors, don't add this to the output and make
129 }); 137 // people think it might be the root cause.
138 if (currentSchedule.errors.isEmpty) {
139 expect(allEvents, hasLength(numEvents));
140 }
141 }, "reset watcher");
130 142
131 // Make sure the schedule is watching it in case it fails. 143 return _watcher.events;
132 currentSchedule.wrapFuture(future); 144 }, "create watcher")).asBroadcastStream();
133 145
134 futures.add(future); 146 schedule(() => _watcher.ready, "wait for watcher to be ready");
135 }
136
137 // Schedule it so that later file modifications don't occur until after this
138 // event is received.
139 schedule(() => Future.wait(futures),
140 "wait for $type events on ${paths.join(', ')}");
141 } 147 }
142 148
143 void expectAddEvent(String path) => expectEvents(ChangeType.ADD, [path]); 149 /// A future set by [inAnyOrder] that will complete to the set of events that
144 void expectModifyEvent(String path) => expectEvents(ChangeType.MODIFY, [path]); 150 /// occur in the [inAnyOrder] block.
145 void expectRemoveEvent(String path) => expectEvents(ChangeType.REMOVE, [path]); 151 Future<Set<WatchEvent>> _unorderedEventFuture;
146 152
147 void expectRemoveEvents(Iterable<String> paths) { 153 /// Runs [block] and allows multiple [expectEvent] calls in that block to match
148 expectEvents(ChangeType.REMOVE, paths); 154 /// events in any order.
155 void inAnyOrder(block()) {
156 var oldFuture = _unorderedEventFuture;
157 try {
158 var firstEvent = _nextEvent;
159 var completer = new Completer();
160 _unorderedEventFuture = completer.future;
161 block();
162
163 _watcherEvents.skip(firstEvent).take(_nextEvent - firstEvent).toSet()
164 .then(completer.complete, onError: completer.completeError);
165 currentSchedule.wrapFuture(_unorderedEventFuture,
166 "waiting for ${_nextEvent - firstEvent} events");
167 } finally {
168 _unorderedEventFuture = oldFuture;
169 }
149 } 170 }
150 171
172 /// Expects that the next set of event will be a change of [type] on [path].
173 ///
174 /// Multiple calls to [expectEvent] require that the events are received in that
175 /// order unless they're called in an [inAnyOrder] block.
176 void expectEvent(ChangeType type, String path) {
177 var matcher = predicate((e) {
178 return e is WatchEvent && e.type == type &&
179 e.path == p.join(_sandboxDir, path);
180 }, "is $type $path");
181
182 if (_unorderedEventFuture != null) {
183 // Assign this to a local variable since it will be un-assigned by the time
184 // the scheduled callback runs.
185 var future = _unorderedEventFuture;
186
187 expect(
188 schedule(() => future, "should fire $type event on $path"),
189 completion(contains(matcher)));
190 } else {
191 var future = currentSchedule.wrapFuture(
192 _watcherEvents.elementAt(_nextEvent),
193 "waiting for $type event on $path");
194
195 expect(
196 schedule(() => future, "should fire $type event on $path"),
197 completion(matcher));
198 }
199 _nextEvent++;
200 }
201
202 void expectAddEvent(String path) => expectEvent(ChangeType.ADD, path);
203 void expectModifyEvent(String path) => expectEvent(ChangeType.MODIFY, path);
204 void expectRemoveEvent(String path) => expectEvent(ChangeType.REMOVE, path);
205
151 /// Schedules writing a file in the sandbox at [path] with [contents]. 206 /// Schedules writing a file in the sandbox at [path] with [contents].
152 /// 207 ///
153 /// If [contents] is omitted, creates an empty file. If [updatedModified] is 208 /// If [contents] is omitted, creates an empty file. If [updatedModified] is
154 /// `false`, the mock file modification time is not changed. 209 /// `false`, the mock file modification time is not changed.
155 void writeFile(String path, {String contents, bool updateModified}) { 210 void writeFile(String path, {String contents, bool updateModified}) {
156 if (contents == null) contents = ""; 211 if (contents == null) contents = "";
157 if (updateModified == null) updateModified = true; 212 if (updateModified == null) updateModified = true;
158 213
159 schedule(() { 214 schedule(() {
160 var fullPath = p.join(_sandboxDir, path); 215 var fullPath = p.join(_sandboxDir, path);
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
194 249
195 // Make sure we always use the same separator on Windows. 250 // Make sure we always use the same separator on Windows.
196 to = p.normalize(to); 251 to = p.normalize(to);
197 252
198 // Manually update the mock modification time for the file. 253 // Manually update the mock modification time for the file.
199 var milliseconds = _mockFileModificationTimes.putIfAbsent(to, () => 0); 254 var milliseconds = _mockFileModificationTimes.putIfAbsent(to, () => 0);
200 _mockFileModificationTimes[to]++; 255 _mockFileModificationTimes[to]++;
201 }, "rename file $from to $to"); 256 }, "rename file $from to $to");
202 } 257 }
203 258
259 /// Schedules creating a directory in the sandbox at [path].
260 void createDir(String path) {
261 schedule(() {
262 new Directory(p.join(_sandboxDir, path)).createSync();
263 }, "create directory $path");
264 }
265
266 /// Schedules renaming a directory in the sandbox from [from] to [to].
267 void renameDir(String from, String to) {
268 schedule(() {
269 new Directory(p.join(_sandboxDir, from))
270 .renameSync(p.join(_sandboxDir, to));
271 }, "rename directory $from to $to");
272 }
273
204 /// Schedules deleting a directory in the sandbox at [path]. 274 /// Schedules deleting a directory in the sandbox at [path].
205 void deleteDir(String path) { 275 void deleteDir(String path) {
206 schedule(() { 276 schedule(() {
207 new Directory(p.join(_sandboxDir, path)).deleteSync(recursive: true); 277 new Directory(p.join(_sandboxDir, path)).deleteSync(recursive: true);
208 }, "delete directory $path"); 278 }, "delete directory $path");
209 } 279 }
210 280
211 /// A [Matcher] for [WatchEvent]s. 281 /// Runs [callback] with every permutation of non-negative [i], [j], and [k]
212 class _ChangeMatcher extends Matcher { 282 /// less than [limit].
213 /// The expected change. 283 ///
214 final ChangeType type; 284 /// [limit] defaults to 3.
215 285 void withPermutations(callback(int i, int j, int k), {int limit}) {
216 /// The expected path. 286 if (limit == null) limit = 3;
217 final String path; 287 for (var i = 0; i < limit; i++) {
218 288 for (var j = 0; j < limit; j++) {
219 _ChangeMatcher(this.type, this.path); 289 for (var k = 0; k < limit; k++) {
220 290 callback(i, j, k);
221 Description describe(Description description) { 291 }
222 description.add("$type $path"); 292 }
223 } 293 }
224
225 bool matches(item, Map matchState) =>
226 item is WatchEvent &&
227 item.type == type &&
228 p.normalize(item.path) == p.normalize(path);
229 } 294 }
OLDNEW
« no previous file with comments | « pkg/watcher/test/ready_test.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698