Handle watching a non-existent directory.
BUG=
R=nweiz@google.com
Review URL: https://codereview.chromium.org//22999008
git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@26153 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart
index c5dd9db..eb948f5 100644
--- a/pkgs/watcher/lib/src/directory_watcher.dart
+++ b/pkgs/watcher/lib/src/directory_watcher.dart
@@ -11,6 +11,7 @@
import 'async_queue.dart';
import 'stat.dart';
+import 'utils.dart';
import 'watch_event.dart';
/// Watches the contents of a directory and emits [WatchEvent]s when something
@@ -118,19 +119,33 @@
_filesToProcess.clear();
_polledFiles.clear();
+ endListing() {
+ assert(_state != _WatchState.UNSUBSCRIBED);
+ _listSubscription = null;
+
+ // Null tells the queue consumer that we're done listing.
+ _filesToProcess.add(null);
+ }
+
var stream = new Directory(directory).list(recursive: true);
_listSubscription = stream.listen((entity) {
assert(_state != _WatchState.UNSUBSCRIBED);
if (entity is! File) return;
_filesToProcess.add(entity.path);
- }, onDone: () {
- assert(_state != _WatchState.UNSUBSCRIBED);
- _listSubscription = null;
+ }, onError: (error) {
+ if (isDirectoryNotFoundException(error)) {
+ // If the directory doesn't exist, we end the listing normally, which
+ // has the desired effect of marking all files that were in the
+ // directory as being removed.
+ endListing();
+ return;
+ }
- // Null tells the queue consumer that we're done listing.
- _filesToProcess.add(null);
- });
+ // It's some unknown error. Pipe it over to the event stream so we don't
+ // take down the whole isolate.
+ _events.addError(error);
+ }, onDone: endListing);
}
/// Processes [file] to determine if it has been modified since the last
diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart
new file mode 100644
index 0000000..319835e
--- /dev/null
+++ b/pkgs/watcher/lib/src/utils.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library watcher.utils;
+
+import 'dart:io';
+
+/// Returns `true` if [error] is a [DirectoryException] for a missing directory.
+bool isDirectoryNotFoundException(error) {
+ if (error is! DirectoryException) return false;
+
+ // See dartbug.com/12461 and tests/standalone/io/directory_error_test.dart.
+ var notFoundCode = Platform.operatingSystem == "windows" ? 3 : 2;
+ return error.osError.errorCode == notFoundCode;
+}
diff --git a/pkgs/watcher/test/directory_watcher_test.dart b/pkgs/watcher/test/directory_watcher_test.dart
index 9070179..841dd08 100644
--- a/pkgs/watcher/test/directory_watcher_test.dart
+++ b/pkgs/watcher/test/directory_watcher_test.dart
@@ -86,4 +86,23 @@
writeFile("a/b/c/d/file.txt");
expectAddEvent("a/b/c/d/file.txt");
});
+
+ test('watches a directory created after the watcher', () {
+ // Watch a subdirectory that doesn't exist yet.
+ createWatcher(dir: "a");
+
+ // This implicity creates it.
+ writeFile("a/b/c/d/file.txt");
+ expectAddEvent("a/b/c/d/file.txt");
+ });
+
+ test('when the watched directory is deleted, removes all files', () {
+ writeFile("dir/a.txt");
+ writeFile("dir/b.txt");
+
+ createWatcher(dir: "dir");
+
+ deleteDir("dir");
+ expectRemoveEvents(["dir/a.txt", "dir/b.txt"]);
+ });
}
diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart
index 65d4719..18bec97 100644
--- a/pkgs/watcher/test/utils.dart
+++ b/pkgs/watcher/test/utils.dart
@@ -76,9 +76,17 @@
/// Normally, this will pause the schedule until the watcher is done scanning
/// and is polling for changes. If you pass `false` for [waitForReady], it will
/// not schedule this delay.
-DirectoryWatcher createWatcher({bool waitForReady}) {
+///
+/// If [dir] is provided, watches a subdirectory in the sandbox with that name.
+DirectoryWatcher createWatcher({String dir, bool waitForReady}) {
+ if (dir == null) {
+ dir = _sandboxDir;
+ } else {
+ dir = p.join(_sandboxDir, dir);
+ }
+
// Use a short delay to make the tests run quickly.
- _watcher = new DirectoryWatcher(_sandboxDir,
+ _watcher = new DirectoryWatcher(dir,
pollingDelay: new Duration(milliseconds: 100));
// Wait until the scan is finished so that we don't miss changes to files
@@ -95,31 +103,45 @@
return _watcher;
}
-void expectEvent(ChangeType type, String path) {
- // Immediately create the future. This ensures we don't register too late and
- // drop the event before we receive it.
- var future = _watcher.events.elementAt(_nextEvent++).then((event) {
- expect(event, new _ChangeMatcher(type, path));
- });
+/// Expects that the next set of events will all be changes of [type] on
+/// [paths].
+///
+/// Validates that events are delivered for all paths in [paths], but allows
+/// them in any order.
+void expectEvents(ChangeType type, Iterable<String> paths) {
+ var pathSet = paths.map((path) => p.join(_sandboxDir, path)).toSet();
- // Make sure the schedule is watching it in case it fails.
- currentSchedule.wrapFuture(future);
+ // Create an expectation for as many paths as we have.
+ var futures = [];
+
+ for (var i = 0; i < paths.length; i++) {
+ // Immediately create the futures. This ensures we don't register too
+ // late and drop the event before we receive it.
+ var future = _watcher.events.elementAt(_nextEvent++).then((event) {
+ expect(event.type, equals(type));
+ expect(pathSet, contains(event.path));
+
+ pathSet.remove(event.path);
+ });
+
+ // Make sure the schedule is watching it in case it fails.
+ currentSchedule.wrapFuture(future);
+
+ futures.add(future);
+ }
// Schedule it so that later file modifications don't occur until after this
// event is received.
- schedule(() => future, "wait for $type event");
+ schedule(() => Future.wait(futures),
+ "wait for $type events on ${paths.join(', ')}");
}
-void expectAddEvent(String path) {
- expectEvent(ChangeType.ADD, p.join(_sandboxDir, path));
-}
+void expectAddEvent(String path) => expectEvents(ChangeType.ADD, [path]);
+void expectModifyEvent(String path) => expectEvents(ChangeType.MODIFY, [path]);
+void expectRemoveEvent(String path) => expectEvents(ChangeType.REMOVE, [path]);
-void expectModifyEvent(String path) {
- expectEvent(ChangeType.MODIFY, p.join(_sandboxDir, path));
-}
-
-void expectRemoveEvent(String path) {
- expectEvent(ChangeType.REMOVE, p.join(_sandboxDir, path));
+void expectRemoveEvents(Iterable<String> paths) {
+ expectEvents(ChangeType.REMOVE, paths);
}
/// Schedules writing a file in the sandbox at [path] with [contents].
@@ -175,6 +197,13 @@
}, "rename file $from to $to");
}
+/// Schedules deleting a directory in the sandbox at [path].
+void deleteDir(String path) {
+ schedule(() {
+ new Directory(p.join(_sandboxDir, path)).deleteSync(recursive: true);
+ }, "delete directory $path");
+}
+
/// A [Matcher] for [WatchEvent]s.
class _ChangeMatcher extends Matcher {
/// The expected change.