From f147253441b13466847e60bcfd8f592b2366b224 Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" Date: Fri, 12 Jul 2013 21:15:02 +0000 Subject: [PATCH 001/201] File watching package. BUG= R=nweiz@google.com Review URL: https://codereview.chromium.org//18612013 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@24971 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/README.md | 2 + pkgs/watcher/example/watch.dart | 24 ++ pkgs/watcher/lib/src/directory_watcher.dart | 226 +++++++++++++++++ pkgs/watcher/lib/src/stat.dart | 33 +++ pkgs/watcher/lib/src/watch_event.dart | 35 +++ pkgs/watcher/lib/watcher.dart | 8 + pkgs/watcher/pubspec.yaml | 13 + pkgs/watcher/test/directory_watcher_test.dart | 239 ++++++++++++++++++ pkgs/watcher/test/utils.dart | 186 ++++++++++++++ 9 files changed, 766 insertions(+) create mode 100644 pkgs/watcher/README.md create mode 100644 pkgs/watcher/example/watch.dart create mode 100644 pkgs/watcher/lib/src/directory_watcher.dart create mode 100644 pkgs/watcher/lib/src/stat.dart create mode 100644 pkgs/watcher/lib/src/watch_event.dart create mode 100644 pkgs/watcher/lib/watcher.dart create mode 100644 pkgs/watcher/pubspec.yaml create mode 100644 pkgs/watcher/test/directory_watcher_test.dart create mode 100644 pkgs/watcher/test/utils.dart diff --git a/pkgs/watcher/README.md b/pkgs/watcher/README.md new file mode 100644 index 000000000..75b0470f0 --- /dev/null +++ b/pkgs/watcher/README.md @@ -0,0 +1,2 @@ +A file watcher. It monitors (currently by polling) for changes to contents of +directories and notifies you when files have been added, removed, or modified. \ No newline at end of file diff --git a/pkgs/watcher/example/watch.dart b/pkgs/watcher/example/watch.dart new file mode 100644 index 000000000..247f66a5e --- /dev/null +++ b/pkgs/watcher/example/watch.dart @@ -0,0 +1,24 @@ +// 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. + +/// Watches the given directory and prints each modification to it. +library watch; + +import 'dart:io'; + +import 'package:pathos/path.dart' as pathos; +import 'package:watcher/watcher.dart'; + +main() { + var args = new Options().arguments; + if (args.length != 1) { + print("Usage: watch "); + return; + } + + var watcher = new DirectoryWatcher(pathos.absolute(args[0])); + watcher.events.listen((event) { + print(event); + }); +} \ No newline at end of file diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart new file mode 100644 index 000000000..0f297ba90 --- /dev/null +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -0,0 +1,226 @@ +// 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.directory_watcher; + +import 'dart:async'; +import 'dart:io'; + +import 'package:crypto/crypto.dart'; + +import 'stat.dart'; +import 'watch_event.dart'; + +/// Watches the contents of a directory and emits [WatchEvent]s when something +/// in the directory has changed. +class DirectoryWatcher { + /// The directory whose contents are being monitored. + final String directory; + + /// The broadcast [Stream] of events that have occurred to files in + /// [directory]. + /// + /// Changes will only be monitored while this stream has subscribers. Any + /// file changes that occur during periods when there are no subscribers + /// will not be reported the next time a subscriber is added. + Stream get events => _events.stream; + StreamController _events; + + _WatchState _state = _WatchState.notWatching; + + /// A [Future] that completes when the watcher is initialized and watching + /// for file changes. + /// + /// If the watcher is not currently monitoring the directory (because there + /// are no subscribers to [events]), this returns a future that isn't + /// complete yet. It will complete when a subscriber starts listening and + /// the watcher finishes any initialization work it needs to do. + /// + /// If the watcher is already monitoring, this returns an already complete + /// future. + Future get ready => _ready.future; + Completer _ready = new Completer(); + + /// The previous status of the files in the directory. + /// + /// Used to tell which files have been modified. + final _statuses = new Map(); + + /// Creates a new [DirectoryWatcher] monitoring [directory]. + DirectoryWatcher(this.directory) { + _events = new StreamController.broadcast(onListen: () { + _state = _state.listen(this); + }, onCancel: () { + _state = _state.cancel(this); + }); + } + + /// Starts the asynchronous polling process. + /// + /// Scans the contents of the directory and compares the results to the + /// previous scan. Loops to continue monitoring as long as there are + /// subscribers to the [events] stream. + Future _watch() { + var files = new Set(); + + var stream = new Directory(directory).list(recursive: true); + + return stream.map((entity) { + if (entity is! File) return new Future.value(); + files.add(entity.path); + // TODO(rnystrom): These all run as fast as possible and read the + // contents of the files. That means there's a pretty big IO hit all at + // once. Maybe these should be queued up and rate limited? + return _refreshFile(entity.path); + }).toList().then((futures) { + // Once the listing is done, make sure to wait until each file is also + // done. + return Future.wait(futures); + }).then((_) { + var removedFiles = _statuses.keys.toSet().difference(files); + for (var removed in removedFiles) { + if (_state.shouldNotify) { + _events.add(new WatchEvent(ChangeType.REMOVE, removed)); + } + _statuses.remove(removed); + } + + var previousState = _state; + _state = _state.finish(this); + + // If we were already sending notifications, add a bit of delay before + // restarting just so that we don't whale on the file system. + // TODO(rnystrom): Tune this and/or make it tunable? + if (_state.shouldNotify) { + return new Future.delayed(new Duration(seconds: 1)); + } + }).then((_) { + // Make sure we haven't transitioned to a non-watching state during the + // delay. + if (_state.shouldWatch) _watch(); + }); + } + + /// Compares the current state of the file at [path] to the state it was in + /// the last time it was scanned. + Future _refreshFile(String path) { + return getModificationTime(path).then((modified) { + var lastStatus = _statuses[path]; + + // If it's modification time hasn't changed, assume the file is unchanged. + if (lastStatus != null && lastStatus.modified == modified) return; + + return _hashFile(path).then((hash) { + var status = new _FileStatus(modified, hash); + _statuses[path] = status; + + // Only notify if the file contents changed. + if (_state.shouldNotify && + (lastStatus == null || !_sameHash(lastStatus.hash, hash))) { + var change = lastStatus == null ? ChangeType.ADD : ChangeType.MODIFY; + _events.add(new WatchEvent(change, path)); + } + }); + }); + } + + /// Calculates the SHA-1 hash of the file at [path]. + Future> _hashFile(String path) { + return new File(path).readAsBytes().then((bytes) { + var sha1 = new SHA1(); + sha1.add(bytes); + return sha1.close(); + }); + } + + /// Returns `true` if [a] and [b] are the same hash value, i.e. the same + /// series of byte values. + bool _sameHash(List a, List b) { + // Hashes should always be the same size. + assert(a.length == b.length); + + for (var i = 0; i < a.length; i++) { + if (a[i] != b[i]) return false; + } + + return true; + } +} + +/// An "event" that is sent to the [_WatchState] FSM to trigger state +/// transitions. +typedef _WatchState _WatchStateEvent(DirectoryWatcher watcher); + +/// The different states that the watcher can be in and the transitions between +/// them. +/// +/// This class defines a finite state machine for keeping track of what the +/// asynchronous file polling is doing. Each instance of this is a state in the +/// machine and its [listen], [cancel], and [finish] fields define the state +/// transitions when those events occur. +class _WatchState { + /// The watcher has no subscribers. + static final notWatching = new _WatchState( + listen: (watcher) { + watcher._watch(); + return _WatchState.scanning; + }); + + /// The watcher has subscribers and is scanning for pre-existing files. + static final scanning = new _WatchState( + cancel: (watcher) { + // No longer watching, so create a new incomplete ready future. + watcher._ready = new Completer(); + return _WatchState.cancelling; + }, finish: (watcher) { + watcher._ready.complete(); + return _WatchState.watching; + }, shouldWatch: true); + + /// The watcher was unsubscribed while polling and we're waiting for the poll + /// to finish. + static final cancelling = new _WatchState( + listen: (_) => _WatchState.scanning, + finish: (_) => _WatchState.notWatching); + + /// The watcher has subscribers, we have scanned for pre-existing files and + /// now we're polling for changes. + static final watching = new _WatchState( + cancel: (watcher) { + // No longer watching, so create a new incomplete ready future. + watcher._ready = new Completer(); + return _WatchState.cancelling; + }, finish: (_) => _WatchState.watching, + shouldWatch: true, shouldNotify: true); + + /// Called when the first subscriber to the watcher has been added. + final _WatchStateEvent listen; + + /// Called when all subscriptions on the watcher have been cancelled. + final _WatchStateEvent cancel; + + /// Called when a poll loop has finished. + final _WatchStateEvent finish; + + /// If the directory watcher should be watching the file system while in + /// this state. + final bool shouldWatch; + + /// If a change event should be sent for a file modification while in this + /// state. + final bool shouldNotify; + + _WatchState({this.listen, this.cancel, this.finish, + this.shouldWatch: false, this.shouldNotify: false}); +} + +class _FileStatus { + /// The last time the file was modified. + DateTime modified; + + /// The SHA-1 hash of the contents of the file. + List hash; + + _FileStatus(this.modified, this.hash); +} \ No newline at end of file diff --git a/pkgs/watcher/lib/src/stat.dart b/pkgs/watcher/lib/src/stat.dart new file mode 100644 index 000000000..d36eff3bd --- /dev/null +++ b/pkgs/watcher/lib/src/stat.dart @@ -0,0 +1,33 @@ +// 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.stat; + +import 'dart:async'; +import 'dart:io'; + +/// A function that takes a file path and returns the last modified time for +/// the file at that path. +typedef DateTime MockTimeCallback(String path); + +MockTimeCallback _mockTimeCallback; + +/// Overrides the default behavior for accessing a file's modification time +/// with [callback]. +/// +/// The OS file modification time has pretty rough granularity (like a few +/// seconds) which can make for slow tests that rely on modtime. This lets you +/// replace it with something you control. +void mockGetModificationTime(MockTimeCallback callback) { + _mockTimeCallback = callback; +} + +/// Gets the modification time for the file at [path]. +Future getModificationTime(String path) { + if (_mockTimeCallback != null) { + return new Future.value(_mockTimeCallback(path)); + } + + return FileStat.stat(path).then((stat) => stat.modified); +} diff --git a/pkgs/watcher/lib/src/watch_event.dart b/pkgs/watcher/lib/src/watch_event.dart new file mode 100644 index 000000000..d998a2532 --- /dev/null +++ b/pkgs/watcher/lib/src/watch_event.dart @@ -0,0 +1,35 @@ +// 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.watch_event; + +/// An event describing a single change to the file system. +class WatchEvent { + /// The manner in which the file at [path] has changed. + final ChangeType type; + + /// The path of the file that changed. + final String path; + + WatchEvent(this.type, this.path); + + String toString() => "$type $path"; +} + +/// Enum for what kind of change has happened to a file. +class ChangeType { + /// A new file has been added. + static const ADD = const ChangeType("add"); + + /// A file has been removed. + static const REMOVE = const ChangeType("remove"); + + /// The contents of a file have changed. + static const MODIFY = const ChangeType("modify"); + + final String _name; + const ChangeType(this._name); + + String toString() => _name; +} \ No newline at end of file diff --git a/pkgs/watcher/lib/watcher.dart b/pkgs/watcher/lib/watcher.dart new file mode 100644 index 000000000..c4824b8ec --- /dev/null +++ b/pkgs/watcher/lib/watcher.dart @@ -0,0 +1,8 @@ +// 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; + +export 'src/watch_event.dart'; +export 'src/directory_watcher.dart'; diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml new file mode 100644 index 000000000..263832efb --- /dev/null +++ b/pkgs/watcher/pubspec.yaml @@ -0,0 +1,13 @@ +name: watcher +author: "Dart Team " +homepage: http://www.dartlang.org +description: > + A file watcher. It monitors (currently by polling) for changes to contents + of directories and notifies you when files have been added, removed, or + modified. +dependencies: + crypto: any + path: any +dev_dependencies: + scheduled_test: any + unittest: any diff --git a/pkgs/watcher/test/directory_watcher_test.dart b/pkgs/watcher/test/directory_watcher_test.dart new file mode 100644 index 000000000..635f7ee67 --- /dev/null +++ b/pkgs/watcher/test/directory_watcher_test.dart @@ -0,0 +1,239 @@ +// Copyright (c) 2012, 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. + +import 'dart:async'; +import 'dart:io'; + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/watcher.dart'; + +import 'utils.dart'; + +main() { + initConfig(); + + setUp(createSandbox); + + test('does not notify for files that already exist when started', () { + // Make some pre-existing files. + writeFile("a.txt"); + writeFile("b.txt"); + + createWatcher(); + + // Change one after the watcher is running. + writeFile("b.txt", contents: "modified"); + + // We should get a modify event for the changed file, but no add events + // for them before this. + expectModifyEvent("b.txt"); + }); + + test('notifies when a file is added', () { + createWatcher(); + writeFile("file.txt"); + expectAddEvent("file.txt"); + }); + + test('notifies when a file is modified', () { + writeFile("file.txt"); + createWatcher(); + writeFile("file.txt", contents: "modified"); + expectModifyEvent("file.txt"); + }); + + test('notifies when a file is removed', () { + writeFile("file.txt"); + createWatcher(); + deleteFile("file.txt"); + expectRemoveEvent("file.txt"); + }); + + test('notifies when a file is moved', () { + writeFile("old.txt"); + createWatcher(); + renameFile("old.txt", "new.txt"); + expectAddEvent("new.txt"); + expectRemoveEvent("old.txt"); + }); + + test('notifies when a file is modified multiple times', () { + writeFile("file.txt"); + createWatcher(); + writeFile("file.txt", contents: "modified"); + expectModifyEvent("file.txt"); + writeFile("file.txt", contents: "modified again"); + expectModifyEvent("file.txt"); + }); + + test('does not notify if the file contents are unchanged', () { + writeFile("a.txt", contents: "same"); + writeFile("b.txt", contents: "before"); + createWatcher(); + writeFile("a.txt", contents: "same"); + writeFile("b.txt", contents: "after"); + expectModifyEvent("b.txt"); + }); + + test('does not notify if the modification time did not change', () { + writeFile("a.txt", contents: "before"); + writeFile("b.txt", contents: "before"); + createWatcher(); + writeFile("a.txt", contents: "after", updateModified: false); + writeFile("b.txt", contents: "after"); + expectModifyEvent("b.txt"); + }); + + test('watches files in subdirectories', () { + createWatcher(); + writeFile("a/b/c/d/file.txt"); + expectAddEvent("a/b/c/d/file.txt"); + }); + + test('does not notify for changes when there were no subscribers', () { + // Note that this test doesn't rely as heavily on the test functions in + // utils.dart because it needs to be very explicit about when the event + // stream is and is not subscribed. + var watcher = createWatcher(); + + // Subscribe to the events. + var completer = new Completer(); + var subscription = watcher.events.listen((event) { + expect(event.type, equals(ChangeType.ADD)); + expect(event.path, endsWith("file.txt")); + completer.complete(); + }); + + writeFile("file.txt"); + + // Then wait until we get an event for it. + schedule(() => completer.future); + + // Unsubscribe. + schedule(() { + subscription.cancel(); + }); + + // Now write a file while we aren't listening. + writeFile("unwatched.txt"); + + // Then start listening again. + schedule(() { + completer = new Completer(); + subscription = watcher.events.listen((event) { + // We should get an event for the third file, not the one added while + // we weren't subscribed. + expect(event.type, equals(ChangeType.ADD)); + expect(event.path, endsWith("added.txt")); + completer.complete(); + }); + }); + + // The watcher will have been cancelled and then resumed in the middle of + // its pause between polling loops. That means the second scan to skip + // what changed while we were unsubscribed won't happen until after that + // delay is done. Wait long enough for that to happen. + schedule(() => new Future.delayed(new Duration(seconds: 1))); + + // And add a third file. + writeFile("added.txt"); + + // Wait until we get an event for the third file. + schedule(() => completer.future); + + schedule(() { + subscription.cancel(); + }); + }); + + + test('ready does not complete until after subscription', () { + var watcher = createWatcher(waitForReady: false); + + var ready = false; + watcher.ready.then((_) { + ready = true; + }); + + // Should not be ready yet. + schedule(() { + expect(ready, isFalse); + }); + + // Subscribe to the events. + schedule(() { + var subscription = watcher.events.listen((event) {}); + + currentSchedule.onComplete.schedule(() { + subscription.cancel(); + }); + }); + + // Should eventually be ready. + schedule(() => watcher.ready); + + schedule(() { + expect(ready, isTrue); + }); + }); + + test('ready completes immediately when already ready', () { + var watcher = createWatcher(waitForReady: false); + + // Subscribe to the events. + schedule(() { + var subscription = watcher.events.listen((event) {}); + + currentSchedule.onComplete.schedule(() { + subscription.cancel(); + }); + }); + + // Should eventually be ready. + schedule(() => watcher.ready); + + // Now ready should be a future that immediately completes. + var ready = false; + schedule(() { + watcher.ready.then((_) { + ready = true; + }); + }); + + schedule(() { + expect(ready, isTrue); + }); + }); + + test('ready returns a future that does not complete after unsubscribing', () { + var watcher = createWatcher(waitForReady: false); + + // Subscribe to the events. + var subscription; + schedule(() { + subscription = watcher.events.listen((event) {}); + }); + + var ready = false; + + // Wait until ready. + schedule(() => watcher.ready); + + // Now unsubscribe. + schedule(() { + subscription.cancel(); + + // Track when it's ready again. + ready = false; + watcher.ready.then((_) { + ready = true; + }); + }); + + // Should be back to not ready. + schedule(() { + expect(ready, isFalse); + }); + }); +} diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart new file mode 100644 index 000000000..387b4ad20 --- /dev/null +++ b/pkgs/watcher/test/utils.dart @@ -0,0 +1,186 @@ +// Copyright (c) 2012, 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.test.utils; + +import 'dart:async'; +import 'dart:io'; + +import 'package:path/path.dart'; +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:unittest/compact_vm_config.dart'; +import 'package:watcher/watcher.dart'; +import 'package:watcher/src/stat.dart'; + +/// The path to the temporary sandbox created for each test. All file +/// operations are implicitly relative to this directory. +String _sandboxDir; + +/// The [DirectoryWatcher] being used for the current scheduled test. +DirectoryWatcher _watcher; + +/// The index in [_watcher]'s event stream for the next event. When event +/// expectations are set using [expectEvent] (et. al.), they use this to +/// expect a series of events in order. +var _nextEvent = 0; + +/// The mock modification times (in milliseconds since epoch) for each file. +/// +/// The actual file system has pretty coarse granularity for file modification +/// times. This means using the real file system requires us to put delays in +/// the tests to ensure we wait long enough between operations for the mod time +/// to be different. +/// +/// Instead, we'll just mock that out. Each time a file is written, we manually +/// increment the mod time for that file instantly. +Map _mockFileModificationTimes; + +void initConfig() { + useCompactVMConfiguration(); +} + +/// Creates the sandbox directory the other functions in this library use and +/// ensures it's deleted when the test ends. +/// +/// This should usually be called by [setUp]. +void createSandbox() { + var dir = new Directory("").createTempSync(); + _sandboxDir = dir.path; + + _mockFileModificationTimes = new Map(); + mockGetModificationTime((path) { + path = relative(path, from: _sandboxDir); + + // Make sure we got a path in the sandbox. + assert(isRelative(path) && !path.startsWith("..")); + + return new DateTime.fromMillisecondsSinceEpoch( + _mockFileModificationTimes[path]); + }); + + // Delete the sandbox when done. + currentSchedule.onComplete.schedule(() { + if (_sandboxDir != null) { + new Directory(_sandboxDir).deleteSync(recursive: true); + _sandboxDir = null; + } + + _mockFileModificationTimes = null; + mockGetModificationTime(null); + }, "delete sandbox"); +} + +/// Creates a new [DirectoryWatcher] that watches a temporary directory. +/// +/// 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}) { + _watcher = new DirectoryWatcher(_sandboxDir); + + // Wait until the scan is finished so that we don't miss changes to files + // that could occur before the scan completes. + if (waitForReady != false) { + schedule(() => _watcher.ready); + } + + currentSchedule.onComplete.schedule(() { + _nextEvent = 0; + _watcher = null; + }, "reset watcher"); + + 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)); + }); + + // Make sure the schedule is watching it in case it fails. + currentSchedule.wrapFuture(future); + + // Schedule it so that later file modifications don't occur until after this + // event is received. + schedule(() => future); +} + +void expectAddEvent(String path) { + expectEvent(ChangeType.ADD, join(_sandboxDir, path)); +} + +void expectModifyEvent(String path) { + expectEvent(ChangeType.MODIFY, join(_sandboxDir, path)); +} + +void expectRemoveEvent(String path) { + expectEvent(ChangeType.REMOVE, join(_sandboxDir, path)); +} + +/// Schedules writing a file in the sandbox at [path] with [contents]. +/// +/// If [contents] is omitted, creates an empty file. If [updatedModified] is +/// `false`, the mock file modification time is not changed. +void writeFile(String path, {String contents, bool updateModified}) { + if (contents == null) contents = ""; + if (updateModified == null) updateModified = true; + + schedule(() { + var fullPath = join(_sandboxDir, path); + + // Create any needed subdirectories. + var dir = new Directory(dirname(fullPath)); + if (!dir.existsSync()) { + dir.createSync(recursive: true); + } + + new File(fullPath).writeAsStringSync(contents); + + // Manually update the mock modification time for the file. + if (updateModified) { + var milliseconds = _mockFileModificationTimes.putIfAbsent(path, () => 0); + _mockFileModificationTimes[path]++; + } + }); +} + +/// Schedules deleting a file in the sandbox at [path]. +void deleteFile(String path) { + schedule(() { + new File(join(_sandboxDir, path)).deleteSync(); + }); +} + +/// Schedules renaming a file in the sandbox from [from] to [to]. +/// +/// If [contents] is omitted, creates an empty file. +void renameFile(String from, String to) { + schedule(() { + new File(join(_sandboxDir, from)).renameSync(join(_sandboxDir, to)); + + // Manually update the mock modification time for the file. + var milliseconds = _mockFileModificationTimes.putIfAbsent(to, () => 0); + _mockFileModificationTimes[to]++; + }); +} + +/// A [Matcher] for [WatchEvent]s. +class _ChangeMatcher extends BaseMatcher { + /// The expected change. + final ChangeType type; + + /// The expected path. + final String path; + + _ChangeMatcher(this.type, this.path); + + Description describe(Description description) { + description.add("$type $path"); + } + + bool matches(item, Map matchState) => + item is WatchEvent && item.type == type && item.path == path; +} From a335cac83528fe26f07f810b240da939505137ea Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" Date: Fri, 12 Jul 2013 21:47:02 +0000 Subject: [PATCH 002/201] Fix pathos->path in watcher example. BUG= R=nweiz@google.com Review URL: https://codereview.chromium.org//18878003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@24976 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/example/watch.dart | 4 ++-- pkgs/watcher/test/utils.dart | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pkgs/watcher/example/watch.dart b/pkgs/watcher/example/watch.dart index 247f66a5e..235578dd6 100644 --- a/pkgs/watcher/example/watch.dart +++ b/pkgs/watcher/example/watch.dart @@ -7,7 +7,7 @@ library watch; import 'dart:io'; -import 'package:pathos/path.dart' as pathos; +import 'package:path/path.dart' as p; import 'package:watcher/watcher.dart'; main() { @@ -17,7 +17,7 @@ main() { return; } - var watcher = new DirectoryWatcher(pathos.absolute(args[0])); + var watcher = new DirectoryWatcher(p.absolute(args[0])); watcher.events.listen((event) { print(event); }); diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 387b4ad20..7b3da028a 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -7,7 +7,7 @@ library watcher.test.utils; import 'dart:async'; import 'dart:io'; -import 'package:path/path.dart'; +import 'package:path/path.dart' as p; import 'package:scheduled_test/scheduled_test.dart'; import 'package:unittest/compact_vm_config.dart'; import 'package:watcher/watcher.dart'; @@ -50,10 +50,10 @@ void createSandbox() { _mockFileModificationTimes = new Map(); mockGetModificationTime((path) { - path = relative(path, from: _sandboxDir); + path = p.relative(path, from: _sandboxDir); // Make sure we got a path in the sandbox. - assert(isRelative(path) && !path.startsWith("..")); + assert(p.isRelative(path) && !path.startsWith("..")); return new DateTime.fromMillisecondsSinceEpoch( _mockFileModificationTimes[path]); @@ -109,15 +109,15 @@ void expectEvent(ChangeType type, String path) { } void expectAddEvent(String path) { - expectEvent(ChangeType.ADD, join(_sandboxDir, path)); + expectEvent(ChangeType.ADD, p.join(_sandboxDir, path)); } void expectModifyEvent(String path) { - expectEvent(ChangeType.MODIFY, join(_sandboxDir, path)); + expectEvent(ChangeType.MODIFY, p.join(_sandboxDir, path)); } void expectRemoveEvent(String path) { - expectEvent(ChangeType.REMOVE, join(_sandboxDir, path)); + expectEvent(ChangeType.REMOVE, p.join(_sandboxDir, path)); } /// Schedules writing a file in the sandbox at [path] with [contents]. @@ -129,10 +129,10 @@ void writeFile(String path, {String contents, bool updateModified}) { if (updateModified == null) updateModified = true; schedule(() { - var fullPath = join(_sandboxDir, path); + var fullPath = p.join(_sandboxDir, path); // Create any needed subdirectories. - var dir = new Directory(dirname(fullPath)); + var dir = new Directory(p.dirname(fullPath)); if (!dir.existsSync()) { dir.createSync(recursive: true); } @@ -150,7 +150,7 @@ void writeFile(String path, {String contents, bool updateModified}) { /// Schedules deleting a file in the sandbox at [path]. void deleteFile(String path) { schedule(() { - new File(join(_sandboxDir, path)).deleteSync(); + new File(p.join(_sandboxDir, path)).deleteSync(); }); } @@ -159,7 +159,7 @@ void deleteFile(String path) { /// If [contents] is omitted, creates an empty file. void renameFile(String from, String to) { schedule(() { - new File(join(_sandboxDir, from)).renameSync(join(_sandboxDir, to)); + new File(p.join(_sandboxDir, from)).renameSync(p.join(_sandboxDir, to)); // Manually update the mock modification time for the file. var milliseconds = _mockFileModificationTimes.putIfAbsent(to, () => 0); From 63933a6660a971707356b156e403a559e25f8e33 Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" Date: Fri, 12 Jul 2013 22:45:43 +0000 Subject: [PATCH 003/201] Split up tests and add some heartbeats to try to make them not timeout. BUG= R=nweiz@google.com Review URL: https://codereview.chromium.org//18877005 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@24978 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/directory_watcher.dart | 15 +- pkgs/watcher/test/directory_watcher_test.dart | 146 ------------------ pkgs/watcher/test/no_subscription_test.dart | 73 +++++++++ pkgs/watcher/test/ready_test.dart | 106 +++++++++++++ pkgs/watcher/test/utils.dart | 4 +- 5 files changed, 195 insertions(+), 149 deletions(-) create mode 100644 pkgs/watcher/test/no_subscription_test.dart create mode 100644 pkgs/watcher/test/ready_test.dart diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 0f297ba90..61bf6c52a 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -42,13 +42,24 @@ class DirectoryWatcher { Future get ready => _ready.future; Completer _ready = new Completer(); + /// The amount of time the watcher pauses between successive polls of the + /// directory contents. + final Duration pollingDelay; + /// The previous status of the files in the directory. /// /// Used to tell which files have been modified. final _statuses = new Map(); /// Creates a new [DirectoryWatcher] monitoring [directory]. - DirectoryWatcher(this.directory) { + /// + /// If [pollingDelay] is passed, it specifies the amount of time the watcher + /// will pause between successive polls of the directory contents. Making + /// this shorter will give more immediate feedback at the expense of doing + /// more IO and higher CPU usage. Defaults to one second. + DirectoryWatcher(this.directory, {Duration pollingDelay}) + : pollingDelay = pollingDelay != null ? pollingDelay : + new Duration(seconds: 1) { _events = new StreamController.broadcast(onListen: () { _state = _state.listen(this); }, onCancel: () { @@ -93,7 +104,7 @@ class DirectoryWatcher { // restarting just so that we don't whale on the file system. // TODO(rnystrom): Tune this and/or make it tunable? if (_state.shouldNotify) { - return new Future.delayed(new Duration(seconds: 1)); + return new Future.delayed(pollingDelay); } }).then((_) { // Make sure we haven't transitioned to a non-watching state during the diff --git a/pkgs/watcher/test/directory_watcher_test.dart b/pkgs/watcher/test/directory_watcher_test.dart index 635f7ee67..800d54d63 100644 --- a/pkgs/watcher/test/directory_watcher_test.dart +++ b/pkgs/watcher/test/directory_watcher_test.dart @@ -90,150 +90,4 @@ main() { writeFile("a/b/c/d/file.txt"); expectAddEvent("a/b/c/d/file.txt"); }); - - test('does not notify for changes when there were no subscribers', () { - // Note that this test doesn't rely as heavily on the test functions in - // utils.dart because it needs to be very explicit about when the event - // stream is and is not subscribed. - var watcher = createWatcher(); - - // Subscribe to the events. - var completer = new Completer(); - var subscription = watcher.events.listen((event) { - expect(event.type, equals(ChangeType.ADD)); - expect(event.path, endsWith("file.txt")); - completer.complete(); - }); - - writeFile("file.txt"); - - // Then wait until we get an event for it. - schedule(() => completer.future); - - // Unsubscribe. - schedule(() { - subscription.cancel(); - }); - - // Now write a file while we aren't listening. - writeFile("unwatched.txt"); - - // Then start listening again. - schedule(() { - completer = new Completer(); - subscription = watcher.events.listen((event) { - // We should get an event for the third file, not the one added while - // we weren't subscribed. - expect(event.type, equals(ChangeType.ADD)); - expect(event.path, endsWith("added.txt")); - completer.complete(); - }); - }); - - // The watcher will have been cancelled and then resumed in the middle of - // its pause between polling loops. That means the second scan to skip - // what changed while we were unsubscribed won't happen until after that - // delay is done. Wait long enough for that to happen. - schedule(() => new Future.delayed(new Duration(seconds: 1))); - - // And add a third file. - writeFile("added.txt"); - - // Wait until we get an event for the third file. - schedule(() => completer.future); - - schedule(() { - subscription.cancel(); - }); - }); - - - test('ready does not complete until after subscription', () { - var watcher = createWatcher(waitForReady: false); - - var ready = false; - watcher.ready.then((_) { - ready = true; - }); - - // Should not be ready yet. - schedule(() { - expect(ready, isFalse); - }); - - // Subscribe to the events. - schedule(() { - var subscription = watcher.events.listen((event) {}); - - currentSchedule.onComplete.schedule(() { - subscription.cancel(); - }); - }); - - // Should eventually be ready. - schedule(() => watcher.ready); - - schedule(() { - expect(ready, isTrue); - }); - }); - - test('ready completes immediately when already ready', () { - var watcher = createWatcher(waitForReady: false); - - // Subscribe to the events. - schedule(() { - var subscription = watcher.events.listen((event) {}); - - currentSchedule.onComplete.schedule(() { - subscription.cancel(); - }); - }); - - // Should eventually be ready. - schedule(() => watcher.ready); - - // Now ready should be a future that immediately completes. - var ready = false; - schedule(() { - watcher.ready.then((_) { - ready = true; - }); - }); - - schedule(() { - expect(ready, isTrue); - }); - }); - - test('ready returns a future that does not complete after unsubscribing', () { - var watcher = createWatcher(waitForReady: false); - - // Subscribe to the events. - var subscription; - schedule(() { - subscription = watcher.events.listen((event) {}); - }); - - var ready = false; - - // Wait until ready. - schedule(() => watcher.ready); - - // Now unsubscribe. - schedule(() { - subscription.cancel(); - - // Track when it's ready again. - ready = false; - watcher.ready.then((_) { - ready = true; - }); - }); - - // Should be back to not ready. - schedule(() { - expect(ready, isFalse); - }); - }); } diff --git a/pkgs/watcher/test/no_subscription_test.dart b/pkgs/watcher/test/no_subscription_test.dart new file mode 100644 index 000000000..e9cb4e363 --- /dev/null +++ b/pkgs/watcher/test/no_subscription_test.dart @@ -0,0 +1,73 @@ +// Copyright (c) 2012, 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. + +import 'dart:async'; +import 'dart:io'; + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/watcher.dart'; + +import 'utils.dart'; + +main() { + initConfig(); + + setUp(createSandbox); + + test('does not notify for changes when there were no subscribers', () { + // Note that this test doesn't rely as heavily on the test functions in + // utils.dart because it needs to be very explicit about when the event + // stream is and is not subscribed. + var watcher = createWatcher(); + + // Subscribe to the events. + var completer = new Completer(); + var subscription = watcher.events.listen((event) { + expect(event.type, equals(ChangeType.ADD)); + expect(event.path, endsWith("file.txt")); + completer.complete(); + }); + + writeFile("file.txt"); + + // Then wait until we get an event for it. + schedule(() => completer.future); + + // Unsubscribe. + schedule(() { + subscription.cancel(); + }); + + // Now write a file while we aren't listening. + writeFile("unwatched.txt"); + + // Then start listening again. + schedule(() { + completer = new Completer(); + subscription = watcher.events.listen((event) { + // We should get an event for the third file, not the one added while + // we weren't subscribed. + expect(event.type, equals(ChangeType.ADD)); + expect(event.path, endsWith("added.txt")); + completer.complete(); + }); + }); + + // The watcher will have been cancelled and then resumed in the middle of + // its pause between polling loops. That means the second scan to skip + // what changed while we were unsubscribed won't happen until after that + // delay is done. Wait long enough for that to happen. + schedule(() => new Future.delayed(new Duration(seconds: 1))); + + // And add a third file. + writeFile("added.txt"); + + // Wait until we get an event for the third file. + schedule(() => completer.future); + + schedule(() { + subscription.cancel(); + }); + }); +} diff --git a/pkgs/watcher/test/ready_test.dart b/pkgs/watcher/test/ready_test.dart new file mode 100644 index 000000000..dd799ce5a --- /dev/null +++ b/pkgs/watcher/test/ready_test.dart @@ -0,0 +1,106 @@ +// Copyright (c) 2012, 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. + +import 'dart:async'; +import 'dart:io'; + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/watcher.dart'; + +import 'utils.dart'; + +main() { + initConfig(); + + setUp(createSandbox); + + test('ready does not complete until after subscription', () { + var watcher = createWatcher(waitForReady: false); + + var ready = false; + watcher.ready.then((_) { + ready = true; + }); + + // Should not be ready yet. + schedule(() { + expect(ready, isFalse); + }); + + // Subscribe to the events. + schedule(() { + var subscription = watcher.events.listen((event) {}); + + currentSchedule.onComplete.schedule(() { + subscription.cancel(); + }); + }); + + // Should eventually be ready. + schedule(() => watcher.ready); + + schedule(() { + expect(ready, isTrue); + }); + }); + + test('ready completes immediately when already ready', () { + var watcher = createWatcher(waitForReady: false); + + // Subscribe to the events. + schedule(() { + var subscription = watcher.events.listen((event) {}); + + currentSchedule.onComplete.schedule(() { + subscription.cancel(); + }); + }); + + // Should eventually be ready. + schedule(() => watcher.ready); + + // Now ready should be a future that immediately completes. + var ready = false; + schedule(() { + watcher.ready.then((_) { + ready = true; + }); + }); + + schedule(() { + expect(ready, isTrue); + }); + }); + + test('ready returns a future that does not complete after unsubscribing', () { + var watcher = createWatcher(waitForReady: false); + + // Subscribe to the events. + var subscription; + schedule(() { + subscription = watcher.events.listen((event) {}); + }); + + var ready = false; + + // Wait until ready. + schedule(() => watcher.ready); + + // Now unsubscribe. + schedule(() { + subscription.cancel(); + + // Track when it's ready again. + ready = false; + watcher.ready.then((_) { + ready = true; + }); + }); + + // Should be back to not ready. + schedule(() { + expect(ready, isFalse); + }); + }); +} diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 7b3da028a..5a22e609b 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -77,7 +77,9 @@ void createSandbox() { /// and is polling for changes. If you pass `false` for [waitForReady], it will /// not schedule this delay. DirectoryWatcher createWatcher({bool waitForReady}) { - _watcher = new DirectoryWatcher(_sandboxDir); + // Use a short delay to make the tests run quickly. + _watcher = new DirectoryWatcher(_sandboxDir, + pollingDelay: new Duration(milliseconds: 100)); // Wait until the scan is finished so that we don't miss changes to files // that could occur before the scan completes. From f07eca145bf2697399131d99b60e1bcbd4ab2cbe Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" Date: Fri, 12 Jul 2013 23:25:28 +0000 Subject: [PATCH 004/201] Add some debug prints to figure out why a test is failing on windows. R=nweiz@google.com Review URL: https://codereview.chromium.org//18513006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@24983 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/test/utils.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 5a22e609b..d822e1fc2 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -48,6 +48,9 @@ void createSandbox() { var dir = new Directory("").createTempSync(); _sandboxDir = dir.path; + // TODO(rnystrom): Temporary while debugging the Windows bot. + print("create mock modtime map for $_sandboxDir"); + _mockFileModificationTimes = new Map(); mockGetModificationTime((path) { path = p.relative(path, from: _sandboxDir); @@ -55,12 +58,17 @@ void createSandbox() { // Make sure we got a path in the sandbox. assert(p.isRelative(path) && !path.startsWith("..")); + // TODO(rnystrom): Temporary while debugging the Windows bot. + print("get mock modtime for $path = ${_mockFileModificationTimes[path]}"); return new DateTime.fromMillisecondsSinceEpoch( _mockFileModificationTimes[path]); }); // Delete the sandbox when done. currentSchedule.onComplete.schedule(() { + // TODO(rnystrom): Temporary while debugging the Windows bot. + print("delete mock modtime map for $_sandboxDir"); + if (_sandboxDir != null) { new Directory(_sandboxDir).deleteSync(recursive: true); _sandboxDir = null; @@ -139,12 +147,17 @@ void writeFile(String path, {String contents, bool updateModified}) { dir.createSync(recursive: true); } + // TODO(rnystrom): Temporary while debugging the Windows bot. + print("write $path"); + new File(fullPath).writeAsStringSync(contents); // Manually update the mock modification time for the file. if (updateModified) { var milliseconds = _mockFileModificationTimes.putIfAbsent(path, () => 0); _mockFileModificationTimes[path]++; + // TODO(rnystrom): Temporary while debugging the Windows bot. + print(" update modtime to ${_mockFileModificationTimes[path]}"); } }); } @@ -163,9 +176,14 @@ void renameFile(String from, String to) { schedule(() { new File(p.join(_sandboxDir, from)).renameSync(p.join(_sandboxDir, to)); + // TODO(rnystrom): Temporary while debugging the Windows bot. + print("rename $from -> $to"); + // Manually update the mock modification time for the file. var milliseconds = _mockFileModificationTimes.putIfAbsent(to, () => 0); _mockFileModificationTimes[to]++; + // TODO(rnystrom): Temporary while debugging the Windows bot. + print(" update modtime to ${_mockFileModificationTimes[to]}"); }); } From 56a381d77526b03b96f7e1d8dcf673f5c6f135f1 Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" Date: Fri, 12 Jul 2013 23:47:49 +0000 Subject: [PATCH 005/201] Normalize paths in mock timestamp map. Makes sure separators are consistent on Windows. Review URL: https://codereview.chromium.org//18430006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@24984 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/test/utils.dart | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index d822e1fc2..747171657 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -48,27 +48,19 @@ void createSandbox() { var dir = new Directory("").createTempSync(); _sandboxDir = dir.path; - // TODO(rnystrom): Temporary while debugging the Windows bot. - print("create mock modtime map for $_sandboxDir"); - _mockFileModificationTimes = new Map(); mockGetModificationTime((path) { - path = p.relative(path, from: _sandboxDir); + path = p.normalize(p.relative(path, from: _sandboxDir)); // Make sure we got a path in the sandbox. assert(p.isRelative(path) && !path.startsWith("..")); - // TODO(rnystrom): Temporary while debugging the Windows bot. - print("get mock modtime for $path = ${_mockFileModificationTimes[path]}"); return new DateTime.fromMillisecondsSinceEpoch( _mockFileModificationTimes[path]); }); // Delete the sandbox when done. currentSchedule.onComplete.schedule(() { - // TODO(rnystrom): Temporary while debugging the Windows bot. - print("delete mock modtime map for $_sandboxDir"); - if (_sandboxDir != null) { new Directory(_sandboxDir).deleteSync(recursive: true); _sandboxDir = null; @@ -147,17 +139,15 @@ void writeFile(String path, {String contents, bool updateModified}) { dir.createSync(recursive: true); } - // TODO(rnystrom): Temporary while debugging the Windows bot. - print("write $path"); - new File(fullPath).writeAsStringSync(contents); // Manually update the mock modification time for the file. if (updateModified) { + // Make sure we always use the same separator on Windows. + path = p.normalize(path); + var milliseconds = _mockFileModificationTimes.putIfAbsent(path, () => 0); _mockFileModificationTimes[path]++; - // TODO(rnystrom): Temporary while debugging the Windows bot. - print(" update modtime to ${_mockFileModificationTimes[path]}"); } }); } @@ -176,14 +166,12 @@ void renameFile(String from, String to) { schedule(() { new File(p.join(_sandboxDir, from)).renameSync(p.join(_sandboxDir, to)); - // TODO(rnystrom): Temporary while debugging the Windows bot. - print("rename $from -> $to"); + // Make sure we always use the same separator on Windows. + to = p.normalize(to); // Manually update the mock modification time for the file. var milliseconds = _mockFileModificationTimes.putIfAbsent(to, () => 0); _mockFileModificationTimes[to]++; - // TODO(rnystrom): Temporary while debugging the Windows bot. - print(" update modtime to ${_mockFileModificationTimes[to]}"); }); } From 03d30957ff0f1818d1bcb26712282620ad5e7218 Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" Date: Sat, 13 Jul 2013 00:10:44 +0000 Subject: [PATCH 006/201] Normalize paths when matching them. Review URL: https://codereview.chromium.org//18464006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@24986 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/test/utils.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 747171657..3f5844ba4 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -190,5 +190,7 @@ class _ChangeMatcher extends BaseMatcher { } bool matches(item, Map matchState) => - item is WatchEvent && item.type == type && item.path == path; + item is WatchEvent && + item.type == type && + p.normalize(item.path) == p.normalize(path); } From 2e152fd2b08ba0bbf941d37733c95431f062eea7 Mon Sep 17 00:00:00 2001 From: "gram@google.com" Date: Tue, 23 Jul 2013 19:48:26 +0000 Subject: [PATCH 007/201] Add missing newline after Actual value in fail message. Remove the BasMatcher class; it made some sense when we had both 'interface' and 'class' but adds no value now. Fix a comment in the CustomMatcher documentation. Fix tests affected by these changes. R=sigmund@google.com Review URL: https://codereview.chromium.org//18442002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@25356 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/test/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 3f5844ba4..5053b76ad 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -176,7 +176,7 @@ void renameFile(String from, String to) { } /// A [Matcher] for [WatchEvent]s. -class _ChangeMatcher extends BaseMatcher { +class _ChangeMatcher extends Matcher { /// The expected change. final ChangeType type; From a8acfcb14db3685ae1490109af871492f29d9b4c Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" Date: Fri, 2 Aug 2013 22:01:39 +0000 Subject: [PATCH 008/201] Re-implement directory polling. BUG=https://code.google.com/p/dart/issues/detail?id=12107 R=nweiz@google.com Review URL: https://codereview.chromium.org//21628002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@25746 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/async_queue.dart | 74 ++++++ pkgs/watcher/lib/src/directory_watcher.dart | 281 +++++++++++--------- pkgs/watcher/test/no_subscription_test.dart | 10 +- pkgs/watcher/test/utils.dart | 10 +- 4 files changed, 240 insertions(+), 135 deletions(-) create mode 100644 pkgs/watcher/lib/src/async_queue.dart diff --git a/pkgs/watcher/lib/src/async_queue.dart b/pkgs/watcher/lib/src/async_queue.dart new file mode 100644 index 000000000..9456631af --- /dev/null +++ b/pkgs/watcher/lib/src/async_queue.dart @@ -0,0 +1,74 @@ +// 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.async_queue; + +import 'dart:async'; +import 'dart:collection'; + +typedef Future ItemProcessor(T item); +typedef void ErrorHandler(error); + +/// A queue of items that are sequentially, asynchronously processed. +/// +/// Unlike [Stream.map] or [Stream.forEach], the callback used to process each +/// item returns a [Future], and it will not advance to the next item until the +/// current item is finished processing. +/// +/// Items can be added at any point in time and processing will be started as +/// needed. When all items are processed, it stops processing until more items +/// are added. +class AsyncQueue { + final _items = new Queue(); + + /// Whether or not the queue is currently waiting on a processing future to + /// complete. + bool _isProcessing = false; + + /// The callback to invoke on each queued item. + /// + /// The next item in the queue will not be processed until the [Future] + /// returned by this completes. + final ItemProcessor _processor; + + /// The handler for errors thrown during processing. + /// + /// Used to avoid top-leveling asynchronous errors. + final ErrorHandler _errorHandler; + + AsyncQueue(this._processor, {ErrorHandler onError}) + : _errorHandler = onError; + + /// Enqueues [item] to be processed and starts asynchronously processing it + /// if a process isn't already running. + void add(T item) { + _items.add(item); + + // Start up the asynchronous processing if not already running. + if (_isProcessing) return; + _isProcessing = true; + + _processNextItem().catchError(_errorHandler); + } + + /// Removes all remaining items to be processed. + void clear() { + _items.clear(); + } + + /// Pulls the next item off [_items] and processes it. + /// + /// When complete, recursively calls itself to continue processing unless + /// the process was cancelled. + Future _processNextItem() { + var item = _items.removeFirst(); + return _processor(item).then((_) { + if (_items.isNotEmpty) return _processNextItem(); + + // We have drained the queue, stop processing and wait until something + // has been enqueued. + _isProcessing = false; + }); + } +} \ No newline at end of file diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 61bf6c52a..913101b60 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -5,10 +5,12 @@ library watcher.directory_watcher; import 'dart:async'; +import 'dart:collection'; import 'dart:io'; import 'package:crypto/crypto.dart'; +import 'async_queue.dart'; import 'stat.dart'; import 'watch_event.dart'; @@ -27,7 +29,7 @@ class DirectoryWatcher { Stream get events => _events.stream; StreamController _events; - _WatchState _state = _WatchState.notWatching; + _WatchState _state = _WatchState.UNSUBSCRIBED; /// A [Future] that completes when the watcher is initialized and watching /// for file changes. @@ -51,6 +53,26 @@ class DirectoryWatcher { /// Used to tell which files have been modified. final _statuses = new Map(); + /// The subscription used while [directory] is being listed. + /// + /// Will be `null` if a list is not currently happening. + StreamSubscription _listSubscription; + + /// The queue of files waiting to be processed to see if they have been + /// modified. + /// + /// Processing a file is asynchronous, as is listing the directory, so the + /// queue exists to let each of those proceed at their own rate. The lister + /// will enqueue files as quickly as it can. Meanwhile, files are dequeued + /// and processed sequentially. + AsyncQueue _filesToProcess; + + /// The set of files that have been seen in the current directory listing. + /// + /// Used to tell which files have been removed: files that are in [_statuses] + /// but not in here when a poll completes have been removed. + final _polledFiles = new Set(); + /// Creates a new [DirectoryWatcher] monitoring [directory]. /// /// If [pollingDelay] is passed, it specifies the amount of time the watcher @@ -60,82 +82,133 @@ class DirectoryWatcher { DirectoryWatcher(this.directory, {Duration pollingDelay}) : pollingDelay = pollingDelay != null ? pollingDelay : new Duration(seconds: 1) { - _events = new StreamController.broadcast(onListen: () { - _state = _state.listen(this); - }, onCancel: () { - _state = _state.cancel(this); - }); + _events = new StreamController.broadcast( + onListen: _watch, onCancel: _cancel); + + _filesToProcess = new AsyncQueue(_processFile, + onError: _events.addError); } - /// Starts the asynchronous polling process. - /// - /// Scans the contents of the directory and compares the results to the - /// previous scan. Loops to continue monitoring as long as there are - /// subscribers to the [events] stream. - Future _watch() { - var files = new Set(); + /// Scans to see which files were already present before the watcher was + /// subscribed to, and then starts watching the directory for changes. + void _watch() { + assert(_state == _WatchState.UNSUBSCRIBED); + _state = _WatchState.SCANNING; + _poll(); + } - var stream = new Directory(directory).list(recursive: true); + /// Stops watching the directory when there are no more subscribers. + void _cancel() { + assert(_state != _WatchState.UNSUBSCRIBED); + _state = _WatchState.UNSUBSCRIBED; - return stream.map((entity) { - if (entity is! File) return new Future.value(); - files.add(entity.path); - // TODO(rnystrom): These all run as fast as possible and read the - // contents of the files. That means there's a pretty big IO hit all at - // once. Maybe these should be queued up and rate limited? - return _refreshFile(entity.path); - }).toList().then((futures) { - // Once the listing is done, make sure to wait until each file is also - // done. - return Future.wait(futures); - }).then((_) { - var removedFiles = _statuses.keys.toSet().difference(files); - for (var removed in removedFiles) { - if (_state.shouldNotify) { - _events.add(new WatchEvent(ChangeType.REMOVE, removed)); - } - _statuses.remove(removed); - } + // If we're in the middle of listing the directory, stop. + if (_listSubscription != null) _listSubscription.cancel(); - var previousState = _state; - _state = _state.finish(this); + // Don't process any remaining files. + _filesToProcess.clear(); + _polledFiles.clear(); + _statuses.clear(); - // If we were already sending notifications, add a bit of delay before - // restarting just so that we don't whale on the file system. - // TODO(rnystrom): Tune this and/or make it tunable? - if (_state.shouldNotify) { - return new Future.delayed(pollingDelay); - } - }).then((_) { - // Make sure we haven't transitioned to a non-watching state during the - // delay. - if (_state.shouldWatch) _watch(); + _ready = new Completer(); + } + + /// Scans the contents of the directory once to see which files have been + /// added, removed, and modified. + void _poll() { + _filesToProcess.clear(); + _polledFiles.clear(); + + 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; + + // Null tells the queue consumer that we're done listing. + _filesToProcess.add(null); }); } - /// Compares the current state of the file at [path] to the state it was in - /// the last time it was scanned. - Future _refreshFile(String path) { - return getModificationTime(path).then((modified) { - var lastStatus = _statuses[path]; + /// Processes [file] to determine if it has been modified since the last + /// time it was scanned. + Future _processFile(String file) { + assert(_state != _WatchState.UNSUBSCRIBED); + + // `null` is the sentinel which means the directory listing is complete. + if (file == null) return _completePoll(); + + return getModificationTime(file).then((modified) { + if (_checkForCancel()) return; + + var lastStatus = _statuses[file]; - // If it's modification time hasn't changed, assume the file is unchanged. - if (lastStatus != null && lastStatus.modified == modified) return; + // If its modification time hasn't changed, assume the file is unchanged. + if (lastStatus != null && lastStatus.modified == modified) { + // The file is still here. + _polledFiles.add(file); + return; + } + + return _hashFile(file).then((hash) { + if (_checkForCancel()) return; - return _hashFile(path).then((hash) { var status = new _FileStatus(modified, hash); - _statuses[path] = status; - - // Only notify if the file contents changed. - if (_state.shouldNotify && - (lastStatus == null || !_sameHash(lastStatus.hash, hash))) { - var change = lastStatus == null ? ChangeType.ADD : ChangeType.MODIFY; - _events.add(new WatchEvent(change, path)); - } + _statuses[file] = status; + _polledFiles.add(file); + + // Only notify while in the watching state. + if (_state != _WatchState.WATCHING) return; + + // And the file is different. + var changed = lastStatus == null || !_sameHash(lastStatus.hash, hash); + if (!changed) return; + + var type = lastStatus == null ? ChangeType.ADD : ChangeType.MODIFY; + _events.add(new WatchEvent(type, file)); }); }); } + /// After the directory listing is complete, this determines which files were + /// removed and then restarts the next poll. + Future _completePoll() { + // Any files that were not seen in the last poll but that we have a + // status for must have been removed. + var removedFiles = _statuses.keys.toSet().difference(_polledFiles); + for (var removed in removedFiles) { + if (_state == _WatchState.WATCHING) { + _events.add(new WatchEvent(ChangeType.REMOVE, removed)); + } + _statuses.remove(removed); + } + + if (_state == _WatchState.SCANNING) { + _state = _WatchState.WATCHING; + _ready.complete(); + } + + // Wait and then poll again. + return new Future.delayed(pollingDelay).then((_) { + if (_checkForCancel()) return; + _poll(); + }); + } + + /// Returns `true` and clears the processing queue if the watcher has been + /// unsubscribed. + bool _checkForCancel() { + if (_state != _WatchState.UNSUBSCRIBED) return false; + + // Don't process any more files. + _filesToProcess.clear(); + return true; + } + /// Calculates the SHA-1 hash of the file at [path]. Future> _hashFile(String path) { return new File(path).readAsBytes().then((bytes) { @@ -159,71 +232,29 @@ class DirectoryWatcher { } } -/// An "event" that is sent to the [_WatchState] FSM to trigger state -/// transitions. -typedef _WatchState _WatchStateEvent(DirectoryWatcher watcher); - -/// The different states that the watcher can be in and the transitions between -/// them. -/// -/// This class defines a finite state machine for keeping track of what the -/// asynchronous file polling is doing. Each instance of this is a state in the -/// machine and its [listen], [cancel], and [finish] fields define the state -/// transitions when those events occur. +/// Enum class for the states that the [DirectoryWatcher] can be in. class _WatchState { - /// The watcher has no subscribers. - static final notWatching = new _WatchState( - listen: (watcher) { - watcher._watch(); - return _WatchState.scanning; - }); - - /// The watcher has subscribers and is scanning for pre-existing files. - static final scanning = new _WatchState( - cancel: (watcher) { - // No longer watching, so create a new incomplete ready future. - watcher._ready = new Completer(); - return _WatchState.cancelling; - }, finish: (watcher) { - watcher._ready.complete(); - return _WatchState.watching; - }, shouldWatch: true); - - /// The watcher was unsubscribed while polling and we're waiting for the poll - /// to finish. - static final cancelling = new _WatchState( - listen: (_) => _WatchState.scanning, - finish: (_) => _WatchState.notWatching); - - /// The watcher has subscribers, we have scanned for pre-existing files and - /// now we're polling for changes. - static final watching = new _WatchState( - cancel: (watcher) { - // No longer watching, so create a new incomplete ready future. - watcher._ready = new Completer(); - return _WatchState.cancelling; - }, finish: (_) => _WatchState.watching, - shouldWatch: true, shouldNotify: true); - - /// Called when the first subscriber to the watcher has been added. - final _WatchStateEvent listen; - - /// Called when all subscriptions on the watcher have been cancelled. - final _WatchStateEvent cancel; - - /// Called when a poll loop has finished. - final _WatchStateEvent finish; - - /// If the directory watcher should be watching the file system while in - /// this state. - final bool shouldWatch; - - /// If a change event should be sent for a file modification while in this - /// state. - final bool shouldNotify; - - _WatchState({this.listen, this.cancel, this.finish, - this.shouldWatch: false, this.shouldNotify: false}); + /// There are no subscribers to the watcher's event stream and no watching + /// is going on. + static const UNSUBSCRIBED = const _WatchState("unsubscribed"); + + /// There are subscribers and the watcher is doing an initial scan of the + /// directory to see which files were already present before watching started. + /// + /// The watcher does not send notifications for changes that occurred while + /// there were no subscribers, or for files already present before watching. + /// The initial scan is used to determine what "before watching" state of + /// the file system was. + static const SCANNING = const _WatchState("scanning"); + + /// There are subscribers and the watcher is polling the directory to look + /// for changes. + static const WATCHING = const _WatchState("watching"); + + /// The name of the state. + final String name; + + const _WatchState(this.name); } class _FileStatus { diff --git a/pkgs/watcher/test/no_subscription_test.dart b/pkgs/watcher/test/no_subscription_test.dart index e9cb4e363..8a3b03159 100644 --- a/pkgs/watcher/test/no_subscription_test.dart +++ b/pkgs/watcher/test/no_subscription_test.dart @@ -23,11 +23,11 @@ main() { // Subscribe to the events. var completer = new Completer(); - var subscription = watcher.events.listen((event) { + var subscription = watcher.events.listen(wrapAsync((event) { expect(event.type, equals(ChangeType.ADD)); expect(event.path, endsWith("file.txt")); completer.complete(); - }); + })); writeFile("file.txt"); @@ -45,20 +45,20 @@ main() { // Then start listening again. schedule(() { completer = new Completer(); - subscription = watcher.events.listen((event) { + subscription = watcher.events.listen(wrapAsync((event) { // We should get an event for the third file, not the one added while // we weren't subscribed. expect(event.type, equals(ChangeType.ADD)); expect(event.path, endsWith("added.txt")); completer.complete(); - }); + })); }); // The watcher will have been cancelled and then resumed in the middle of // its pause between polling loops. That means the second scan to skip // what changed while we were unsubscribed won't happen until after that // delay is done. Wait long enough for that to happen. - schedule(() => new Future.delayed(new Duration(seconds: 1))); + schedule(() => new Future.delayed(watcher.pollingDelay * 2)); // And add a third file. writeFile("added.txt"); diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 5053b76ad..65d47197d 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -84,7 +84,7 @@ DirectoryWatcher createWatcher({bool waitForReady}) { // Wait until the scan is finished so that we don't miss changes to files // that could occur before the scan completes. if (waitForReady != false) { - schedule(() => _watcher.ready); + schedule(() => _watcher.ready, "wait for watcher to be ready"); } currentSchedule.onComplete.schedule(() { @@ -107,7 +107,7 @@ void expectEvent(ChangeType type, String path) { // Schedule it so that later file modifications don't occur until after this // event is received. - schedule(() => future); + schedule(() => future, "wait for $type event"); } void expectAddEvent(String path) { @@ -149,14 +149,14 @@ void writeFile(String path, {String contents, bool updateModified}) { var milliseconds = _mockFileModificationTimes.putIfAbsent(path, () => 0); _mockFileModificationTimes[path]++; } - }); + }, "write file $path"); } /// Schedules deleting a file in the sandbox at [path]. void deleteFile(String path) { schedule(() { new File(p.join(_sandboxDir, path)).deleteSync(); - }); + }, "delete file $path"); } /// Schedules renaming a file in the sandbox from [from] to [to]. @@ -172,7 +172,7 @@ void renameFile(String from, String to) { // Manually update the mock modification time for the file. var milliseconds = _mockFileModificationTimes.putIfAbsent(to, () => 0); _mockFileModificationTimes[to]++; - }); + }, "rename file $from to $to"); } /// A [Matcher] for [WatchEvent]s. From 954d17fa3bf65e0f57a476663c84f634fc82713c Mon Sep 17 00:00:00 2001 From: "kevmoo@j832.com" Date: Tue, 6 Aug 2013 20:42:04 +0000 Subject: [PATCH 009/201] pkg: analysis aided cleanup Removed a lot of warnings and hints when opening many pkg projects in the editor R=gram@google.com Review URL: https://codereview.chromium.org//22284003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@25831 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/directory_watcher.dart | 1 - pkgs/watcher/test/directory_watcher_test.dart | 4 ---- pkgs/watcher/test/no_subscription_test.dart | 1 - pkgs/watcher/test/ready_test.dart | 2 -- 4 files changed, 8 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 913101b60..c5dd9db60 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -5,7 +5,6 @@ library watcher.directory_watcher; import 'dart:async'; -import 'dart:collection'; import 'dart:io'; import 'package:crypto/crypto.dart'; diff --git a/pkgs/watcher/test/directory_watcher_test.dart b/pkgs/watcher/test/directory_watcher_test.dart index 800d54d63..9070179a9 100644 --- a/pkgs/watcher/test/directory_watcher_test.dart +++ b/pkgs/watcher/test/directory_watcher_test.dart @@ -2,11 +2,7 @@ // 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. -import 'dart:async'; -import 'dart:io'; - import 'package:scheduled_test/scheduled_test.dart'; -import 'package:watcher/watcher.dart'; import 'utils.dart'; diff --git a/pkgs/watcher/test/no_subscription_test.dart b/pkgs/watcher/test/no_subscription_test.dart index 8a3b03159..d0a396ac4 100644 --- a/pkgs/watcher/test/no_subscription_test.dart +++ b/pkgs/watcher/test/no_subscription_test.dart @@ -3,7 +3,6 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; -import 'dart:io'; import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/watcher.dart'; diff --git a/pkgs/watcher/test/ready_test.dart b/pkgs/watcher/test/ready_test.dart index dd799ce5a..11b77e02c 100644 --- a/pkgs/watcher/test/ready_test.dart +++ b/pkgs/watcher/test/ready_test.dart @@ -3,10 +3,8 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; -import 'dart:io'; import 'package:scheduled_test/scheduled_test.dart'; -import 'package:watcher/watcher.dart'; import 'utils.dart'; From 35f471795db05676d02ef6c0cde10a1740d05524 Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" Date: Wed, 14 Aug 2013 21:02:02 +0000 Subject: [PATCH 010/201] 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 --- pkgs/watcher/lib/src/directory_watcher.dart | 27 ++++++-- pkgs/watcher/lib/src/utils.dart | 16 +++++ pkgs/watcher/test/directory_watcher_test.dart | 19 +++++ pkgs/watcher/test/utils.dart | 69 +++++++++++++------ 4 files changed, 105 insertions(+), 26 deletions(-) create mode 100644 pkgs/watcher/lib/src/utils.dart diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index c5dd9db60..eb948f57c 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -11,6 +11,7 @@ import 'package:crypto/crypto.dart'; 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 @@ class DirectoryWatcher { _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 000000000..319835ec9 --- /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 9070179a9..841dd0808 100644 --- a/pkgs/watcher/test/directory_watcher_test.dart +++ b/pkgs/watcher/test/directory_watcher_test.dart @@ -86,4 +86,23 @@ main() { 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 65d47197d..18bec9728 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -76,9 +76,17 @@ void createSandbox() { /// 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 @@ DirectoryWatcher createWatcher({bool waitForReady}) { 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 paths) { + var pathSet = paths.map((path) => p.join(_sandboxDir, path)).toSet(); + + // 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); + // 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"); -} - -void expectAddEvent(String path) { - expectEvent(ChangeType.ADD, p.join(_sandboxDir, path)); + schedule(() => Future.wait(futures), + "wait for $type events on ${paths.join(', ')}"); } -void expectModifyEvent(String path) { - expectEvent(ChangeType.MODIFY, 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 expectRemoveEvent(String path) { - expectEvent(ChangeType.REMOVE, p.join(_sandboxDir, path)); +void expectRemoveEvents(Iterable paths) { + expectEvents(ChangeType.REMOVE, paths); } /// Schedules writing a file in the sandbox at [path] with [contents]. @@ -175,6 +197,13 @@ void renameFile(String from, String to) { }, "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. From 897f32c771badac59bb350566e62338f4fdca53c Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" Date: Wed, 14 Aug 2013 21:28:31 +0000 Subject: [PATCH 011/201] Fix error handling in watcher. BUG= R=nweiz@google.com Review URL: https://codereview.chromium.org//23085006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@26156 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/directory_watcher.dart | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index eb948f57c..16a5cf1c5 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -134,18 +134,17 @@ class DirectoryWatcher { if (entity is! File) return; _filesToProcess.add(entity.path); }, 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; + if (!isDirectoryNotFoundException(error)) { + // It's some unknown error. Pipe it over to the event stream so the + // user can see it. + _events.addError(error); } - // 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); + // When an error occurs, we end the listing normally, which has the + // desired effect of marking all files that were in the directory as + // being removed. + endListing(); + }, onDone: endListing, cancelOnError: true); } /// Processes [file] to determine if it has been modified since the last From 1da9e39ae97edcd8cf210668dc26569aca31c9f5 Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" Date: Wed, 14 Aug 2013 21:46:41 +0000 Subject: [PATCH 012/201] Normalize test paths. BUG= R=nweiz@google.com Review URL: https://codereview.chromium.org//23183006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@26160 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/test/utils.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 18bec9728..3bbe0e1a6 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -109,7 +109,10 @@ DirectoryWatcher createWatcher({String dir, bool waitForReady}) { /// Validates that events are delivered for all paths in [paths], but allows /// them in any order. void expectEvents(ChangeType type, Iterable paths) { - var pathSet = paths.map((path) => p.join(_sandboxDir, path)).toSet(); + var pathSet = paths + .map((path) => p.join(_sandboxDir, path)) + .map(p.normalize) + .toSet(); // Create an expectation for as many paths as we have. var futures = []; From b027f42cf926c97aac7b49a49c586f6af5953c72 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 27 Aug 2013 23:13:30 +0000 Subject: [PATCH 013/201] Use filtered stacks in pub, barback, and watcher tests. This also exposes more unittest stuff to users of scheduled_test. R=rnystrom@google.com BUG= Review URL: https://codereview.chromium.org//23582003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@26751 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/test/utils.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 3bbe0e1a6..656ddf8ed 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -38,6 +38,7 @@ Map _mockFileModificationTimes; void initConfig() { useCompactVMConfiguration(); + filterStacks = true; } /// Creates the sandbox directory the other functions in this library use and From 18200151eab235cc5b4a27e672164f09ca2b4981 Mon Sep 17 00:00:00 2001 From: "whesse@google.com" Date: Thu, 3 Oct 2013 13:12:10 +0000 Subject: [PATCH 014/201] Use Directory.systemTemp getter in pkg subdirectory of repository. BUG= R=sgjesse@google.com Review URL: https://codereview.chromium.org//25731003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@28218 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/test/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 656ddf8ed..28d57b05c 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -46,7 +46,7 @@ void initConfig() { /// /// This should usually be called by [setUp]. void createSandbox() { - var dir = new Directory("").createTempSync(); + var dir = Directory.systemTemp.createTempSync('watcher_test_'); _sandboxDir = dir.path; _mockFileModificationTimes = new Map(); From 41732d43baefcd4509306770375540316c53b74d Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" Date: Tue, 8 Oct 2013 00:14:44 +0000 Subject: [PATCH 015/201] Bump wait time for watcher test. This is to see if it makes the test less flaky on the bots, in particular the ARM ones. BUG=https://code.google.com/p/dart/issues/detail?id=13705 R=nweiz@google.com Review URL: https://codereview.chromium.org//26398002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@28341 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/test/no_subscription_test.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkgs/watcher/test/no_subscription_test.dart b/pkgs/watcher/test/no_subscription_test.dart index d0a396ac4..2e7b6d3ff 100644 --- a/pkgs/watcher/test/no_subscription_test.dart +++ b/pkgs/watcher/test/no_subscription_test.dart @@ -57,7 +57,10 @@ main() { // its pause between polling loops. That means the second scan to skip // what changed while we were unsubscribed won't happen until after that // delay is done. Wait long enough for that to happen. - schedule(() => new Future.delayed(watcher.pollingDelay * 2)); + // + // We're doing * 4 here because that seems to give the slower bots enough + // time for this to complete. + schedule(() => new Future.delayed(watcher.pollingDelay * 4)); // And add a third file. writeFile("added.txt"); From 7fd14c90f3b58462612b60b2ebed3c39b5fcf2b1 Mon Sep 17 00:00:00 2001 From: "floitsch@google.com" Date: Fri, 11 Oct 2013 12:02:20 +0000 Subject: [PATCH 016/201] Adapt streams for additional stackTrace argument. R=lrn@google.com Review URL: https://codereview.chromium.org//25094002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@28511 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/directory_watcher.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 16a5cf1c5..3b39f5a3e 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -133,11 +133,11 @@ class DirectoryWatcher { if (entity is! File) return; _filesToProcess.add(entity.path); - }, onError: (error) { + }, onError: (error, StackTrace stackTrace) { if (!isDirectoryNotFoundException(error)) { // It's some unknown error. Pipe it over to the event stream so the // user can see it. - _events.addError(error); + _events.addError(error, stackTrace); } // When an error occurs, we end the listing normally, which has the From f2807baf801592d5a8baec9e5e653d117ed987c2 Mon Sep 17 00:00:00 2001 From: "ajohnsen@google.com" Date: Thu, 24 Oct 2013 12:17:20 +0000 Subject: [PATCH 017/201] Remove FileException, DirectoryException and LinkException from dart:io and use FileSystemException instaed. BUG=https://code.google.com/p/dart/issues/detail?id=12461 R=nweiz@google.com, sgjesse@google.com Review URL: https://codereview.chromium.org//26968003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@29168 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/utils.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index 319835ec9..3d00c0843 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -6,9 +6,10 @@ library watcher.utils; import 'dart:io'; -/// Returns `true` if [error] is a [DirectoryException] for a missing directory. +/// Returns `true` if [error] is a [FileSystemException] for a missing +/// directory. bool isDirectoryNotFoundException(error) { - if (error is! DirectoryException) return false; + if (error is! FileSystemException) return false; // See dartbug.com/12461 and tests/standalone/io/directory_error_test.dart. var notFoundCode = Platform.operatingSystem == "windows" ? 3 : 2; From 5bd235d02ede233fdb74d488529a5dd825fd3a51 Mon Sep 17 00:00:00 2001 From: "whesse@google.com" Date: Wed, 30 Oct 2013 15:17:01 +0000 Subject: [PATCH 018/201] Remove uses of Options from pkg, samples, tests, and third_party directories. BUG= R=sgjesse@google.com Review URL: https://codereview.chromium.org//52573002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@29552 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/example/watch.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pkgs/watcher/example/watch.dart b/pkgs/watcher/example/watch.dart index 235578dd6..aba127d6d 100644 --- a/pkgs/watcher/example/watch.dart +++ b/pkgs/watcher/example/watch.dart @@ -10,15 +10,14 @@ import 'dart:io'; import 'package:path/path.dart' as p; import 'package:watcher/watcher.dart'; -main() { - var args = new Options().arguments; - if (args.length != 1) { +main(List arguments) { + if (arguments.length != 1) { print("Usage: watch "); return; } - var watcher = new DirectoryWatcher(p.absolute(args[0])); + var watcher = new DirectoryWatcher(p.absolute(arguments[0])); watcher.events.listen((event) { print(event); }); -} \ No newline at end of file +} From 1efdfcbf104c87a3f92ef4e8c8bdd5c8a9b56afa Mon Sep 17 00:00:00 2001 From: "jmesserly@google.com" Date: Thu, 31 Oct 2013 02:43:00 +0000 Subject: [PATCH 019/201] fix csslib and watcher warnings R=rnystrom@google.com, terry@google.com Review URL: https://codereview.chromium.org//50703011 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@29632 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/directory_watcher.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 3b39f5a3e..679e227e8 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -156,7 +156,7 @@ class DirectoryWatcher { if (file == null) return _completePoll(); return getModificationTime(file).then((modified) { - if (_checkForCancel()) return; + if (_checkForCancel()) return null; var lastStatus = _statuses[file]; @@ -164,7 +164,7 @@ class DirectoryWatcher { if (lastStatus != null && lastStatus.modified == modified) { // The file is still here. _polledFiles.add(file); - return; + return null; } return _hashFile(file).then((hash) { From 0abea1bfb5a8b5ce0f4877879b7c4633ddc68431 Mon Sep 17 00:00:00 2001 From: "jmesserly@google.com" Date: Wed, 6 Nov 2013 03:27:58 +0000 Subject: [PATCH 020/201] add versions and constraints for packages and samples - all packages at 0.9.0, except "analyzer" which had a version already - dependencies at ">=0.9.0 <0.10.0" except analyzer is ">=0.10.0 <0.11.0" - sdk constraint ">=1.0.0 <2.0.0" R=sigmund@google.com Review URL: https://codereview.chromium.org//59763006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@29957 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/pubspec.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 263832efb..6b8faca8e 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,4 +1,5 @@ name: watcher +version: 0.9.0 author: "Dart Team " homepage: http://www.dartlang.org description: > @@ -6,8 +7,10 @@ description: > of directories and notifies you when files have been added, removed, or modified. dependencies: - crypto: any - path: any + crypto: ">=0.9.0 <0.10.0" + path: ">=0.9.0 <0.10.0" dev_dependencies: - scheduled_test: any - unittest: any + scheduled_test: ">=0.9.0 <0.10.0" + unittest: ">=0.9.0 <0.10.0" +environment: + sdk: ">=1.0.0 <2.0.0" From db84ec8e7b68f8e1311deb7e4b81b1c8c0b5bf57 Mon Sep 17 00:00:00 2001 From: "ajohnsen@google.com" Date: Wed, 6 Nov 2013 09:09:18 +0000 Subject: [PATCH 021/201] Revert "add versions and constraints for packages and samples" This is currently blocking us from testing samples. BUG= R=kasperl@google.com Review URL: https://codereview.chromium.org//59513007 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@29960 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/pubspec.yaml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 6b8faca8e..263832efb 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,4 @@ name: watcher -version: 0.9.0 author: "Dart Team " homepage: http://www.dartlang.org description: > @@ -7,10 +6,8 @@ description: > of directories and notifies you when files have been added, removed, or modified. dependencies: - crypto: ">=0.9.0 <0.10.0" - path: ">=0.9.0 <0.10.0" + crypto: any + path: any dev_dependencies: - scheduled_test: ">=0.9.0 <0.10.0" - unittest: ">=0.9.0 <0.10.0" -environment: - sdk: ">=1.0.0 <2.0.0" + scheduled_test: any + unittest: any From c8c3d49c28f603bae92edcc63307d43184645003 Mon Sep 17 00:00:00 2001 From: "dgrove@google.com" Date: Wed, 6 Nov 2013 18:28:22 +0000 Subject: [PATCH 022/201] Re-land r29957 (add versions and constraints for packages and samples), with SDK constraints bumped from 1.0.0 to 0.8.10+6 . R=ricow@google.com, sigmund@google.com Review URL: https://codereview.chromium.org//62473002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@29986 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/pubspec.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 263832efb..b95c7efdf 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,4 +1,5 @@ name: watcher +version: 0.9.0 author: "Dart Team " homepage: http://www.dartlang.org description: > @@ -6,8 +7,10 @@ description: > of directories and notifies you when files have been added, removed, or modified. dependencies: - crypto: any - path: any + crypto: ">=0.9.0 <0.10.0" + path: ">=0.9.0 <0.10.0" dev_dependencies: - scheduled_test: any - unittest: any + scheduled_test: ">=0.9.0 <0.10.0" + unittest: ">=0.9.0 <0.10.0" +environment: + sdk: ">=0.8.10+6 <2.0.0" From 90e0361e775586d496ea5bfc83f9a25c09766820 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Thu, 7 Nov 2013 22:49:18 +0000 Subject: [PATCH 023/201] Wrap Directory.watch on linux for the watcher package. R=rnystrom@google.com BUG=14428 Review URL: https://codereview.chromium.org//46843003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30081 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/directory_watcher.dart | 264 ++-------------- .../lib/src/directory_watcher/linux.dart | 298 ++++++++++++++++++ .../lib/src/directory_watcher/polling.dart | 217 +++++++++++++ .../src/directory_watcher/resubscribable.dart | 79 +++++ pkgs/watcher/lib/src/utils.dart | 56 ++++ pkgs/watcher/lib/watcher.dart | 1 + .../test/directory_watcher/linux_test.dart | 62 ++++ .../test/directory_watcher/polling_test.dart | 39 +++ .../test/directory_watcher/shared.dart | 222 +++++++++++++ pkgs/watcher/test/directory_watcher_test.dart | 108 ------- .../test/no_subscription/linux_test.dart | 20 ++ .../test/no_subscription/polling_test.dart | 19 ++ .../shared.dart} | 22 +- pkgs/watcher/test/ready/linux_test.dart | 20 ++ pkgs/watcher/test/ready/polling_test.dart | 19 ++ .../{ready_test.dart => ready/shared.dart} | 10 +- pkgs/watcher/test/utils.dart | 199 ++++++++---- 17 files changed, 1213 insertions(+), 442 deletions(-) create mode 100644 pkgs/watcher/lib/src/directory_watcher/linux.dart create mode 100644 pkgs/watcher/lib/src/directory_watcher/polling.dart create mode 100644 pkgs/watcher/lib/src/directory_watcher/resubscribable.dart create mode 100644 pkgs/watcher/test/directory_watcher/linux_test.dart create mode 100644 pkgs/watcher/test/directory_watcher/polling_test.dart create mode 100644 pkgs/watcher/test/directory_watcher/shared.dart delete mode 100644 pkgs/watcher/test/directory_watcher_test.dart create mode 100644 pkgs/watcher/test/no_subscription/linux_test.dart create mode 100644 pkgs/watcher/test/no_subscription/polling_test.dart rename pkgs/watcher/test/{no_subscription_test.dart => no_subscription/shared.dart} (73%) create mode 100644 pkgs/watcher/test/ready/linux_test.dart create mode 100644 pkgs/watcher/test/ready/polling_test.dart rename pkgs/watcher/test/{ready_test.dart => ready/shared.dart} (95%) diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 679e227e8..4484c2b77 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -7,18 +7,15 @@ library watcher.directory_watcher; import 'dart:async'; import 'dart:io'; -import 'package:crypto/crypto.dart'; - -import 'async_queue.dart'; -import 'stat.dart'; -import 'utils.dart'; import 'watch_event.dart'; +import 'directory_watcher/linux.dart'; +import 'directory_watcher/polling.dart'; /// Watches the contents of a directory and emits [WatchEvent]s when something /// in the directory has changed. -class DirectoryWatcher { +abstract class DirectoryWatcher { /// The directory whose contents are being monitored. - final String directory; + String get directory; /// The broadcast [Stream] of events that have occurred to files in /// [directory]. @@ -26,10 +23,12 @@ class DirectoryWatcher { /// Changes will only be monitored while this stream has subscribers. Any /// file changes that occur during periods when there are no subscribers /// will not be reported the next time a subscriber is added. - Stream get events => _events.stream; - StreamController _events; + Stream get events; - _WatchState _state = _WatchState.UNSUBSCRIBED; + /// Whether the watcher is initialized and watching for file changes. + /// + /// This is true if and only if [ready] is complete. + bool get isReady; /// A [Future] that completes when the watcher is initialized and watching /// for file changes. @@ -41,241 +40,20 @@ class DirectoryWatcher { /// /// If the watcher is already monitoring, this returns an already complete /// future. - Future get ready => _ready.future; - Completer _ready = new Completer(); - - /// The amount of time the watcher pauses between successive polls of the - /// directory contents. - final Duration pollingDelay; - - /// The previous status of the files in the directory. - /// - /// Used to tell which files have been modified. - final _statuses = new Map(); - - /// The subscription used while [directory] is being listed. - /// - /// Will be `null` if a list is not currently happening. - StreamSubscription _listSubscription; - - /// The queue of files waiting to be processed to see if they have been - /// modified. - /// - /// Processing a file is asynchronous, as is listing the directory, so the - /// queue exists to let each of those proceed at their own rate. The lister - /// will enqueue files as quickly as it can. Meanwhile, files are dequeued - /// and processed sequentially. - AsyncQueue _filesToProcess; - - /// The set of files that have been seen in the current directory listing. - /// - /// Used to tell which files have been removed: files that are in [_statuses] - /// but not in here when a poll completes have been removed. - final _polledFiles = new Set(); + Future get ready; /// Creates a new [DirectoryWatcher] monitoring [directory]. /// - /// If [pollingDelay] is passed, it specifies the amount of time the watcher - /// will pause between successive polls of the directory contents. Making - /// this shorter will give more immediate feedback at the expense of doing - /// more IO and higher CPU usage. Defaults to one second. - DirectoryWatcher(this.directory, {Duration pollingDelay}) - : pollingDelay = pollingDelay != null ? pollingDelay : - new Duration(seconds: 1) { - _events = new StreamController.broadcast( - onListen: _watch, onCancel: _cancel); - - _filesToProcess = new AsyncQueue(_processFile, - onError: _events.addError); - } - - /// Scans to see which files were already present before the watcher was - /// subscribed to, and then starts watching the directory for changes. - void _watch() { - assert(_state == _WatchState.UNSUBSCRIBED); - _state = _WatchState.SCANNING; - _poll(); - } - - /// Stops watching the directory when there are no more subscribers. - void _cancel() { - assert(_state != _WatchState.UNSUBSCRIBED); - _state = _WatchState.UNSUBSCRIBED; - - // If we're in the middle of listing the directory, stop. - if (_listSubscription != null) _listSubscription.cancel(); - - // Don't process any remaining files. - _filesToProcess.clear(); - _polledFiles.clear(); - _statuses.clear(); - - _ready = new Completer(); - } - - /// Scans the contents of the directory once to see which files have been - /// added, removed, and modified. - void _poll() { - _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); - }, onError: (error, StackTrace stackTrace) { - if (!isDirectoryNotFoundException(error)) { - // It's some unknown error. Pipe it over to the event stream so the - // user can see it. - _events.addError(error, stackTrace); - } - - // When an error occurs, we end the listing normally, which has the - // desired effect of marking all files that were in the directory as - // being removed. - endListing(); - }, onDone: endListing, cancelOnError: true); - } - - /// Processes [file] to determine if it has been modified since the last - /// time it was scanned. - Future _processFile(String file) { - assert(_state != _WatchState.UNSUBSCRIBED); - - // `null` is the sentinel which means the directory listing is complete. - if (file == null) return _completePoll(); - - return getModificationTime(file).then((modified) { - if (_checkForCancel()) return null; - - var lastStatus = _statuses[file]; - - // If its modification time hasn't changed, assume the file is unchanged. - if (lastStatus != null && lastStatus.modified == modified) { - // The file is still here. - _polledFiles.add(file); - return null; - } - - return _hashFile(file).then((hash) { - if (_checkForCancel()) return; - - var status = new _FileStatus(modified, hash); - _statuses[file] = status; - _polledFiles.add(file); - - // Only notify while in the watching state. - if (_state != _WatchState.WATCHING) return; - - // And the file is different. - var changed = lastStatus == null || !_sameHash(lastStatus.hash, hash); - if (!changed) return; - - var type = lastStatus == null ? ChangeType.ADD : ChangeType.MODIFY; - _events.add(new WatchEvent(type, file)); - }); - }); - } - - /// After the directory listing is complete, this determines which files were - /// removed and then restarts the next poll. - Future _completePoll() { - // Any files that were not seen in the last poll but that we have a - // status for must have been removed. - var removedFiles = _statuses.keys.toSet().difference(_polledFiles); - for (var removed in removedFiles) { - if (_state == _WatchState.WATCHING) { - _events.add(new WatchEvent(ChangeType.REMOVE, removed)); - } - _statuses.remove(removed); - } - - if (_state == _WatchState.SCANNING) { - _state = _WatchState.WATCHING; - _ready.complete(); - } - - // Wait and then poll again. - return new Future.delayed(pollingDelay).then((_) { - if (_checkForCancel()) return; - _poll(); - }); - } - - /// Returns `true` and clears the processing queue if the watcher has been - /// unsubscribed. - bool _checkForCancel() { - if (_state != _WatchState.UNSUBSCRIBED) return false; - - // Don't process any more files. - _filesToProcess.clear(); - return true; - } - - /// Calculates the SHA-1 hash of the file at [path]. - Future> _hashFile(String path) { - return new File(path).readAsBytes().then((bytes) { - var sha1 = new SHA1(); - sha1.add(bytes); - return sha1.close(); - }); - } - - /// Returns `true` if [a] and [b] are the same hash value, i.e. the same - /// series of byte values. - bool _sameHash(List a, List b) { - // Hashes should always be the same size. - assert(a.length == b.length); - - for (var i = 0; i < a.length; i++) { - if (a[i] != b[i]) return false; - } - - return true; - } -} - -/// Enum class for the states that the [DirectoryWatcher] can be in. -class _WatchState { - /// There are no subscribers to the watcher's event stream and no watching - /// is going on. - static const UNSUBSCRIBED = const _WatchState("unsubscribed"); - - /// There are subscribers and the watcher is doing an initial scan of the - /// directory to see which files were already present before watching started. + /// If a native directory watcher is available for this platform, this will + /// use it. Otherwise, it will fall back to a [PollingDirectoryWatcher]. /// - /// The watcher does not send notifications for changes that occurred while - /// there were no subscribers, or for files already present before watching. - /// The initial scan is used to determine what "before watching" state of - /// the file system was. - static const SCANNING = const _WatchState("scanning"); - - /// There are subscribers and the watcher is polling the directory to look - /// for changes. - static const WATCHING = const _WatchState("watching"); - - /// The name of the state. - final String name; - - const _WatchState(this.name); + /// If [_pollingDelay] is passed, it specifies the amount of time the watcher + /// will pause between successive polls of the directory contents. Making this + /// shorter will give more immediate feedback at the expense of doing more IO + /// and higher CPU usage. Defaults to one second. Ignored for non-polling + /// watchers. + factory DirectoryWatcher(String directory, {Duration pollingDelay}) { + if (Platform.isLinux) return new LinuxDirectoryWatcher(directory); + return new PollingDirectoryWatcher(directory, pollingDelay: pollingDelay); + } } - -class _FileStatus { - /// The last time the file was modified. - DateTime modified; - - /// The SHA-1 hash of the contents of the file. - List hash; - - _FileStatus(this.modified, this.hash); -} \ No newline at end of file diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart new file mode 100644 index 000000000..9acecf17b --- /dev/null +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -0,0 +1,298 @@ +// 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.directory_watcher.linux; + +import 'dart:async'; +import 'dart:io'; + +import '../directory_watcher.dart'; +import '../utils.dart'; +import '../watch_event.dart'; +import 'resubscribable.dart'; + +import 'package:stack_trace/stack_trace.dart'; + +/// Uses the inotify subsystem to watch for filesystem events. +/// +/// Inotify doesn't suport recursively watching subdirectories, nor does +/// [Directory.watch] polyfill that functionality. This class polyfills it +/// instead. +/// +/// This class also compensates for the non-inotify-specific issues of +/// [Directory.watch] producing multiple events for a single logical action +/// (issue 14372) and providing insufficient information about move events +/// (issue 14424). +class LinuxDirectoryWatcher extends ResubscribableDirectoryWatcher { + LinuxDirectoryWatcher(String directory) + : super(directory, () => new _LinuxDirectoryWatcher(directory)); +} + +class _LinuxDirectoryWatcher implements ManuallyClosedDirectoryWatcher { + final String directory; + + Stream get events => _eventsController.stream; + final _eventsController = new StreamController.broadcast(); + + bool get isReady => _readyCompleter.isCompleted; + + Future get ready => _readyCompleter.future; + final _readyCompleter = new Completer(); + + /// The last known state for each entry in this directory. + /// + /// The keys in this map are the paths to the directory entries; the values + /// are [_EntryState]s indicating whether the entries are files or + /// directories. + final _entries = new Map(); + + /// The watchers for subdirectories of [directory]. + final _subWatchers = new Map(); + + /// A set of all subscriptions that this watcher subscribes to. + /// + /// These are gathered together so that they may all be canceled when the + /// watcher is closed. + final _subscriptions = new Set(); + + _LinuxDirectoryWatcher(String directory) + : directory = directory { + // Batch the inotify changes together so that we can dedup events. + var innerStream = new Directory(directory).watch().transform( + new BatchedStreamTransformer()); + _listen(innerStream, _onBatch, + onError: _eventsController.addError, + onDone: _onDone); + + _listen(new Directory(directory).list(), (entity) { + _entries[entity.path] = new _EntryState(entity is Directory); + if (entity is! Directory) return; + _watchSubdir(entity.path); + }, onError: (error, stackTrace) { + _eventsController.addError(error, stackTrace); + close(); + }, onDone: () { + _waitUntilReady().then((_) => _readyCompleter.complete()); + }, cancelOnError: true); + } + + /// Returns a [Future] that completes once all the subdirectory watchers are + /// fully initialized. + Future _waitUntilReady() { + return Future.wait(_subWatchers.values.map((watcher) => watcher.ready)) + .then((_) { + if (_subWatchers.values.every((watcher) => watcher.isReady)) return; + return _waitUntilReady(); + }); + } + + void close() { + for (var subscription in _subscriptions) { + subscription.cancel(); + } + for (var watcher in _subWatchers.values) { + watcher.close(); + } + + _subWatchers.clear(); + _subscriptions.clear(); + _eventsController.close(); + } + + /// Returns all files (not directories) that this watcher knows of are + /// recursively in the watched directory. + Set get _allFiles { + var files = new Set(); + _getAllFiles(files); + return files; + } + + /// Helper function for [_allFiles]. + /// + /// Adds all files that this watcher knows of to [files]. + void _getAllFiles(Set files) { + files.addAll(_entries.keys + .where((path) => _entries[path] == _EntryState.FILE).toSet()); + for (var watcher in _subWatchers.values) { + watcher._getAllFiles(files); + } + } + + /// Watch a subdirectory of [directory] for changes. + /// + /// If the subdirectory was added after [this] began emitting events, its + /// contents will be emitted as ADD events. + void _watchSubdir(String path) { + if (_subWatchers.containsKey(path)) return; + var watcher = new _LinuxDirectoryWatcher(path); + _subWatchers[path] = watcher; + + // TODO(nweiz): Catch any errors here that indicate that the directory in + // question doesn't exist and silently stop watching it instead of + // propagating the errors. + _listen(watcher.events, (event) { + if (isReady) _eventsController.add(event); + }, onError: (error, stackTrace) { + _eventsController.addError(error, stackTrace); + _eventsController.close(); + }, onDone: () { + if (_subWatchers[path] == watcher) _subWatchers.remove(path); + + // It's possible that a directory was removed and recreated very quickly. + // If so, make sure we're still watching it. + if (new Directory(path).existsSync()) _watchSubdir(path); + }); + + // TODO(nweiz): Right now it's possible for the watcher to emit an event for + // a file before the directory list is complete. This could lead to the user + // seeing a MODIFY or REMOVE event for a file before they see an ADD event, + // which is bad. We should handle that. + // + // One possibility is to provide a general means (e.g. + // `DirectoryWatcher.eventsAndExistingFiles`) to tell a watcher to emit + // events for all the files that already exist. This would be useful for + // top-level clients such as barback as well, and could be implemented with + // a wrapper similar to how listening/canceling works now. + + // If a directory is added after we're finished with the initial scan, emit + // an event for each entry in it. This gives the user consistently gets an + // event for every new file. + watcher.ready.then((_) { + if (!isReady || _eventsController.isClosed) return; + _listen(new Directory(path).list(recursive: true), (entry) { + if (entry is Directory) return; + _eventsController.add(new WatchEvent(ChangeType.ADD, entry.path)); + }, onError: (error, stackTrace) { + // Ignore an exception caused by the dir not existing. It's fine if it + // was added and then quickly removed. + if (error is FileSystemException) return; + + _eventsController.addError(error, stackTrace); + _eventsController.close(); + }, cancelOnError: true); + }); + } + + /// The callback that's run when a batch of changes comes in. + void _onBatch(List batch) { + var changedEntries = new Set(); + var oldEntries = new Map.from(_entries); + + // inotify event batches are ordered by occurrence, so we treat them as a + // log of what happened to a file. + for (var event in batch) { + // If the watched directory is deleted or moved, we'll get a deletion + // event for it. Ignore it; we handle closing [this] when the underlying + // stream is closed. + if (event.path == directory) continue; + + changedEntries.add(event.path); + + if (event is FileSystemMoveEvent) { + changedEntries.add(event.destination); + _changeEntryState(event.path, ChangeType.REMOVE, event.isDirectory); + _changeEntryState(event.destination, ChangeType.ADD, event.isDirectory); + } else { + _changeEntryState(event.path, _changeTypeFor(event), event.isDirectory); + } + } + + for (var path in changedEntries) { + emitEvent(ChangeType type) { + if (isReady) _eventsController.add(new WatchEvent(type, path)); + } + + var oldState = oldEntries[path]; + var newState = _entries[path]; + + if (oldState != _EntryState.FILE && newState == _EntryState.FILE) { + emitEvent(ChangeType.ADD); + } else if (oldState == _EntryState.FILE && newState == _EntryState.FILE) { + emitEvent(ChangeType.MODIFY); + } else if (oldState == _EntryState.FILE && newState != _EntryState.FILE) { + emitEvent(ChangeType.REMOVE); + } + + if (oldState == _EntryState.DIRECTORY) { + var watcher = _subWatchers.remove(path); + if (watcher == null) return; + for (var path in watcher._allFiles) { + _eventsController.add(new WatchEvent(ChangeType.REMOVE, path)); + } + watcher.close(); + } + + if (newState == _EntryState.DIRECTORY) _watchSubdir(path); + } + } + + /// Changes the known state of the entry at [path] based on [change] and + /// [isDir]. + void _changeEntryState(String path, ChangeType change, bool isDir) { + if (change == ChangeType.ADD || change == ChangeType.MODIFY) { + _entries[path] = new _EntryState(isDir); + } else { + assert(change == ChangeType.REMOVE); + _entries.remove(path); + } + } + + /// Determines the [ChangeType] associated with [event]. + ChangeType _changeTypeFor(FileSystemEvent event) { + if (event is FileSystemDeleteEvent) return ChangeType.REMOVE; + if (event is FileSystemCreateEvent) return ChangeType.ADD; + + assert(event is FileSystemModifyEvent); + return ChangeType.MODIFY; + } + + /// Handles the underlying event stream closing, indicating that the directory + /// being watched was removed. + void _onDone() { + // The parent directory often gets a close event before the subdirectories + // are done emitting events. We wait for them to finish before we close + // [events] so that we can be sure to emit a remove event for every file + // that used to exist. + Future.wait(_subWatchers.values.map((watcher) { + try { + return watcher.events.toList(); + } on StateError catch (_) { + // It's possible that [watcher.events] is closed but the onDone event + // hasn't reached us yet. It's fine if so. + return new Future.value(); + } + })).then((_) => close()); + } + + /// Like [Stream.listen], but automatically adds the subscription to + /// [_subscriptions] so that it can be canceled when [close] is called. + void _listen(Stream stream, void onData(event), {Function onError, + void onDone(), bool cancelOnError}) { + var subscription; + subscription = stream.listen(onData, onError: onError, onDone: () { + _subscriptions.remove(subscription); + if (onDone != null) onDone(); + }, cancelOnError: cancelOnError); + _subscriptions.add(subscription); + } +} + +/// An enum for the possible states of entries in a watched directory. +class _EntryState { + final String _name; + + /// The entry is a file. + static const FILE = const _EntryState._("file"); + + /// The entry is a directory. + static const DIRECTORY = const _EntryState._("directory"); + + const _EntryState._(this._name); + + /// Returns [DIRECTORY] if [isDir] is true, and [FILE] otherwise. + factory _EntryState(bool isDir) => + isDir ? _EntryState.DIRECTORY : _EntryState.FILE; + + String toString() => _name; +} diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart new file mode 100644 index 000000000..91ca005f3 --- /dev/null +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -0,0 +1,217 @@ +// 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.directory_watcher.polling; + +import 'dart:async'; +import 'dart:io'; + +import 'package:crypto/crypto.dart'; + +import '../async_queue.dart'; +import '../directory_watcher.dart'; +import '../stat.dart'; +import '../utils.dart'; +import '../watch_event.dart'; +import 'resubscribable.dart'; + +/// Periodically polls a directory for changes. +class PollingDirectoryWatcher extends ResubscribableDirectoryWatcher { + /// Creates a new polling watcher monitoring [directory]. + /// + /// If [_pollingDelay] is passed, it specifies the amount of time the watcher + /// will pause between successive polls of the directory contents. Making this + /// shorter will give more immediate feedback at the expense of doing more IO + /// and higher CPU usage. Defaults to one second. + PollingDirectoryWatcher(String directory, {Duration pollingDelay}) + : super(directory, () { + return new _PollingDirectoryWatcher(directory, + pollingDelay != null ? pollingDelay : new Duration(seconds: 1)); + }); +} + +class _PollingDirectoryWatcher implements ManuallyClosedDirectoryWatcher { + final String directory; + + Stream get events => _events.stream; + final _events = new StreamController.broadcast(); + + bool get isReady => _ready.isCompleted; + + Future get ready => _ready.future; + final _ready = new Completer(); + + /// The amount of time the watcher pauses between successive polls of the + /// directory contents. + final Duration _pollingDelay; + + /// The previous status of the files in the directory. + /// + /// Used to tell which files have been modified. + final _statuses = new Map(); + + /// The subscription used while [directory] is being listed. + /// + /// Will be `null` if a list is not currently happening. + StreamSubscription _listSubscription; + + /// The queue of files waiting to be processed to see if they have been + /// modified. + /// + /// Processing a file is asynchronous, as is listing the directory, so the + /// queue exists to let each of those proceed at their own rate. The lister + /// will enqueue files as quickly as it can. Meanwhile, files are dequeued + /// and processed sequentially. + AsyncQueue _filesToProcess; + + /// The set of files that have been seen in the current directory listing. + /// + /// Used to tell which files have been removed: files that are in [_statuses] + /// but not in here when a poll completes have been removed. + final _polledFiles = new Set(); + + _PollingDirectoryWatcher(this.directory, this._pollingDelay) { + _filesToProcess = new AsyncQueue(_processFile, + onError: _events.addError); + + _poll(); + } + + void close() { + _events.close(); + + // If we're in the middle of listing the directory, stop. + if (_listSubscription != null) _listSubscription.cancel(); + + // Don't process any remaining files. + _filesToProcess.clear(); + _polledFiles.clear(); + _statuses.clear(); + } + + /// Scans the contents of the directory once to see which files have been + /// added, removed, and modified. + void _poll() { + _filesToProcess.clear(); + _polledFiles.clear(); + + endListing() { + assert(!_events.isClosed); + _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(!_events.isClosed); + + if (entity is! File) return; + _filesToProcess.add(entity.path); + }, onError: (error, stackTrace) { + if (!isDirectoryNotFoundException(error)) { + // It's some unknown error. Pipe it over to the event stream so the + // user can see it. + _events.addError(error, stackTrace); + } + + // When an error occurs, we end the listing normally, which has the + // desired effect of marking all files that were in the directory as + // being removed. + endListing(); + }, onDone: endListing, cancelOnError: true); + } + + /// Processes [file] to determine if it has been modified since the last + /// time it was scanned. + Future _processFile(String file) { + // `null` is the sentinel which means the directory listing is complete. + if (file == null) return _completePoll(); + + return getModificationTime(file).then((modified) { + if (_events.isClosed) return null; + + var lastStatus = _statuses[file]; + + // If its modification time hasn't changed, assume the file is unchanged. + if (lastStatus != null && lastStatus.modified == modified) { + // The file is still here. + _polledFiles.add(file); + return null; + } + + return _hashFile(file).then((hash) { + if (_events.isClosed) return; + + var status = new _FileStatus(modified, hash); + _statuses[file] = status; + _polledFiles.add(file); + + // Only notify if we're ready to emit events. + if (!isReady) return; + + // And the file is different. + var changed = lastStatus == null || !_sameHash(lastStatus.hash, hash); + if (!changed) return; + + var type = lastStatus == null ? ChangeType.ADD : ChangeType.MODIFY; + _events.add(new WatchEvent(type, file)); + }); + }); + } + + /// After the directory listing is complete, this determines which files were + /// removed and then restarts the next poll. + Future _completePoll() { + // Any files that were not seen in the last poll but that we have a + // status for must have been removed. + var removedFiles = _statuses.keys.toSet().difference(_polledFiles); + for (var removed in removedFiles) { + if (isReady) _events.add(new WatchEvent(ChangeType.REMOVE, removed)); + _statuses.remove(removed); + } + + if (!isReady) _ready.complete(); + + // Wait and then poll again. + return new Future.delayed(_pollingDelay).then((_) { + if (_events.isClosed) return; + _poll(); + }); + } + + /// Calculates the SHA-1 hash of the file at [path]. + Future> _hashFile(String path) { + return new File(path).readAsBytes().then((bytes) { + var sha1 = new SHA1(); + sha1.add(bytes); + return sha1.close(); + }); + } + + /// Returns `true` if [a] and [b] are the same hash value, i.e. the same + /// series of byte values. + bool _sameHash(List a, List b) { + // Hashes should always be the same size. + assert(a.length == b.length); + + for (var i = 0; i < a.length; i++) { + if (a[i] != b[i]) return false; + } + + return true; + } +} + +class _FileStatus { + /// The last time the file was modified. + DateTime modified; + + /// The SHA-1 hash of the contents of the file. + List hash; + + _FileStatus(this.modified, this.hash); +} + diff --git a/pkgs/watcher/lib/src/directory_watcher/resubscribable.dart b/pkgs/watcher/lib/src/directory_watcher/resubscribable.dart new file mode 100644 index 000000000..daa813a3e --- /dev/null +++ b/pkgs/watcher/lib/src/directory_watcher/resubscribable.dart @@ -0,0 +1,79 @@ +// 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.directory_watcher.resubscribable; + +import 'dart:async'; +import 'dart:io'; + +import '../directory_watcher.dart'; +import '../utils.dart'; +import '../watch_event.dart'; + +typedef ManuallyClosedDirectoryWatcher WatcherFactory(); + +/// A wrapper for [ManuallyClosedDirectoryWatcher] that encapsulates support for +/// closing the watcher when it has no subscribers and re-opening it when it's +/// re-subscribed. +/// +/// It's simpler to implement watchers without worrying about this behavior. +/// This class wraps a watcher class which can be written with the simplifying +/// assumption that it can continue emitting events until an explicit `close` +/// method is called, at which point it will cease emitting events entirely. The +/// [ManuallyClosedDirectoryWatcher] interface is used for these watchers. +/// +/// This would be more cleanly implemented as a function that takes a class and +/// emits a new class, but Dart doesn't support that sort of thing. Instead it +/// takes a factory function that produces instances of the inner class. +abstract class ResubscribableDirectoryWatcher implements DirectoryWatcher { + /// The factory function that produces instances of the inner class. + final WatcherFactory _factory; + + final String directory; + + Stream get events => _eventsController.stream; + StreamController _eventsController; + + bool get isReady => _readyCompleter.isCompleted; + + Future get ready => _readyCompleter.future; + var _readyCompleter = new Completer(); + + /// Creates a new [ResubscribableDirectoryWatcher] wrapping the watchers + /// emitted by [_factory]. + ResubscribableDirectoryWatcher(this.directory, this._factory) { + var watcher; + var subscription; + + _eventsController = new StreamController.broadcast( + onListen: () { + watcher = _factory(); + subscription = watcher.events.listen(_eventsController.add, + onError: _eventsController.addError, + onDone: _eventsController.close); + + // It's important that we complete the value of [_readyCompleter] at the + // time [onListen] is called, as opposed to the value when [watcher.ready] + // fires. A new completer may be created by that time. + watcher.ready.then(_readyCompleter.complete); + }, onCancel: () { + // Cancel the subscription before closing the watcher so that the + // watcher's `onDone` event doesn't close [events]. + subscription.cancel(); + watcher.close(); + _readyCompleter = new Completer(); + }, sync: true); + } +} + +/// An interface for watchers with an explicit, manual [close] method. +/// +/// See [ResubscribableDirectoryWatcher]. +abstract class ManuallyClosedDirectoryWatcher implements DirectoryWatcher { + /// Closes the watcher. + /// + /// Subclasses should close their [events] stream and release any internal + /// resources. + void close(); +} diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index 3d00c0843..e4e445794 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -4,7 +4,9 @@ library watcher.utils; +import 'dart:async'; import 'dart:io'; +import 'dart:collection'; /// Returns `true` if [error] is a [FileSystemException] for a missing /// directory. @@ -15,3 +17,57 @@ bool isDirectoryNotFoundException(error) { var notFoundCode = Platform.operatingSystem == "windows" ? 3 : 2; return error.osError.errorCode == notFoundCode; } + +/// Returns a buffered stream that will emit the same values as the stream +/// returned by [future] once [future] completes. +/// +/// If [future] completes to an error, the return value will emit that error and +/// then close. +Stream futureStream(Future future) { + var controller = new StreamController(sync: true); + future.then((stream) { + stream.listen( + controller.add, + onError: controller.addError, + onDone: controller.close); + }).catchError((e, stackTrace) { + controller.addError(e, stackTrace); + controller.close(); + }); + return controller.stream; +} + +/// Like [new Future], but avoids around issue 11911 by using [new Future.value] +/// under the covers. +Future newFuture(callback()) => new Future.value().then((_) => callback()); + +/// A stream transformer that batches all events that are sent at the same time. +/// +/// When multiple events are synchronously added to a stream controller, the +/// [StreamController] implementation uses [scheduleMicrotask] to schedule the +/// asynchronous firing of each event. In order to recreate the synchronous +/// batches, this collates all the events that are received in "nearby" +/// microtasks. +class BatchedStreamTransformer implements StreamTransformer> { + Stream> bind(Stream input) { + var batch = new Queue(); + return new StreamTransformer>.fromHandlers( + handleData: (event, sink) { + batch.add(event); + + // [Timer.run] schedules an event that runs after any microtasks that have + // been scheduled. + Timer.run(() { + if (batch.isEmpty) return; + sink.add(batch.toList()); + batch.clear(); + }); + }, handleDone: (sink) { + if (batch.isNotEmpty) { + sink.add(batch.toList()); + batch.clear(); + } + sink.close(); + }).bind(input); + } +} diff --git a/pkgs/watcher/lib/watcher.dart b/pkgs/watcher/lib/watcher.dart index c4824b8ec..88531f203 100644 --- a/pkgs/watcher/lib/watcher.dart +++ b/pkgs/watcher/lib/watcher.dart @@ -6,3 +6,4 @@ library watcher; export 'src/watch_event.dart'; export 'src/directory_watcher.dart'; +export 'src/directory_watcher/polling.dart'; diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart new file mode 100644 index 000000000..ba695698b --- /dev/null +++ b/pkgs/watcher/test/directory_watcher/linux_test.dart @@ -0,0 +1,62 @@ +// 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. + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/src/directory_watcher/linux.dart'; +import 'package:watcher/watcher.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +main() { + initConfig(); + + watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); + + setUp(() { + // Increase the timeout because closing a [Directory.watch] stream blocks + // the main isolate for a very long time on Goobuntu, as of kernel + // 3.2.5-gg1336 (see issue 14606). + currentSchedule.timeout *= 3; + + createSandbox(); + }); + + sharedTests(); + + test('DirectoryWatcher creates a LinuxDirectoryWatcher on Linux', () { + expect(new DirectoryWatcher('.'), + new isInstanceOf()); + }); + + test('notifies even if the file contents are unchanged', () { + writeFile("a.txt", contents: "same"); + writeFile("b.txt", contents: "before"); + startWatcher(); + writeFile("a.txt", contents: "same"); + writeFile("b.txt", contents: "after"); + expectModifyEvent("a.txt"); + expectModifyEvent("b.txt"); + }); + + test('emits events for many nested files moved out then immediately back in', + () { + withPermutations((i, j, k) => + writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); + startWatcher(dir: "dir"); + + renameDir("dir/sub", "sub"); + renameDir("sub", "dir/sub"); + + inAnyOrder(() { + withPermutations((i, j, k) => + expectRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); + }); + + inAnyOrder(() { + withPermutations((i, j, k) => + expectAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); + }); + }); +} diff --git a/pkgs/watcher/test/directory_watcher/polling_test.dart b/pkgs/watcher/test/directory_watcher/polling_test.dart new file mode 100644 index 000000000..02ed5d215 --- /dev/null +++ b/pkgs/watcher/test/directory_watcher/polling_test.dart @@ -0,0 +1,39 @@ +// 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. + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/watcher.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +main() { + initConfig(); + + // Use a short delay to make the tests run quickly. + watcherFactory = (dir) => new PollingDirectoryWatcher(dir, + pollingDelay: new Duration(milliseconds: 100)); + + setUp(createSandbox); + + sharedTests(); + + test('does not notify if the file contents are unchanged', () { + writeFile("a.txt", contents: "same"); + writeFile("b.txt", contents: "before"); + startWatcher(); + writeFile("a.txt", contents: "same"); + writeFile("b.txt", contents: "after"); + expectModifyEvent("b.txt"); + }); + + test('does not notify if the modification time did not change', () { + writeFile("a.txt", contents: "before"); + writeFile("b.txt", contents: "before"); + startWatcher(); + writeFile("a.txt", contents: "after", updateModified: false); + writeFile("b.txt", contents: "after"); + expectModifyEvent("b.txt"); + }); +} diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart new file mode 100644 index 000000000..eed606931 --- /dev/null +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -0,0 +1,222 @@ +// Copyright (c) 2012, 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. + +import 'package:scheduled_test/scheduled_test.dart'; + +import '../utils.dart'; + +sharedTests() { + test('does not notify for files that already exist when started', () { + // Make some pre-existing files. + writeFile("a.txt"); + writeFile("b.txt"); + + startWatcher(); + + // Change one after the watcher is running. + writeFile("b.txt", contents: "modified"); + + // We should get a modify event for the changed file, but no add events + // for them before this. + expectModifyEvent("b.txt"); + }); + + test('notifies when a file is added', () { + startWatcher(); + writeFile("file.txt"); + expectAddEvent("file.txt"); + }); + + test('notifies when a file is modified', () { + writeFile("file.txt"); + startWatcher(); + writeFile("file.txt", contents: "modified"); + expectModifyEvent("file.txt"); + }); + + test('notifies when a file is removed', () { + writeFile("file.txt"); + startWatcher(); + deleteFile("file.txt"); + expectRemoveEvent("file.txt"); + }); + + test('notifies when a file is modified multiple times', () { + writeFile("file.txt"); + startWatcher(); + writeFile("file.txt", contents: "modified"); + expectModifyEvent("file.txt"); + writeFile("file.txt", contents: "modified again"); + expectModifyEvent("file.txt"); + }); + + test('when the watched directory is deleted, removes all files', () { + writeFile("dir/a.txt"); + writeFile("dir/b.txt"); + + startWatcher(dir: "dir"); + + deleteDir("dir"); + inAnyOrder(() { + expectRemoveEvent("dir/a.txt"); + expectRemoveEvent("dir/b.txt"); + }); + }); + + group("moves", () { + test('notifies when a file is moved within the watched directory', () { + writeFile("old.txt"); + startWatcher(); + renameFile("old.txt", "new.txt"); + + inAnyOrder(() { + expectAddEvent("new.txt"); + expectRemoveEvent("old.txt"); + }); + }); + + test('notifies when a file is moved from outside the watched directory', + () { + writeFile("old.txt"); + createDir("dir"); + startWatcher(dir: "dir"); + + renameFile("old.txt", "dir/new.txt"); + expectAddEvent("dir/new.txt"); + }); + + test('notifies when a file is moved outside the watched directory', () { + writeFile("dir/old.txt"); + startWatcher(dir: "dir"); + + renameFile("dir/old.txt", "new.txt"); + expectRemoveEvent("dir/old.txt"); + }); + }); + + group("clustered changes", () { + test("doesn't notify when a file is created and then immediately removed", + () { + startWatcher(); + writeFile("file.txt"); + deleteFile("file.txt"); + + // [startWatcher] will assert that no events were fired. + }); + + test("reports a modification when a file is deleted and then immediately " + "recreated", () { + writeFile("file.txt"); + startWatcher(); + + deleteFile("file.txt"); + writeFile("file.txt", contents: "re-created"); + expectModifyEvent("file.txt"); + }); + + test("reports a modification when a file is moved and then immediately " + "recreated", () { + writeFile("old.txt"); + startWatcher(); + + renameFile("old.txt", "new.txt"); + writeFile("old.txt", contents: "re-created"); + inAnyOrder(() { + expectModifyEvent("old.txt"); + expectAddEvent("new.txt"); + }); + }); + + test("reports a removal when a file is modified and then immediately " + "removed", () { + writeFile("file.txt"); + startWatcher(); + + writeFile("file.txt", contents: "modified"); + deleteFile("file.txt"); + expectRemoveEvent("file.txt"); + }); + + test("reports an add when a file is added and then immediately modified", + () { + startWatcher(); + + writeFile("file.txt"); + writeFile("file.txt", contents: "modified"); + expectAddEvent("file.txt"); + }); + }); + + group("subdirectories", () { + test('watches files in subdirectories', () { + startWatcher(); + writeFile("a/b/c/d/file.txt"); + expectAddEvent("a/b/c/d/file.txt"); + }); + + test('notifies when a subdirectory is moved within the watched directory ' + 'and then its contents are modified', () { + writeFile("old/file.txt"); + startWatcher(); + + renameDir("old", "new"); + inAnyOrder(() { + expectRemoveEvent("old/file.txt"); + expectAddEvent("new/file.txt"); + }); + + writeFile("new/file.txt", contents: "modified"); + expectModifyEvent("new/file.txt"); + }); + + test('emits events for many nested files added at once', () { + withPermutations((i, j, k) => + writeFile("sub/sub-$i/sub-$j/file-$k.txt")); + + createDir("dir"); + startWatcher(dir: "dir"); + renameDir("sub", "dir/sub"); + + inAnyOrder(() { + withPermutations((i, j, k) => + expectAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); + }); + }); + + test('emits events for many nested files removed at once', () { + withPermutations((i, j, k) => + writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); + + createDir("dir"); + startWatcher(dir: "dir"); + + // Rename the directory rather than deleting it because native watchers + // report a rename as a single DELETE event for the directory, whereas + // they report recursive deletion with DELETE events for every file in the + // directory. + renameDir("dir/sub", "sub"); + + inAnyOrder(() { + withPermutations((i, j, k) => + expectRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); + }); + }); + + test('emits events for many nested files moved at once', () { + withPermutations((i, j, k) => + writeFile("dir/old/sub-$i/sub-$j/file-$k.txt")); + + createDir("dir"); + startWatcher(dir: "dir"); + renameDir("dir/old", "dir/new"); + + inAnyOrder(() { + withPermutations((i, j, k) { + expectRemoveEvent("dir/old/sub-$i/sub-$j/file-$k.txt"); + expectAddEvent("dir/new/sub-$i/sub-$j/file-$k.txt"); + }); + }); + }); + }); +} diff --git a/pkgs/watcher/test/directory_watcher_test.dart b/pkgs/watcher/test/directory_watcher_test.dart deleted file mode 100644 index 841dd0808..000000000 --- a/pkgs/watcher/test/directory_watcher_test.dart +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) 2012, 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. - -import 'package:scheduled_test/scheduled_test.dart'; - -import 'utils.dart'; - -main() { - initConfig(); - - setUp(createSandbox); - - test('does not notify for files that already exist when started', () { - // Make some pre-existing files. - writeFile("a.txt"); - writeFile("b.txt"); - - createWatcher(); - - // Change one after the watcher is running. - writeFile("b.txt", contents: "modified"); - - // We should get a modify event for the changed file, but no add events - // for them before this. - expectModifyEvent("b.txt"); - }); - - test('notifies when a file is added', () { - createWatcher(); - writeFile("file.txt"); - expectAddEvent("file.txt"); - }); - - test('notifies when a file is modified', () { - writeFile("file.txt"); - createWatcher(); - writeFile("file.txt", contents: "modified"); - expectModifyEvent("file.txt"); - }); - - test('notifies when a file is removed', () { - writeFile("file.txt"); - createWatcher(); - deleteFile("file.txt"); - expectRemoveEvent("file.txt"); - }); - - test('notifies when a file is moved', () { - writeFile("old.txt"); - createWatcher(); - renameFile("old.txt", "new.txt"); - expectAddEvent("new.txt"); - expectRemoveEvent("old.txt"); - }); - - test('notifies when a file is modified multiple times', () { - writeFile("file.txt"); - createWatcher(); - writeFile("file.txt", contents: "modified"); - expectModifyEvent("file.txt"); - writeFile("file.txt", contents: "modified again"); - expectModifyEvent("file.txt"); - }); - - test('does not notify if the file contents are unchanged', () { - writeFile("a.txt", contents: "same"); - writeFile("b.txt", contents: "before"); - createWatcher(); - writeFile("a.txt", contents: "same"); - writeFile("b.txt", contents: "after"); - expectModifyEvent("b.txt"); - }); - - test('does not notify if the modification time did not change', () { - writeFile("a.txt", contents: "before"); - writeFile("b.txt", contents: "before"); - createWatcher(); - writeFile("a.txt", contents: "after", updateModified: false); - writeFile("b.txt", contents: "after"); - expectModifyEvent("b.txt"); - }); - - test('watches files in subdirectories', () { - createWatcher(); - 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/no_subscription/linux_test.dart b/pkgs/watcher/test/no_subscription/linux_test.dart new file mode 100644 index 000000000..7978830bc --- /dev/null +++ b/pkgs/watcher/test/no_subscription/linux_test.dart @@ -0,0 +1,20 @@ +// 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. + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/src/directory_watcher/linux.dart'; +import 'package:watcher/watcher.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +main() { + initConfig(); + + watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); + + setUp(createSandbox); + + sharedTests(); +} \ No newline at end of file diff --git a/pkgs/watcher/test/no_subscription/polling_test.dart b/pkgs/watcher/test/no_subscription/polling_test.dart new file mode 100644 index 000000000..fa4f0cbe2 --- /dev/null +++ b/pkgs/watcher/test/no_subscription/polling_test.dart @@ -0,0 +1,19 @@ +// 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. + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/watcher.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +main() { + initConfig(); + + watcherFactory = (dir) => new PollingDirectoryWatcher(dir); + + setUp(createSandbox); + + sharedTests(); +} \ No newline at end of file diff --git a/pkgs/watcher/test/no_subscription_test.dart b/pkgs/watcher/test/no_subscription/shared.dart similarity index 73% rename from pkgs/watcher/test/no_subscription_test.dart rename to pkgs/watcher/test/no_subscription/shared.dart index 2e7b6d3ff..cd279e1ca 100644 --- a/pkgs/watcher/test/no_subscription_test.dart +++ b/pkgs/watcher/test/no_subscription/shared.dart @@ -7,14 +7,10 @@ import 'dart:async'; import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/watcher.dart'; -import 'utils.dart'; +import '../utils.dart'; -main() { - initConfig(); - - setUp(createSandbox); - - test('does not notify for changes when there were no subscribers', () { +sharedTests() { + test('does not notify for changes when there are no subscribers', () { // Note that this test doesn't rely as heavily on the test functions in // utils.dart because it needs to be very explicit about when the event // stream is and is not subscribed. @@ -51,16 +47,10 @@ main() { expect(event.path, endsWith("added.txt")); completer.complete(); })); - }); - // The watcher will have been cancelled and then resumed in the middle of - // its pause between polling loops. That means the second scan to skip - // what changed while we were unsubscribed won't happen until after that - // delay is done. Wait long enough for that to happen. - // - // We're doing * 4 here because that seems to give the slower bots enough - // time for this to complete. - schedule(() => new Future.delayed(watcher.pollingDelay * 4)); + // Wait until the watcher is ready to dispatch events again. + return watcher.ready; + }); // And add a third file. writeFile("added.txt"); diff --git a/pkgs/watcher/test/ready/linux_test.dart b/pkgs/watcher/test/ready/linux_test.dart new file mode 100644 index 000000000..7978830bc --- /dev/null +++ b/pkgs/watcher/test/ready/linux_test.dart @@ -0,0 +1,20 @@ +// 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. + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/src/directory_watcher/linux.dart'; +import 'package:watcher/watcher.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +main() { + initConfig(); + + watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); + + setUp(createSandbox); + + sharedTests(); +} \ No newline at end of file diff --git a/pkgs/watcher/test/ready/polling_test.dart b/pkgs/watcher/test/ready/polling_test.dart new file mode 100644 index 000000000..fa4f0cbe2 --- /dev/null +++ b/pkgs/watcher/test/ready/polling_test.dart @@ -0,0 +1,19 @@ +// 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. + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/watcher.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +main() { + initConfig(); + + watcherFactory = (dir) => new PollingDirectoryWatcher(dir); + + setUp(createSandbox); + + sharedTests(); +} \ No newline at end of file diff --git a/pkgs/watcher/test/ready_test.dart b/pkgs/watcher/test/ready/shared.dart similarity index 95% rename from pkgs/watcher/test/ready_test.dart rename to pkgs/watcher/test/ready/shared.dart index 11b77e02c..af1b58f99 100644 --- a/pkgs/watcher/test/ready_test.dart +++ b/pkgs/watcher/test/ready/shared.dart @@ -2,17 +2,11 @@ // 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. -import 'dart:async'; - import 'package:scheduled_test/scheduled_test.dart'; -import 'utils.dart'; - -main() { - initConfig(); - - setUp(createSandbox); +import '../utils.dart'; +sharedTests() { test('ready does not complete until after subscription', () { var watcher = createWatcher(waitForReady: false); diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 28d57b05c..d1b9f94b8 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -5,6 +5,7 @@ library watcher.test.utils; import 'dart:async'; +import 'dart:collection'; import 'dart:io'; import 'package:path/path.dart' as p; @@ -12,6 +13,7 @@ import 'package:scheduled_test/scheduled_test.dart'; import 'package:unittest/compact_vm_config.dart'; import 'package:watcher/watcher.dart'; import 'package:watcher/src/stat.dart'; +import 'package:watcher/src/utils.dart'; /// The path to the temporary sandbox created for each test. All file /// operations are implicitly relative to this directory. @@ -36,6 +38,14 @@ var _nextEvent = 0; /// increment the mod time for that file instantly. Map _mockFileModificationTimes; +typedef DirectoryWatcher WatcherFactory(String directory); + +/// Sets the function used to create the directory watcher. +set watcherFactory(WatcherFactory factory) { + _watcherFactory = factory; +} +WatcherFactory _watcherFactory; + void initConfig() { useCompactVMConfiguration(); filterStacks = true; @@ -54,10 +64,10 @@ void createSandbox() { path = p.normalize(p.relative(path, from: _sandboxDir)); // Make sure we got a path in the sandbox. - assert(p.isRelative(path) && !path.startsWith("..")); + assert(p.isRelative(path) && !path.startsWith("..")); - return new DateTime.fromMillisecondsSinceEpoch( - _mockFileModificationTimes[path]); + var mtime = _mockFileModificationTimes[path]; + return new DateTime.fromMillisecondsSinceEpoch(mtime == null ? 0 : mtime); }); // Delete the sandbox when done. @@ -86,68 +96,113 @@ DirectoryWatcher createWatcher({String dir, bool waitForReady}) { dir = p.join(_sandboxDir, dir); } - // Use a short delay to make the tests run quickly. - _watcher = new DirectoryWatcher(dir, - pollingDelay: new Duration(milliseconds: 100)); + var watcher = _watcherFactory(dir); // Wait until the scan is finished so that we don't miss changes to files // that could occur before the scan completes. if (waitForReady != false) { - schedule(() => _watcher.ready, "wait for watcher to be ready"); + schedule(() => watcher.ready, "wait for watcher to be ready"); } - currentSchedule.onComplete.schedule(() { - _nextEvent = 0; - _watcher = null; - }, "reset watcher"); - - return _watcher; + return watcher; } -/// Expects that the next set of events will all be changes of [type] on -/// [paths]. +/// The stream of events from the watcher started with [startWatcher]. +Stream _watcherEvents; + +/// Creates a new [DirectoryWatcher] that watches a temporary directory and +/// starts monitoring it for events. /// -/// Validates that events are delivered for all paths in [paths], but allows -/// them in any order. -void expectEvents(ChangeType type, Iterable paths) { - var pathSet = paths - .map((path) => p.join(_sandboxDir, path)) - .map(p.normalize) - .toSet(); - - // 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); - } +/// If [dir] is provided, watches a subdirectory in the sandbox with that name. +void startWatcher({String dir}) { + // We want to wait until we're ready *after* we subscribe to the watcher's + // events. + _watcher = createWatcher(dir: dir, waitForReady: false); + + // Schedule [_watcher.events.listen] so that the watcher doesn't start + // watching [dir] before it exists. Expose [_watcherEvents] immediately so + // that it can be accessed synchronously after this. + _watcherEvents = futureStream(schedule(() { + var allEvents = new Queue(); + var subscription = _watcher.events.listen(allEvents.add, + onError: currentSchedule.signalError); + + currentSchedule.onComplete.schedule(() { + var numEvents = _nextEvent; + subscription.cancel(); + _nextEvent = 0; + _watcher = null; + + // If there are already errors, don't add this to the output and make + // people think it might be the root cause. + if (currentSchedule.errors.isEmpty) { + expect(allEvents, hasLength(numEvents)); + } + }, "reset watcher"); + + return _watcher.events; + }, "create watcher")).asBroadcastStream(); + + schedule(() => _watcher.ready, "wait for watcher to be ready"); +} - // Schedule it so that later file modifications don't occur until after this - // event is received. - schedule(() => Future.wait(futures), - "wait for $type events on ${paths.join(', ')}"); +/// A future set by [inAnyOrder] that will complete to the set of events that +/// occur in the [inAnyOrder] block. +Future> _unorderedEventFuture; + +/// Runs [block] and allows multiple [expectEvent] calls in that block to match +/// events in any order. +void inAnyOrder(block()) { + var oldFuture = _unorderedEventFuture; + try { + var firstEvent = _nextEvent; + var completer = new Completer(); + _unorderedEventFuture = completer.future; + block(); + + _watcherEvents.skip(firstEvent).take(_nextEvent - firstEvent).toSet() + .then(completer.complete, onError: completer.completeError); + currentSchedule.wrapFuture(_unorderedEventFuture, + "waiting for ${_nextEvent - firstEvent} events"); + } finally { + _unorderedEventFuture = oldFuture; + } } -void expectAddEvent(String path) => expectEvents(ChangeType.ADD, [path]); -void expectModifyEvent(String path) => expectEvents(ChangeType.MODIFY, [path]); -void expectRemoveEvent(String path) => expectEvents(ChangeType.REMOVE, [path]); +/// Expects that the next set of event will be a change of [type] on [path]. +/// +/// Multiple calls to [expectEvent] require that the events are received in that +/// order unless they're called in an [inAnyOrder] block. +void expectEvent(ChangeType type, String path) { + var matcher = predicate((e) { + return e is WatchEvent && e.type == type && + e.path == p.join(_sandboxDir, path); + }, "is $type $path"); + + if (_unorderedEventFuture != null) { + // Assign this to a local variable since it will be un-assigned by the time + // the scheduled callback runs. + var future = _unorderedEventFuture; + + expect( + schedule(() => future, "should fire $type event on $path"), + completion(contains(matcher))); + } else { + var future = currentSchedule.wrapFuture( + _watcherEvents.elementAt(_nextEvent), + "waiting for $type event on $path"); -void expectRemoveEvents(Iterable paths) { - expectEvents(ChangeType.REMOVE, paths); + expect( + schedule(() => future, "should fire $type event on $path"), + completion(matcher)); + } + _nextEvent++; } +void expectAddEvent(String path) => expectEvent(ChangeType.ADD, path); +void expectModifyEvent(String path) => expectEvent(ChangeType.MODIFY, path); +void expectRemoveEvent(String path) => expectEvent(ChangeType.REMOVE, path); + /// Schedules writing a file in the sandbox at [path] with [contents]. /// /// If [contents] is omitted, creates an empty file. If [updatedModified] is @@ -201,6 +256,21 @@ void renameFile(String from, String to) { }, "rename file $from to $to"); } +/// Schedules creating a directory in the sandbox at [path]. +void createDir(String path) { + schedule(() { + new Directory(p.join(_sandboxDir, path)).createSync(); + }, "create directory $path"); +} + +/// Schedules renaming a directory in the sandbox from [from] to [to]. +void renameDir(String from, String to) { + schedule(() { + new Directory(p.join(_sandboxDir, from)) + .renameSync(p.join(_sandboxDir, to)); + }, "rename directory $from to $to"); +} + /// Schedules deleting a directory in the sandbox at [path]. void deleteDir(String path) { schedule(() { @@ -208,22 +278,17 @@ void deleteDir(String path) { }, "delete directory $path"); } -/// A [Matcher] for [WatchEvent]s. -class _ChangeMatcher extends Matcher { - /// The expected change. - final ChangeType type; - - /// The expected path. - final String path; - - _ChangeMatcher(this.type, this.path); - - Description describe(Description description) { - description.add("$type $path"); +/// Runs [callback] with every permutation of non-negative [i], [j], and [k] +/// less than [limit]. +/// +/// [limit] defaults to 3. +void withPermutations(callback(int i, int j, int k), {int limit}) { + if (limit == null) limit = 3; + for (var i = 0; i < limit; i++) { + for (var j = 0; j < limit; j++) { + for (var k = 0; k < limit; k++) { + callback(i, j, k); + } + } } - - bool matches(item, Map matchState) => - item is WatchEvent && - item.type == type && - p.normalize(item.path) == p.normalize(path); } From 3d4e068bf707fe4a87c3152ef1f02ffd48f75361 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Fri, 8 Nov 2013 01:44:53 +0000 Subject: [PATCH 024/201] Fix the watcher tests on Windows. The tests were assuming POSIX paths. R=rnystrom@google.com BUG= Review URL: https://codereview.chromium.org//66073002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30091 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/test/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index d1b9f94b8..3070d7906 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -176,7 +176,7 @@ void inAnyOrder(block()) { void expectEvent(ChangeType type, String path) { var matcher = predicate((e) { return e is WatchEvent && e.type == type && - e.path == p.join(_sandboxDir, path); + e.path == p.join(_sandboxDir, p.normalize(path)); }, "is $type $path"); if (_unorderedEventFuture != null) { From 1b8eb689d0b2a46abb0f0944d38f1320077f6ac3 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Fri, 8 Nov 2013 01:46:11 +0000 Subject: [PATCH 025/201] Fix an analyzer error in the linux directory watcher. R=rnystrom@google.com BUG= Review URL: https://codereview.chromium.org//65533005 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30093 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/directory_watcher/linux.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index 9acecf17b..7ac6e6e08 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -82,7 +82,7 @@ class _LinuxDirectoryWatcher implements ManuallyClosedDirectoryWatcher { Future _waitUntilReady() { return Future.wait(_subWatchers.values.map((watcher) => watcher.ready)) .then((_) { - if (_subWatchers.values.every((watcher) => watcher.isReady)) return; + if (_subWatchers.values.every((watcher) => watcher.isReady)) return null; return _waitUntilReady(); }); } From 9d1f993889574a9957bc3427254ec019121d0be9 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 11 Nov 2013 19:47:18 +0000 Subject: [PATCH 026/201] Wrap Directory.watch on Mac OS for the watcher package. R=rnystrom@google.com BUG=14428 Review URL: https://codereview.chromium.org//66163002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30168 260f80e4-7a28-3924-810f-c04153c831b5 --- .../src/constructable_file_system_event.dart | 60 +++ pkgs/watcher/lib/src/directory_watcher.dart | 2 + .../lib/src/directory_watcher/linux.dart | 2 - .../lib/src/directory_watcher/mac_os.dart | 355 ++++++++++++++++++ pkgs/watcher/lib/src/path_set.dart | 168 +++++++++ pkgs/watcher/lib/src/utils.dart | 4 + .../test/directory_watcher/mac_os_test.dart | 67 ++++ .../test/directory_watcher/shared.dart | 18 + .../test/no_subscription/mac_os_test.dart | 46 +++ pkgs/watcher/test/path_set_test.dart | 235 ++++++++++++ pkgs/watcher/test/ready/mac_os_test.dart | 20 + pkgs/watcher/test/utils.dart | 2 + 12 files changed, 977 insertions(+), 2 deletions(-) create mode 100644 pkgs/watcher/lib/src/constructable_file_system_event.dart create mode 100644 pkgs/watcher/lib/src/directory_watcher/mac_os.dart create mode 100644 pkgs/watcher/lib/src/path_set.dart create mode 100644 pkgs/watcher/test/directory_watcher/mac_os_test.dart create mode 100644 pkgs/watcher/test/no_subscription/mac_os_test.dart create mode 100644 pkgs/watcher/test/path_set_test.dart create mode 100644 pkgs/watcher/test/ready/mac_os_test.dart diff --git a/pkgs/watcher/lib/src/constructable_file_system_event.dart b/pkgs/watcher/lib/src/constructable_file_system_event.dart new file mode 100644 index 000000000..e65e25827 --- /dev/null +++ b/pkgs/watcher/lib/src/constructable_file_system_event.dart @@ -0,0 +1,60 @@ +// 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.constructable_file_system_event; + +import 'dart:io'; + +abstract class _ConstructableFileSystemEvent implements FileSystemEvent { + final bool isDirectory; + final String path; + final int type; + + _ConstructableFileSystemEvent(this.path, this.isDirectory); +} + +class ConstructableFileSystemCreateEvent extends _ConstructableFileSystemEvent + implements FileSystemCreateEvent { + final type = FileSystemEvent.CREATE; + + ConstructableFileSystemCreateEvent(String path, bool isDirectory) + : super(path, isDirectory); + + String toString() => "FileSystemCreateEvent('$path')"; +} + +class ConstructableFileSystemDeleteEvent extends _ConstructableFileSystemEvent + implements FileSystemDeleteEvent { + final type = FileSystemEvent.DELETE; + + ConstructableFileSystemDeleteEvent(String path, bool isDirectory) + : super(path, isDirectory); + + String toString() => "FileSystemDeleteEvent('$path')"; +} + +class ConstructableFileSystemModifyEvent extends _ConstructableFileSystemEvent + implements FileSystemModifyEvent { + final bool contentChanged; + final type = FileSystemEvent.MODIFY; + + ConstructableFileSystemModifyEvent(String path, bool isDirectory, + this.contentChanged) + : super(path, isDirectory); + + String toString() => + "FileSystemModifyEvent('$path', contentChanged=$contentChanged)"; +} + +class ConstructableFileSystemMoveEvent extends _ConstructableFileSystemEvent + implements FileSystemMoveEvent { + final String destination; + final type = FileSystemEvent.MOVE; + + ConstructableFileSystemMoveEvent(String path, bool isDirectory, + this.destination) + : super(path, isDirectory); + + String toString() => "FileSystemMoveEvent('$path', '$destination')"; +} diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 4484c2b77..78b1174e1 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -9,6 +9,7 @@ import 'dart:io'; import 'watch_event.dart'; import 'directory_watcher/linux.dart'; +import 'directory_watcher/mac_os.dart'; import 'directory_watcher/polling.dart'; /// Watches the contents of a directory and emits [WatchEvent]s when something @@ -54,6 +55,7 @@ abstract class DirectoryWatcher { /// watchers. factory DirectoryWatcher(String directory, {Duration pollingDelay}) { if (Platform.isLinux) return new LinuxDirectoryWatcher(directory); + if (Platform.isMacOS) return new MacOSDirectoryWatcher(directory); return new PollingDirectoryWatcher(directory, pollingDelay: pollingDelay); } } diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index 7ac6e6e08..f0622f646 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -12,8 +12,6 @@ import '../utils.dart'; import '../watch_event.dart'; import 'resubscribable.dart'; -import 'package:stack_trace/stack_trace.dart'; - /// Uses the inotify subsystem to watch for filesystem events. /// /// Inotify doesn't suport recursively watching subdirectories, nor does diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart new file mode 100644 index 000000000..9d3660df7 --- /dev/null +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -0,0 +1,355 @@ +// 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.directory_watcher.mac_os; + +import 'dart:async'; +import 'dart:io'; + +import '../constructable_file_system_event.dart'; +import '../path_set.dart'; +import '../utils.dart'; +import '../watch_event.dart'; +import 'resubscribable.dart'; + +import 'package:path/path.dart' as p; + +/// Uses the FSEvents subsystem to watch for filesystem events. +/// +/// FSEvents has two main idiosyncrasies that this class works around. First, it +/// will occasionally report events that occurred before the filesystem watch +/// was initiated. Second, if multiple events happen to the same file in close +/// succession, it won't report them in the order they occurred. See issue +/// 14373. +/// +/// This also works around issues 14793, 14806, and 14849 in the implementation +/// of [Directory.watch]. +class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher { + MacOSDirectoryWatcher(String directory) + : super(directory, () => new _MacOSDirectoryWatcher(directory)); +} + +class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { + final String directory; + + Stream get events => _eventsController.stream; + final _eventsController = new StreamController.broadcast(); + + bool get isReady => _readyCompleter.isCompleted; + + Future get ready => _readyCompleter.future; + final _readyCompleter = new Completer(); + + /// The number of event batches that have been received from + /// [Directory.watch]. + /// + /// This is used to determine if the [Directory.watch] stream was falsely + /// closed due to issue 14849. A close caused by events in the past will only + /// happen before or immediately after the first batch of events. + int batches = 0; + + /// The set of files that are known to exist recursively within the watched + /// directory. + /// + /// The state of files on the filesystem is compared against this to determine + /// the real change that occurred when working around issue 14373. This is + /// also used to emit REMOVE events when subdirectories are moved out of the + /// watched directory. + final PathSet _files; + + /// The subscription to the stream returned by [Directory.watch]. + /// + /// This is separate from [_subscriptions] because this stream occasionally + /// needs to be resubscribed in order to work around issue 14849. + StreamSubscription _watchSubscription; + + /// A set of subscriptions that this watcher subscribes to. + /// + /// These are gathered together so that they may all be canceled when the + /// watcher is closed. This does not include [_watchSubscription]. + final _subscriptions = new Set(); + + _MacOSDirectoryWatcher(String directory) + : directory = directory, + _files = new PathSet(directory) { + _startWatch(); + + _listen(new Directory(directory).list(recursive: true), + (entity) { + if (entity is! Directory) _files.add(entity.path); + }, + onError: _emitError, + onDone: _readyCompleter.complete, + cancelOnError: true); + } + + void close() { + for (var subscription in _subscriptions) { + subscription.cancel(); + } + _subscriptions.clear(); + if (_watchSubscription != null) _watchSubscription.cancel(); + _watchSubscription = null; + _eventsController.close(); + } + + /// The callback that's run when [Directory.watch] emits a batch of events. + void _onBatch(List batch) { + batches++; + + _sortEvents(batch).forEach((path, events) { + var canonicalEvent = _canonicalEvent(events); + events = canonicalEvent == null ? + _eventsBasedOnFileSystem(path) : [canonicalEvent]; + + for (var event in events) { + if (event is FileSystemCreateEvent) { + if (!event.isDirectory) { + _emitEvent(ChangeType.ADD, path); + _files.add(path); + continue; + } + + _listen(new Directory(path).list(recursive: true), (entity) { + if (entity is Directory) return; + _emitEvent(ChangeType.ADD, entity.path); + _files.add(entity.path); + }, onError: _emitError, cancelOnError: true); + } else if (event is FileSystemModifyEvent) { + assert(!event.isDirectory); + _emitEvent(ChangeType.MODIFY, path); + } else { + assert(event is FileSystemDeleteEvent); + for (var removedPath in _files.remove(path)) { + _emitEvent(ChangeType.REMOVE, removedPath); + } + } + } + }); + } + + /// Sort all the events in a batch into sets based on their path. + /// + /// A single input event may result in multiple events in the returned map; + /// for example, a MOVE event becomes a DELETE event for the source and a + /// CREATE event for the destination. + /// + /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it + /// contain any events relating to [directory]. + Map> _sortEvents(List batch) { + var eventsForPaths = {}; + + // FSEvents can report past events, including events on the root directory + // such as it being created. We want to ignore these. If the directory is + // really deleted, that's handled by [_onDone]. + batch = batch.where((event) => event.path != directory).toList(); + + // Events within directories that already have events are superfluous; the + // directory's full contents will be examined anyway, so we ignore such + // events. Emitting them could cause useless or out-of-order events. + var directories = unionAll(batch.map((event) { + if (!event.isDirectory) return new Set(); + if (event is! FileSystemMoveEvent) return new Set.from([event.path]); + return new Set.from([event.path, event.destination]); + })); + + isInModifiedDirectory(path) => + directories.any((dir) => path != dir && path.startsWith(dir)); + + addEvent(path, event) { + if (isInModifiedDirectory(path)) return; + var set = eventsForPaths.putIfAbsent(path, () => new Set()); + set.add(event); + } + + for (var event in batch.where((event) => event is! FileSystemMoveEvent)) { + addEvent(event.path, event); + } + + // Issue 14806 means that move events can be misleading if they're in the + // same batch as another modification of a related file. If they are, we + // make the event set empty to ensure we check the state of the filesystem. + // Otherwise, treat them as a DELETE followed by an ADD. + for (var event in batch.where((event) => event is FileSystemMoveEvent)) { + if (eventsForPaths.containsKey(event.path) || + eventsForPaths.containsKey(event.destination)) { + + if (!isInModifiedDirectory(event.path)) { + eventsForPaths[event.path] = new Set(); + } + if (!isInModifiedDirectory(event.destination)) { + eventsForPaths[event.destination] = new Set(); + } + + continue; + } + + addEvent(event.path, new ConstructableFileSystemDeleteEvent( + event.path, event.isDirectory)); + addEvent(event.destination, new ConstructableFileSystemCreateEvent( + event.path, event.isDirectory)); + } + + return eventsForPaths; + } + + /// Returns the canonical event from a batch of events on the same path, if + /// one exists. + /// + /// If [batch] doesn't contain any contradictory events (e.g. DELETE and + /// CREATE, or events with different values for [isDirectory]), this returns a + /// single event that describes what happened to the path in question. + /// + /// If [batch] does contain contradictory events, this returns `null` to + /// indicate that the state of the path on the filesystem should be checked to + /// determine what occurred. + FileSystemEvent _canonicalEvent(Set batch) { + // An empty batch indicates that we've learned earlier that the batch is + // contradictory (e.g. because of a move). + if (batch.isEmpty) return null; + + var type = batch.first.type; + var isDir = batch.first.isDirectory; + + for (var event in batch.skip(1)) { + // If one event reports that the file is a directory and another event + // doesn't, that's a contradiction. + if (isDir != event.isDirectory) return null; + + // Modify events don't contradict either CREATE or REMOVE events. We can + // safely assume the file was modified after a CREATE or before the + // REMOVE; otherwise there will also be a REMOVE or CREATE event + // (respectively) that will be contradictory. + if (event is FileSystemModifyEvent) continue; + assert(event is FileSystemCreateEvent || event is FileSystemDeleteEvent); + + // If we previously thought this was a MODIFY, we now consider it to be a + // CREATE or REMOVE event. This is safe for the same reason as above. + if (type == FileSystemEvent.MODIFY) { + type = event.type; + continue; + } + + // A CREATE event contradicts a REMOVE event and vice versa. + assert(type == FileSystemEvent.CREATE || type == FileSystemEvent.DELETE); + if (type != event.type) return null; + } + + switch (type) { + case FileSystemEvent.CREATE: + // Issue 14793 means that CREATE events can actually mean DELETE, so we + // should always check the filesystem for them. + return null; + case FileSystemEvent.DELETE: + return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir); + case FileSystemEvent.MODIFY: + return new ConstructableFileSystemModifyEvent( + batch.first.path, isDir, false); + default: assert(false); + } + } + + /// Returns one or more events that describe the change between the last known + /// state of [path] and its current state on the filesystem. + /// + /// This returns a list whose order should be reflected in the events emitted + /// to the user, unlike the batched events from [Directory.watch]. The + /// returned list may be empty, indicating that no changes occurred to [path] + /// (probably indicating that it was created and then immediately deleted). + List _eventsBasedOnFileSystem(String path) { + var fileExisted = _files.contains(path); + var dirExisted = _files.containsDir(path); + var fileExists = new File(path).existsSync(); + var dirExists = new Directory(path).existsSync(); + + var events = []; + if (fileExisted) { + if (fileExists) { + events.add(new ConstructableFileSystemModifyEvent(path, false, false)); + } else { + events.add(new ConstructableFileSystemDeleteEvent(path, false)); + } + } else if (dirExisted) { + if (dirExists) { + // If we got contradictory events for a directory that used to exist and + // still exists, we need to rescan the whole thing in case it was + // replaced with a different directory. + events.add(new ConstructableFileSystemDeleteEvent(path, true)); + events.add(new ConstructableFileSystemCreateEvent(path, true)); + } else { + events.add(new ConstructableFileSystemDeleteEvent(path, true)); + } + } + + if (!fileExisted && fileExists) { + events.add(new ConstructableFileSystemCreateEvent(path, false)); + } else if (!dirExisted && dirExists) { + events.add(new ConstructableFileSystemCreateEvent(path, true)); + } + + return events; + } + + /// The callback that's run when the [Directory.watch] stream is closed. + void _onDone() { + _watchSubscription = null; + + // If the directory still exists and we haven't seen more than one batch, + // this is probably issue 14849 rather than a real close event. We should + // just restart the watcher. + if (batches < 2 && new Directory(directory).existsSync()) { + _startWatch(); + return; + } + + // FSEvents can fail to report the contents of the directory being removed + // when the directory itself is removed, so we need to manually mark the as + // removed. + for (var file in _files.toSet()) { + _emitEvent(ChangeType.REMOVE, file); + } + _files.clear(); + close(); + } + + /// Start or restart the underlying [Directory.watch] stream. + void _startWatch() { + // Batch the FSEvent changes together so that we can dedup events. + var innerStream = new Directory(directory).watch(recursive: true).transform( + new BatchedStreamTransformer()); + _watchSubscription = innerStream.listen(_onBatch, + onError: _eventsController.addError, + onDone: _onDone); + } + + /// Emit an event with the given [type] and [path]. + void _emitEvent(ChangeType type, String path) { + if (!isReady) return; + + // Don't emit ADD events for files that we already know about. Such an event + // probably comes from FSEvents reporting an add that happened prior to the + // watch beginning. + if (type == ChangeType.ADD && _files.contains(path)) return; + + _eventsController.add(new WatchEvent(type, path)); + } + + /// Emit an error, then close the watcher. + void _emitError(error, StackTrace stackTrace) { + _eventsController.add(error, stackTrace); + close(); + } + + /// Like [Stream.listen], but automatically adds the subscription to + /// [_subscriptions] so that it can be canceled when [close] is called. + void _listen(Stream stream, void onData(event), {Function onError, + void onDone(), bool cancelOnError}) { + var subscription; + subscription = stream.listen(onData, onError: onError, onDone: () { + _subscriptions.remove(subscription); + if (onDone != null) onDone(); + }, cancelOnError: cancelOnError); + _subscriptions.add(subscription); + } +} diff --git a/pkgs/watcher/lib/src/path_set.dart b/pkgs/watcher/lib/src/path_set.dart new file mode 100644 index 000000000..01d4208f4 --- /dev/null +++ b/pkgs/watcher/lib/src/path_set.dart @@ -0,0 +1,168 @@ +// 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.path_dart; + +import 'dart:collection'; + +import 'package:path/path.dart' as p; + +/// A set of paths, organized into a directory hierarchy. +/// +/// When a path is [add]ed, it creates an implicit directory structure above +/// that path. Directories can be inspected using [containsDir] and removed +/// using [remove]. If they're removed, their contents are removed as well. +/// +/// The paths in the set are normalized so that they all begin with [root]. +class PathSet { + /// The root path, which all paths in the set must be under. + final String root; + + /// The path set's directory hierarchy. + /// + /// Each level of this hierarchy has the same structure: a map from strings to + /// other maps, which are further levels of the hierarchy. A map with no + /// elements indicates a path that was added to the set that has no paths + /// beneath it. Such a path should not be treated as a directory by + /// [containsDir]. + final _entries = new Map(); + + /// The set of paths that were explicitly added to this set. + /// + /// This is needed to disambiguate a directory that was explicitly added to + /// the set from a directory that was implicitly added by adding a path + /// beneath it. + final _paths = new Set(); + + PathSet(this.root); + + /// Adds [path] to the set. + void add(String path) { + path = _normalize(path); + _paths.add(path); + + var parts = _split(path); + var dir = _entries; + for (var part in parts) { + dir = dir.putIfAbsent(part, () => {}); + } + } + + /// Removes [path] and any paths beneath it from the set and returns the + /// removed paths. + /// + /// Even if [path] itself isn't in the set, if it's a directory containing + /// paths that are in the set those paths will be removed and returned. + /// + /// If neither [path] nor any paths beneath it are in the set, returns an + /// empty set. + Set remove(String path) { + path = _normalize(path); + var parts = new Queue.from(_split(path)); + + // Remove the children of [dir], as well as [dir] itself if necessary. + // + // [partialPath] is the path to [dir], and a prefix of [path]; the remaining + // components of [path] are in [parts]. + recurse(dir, partialPath) { + if (parts.length > 1) { + // If there's more than one component left in [path], recurse down to + // the next level. + var part = parts.removeFirst(); + var entry = dir[part]; + if (entry.isEmpty) return new Set(); + + partialPath = p.join(partialPath, part); + var paths = recurse(entry, partialPath); + // After removing this entry's children, if it has no more children and + // it's not in the set in its own right, remove it as well. + if (entry.isEmpty && !_paths.contains(partialPath)) dir.remove(part); + return paths; + } + + // If there's only one component left in [path], we should remove it. + var entry = dir.remove(parts.first); + if (entry == null) return new Set(); + + if (entry.isEmpty) { + _paths.remove(path); + return new Set.from([path]); + } + + var set = _removePathsIn(entry, path); + if (_paths.contains(path)) { + _paths.remove(path); + set.add(path); + } + return set; + } + + return recurse(_entries, root); + } + + /// Recursively removes and returns all paths in [dir]. + /// + /// [root] should be the path to [dir]. + Set _removePathsIn(Map dir, String root) { + var removedPaths = new Set(); + recurse(dir, path) { + dir.forEach((name, entry) { + var entryPath = p.join(path, name); + if (_paths.remove(entryPath)) removedPaths.add(entryPath); + recurse(entry, entryPath); + }); + } + + recurse(dir, root); + return removedPaths; + } + + /// Returns whether [this] contains [path]. + /// + /// This only returns true for paths explicitly added to [this]. + /// Implicitly-added directories can be inspected using [containsDir]. + bool contains(String path) => _paths.contains(_normalize(path)); + + /// Returns whether [this] contains paths beneath [path]. + bool containsDir(String path) { + path = _normalize(path); + var dir = _entries; + + for (var part in _split(path)) { + dir = dir[part]; + if (dir == null) return false; + } + + return !dir.isEmpty; + } + + /// Returns a [Set] of all paths in [this]. + Set toSet() => _paths.toSet(); + + /// Removes all paths from [this]. + void clear() { + _paths.clear(); + _entries.clear(); + } + + String toString() => _paths.toString(); + + /// Returns a normalized version of [path]. + /// + /// This removes any extra ".." or "."s and ensure that the returned path + /// begins with [root]. It's an error if [path] isn't within [root]. + String _normalize(String path) { + var relative = p.relative(p.normalize(path), from: root); + var parts = p.split(relative); + // TODO(nweiz): replace this with [p.isWithin] when that exists (issue + // 14980). + if (!p.isRelative(relative) || parts.first == '..' || parts.first == '.') { + throw new ArgumentError('Path "$path" is not inside "$root".'); + } + return p.join(root, relative); + } + + /// Returns the segments of [path] beneath [root]. + List _split(String path) => p.split(p.relative(path, from: root)); +} diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index e4e445794..66c0f09cf 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -18,6 +18,10 @@ bool isDirectoryNotFoundException(error) { return error.osError.errorCode == notFoundCode; } +/// Returns the union of all elements in each set in [sets]. +Set unionAll(Iterable sets) => + sets.fold(new Set(), (union, set) => union.union(set)); + /// Returns a buffered stream that will emit the same values as the stream /// returned by [future] once [future] completes. /// diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart new file mode 100644 index 000000000..f7f8176ae --- /dev/null +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -0,0 +1,67 @@ +// 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. + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/src/directory_watcher/mac_os.dart'; +import 'package:watcher/watcher.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +main() { + initConfig(); + + watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); + + setUp(createSandbox); + + sharedTests(); + + test('DirectoryWatcher creates a MacOSDirectoryWatcher on Mac OS', () { + expect(new DirectoryWatcher('.'), + new isInstanceOf()); + }); + + test('does not notify about the watched directory being deleted and ' + 'recreated immediately before watching', () { + createDir("dir"); + writeFile("dir/old.txt"); + deleteDir("dir"); + createDir("dir"); + + startWatcher(dir: "dir"); + writeFile("dir/newer.txt"); + expectAddEvent("dir/newer.txt"); + }); + + test('notifies even if the file contents are unchanged', () { + writeFile("a.txt", contents: "same"); + writeFile("b.txt", contents: "before"); + startWatcher(); + writeFile("a.txt", contents: "same"); + writeFile("b.txt", contents: "after"); + expectModifyEvent("a.txt"); + expectModifyEvent("b.txt"); + }); + + test('emits events for many nested files moved out then immediately back in', + () { + withPermutations((i, j, k) => + writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); + startWatcher(dir: "dir"); + + renameDir("dir/sub", "sub"); + renameDir("sub", "dir/sub"); + + inAnyOrder(() { + withPermutations((i, j, k) => + expectRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); + }); + + inAnyOrder(() { + withPermutations((i, j, k) => + expectAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); + }); + }); +} diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index eed606931..34b5e6284 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -6,6 +6,8 @@ import 'package:scheduled_test/scheduled_test.dart'; import '../utils.dart'; +import 'dart:async'; + sharedTests() { test('does not notify for files that already exist when started', () { // Make some pre-existing files. @@ -218,5 +220,21 @@ sharedTests() { }); }); }); + + test("emits events for many files added at once in a subdirectory with the " + "same name as a removed file", () { + writeFile("dir/sub"); + withPermutations((i, j, k) => + writeFile("old/sub-$i/sub-$j/file-$k.txt")); + startWatcher(dir: "dir"); + + deleteFile("dir/sub"); + renameDir("old", "dir/sub"); + inAnyOrder(() { + expectRemoveEvent("dir/sub"); + withPermutations((i, j, k) => + expectAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); + }); + }); }); } diff --git a/pkgs/watcher/test/no_subscription/mac_os_test.dart b/pkgs/watcher/test/no_subscription/mac_os_test.dart new file mode 100644 index 000000000..3862134d6 --- /dev/null +++ b/pkgs/watcher/test/no_subscription/mac_os_test.dart @@ -0,0 +1,46 @@ +// 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. + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/src/directory_watcher/mac_os.dart'; +import 'package:watcher/watcher.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +// This is currently failing due to issue 14793. The reason is fairly complex: +// +// 1. As part of the test, an "unwatched.txt" file is created while there are no +// active watchers on the containing directory. +// +// 2. A watcher is then added. +// +// 3. The watcher lists the contents of the directory and notices that +// "unwatched.txt" already exists. +// +// 4. Since FSEvents reports past changes (issue 14373), the IO event stream +// emits a CREATED event for "unwatched.txt". +// +// 5. Due to issue 14793, the watcher cannot trust that this is really a CREATED +// event and checks the status of "unwatched.txt" on the filesystem against +// its internal state. +// +// 6. "unwatched.txt" exists on the filesystem and the watcher knows about it +// internally as well. It assumes this means that the file was modified. +// +// 7. The watcher emits an unexpected MODIFIED event for "unwatched.txt", +// causing the test to fail. +// +// Once issue 14793 is fixed, this will no longer be the case and the test will +// work again. + +main() { + initConfig(); + + watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); + + setUp(createSandbox); + + sharedTests(); +} \ No newline at end of file diff --git a/pkgs/watcher/test/path_set_test.dart b/pkgs/watcher/test/path_set_test.dart new file mode 100644 index 000000000..cc8932d78 --- /dev/null +++ b/pkgs/watcher/test/path_set_test.dart @@ -0,0 +1,235 @@ +// 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. + +import 'package:path/path.dart' as p; +import 'package:unittest/unittest.dart'; +import 'package:watcher/src/path_set.dart'; + +import 'utils.dart'; + +Matcher containsPath(String path) => predicate((set) => + set is PathSet && set.contains(path), + 'set contains "$path"'); + +Matcher containsDir(String path) => predicate((set) => + set is PathSet && set.containsDir(path), + 'set contains directory "$path"'); + +void main() { + initConfig(); + + var set; + setUp(() => set = new PathSet("root")); + + group("adding a path", () { + test("stores the path in the set", () { + set.add("root/path/to/file"); + expect(set, containsPath("root/path/to/file")); + }); + + test("that's a subdir of another path keeps both in the set", () { + set.add("root/path"); + set.add("root/path/to/file"); + expect(set, containsPath("root/path")); + expect(set, containsPath("root/path/to/file")); + }); + + test("that's not normalized normalizes the path before storing it", () { + set.add("root/../root/path/to/../to/././file"); + expect(set, containsPath("root/path/to/file")); + }); + + test("that's absolute normalizes the path before storing it", () { + set.add(p.absolute("root/path/to/file")); + expect(set, containsPath("root/path/to/file")); + }); + + test("that's not beneath the root throws an error", () { + expect(() => set.add("path/to/file"), throwsArgumentError); + }); + }); + + group("removing a path", () { + test("that's in the set removes and returns that path", () { + set.add("root/path/to/file"); + expect(set.remove("root/path/to/file"), + unorderedEquals(["root/path/to/file"])); + expect(set, isNot(containsPath("root/path/to/file"))); + }); + + test("that's not in the set returns an empty set", () { + set.add("root/path/to/file"); + expect(set.remove("root/path/to/nothing"), isEmpty); + }); + + test("that's a directory removes and returns all files beneath it", () { + set.add("root/outside"); + set.add("root/path/to/one"); + set.add("root/path/to/two"); + set.add("root/path/to/sub/three"); + + expect(set.remove("root/path"), unorderedEquals([ + "root/path/to/one", + "root/path/to/two", + "root/path/to/sub/three" + ])); + + expect(set, containsPath("root/outside")); + expect(set, isNot(containsPath("root/path/to/one"))); + expect(set, isNot(containsPath("root/path/to/two"))); + expect(set, isNot(containsPath("root/path/to/sub/three"))); + }); + + test("that's a directory in the set removes and returns it and all files " + "beneath it", () { + set.add("root/path"); + set.add("root/path/to/one"); + set.add("root/path/to/two"); + set.add("root/path/to/sub/three"); + + expect(set.remove("root/path"), unorderedEquals([ + "root/path", + "root/path/to/one", + "root/path/to/two", + "root/path/to/sub/three" + ])); + + expect(set, isNot(containsPath("root/path"))); + expect(set, isNot(containsPath("root/path/to/one"))); + expect(set, isNot(containsPath("root/path/to/two"))); + expect(set, isNot(containsPath("root/path/to/sub/three"))); + }); + + test("that's not normalized removes and returns the normalized path", () { + set.add("root/path/to/file"); + expect(set.remove("root/../root/path/to/../to/./file"), + unorderedEquals(["root/path/to/file"])); + }); + + test("that's absolute removes and returns the normalized path", () { + set.add("root/path/to/file"); + expect(set.remove(p.absolute("root/path/to/file")), + unorderedEquals(["root/path/to/file"])); + }); + + test("that's not beneath the root throws an error", () { + expect(() => set.remove("path/to/file"), throwsArgumentError); + }); + }); + + group("containsPath()", () { + test("returns false for a non-existent path", () { + set.add("root/path/to/file"); + expect(set, isNot(containsPath("root/path/to/nothing"))); + }); + + test("returns false for a directory that wasn't added explicitly", () { + set.add("root/path/to/file"); + expect(set, isNot(containsPath("root/path"))); + }); + + test("returns true for a directory that was added explicitly", () { + set.add("root/path"); + set.add("root/path/to/file"); + expect(set, containsPath("root/path")); + }); + + test("with a non-normalized path normalizes the path before looking it up", + () { + set.add("root/path/to/file"); + expect(set, containsPath("root/../root/path/to/../to/././file")); + }); + + test("with an absolute path normalizes the path before looking it up", () { + set.add("root/path/to/file"); + expect(set, containsPath(p.absolute("root/path/to/file"))); + }); + + test("with a path that's not beneath the root throws an error", () { + expect(() => set.contains("path/to/file"), throwsArgumentError); + }); + }); + + group("containsDir()", () { + test("returns true for a directory that was added implicitly", () { + set.add("root/path/to/file"); + expect(set, containsDir("root/path")); + expect(set, containsDir("root/path/to")); + }); + + test("returns true for a directory that was added explicitly", () { + set.add("root/path"); + set.add("root/path/to/file"); + expect(set, containsDir("root/path")); + }); + + test("returns false for a directory that wasn't added", () { + expect(set, isNot(containsDir("root/nothing"))); + }); + + test("returns false for a non-directory path that was added", () { + set.add("root/path/to/file"); + expect(set, isNot(containsDir("root/path/to/file"))); + }); + + test("returns false for a directory that was added implicitly and then " + "removed implicitly", () { + set.add("root/path/to/file"); + set.remove("root/path/to/file"); + expect(set, isNot(containsDir("root/path"))); + }); + + test("returns false for a directory that was added explicitly whose " + "children were then removed", () { + set.add("root/path"); + set.add("root/path/to/file"); + set.remove("root/path/to/file"); + expect(set, isNot(containsDir("root/path"))); + }); + + test("with a non-normalized path normalizes the path before looking it up", + () { + set.add("root/path/to/file"); + expect(set, containsDir("root/../root/path/to/../to/.")); + }); + + test("with an absolute path normalizes the path before looking it up", () { + set.add("root/path/to/file"); + expect(set, containsDir(p.absolute("root/path"))); + }); + }); + + group("toSet", () { + test("returns paths added to the set", () { + set.add("root/path"); + set.add("root/path/to/one"); + set.add("root/path/to/two"); + + expect(set.toSet(), unorderedEquals([ + "root/path", + "root/path/to/one", + "root/path/to/two", + ])); + }); + + test("doesn't return paths removed from the set", () { + set.add("root/path/to/one"); + set.add("root/path/to/two"); + set.remove("root/path/to/two"); + + expect(set.toSet(), unorderedEquals(["root/path/to/one"])); + }); + }); + + group("clear", () { + test("removes all paths from the set", () { + set.add("root/path"); + set.add("root/path/to/one"); + set.add("root/path/to/two"); + + set.clear(); + expect(set.toSet(), isEmpty); + }); + }); +} diff --git a/pkgs/watcher/test/ready/mac_os_test.dart b/pkgs/watcher/test/ready/mac_os_test.dart new file mode 100644 index 000000000..e0275c4ee --- /dev/null +++ b/pkgs/watcher/test/ready/mac_os_test.dart @@ -0,0 +1,20 @@ +// 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. + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/src/directory_watcher/mac_os.dart'; +import 'package:watcher/watcher.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +main() { + initConfig(); + + watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); + + setUp(createSandbox); + + sharedTests(); +} \ No newline at end of file diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 3070d7906..e18c7e813 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -137,6 +137,8 @@ void startWatcher({String dir}) { // people think it might be the root cause. if (currentSchedule.errors.isEmpty) { expect(allEvents, hasLength(numEvents)); + } else { + currentSchedule.addDebugInfo("Events fired:\n${allEvents.join('\n')}"); } }, "reset watcher"); From 1918a1ad6618809b68f349346d6cfac9d2849778 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 11 Nov 2013 21:01:41 +0000 Subject: [PATCH 027/201] Make path_set_test use Windows-friendly paths where necessary. R=rnystrom@google.com BUG= Review URL: https://codereview.chromium.org//68683004 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30171 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/test/path_set_test.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkgs/watcher/test/path_set_test.dart b/pkgs/watcher/test/path_set_test.dart index cc8932d78..dc1f72977 100644 --- a/pkgs/watcher/test/path_set_test.dart +++ b/pkgs/watcher/test/path_set_test.dart @@ -54,7 +54,7 @@ void main() { test("that's in the set removes and returns that path", () { set.add("root/path/to/file"); expect(set.remove("root/path/to/file"), - unorderedEquals(["root/path/to/file"])); + unorderedEquals([p.normalize("root/path/to/file")])); expect(set, isNot(containsPath("root/path/to/file"))); }); @@ -73,7 +73,7 @@ void main() { "root/path/to/one", "root/path/to/two", "root/path/to/sub/three" - ])); + ].map(p.normalize))); expect(set, containsPath("root/outside")); expect(set, isNot(containsPath("root/path/to/one"))); @@ -93,7 +93,7 @@ void main() { "root/path/to/one", "root/path/to/two", "root/path/to/sub/three" - ])); + ].map(p.normalize))); expect(set, isNot(containsPath("root/path"))); expect(set, isNot(containsPath("root/path/to/one"))); @@ -104,13 +104,13 @@ void main() { test("that's not normalized removes and returns the normalized path", () { set.add("root/path/to/file"); expect(set.remove("root/../root/path/to/../to/./file"), - unorderedEquals(["root/path/to/file"])); + unorderedEquals([p.normalize("root/path/to/file")])); }); test("that's absolute removes and returns the normalized path", () { set.add("root/path/to/file"); expect(set.remove(p.absolute("root/path/to/file")), - unorderedEquals(["root/path/to/file"])); + unorderedEquals([p.normalize("root/path/to/file")])); }); test("that's not beneath the root throws an error", () { @@ -210,7 +210,7 @@ void main() { "root/path", "root/path/to/one", "root/path/to/two", - ])); + ].map(p.normalize))); }); test("doesn't return paths removed from the set", () { @@ -218,7 +218,7 @@ void main() { set.add("root/path/to/two"); set.remove("root/path/to/two"); - expect(set.toSet(), unorderedEquals(["root/path/to/one"])); + expect(set.toSet(), unorderedEquals([p.normalizze("root/path/to/one")])); }); }); From 00908271600de1d70cc5aa1062546d1ea6b47d51 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 11 Nov 2013 21:02:06 +0000 Subject: [PATCH 028/201] Fix some analyzer warnings and hints in pkg/watcher. R=rnystrom@google.com BUG= Review URL: https://codereview.chromium.org//69013003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30172 260f80e4-7a28-3924-810f-c04153c831b5 --- .../src/constructable_file_system_event.dart | 2 +- .../lib/src/directory_watcher/linux.dart | 1 - .../lib/src/directory_watcher/mac_os.dart | 4 +--- .../lib/src/directory_watcher/polling.dart | 1 - .../src/directory_watcher/resubscribable.dart | 2 -- .../watcher/test/directory_watcher/shared.dart | 2 -- pkgs/watcher/test/no_subscription/shared.dart | 16 ++++++++++++---- pkgs/watcher/test/utils.dart | 18 +++++++++++------- 8 files changed, 25 insertions(+), 21 deletions(-) diff --git a/pkgs/watcher/lib/src/constructable_file_system_event.dart b/pkgs/watcher/lib/src/constructable_file_system_event.dart index e65e25827..010d2979d 100644 --- a/pkgs/watcher/lib/src/constructable_file_system_event.dart +++ b/pkgs/watcher/lib/src/constructable_file_system_event.dart @@ -9,7 +9,7 @@ import 'dart:io'; abstract class _ConstructableFileSystemEvent implements FileSystemEvent { final bool isDirectory; final String path; - final int type; + int get type; _ConstructableFileSystemEvent(this.path, this.isDirectory); } diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index f0622f646..519607839 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -7,7 +7,6 @@ library watcher.directory_watcher.linux; import 'dart:async'; import 'dart:io'; -import '../directory_watcher.dart'; import '../utils.dart'; import '../watch_event.dart'; import 'resubscribable.dart'; diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 9d3660df7..5b1feb342 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -13,8 +13,6 @@ import '../utils.dart'; import '../watch_event.dart'; import 'resubscribable.dart'; -import 'package:path/path.dart' as p; - /// Uses the FSEvents subsystem to watch for filesystem events. /// /// FSEvents has two main idiosyncrasies that this class works around. First, it @@ -337,7 +335,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// Emit an error, then close the watcher. void _emitError(error, StackTrace stackTrace) { - _eventsController.add(error, stackTrace); + _eventsController.addError(error, stackTrace); close(); } diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 91ca005f3..0e190b02b 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -10,7 +10,6 @@ import 'dart:io'; import 'package:crypto/crypto.dart'; import '../async_queue.dart'; -import '../directory_watcher.dart'; import '../stat.dart'; import '../utils.dart'; import '../watch_event.dart'; diff --git a/pkgs/watcher/lib/src/directory_watcher/resubscribable.dart b/pkgs/watcher/lib/src/directory_watcher/resubscribable.dart index daa813a3e..7d99fc05b 100644 --- a/pkgs/watcher/lib/src/directory_watcher/resubscribable.dart +++ b/pkgs/watcher/lib/src/directory_watcher/resubscribable.dart @@ -5,10 +5,8 @@ library watcher.directory_watcher.resubscribable; import 'dart:async'; -import 'dart:io'; import '../directory_watcher.dart'; -import '../utils.dart'; import '../watch_event.dart'; typedef ManuallyClosedDirectoryWatcher WatcherFactory(); diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index 34b5e6284..fe76a03d3 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -6,8 +6,6 @@ import 'package:scheduled_test/scheduled_test.dart'; import '../utils.dart'; -import 'dart:async'; - sharedTests() { test('does not notify for files that already exist when started', () { // Make some pre-existing files. diff --git a/pkgs/watcher/test/no_subscription/shared.dart b/pkgs/watcher/test/no_subscription/shared.dart index cd279e1ca..6623ba363 100644 --- a/pkgs/watcher/test/no_subscription/shared.dart +++ b/pkgs/watcher/test/no_subscription/shared.dart @@ -19,8 +19,7 @@ sharedTests() { // Subscribe to the events. var completer = new Completer(); var subscription = watcher.events.listen(wrapAsync((event) { - expect(event.type, equals(ChangeType.ADD)); - expect(event.path, endsWith("file.txt")); + expect(event, isWatchEvent(ChangeType.ADD, "file.txt")); completer.complete(); })); @@ -41,10 +40,19 @@ sharedTests() { schedule(() { completer = new Completer(); subscription = watcher.events.listen(wrapAsync((event) { + // TODO(nweiz): Remove this when either issue 14373 or 14793 is fixed. + // Issue 14373 means that the new [Directory.watch] will emit an event + // for "unwatched.txt" being created, and issue 14793 means we have to + // check the filesystem, which leads us to assume that the file has been + // modified. + if (Platform.isMacOS && event.path.endsWith("unwatched.txt")) { + expect(event, isWatchEvent(ChangeType.MODIFY, "unwatched.txt")); + return; + } + // We should get an event for the third file, not the one added while // we weren't subscribed. - expect(event.type, equals(ChangeType.ADD)); - expect(event.path, endsWith("added.txt")); + expect(event, isWatchEvent(ChangeType.ADD, "added.txt")); completer.complete(); })); diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index e18c7e813..651fd6de4 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -176,11 +176,6 @@ void inAnyOrder(block()) { /// Multiple calls to [expectEvent] require that the events are received in that /// order unless they're called in an [inAnyOrder] block. void expectEvent(ChangeType type, String path) { - var matcher = predicate((e) { - return e is WatchEvent && e.type == type && - e.path == p.join(_sandboxDir, p.normalize(path)); - }, "is $type $path"); - if (_unorderedEventFuture != null) { // Assign this to a local variable since it will be un-assigned by the time // the scheduled callback runs. @@ -188,7 +183,7 @@ void expectEvent(ChangeType type, String path) { expect( schedule(() => future, "should fire $type event on $path"), - completion(contains(matcher))); + completion(contains(isWatchEvent(type, path)))); } else { var future = currentSchedule.wrapFuture( _watcherEvents.elementAt(_nextEvent), @@ -196,11 +191,20 @@ void expectEvent(ChangeType type, String path) { expect( schedule(() => future, "should fire $type event on $path"), - completion(matcher)); + completion(isWatchEvent(type, path))); } _nextEvent++; } +/// Returns a matcher that matches a [WatchEvent] with the given [type] and +/// [path]. +Match isWatchEvent(ChangeType type, String path) { + return predicate((e) { + return e is WatchEvent && e.type == type && + e.path == p.join(_sandboxDir, p.normalize(path)); + }, "is $type $path"); +} + void expectAddEvent(String path) => expectEvent(ChangeType.ADD, path); void expectModifyEvent(String path) => expectEvent(ChangeType.MODIFY, path); void expectRemoveEvent(String path) => expectEvent(ChangeType.REMOVE, path); From a05fcc1851ea931461fb49b7d76c73245a89265c Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 11 Nov 2013 21:20:51 +0000 Subject: [PATCH 029/201] Fix a typo in path_set_test. I'd have sworn I fixed this in the orignal CL. R=rnystrom@google.com TBR Review URL: https://codereview.chromium.org//67973003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30173 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/test/path_set_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/test/path_set_test.dart b/pkgs/watcher/test/path_set_test.dart index dc1f72977..f433ad3c3 100644 --- a/pkgs/watcher/test/path_set_test.dart +++ b/pkgs/watcher/test/path_set_test.dart @@ -218,7 +218,7 @@ void main() { set.add("root/path/to/two"); set.remove("root/path/to/two"); - expect(set.toSet(), unorderedEquals([p.normalizze("root/path/to/one")])); + expect(set.toSet(), unorderedEquals([p.normalize("root/path/to/one")])); }); }); From 85894abd806ad30ca2991c8ef51d0cb976a4f554 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 11 Nov 2013 21:26:11 +0000 Subject: [PATCH 030/201] Work around a flake in the Mac OS watcher test on the bots. Issues 14373 and 14793 were tag-teaming to cause no_subscription_test to fail for the Mac OS watcher occasionally. This also marks directory_watcher/mac_os_test as timing out, apparently due to issue 14943. R=rnystrom@google.com BUG= Review URL: https://codereview.chromium.org//59143006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30174 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/test/no_subscription/shared.dart | 1 + pkgs/watcher/test/utils.dart | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/watcher/test/no_subscription/shared.dart b/pkgs/watcher/test/no_subscription/shared.dart index 6623ba363..de625d51d 100644 --- a/pkgs/watcher/test/no_subscription/shared.dart +++ b/pkgs/watcher/test/no_subscription/shared.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/watcher.dart'; diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 651fd6de4..136c8b014 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -198,7 +198,7 @@ void expectEvent(ChangeType type, String path) { /// Returns a matcher that matches a [WatchEvent] with the given [type] and /// [path]. -Match isWatchEvent(ChangeType type, String path) { +Matcher isWatchEvent(ChangeType type, String path) { return predicate((e) { return e is WatchEvent && e.type == type && e.path == p.join(_sandboxDir, p.normalize(path)); From f76c642db49a72e64a1a7cf7661009c638d00658 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 12 Nov 2013 01:42:00 +0000 Subject: [PATCH 031/201] Properly close watcher streams during tests. This also makes [futureStream] pass cancellations through to the underlying stream, and adds the ability for it to return broadcast streams. R=rnystrom@google.com BUG=14943 Review URL: https://codereview.chromium.org//66293008 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30180 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/utils.dart | 43 +++++++++++++++++++++++++++------ pkgs/watcher/test/utils.dart | 2 +- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index 66c0f09cf..a64575b9f 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -27,17 +27,44 @@ Set unionAll(Iterable sets) => /// /// If [future] completes to an error, the return value will emit that error and /// then close. -Stream futureStream(Future future) { - var controller = new StreamController(sync: true); - future.then((stream) { - stream.listen( - controller.add, - onError: controller.addError, - onDone: controller.close); - }).catchError((e, stackTrace) { +/// +/// If [broadcast] is true, a broadcast stream is returned. This assumes that +/// the stream returned by [future] will be a broadcast stream as well. +/// [broadcast] defaults to false. +Stream futureStream(Future future, {bool broadcast: false}) { + var subscription; + var controller; + + future = future.catchError((e, stackTrace) { + if (controller == null) return; controller.addError(e, stackTrace); controller.close(); + controller = null; }); + + onListen() { + future.then((stream) { + if (controller == null) return; + subscription = stream.listen( + controller.add, + onError: controller.addError, + onDone: controller.close); + }); + } + + onCancel() { + if (subscription != null) subscription.cancel(); + subscription = null; + controller = null; + } + + if (broadcast) { + controller = new StreamController.broadcast( + sync: true, onListen: onListen, onCancel: onCancel); + } else { + controller = new StreamController( + sync: true, onListen: onListen, onCancel: onCancel); + } return controller.stream; } diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 136c8b014..567bdb221 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -143,7 +143,7 @@ void startWatcher({String dir}) { }, "reset watcher"); return _watcher.events; - }, "create watcher")).asBroadcastStream(); + }, "create watcher"), broadcast: true); schedule(() => _watcher.ready, "wait for watcher to be ready"); } From 713dcc22febf47ad64dc3b3024dcce5ba61eabc2 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 12 Nov 2013 01:43:38 +0000 Subject: [PATCH 032/201] Add a bunch of debugging prints to the Mac OS watcher. This should help debug the intermittent, difficult-to-reproduce issues on the vm-mac-release bot. R=rnystrom@google.com Review URL: https://codereview.chromium.org//68293005 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30181 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/directory_watcher/mac_os.dart | 80 ++++++++++++++++++- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 5b1feb342..3063e0915 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -7,6 +7,8 @@ library watcher.directory_watcher.mac_os; import 'dart:async'; import 'dart:io'; +import 'package:path/path.dart' as p; + import '../constructable_file_system_event.dart'; import '../path_set.dart'; import '../utils.dart'; @@ -78,7 +80,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (entity is! Directory) _files.add(entity.path); }, onError: _emitError, - onDone: _readyCompleter.complete, + onDone: () { + if (_runningOnBuildbot) { + print("watcher is ready"); + } + _readyCompleter.complete(); + }, cancelOnError: true); } @@ -94,12 +101,38 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// The callback that's run when [Directory.watch] emits a batch of events. void _onBatch(List batch) { + if (_runningOnBuildbot) { + print("======== batch:"); + for (var event in batch) { + print(" ${_formatEvent(event)}"); + } + + print("known files:"); + for (var foo in _files.toSet()) { + print(" ${p.relative(foo, from: directory)}"); + } + } + batches++; _sortEvents(batch).forEach((path, events) { + var relativePath = p.relative(path, from: directory); + if (_runningOnBuildbot) { + print("events for $relativePath:\n"); + for (var event in events) { + print(" ${_formatEvent(event)}"); + } + } + var canonicalEvent = _canonicalEvent(events); events = canonicalEvent == null ? _eventsBasedOnFileSystem(path) : [canonicalEvent]; + if (_runningOnBuildbot) { + print("canonical event for $relativePath: " + "${_formatEvent(canonicalEvent)}"); + print("actionable events for $relativePath: " + "${events.map(_formatEvent)}"); + } for (var event in events) { if (event is FileSystemCreateEvent) { @@ -113,7 +146,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (entity is Directory) return; _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); - }, onError: _emitError, cancelOnError: true); + }, onError: (e, stackTrace) { + if (_runningOnBuildbot) { + print("got error listing $relativePath: $e"); + } + _emitError(e, stackTrace); + }, cancelOnError: true); } else if (event is FileSystemModifyEvent) { assert(!event.isDirectory); _emitEvent(ChangeType.MODIFY, path); @@ -125,6 +163,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } } }); + + if (_runningOnBuildbot) { + print("========"); + } } /// Sort all the events in a batch into sets based on their path. @@ -261,6 +303,13 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { var fileExists = new File(path).existsSync(); var dirExists = new Directory(path).existsSync(); + if (_runningOnBuildbot) { + print("file existed: $fileExisted"); + print("dir existed: $dirExisted"); + print("file exists: $fileExists"); + print("dir exists: $dirExists"); + } + var events = []; if (fileExisted) { if (fileExists) { @@ -330,6 +379,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { // watch beginning. if (type == ChangeType.ADD && _files.contains(path)) return; + if (_runningOnBuildbot) { + print("emitting $type ${p.relative(path, from: directory)}"); + } + _eventsController.add(new WatchEvent(type, path)); } @@ -350,4 +403,27 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { }, cancelOnError: cancelOnError); _subscriptions.add(subscription); } + + /// Return a human-friendly string representation of [event]. + String _formatEvent(FileSystemEvent event) { + if (event == null) return 'null'; + + var path = p.relative(event.path, from: directory); + var type = event.isDirectory ? 'directory' : 'file'; + if (event is FileSystemCreateEvent) { + return "create $type $path"; + } else if (event is FileSystemDeleteEvent) { + return "delete $type $path"; + } else if (event is FileSystemModifyEvent) { + return "modify $type $path"; + } else if (event is FileSystemMoveEvent) { + return "move $type $path to " + "${p.relative(event.destination, from: directory)}"; + } + } + + // TODO(nweiz): remove this when the buildbots calm down. + /// Whether this code is running on a buildbot. + bool get _runningOnBuildbot => + Platform.environment.containsKey('BUILDBOT_BUILDERNAME'); } From 51905f5a941f724af4c9ffa230997e61d7045c54 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 12 Nov 2013 01:56:18 +0000 Subject: [PATCH 033/201] Fix a synchronous event bug in futureStream. R=rnystrom@google.com TBR Review URL: https://codereview.chromium.org//68713004 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30182 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/utils.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index a64575b9f..a235f7da5 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -36,9 +36,10 @@ Stream futureStream(Future future, {bool broadcast: false}) { var controller; future = future.catchError((e, stackTrace) { - if (controller == null) return; - controller.addError(e, stackTrace); - controller.close(); + // Since [controller] is synchronous, it's likely that emitting an error + // will cause it to be cancelled before we call close. + if (controller != null) controller.addError(e, stackTrace); + if (controller != null) controller.close(); controller = null; }); From 782237371f6aaaf02eb69d58643bda969e5d011d Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 12 Nov 2013 02:23:05 +0000 Subject: [PATCH 034/201] Revert "Add a bunch of debugging prints to the Mac OS watcher." This reverts r30181. This CL causes the pub tests to fail due to excess output, so I didn't want to leave it in the build overnight. In addition, it didn't seem to be working; no additional output was being printed in the tests. It's possible BUILDBOT_BUILDERNAME isn't being set on vm-mac-release. This also marks the directory_watcher/mac_os_test as flaky. R=rnystrom@google.com TBR Review URL: https://codereview.chromium.org//68203008 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30184 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/directory_watcher/mac_os.dart | 80 +------------------ 1 file changed, 2 insertions(+), 78 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 3063e0915..5b1feb342 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -7,8 +7,6 @@ library watcher.directory_watcher.mac_os; import 'dart:async'; import 'dart:io'; -import 'package:path/path.dart' as p; - import '../constructable_file_system_event.dart'; import '../path_set.dart'; import '../utils.dart'; @@ -80,12 +78,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (entity is! Directory) _files.add(entity.path); }, onError: _emitError, - onDone: () { - if (_runningOnBuildbot) { - print("watcher is ready"); - } - _readyCompleter.complete(); - }, + onDone: _readyCompleter.complete, cancelOnError: true); } @@ -101,38 +94,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// The callback that's run when [Directory.watch] emits a batch of events. void _onBatch(List batch) { - if (_runningOnBuildbot) { - print("======== batch:"); - for (var event in batch) { - print(" ${_formatEvent(event)}"); - } - - print("known files:"); - for (var foo in _files.toSet()) { - print(" ${p.relative(foo, from: directory)}"); - } - } - batches++; _sortEvents(batch).forEach((path, events) { - var relativePath = p.relative(path, from: directory); - if (_runningOnBuildbot) { - print("events for $relativePath:\n"); - for (var event in events) { - print(" ${_formatEvent(event)}"); - } - } - var canonicalEvent = _canonicalEvent(events); events = canonicalEvent == null ? _eventsBasedOnFileSystem(path) : [canonicalEvent]; - if (_runningOnBuildbot) { - print("canonical event for $relativePath: " - "${_formatEvent(canonicalEvent)}"); - print("actionable events for $relativePath: " - "${events.map(_formatEvent)}"); - } for (var event in events) { if (event is FileSystemCreateEvent) { @@ -146,12 +113,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (entity is Directory) return; _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); - }, onError: (e, stackTrace) { - if (_runningOnBuildbot) { - print("got error listing $relativePath: $e"); - } - _emitError(e, stackTrace); - }, cancelOnError: true); + }, onError: _emitError, cancelOnError: true); } else if (event is FileSystemModifyEvent) { assert(!event.isDirectory); _emitEvent(ChangeType.MODIFY, path); @@ -163,10 +125,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } } }); - - if (_runningOnBuildbot) { - print("========"); - } } /// Sort all the events in a batch into sets based on their path. @@ -303,13 +261,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { var fileExists = new File(path).existsSync(); var dirExists = new Directory(path).existsSync(); - if (_runningOnBuildbot) { - print("file existed: $fileExisted"); - print("dir existed: $dirExisted"); - print("file exists: $fileExists"); - print("dir exists: $dirExists"); - } - var events = []; if (fileExisted) { if (fileExists) { @@ -379,10 +330,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { // watch beginning. if (type == ChangeType.ADD && _files.contains(path)) return; - if (_runningOnBuildbot) { - print("emitting $type ${p.relative(path, from: directory)}"); - } - _eventsController.add(new WatchEvent(type, path)); } @@ -403,27 +350,4 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { }, cancelOnError: cancelOnError); _subscriptions.add(subscription); } - - /// Return a human-friendly string representation of [event]. - String _formatEvent(FileSystemEvent event) { - if (event == null) return 'null'; - - var path = p.relative(event.path, from: directory); - var type = event.isDirectory ? 'directory' : 'file'; - if (event is FileSystemCreateEvent) { - return "create $type $path"; - } else if (event is FileSystemDeleteEvent) { - return "delete $type $path"; - } else if (event is FileSystemModifyEvent) { - return "modify $type $path"; - } else if (event is FileSystemMoveEvent) { - return "move $type $path to " - "${p.relative(event.destination, from: directory)}"; - } - } - - // TODO(nweiz): remove this when the buildbots calm down. - /// Whether this code is running on a buildbot. - bool get _runningOnBuildbot => - Platform.environment.containsKey('BUILDBOT_BUILDERNAME'); } From a3d8591f22613cd27c7cd6b5997845214d4ad01a Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 12 Nov 2013 20:08:41 +0000 Subject: [PATCH 035/201] Add a bunch of debugging prints to the Mac OS watcher. This reverts r30184, which in turn reverted r30181. It correctly detects whether it's running on a buildbot in a way that will work for the vm-mac-release bot. This is a temporary CL intended to help provide information about a test failure that's only reproducing on the build bot. It will be rolled back as soon as sufficient information is gathered. R=rnystrom@google.com BUG= Review URL: https://codereview.chromium.org//69933007 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30205 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/directory_watcher/mac_os.dart | 80 ++++++++++++++++++- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 5b1feb342..cd3a4832e 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -7,6 +7,8 @@ library watcher.directory_watcher.mac_os; import 'dart:async'; import 'dart:io'; +import 'package:path/path.dart' as p; + import '../constructable_file_system_event.dart'; import '../path_set.dart'; import '../utils.dart'; @@ -78,7 +80,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (entity is! Directory) _files.add(entity.path); }, onError: _emitError, - onDone: _readyCompleter.complete, + onDone: () { + if (_runningOnBuildbot) { + print("watcher is ready"); + } + _readyCompleter.complete(); + }, cancelOnError: true); } @@ -94,12 +101,38 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// The callback that's run when [Directory.watch] emits a batch of events. void _onBatch(List batch) { + if (_runningOnBuildbot) { + print("======== batch:"); + for (var event in batch) { + print(" ${_formatEvent(event)}"); + } + + print("known files:"); + for (var foo in _files.toSet()) { + print(" ${p.relative(foo, from: directory)}"); + } + } + batches++; _sortEvents(batch).forEach((path, events) { + var relativePath = p.relative(path, from: directory); + if (_runningOnBuildbot) { + print("events for $relativePath:\n"); + for (var event in events) { + print(" ${_formatEvent(event)}"); + } + } + var canonicalEvent = _canonicalEvent(events); events = canonicalEvent == null ? _eventsBasedOnFileSystem(path) : [canonicalEvent]; + if (_runningOnBuildbot) { + print("canonical event for $relativePath: " + "${_formatEvent(canonicalEvent)}"); + print("actionable events for $relativePath: " + "${events.map(_formatEvent)}"); + } for (var event in events) { if (event is FileSystemCreateEvent) { @@ -113,7 +146,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (entity is Directory) return; _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); - }, onError: _emitError, cancelOnError: true); + }, onError: (e, stackTrace) { + if (_runningOnBuildbot) { + print("got error listing $relativePath: $e"); + } + _emitError(e, stackTrace); + }, cancelOnError: true); } else if (event is FileSystemModifyEvent) { assert(!event.isDirectory); _emitEvent(ChangeType.MODIFY, path); @@ -125,6 +163,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } } }); + + if (_runningOnBuildbot) { + print("========"); + } } /// Sort all the events in a batch into sets based on their path. @@ -261,6 +303,13 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { var fileExists = new File(path).existsSync(); var dirExists = new Directory(path).existsSync(); + if (_runningOnBuildbot) { + print("file existed: $fileExisted"); + print("dir existed: $dirExisted"); + print("file exists: $fileExists"); + print("dir exists: $dirExists"); + } + var events = []; if (fileExisted) { if (fileExists) { @@ -330,6 +379,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { // watch beginning. if (type == ChangeType.ADD && _files.contains(path)) return; + if (_runningOnBuildbot) { + print("emitting $type ${p.relative(path, from: directory)}"); + } + _eventsController.add(new WatchEvent(type, path)); } @@ -350,4 +403,27 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { }, cancelOnError: cancelOnError); _subscriptions.add(subscription); } + + /// Return a human-friendly string representation of [event]. + String _formatEvent(FileSystemEvent event) { + if (event == null) return 'null'; + + var path = p.relative(event.path, from: directory); + var type = event.isDirectory ? 'directory' : 'file'; + if (event is FileSystemCreateEvent) { + return "create $type $path"; + } else if (event is FileSystemDeleteEvent) { + return "delete $type $path"; + } else if (event is FileSystemModifyEvent) { + return "modify $type $path"; + } else if (event is FileSystemMoveEvent) { + return "move $type $path to " + "${p.relative(event.destination, from: directory)}"; + } + } + + // TODO(nweiz): remove this when the buildbots calm down. + /// Whether this code is running on a buildbot. + bool get _runningOnBuildbot => + Platform.environment['LOGNAME'] == 'chrome-bot'; } From 1815318f12f5c2df534e2b90e6c594f4c09b1147 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 12 Nov 2013 22:17:01 +0000 Subject: [PATCH 036/201] Be sure we don't forward an error to a closed event stream. R=rnystrom@google.com BUG=15034 Review URL: https://codereview.chromium.org//68253010 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30210 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/async_queue.dart | 5 ++--- pkgs/watcher/lib/src/directory_watcher/polling.dart | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkgs/watcher/lib/src/async_queue.dart b/pkgs/watcher/lib/src/async_queue.dart index 9456631af..8ac0cdfe1 100644 --- a/pkgs/watcher/lib/src/async_queue.dart +++ b/pkgs/watcher/lib/src/async_queue.dart @@ -8,7 +8,6 @@ import 'dart:async'; import 'dart:collection'; typedef Future ItemProcessor(T item); -typedef void ErrorHandler(error); /// A queue of items that are sequentially, asynchronously processed. /// @@ -35,9 +34,9 @@ class AsyncQueue { /// The handler for errors thrown during processing. /// /// Used to avoid top-leveling asynchronous errors. - final ErrorHandler _errorHandler; + final Function _errorHandler; - AsyncQueue(this._processor, {ErrorHandler onError}) + AsyncQueue(this._processor, {Function onError}) : _errorHandler = onError; /// Enqueues [item] to be processed and starts asynchronously processing it diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 0e190b02b..e50a0c05c 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -72,7 +72,9 @@ class _PollingDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _PollingDirectoryWatcher(this.directory, this._pollingDelay) { _filesToProcess = new AsyncQueue(_processFile, - onError: _events.addError); + onError: (e, stackTrace) { + if (!_events.isClosed) _events.addError(e, stackTrace); + }); _poll(); } From dfb26e9baa64b0ff4b87cad6009adddb12e4bd18 Mon Sep 17 00:00:00 2001 From: "jmesserly@google.com" Date: Wed, 13 Nov 2013 03:03:33 +0000 Subject: [PATCH 037/201] "Reverting 30205" Review URL: https://codereview.chromium.org//69863003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30217 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/directory_watcher/mac_os.dart | 80 +------------------ 1 file changed, 2 insertions(+), 78 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index cd3a4832e..5b1feb342 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -7,8 +7,6 @@ library watcher.directory_watcher.mac_os; import 'dart:async'; import 'dart:io'; -import 'package:path/path.dart' as p; - import '../constructable_file_system_event.dart'; import '../path_set.dart'; import '../utils.dart'; @@ -80,12 +78,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (entity is! Directory) _files.add(entity.path); }, onError: _emitError, - onDone: () { - if (_runningOnBuildbot) { - print("watcher is ready"); - } - _readyCompleter.complete(); - }, + onDone: _readyCompleter.complete, cancelOnError: true); } @@ -101,38 +94,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// The callback that's run when [Directory.watch] emits a batch of events. void _onBatch(List batch) { - if (_runningOnBuildbot) { - print("======== batch:"); - for (var event in batch) { - print(" ${_formatEvent(event)}"); - } - - print("known files:"); - for (var foo in _files.toSet()) { - print(" ${p.relative(foo, from: directory)}"); - } - } - batches++; _sortEvents(batch).forEach((path, events) { - var relativePath = p.relative(path, from: directory); - if (_runningOnBuildbot) { - print("events for $relativePath:\n"); - for (var event in events) { - print(" ${_formatEvent(event)}"); - } - } - var canonicalEvent = _canonicalEvent(events); events = canonicalEvent == null ? _eventsBasedOnFileSystem(path) : [canonicalEvent]; - if (_runningOnBuildbot) { - print("canonical event for $relativePath: " - "${_formatEvent(canonicalEvent)}"); - print("actionable events for $relativePath: " - "${events.map(_formatEvent)}"); - } for (var event in events) { if (event is FileSystemCreateEvent) { @@ -146,12 +113,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (entity is Directory) return; _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); - }, onError: (e, stackTrace) { - if (_runningOnBuildbot) { - print("got error listing $relativePath: $e"); - } - _emitError(e, stackTrace); - }, cancelOnError: true); + }, onError: _emitError, cancelOnError: true); } else if (event is FileSystemModifyEvent) { assert(!event.isDirectory); _emitEvent(ChangeType.MODIFY, path); @@ -163,10 +125,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } } }); - - if (_runningOnBuildbot) { - print("========"); - } } /// Sort all the events in a batch into sets based on their path. @@ -303,13 +261,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { var fileExists = new File(path).existsSync(); var dirExists = new Directory(path).existsSync(); - if (_runningOnBuildbot) { - print("file existed: $fileExisted"); - print("dir existed: $dirExisted"); - print("file exists: $fileExists"); - print("dir exists: $dirExists"); - } - var events = []; if (fileExisted) { if (fileExists) { @@ -379,10 +330,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { // watch beginning. if (type == ChangeType.ADD && _files.contains(path)) return; - if (_runningOnBuildbot) { - print("emitting $type ${p.relative(path, from: directory)}"); - } - _eventsController.add(new WatchEvent(type, path)); } @@ -403,27 +350,4 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { }, cancelOnError: cancelOnError); _subscriptions.add(subscription); } - - /// Return a human-friendly string representation of [event]. - String _formatEvent(FileSystemEvent event) { - if (event == null) return 'null'; - - var path = p.relative(event.path, from: directory); - var type = event.isDirectory ? 'directory' : 'file'; - if (event is FileSystemCreateEvent) { - return "create $type $path"; - } else if (event is FileSystemDeleteEvent) { - return "delete $type $path"; - } else if (event is FileSystemModifyEvent) { - return "modify $type $path"; - } else if (event is FileSystemMoveEvent) { - return "move $type $path to " - "${p.relative(event.destination, from: directory)}"; - } - } - - // TODO(nweiz): remove this when the buildbots calm down. - /// Whether this code is running on a buildbot. - bool get _runningOnBuildbot => - Platform.environment['LOGNAME'] == 'chrome-bot'; } From e3b44e1a36092b5e1796111abd34b5629513b4d2 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Wed, 13 Nov 2013 21:56:52 +0000 Subject: [PATCH 038/201] Roll forward r30205. Unlike previous versions of this CL, the debug info will only be printed when running directory_watcher/mac_os_test, so it won't break the pub bots. R=rnystrom@google.com BUG=15042 Review URL: https://codereview.chromium.org//71353011 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30254 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/directory_watcher/mac_os.dart | 79 ++++++++++++++++++- .../test/directory_watcher/mac_os_test.dart | 1 + 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 5b1feb342..09fdd66d9 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -7,6 +7,8 @@ library watcher.directory_watcher.mac_os; import 'dart:async'; import 'dart:io'; +import 'package:path/path.dart' as p; + import '../constructable_file_system_event.dart'; import '../path_set.dart'; import '../utils.dart'; @@ -24,6 +26,9 @@ import 'resubscribable.dart'; /// This also works around issues 14793, 14806, and 14849 in the implementation /// of [Directory.watch]. class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher { + // TODO(nweiz): remove this when issue 15042 is fixed. + static bool logDebugInfo = false; + MacOSDirectoryWatcher(String directory) : super(directory, () => new _MacOSDirectoryWatcher(directory)); } @@ -78,7 +83,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (entity is! Directory) _files.add(entity.path); }, onError: _emitError, - onDone: _readyCompleter.complete, + onDone: () { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("watcher is ready"); + } + _readyCompleter.complete(); + }, cancelOnError: true); } @@ -94,12 +104,38 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// The callback that's run when [Directory.watch] emits a batch of events. void _onBatch(List batch) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("======== batch:"); + for (var event in batch) { + print(" ${_formatEvent(event)}"); + } + + print("known files:"); + for (var foo in _files.toSet()) { + print(" ${p.relative(foo, from: directory)}"); + } + } + batches++; _sortEvents(batch).forEach((path, events) { + var relativePath = p.relative(path, from: directory); + if (MacOSDirectoryWatcher.logDebugInfo) { + print("events for $relativePath:\n"); + for (var event in events) { + print(" ${_formatEvent(event)}"); + } + } + var canonicalEvent = _canonicalEvent(events); events = canonicalEvent == null ? _eventsBasedOnFileSystem(path) : [canonicalEvent]; + if (MacOSDirectoryWatcher.logDebugInfo) { + print("canonical event for $relativePath: " + "${_formatEvent(canonicalEvent)}"); + print("actionable events for $relativePath: " + "${events.map(_formatEvent)}"); + } for (var event in events) { if (event is FileSystemCreateEvent) { @@ -113,7 +149,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (entity is Directory) return; _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); - }, onError: _emitError, cancelOnError: true); + }, onError: (e, stackTrace) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("got error listing $relativePath: $e"); + } + _emitError(e, stackTrace); + }, cancelOnError: true); } else if (event is FileSystemModifyEvent) { assert(!event.isDirectory); _emitEvent(ChangeType.MODIFY, path); @@ -125,6 +166,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } } }); + + if (MacOSDirectoryWatcher.logDebugInfo) { + print("========"); + } } /// Sort all the events in a batch into sets based on their path. @@ -261,6 +306,13 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { var fileExists = new File(path).existsSync(); var dirExists = new Directory(path).existsSync(); + if (MacOSDirectoryWatcher.logDebugInfo) { + print("file existed: $fileExisted"); + print("dir existed: $dirExisted"); + print("file exists: $fileExists"); + print("dir exists: $dirExists"); + } + var events = []; if (fileExisted) { if (fileExists) { @@ -330,6 +382,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { // watch beginning. if (type == ChangeType.ADD && _files.contains(path)) return; + if (MacOSDirectoryWatcher.logDebugInfo) { + print("emitting $type ${p.relative(path, from: directory)}"); + } + _eventsController.add(new WatchEvent(type, path)); } @@ -350,4 +406,23 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { }, cancelOnError: cancelOnError); _subscriptions.add(subscription); } + + // TODO(nweiz): remove this when issue 15042 is fixed. + /// Return a human-friendly string representation of [event]. + String _formatEvent(FileSystemEvent event) { + if (event == null) return 'null'; + + var path = p.relative(event.path, from: directory); + var type = event.isDirectory ? 'directory' : 'file'; + if (event is FileSystemCreateEvent) { + return "create $type $path"; + } else if (event is FileSystemDeleteEvent) { + return "delete $type $path"; + } else if (event is FileSystemModifyEvent) { + return "modify $type $path"; + } else if (event is FileSystemMoveEvent) { + return "move $type $path to " + "${p.relative(event.destination, from: directory)}"; + } + } } diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index f7f8176ae..78ff88985 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -11,6 +11,7 @@ import '../utils.dart'; main() { initConfig(); + MacOSDirectoryWatcher.logDebugInfo = true; watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); From f7f7051739f032b8a0385fcce0805aaf9ba54fea Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Fri, 15 Nov 2013 21:43:50 +0000 Subject: [PATCH 039/201] Re-mark directory_watcher/mac_os_test as passing. As before, this test is expected to fail. It's only failing on the bots so we need to get the debug information from the bots to figure out how to fix it. This also adds some additional debug information to help clarify some confusing output from the most recent failing log. R=rnystrom@google.com BUG=15024 Review URL: https://codereview.chromium.org//64383004 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30315 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/directory_watcher/mac_os.dart | 58 ++++++++++++------- pkgs/watcher/test/utils.dart | 12 ++++ 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 09fdd66d9..e7b97e46c 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -26,14 +26,21 @@ import 'resubscribable.dart'; /// This also works around issues 14793, 14806, and 14849 in the implementation /// of [Directory.watch]. class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher { - // TODO(nweiz): remove this when issue 15042 is fixed. - static bool logDebugInfo = false; + // TODO(nweiz): remove these when issue 15042 is fixed. + static var logDebugInfo = false; + static var _count = 0; + final int _id; MacOSDirectoryWatcher(String directory) - : super(directory, () => new _MacOSDirectoryWatcher(directory)); + : _id = _count++, + super(directory, () => new _MacOSDirectoryWatcher(directory, _count)); } class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { + // TODO(nweiz): remove these when issue 15042 is fixed. + static var _count = 0; + final String _id; + final String directory; Stream get events => _eventsController.stream; @@ -73,9 +80,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// watcher is closed. This does not include [_watchSubscription]. final _subscriptions = new Set(); - _MacOSDirectoryWatcher(String directory) + _MacOSDirectoryWatcher(String directory, int parentId) : directory = directory, - _files = new PathSet(directory) { + _files = new PathSet(directory), + _id = "$parentId/${_count++}" { _startWatch(); _listen(new Directory(directory).list(recursive: true), @@ -85,7 +93,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { onError: _emitError, onDone: () { if (MacOSDirectoryWatcher.logDebugInfo) { - print("watcher is ready"); + print("[$_id] watcher is ready, known files:"); + for (var file in _files.toSet()) { + print("[$_id] ${p.relative(file, from: directory)}"); + } } _readyCompleter.complete(); }, @@ -93,6 +104,9 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } void close() { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] watcher is closed"); + } for (var subscription in _subscriptions) { subscription.cancel(); } @@ -105,14 +119,14 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// The callback that's run when [Directory.watch] emits a batch of events. void _onBatch(List batch) { if (MacOSDirectoryWatcher.logDebugInfo) { - print("======== batch:"); + print("[$_id] ======== batch:"); for (var event in batch) { - print(" ${_formatEvent(event)}"); + print("[$_id] ${_formatEvent(event)}"); } - print("known files:"); - for (var foo in _files.toSet()) { - print(" ${p.relative(foo, from: directory)}"); + print("[$_id] known files:"); + for (var file in _files.toSet()) { + print("[$_id] ${p.relative(file, from: directory)}"); } } @@ -121,9 +135,9 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _sortEvents(batch).forEach((path, events) { var relativePath = p.relative(path, from: directory); if (MacOSDirectoryWatcher.logDebugInfo) { - print("events for $relativePath:\n"); + print("[$_id] events for $relativePath:\n"); for (var event in events) { - print(" ${_formatEvent(event)}"); + print("[$_id] ${_formatEvent(event)}"); } } @@ -131,9 +145,9 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { events = canonicalEvent == null ? _eventsBasedOnFileSystem(path) : [canonicalEvent]; if (MacOSDirectoryWatcher.logDebugInfo) { - print("canonical event for $relativePath: " + print("[$_id] canonical event for $relativePath: " "${_formatEvent(canonicalEvent)}"); - print("actionable events for $relativePath: " + print("[$_id] actionable events for $relativePath: " "${events.map(_formatEvent)}"); } @@ -151,7 +165,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _files.add(entity.path); }, onError: (e, stackTrace) { if (MacOSDirectoryWatcher.logDebugInfo) { - print("got error listing $relativePath: $e"); + print("[$_id] got error listing $relativePath: $e"); } _emitError(e, stackTrace); }, cancelOnError: true); @@ -168,7 +182,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { }); if (MacOSDirectoryWatcher.logDebugInfo) { - print("========"); + print("[$_id] ======== batch complete"); } } @@ -307,10 +321,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { var dirExists = new Directory(path).existsSync(); if (MacOSDirectoryWatcher.logDebugInfo) { - print("file existed: $fileExisted"); - print("dir existed: $dirExisted"); - print("file exists: $fileExists"); - print("dir exists: $dirExists"); + print("[$_id] file existed: $fileExisted"); + print("[$_id] dir existed: $dirExisted"); + print("[$_id] file exists: $fileExists"); + print("[$_id] dir exists: $dirExists"); } var events = []; @@ -383,7 +397,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (type == ChangeType.ADD && _files.contains(path)) return; if (MacOSDirectoryWatcher.logDebugInfo) { - print("emitting $type ${p.relative(path, from: directory)}"); + print("[$_id] emitting $type ${p.relative(path, from: directory)}"); } _eventsController.add(new WatchEvent(type, path)); diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 567bdb221..a7bd9b636 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -15,6 +15,9 @@ import 'package:watcher/watcher.dart'; import 'package:watcher/src/stat.dart'; import 'package:watcher/src/utils.dart'; +// TODO(nweiz): remove this when issue 15042 is fixed. +import 'package:watcher/src/directory_watcher/mac_os.dart'; + /// The path to the temporary sandbox created for each test. All file /// operations are implicitly relative to this directory. String _sandboxDir; @@ -115,6 +118,11 @@ Stream _watcherEvents; /// /// If [dir] is provided, watches a subdirectory in the sandbox with that name. void startWatcher({String dir}) { + var testCase = currentTestCase.description; + if (MacOSDirectoryWatcher.logDebugInfo) { + print("starting watcher for $testCase"); + } + // We want to wait until we're ready *after* we subscribe to the watcher's // events. _watcher = createWatcher(dir: dir, waitForReady: false); @@ -128,6 +136,10 @@ void startWatcher({String dir}) { onError: currentSchedule.signalError); currentSchedule.onComplete.schedule(() { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("stopping watcher for $testCase"); + } + var numEvents = _nextEvent; subscription.cancel(); _nextEvent = 0; From 31241bc2e72dc2396552cf993581e9d905f940e1 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Sat, 16 Nov 2013 00:54:50 +0000 Subject: [PATCH 040/201] Fix the Mac OS directory watcher tests on the bots. R=rnystrom@google.com BUG=15024 Review URL: https://codereview.chromium.org//58903015 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30328 260f80e4-7a28-3924-810f-c04153c831b5 --- .../test/directory_watcher/mac_os_test.dart | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 78ff88985..b6a65e6e2 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -50,6 +50,29 @@ main() { () { withPermutations((i, j, k) => writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); + + // We sleep here because a narrow edge case caused by two interacting bugs + // can produce events that aren't expected if we start the watcher too + // soon after creating the files above. Here's what happens: + // + // * We create "dir/sub" and its contents above. + // + // * We initialize the watcher watching "dir". + // + // * Due to issue 14373, the watcher can receive native events describing + // the creation of "dir/sub" and its contents despite the fact that they + // occurred before the watcher was started. + // + // * Usually the above events will occur while the watcher is doing its + // initial scan of "dir/sub" and be ignored, but occasionally they will + // occur afterwards. + // + // * When processing the bogus CREATE events, the watcher has to assume that + // they could mean something other than CREATE (issue 14793). Thus it + // assumes that the files or directories in question could have changed + // and emits CHANGE events or additional REMOVE/CREATE pairs for them. + schedule(() => new Future.delayed(new Duration(seconds: 2))); + startWatcher(dir: "dir"); renameDir("dir/sub", "sub"); From 84caa2ac2782d30ab2818415f7662bd8492ad3e0 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Sat, 16 Nov 2013 01:05:45 +0000 Subject: [PATCH 041/201] Remove debugging prints from the Mac OS directory watcher. R=rnystrom@google.com BUG=15024 Review URL: https://codereview.chromium.org//63643008 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30329 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/directory_watcher/mac_os.dart | 99 +------------------ .../test/directory_watcher/mac_os_test.dart | 1 - pkgs/watcher/test/utils.dart | 12 --- 3 files changed, 5 insertions(+), 107 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index e7b97e46c..5b1feb342 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -7,8 +7,6 @@ library watcher.directory_watcher.mac_os; import 'dart:async'; import 'dart:io'; -import 'package:path/path.dart' as p; - import '../constructable_file_system_event.dart'; import '../path_set.dart'; import '../utils.dart'; @@ -26,21 +24,11 @@ import 'resubscribable.dart'; /// This also works around issues 14793, 14806, and 14849 in the implementation /// of [Directory.watch]. class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher { - // TODO(nweiz): remove these when issue 15042 is fixed. - static var logDebugInfo = false; - static var _count = 0; - final int _id; - MacOSDirectoryWatcher(String directory) - : _id = _count++, - super(directory, () => new _MacOSDirectoryWatcher(directory, _count)); + : super(directory, () => new _MacOSDirectoryWatcher(directory)); } class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { - // TODO(nweiz): remove these when issue 15042 is fixed. - static var _count = 0; - final String _id; - final String directory; Stream get events => _eventsController.stream; @@ -80,10 +68,9 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// watcher is closed. This does not include [_watchSubscription]. final _subscriptions = new Set(); - _MacOSDirectoryWatcher(String directory, int parentId) + _MacOSDirectoryWatcher(String directory) : directory = directory, - _files = new PathSet(directory), - _id = "$parentId/${_count++}" { + _files = new PathSet(directory) { _startWatch(); _listen(new Directory(directory).list(recursive: true), @@ -91,22 +78,11 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (entity is! Directory) _files.add(entity.path); }, onError: _emitError, - onDone: () { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] watcher is ready, known files:"); - for (var file in _files.toSet()) { - print("[$_id] ${p.relative(file, from: directory)}"); - } - } - _readyCompleter.complete(); - }, + onDone: _readyCompleter.complete, cancelOnError: true); } void close() { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] watcher is closed"); - } for (var subscription in _subscriptions) { subscription.cancel(); } @@ -118,38 +94,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// The callback that's run when [Directory.watch] emits a batch of events. void _onBatch(List batch) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] ======== batch:"); - for (var event in batch) { - print("[$_id] ${_formatEvent(event)}"); - } - - print("[$_id] known files:"); - for (var file in _files.toSet()) { - print("[$_id] ${p.relative(file, from: directory)}"); - } - } - batches++; _sortEvents(batch).forEach((path, events) { - var relativePath = p.relative(path, from: directory); - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] events for $relativePath:\n"); - for (var event in events) { - print("[$_id] ${_formatEvent(event)}"); - } - } - var canonicalEvent = _canonicalEvent(events); events = canonicalEvent == null ? _eventsBasedOnFileSystem(path) : [canonicalEvent]; - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] canonical event for $relativePath: " - "${_formatEvent(canonicalEvent)}"); - print("[$_id] actionable events for $relativePath: " - "${events.map(_formatEvent)}"); - } for (var event in events) { if (event is FileSystemCreateEvent) { @@ -163,12 +113,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (entity is Directory) return; _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); - }, onError: (e, stackTrace) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] got error listing $relativePath: $e"); - } - _emitError(e, stackTrace); - }, cancelOnError: true); + }, onError: _emitError, cancelOnError: true); } else if (event is FileSystemModifyEvent) { assert(!event.isDirectory); _emitEvent(ChangeType.MODIFY, path); @@ -180,10 +125,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } } }); - - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] ======== batch complete"); - } } /// Sort all the events in a batch into sets based on their path. @@ -320,13 +261,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { var fileExists = new File(path).existsSync(); var dirExists = new Directory(path).existsSync(); - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] file existed: $fileExisted"); - print("[$_id] dir existed: $dirExisted"); - print("[$_id] file exists: $fileExists"); - print("[$_id] dir exists: $dirExists"); - } - var events = []; if (fileExisted) { if (fileExists) { @@ -396,10 +330,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { // watch beginning. if (type == ChangeType.ADD && _files.contains(path)) return; - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] emitting $type ${p.relative(path, from: directory)}"); - } - _eventsController.add(new WatchEvent(type, path)); } @@ -420,23 +350,4 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { }, cancelOnError: cancelOnError); _subscriptions.add(subscription); } - - // TODO(nweiz): remove this when issue 15042 is fixed. - /// Return a human-friendly string representation of [event]. - String _formatEvent(FileSystemEvent event) { - if (event == null) return 'null'; - - var path = p.relative(event.path, from: directory); - var type = event.isDirectory ? 'directory' : 'file'; - if (event is FileSystemCreateEvent) { - return "create $type $path"; - } else if (event is FileSystemDeleteEvent) { - return "delete $type $path"; - } else if (event is FileSystemModifyEvent) { - return "modify $type $path"; - } else if (event is FileSystemMoveEvent) { - return "move $type $path to " - "${p.relative(event.destination, from: directory)}"; - } - } } diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index b6a65e6e2..8a30e27e6 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -11,7 +11,6 @@ import '../utils.dart'; main() { initConfig(); - MacOSDirectoryWatcher.logDebugInfo = true; watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index a7bd9b636..567bdb221 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -15,9 +15,6 @@ import 'package:watcher/watcher.dart'; import 'package:watcher/src/stat.dart'; import 'package:watcher/src/utils.dart'; -// TODO(nweiz): remove this when issue 15042 is fixed. -import 'package:watcher/src/directory_watcher/mac_os.dart'; - /// The path to the temporary sandbox created for each test. All file /// operations are implicitly relative to this directory. String _sandboxDir; @@ -118,11 +115,6 @@ Stream _watcherEvents; /// /// If [dir] is provided, watches a subdirectory in the sandbox with that name. void startWatcher({String dir}) { - var testCase = currentTestCase.description; - if (MacOSDirectoryWatcher.logDebugInfo) { - print("starting watcher for $testCase"); - } - // We want to wait until we're ready *after* we subscribe to the watcher's // events. _watcher = createWatcher(dir: dir, waitForReady: false); @@ -136,10 +128,6 @@ void startWatcher({String dir}) { onError: currentSchedule.signalError); currentSchedule.onComplete.schedule(() { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("stopping watcher for $testCase"); - } - var numEvents = _nextEvent; subscription.cancel(); _nextEvent = 0; From 3451b4bd7e61bb7df64dca91eb09bb34743198f5 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Sat, 16 Nov 2013 01:10:23 +0000 Subject: [PATCH 042/201] Add a missing import to mac_os_watcher. TBR Review URL: https://codereview.chromium.org//68523020 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30330 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/test/directory_watcher/mac_os_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 8a30e27e6..7d31221de 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -2,6 +2,8 @@ // 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. +import 'dart:async'; + import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/directory_watcher/mac_os.dart'; import 'package:watcher/watcher.dart'; From 778821e1faac76d67b9df7e00c6e79652a7f5ad7 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Wed, 27 Nov 2013 01:16:56 +0000 Subject: [PATCH 043/201] Properly close the Linux directory watcher when listing a subdirectory fails. R=rnystrom@google.com BUG=15279 Review URL: https://codereview.chromium.org//89503002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30690 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/directory_watcher/linux.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index 519607839..d62c6ef10 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -166,7 +166,7 @@ class _LinuxDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (error is FileSystemException) return; _eventsController.addError(error, stackTrace); - _eventsController.close(); + close(); }, cancelOnError: true); }); } From cb64c9e76d8e2d1219471c0091c6f19adf01182b Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Wed, 4 Dec 2013 22:31:18 +0000 Subject: [PATCH 044/201] Update the Linux watcher to accommodate changes in [Directory.watch]. This also removes the extra time allotted for the test since issue 14606 is now mitigated. R=rnystrom@google.com BUG=15446 Review URL: https://codereview.chromium.org//99483006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30874 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/directory_watcher/linux.dart | 13 ++++++++++++- pkgs/watcher/test/directory_watcher/linux_test.dart | 9 +-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index d62c6ef10..5e86a4344 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -213,7 +213,7 @@ class _LinuxDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (oldState == _EntryState.DIRECTORY) { var watcher = _subWatchers.remove(path); - if (watcher == null) return; + if (watcher == null) continue; for (var path in watcher._allFiles) { _eventsController.add(new WatchEvent(ChangeType.REMOVE, path)); } @@ -247,6 +247,17 @@ class _LinuxDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// Handles the underlying event stream closing, indicating that the directory /// being watched was removed. void _onDone() { + // Most of the time when a directory is removed, its contents will get + // individual REMOVE events before the watch stream is closed -- in that + // case, [_entries] will be empty here. However, if the directory's removal + // is caused by a MOVE, we need to manually emit events. + if (isReady) { + _entries.forEach((path, state) { + if (state == _EntryState.DIRECTORY) return; + _eventsController.add(new WatchEvent(ChangeType.REMOVE, path)); + }); + } + // The parent directory often gets a close event before the subdirectories // are done emitting events. We wait for them to finish before we close // [events] so that we can be sure to emit a remove event for every file diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart index ba695698b..f53b272b4 100644 --- a/pkgs/watcher/test/directory_watcher/linux_test.dart +++ b/pkgs/watcher/test/directory_watcher/linux_test.dart @@ -14,14 +14,7 @@ main() { watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); - setUp(() { - // Increase the timeout because closing a [Directory.watch] stream blocks - // the main isolate for a very long time on Goobuntu, as of kernel - // 3.2.5-gg1336 (see issue 14606). - currentSchedule.timeout *= 3; - - createSandbox(); - }); + setUp(createSandbox); sharedTests(); From 91f82aa4facf3a2b02f6185a02d32505163d0c62 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Thu, 5 Dec 2013 19:31:19 +0000 Subject: [PATCH 045/201] Add stack chain support to pkg/watcher and pkg/http. R=rnystrom@google.com BUG= Review URL: https://codereview.chromium.org//94093007 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30912 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/directory_watcher/linux.dart | 10 ++++++---- pkgs/watcher/lib/src/directory_watcher/mac_os.dart | 10 ++++++---- pkgs/watcher/lib/src/directory_watcher/polling.dart | 5 +++-- pkgs/watcher/lib/src/stat.dart | 4 +++- pkgs/watcher/pubspec.yaml | 1 + 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index 5e86a4344..76558dd27 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -7,6 +7,8 @@ library watcher.directory_watcher.linux; import 'dart:async'; import 'dart:io'; +import 'package:stack_trace/stack_trace.dart'; + import '../utils.dart'; import '../watch_event.dart'; import 'resubscribable.dart'; @@ -56,13 +58,13 @@ class _LinuxDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _LinuxDirectoryWatcher(String directory) : directory = directory { // Batch the inotify changes together so that we can dedup events. - var innerStream = new Directory(directory).watch().transform( - new BatchedStreamTransformer()); + var innerStream = Chain.track(new Directory(directory).watch()) + .transform(new BatchedStreamTransformer()); _listen(innerStream, _onBatch, onError: _eventsController.addError, onDone: _onDone); - _listen(new Directory(directory).list(), (entity) { + _listen(Chain.track(new Directory(directory).list()), (entity) { _entries[entity.path] = new _EntryState(entity is Directory); if (entity is! Directory) return; _watchSubdir(entity.path); @@ -157,7 +159,7 @@ class _LinuxDirectoryWatcher implements ManuallyClosedDirectoryWatcher { // event for every new file. watcher.ready.then((_) { if (!isReady || _eventsController.isClosed) return; - _listen(new Directory(path).list(recursive: true), (entry) { + _listen(Chain.track(new Directory(path).list(recursive: true)), (entry) { if (entry is Directory) return; _eventsController.add(new WatchEvent(ChangeType.ADD, entry.path)); }, onError: (error, stackTrace) { diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 5b1feb342..16de1d93f 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -73,7 +73,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _files = new PathSet(directory) { _startWatch(); - _listen(new Directory(directory).list(recursive: true), + _listen(Chain.track(new Directory(directory).list(recursive: true)), (entity) { if (entity is! Directory) _files.add(entity.path); }, @@ -109,7 +109,8 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { continue; } - _listen(new Directory(path).list(recursive: true), (entity) { + _listen(Chain.track(new Directory(path).list(recursive: true)), + (entity) { if (entity is Directory) return; _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); @@ -314,8 +315,9 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// Start or restart the underlying [Directory.watch] stream. void _startWatch() { // Batch the FSEvent changes together so that we can dedup events. - var innerStream = new Directory(directory).watch(recursive: true).transform( - new BatchedStreamTransformer()); + var innerStream = + Chain.track(new Directory(directory).watch(recursive: true)) + .transform(new BatchedStreamTransformer()); _watchSubscription = innerStream.listen(_onBatch, onError: _eventsController.addError, onDone: _onDone); diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index e50a0c05c..684f7aad8 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -8,6 +8,7 @@ import 'dart:async'; import 'dart:io'; import 'package:crypto/crypto.dart'; +import 'package:stack_trace/stack_trace.dart'; import '../async_queue.dart'; import '../stat.dart'; @@ -105,7 +106,7 @@ class _PollingDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _filesToProcess.add(null); } - var stream = new Directory(directory).list(recursive: true); + var stream = Chain.track(new Directory(directory).list(recursive: true)); _listSubscription = stream.listen((entity) { assert(!_events.isClosed); @@ -185,7 +186,7 @@ class _PollingDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// Calculates the SHA-1 hash of the file at [path]. Future> _hashFile(String path) { - return new File(path).readAsBytes().then((bytes) { + return Chain.track(new File(path).readAsBytes()).then((bytes) { var sha1 = new SHA1(); sha1.add(bytes); return sha1.close(); diff --git a/pkgs/watcher/lib/src/stat.dart b/pkgs/watcher/lib/src/stat.dart index d36eff3bd..166d78988 100644 --- a/pkgs/watcher/lib/src/stat.dart +++ b/pkgs/watcher/lib/src/stat.dart @@ -7,6 +7,8 @@ library watcher.stat; import 'dart:async'; import 'dart:io'; +import 'package:stack_trace/stack_trace.dart'; + /// A function that takes a file path and returns the last modified time for /// the file at that path. typedef DateTime MockTimeCallback(String path); @@ -29,5 +31,5 @@ Future getModificationTime(String path) { return new Future.value(_mockTimeCallback(path)); } - return FileStat.stat(path).then((stat) => stat.modified); + return Chain.track(FileStat.stat(path)).then((stat) => stat.modified); } diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index b95c7efdf..93d8eb48c 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -9,6 +9,7 @@ description: > dependencies: crypto: ">=0.9.0 <0.10.0" path: ">=0.9.0 <0.10.0" + stack_trace: ">=0.9.0 <0.10.0" dev_dependencies: scheduled_test: ">=0.9.0 <0.10.0" unittest: ">=0.9.0 <0.10.0" From d222b70ac1c0294f3171304a1e4b2a43977df565 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Thu, 5 Dec 2013 20:24:10 +0000 Subject: [PATCH 046/201] Add a missing import to pkg/watcher. R=rnystrom@google.com BUG= Review URL: https://codereview.chromium.org//104033003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30914 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/directory_watcher/mac_os.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 16de1d93f..c3e03b852 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -7,6 +7,8 @@ library watcher.directory_watcher.mac_os; import 'dart:async'; import 'dart:io'; +import 'package:stack_trace/stack_trace.dart'; + import '../constructable_file_system_event.dart'; import '../path_set.dart'; import '../utils.dart'; From dc5efb8cce969e118008b31d372b1ac414d35aeb Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Thu, 5 Dec 2013 20:26:18 +0000 Subject: [PATCH 047/201] Update the Mac OS watcher in pkg/watcher. The behavior of Directory.watch has changed, and this updates the watcher to match the new behavior. R=rnystrom@google.com BUG= Review URL: https://codereview.chromium.org//100823005 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30915 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/directory_watcher/mac_os.dart | 68 +++++++++---------- .../test/directory_watcher/mac_os_test.dart | 24 ------- .../test/no_subscription/mac_os_test.dart | 26 ------- pkgs/watcher/test/no_subscription/shared.dart | 10 --- 4 files changed, 31 insertions(+), 97 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index c3e03b852..3e68cd936 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -23,8 +23,8 @@ import 'resubscribable.dart'; /// succession, it won't report them in the order they occurred. See issue /// 14373. /// -/// This also works around issues 14793, 14806, and 14849 in the implementation -/// of [Directory.watch]. +/// This also works around issues 15458 and 14849 in the implementation of +/// [Directory.watch]. class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher { MacOSDirectoryWatcher(String directory) : super(directory, () => new _MacOSDirectoryWatcher(directory)); @@ -106,11 +106,18 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { for (var event in events) { if (event is FileSystemCreateEvent) { if (!event.isDirectory) { + // Don't emit ADD events for files or directories that we already + // know about. Such an event comes from FSEvents reporting an add + // that happened prior to the watch beginning. + if (_files.contains(path)) continue; + _emitEvent(ChangeType.ADD, path); _files.add(path); continue; } + if (_files.containsDir(path)) continue; + _listen(Chain.track(new Directory(path).list(recursive: true)), (entity) { if (entity is Directory) return; @@ -164,34 +171,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { set.add(event); } - for (var event in batch.where((event) => event is! FileSystemMoveEvent)) { + for (var event in batch) { + // The Mac OS watcher doesn't emit move events. See issue 14806. + assert(event is! FileSystemMoveEvent); addEvent(event.path, event); } - // Issue 14806 means that move events can be misleading if they're in the - // same batch as another modification of a related file. If they are, we - // make the event set empty to ensure we check the state of the filesystem. - // Otherwise, treat them as a DELETE followed by an ADD. - for (var event in batch.where((event) => event is FileSystemMoveEvent)) { - if (eventsForPaths.containsKey(event.path) || - eventsForPaths.containsKey(event.destination)) { - - if (!isInModifiedDirectory(event.path)) { - eventsForPaths[event.path] = new Set(); - } - if (!isInModifiedDirectory(event.destination)) { - eventsForPaths[event.destination] = new Set(); - } - - continue; - } - - addEvent(event.path, new ConstructableFileSystemDeleteEvent( - event.path, event.isDirectory)); - addEvent(event.destination, new ConstructableFileSystemCreateEvent( - event.path, event.isDirectory)); - } - return eventsForPaths; } @@ -212,6 +197,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { var type = batch.first.type; var isDir = batch.first.isDirectory; + var hadModifyEvent = false; for (var event in batch.skip(1)) { // If one event reports that the file is a directory and another event @@ -222,7 +208,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { // safely assume the file was modified after a CREATE or before the // REMOVE; otherwise there will also be a REMOVE or CREATE event // (respectively) that will be contradictory. - if (event is FileSystemModifyEvent) continue; + if (event is FileSystemModifyEvent) { + hadModifyEvent = true; + continue; + } assert(event is FileSystemCreateEvent || event is FileSystemDeleteEvent); // If we previously thought this was a MODIFY, we now consider it to be a @@ -237,13 +226,23 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (type != event.type) return null; } + // If we got a CREATE event for a file we already knew about, that comes + // from FSEvents reporting an add that happened prior to the watch + // beginning. If we also received a MODIFY event, we want to report that, + // but not the CREATE. + if (type == FileSystemEvent.CREATE && hadModifyEvent && + _files.contains(batch.first.path)) { + type = FileSystemEvent.MODIFY; + } + switch (type) { case FileSystemEvent.CREATE: - // Issue 14793 means that CREATE events can actually mean DELETE, so we - // should always check the filesystem for them. - return null; + return new ConstructableFileSystemCreateEvent(batch.first.path, isDir); case FileSystemEvent.DELETE: - return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir); + // Issue 15458 means that DELETE events for directories can actually + // mean CREATE, so we always check the filesystem for them. + if (isDir) return null; + return new ConstructableFileSystemCreateEvent(batch.first.path, false); case FileSystemEvent.MODIFY: return new ConstructableFileSystemModifyEvent( batch.first.path, isDir, false); @@ -329,11 +328,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { void _emitEvent(ChangeType type, String path) { if (!isReady) return; - // Don't emit ADD events for files that we already know about. Such an event - // probably comes from FSEvents reporting an add that happened prior to the - // watch beginning. - if (type == ChangeType.ADD && _files.contains(path)) return; - _eventsController.add(new WatchEvent(type, path)); } diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 7d31221de..b71e1458b 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -2,8 +2,6 @@ // 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. -import 'dart:async'; - import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/directory_watcher/mac_os.dart'; import 'package:watcher/watcher.dart'; @@ -52,28 +50,6 @@ main() { withPermutations((i, j, k) => writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); - // We sleep here because a narrow edge case caused by two interacting bugs - // can produce events that aren't expected if we start the watcher too - // soon after creating the files above. Here's what happens: - // - // * We create "dir/sub" and its contents above. - // - // * We initialize the watcher watching "dir". - // - // * Due to issue 14373, the watcher can receive native events describing - // the creation of "dir/sub" and its contents despite the fact that they - // occurred before the watcher was started. - // - // * Usually the above events will occur while the watcher is doing its - // initial scan of "dir/sub" and be ignored, but occasionally they will - // occur afterwards. - // - // * When processing the bogus CREATE events, the watcher has to assume that - // they could mean something other than CREATE (issue 14793). Thus it - // assumes that the files or directories in question could have changed - // and emits CHANGE events or additional REMOVE/CREATE pairs for them. - schedule(() => new Future.delayed(new Duration(seconds: 2))); - startWatcher(dir: "dir"); renameDir("dir/sub", "sub"); diff --git a/pkgs/watcher/test/no_subscription/mac_os_test.dart b/pkgs/watcher/test/no_subscription/mac_os_test.dart index 3862134d6..e0275c4ee 100644 --- a/pkgs/watcher/test/no_subscription/mac_os_test.dart +++ b/pkgs/watcher/test/no_subscription/mac_os_test.dart @@ -9,32 +9,6 @@ import 'package:watcher/watcher.dart'; import 'shared.dart'; import '../utils.dart'; -// This is currently failing due to issue 14793. The reason is fairly complex: -// -// 1. As part of the test, an "unwatched.txt" file is created while there are no -// active watchers on the containing directory. -// -// 2. A watcher is then added. -// -// 3. The watcher lists the contents of the directory and notices that -// "unwatched.txt" already exists. -// -// 4. Since FSEvents reports past changes (issue 14373), the IO event stream -// emits a CREATED event for "unwatched.txt". -// -// 5. Due to issue 14793, the watcher cannot trust that this is really a CREATED -// event and checks the status of "unwatched.txt" on the filesystem against -// its internal state. -// -// 6. "unwatched.txt" exists on the filesystem and the watcher knows about it -// internally as well. It assumes this means that the file was modified. -// -// 7. The watcher emits an unexpected MODIFIED event for "unwatched.txt", -// causing the test to fail. -// -// Once issue 14793 is fixed, this will no longer be the case and the test will -// work again. - main() { initConfig(); diff --git a/pkgs/watcher/test/no_subscription/shared.dart b/pkgs/watcher/test/no_subscription/shared.dart index de625d51d..99172a2e8 100644 --- a/pkgs/watcher/test/no_subscription/shared.dart +++ b/pkgs/watcher/test/no_subscription/shared.dart @@ -41,16 +41,6 @@ sharedTests() { schedule(() { completer = new Completer(); subscription = watcher.events.listen(wrapAsync((event) { - // TODO(nweiz): Remove this when either issue 14373 or 14793 is fixed. - // Issue 14373 means that the new [Directory.watch] will emit an event - // for "unwatched.txt" being created, and issue 14793 means we have to - // check the filesystem, which leads us to assume that the file has been - // modified. - if (Platform.isMacOS && event.path.endsWith("unwatched.txt")) { - expect(event, isWatchEvent(ChangeType.MODIFY, "unwatched.txt")); - return; - } - // We should get an event for the third file, not the one added while // we weren't subscribed. expect(event, isWatchEvent(ChangeType.ADD, "added.txt")); From c47d6266a2f9eec62fd66ac9dee39f2d29191418 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Thu, 5 Dec 2013 22:49:03 +0000 Subject: [PATCH 048/201] Add debugging prints to the Mac OS directory watcher. This is a temporary CL that will make it easier to track down and fix the errors in the mac watcher. R=rnystrom@google.com BUG= Review URL: https://codereview.chromium.org//107403002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30919 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/directory_watcher/mac_os.dart | 96 ++++++++++++++++++- .../test/directory_watcher/mac_os_test.dart | 1 + pkgs/watcher/test/utils.dart | 12 +++ 3 files changed, 104 insertions(+), 5 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 3e68cd936..90036ccc4 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -7,6 +7,7 @@ library watcher.directory_watcher.mac_os; import 'dart:async'; import 'dart:io'; +import 'package:path/path.dart' as p; import 'package:stack_trace/stack_trace.dart'; import '../constructable_file_system_event.dart'; @@ -26,11 +27,19 @@ import 'resubscribable.dart'; /// This also works around issues 15458 and 14849 in the implementation of /// [Directory.watch]. class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher { + // TODO(nweiz): remove these when issue 15042 is fixed. + static var logDebugInfo = false; + static var _count = 0; + MacOSDirectoryWatcher(String directory) - : super(directory, () => new _MacOSDirectoryWatcher(directory)); + : super(directory, () => new _MacOSDirectoryWatcher(directory, _count++)); } class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { + // TODO(nweiz): remove these when issue 15042 is fixed. + static var _count = 0; + final String _id; + final String directory; Stream get events => _eventsController.stream; @@ -70,9 +79,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// watcher is closed. This does not include [_watchSubscription]. final _subscriptions = new Set(); - _MacOSDirectoryWatcher(String directory) + _MacOSDirectoryWatcher(String directory, int parentId) : directory = directory, - _files = new PathSet(directory) { + _files = new PathSet(directory), + _id = "$parentId/${_count++}" { _startWatch(); _listen(Chain.track(new Directory(directory).list(recursive: true)), @@ -80,11 +90,22 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (entity is! Directory) _files.add(entity.path); }, onError: _emitError, - onDone: _readyCompleter.complete, + onDone: () { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] watcher is ready, known files:"); + for (var file in _files.toSet()) { + print("[$_id] ${p.relative(file, from: directory)}"); + } + } + _readyCompleter.complete(); + }, cancelOnError: true); } void close() { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] watcher is closed"); + } for (var subscription in _subscriptions) { subscription.cancel(); } @@ -96,12 +117,38 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// The callback that's run when [Directory.watch] emits a batch of events. void _onBatch(List batch) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] ======== batch:"); + for (var event in batch) { + print("[$_id] ${_formatEvent(event)}"); + } + + print("[$_id] known files:"); + for (var file in _files.toSet()) { + print("[$_id] ${p.relative(file, from: directory)}"); + } + } + batches++; _sortEvents(batch).forEach((path, events) { + var relativePath = p.relative(path, from: directory); + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] events for $relativePath:\n"); + for (var event in events) { + print("[$_id] ${_formatEvent(event)}"); + } + } + var canonicalEvent = _canonicalEvent(events); events = canonicalEvent == null ? _eventsBasedOnFileSystem(path) : [canonicalEvent]; + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] canonical event for $relativePath: " + "${_formatEvent(canonicalEvent)}"); + print("[$_id] actionable events for $relativePath: " + "${events.map(_formatEvent)}"); + } for (var event in events) { if (event is FileSystemCreateEvent) { @@ -123,7 +170,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (entity is Directory) return; _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); - }, onError: _emitError, cancelOnError: true); + }, onError: (e, stackTrace) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] got error listing $relativePath: $e"); + } + _emitError(e, stackTrace); + }, cancelOnError: true); } else if (event is FileSystemModifyEvent) { assert(!event.isDirectory); _emitEvent(ChangeType.MODIFY, path); @@ -135,6 +187,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } } }); + + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] ======== batch complete"); + } } /// Sort all the events in a batch into sets based on their path. @@ -263,6 +319,13 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { var fileExists = new File(path).existsSync(); var dirExists = new Directory(path).existsSync(); + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] file existed: $fileExisted"); + print("[$_id] dir existed: $dirExisted"); + print("[$_id] file exists: $fileExists"); + print("[$_id] dir exists: $dirExists"); + } + var events = []; if (fileExisted) { if (fileExists) { @@ -328,6 +391,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { void _emitEvent(ChangeType type, String path) { if (!isReady) return; + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] emitting $type ${p.relative(path, from: directory)}"); + } + _eventsController.add(new WatchEvent(type, path)); } @@ -348,4 +415,23 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { }, cancelOnError: cancelOnError); _subscriptions.add(subscription); } + + // TODO(nweiz): remove this when issue 15042 is fixed. + /// Return a human-friendly string representation of [event]. + String _formatEvent(FileSystemEvent event) { + if (event == null) return 'null'; + + var path = p.relative(event.path, from: directory); + var type = event.isDirectory ? 'directory' : 'file'; + if (event is FileSystemCreateEvent) { + return "create $type $path"; + } else if (event is FileSystemDeleteEvent) { + return "delete $type $path"; + } else if (event is FileSystemModifyEvent) { + return "modify $type $path"; + } else if (event is FileSystemMoveEvent) { + return "move $type $path to " + "${p.relative(event.destination, from: directory)}"; + } + } } diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index b71e1458b..8fc2d3e54 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -11,6 +11,7 @@ import '../utils.dart'; main() { initConfig(); + MacOSDirectoryWatcher.logDebugInfo = true; watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 567bdb221..a7bd9b636 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -15,6 +15,9 @@ import 'package:watcher/watcher.dart'; import 'package:watcher/src/stat.dart'; import 'package:watcher/src/utils.dart'; +// TODO(nweiz): remove this when issue 15042 is fixed. +import 'package:watcher/src/directory_watcher/mac_os.dart'; + /// The path to the temporary sandbox created for each test. All file /// operations are implicitly relative to this directory. String _sandboxDir; @@ -115,6 +118,11 @@ Stream _watcherEvents; /// /// If [dir] is provided, watches a subdirectory in the sandbox with that name. void startWatcher({String dir}) { + var testCase = currentTestCase.description; + if (MacOSDirectoryWatcher.logDebugInfo) { + print("starting watcher for $testCase"); + } + // We want to wait until we're ready *after* we subscribe to the watcher's // events. _watcher = createWatcher(dir: dir, waitForReady: false); @@ -128,6 +136,10 @@ void startWatcher({String dir}) { onError: currentSchedule.signalError); currentSchedule.onComplete.schedule(() { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("stopping watcher for $testCase"); + } + var numEvents = _nextEvent; subscription.cancel(); _nextEvent = 0; From 4214ba98b8a962370847ca988f35c43ee3e47854 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 9 Dec 2013 20:44:05 +0000 Subject: [PATCH 049/201] Increment package versions for stack trace changes. R=rnystrom@google.com BUG= Review URL: https://codereview.chromium.org//110613003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@30997 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 93d8eb48c..90b93e551 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.0 +version: 0.9.1 author: "Dart Team " homepage: http://www.dartlang.org description: > @@ -9,7 +9,7 @@ description: > dependencies: crypto: ">=0.9.0 <0.10.0" path: ">=0.9.0 <0.10.0" - stack_trace: ">=0.9.0 <0.10.0" + stack_trace: ">=0.9.1 <0.10.0" dev_dependencies: scheduled_test: ">=0.9.0 <0.10.0" unittest: ">=0.9.0 <0.10.0" From ce9c760bd4ce9f56b26c5628706ac075a6d48dfb Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 10 Dec 2013 00:24:01 +0000 Subject: [PATCH 050/201] Make pkg/path 1.0.0 and upgrade dependencies appropriately. All existing packages that use pkg/path (other than pkg/stack_trace, which has already been updated) are compatible with both the pre-1.0 and post-1.0 path API, so I've marked their version constraints as ">=0.9.0 <2.0.0". I've also incremented their patch versions and I intend to release new versions as soon as this CL lands. R=alanknight@google.com, efortuna@google.com, jmesserly@google.com, rnystrom@google.com, scheglov@google.com Review URL: https://codereview.chromium.org//110873002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@31005 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 90b93e551..a9672946d 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.1 +version: 0.9.2 author: "Dart Team " homepage: http://www.dartlang.org description: > @@ -8,7 +8,7 @@ description: > modified. dependencies: crypto: ">=0.9.0 <0.10.0" - path: ">=0.9.0 <0.10.0" + path: ">=0.9.0 <2.0.0" stack_trace: ">=0.9.1 <0.10.0" dev_dependencies: scheduled_test: ">=0.9.0 <0.10.0" From f16db649803d130ce25e33f6dd8be1ae3292d9de Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" Date: Tue, 7 Jan 2014 21:09:58 +0000 Subject: [PATCH 051/201] Don't test for file differences in the polling watcher. The non-polling forms don't do this, so this makes the polling style behave the same. It also has two important benefits: 1. On Windows, this avoids opening the files being watched. Since opening a file prevents it from being deleted on Windows, this causes the watcher to interfere with the files and leads to some flaky tests and buggy user behavior. 2. It saves some time and memory since we don't need to store the SHA1 for every file being watched. BUG=http://dartbug.com/13026, http://dartbug.com/15431 R=nweiz@google.com Review URL: https://codereview.chromium.org//122573003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@31574 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/directory_watcher/polling.dart | 69 ++++--------------- pkgs/watcher/pubspec.yaml | 8 +-- .../test/directory_watcher/linux_test.dart | 10 --- .../test/directory_watcher/mac_os_test.dart | 10 --- .../test/directory_watcher/polling_test.dart | 9 --- .../test/directory_watcher/shared.dart | 10 +++ 6 files changed, 27 insertions(+), 89 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 684f7aad8..12a32458e 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -7,7 +7,6 @@ library watcher.directory_watcher.polling; import 'dart:async'; import 'dart:io'; -import 'package:crypto/crypto.dart'; import 'package:stack_trace/stack_trace.dart'; import '../async_queue.dart'; @@ -46,10 +45,10 @@ class _PollingDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// directory contents. final Duration _pollingDelay; - /// The previous status of the files in the directory. + /// The previous modification times of the files in the directory. /// /// Used to tell which files have been modified. - final _statuses = new Map(); + final _lastModifieds = new Map(); /// The subscription used while [directory] is being listed. /// @@ -89,7 +88,7 @@ class _PollingDirectoryWatcher implements ManuallyClosedDirectoryWatcher { // Don't process any remaining files. _filesToProcess.clear(); _polledFiles.clear(); - _statuses.clear(); + _lastModifieds.clear(); } /// Scans the contents of the directory once to see which files have been @@ -135,32 +134,25 @@ class _PollingDirectoryWatcher implements ManuallyClosedDirectoryWatcher { return getModificationTime(file).then((modified) { if (_events.isClosed) return null; - var lastStatus = _statuses[file]; + var lastModified = _lastModifieds[file]; // If its modification time hasn't changed, assume the file is unchanged. - if (lastStatus != null && lastStatus.modified == modified) { + if (lastModified != null && lastModified == modified) { // The file is still here. _polledFiles.add(file); return null; } - return _hashFile(file).then((hash) { - if (_events.isClosed) return; - - var status = new _FileStatus(modified, hash); - _statuses[file] = status; - _polledFiles.add(file); + if (_events.isClosed) return null; - // Only notify if we're ready to emit events. - if (!isReady) return; + _lastModifieds[file] = modified; + _polledFiles.add(file); - // And the file is different. - var changed = lastStatus == null || !_sameHash(lastStatus.hash, hash); - if (!changed) return; + // Only notify if we're ready to emit events. + if (!isReady) return null; - var type = lastStatus == null ? ChangeType.ADD : ChangeType.MODIFY; - _events.add(new WatchEvent(type, file)); - }); + var type = lastModified == null ? ChangeType.ADD : ChangeType.MODIFY; + _events.add(new WatchEvent(type, file)); }); } @@ -169,10 +161,10 @@ class _PollingDirectoryWatcher implements ManuallyClosedDirectoryWatcher { Future _completePoll() { // Any files that were not seen in the last poll but that we have a // status for must have been removed. - var removedFiles = _statuses.keys.toSet().difference(_polledFiles); + var removedFiles = _lastModifieds.keys.toSet().difference(_polledFiles); for (var removed in removedFiles) { if (isReady) _events.add(new WatchEvent(ChangeType.REMOVE, removed)); - _statuses.remove(removed); + _lastModifieds.remove(removed); } if (!isReady) _ready.complete(); @@ -183,37 +175,4 @@ class _PollingDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _poll(); }); } - - /// Calculates the SHA-1 hash of the file at [path]. - Future> _hashFile(String path) { - return Chain.track(new File(path).readAsBytes()).then((bytes) { - var sha1 = new SHA1(); - sha1.add(bytes); - return sha1.close(); - }); - } - - /// Returns `true` if [a] and [b] are the same hash value, i.e. the same - /// series of byte values. - bool _sameHash(List a, List b) { - // Hashes should always be the same size. - assert(a.length == b.length); - - for (var i = 0; i < a.length; i++) { - if (a[i] != b[i]) return false; - } - - return true; - } -} - -class _FileStatus { - /// The last time the file was modified. - DateTime modified; - - /// The SHA-1 hash of the contents of the file. - List hash; - - _FileStatus(this.modified, this.hash); } - diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index a9672946d..c77dddfd2 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,13 +1,11 @@ name: watcher -version: 0.9.2 +version: 0.9.3-dev author: "Dart Team " homepage: http://www.dartlang.org description: > - A file watcher. It monitors (currently by polling) for changes to contents - of directories and notifies you when files have been added, removed, or - modified. + A file watcher. It monitors for changes to contents of directories and + notifies you when files have been added, removed, or modified. dependencies: - crypto: ">=0.9.0 <0.10.0" path: ">=0.9.0 <2.0.0" stack_trace: ">=0.9.1 <0.10.0" dev_dependencies: diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart index f53b272b4..c2d017677 100644 --- a/pkgs/watcher/test/directory_watcher/linux_test.dart +++ b/pkgs/watcher/test/directory_watcher/linux_test.dart @@ -23,16 +23,6 @@ main() { new isInstanceOf()); }); - test('notifies even if the file contents are unchanged', () { - writeFile("a.txt", contents: "same"); - writeFile("b.txt", contents: "before"); - startWatcher(); - writeFile("a.txt", contents: "same"); - writeFile("b.txt", contents: "after"); - expectModifyEvent("a.txt"); - expectModifyEvent("b.txt"); - }); - test('emits events for many nested files moved out then immediately back in', () { withPermutations((i, j, k) => diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 8fc2d3e54..077787a29 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -36,16 +36,6 @@ main() { expectAddEvent("dir/newer.txt"); }); - test('notifies even if the file contents are unchanged', () { - writeFile("a.txt", contents: "same"); - writeFile("b.txt", contents: "before"); - startWatcher(); - writeFile("a.txt", contents: "same"); - writeFile("b.txt", contents: "after"); - expectModifyEvent("a.txt"); - expectModifyEvent("b.txt"); - }); - test('emits events for many nested files moved out then immediately back in', () { withPermutations((i, j, k) => diff --git a/pkgs/watcher/test/directory_watcher/polling_test.dart b/pkgs/watcher/test/directory_watcher/polling_test.dart index 02ed5d215..da292073c 100644 --- a/pkgs/watcher/test/directory_watcher/polling_test.dart +++ b/pkgs/watcher/test/directory_watcher/polling_test.dart @@ -19,15 +19,6 @@ main() { sharedTests(); - test('does not notify if the file contents are unchanged', () { - writeFile("a.txt", contents: "same"); - writeFile("b.txt", contents: "before"); - startWatcher(); - writeFile("a.txt", contents: "same"); - writeFile("b.txt", contents: "after"); - expectModifyEvent("b.txt"); - }); - test('does not notify if the modification time did not change', () { writeFile("a.txt", contents: "before"); writeFile("b.txt", contents: "before"); diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index fe76a03d3..f831196a8 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -51,6 +51,16 @@ sharedTests() { expectModifyEvent("file.txt"); }); + test('notifies even if the file contents are unchanged', () { + writeFile("a.txt", contents: "same"); + writeFile("b.txt", contents: "before"); + startWatcher(); + writeFile("a.txt", contents: "same"); + writeFile("b.txt", contents: "after"); + expectModifyEvent("a.txt"); + expectModifyEvent("b.txt"); + }); + test('when the watched directory is deleted, removes all files', () { writeFile("dir/a.txt"); writeFile("dir/b.txt"); From 5be127ba595f23601bbe85ee3d9f8fa3fc91f525 Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" Date: Tue, 7 Jan 2014 23:39:17 +0000 Subject: [PATCH 052/201] I guess Linux doesn't iterate over folders alphabetically. R=nweiz@google.com Review URL: https://codereview.chromium.org//127033002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@31591 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/test/directory_watcher/shared.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index f831196a8..2e0186f52 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -55,10 +55,13 @@ sharedTests() { writeFile("a.txt", contents: "same"); writeFile("b.txt", contents: "before"); startWatcher(); + writeFile("a.txt", contents: "same"); writeFile("b.txt", contents: "after"); - expectModifyEvent("a.txt"); - expectModifyEvent("b.txt"); + inAnyOrder(() { + expectModifyEvent("a.txt"); + expectModifyEvent("b.txt"); + }); }); test('when the watched directory is deleted, removes all files', () { From 51191b81bb183f77196fdcdac61f32b0d9909216 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Fri, 10 Jan 2014 20:36:33 +0000 Subject: [PATCH 053/201] Use stream matchers to unflake the mac OS watcher tests. R=rnystrom@google.com BUG=15024 Review URL: https://codereview.chromium.org//129473003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@31705 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/utils.dart | 12 ++ pkgs/watcher/pubspec.yaml | 4 +- .../test/directory_watcher/linux_test.dart | 17 +- .../test/directory_watcher/mac_os_test.dart | 42 ++-- .../test/directory_watcher/shared.dart | 111 +++++++---- pkgs/watcher/test/utils.dart | 182 ++++++++++++------ 6 files changed, 240 insertions(+), 128 deletions(-) diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index a235f7da5..163e9f445 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -73,6 +73,18 @@ Stream futureStream(Future future, {bool broadcast: false}) { /// under the covers. Future newFuture(callback()) => new Future.value().then((_) => callback()); +/// Returns a [Future] that completes after pumping the event queue [times] +/// times. By default, this should pump the event queue enough times to allow +/// any code to run, as long as it's not waiting on some external event. +Future pumpEventQueue([int times=20]) { + if (times == 0) return new Future.value(); + // We use a delayed future to allow microtask events to finish. The + // Future.value or Future() constructors use scheduleMicrotask themselves and + // would therefore not wait for microtask callbacks that are scheduled after + // invoking this method. + return new Future.delayed(Duration.ZERO, () => pumpEventQueue(times - 1)); +} + /// A stream transformer that batches all events that are sent at the same time. /// /// When multiple events are synchronously added to a stream controller, the diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index c77dddfd2..bf00d9e31 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -9,7 +9,7 @@ dependencies: path: ">=0.9.0 <2.0.0" stack_trace: ">=0.9.1 <0.10.0" dev_dependencies: - scheduled_test: ">=0.9.0 <0.10.0" - unittest: ">=0.9.0 <0.10.0" + scheduled_test: ">=0.9.3-dev <0.10.0" + unittest: ">=0.9.2 <0.10.0" environment: sdk: ">=0.8.10+6 <2.0.0" diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart index c2d017677..cae38adf8 100644 --- a/pkgs/watcher/test/directory_watcher/linux_test.dart +++ b/pkgs/watcher/test/directory_watcher/linux_test.dart @@ -32,14 +32,15 @@ main() { renameDir("dir/sub", "sub"); renameDir("sub", "dir/sub"); - inAnyOrder(() { - withPermutations((i, j, k) => - expectRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); - }); - - inAnyOrder(() { - withPermutations((i, j, k) => - expectAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); + allowEither(() { + inAnyOrder(withPermutations((i, j, k) => + isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + + inAnyOrder(withPermutations((i, j, k) => + isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + }, () { + inAnyOrder(withPermutations((i, j, k) => + isModifyEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); }); }); } diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 077787a29..43567f0e7 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -36,24 +36,26 @@ main() { expectAddEvent("dir/newer.txt"); }); - test('emits events for many nested files moved out then immediately back in', - () { - withPermutations((i, j, k) => - writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); - - startWatcher(dir: "dir"); - - renameDir("dir/sub", "sub"); - renameDir("sub", "dir/sub"); - - inAnyOrder(() { - withPermutations((i, j, k) => - expectRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); - }); - - inAnyOrder(() { - withPermutations((i, j, k) => - expectAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); - }); - }); + // TODO(nweiz): re-enable this when issue 16003 is fixed. + // test('emits events for many nested files moved out then immediately back in', + // () { + // withPermutations((i, j, k) => + // writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); + + // startWatcher(dir: "dir"); + + // renameDir("dir/sub", "sub"); + // renameDir("sub", "dir/sub"); + + // allowEither(() { + // inAnyOrder(withPermutations((i, j, k) => + // isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + + // inAnyOrder(withPermutations((i, j, k) => + // isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + // }, () { + // inAnyOrder(withPermutations((i, j, k) => + // isModifyEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + // }); + // }); } diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index 2e0186f52..849cea00d 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/src/utils.dart'; import '../utils.dart'; @@ -58,10 +59,10 @@ sharedTests() { writeFile("a.txt", contents: "same"); writeFile("b.txt", contents: "after"); - inAnyOrder(() { - expectModifyEvent("a.txt"); - expectModifyEvent("b.txt"); - }); + inAnyOrder([ + isModifyEvent("a.txt"), + isModifyEvent("b.txt") + ]); }); test('when the watched directory is deleted, removes all files', () { @@ -71,10 +72,10 @@ sharedTests() { startWatcher(dir: "dir"); deleteDir("dir"); - inAnyOrder(() { - expectRemoveEvent("dir/a.txt"); - expectRemoveEvent("dir/b.txt"); - }); + inAnyOrder([ + isRemoveEvent("dir/a.txt"), + isRemoveEvent("dir/b.txt") + ]); }); group("moves", () { @@ -83,10 +84,10 @@ sharedTests() { startWatcher(); renameFile("old.txt", "new.txt"); - inAnyOrder(() { - expectAddEvent("new.txt"); - expectRemoveEvent("old.txt"); - }); + inAnyOrder([ + isAddEvent("new.txt"), + isRemoveEvent("old.txt") + ]); }); test('notifies when a file is moved from outside the watched directory', @@ -108,6 +109,13 @@ sharedTests() { }); }); + // Most of the time, when multiple filesystem actions happen in sequence, + // they'll be batched together and the watcher will see them all at once. + // These tests verify that the watcher normalizes and combine these events + // properly. However, very occasionally the events will be reported in + // separate batches, and the watcher will report them as though they occurred + // far apart in time, so each of these tests has a "backup case" to allow for + // that as well. group("clustered changes", () { test("doesn't notify when a file is created and then immediately removed", () { @@ -115,7 +123,12 @@ sharedTests() { writeFile("file.txt"); deleteFile("file.txt"); - // [startWatcher] will assert that no events were fired. + // Backup case. + startClosingEventStream(); + allowEvents(() { + expectAddEvent("file.txt"); + expectRemoveEvent("file.txt"); + }); }); test("reports a modification when a file is deleted and then immediately " @@ -125,7 +138,14 @@ sharedTests() { deleteFile("file.txt"); writeFile("file.txt", contents: "re-created"); - expectModifyEvent("file.txt"); + + allowEither(() { + expectModifyEvent("file.txt"); + }, () { + // Backup case. + expectRemoveEvent("file.txt"); + expectAddEvent("file.txt"); + }); }); test("reports a modification when a file is moved and then immediately " @@ -135,9 +155,17 @@ sharedTests() { renameFile("old.txt", "new.txt"); writeFile("old.txt", contents: "re-created"); - inAnyOrder(() { - expectModifyEvent("old.txt"); + + allowEither(() { + inAnyOrder([ + isModifyEvent("old.txt"), + isAddEvent("new.txt") + ]); + }, () { + // Backup case. + expectRemoveEvent("old.txt"); expectAddEvent("new.txt"); + expectAddEvent("old.txt"); }); }); @@ -148,6 +176,10 @@ sharedTests() { writeFile("file.txt", contents: "modified"); deleteFile("file.txt"); + + // Backup case. + allowModifyEvent("file.txt"); + expectRemoveEvent("file.txt"); }); @@ -157,7 +189,12 @@ sharedTests() { writeFile("file.txt"); writeFile("file.txt", contents: "modified"); + expectAddEvent("file.txt"); + + // Backup case. + startClosingEventStream(); + allowModifyEvent("file.txt"); }); }); @@ -174,10 +211,10 @@ sharedTests() { startWatcher(); renameDir("old", "new"); - inAnyOrder(() { - expectRemoveEvent("old/file.txt"); - expectAddEvent("new/file.txt"); - }); + inAnyOrder([ + isRemoveEvent("old/file.txt"), + isAddEvent("new/file.txt") + ]); writeFile("new/file.txt", contents: "modified"); expectModifyEvent("new/file.txt"); @@ -191,10 +228,8 @@ sharedTests() { startWatcher(dir: "dir"); renameDir("sub", "dir/sub"); - inAnyOrder(() { - withPermutations((i, j, k) => - expectAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); - }); + inAnyOrder(withPermutations((i, j, k) => + isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); }); test('emits events for many nested files removed at once', () { @@ -210,10 +245,8 @@ sharedTests() { // directory. renameDir("dir/sub", "sub"); - inAnyOrder(() { - withPermutations((i, j, k) => - expectRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); - }); + inAnyOrder(withPermutations((i, j, k) => + isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); }); test('emits events for many nested files moved at once', () { @@ -224,12 +257,12 @@ sharedTests() { startWatcher(dir: "dir"); renameDir("dir/old", "dir/new"); - inAnyOrder(() { - withPermutations((i, j, k) { - expectRemoveEvent("dir/old/sub-$i/sub-$j/file-$k.txt"); - expectAddEvent("dir/new/sub-$i/sub-$j/file-$k.txt"); - }); - }); + inAnyOrder(unionAll(withPermutations((i, j, k) { + return new Set.from([ + isRemoveEvent("dir/old/sub-$i/sub-$j/file-$k.txt"), + isAddEvent("dir/new/sub-$i/sub-$j/file-$k.txt") + ]); + }))); }); test("emits events for many files added at once in a subdirectory with the " @@ -241,11 +274,11 @@ sharedTests() { deleteFile("dir/sub"); renameDir("old", "dir/sub"); - inAnyOrder(() { - expectRemoveEvent("dir/sub"); - withPermutations((i, j, k) => - expectAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); - }); + + var events = withPermutations((i, j, k) => + isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); + events.add(isRemoveEvent("dir/sub")); + inAnyOrder(events); }); }); } diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index a7bd9b636..f7e35f18a 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -9,6 +9,7 @@ import 'dart:collection'; import 'dart:io'; import 'package:path/path.dart' as p; +import 'package:scheduled_test/scheduled_stream.dart'; import 'package:scheduled_test/scheduled_test.dart'; import 'package:unittest/compact_vm_config.dart'; import 'package:watcher/watcher.dart'; @@ -25,11 +26,6 @@ String _sandboxDir; /// The [DirectoryWatcher] being used for the current scheduled test. DirectoryWatcher _watcher; -/// The index in [_watcher]'s event stream for the next event. When event -/// expectations are set using [expectEvent] (et. al.), they use this to -/// expect a series of events in order. -var _nextEvent = 0; - /// The mock modification times (in milliseconds since epoch) for each file. /// /// The actual file system has pretty coarse granularity for file modification @@ -111,7 +107,7 @@ DirectoryWatcher createWatcher({String dir, bool waitForReady}) { } /// The stream of events from the watcher started with [startWatcher]. -Stream _watcherEvents; +ScheduledStream _watcherEvents; /// Creates a new [DirectoryWatcher] that watches a temporary directory and /// starts monitoring it for events. @@ -130,82 +126,102 @@ void startWatcher({String dir}) { // Schedule [_watcher.events.listen] so that the watcher doesn't start // watching [dir] before it exists. Expose [_watcherEvents] immediately so // that it can be accessed synchronously after this. - _watcherEvents = futureStream(schedule(() { - var allEvents = new Queue(); - var subscription = _watcher.events.listen(allEvents.add, - onError: currentSchedule.signalError); - + _watcherEvents = new ScheduledStream(futureStream(schedule(() { currentSchedule.onComplete.schedule(() { if (MacOSDirectoryWatcher.logDebugInfo) { print("stopping watcher for $testCase"); } - var numEvents = _nextEvent; - subscription.cancel(); - _nextEvent = 0; _watcher = null; + if (!_closePending) _watcherEvents.close(); // If there are already errors, don't add this to the output and make // people think it might be the root cause. if (currentSchedule.errors.isEmpty) { - expect(allEvents, hasLength(numEvents)); - } else { - currentSchedule.addDebugInfo("Events fired:\n${allEvents.join('\n')}"); + _watcherEvents.expect(isDone); } }, "reset watcher"); return _watcher.events; - }, "create watcher"), broadcast: true); + }, "create watcher"), broadcast: true)); schedule(() => _watcher.ready, "wait for watcher to be ready"); } -/// A future set by [inAnyOrder] that will complete to the set of events that -/// occur in the [inAnyOrder] block. -Future> _unorderedEventFuture; +/// Whether an event to close [_watcherEvents] has been scheduled. +bool _closePending = false; -/// Runs [block] and allows multiple [expectEvent] calls in that block to match -/// events in any order. -void inAnyOrder(block()) { - var oldFuture = _unorderedEventFuture; +/// Schedule closing the directory watcher stream after the event queue has been +/// pumped. +/// +/// This is necessary when events are allowed to occur, but don't have to occur, +/// at the end of a test. Otherwise, if they don't occur, the test will wait +/// indefinitely because they might in the future and because the watcher is +/// normally only closed after the test completes. +void startClosingEventStream() { + schedule(() { + _closePending = true; + pumpEventQueue().then((_) => _watcherEvents.close()).whenComplete(() { + _closePending = false; + }); + }, 'start closing event stream'); +} + +/// A list of [StreamMatcher]s that have been collected using +/// [_collectStreamMatcher]. +List _collectedStreamMatchers; + +/// Collects all stream matchers that are registered within [block] into a +/// single stream matcher. +/// +/// The returned matcher will match each of the collected matchers in order. +StreamMatcher _collectStreamMatcher(block()) { + var oldStreamMatchers = _collectedStreamMatchers; + _collectedStreamMatchers = new List(); try { - var firstEvent = _nextEvent; - var completer = new Completer(); - _unorderedEventFuture = completer.future; block(); - - _watcherEvents.skip(firstEvent).take(_nextEvent - firstEvent).toSet() - .then(completer.complete, onError: completer.completeError); - currentSchedule.wrapFuture(_unorderedEventFuture, - "waiting for ${_nextEvent - firstEvent} events"); + return inOrder(_collectedStreamMatchers); } finally { - _unorderedEventFuture = oldFuture; + _collectedStreamMatchers = oldStreamMatchers; } } -/// Expects that the next set of event will be a change of [type] on [path]. +/// Either add [streamMatcher] as an expectation to [_watcherEvents], or collect +/// it with [_collectStreamMatcher]. /// -/// Multiple calls to [expectEvent] require that the events are received in that -/// order unless they're called in an [inAnyOrder] block. -void expectEvent(ChangeType type, String path) { - if (_unorderedEventFuture != null) { - // Assign this to a local variable since it will be un-assigned by the time - // the scheduled callback runs. - var future = _unorderedEventFuture; - - expect( - schedule(() => future, "should fire $type event on $path"), - completion(contains(isWatchEvent(type, path)))); +/// [streamMatcher] can be a [StreamMatcher], a [Matcher], or a value. +void _expectOrCollect(streamMatcher) { + if (_collectedStreamMatchers != null) { + _collectedStreamMatchers.add(new StreamMatcher.wrap(streamMatcher)); } else { - var future = currentSchedule.wrapFuture( - _watcherEvents.elementAt(_nextEvent), - "waiting for $type event on $path"); - - expect( - schedule(() => future, "should fire $type event on $path"), - completion(isWatchEvent(type, path))); + _watcherEvents.expect(streamMatcher); } - _nextEvent++; +} + +/// Expects that [matchers] will match emitted events in any order. +/// +/// [matchers] may be [Matcher]s or values, but not [StreamMatcher]s. +void inAnyOrder(Iterable matchers) { + matchers = matchers.toSet(); + _expectOrCollect(nextValues(matchers.length, unorderedMatches(matchers))); +} + +/// Expects that the expectations established in either [block1] or [block2] +/// will match the emitted events. +/// +/// If both blocks match, the one that consumed more events will be used. +void allowEither(block1(), block2()) { + _expectOrCollect(either( + _collectStreamMatcher(block1), _collectStreamMatcher(block2))); +} + +/// Allows the expectations established in [block] to match the emitted events. +/// +/// If the expectations in [block] don't match, no error will be raised and no +/// events will be consumed. If this is used at the end of a test, +/// [startClosingEventStream] should be called before it. +void allowEvents(block()) { + _expectOrCollect(allow(_collectStreamMatcher(block))); } /// Returns a matcher that matches a [WatchEvent] with the given [type] and @@ -217,9 +233,53 @@ Matcher isWatchEvent(ChangeType type, String path) { }, "is $type $path"); } -void expectAddEvent(String path) => expectEvent(ChangeType.ADD, path); -void expectModifyEvent(String path) => expectEvent(ChangeType.MODIFY, path); -void expectRemoveEvent(String path) => expectEvent(ChangeType.REMOVE, path); +/// Returns a [Matcher] that matches a [WatchEvent] for an add event for [path]. +Matcher isAddEvent(String path) => isWatchEvent(ChangeType.ADD, path); + +/// Returns a [Matcher] that matches a [WatchEvent] for a modification event for +/// [path]. +Matcher isModifyEvent(String path) => isWatchEvent(ChangeType.MODIFY, path); + +/// Returns a [Matcher] that matches a [WatchEvent] for a removal event for +/// [path]. +Matcher isRemoveEvent(String path) => isWatchEvent(ChangeType.REMOVE, path); + +/// Expects that the next event emitted will be for an add event for [path]. +void expectAddEvent(String path) => + _expectOrCollect(isWatchEvent(ChangeType.ADD, path)); + +/// Expects that the next event emitted will be for a modification event for +/// [path]. +void expectModifyEvent(String path) => + _expectOrCollect(isWatchEvent(ChangeType.MODIFY, path)); + +/// Expects that the next event emitted will be for a removal event for [path]. +void expectRemoveEvent(String path) => + _expectOrCollect(isWatchEvent(ChangeType.REMOVE, path)); + +/// Consumes an add event for [path] if one is emitted at this point in the +/// schedule, but doesn't throw an error if it isn't. +/// +/// If this is used at the end of a test, [startClosingEventStream] should be +/// called before it. +void allowAddEvent(String path) => + _expectOrCollect(allow(isWatchEvent(ChangeType.ADD, path))); + +/// Consumes a modification event for [path] if one is emitted at this point in +/// the schedule, but doesn't throw an error if it isn't. +/// +/// If this is used at the end of a test, [startClosingEventStream] should be +/// called before it. +void allowModifyEvent(String path) => + _expectOrCollect(allow(isWatchEvent(ChangeType.MODIFY, path))); + +/// Consumes a removal event for [path] if one is emitted at this point in the +/// schedule, but doesn't throw an error if it isn't. +/// +/// If this is used at the end of a test, [startClosingEventStream] should be +/// called before it. +void allowRemoveEvent(String path) => + _expectOrCollect(allow(isWatchEvent(ChangeType.REMOVE, path))); /// Schedules writing a file in the sandbox at [path] with [contents]. /// @@ -299,14 +359,18 @@ void deleteDir(String path) { /// Runs [callback] with every permutation of non-negative [i], [j], and [k] /// less than [limit]. /// +/// Returns a set of all values returns by [callback]. +/// /// [limit] defaults to 3. -void withPermutations(callback(int i, int j, int k), {int limit}) { +Set withPermutations(callback(int i, int j, int k), {int limit}) { if (limit == null) limit = 3; + var results = new Set(); for (var i = 0; i < limit; i++) { for (var j = 0; j < limit; j++) { for (var k = 0; k < limit; k++) { - callback(i, j, k); + results.add(callback(i, j, k)); } } } + return results; } From 1fe426cd684bdd6efaca6236d161b6637dda6c86 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Fri, 10 Jan 2014 21:27:08 +0000 Subject: [PATCH 054/201] Stop working around issue 15458 in pkg/watcher. R=rnystrom@google.com Review URL: https://codereview.chromium.org//134773002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@31709 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/directory_watcher/mac_os.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 90036ccc4..17c25adbc 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -24,7 +24,7 @@ import 'resubscribable.dart'; /// succession, it won't report them in the order they occurred. See issue /// 14373. /// -/// This also works around issues 15458 and 14849 in the implementation of +/// This also works around issue 14849 in the implementation of /// [Directory.watch]. class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher { // TODO(nweiz): remove these when issue 15042 is fixed. @@ -295,9 +295,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { case FileSystemEvent.CREATE: return new ConstructableFileSystemCreateEvent(batch.first.path, isDir); case FileSystemEvent.DELETE: - // Issue 15458 means that DELETE events for directories can actually - // mean CREATE, so we always check the filesystem for them. - if (isDir) return null; return new ConstructableFileSystemCreateEvent(batch.first.path, false); case FileSystemEvent.MODIFY: return new ConstructableFileSystemModifyEvent( From 0812547c3fda4962a8abe43c3800bca242a020fd Mon Sep 17 00:00:00 2001 From: "kevmoo@google.com" Date: Mon, 13 Jan 2014 18:07:45 +0000 Subject: [PATCH 055/201] pkg/unittest: added LICENSE R=rnystrom@google.com Review URL: https://codereview.chromium.org//135343002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@31750 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/LICENSE | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 pkgs/watcher/LICENSE diff --git a/pkgs/watcher/LICENSE b/pkgs/watcher/LICENSE new file mode 100644 index 000000000..5c60afea3 --- /dev/null +++ b/pkgs/watcher/LICENSE @@ -0,0 +1,26 @@ +Copyright 2014, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 450bf11ae4ba8aa1bdbfa6021fd448555eedf7ce Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 14 Jan 2014 23:49:01 +0000 Subject: [PATCH 056/201] Fix a flaky test and work around issue 16003 in pkg/watcher. There's an additional flaky test that this CL is not likely to fix. Additional debugging is added to help track down the cause of that flake. R=rnystrom@google.com BUG=16079 Review URL: https://codereview.chromium.org//134963007 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@31815 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/directory_watcher/mac_os.dart | 40 +++++++++++------ .../test/directory_watcher/mac_os_test.dart | 43 +++++++++---------- pkgs/watcher/test/utils.dart | 23 +++++++++- 3 files changed, 69 insertions(+), 37 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 17c25adbc..bb82a4c3e 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -24,7 +24,7 @@ import 'resubscribable.dart'; /// succession, it won't report them in the order they occurred. See issue /// 14373. /// -/// This also works around issue 14849 in the implementation of +/// This also works around issues 16003 and 14849 in the implementation of /// [Directory.watch]. class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher { // TODO(nweiz): remove these when issue 15042 is fixed. @@ -56,7 +56,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// This is used to determine if the [Directory.watch] stream was falsely /// closed due to issue 14849. A close caused by events in the past will only /// happen before or immediately after the first batch of events. - int batches = 0; + int _batches = 0; /// The set of files that are known to exist recursively within the watched /// directory. @@ -129,12 +129,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } } - batches++; + _batches++; _sortEvents(batch).forEach((path, events) { var relativePath = p.relative(path, from: directory); if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] events for $relativePath:\n"); + print("[$_id] events for $relativePath:"); for (var event in events) { print("[$_id] ${_formatEvent(event)}"); } @@ -168,6 +168,8 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _listen(Chain.track(new Directory(path).list(recursive: true)), (entity) { if (entity is Directory) return; + if (_files.contains(path)) return; + _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); }, onError: (e, stackTrace) { @@ -293,9 +295,14 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { switch (type) { case FileSystemEvent.CREATE: - return new ConstructableFileSystemCreateEvent(batch.first.path, isDir); - case FileSystemEvent.DELETE: + // Issue 16003 means that a CREATE event for a directory can indicate + // that the directory was moved and then re-created. + // [_eventsBasedOnFileSystem] will handle this correctly by producing a + // DELETE event followed by a CREATE event if the directory exists. + if (isDir) return null; return new ConstructableFileSystemCreateEvent(batch.first.path, false); + case FileSystemEvent.DELETE: + return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir); case FileSystemEvent.MODIFY: return new ConstructableFileSystemModifyEvent( batch.first.path, isDir, false); @@ -317,10 +324,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { var dirExists = new Directory(path).existsSync(); if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] file existed: $fileExisted"); - print("[$_id] dir existed: $dirExisted"); - print("[$_id] file exists: $fileExists"); - print("[$_id] dir exists: $dirExists"); + print("[$_id] checking file system for " + "${p.relative(path, from: directory)}"); + print("[$_id] file existed: $fileExisted"); + print("[$_id] dir existed: $dirExisted"); + print("[$_id] file exists: $fileExists"); + print("[$_id] dir exists: $dirExists"); } var events = []; @@ -353,19 +362,24 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// The callback that's run when the [Directory.watch] stream is closed. void _onDone() { + if (MacOSDirectoryWatcher.logDebugInfo) print("[$_id] stream closed"); + _watchSubscription = null; // If the directory still exists and we haven't seen more than one batch, // this is probably issue 14849 rather than a real close event. We should // just restart the watcher. - if (batches < 2 && new Directory(directory).existsSync()) { + if (_batches < 2 && new Directory(directory).existsSync()) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] fake closure (issue 14849), re-opening stream"); + } _startWatch(); return; } // FSEvents can fail to report the contents of the directory being removed - // when the directory itself is removed, so we need to manually mark the as - // removed. + // when the directory itself is removed, so we need to manually mark the + // files as removed. for (var file in _files.toSet()) { _emitEvent(ChangeType.REMOVE, file); } diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 43567f0e7..1e9bd7d10 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -36,26 +36,25 @@ main() { expectAddEvent("dir/newer.txt"); }); - // TODO(nweiz): re-enable this when issue 16003 is fixed. - // test('emits events for many nested files moved out then immediately back in', - // () { - // withPermutations((i, j, k) => - // writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); - - // startWatcher(dir: "dir"); - - // renameDir("dir/sub", "sub"); - // renameDir("sub", "dir/sub"); - - // allowEither(() { - // inAnyOrder(withPermutations((i, j, k) => - // isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); - - // inAnyOrder(withPermutations((i, j, k) => - // isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); - // }, () { - // inAnyOrder(withPermutations((i, j, k) => - // isModifyEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); - // }); - // }); + test('emits events for many nested files moved out then immediately back in', + () { + withPermutations((i, j, k) => + writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); + + startWatcher(dir: "dir"); + + renameDir("dir/sub", "sub"); + renameDir("sub", "dir/sub"); + + allowEither(() { + inAnyOrder(withPermutations((i, j, k) => + isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + + inAnyOrder(withPermutations((i, j, k) => + isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + }, () { + inAnyOrder(withPermutations((i, j, k) => + isModifyEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + }); + }); } diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index f7e35f18a..885018bd1 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -116,7 +116,7 @@ ScheduledStream _watcherEvents; void startWatcher({String dir}) { var testCase = currentTestCase.description; if (MacOSDirectoryWatcher.logDebugInfo) { - print("starting watcher for $testCase"); + print("starting watcher for $testCase (${new DateTime.now()})"); } // We want to wait until we're ready *after* we subscribe to the watcher's @@ -129,7 +129,7 @@ void startWatcher({String dir}) { _watcherEvents = new ScheduledStream(futureStream(schedule(() { currentSchedule.onComplete.schedule(() { if (MacOSDirectoryWatcher.logDebugInfo) { - print("stopping watcher for $testCase"); + print("stopping watcher for $testCase (${new DateTime.now()})"); } _watcher = null; @@ -298,6 +298,9 @@ void writeFile(String path, {String contents, bool updateModified}) { dir.createSync(recursive: true); } + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[test] writing file $path"); + } new File(fullPath).writeAsStringSync(contents); // Manually update the mock modification time for the file. @@ -314,6 +317,9 @@ void writeFile(String path, {String contents, bool updateModified}) { /// Schedules deleting a file in the sandbox at [path]. void deleteFile(String path) { schedule(() { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[test] deleting file $path"); + } new File(p.join(_sandboxDir, path)).deleteSync(); }, "delete file $path"); } @@ -323,6 +329,10 @@ void deleteFile(String path) { /// If [contents] is omitted, creates an empty file. void renameFile(String from, String to) { schedule(() { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[test] renaming file $from to $to"); + } + new File(p.join(_sandboxDir, from)).renameSync(p.join(_sandboxDir, to)); // Make sure we always use the same separator on Windows. @@ -337,6 +347,9 @@ void renameFile(String from, String to) { /// Schedules creating a directory in the sandbox at [path]. void createDir(String path) { schedule(() { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[test] creating directory $path"); + } new Directory(p.join(_sandboxDir, path)).createSync(); }, "create directory $path"); } @@ -344,6 +357,9 @@ void createDir(String path) { /// Schedules renaming a directory in the sandbox from [from] to [to]. void renameDir(String from, String to) { schedule(() { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[test] renaming directory $from to $to"); + } new Directory(p.join(_sandboxDir, from)) .renameSync(p.join(_sandboxDir, to)); }, "rename directory $from to $to"); @@ -352,6 +368,9 @@ void renameDir(String from, String to) { /// Schedules deleting a directory in the sandbox at [path]. void deleteDir(String path) { schedule(() { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[test] deleting directory $path"); + } new Directory(p.join(_sandboxDir, path)).deleteSync(recursive: true); }, "delete directory $path"); } From d8c8244ce24f35a9da37c308ab687227e457e79b Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Wed, 15 Jan 2014 21:49:31 +0000 Subject: [PATCH 057/201] Take a broader approach to filtering out bogus Mac OS watcher events. With luck, this should unflake the test. R=rnystrom@google.com BUG=16079 Review URL: https://codereview.chromium.org//140013002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@31853 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/directory_watcher/mac_os.dart | 129 ++++++++++++------ pkgs/watcher/test/utils.dart | 1 - 2 files changed, 87 insertions(+), 43 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index bb82a4c3e..ba0ede4cb 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -50,14 +50,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { Future get ready => _readyCompleter.future; final _readyCompleter = new Completer(); - /// The number of event batches that have been received from - /// [Directory.watch]. - /// - /// This is used to determine if the [Directory.watch] stream was falsely - /// closed due to issue 14849. A close caused by events in the past will only - /// happen before or immediately after the first batch of events. - int _batches = 0; - /// The set of files that are known to exist recursively within the watched /// directory. /// @@ -73,11 +65,17 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// needs to be resubscribed in order to work around issue 14849. StreamSubscription _watchSubscription; - /// A set of subscriptions that this watcher subscribes to. - /// - /// These are gathered together so that they may all be canceled when the - /// watcher is closed. This does not include [_watchSubscription]. - final _subscriptions = new Set(); + /// The subscription to the [Directory.list] call for the initial listing of + /// the directory to determine its initial state. + StreamSubscription _initialListSubscription; + + /// The subscription to the [Directory.list] call for listing the contents of + /// a subdirectory that was moved into the watched directory. + StreamSubscription _listSubscription; + + /// The timer for tracking how long we wait for an initial batch of bogus + /// events (see issue 14373). + Timer _bogusEventTimer; _MacOSDirectoryWatcher(String directory, int parentId) : directory = directory, @@ -85,12 +83,20 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _id = "$parentId/${_count++}" { _startWatch(); - _listen(Chain.track(new Directory(directory).list(recursive: true)), - (entity) { - if (entity is! Directory) _files.add(entity.path); - }, - onError: _emitError, - onDone: () { + // Before we're ready to emit events, wait for [_listDir] to complete and + // for enough time to elapse that if bogus events (issue 14373) would be + // emitted, they will be. + // + // If we do receive a batch of events, [_onBatch] will ensure that these + // futures don't fire and that the directory is re-listed. + Future.wait([ + _listDir().then((_) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] finished initial directory list"); + } + }), + _waitForBogusEvents() + ]).then((_) { if (MacOSDirectoryWatcher.logDebugInfo) { print("[$_id] watcher is ready, known files:"); for (var file in _files.toSet()) { @@ -98,20 +104,19 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } } _readyCompleter.complete(); - }, - cancelOnError: true); + }); } void close() { if (MacOSDirectoryWatcher.logDebugInfo) { print("[$_id] watcher is closed"); } - for (var subscription in _subscriptions) { - subscription.cancel(); - } - _subscriptions.clear(); if (_watchSubscription != null) _watchSubscription.cancel(); + if (_initialListSubscription != null) _initialListSubscription.cancel(); + if (_listSubscription != null) _listSubscription.cancel(); _watchSubscription = null; + _initialListSubscription = null; + _listSubscription = null; _eventsController.close(); } @@ -129,7 +134,28 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } } - _batches++; + // If we get a batch of events before we're ready to begin emitting events, + // it's probable that it's a batch of pre-watcher events (see issue 14373). + // Ignore those events and re-list the directory. + if (!isReady) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] not ready to emit events, re-listing directory"); + } + + // Cancel the timer because bogus events only occur in the first batch, so + // we can fire [ready] as soon as we're done listing the directory. + _bogusEventTimer.cancel(); + _listDir().then((_) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] watcher is ready, known files:"); + for (var file in _files.toSet()) { + print("[$_id] ${p.relative(file, from: directory)}"); + } + } + _readyCompleter.complete(); + }); + return; + } _sortEvents(batch).forEach((path, events) { var relativePath = p.relative(path, from: directory); @@ -165,8 +191,8 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (_files.containsDir(path)) continue; - _listen(Chain.track(new Directory(path).list(recursive: true)), - (entity) { + var stream = Chain.track(new Directory(path).list(recursive: true)); + _listSubscription = stream.listen((entity) { if (entity is Directory) return; if (_files.contains(path)) return; @@ -366,10 +392,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _watchSubscription = null; - // If the directory still exists and we haven't seen more than one batch, + // If the directory still exists and we're still expecting bogus events, // this is probably issue 14849 rather than a real close event. We should // just restart the watcher. - if (_batches < 2 && new Directory(directory).existsSync()) { + if (!isReady && new Directory(directory).existsSync()) { if (MacOSDirectoryWatcher.logDebugInfo) { print("[$_id] fake closure (issue 14849), re-opening stream"); } @@ -398,6 +424,37 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { onDone: _onDone); } + /// Starts or restarts listing the watched directory to get an initial picture + /// of its state. + Future _listDir() { + assert(!isReady); + if (_initialListSubscription != null) _initialListSubscription.cancel(); + + _files.clear(); + var completer = new Completer(); + var stream = Chain.track(new Directory(directory).list(recursive: true)); + _initialListSubscription = stream.listen((entity) { + if (entity is! Directory) _files.add(entity.path); + }, + onError: _emitError, + onDone: completer.complete, + cancelOnError: true); + return completer.future; + } + + /// Wait 200ms for a batch of bogus events (issue 14373) to come in. + /// + /// 200ms is short in terms of human interaction, but longer than any Mac OS + /// watcher tests take on the bots, so it should be safe to assume that any + /// bogus events will be signaled in that time frame. + Future _waitForBogusEvents() { + var completer = new Completer(); + _bogusEventTimer = new Timer( + new Duration(milliseconds: 200), + completer.complete); + return completer.future; + } + /// Emit an event with the given [type] and [path]. void _emitEvent(ChangeType type, String path) { if (!isReady) return; @@ -415,18 +472,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { close(); } - /// Like [Stream.listen], but automatically adds the subscription to - /// [_subscriptions] so that it can be canceled when [close] is called. - void _listen(Stream stream, void onData(event), {Function onError, - void onDone(), bool cancelOnError}) { - var subscription; - subscription = stream.listen(onData, onError: onError, onDone: () { - _subscriptions.remove(subscription); - if (onDone != null) onDone(); - }, cancelOnError: cancelOnError); - _subscriptions.add(subscription); - } - // TODO(nweiz): remove this when issue 15042 is fixed. /// Return a human-friendly string representation of [event]. String _formatEvent(FileSystemEvent event) { diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 885018bd1..b520bbd9a 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -5,7 +5,6 @@ library watcher.test.utils; import 'dart:async'; -import 'dart:collection'; import 'dart:io'; import 'package:path/path.dart' as p; From 3e9d97362d82040d44c247a736e52ad4b92c1942 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Wed, 29 Jan 2014 21:06:50 +0000 Subject: [PATCH 058/201] Add some additional debugging prints to the mac os watcher. R=rnystrom@google.com Review URL: https://codereview.chromium.org//146693014 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@32139 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/directory_watcher/mac_os.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index ba0ede4cb..e3efa2dd6 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -109,7 +109,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { void close() { if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] watcher is closed"); + print("[$_id] watcher is closed\n${new Chain.current().terse}"); } if (_watchSubscription != null) _watchSubscription.cancel(); if (_initialListSubscription != null) _initialListSubscription.cancel(); @@ -468,6 +468,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// Emit an error, then close the watcher. void _emitError(error, StackTrace stackTrace) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] emitting error: $error\n" + + "${new Chain.forTrace(stackTrace).terse}"); + } _eventsController.addError(error, stackTrace); close(); } From 94acc441474c44a25971b64ea998827e3109a5fe Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 18 Feb 2014 22:56:58 +0000 Subject: [PATCH 059/201] Change ScheduledProcess's output streams over to be ScheduledStreams. This will help test "pub serve" with multiple entrypoints. R=rnystrom@google.com BUG= Review URL: https://codereview.chromium.org//164773003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@32770 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index bf00d9e31..1045bc56b 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -9,7 +9,7 @@ dependencies: path: ">=0.9.0 <2.0.0" stack_trace: ">=0.9.1 <0.10.0" dev_dependencies: - scheduled_test: ">=0.9.3-dev <0.10.0" + scheduled_test: ">=0.9.3-dev <0.11.0" unittest: ">=0.9.2 <0.10.0" environment: sdk: ">=0.8.10+6 <2.0.0" From 9e3e710cb2812177b51e68e361b59bbf65e35e67 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 2 Jun 2014 18:40:25 +0000 Subject: [PATCH 060/201] Release stack_trace 1.0.0. R=kevmoo@google.com, rnystrom@google.com Review URL: https://codereview.chromium.org//308763003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@36891 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 1045bc56b..51ba562a6 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -7,7 +7,7 @@ description: > notifies you when files have been added, removed, or modified. dependencies: path: ">=0.9.0 <2.0.0" - stack_trace: ">=0.9.1 <0.10.0" + stack_trace: ">=0.9.1 <2.0.0" dev_dependencies: scheduled_test: ">=0.9.3-dev <0.11.0" unittest: ">=0.9.2 <0.10.0" From 0de56c44b1d3896c0e119e4927b19152fc39fd96 Mon Sep 17 00:00:00 2001 From: "ajohnsen@google.com" Date: Wed, 4 Jun 2014 10:23:32 +0000 Subject: [PATCH 061/201] Use 'Directory.watch' on Windows in pkg/watcher, instead of pooling. This is a close copy of MacOS, except for the special handling on mac, and the extra watcher on Windows for detecting if the watched folder is deleted. BUG=https://code.google.com/p/dart/issues/detail?id=14428,https://code.google.com/p/dart/issues/detail?id=18108,http://code.google.com/p/dart/issues/detail?id=19189 R=kasperl@google.com, sgjesse@google.com Review URL: https://codereview.chromium.org//312743002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@36988 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/directory_watcher.dart | 2 + .../lib/src/directory_watcher/windows.dart | 419 ++++++++++++++++++ .../test/directory_watcher/windows_test.dart | 41 ++ pkgs/watcher/test/utils.dart | 5 + 4 files changed, 467 insertions(+) create mode 100644 pkgs/watcher/lib/src/directory_watcher/windows.dart create mode 100644 pkgs/watcher/test/directory_watcher/windows_test.dart diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 78b1174e1..27dbb3f51 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -10,6 +10,7 @@ import 'dart:io'; import 'watch_event.dart'; import 'directory_watcher/linux.dart'; import 'directory_watcher/mac_os.dart'; +import 'directory_watcher/windows.dart'; import 'directory_watcher/polling.dart'; /// Watches the contents of a directory and emits [WatchEvent]s when something @@ -56,6 +57,7 @@ abstract class DirectoryWatcher { factory DirectoryWatcher(String directory, {Duration pollingDelay}) { if (Platform.isLinux) return new LinuxDirectoryWatcher(directory); if (Platform.isMacOS) return new MacOSDirectoryWatcher(directory); + if (Platform.isWindows) return new WindowsDirectoryWatcher(directory); return new PollingDirectoryWatcher(directory, pollingDelay: pollingDelay); } } diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart new file mode 100644 index 000000000..ddcf897d3 --- /dev/null +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -0,0 +1,419 @@ +// Copyright (c) 2014, 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. +// TODO(rnystrom): Merge with mac_os version. + +library watcher.directory_watcher.windows; + +import 'dart:async'; +import 'dart:collection'; +import 'dart:io'; + +import 'package:path/path.dart' as p; +import 'package:stack_trace/stack_trace.dart'; + +import '../constructable_file_system_event.dart'; +import '../path_set.dart'; +import '../utils.dart'; +import '../watch_event.dart'; +import 'resubscribable.dart'; + +class WindowsDirectoryWatcher extends ResubscribableDirectoryWatcher { + WindowsDirectoryWatcher(String directory) + : super(directory, () => new _WindowsDirectoryWatcher(directory)); +} + +class _EventBatcher { + static const Duration _BATCH_DELAY = const Duration(milliseconds: 100); + final List events = []; + Timer timer; + + void addEvent(FileSystemEvent event) { + events.add(event); + } + + void startTimer(void callback()) { + if (timer != null) { + timer.cancel(); + } + timer = new Timer(_BATCH_DELAY, callback); + } + + void cancelTimer() { + timer.cancel(); + } +} + +class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { + final String directory; + + Stream get events => _eventsController.stream; + final _eventsController = new StreamController.broadcast(); + + bool get isReady => _readyCompleter.isCompleted; + + Future get ready => _readyCompleter.future; + final _readyCompleter = new Completer(); + + final Map _eventBatchers = + new HashMap(); + + /// The set of files that are known to exist recursively within the watched + /// directory. + /// + /// The state of files on the filesystem is compared against this to determine + /// the real change that occurred. This is also used to emit REMOVE events + /// when subdirectories are moved out of the watched directory. + final PathSet _files; + + /// The subscription to the stream returned by [Directory.watch]. + StreamSubscription _watchSubscription; + + /// The subscription to the stream returned by [Directory.watch] of the + /// parent directory to [directory]. This is needed to detect changes to + /// [directory], as they are not included on Windows. + StreamSubscription _parentWatchSubscription; + + /// The subscription to the [Directory.list] call for the initial listing of + /// the directory to determine its initial state. + StreamSubscription _initialListSubscription; + + /// The subscriptions to the [Directory.list] call for listing the contents of + /// subdirectories that was moved into the watched directory. + final Set> _listSubscriptions + = new HashSet>(); + + _WindowsDirectoryWatcher(String directory) + : directory = directory, _files = new PathSet(directory) { + _startWatch(); + _startParentWatcher(); + + // Before we're ready to emit events, wait for [_listDir] to complete. + _listDir().then(_readyCompleter.complete); + } + + void close() { + if (_watchSubscription != null) _watchSubscription.cancel(); + if (_parentWatchSubscription != null) _parentWatchSubscription.cancel(); + if (_initialListSubscription != null) _initialListSubscription.cancel(); + for (var sub in _listSubscriptions) { + sub.cancel(); + } + _listSubscriptions.clear(); + for (var batcher in _eventBatchers.values) { + batcher.cancelTimer(); + } + _eventBatchers.clear(); + _watchSubscription = null; + _parentWatchSubscription = null; + _initialListSubscription = null; + _eventsController.close(); + } + + /// On Windows, if [directory] is deleted, we will not receive any event. + /// Instead, we add a watcher on the parent folder (if any), that can notify + /// us about [directory]. + /// This also includes events such as moves. + void _startParentWatcher() { + var absoluteDir = p.absolute(directory); + var parent = p.dirname(absoluteDir); + // Check if we [directory] is already the root directory. + if (FileSystemEntity.identicalSync(parent, directory)) return; + var parentStream = Chain.track( + new Directory(parent).watch(recursive: false)); + _parentWatchSubscription = parentStream.listen((event) { + // Only look at events for 'directory'. + if (p.basename(event.path) != p.basename(absoluteDir)) return; + // Test if the directory is removed. FileSystemEntity.typeSync will + // return NOT_FOUND if it's unable to decide upon the type, including + // access denied issues, which may happen when the directory is deleted. + // FileSystemMoveEvent and FileSystemDeleteEvent events will always mean + // the directory is now gone. + if (event is FileSystemMoveEvent || + event is FileSystemDeleteEvent || + (FileSystemEntity.typeSync(directory) == + FileSystemEntityType.NOT_FOUND)) { + for (var path in _files.toSet()) { + _emitEvent(ChangeType.REMOVE, path); + } + _files.clear(); + close(); + } + }, onError: (error) { + // Ignore errors, simply close the stream. + _parentWatchSubscription.cancel(); + _parentWatchSubscription = null; + }); + } + + void _onEvent(FileSystemEvent event) { + // If we get a event before we're ready to begin emitting events, + // ignore those events and re-list the directory. + if (!isReady) { + _listDir().then((_) { + _readyCompleter.complete(); + }); + return; + } + + _EventBatcher batcher = _eventBatchers.putIfAbsent( + event.path, () => new _EventBatcher()); + batcher.addEvent(event); + batcher.startTimer(() { + _eventBatchers.remove(event.path); + _onBatch(batcher.events); + }); + } + + /// The callback that's run when [Directory.watch] emits a batch of events. + void _onBatch(List batch) { + _sortEvents(batch).forEach((path, events) { + var relativePath = p.relative(path, from: directory); + + var canonicalEvent = _canonicalEvent(events); + events = canonicalEvent == null ? + _eventsBasedOnFileSystem(path) : [canonicalEvent]; + + for (var event in events) { + if (event is FileSystemCreateEvent) { + if (!event.isDirectory) { + if (_files.contains(path)) continue; + + _emitEvent(ChangeType.ADD, path); + _files.add(path); + continue; + } + + if (_files.containsDir(path)) continue; + + var stream = Chain.track(new Directory(path).list(recursive: true)); + var sub; + sub = stream.listen((entity) { + if (entity is Directory) return; + if (_files.contains(path)) return; + + _emitEvent(ChangeType.ADD, entity.path); + _files.add(entity.path); + }, onDone: () { + _listSubscriptions.remove(sub); + }, onError: (e, stackTrace) { + _listSubscriptions.remove(sub); + _emitError(e, stackTrace); + }, cancelOnError: true); + _listSubscriptions.add(sub); + } else if (event is FileSystemModifyEvent) { + if (!event.isDirectory) { + _emitEvent(ChangeType.MODIFY, path); + } + } else { + assert(event is FileSystemDeleteEvent); + for (var removedPath in _files.remove(path)) { + _emitEvent(ChangeType.REMOVE, removedPath); + } + } + } + }); + } + + /// Sort all the events in a batch into sets based on their path. + /// + /// A single input event may result in multiple events in the returned map; + /// for example, a MOVE event becomes a DELETE event for the source and a + /// CREATE event for the destination. + /// + /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it + /// contain any events relating to [directory]. + Map> _sortEvents(List batch) { + var eventsForPaths = {}; + + // Events within directories that already have events are superfluous; the + // directory's full contents will be examined anyway, so we ignore such + // events. Emitting them could cause useless or out-of-order events. + var directories = unionAll(batch.map((event) { + if (!event.isDirectory) return new Set(); + if (event is! FileSystemMoveEvent) return new Set.from([event.path]); + return new Set.from([event.path, event.destination]); + })); + + isInModifiedDirectory(path) => + directories.any((dir) => path != dir && path.startsWith(dir)); + + addEvent(path, event) { + if (isInModifiedDirectory(path)) return; + var set = eventsForPaths.putIfAbsent(path, () => new Set()); + set.add(event); + } + + for (var event in batch) { + if (event is FileSystemMoveEvent) { + FileSystemMoveEvent moveEvent = event; + addEvent(moveEvent.destination, event); + } + addEvent(event.path, event); + } + + return eventsForPaths; + } + + /// Returns the canonical event from a batch of events on the same path, if + /// one exists. + /// + /// If [batch] doesn't contain any contradictory events (e.g. DELETE and + /// CREATE, or events with different values for [isDirectory]), this returns a + /// single event that describes what happened to the path in question. + /// + /// If [batch] does contain contradictory events, this returns `null` to + /// indicate that the state of the path on the filesystem should be checked to + /// determine what occurred. + FileSystemEvent _canonicalEvent(Set batch) { + // An empty batch indicates that we've learned earlier that the batch is + // contradictory (e.g. because of a move). + if (batch.isEmpty) return null; + + var type = batch.first.type; + var isDir = batch.first.isDirectory; + var hadModifyEvent = false; + + for (var event in batch.skip(1)) { + // If one event reports that the file is a directory and another event + // doesn't, that's a contradiction. + if (isDir != event.isDirectory) return null; + + // Modify events don't contradict either CREATE or REMOVE events. We can + // safely assume the file was modified after a CREATE or before the + // REMOVE; otherwise there will also be a REMOVE or CREATE event + // (respectively) that will be contradictory. + if (event is FileSystemModifyEvent) { + hadModifyEvent = true; + continue; + } + assert(event is FileSystemCreateEvent || + event is FileSystemDeleteEvent || + event is FileSystemMoveEvent); + + // If we previously thought this was a MODIFY, we now consider it to be a + // CREATE or REMOVE event. This is safe for the same reason as above. + if (type == FileSystemEvent.MODIFY) { + type = event.type; + continue; + } + + // A CREATE event contradicts a REMOVE event and vice versa. + assert(type == FileSystemEvent.CREATE || + type == FileSystemEvent.DELETE || + type == FileSystemEvent.MOVE); + if (type != event.type) return null; + } + + switch (type) { + case FileSystemEvent.CREATE: + return new ConstructableFileSystemCreateEvent(batch.first.path, isDir); + case FileSystemEvent.DELETE: + return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir); + case FileSystemEvent.MODIFY: + return new ConstructableFileSystemModifyEvent( + batch.first.path, isDir, false); + case FileSystemEvent.MOVE: + return null; + default: assert(false); + } + } + + /// Returns one or more events that describe the change between the last known + /// state of [path] and its current state on the filesystem. + /// + /// This returns a list whose order should be reflected in the events emitted + /// to the user, unlike the batched events from [Directory.watch]. The + /// returned list may be empty, indicating that no changes occurred to [path] + /// (probably indicating that it was created and then immediately deleted). + List _eventsBasedOnFileSystem(String path) { + var fileExisted = _files.contains(path); + var dirExisted = _files.containsDir(path); + var fileExists = new File(path).existsSync(); + var dirExists = new Directory(path).existsSync(); + + var events = []; + if (fileExisted) { + if (fileExists) { + events.add(new ConstructableFileSystemModifyEvent(path, false, false)); + } else { + events.add(new ConstructableFileSystemDeleteEvent(path, false)); + } + } else if (dirExisted) { + if (dirExists) { + // If we got contradictory events for a directory that used to exist and + // still exists, we need to rescan the whole thing in case it was + // replaced with a different directory. + events.add(new ConstructableFileSystemDeleteEvent(path, true)); + events.add(new ConstructableFileSystemCreateEvent(path, true)); + } else { + events.add(new ConstructableFileSystemDeleteEvent(path, true)); + } + } + + if (!fileExisted && fileExists) { + events.add(new ConstructableFileSystemCreateEvent(path, false)); + } else if (!dirExisted && dirExists) { + events.add(new ConstructableFileSystemCreateEvent(path, true)); + } + + return events; + } + + /// The callback that's run when the [Directory.watch] stream is closed. + /// Note that this is unlikely to happen on Windows, unless the system itself + /// closes the handle. + void _onDone() { + _watchSubscription = null; + + // Emit remove-events for any remaining files. + for (var file in _files.toSet()) { + _emitEvent(ChangeType.REMOVE, file); + } + _files.clear(); + close(); + } + + /// Start or restart the underlying [Directory.watch] stream. + void _startWatch() { + // Batch the events changes together so that we can dedup events. + var innerStream = + Chain.track(new Directory(directory).watch(recursive: true)); + _watchSubscription = innerStream.listen(_onEvent, + onError: _eventsController.addError, + onDone: _onDone); + } + + /// Starts or restarts listing the watched directory to get an initial picture + /// of its state. + Future _listDir() { + assert(!isReady); + if (_initialListSubscription != null) _initialListSubscription.cancel(); + + _files.clear(); + var completer = new Completer(); + var stream = Chain.track(new Directory(directory).list(recursive: true)); + void handleEntity(entity) { + if (entity is! Directory) _files.add(entity.path); + } + _initialListSubscription = stream.listen( + handleEntity, + onError: _emitError, + onDone: completer.complete, + cancelOnError: true); + return completer.future; + } + + /// Emit an event with the given [type] and [path]. + void _emitEvent(ChangeType type, String path) { + if (!isReady) return; + + _eventsController.add(new WatchEvent(type, path)); + } + + /// Emit an error, then close the watcher. + void _emitError(error, StackTrace stackTrace) { + _eventsController.addError(error, stackTrace); + close(); + } +} diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart new file mode 100644 index 000000000..3e9a854a9 --- /dev/null +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -0,0 +1,41 @@ +// Copyright (c) 2014, 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. + +import 'package:path/path.dart' as p; +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/src/directory_watcher/windows.dart'; +import 'package:watcher/watcher.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +main() { + initConfig(); + + watcherFactory = (dir) => new WindowsDirectoryWatcher(dir); + + setUp(createSandbox); + + sharedTests(); + + test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () { + expect(new DirectoryWatcher('.'), + new isInstanceOf()); + }); + + test('when the watched directory is moved, removes all files', () { + writeFile("dir/a.txt"); + writeFile("dir/b.txt"); + + startWatcher(dir: "dir"); + + renameDir("dir", "moved_dir"); + createDir("dir"); + inAnyOrder([ + isRemoveEvent("dir/a.txt"), + isRemoveEvent("dir/b.txt") + ]); + }); +} + diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index b520bbd9a..8b660e845 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -71,6 +71,11 @@ void createSandbox() { // Delete the sandbox when done. currentSchedule.onComplete.schedule(() { if (_sandboxDir != null) { + // TODO(rnystrom): Issue 19155. The watcher should already be closed when + // we clean up the sandbox. + if (_watcherEvents != null) { + _watcherEvents.close(); + } new Directory(_sandboxDir).deleteSync(recursive: true); _sandboxDir = null; } From c6b489a4e144770dc411dede5d12177775f56108 Mon Sep 17 00:00:00 2001 From: "ajohnsen@google.com" Date: Wed, 4 Jun 2014 17:57:45 +0000 Subject: [PATCH 062/201] Fall back to PollingDirectoryWatcher if FileSystemEntity:watch is not supported. BUG= R=rnystrom@google.com Review URL: https://codereview.chromium.org//313743004 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@37003 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/directory_watcher.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 27dbb3f51..605eaea48 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -55,9 +55,11 @@ abstract class DirectoryWatcher { /// and higher CPU usage. Defaults to one second. Ignored for non-polling /// watchers. factory DirectoryWatcher(String directory, {Duration pollingDelay}) { - if (Platform.isLinux) return new LinuxDirectoryWatcher(directory); - if (Platform.isMacOS) return new MacOSDirectoryWatcher(directory); - if (Platform.isWindows) return new WindowsDirectoryWatcher(directory); + if (FileSystemEntity.isWatchSupported) { + if (Platform.isLinux) return new LinuxDirectoryWatcher(directory); + if (Platform.isMacOS) return new MacOSDirectoryWatcher(directory); + if (Platform.isWindows) return new WindowsDirectoryWatcher(directory); + } return new PollingDirectoryWatcher(directory, pollingDelay: pollingDelay); } } From 271ee990408bae4c0f57ca39c5a251b82c92299e Mon Sep 17 00:00:00 2001 From: "ajohnsen@google.com" Date: Tue, 10 Jun 2014 06:48:31 +0000 Subject: [PATCH 063/201] Clean up the Windows watcher. BUG= R=rnystrom@google.com Review URL: https://codereview.chromium.org//313353003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@37159 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/directory_watcher/windows.dart | 50 ++++++++----------- .../test/directory_watcher/shared.dart | 14 ++++++ .../test/directory_watcher/windows_test.dart | 14 ------ 3 files changed, 34 insertions(+), 44 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index ddcf897d3..4f41d3395 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -28,11 +28,8 @@ class _EventBatcher { final List events = []; Timer timer; - void addEvent(FileSystemEvent event) { + void addEvent(FileSystemEvent event, void callback()) { events.add(event); - } - - void startTimer(void callback()) { if (timer != null) { timer.cancel(); } @@ -78,18 +75,19 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// the directory to determine its initial state. StreamSubscription _initialListSubscription; - /// The subscriptions to the [Directory.list] call for listing the contents of - /// subdirectories that was moved into the watched directory. + /// The subscriptions to the [Directory.list] calls for listing the contents + /// of subdirectories that were moved into the watched directory. final Set> _listSubscriptions = new HashSet>(); _WindowsDirectoryWatcher(String directory) : directory = directory, _files = new PathSet(directory) { - _startWatch(); - _startParentWatcher(); - // Before we're ready to emit events, wait for [_listDir] to complete. - _listDir().then(_readyCompleter.complete); + _listDir().then((_) { + _startWatch(); + _startParentWatcher(); + _readyCompleter.complete(); + }); } void close() { @@ -111,13 +109,13 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } /// On Windows, if [directory] is deleted, we will not receive any event. + /// /// Instead, we add a watcher on the parent folder (if any), that can notify - /// us about [directory]. - /// This also includes events such as moves. + /// us about [directory]. This also includes events such as moves. void _startParentWatcher() { var absoluteDir = p.absolute(directory); var parent = p.dirname(absoluteDir); - // Check if we [directory] is already the root directory. + // Check if [directory] is already the root directory. if (FileSystemEntity.identicalSync(parent, directory)) return; var parentStream = Chain.track( new Directory(parent).watch(recursive: false)); @@ -140,26 +138,19 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { close(); } }, onError: (error) { - // Ignore errors, simply close the stream. + // Ignore errors, simply close the stream. The user listens on + // [directory], and while it can fail to listen on the parent, we may + // still be able to listen on the path requested. _parentWatchSubscription.cancel(); _parentWatchSubscription = null; }); } void _onEvent(FileSystemEvent event) { - // If we get a event before we're ready to begin emitting events, - // ignore those events and re-list the directory. - if (!isReady) { - _listDir().then((_) { - _readyCompleter.complete(); - }); - return; - } - - _EventBatcher batcher = _eventBatchers.putIfAbsent( + assert(isReady); + final batcher = _eventBatchers.putIfAbsent( event.path, () => new _EventBatcher()); - batcher.addEvent(event); - batcher.startTimer(() { + batcher.addEvent(event, () { _eventBatchers.remove(event.path); _onBatch(batcher.events); }); @@ -246,8 +237,7 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { for (var event in batch) { if (event is FileSystemMoveEvent) { - FileSystemMoveEvent moveEvent = event; - addEvent(moveEvent.destination, event); + addEvent(event.destination, event); } addEvent(event.path, event); } @@ -366,7 +356,7 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { void _onDone() { _watchSubscription = null; - // Emit remove-events for any remaining files. + // Emit remove events for any remaining files. for (var file in _files.toSet()) { _emitEvent(ChangeType.REMOVE, file); } @@ -376,7 +366,7 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// Start or restart the underlying [Directory.watch] stream. void _startWatch() { - // Batch the events changes together so that we can dedup events. + // Batch the events together so that we can dedup events. var innerStream = Chain.track(new Directory(directory).watch(recursive: true)); _watchSubscription = innerStream.listen(_onEvent, diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index 849cea00d..d3575ea4d 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -78,6 +78,20 @@ sharedTests() { ]); }); + test('when the watched directory is moved, removes all files', () { + writeFile("dir/a.txt"); + writeFile("dir/b.txt"); + + startWatcher(dir: "dir"); + + renameDir("dir", "moved_dir"); + createDir("dir"); + inAnyOrder([ + isRemoveEvent("dir/a.txt"), + isRemoveEvent("dir/b.txt") + ]); + }); + group("moves", () { test('notifies when a file is moved within the watched directory', () { writeFile("old.txt"); diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 3e9a854a9..6bfb88b44 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -23,19 +23,5 @@ main() { expect(new DirectoryWatcher('.'), new isInstanceOf()); }); - - test('when the watched directory is moved, removes all files', () { - writeFile("dir/a.txt"); - writeFile("dir/b.txt"); - - startWatcher(dir: "dir"); - - renameDir("dir", "moved_dir"); - createDir("dir"); - inAnyOrder([ - isRemoveEvent("dir/a.txt"), - isRemoveEvent("dir/b.txt") - ]); - }); } From 33592ea196c412b640dd0a3eeb3ced12a7eb0c55 Mon Sep 17 00:00:00 2001 From: "ajohnsen@google.com" Date: Tue, 10 Jun 2014 07:03:53 +0000 Subject: [PATCH 064/201] Mac no longer fire bogus events. Fix Mac watcher. BUG= R=nweiz@google.com Review URL: https://codereview.chromium.org//319433003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@37162 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/directory_watcher/mac_os.dart | 72 ++----------------- pkgs/watcher/pubspec.yaml | 4 +- 2 files changed, 7 insertions(+), 69 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index e3efa2dd6..65f07fa92 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -73,30 +73,15 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// a subdirectory that was moved into the watched directory. StreamSubscription _listSubscription; - /// The timer for tracking how long we wait for an initial batch of bogus - /// events (see issue 14373). - Timer _bogusEventTimer; - _MacOSDirectoryWatcher(String directory, int parentId) : directory = directory, _files = new PathSet(directory), _id = "$parentId/${_count++}" { - _startWatch(); - - // Before we're ready to emit events, wait for [_listDir] to complete and - // for enough time to elapse that if bogus events (issue 14373) would be - // emitted, they will be. - // - // If we do receive a batch of events, [_onBatch] will ensure that these - // futures don't fire and that the directory is re-listed. - Future.wait([ - _listDir().then((_) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] finished initial directory list"); - } - }), - _waitForBogusEvents() - ]).then((_) { + _listDir().then((_) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] finished initial directory list"); + } + _startWatch(); if (MacOSDirectoryWatcher.logDebugInfo) { print("[$_id] watcher is ready, known files:"); for (var file in _files.toSet()) { @@ -134,29 +119,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } } - // If we get a batch of events before we're ready to begin emitting events, - // it's probable that it's a batch of pre-watcher events (see issue 14373). - // Ignore those events and re-list the directory. - if (!isReady) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] not ready to emit events, re-listing directory"); - } - - // Cancel the timer because bogus events only occur in the first batch, so - // we can fire [ready] as soon as we're done listing the directory. - _bogusEventTimer.cancel(); - _listDir().then((_) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] watcher is ready, known files:"); - for (var file in _files.toSet()) { - print("[$_id] ${p.relative(file, from: directory)}"); - } - } - _readyCompleter.complete(); - }); - return; - } - _sortEvents(batch).forEach((path, events) { var relativePath = p.relative(path, from: directory); if (MacOSDirectoryWatcher.logDebugInfo) { @@ -392,17 +354,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _watchSubscription = null; - // If the directory still exists and we're still expecting bogus events, - // this is probably issue 14849 rather than a real close event. We should - // just restart the watcher. - if (!isReady && new Directory(directory).existsSync()) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] fake closure (issue 14849), re-opening stream"); - } - _startWatch(); - return; - } - // FSEvents can fail to report the contents of the directory being removed // when the directory itself is removed, so we need to manually mark the // files as removed. @@ -442,19 +393,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { return completer.future; } - /// Wait 200ms for a batch of bogus events (issue 14373) to come in. - /// - /// 200ms is short in terms of human interaction, but longer than any Mac OS - /// watcher tests take on the bots, so it should be safe to assume that any - /// bogus events will be signaled in that time frame. - Future _waitForBogusEvents() { - var completer = new Completer(); - _bogusEventTimer = new Timer( - new Duration(milliseconds: 200), - completer.complete); - return completer.future; - } - /// Emit an event with the given [type] and [path]. void _emitEvent(ChangeType type, String path) { if (!isReady) return; diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 51ba562a6..a0e0ce4e2 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.3-dev +version: 0.9.4-dev author: "Dart Team " homepage: http://www.dartlang.org description: > @@ -12,4 +12,4 @@ dev_dependencies: scheduled_test: ">=0.9.3-dev <0.11.0" unittest: ">=0.9.2 <0.10.0" environment: - sdk: ">=0.8.10+6 <2.0.0" + sdk: ">=1.3.0 <2.0.0" From e213974f86b614e96b6a48efb070a2e259645e8f Mon Sep 17 00:00:00 2001 From: "ajohnsen@google.com" Date: Tue, 10 Jun 2014 10:44:20 +0000 Subject: [PATCH 065/201] Don't use kFSEventStreamCreateFlagNoDefer as it messes with event flushing. BUG= R=sgjesse@google.com Review URL: https://codereview.chromium.org//321133002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@37171 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index a0e0ce4e2..adf8ddb6f 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -12,4 +12,4 @@ dev_dependencies: scheduled_test: ">=0.9.3-dev <0.11.0" unittest: ">=0.9.2 <0.10.0" environment: - sdk: ">=1.3.0 <2.0.0" + sdk: ">=1.6.0 <2.0.0" From 16349628fa49955c4ecff4858ac53e700c55d085 Mon Sep 17 00:00:00 2001 From: "ajohnsen@google.com" Date: Tue, 10 Jun 2014 12:35:56 +0000 Subject: [PATCH 066/201] Revert watcher changes for mac. We ended up with a 1.6 dependency, which I don't know if is a good idea or not. Revert "Mac no longer fire bogus events. Fix Mac watcher." Revert "Don't use kFSEventStreamCreateFlagNoDefer as it messes with event flushing." BUG= Review URL: https://codereview.chromium.org//325963005 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@37173 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/directory_watcher/mac_os.dart | 72 +++++++++++++++++-- pkgs/watcher/pubspec.yaml | 4 +- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 65f07fa92..e3efa2dd6 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -73,15 +73,30 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// a subdirectory that was moved into the watched directory. StreamSubscription _listSubscription; + /// The timer for tracking how long we wait for an initial batch of bogus + /// events (see issue 14373). + Timer _bogusEventTimer; + _MacOSDirectoryWatcher(String directory, int parentId) : directory = directory, _files = new PathSet(directory), _id = "$parentId/${_count++}" { - _listDir().then((_) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] finished initial directory list"); - } - _startWatch(); + _startWatch(); + + // Before we're ready to emit events, wait for [_listDir] to complete and + // for enough time to elapse that if bogus events (issue 14373) would be + // emitted, they will be. + // + // If we do receive a batch of events, [_onBatch] will ensure that these + // futures don't fire and that the directory is re-listed. + Future.wait([ + _listDir().then((_) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] finished initial directory list"); + } + }), + _waitForBogusEvents() + ]).then((_) { if (MacOSDirectoryWatcher.logDebugInfo) { print("[$_id] watcher is ready, known files:"); for (var file in _files.toSet()) { @@ -119,6 +134,29 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } } + // If we get a batch of events before we're ready to begin emitting events, + // it's probable that it's a batch of pre-watcher events (see issue 14373). + // Ignore those events and re-list the directory. + if (!isReady) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] not ready to emit events, re-listing directory"); + } + + // Cancel the timer because bogus events only occur in the first batch, so + // we can fire [ready] as soon as we're done listing the directory. + _bogusEventTimer.cancel(); + _listDir().then((_) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] watcher is ready, known files:"); + for (var file in _files.toSet()) { + print("[$_id] ${p.relative(file, from: directory)}"); + } + } + _readyCompleter.complete(); + }); + return; + } + _sortEvents(batch).forEach((path, events) { var relativePath = p.relative(path, from: directory); if (MacOSDirectoryWatcher.logDebugInfo) { @@ -354,6 +392,17 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _watchSubscription = null; + // If the directory still exists and we're still expecting bogus events, + // this is probably issue 14849 rather than a real close event. We should + // just restart the watcher. + if (!isReady && new Directory(directory).existsSync()) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] fake closure (issue 14849), re-opening stream"); + } + _startWatch(); + return; + } + // FSEvents can fail to report the contents of the directory being removed // when the directory itself is removed, so we need to manually mark the // files as removed. @@ -393,6 +442,19 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { return completer.future; } + /// Wait 200ms for a batch of bogus events (issue 14373) to come in. + /// + /// 200ms is short in terms of human interaction, but longer than any Mac OS + /// watcher tests take on the bots, so it should be safe to assume that any + /// bogus events will be signaled in that time frame. + Future _waitForBogusEvents() { + var completer = new Completer(); + _bogusEventTimer = new Timer( + new Duration(milliseconds: 200), + completer.complete); + return completer.future; + } + /// Emit an event with the given [type] and [path]. void _emitEvent(ChangeType type, String path) { if (!isReady) return; diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index adf8ddb6f..51ba562a6 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.4-dev +version: 0.9.3-dev author: "Dart Team " homepage: http://www.dartlang.org description: > @@ -12,4 +12,4 @@ dev_dependencies: scheduled_test: ">=0.9.3-dev <0.11.0" unittest: ">=0.9.2 <0.10.0" environment: - sdk: ">=1.6.0 <2.0.0" + sdk: ">=0.8.10+6 <2.0.0" From b5dd0d76d385aeae3fcbf4fd8ad1a788779561bf Mon Sep 17 00:00:00 2001 From: "ajohnsen@google.com" Date: Mon, 4 Aug 2014 07:25:05 +0000 Subject: [PATCH 067/201] Add check to watcher, to avoid null-error if unknown path is removed. BUG= R=kevmoo@google.com Review URL: https://codereview.chromium.org//428493004 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@38846 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/lib/src/path_set.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/lib/src/path_set.dart b/pkgs/watcher/lib/src/path_set.dart index 01d4208f4..e9f7d32d2 100644 --- a/pkgs/watcher/lib/src/path_set.dart +++ b/pkgs/watcher/lib/src/path_set.dart @@ -71,7 +71,7 @@ class PathSet { // the next level. var part = parts.removeFirst(); var entry = dir[part]; - if (entry.isEmpty) return new Set(); + if (entry == null || entry.isEmpty) return new Set(); partialPath = p.join(partialPath, part); var paths = recurse(entry, partialPath); From 747f891829ed7cd173449ffdbd51d50c17404fb0 Mon Sep 17 00:00:00 2001 From: "kevmoo@google.com" Date: Mon, 4 Aug 2014 15:55:05 +0000 Subject: [PATCH 068/201] pkg/watcher: prepare for 0.9.3 added changelog updated pubspec code formatting removed unused imports R=ajohnsen@google.com Review URL: https://codereview.chromium.org//441483004 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@38862 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/CHANGELOG.md | 7 +++++++ pkgs/watcher/example/watch.dart | 2 -- pkgs/watcher/lib/src/async_queue.dart | 2 +- .../lib/src/constructable_file_system_event.dart | 6 +++--- pkgs/watcher/lib/src/utils.dart | 4 ++-- pkgs/watcher/lib/src/watch_event.dart | 2 +- pkgs/watcher/pubspec.yaml | 14 +++++++------- .../watcher/test/directory_watcher/linux_test.dart | 2 +- .../test/directory_watcher/mac_os_test.dart | 2 +- .../test/directory_watcher/polling_test.dart | 2 +- pkgs/watcher/test/directory_watcher/shared.dart | 2 +- .../test/directory_watcher/windows_test.dart | 4 +--- pkgs/watcher/test/no_subscription/linux_test.dart | 5 ++--- pkgs/watcher/test/no_subscription/mac_os_test.dart | 5 ++--- .../watcher/test/no_subscription/polling_test.dart | 4 ++-- pkgs/watcher/test/no_subscription/shared.dart | 3 +-- pkgs/watcher/test/ready/linux_test.dart | 5 ++--- pkgs/watcher/test/ready/mac_os_test.dart | 5 ++--- pkgs/watcher/test/ready/polling_test.dart | 4 ++-- pkgs/watcher/test/ready/shared.dart | 2 +- pkgs/watcher/test/utils.dart | 13 ++++++------- 21 files changed, 46 insertions(+), 49 deletions(-) create mode 100644 pkgs/watcher/CHANGELOG.md diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md new file mode 100644 index 000000000..eb544f61e --- /dev/null +++ b/pkgs/watcher/CHANGELOG.md @@ -0,0 +1,7 @@ +# 0.9.3 + +* Improved support for Windows via `WindowsDirectoryWatcher`. + +* Simplified `PollingDirectoryWatcher`. + +* Fixed bugs in `MacOSDirectoryWatcher` diff --git a/pkgs/watcher/example/watch.dart b/pkgs/watcher/example/watch.dart index aba127d6d..da3c2633c 100644 --- a/pkgs/watcher/example/watch.dart +++ b/pkgs/watcher/example/watch.dart @@ -5,8 +5,6 @@ /// Watches the given directory and prints each modification to it. library watch; -import 'dart:io'; - import 'package:path/path.dart' as p; import 'package:watcher/watcher.dart'; diff --git a/pkgs/watcher/lib/src/async_queue.dart b/pkgs/watcher/lib/src/async_queue.dart index 8ac0cdfe1..b83493d73 100644 --- a/pkgs/watcher/lib/src/async_queue.dart +++ b/pkgs/watcher/lib/src/async_queue.dart @@ -70,4 +70,4 @@ class AsyncQueue { _isProcessing = false; }); } -} \ No newline at end of file +} diff --git a/pkgs/watcher/lib/src/constructable_file_system_event.dart b/pkgs/watcher/lib/src/constructable_file_system_event.dart index 010d2979d..d00a1dce7 100644 --- a/pkgs/watcher/lib/src/constructable_file_system_event.dart +++ b/pkgs/watcher/lib/src/constructable_file_system_event.dart @@ -40,11 +40,11 @@ class ConstructableFileSystemModifyEvent extends _ConstructableFileSystemEvent final type = FileSystemEvent.MODIFY; ConstructableFileSystemModifyEvent(String path, bool isDirectory, - this.contentChanged) + this.contentChanged) : super(path, isDirectory); String toString() => - "FileSystemModifyEvent('$path', contentChanged=$contentChanged)"; + "FileSystemModifyEvent('$path', contentChanged=$contentChanged)"; } class ConstructableFileSystemMoveEvent extends _ConstructableFileSystemEvent @@ -53,7 +53,7 @@ class ConstructableFileSystemMoveEvent extends _ConstructableFileSystemEvent final type = FileSystemEvent.MOVE; ConstructableFileSystemMoveEvent(String path, bool isDirectory, - this.destination) + this.destination) : super(path, isDirectory); String toString() => "FileSystemMoveEvent('$path', '$destination')"; diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index 163e9f445..007c84c19 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -20,7 +20,7 @@ bool isDirectoryNotFoundException(error) { /// Returns the union of all elements in each set in [sets]. Set unionAll(Iterable sets) => - sets.fold(new Set(), (union, set) => union.union(set)); + sets.fold(new Set(), (union, set) => union.union(set)); /// Returns a buffered stream that will emit the same values as the stream /// returned by [future] once [future] completes. @@ -76,7 +76,7 @@ Future newFuture(callback()) => new Future.value().then((_) => callback()); /// Returns a [Future] that completes after pumping the event queue [times] /// times. By default, this should pump the event queue enough times to allow /// any code to run, as long as it's not waiting on some external event. -Future pumpEventQueue([int times=20]) { +Future pumpEventQueue([int times = 20]) { if (times == 0) return new Future.value(); // We use a delayed future to allow microtask events to finish. The // Future.value or Future() constructors use scheduleMicrotask themselves and diff --git a/pkgs/watcher/lib/src/watch_event.dart b/pkgs/watcher/lib/src/watch_event.dart index d998a2532..be6d70c92 100644 --- a/pkgs/watcher/lib/src/watch_event.dart +++ b/pkgs/watcher/lib/src/watch_event.dart @@ -32,4 +32,4 @@ class ChangeType { const ChangeType(this._name); String toString() => _name; -} \ No newline at end of file +} diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 51ba562a6..d517e8f6a 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,15 +1,15 @@ name: watcher version: 0.9.3-dev -author: "Dart Team " +author: Dart Team homepage: http://www.dartlang.org description: > A file watcher. It monitors for changes to contents of directories and notifies you when files have been added, removed, or modified. +environment: + sdk: '>=1.0.0 <2.0.0' dependencies: - path: ">=0.9.0 <2.0.0" - stack_trace: ">=0.9.1 <2.0.0" + path: '>=0.9.0 <2.0.0' + stack_trace: '>=0.9.1 <2.0.0' dev_dependencies: - scheduled_test: ">=0.9.3-dev <0.11.0" - unittest: ">=0.9.2 <0.10.0" -environment: - sdk: ">=0.8.10+6 <2.0.0" + scheduled_test: '>=0.9.3 <0.12.0' + unittest: '>=0.9.2 <0.12.0' diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart index cae38adf8..c17eb534c 100644 --- a/pkgs/watcher/test/directory_watcher/linux_test.dart +++ b/pkgs/watcher/test/directory_watcher/linux_test.dart @@ -9,7 +9,7 @@ import 'package:watcher/watcher.dart'; import 'shared.dart'; import '../utils.dart'; -main() { +void main() { initConfig(); watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 1e9bd7d10..bbf966aa9 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -9,7 +9,7 @@ import 'package:watcher/watcher.dart'; import 'shared.dart'; import '../utils.dart'; -main() { +void main() { initConfig(); MacOSDirectoryWatcher.logDebugInfo = true; diff --git a/pkgs/watcher/test/directory_watcher/polling_test.dart b/pkgs/watcher/test/directory_watcher/polling_test.dart index da292073c..1ef49b57a 100644 --- a/pkgs/watcher/test/directory_watcher/polling_test.dart +++ b/pkgs/watcher/test/directory_watcher/polling_test.dart @@ -8,7 +8,7 @@ import 'package:watcher/watcher.dart'; import 'shared.dart'; import '../utils.dart'; -main() { +void main() { initConfig(); // Use a short delay to make the tests run quickly. diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index d3575ea4d..86324014b 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -7,7 +7,7 @@ import 'package:watcher/src/utils.dart'; import '../utils.dart'; -sharedTests() { +void sharedTests() { test('does not notify for files that already exist when started', () { // Make some pre-existing files. writeFile("a.txt"); diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 6bfb88b44..ea5c8c5a5 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -2,7 +2,6 @@ // 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. -import 'package:path/path.dart' as p; import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/directory_watcher/windows.dart'; import 'package:watcher/watcher.dart'; @@ -10,7 +9,7 @@ import 'package:watcher/watcher.dart'; import 'shared.dart'; import '../utils.dart'; -main() { +void main() { initConfig(); watcherFactory = (dir) => new WindowsDirectoryWatcher(dir); @@ -24,4 +23,3 @@ main() { new isInstanceOf()); }); } - diff --git a/pkgs/watcher/test/no_subscription/linux_test.dart b/pkgs/watcher/test/no_subscription/linux_test.dart index 7978830bc..f7f1b49c1 100644 --- a/pkgs/watcher/test/no_subscription/linux_test.dart +++ b/pkgs/watcher/test/no_subscription/linux_test.dart @@ -4,12 +4,11 @@ import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/directory_watcher/linux.dart'; -import 'package:watcher/watcher.dart'; import 'shared.dart'; import '../utils.dart'; -main() { +void main() { initConfig(); watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); @@ -17,4 +16,4 @@ main() { setUp(createSandbox); sharedTests(); -} \ No newline at end of file +} diff --git a/pkgs/watcher/test/no_subscription/mac_os_test.dart b/pkgs/watcher/test/no_subscription/mac_os_test.dart index e0275c4ee..721d3e7a0 100644 --- a/pkgs/watcher/test/no_subscription/mac_os_test.dart +++ b/pkgs/watcher/test/no_subscription/mac_os_test.dart @@ -4,12 +4,11 @@ import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/directory_watcher/mac_os.dart'; -import 'package:watcher/watcher.dart'; import 'shared.dart'; import '../utils.dart'; -main() { +void main() { initConfig(); watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); @@ -17,4 +16,4 @@ main() { setUp(createSandbox); sharedTests(); -} \ No newline at end of file +} diff --git a/pkgs/watcher/test/no_subscription/polling_test.dart b/pkgs/watcher/test/no_subscription/polling_test.dart index fa4f0cbe2..c71b5ceec 100644 --- a/pkgs/watcher/test/no_subscription/polling_test.dart +++ b/pkgs/watcher/test/no_subscription/polling_test.dart @@ -8,7 +8,7 @@ import 'package:watcher/watcher.dart'; import 'shared.dart'; import '../utils.dart'; -main() { +void main() { initConfig(); watcherFactory = (dir) => new PollingDirectoryWatcher(dir); @@ -16,4 +16,4 @@ main() { setUp(createSandbox); sharedTests(); -} \ No newline at end of file +} diff --git a/pkgs/watcher/test/no_subscription/shared.dart b/pkgs/watcher/test/no_subscription/shared.dart index 99172a2e8..9ba5c98ed 100644 --- a/pkgs/watcher/test/no_subscription/shared.dart +++ b/pkgs/watcher/test/no_subscription/shared.dart @@ -3,14 +3,13 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; -import 'dart:io'; import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/watcher.dart'; import '../utils.dart'; -sharedTests() { +void sharedTests() { test('does not notify for changes when there are no subscribers', () { // Note that this test doesn't rely as heavily on the test functions in // utils.dart because it needs to be very explicit about when the event diff --git a/pkgs/watcher/test/ready/linux_test.dart b/pkgs/watcher/test/ready/linux_test.dart index 7978830bc..f7f1b49c1 100644 --- a/pkgs/watcher/test/ready/linux_test.dart +++ b/pkgs/watcher/test/ready/linux_test.dart @@ -4,12 +4,11 @@ import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/directory_watcher/linux.dart'; -import 'package:watcher/watcher.dart'; import 'shared.dart'; import '../utils.dart'; -main() { +void main() { initConfig(); watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); @@ -17,4 +16,4 @@ main() { setUp(createSandbox); sharedTests(); -} \ No newline at end of file +} diff --git a/pkgs/watcher/test/ready/mac_os_test.dart b/pkgs/watcher/test/ready/mac_os_test.dart index e0275c4ee..721d3e7a0 100644 --- a/pkgs/watcher/test/ready/mac_os_test.dart +++ b/pkgs/watcher/test/ready/mac_os_test.dart @@ -4,12 +4,11 @@ import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/directory_watcher/mac_os.dart'; -import 'package:watcher/watcher.dart'; import 'shared.dart'; import '../utils.dart'; -main() { +void main() { initConfig(); watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); @@ -17,4 +16,4 @@ main() { setUp(createSandbox); sharedTests(); -} \ No newline at end of file +} diff --git a/pkgs/watcher/test/ready/polling_test.dart b/pkgs/watcher/test/ready/polling_test.dart index fa4f0cbe2..c71b5ceec 100644 --- a/pkgs/watcher/test/ready/polling_test.dart +++ b/pkgs/watcher/test/ready/polling_test.dart @@ -8,7 +8,7 @@ import 'package:watcher/watcher.dart'; import 'shared.dart'; import '../utils.dart'; -main() { +void main() { initConfig(); watcherFactory = (dir) => new PollingDirectoryWatcher(dir); @@ -16,4 +16,4 @@ main() { setUp(createSandbox); sharedTests(); -} \ No newline at end of file +} diff --git a/pkgs/watcher/test/ready/shared.dart b/pkgs/watcher/test/ready/shared.dart index af1b58f99..7be48331e 100644 --- a/pkgs/watcher/test/ready/shared.dart +++ b/pkgs/watcher/test/ready/shared.dart @@ -6,7 +6,7 @@ import 'package:scheduled_test/scheduled_test.dart'; import '../utils.dart'; -sharedTests() { +void sharedTests() { test('ready does not complete until after subscription', () { var watcher = createWatcher(waitForReady: false); diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 8b660e845..6758dae0c 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -4,7 +4,6 @@ library watcher.test.utils; -import 'dart:async'; import 'dart:io'; import 'package:path/path.dart' as p; @@ -250,16 +249,16 @@ Matcher isRemoveEvent(String path) => isWatchEvent(ChangeType.REMOVE, path); /// Expects that the next event emitted will be for an add event for [path]. void expectAddEvent(String path) => - _expectOrCollect(isWatchEvent(ChangeType.ADD, path)); + _expectOrCollect(isWatchEvent(ChangeType.ADD, path)); /// Expects that the next event emitted will be for a modification event for /// [path]. void expectModifyEvent(String path) => - _expectOrCollect(isWatchEvent(ChangeType.MODIFY, path)); + _expectOrCollect(isWatchEvent(ChangeType.MODIFY, path)); /// Expects that the next event emitted will be for a removal event for [path]. void expectRemoveEvent(String path) => - _expectOrCollect(isWatchEvent(ChangeType.REMOVE, path)); + _expectOrCollect(isWatchEvent(ChangeType.REMOVE, path)); /// Consumes an add event for [path] if one is emitted at this point in the /// schedule, but doesn't throw an error if it isn't. @@ -267,7 +266,7 @@ void expectRemoveEvent(String path) => /// If this is used at the end of a test, [startClosingEventStream] should be /// called before it. void allowAddEvent(String path) => - _expectOrCollect(allow(isWatchEvent(ChangeType.ADD, path))); + _expectOrCollect(allow(isWatchEvent(ChangeType.ADD, path))); /// Consumes a modification event for [path] if one is emitted at this point in /// the schedule, but doesn't throw an error if it isn't. @@ -275,7 +274,7 @@ void allowAddEvent(String path) => /// If this is used at the end of a test, [startClosingEventStream] should be /// called before it. void allowModifyEvent(String path) => - _expectOrCollect(allow(isWatchEvent(ChangeType.MODIFY, path))); + _expectOrCollect(allow(isWatchEvent(ChangeType.MODIFY, path))); /// Consumes a removal event for [path] if one is emitted at this point in the /// schedule, but doesn't throw an error if it isn't. @@ -283,7 +282,7 @@ void allowModifyEvent(String path) => /// If this is used at the end of a test, [startClosingEventStream] should be /// called before it. void allowRemoveEvent(String path) => - _expectOrCollect(allow(isWatchEvent(ChangeType.REMOVE, path))); + _expectOrCollect(allow(isWatchEvent(ChangeType.REMOVE, path))); /// Schedules writing a file in the sandbox at [path] with [contents]. /// From 249da16589359b9bfe1f7af245368a9176fcc7d4 Mon Sep 17 00:00:00 2001 From: "kevmoo@google.com" Date: Mon, 4 Aug 2014 16:06:37 +0000 Subject: [PATCH 069/201] pkg/watcher: update REAME descriptions TBR Review URL: https://codereview.chromium.org//439713007 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@38863 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/README.md b/pkgs/watcher/README.md index 75b0470f0..61cc1f9f9 100644 --- a/pkgs/watcher/README.md +++ b/pkgs/watcher/README.md @@ -1,2 +1,4 @@ -A file watcher. It monitors (currently by polling) for changes to contents of -directories and notifies you when files have been added, removed, or modified. \ No newline at end of file +A file system watcher. + +It monitors changes to contents of directories and sends notifications when +files have been added, removed, or modified. From a8b8e3f313d329230a9385e19bf88b560d5b4303 Mon Sep 17 00:00:00 2001 From: "kevmoo@google.com" Date: Mon, 4 Aug 2014 16:10:07 +0000 Subject: [PATCH 070/201] pkg/watcher: actually flag for release align package description with recent README updates TBR Review URL: https://codereview.chromium.org//439713008 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@38864 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/pubspec.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index d517e8f6a..6f96a56a5 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,10 +1,10 @@ name: watcher -version: 0.9.3-dev +version: 0.9.3 author: Dart Team homepage: http://www.dartlang.org description: > - A file watcher. It monitors for changes to contents of directories and - notifies you when files have been added, removed, or modified. + A file system watcher. It monitors changes to contents of directories and + sends notifications when files have been added, removed, or modified. environment: sdk: '>=1.0.0 <2.0.0' dependencies: From 8dd6edf923b2827bee6a4a1c63abb29db6be925e Mon Sep 17 00:00:00 2001 From: "ajohnsen@google.com" Date: Tue, 21 Oct 2014 06:06:19 +0000 Subject: [PATCH 071/201] Mac no longer fire bogus events. Fix Mac watcher. This is a re-apply of r37162. BUG= R=nweiz@google.com Review URL: https://codereview.chromium.org//621813002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@41220 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/CHANGELOG.md | 4 ++ .../lib/src/directory_watcher/mac_os.dart | 72 ++----------------- pkgs/watcher/pubspec.yaml | 4 +- 3 files changed, 12 insertions(+), 68 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index eb544f61e..175a86025 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.9.4 + +* Remove delay-fixes in MacOSDirectoryWatcher, by depending on sdk 1.6. + # 0.9.3 * Improved support for Windows via `WindowsDirectoryWatcher`. diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index e3efa2dd6..65f07fa92 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -73,30 +73,15 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// a subdirectory that was moved into the watched directory. StreamSubscription _listSubscription; - /// The timer for tracking how long we wait for an initial batch of bogus - /// events (see issue 14373). - Timer _bogusEventTimer; - _MacOSDirectoryWatcher(String directory, int parentId) : directory = directory, _files = new PathSet(directory), _id = "$parentId/${_count++}" { - _startWatch(); - - // Before we're ready to emit events, wait for [_listDir] to complete and - // for enough time to elapse that if bogus events (issue 14373) would be - // emitted, they will be. - // - // If we do receive a batch of events, [_onBatch] will ensure that these - // futures don't fire and that the directory is re-listed. - Future.wait([ - _listDir().then((_) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] finished initial directory list"); - } - }), - _waitForBogusEvents() - ]).then((_) { + _listDir().then((_) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] finished initial directory list"); + } + _startWatch(); if (MacOSDirectoryWatcher.logDebugInfo) { print("[$_id] watcher is ready, known files:"); for (var file in _files.toSet()) { @@ -134,29 +119,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } } - // If we get a batch of events before we're ready to begin emitting events, - // it's probable that it's a batch of pre-watcher events (see issue 14373). - // Ignore those events and re-list the directory. - if (!isReady) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] not ready to emit events, re-listing directory"); - } - - // Cancel the timer because bogus events only occur in the first batch, so - // we can fire [ready] as soon as we're done listing the directory. - _bogusEventTimer.cancel(); - _listDir().then((_) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] watcher is ready, known files:"); - for (var file in _files.toSet()) { - print("[$_id] ${p.relative(file, from: directory)}"); - } - } - _readyCompleter.complete(); - }); - return; - } - _sortEvents(batch).forEach((path, events) { var relativePath = p.relative(path, from: directory); if (MacOSDirectoryWatcher.logDebugInfo) { @@ -392,17 +354,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _watchSubscription = null; - // If the directory still exists and we're still expecting bogus events, - // this is probably issue 14849 rather than a real close event. We should - // just restart the watcher. - if (!isReady && new Directory(directory).existsSync()) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] fake closure (issue 14849), re-opening stream"); - } - _startWatch(); - return; - } - // FSEvents can fail to report the contents of the directory being removed // when the directory itself is removed, so we need to manually mark the // files as removed. @@ -442,19 +393,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { return completer.future; } - /// Wait 200ms for a batch of bogus events (issue 14373) to come in. - /// - /// 200ms is short in terms of human interaction, but longer than any Mac OS - /// watcher tests take on the bots, so it should be safe to assume that any - /// bogus events will be signaled in that time frame. - Future _waitForBogusEvents() { - var completer = new Completer(); - _bogusEventTimer = new Timer( - new Duration(milliseconds: 200), - completer.complete); - return completer.future; - } - /// Emit an event with the given [type] and [path]. void _emitEvent(ChangeType type, String path) { if (!isReady) return; diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 6f96a56a5..473e1c962 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.3 +version: 0.9.4 author: Dart Team homepage: http://www.dartlang.org description: > @@ -13,3 +13,5 @@ dependencies: dev_dependencies: scheduled_test: '>=0.9.3 <0.12.0' unittest: '>=0.9.2 <0.12.0' +environment: + sdk: ">=1.6.0 <2.0.0" From c4150c42bf927dc6e7a3716ffe4dbfe048b7c2f1 Mon Sep 17 00:00:00 2001 From: "ajohnsen@google.com" Date: Tue, 21 Oct 2014 07:43:59 +0000 Subject: [PATCH 072/201] Revert "Mac no longer fire bogus events. Fix Mac watcher." BUG= Review URL: https://codereview.chromium.org//670653003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/watcher@41221 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/watcher/CHANGELOG.md | 4 -- .../lib/src/directory_watcher/mac_os.dart | 72 +++++++++++++++++-- pkgs/watcher/pubspec.yaml | 4 +- 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 175a86025..eb544f61e 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,7 +1,3 @@ -# 0.9.4 - -* Remove delay-fixes in MacOSDirectoryWatcher, by depending on sdk 1.6. - # 0.9.3 * Improved support for Windows via `WindowsDirectoryWatcher`. diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 65f07fa92..e3efa2dd6 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -73,15 +73,30 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// a subdirectory that was moved into the watched directory. StreamSubscription _listSubscription; + /// The timer for tracking how long we wait for an initial batch of bogus + /// events (see issue 14373). + Timer _bogusEventTimer; + _MacOSDirectoryWatcher(String directory, int parentId) : directory = directory, _files = new PathSet(directory), _id = "$parentId/${_count++}" { - _listDir().then((_) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] finished initial directory list"); - } - _startWatch(); + _startWatch(); + + // Before we're ready to emit events, wait for [_listDir] to complete and + // for enough time to elapse that if bogus events (issue 14373) would be + // emitted, they will be. + // + // If we do receive a batch of events, [_onBatch] will ensure that these + // futures don't fire and that the directory is re-listed. + Future.wait([ + _listDir().then((_) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] finished initial directory list"); + } + }), + _waitForBogusEvents() + ]).then((_) { if (MacOSDirectoryWatcher.logDebugInfo) { print("[$_id] watcher is ready, known files:"); for (var file in _files.toSet()) { @@ -119,6 +134,29 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } } + // If we get a batch of events before we're ready to begin emitting events, + // it's probable that it's a batch of pre-watcher events (see issue 14373). + // Ignore those events and re-list the directory. + if (!isReady) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] not ready to emit events, re-listing directory"); + } + + // Cancel the timer because bogus events only occur in the first batch, so + // we can fire [ready] as soon as we're done listing the directory. + _bogusEventTimer.cancel(); + _listDir().then((_) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] watcher is ready, known files:"); + for (var file in _files.toSet()) { + print("[$_id] ${p.relative(file, from: directory)}"); + } + } + _readyCompleter.complete(); + }); + return; + } + _sortEvents(batch).forEach((path, events) { var relativePath = p.relative(path, from: directory); if (MacOSDirectoryWatcher.logDebugInfo) { @@ -354,6 +392,17 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _watchSubscription = null; + // If the directory still exists and we're still expecting bogus events, + // this is probably issue 14849 rather than a real close event. We should + // just restart the watcher. + if (!isReady && new Directory(directory).existsSync()) { + if (MacOSDirectoryWatcher.logDebugInfo) { + print("[$_id] fake closure (issue 14849), re-opening stream"); + } + _startWatch(); + return; + } + // FSEvents can fail to report the contents of the directory being removed // when the directory itself is removed, so we need to manually mark the // files as removed. @@ -393,6 +442,19 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { return completer.future; } + /// Wait 200ms for a batch of bogus events (issue 14373) to come in. + /// + /// 200ms is short in terms of human interaction, but longer than any Mac OS + /// watcher tests take on the bots, so it should be safe to assume that any + /// bogus events will be signaled in that time frame. + Future _waitForBogusEvents() { + var completer = new Completer(); + _bogusEventTimer = new Timer( + new Duration(milliseconds: 200), + completer.complete); + return completer.future; + } + /// Emit an event with the given [type] and [path]. void _emitEvent(ChangeType type, String path) { if (!isReady) return; diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 473e1c962..6f96a56a5 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.4 +version: 0.9.3 author: Dart Team homepage: http://www.dartlang.org description: > @@ -13,5 +13,3 @@ dependencies: dev_dependencies: scheduled_test: '>=0.9.3 <0.12.0' unittest: '>=0.9.2 <0.12.0' -environment: - sdk: ">=1.6.0 <2.0.0" From 14c9b780be03469c088fed927f6cce211be93646 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 17 Dec 2014 17:00:20 -0800 Subject: [PATCH 073/201] Add gitignore, status, and codereview files. --- pkgs/watcher/.gitignore | 14 ++++++++++++++ pkgs/watcher/.status | 25 +++++++++++++++++++++++++ pkgs/watcher/codereview.settings | 3 +++ 3 files changed, 42 insertions(+) create mode 100644 pkgs/watcher/.gitignore create mode 100644 pkgs/watcher/.status create mode 100644 pkgs/watcher/codereview.settings diff --git a/pkgs/watcher/.gitignore b/pkgs/watcher/.gitignore new file mode 100644 index 000000000..388eff0ba --- /dev/null +++ b/pkgs/watcher/.gitignore @@ -0,0 +1,14 @@ +# Don’t commit the following directories created by pub. +.buildlog +.pub/ +build/ +packages + +# Or the files created by dart2js. +*.dart.js +*.js_ +*.js.deps +*.js.map + +# Include when developing application packages. +pubspec.lock \ No newline at end of file diff --git a/pkgs/watcher/.status b/pkgs/watcher/.status new file mode 100644 index 000000000..728f81631 --- /dev/null +++ b/pkgs/watcher/.status @@ -0,0 +1,25 @@ +# Copyright (c) 2014, 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. + +# Skip non-test files ending with "_test". +*/packages/*: Skip + +# Only run tests from the build directory, since we don't care about the +# difference between transformed an untransformed code. +test/*: Skip + +[ $browser ] +*: Fail, OK # Uses dart:io. + +[ $arch == simarm && $checked ] +*/directory_watcher/linux_test: Skip # Issue 16118 + +[ $runtime == vm && ($system == windows || $system == macos) ] +*/linux_test: Skip + +[ $runtime == vm && ($system == windows || $system == linux) ] +*/mac_os_test: Skip + +[ $runtime == vm && ($system == macos || $system == linux) ] +*/windows_test: Skip diff --git a/pkgs/watcher/codereview.settings b/pkgs/watcher/codereview.settings new file mode 100644 index 000000000..25367dd29 --- /dev/null +++ b/pkgs/watcher/codereview.settings @@ -0,0 +1,3 @@ +CODE_REVIEW_SERVER: http://codereview.chromium.org/ +VIEW_VC: https://github.com/dart-lang/watcher/commit/ +CC_LIST: reviews@dartlang.org \ No newline at end of file From 1aa48de52271ed877874f074702173a88d9c678b Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 17 Dec 2014 17:00:32 -0800 Subject: [PATCH 074/201] Update the pubspec's homepage link. --- pkgs/watcher/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 6f96a56a5..658ef78c5 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,7 +1,7 @@ name: watcher version: 0.9.3 author: Dart Team -homepage: http://www.dartlang.org +homepage: http://github.com/dart-lang/watcher description: > A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. From 3938793bffe2048054150c515aaf87fbb67e1b95 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 18 Dec 2014 17:42:49 -0800 Subject: [PATCH 075/201] Properly skip tests in packages directories. --- pkgs/watcher/.status | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pkgs/watcher/.status b/pkgs/watcher/.status index 728f81631..cc13f00fa 100644 --- a/pkgs/watcher/.status +++ b/pkgs/watcher/.status @@ -3,7 +3,22 @@ # BSD-style license that can be found in the LICENSE file. # Skip non-test files ending with "_test". +packages/*: Skip +packages/*: Skip */packages/*: Skip +*/*/packages/*: Skip +*/*/*/packages/*: Skip +*/*/*/*packages/*: Skip +*/*/*/*/*packages/*: Skip +*/*/packages/*: Skip +*/*/packages/*: Skip +*/packages/*: Skip +*/*/packages/*: Skip +*/*/*/packages/*: Skip +*/*/*/*packages/*: Skip +*/*/*/*/*packages/*: Skip +*/*/*/*packages/*: Skip +*/*/*/*/*packages/*: Skip # Only run tests from the build directory, since we don't care about the # difference between transformed an untransformed code. From 4f38cacad56a0c78cc161fb79814edb5581b5f0b Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 18 Dec 2014 17:52:12 -0800 Subject: [PATCH 076/201] Remove initial */s in .status. --- pkgs/watcher/.status | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/pkgs/watcher/.status b/pkgs/watcher/.status index cc13f00fa..b160ec0f7 100644 --- a/pkgs/watcher/.status +++ b/pkgs/watcher/.status @@ -4,21 +4,11 @@ # Skip non-test files ending with "_test". packages/*: Skip -packages/*: Skip -*/packages/*: Skip -*/*/packages/*: Skip -*/*/*/packages/*: Skip -*/*/*/*packages/*: Skip -*/*/*/*/*packages/*: Skip -*/*/packages/*: Skip -*/*/packages/*: Skip */packages/*: Skip */*/packages/*: Skip */*/*/packages/*: Skip */*/*/*packages/*: Skip */*/*/*/*packages/*: Skip -*/*/*/*packages/*: Skip -*/*/*/*/*packages/*: Skip # Only run tests from the build directory, since we don't care about the # difference between transformed an untransformed code. @@ -28,13 +18,13 @@ test/*: Skip *: Fail, OK # Uses dart:io. [ $arch == simarm && $checked ] -*/directory_watcher/linux_test: Skip # Issue 16118 +build/test/directory_watcher/linux_test: Skip # Issue 16118 [ $runtime == vm && ($system == windows || $system == macos) ] -*/linux_test: Skip +build/test/linux_test: Skip [ $runtime == vm && ($system == windows || $system == linux) ] -*/mac_os_test: Skip +build/test/mac_os_test: Skip [ $runtime == vm && ($system == macos || $system == linux) ] -*/windows_test: Skip +build/test/windows_test: Skip From 8616fff0d36ec7133421a5c1c31ff822160c27b5 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 19 Dec 2014 13:03:59 -0800 Subject: [PATCH 077/201] Update .status to properly skip tests. --- pkgs/watcher/.status | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/.status b/pkgs/watcher/.status index b160ec0f7..40ed64ce1 100644 --- a/pkgs/watcher/.status +++ b/pkgs/watcher/.status @@ -21,10 +21,10 @@ test/*: Skip build/test/directory_watcher/linux_test: Skip # Issue 16118 [ $runtime == vm && ($system == windows || $system == macos) ] -build/test/linux_test: Skip +build/test/directory_watcher/linux_test: Skip [ $runtime == vm && ($system == windows || $system == linux) ] -build/test/mac_os_test: Skip +build/test/directory_watcher/mac_os_test: Skip [ $runtime == vm && ($system == macos || $system == linux) ] -build/test/windows_test: Skip +build/test/directory_watcher/windows_test: Skip From 38a2b19568c0dc8f07a86f55868e7c9a156e2d08 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 19 Dec 2014 13:47:30 -0800 Subject: [PATCH 078/201] Skip tests even more properly. --- pkgs/watcher/.status | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/.status b/pkgs/watcher/.status index 40ed64ce1..e79b8e04a 100644 --- a/pkgs/watcher/.status +++ b/pkgs/watcher/.status @@ -21,10 +21,10 @@ test/*: Skip build/test/directory_watcher/linux_test: Skip # Issue 16118 [ $runtime == vm && ($system == windows || $system == macos) ] -build/test/directory_watcher/linux_test: Skip +build/test/*: Skip [ $runtime == vm && ($system == windows || $system == linux) ] -build/test/directory_watcher/mac_os_test: Skip +build/test/*: Skip [ $runtime == vm && ($system == macos || $system == linux) ] -build/test/directory_watcher/windows_test: Skip +build/test/*: Skip From 9a301f7ca14d6f99321cef41f6926912055f19f1 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Thu, 22 Jan 2015 09:48:54 -0800 Subject: [PATCH 079/201] Treat add events for known files as modifications instead of discarding them on Mac OS. Fixes pub serve not seeing files saved in IntelliJ or other tools that use "safe" writes. https://code.google.com/p/dart/issues/detail?id=21402 R=ajohnsen@google.com, nweiz@google.com Review URL: https://codereview.chromium.org//861313006 --- pkgs/watcher/CHANGELOG.md | 5 +++++ .../watcher/lib/src/directory_watcher/mac_os.dart | 15 +++++++++------ pkgs/watcher/pubspec.yaml | 2 +- pkgs/watcher/test/directory_watcher/shared.dart | 12 ++++++++++++ 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index eb544f61e..2ff887ebe 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.9.4 + +* Treat add events for known files as modifications instead of discarding them + on Mac OS. + # 0.9.3 * Improved support for Windows via `WindowsDirectoryWatcher`. diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index e3efa2dd6..ee2013665 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -179,12 +179,15 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { for (var event in events) { if (event is FileSystemCreateEvent) { if (!event.isDirectory) { - // Don't emit ADD events for files or directories that we already - // know about. Such an event comes from FSEvents reporting an add - // that happened prior to the watch beginning. - if (_files.contains(path)) continue; - - _emitEvent(ChangeType.ADD, path); + // If we already know about the file, treat it like a modification. + // This can happen if a file is copied on top of an existing one. + // We'll see an ADD event for the latter file when from the user's + // perspective, the file's contents just changed. + var type = _files.contains(path) + ? ChangeType.MODIFY + : ChangeType.ADD; + + _emitEvent(type, path); _files.add(path); continue; } diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 658ef78c5..c0c71eb1a 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.3 +version: 0.9.4 author: Dart Team homepage: http://github.com/dart-lang/watcher description: > diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index 86324014b..a4cec5e11 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -121,6 +121,18 @@ void sharedTests() { renameFile("dir/old.txt", "new.txt"); expectRemoveEvent("dir/old.txt"); }); + + test('notifies when a file is moved onto an existing one', () { + writeFile("from.txt"); + writeFile("to.txt"); + startWatcher(); + + renameFile("from.txt", "to.txt"); + inAnyOrder([ + isRemoveEvent("from.txt"), + isModifyEvent("to.txt") + ]); + }); }); // Most of the time, when multiple filesystem actions happen in sequence, From da5d8d338bb564ddb435ef789e6d1c02f279ee8b Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 10 Mar 2015 12:47:52 -0700 Subject: [PATCH 080/201] Fix bugs where events could be added after watchers were closed. R=rnystrom@google.com BUG=dartbug.com/22653 Review URL: https://codereview.chromium.org//995623002 --- pkgs/watcher/CHANGELOG.md | 4 ++++ .../lib/src/directory_watcher/linux.dart | 2 +- .../lib/src/directory_watcher/mac_os.dart | 22 +++++++++++++------ pkgs/watcher/pubspec.yaml | 2 +- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 2ff887ebe..77729800c 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.9.5 + +* Fix bugs where events could be added after watchers were closed. + # 0.9.4 * Treat add events for known files as modifications instead of discarding them diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index 76558dd27..870faa760 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -134,7 +134,7 @@ class _LinuxDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (isReady) _eventsController.add(event); }, onError: (error, stackTrace) { _eventsController.addError(error, stackTrace); - _eventsController.close(); + close(); }, onDone: () { if (_subWatchers[path] == watcher) _subWatchers.remove(path); diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index ee2013665..c242c75da 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -69,9 +69,9 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// the directory to determine its initial state. StreamSubscription _initialListSubscription; - /// The subscription to the [Directory.list] call for listing the contents of - /// a subdirectory that was moved into the watched directory. - StreamSubscription _listSubscription; + /// The subscriptions to [Directory.list] calls for listing the contents of a + /// subdirectory that was moved into the watched directory. + final _listSubscriptions = new Set>(); /// The timer for tracking how long we wait for an initial batch of bogus /// events (see issue 14373). @@ -113,10 +113,14 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } if (_watchSubscription != null) _watchSubscription.cancel(); if (_initialListSubscription != null) _initialListSubscription.cancel(); - if (_listSubscription != null) _listSubscription.cancel(); _watchSubscription = null; _initialListSubscription = null; - _listSubscription = null; + + for (var subscription in _listSubscriptions) { + subscription.cancel(); + } + _listSubscriptions.clear(); + _eventsController.close(); } @@ -194,8 +198,9 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (_files.containsDir(path)) continue; - var stream = Chain.track(new Directory(path).list(recursive: true)); - _listSubscription = stream.listen((entity) { + var subscription; + subscription = Chain.track(new Directory(path).list(recursive: true)) + .listen((entity) { if (entity is Directory) return; if (_files.contains(path)) return; @@ -206,7 +211,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { print("[$_id] got error listing $relativePath: $e"); } _emitError(e, stackTrace); + }, onDone: () { + _listSubscriptions.remove(subscription); }, cancelOnError: true); + _listSubscriptions.add(subscription); } else if (event is FileSystemModifyEvent) { assert(!event.isDirectory); _emitEvent(ChangeType.MODIFY, path); diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index c0c71eb1a..a16a0200e 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.4 +version: 0.9.5 author: Dart Team homepage: http://github.com/dart-lang/watcher description: > From 23edaac9648df77d17db057a49b4b47ff041f6c6 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 12 Jun 2015 16:00:26 -0700 Subject: [PATCH 081/201] Remove debug prints. As far as I know, the issue that required these was fixed a while ago. R=rnystrom@google.com Review URL: https://codereview.chromium.org//1187553003. --- .../lib/src/directory_watcher/mac_os.dart | 124 +----------------- pkgs/watcher/pubspec.yaml | 2 +- .../test/directory_watcher/mac_os_test.dart | 2 - pkgs/watcher/test/utils.dart | 28 ---- 4 files changed, 7 insertions(+), 149 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index c242c75da..89ac905ab 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -7,7 +7,6 @@ library watcher.directory_watcher.mac_os; import 'dart:async'; import 'dart:io'; -import 'package:path/path.dart' as p; import 'package:stack_trace/stack_trace.dart'; import '../constructable_file_system_event.dart'; @@ -27,19 +26,11 @@ import 'resubscribable.dart'; /// This also works around issues 16003 and 14849 in the implementation of /// [Directory.watch]. class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher { - // TODO(nweiz): remove these when issue 15042 is fixed. - static var logDebugInfo = false; - static var _count = 0; - MacOSDirectoryWatcher(String directory) - : super(directory, () => new _MacOSDirectoryWatcher(directory, _count++)); + : super(directory, () => new _MacOSDirectoryWatcher(directory)); } class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { - // TODO(nweiz): remove these when issue 15042 is fixed. - static var _count = 0; - final String _id; - final String directory; Stream get events => _eventsController.stream; @@ -77,10 +68,9 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// events (see issue 14373). Timer _bogusEventTimer; - _MacOSDirectoryWatcher(String directory, int parentId) + _MacOSDirectoryWatcher(String directory) : directory = directory, - _files = new PathSet(directory), - _id = "$parentId/${_count++}" { + _files = new PathSet(directory) { _startWatch(); // Before we're ready to emit events, wait for [_listDir] to complete and @@ -90,27 +80,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { // If we do receive a batch of events, [_onBatch] will ensure that these // futures don't fire and that the directory is re-listed. Future.wait([ - _listDir().then((_) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] finished initial directory list"); - } - }), + _listDir(), _waitForBogusEvents() - ]).then((_) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] watcher is ready, known files:"); - for (var file in _files.toSet()) { - print("[$_id] ${p.relative(file, from: directory)}"); - } - } - _readyCompleter.complete(); - }); + ]).then((_) => _readyCompleter.complete()); } void close() { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] watcher is closed\n${new Chain.current().terse}"); - } if (_watchSubscription != null) _watchSubscription.cancel(); if (_initialListSubscription != null) _initialListSubscription.cancel(); _watchSubscription = null; @@ -126,59 +101,21 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// The callback that's run when [Directory.watch] emits a batch of events. void _onBatch(List batch) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] ======== batch:"); - for (var event in batch) { - print("[$_id] ${_formatEvent(event)}"); - } - - print("[$_id] known files:"); - for (var file in _files.toSet()) { - print("[$_id] ${p.relative(file, from: directory)}"); - } - } - // If we get a batch of events before we're ready to begin emitting events, // it's probable that it's a batch of pre-watcher events (see issue 14373). // Ignore those events and re-list the directory. if (!isReady) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] not ready to emit events, re-listing directory"); - } - // Cancel the timer because bogus events only occur in the first batch, so // we can fire [ready] as soon as we're done listing the directory. _bogusEventTimer.cancel(); - _listDir().then((_) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] watcher is ready, known files:"); - for (var file in _files.toSet()) { - print("[$_id] ${p.relative(file, from: directory)}"); - } - } - _readyCompleter.complete(); - }); + _listDir().then((_) => _readyCompleter.complete()); return; } _sortEvents(batch).forEach((path, events) { - var relativePath = p.relative(path, from: directory); - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] events for $relativePath:"); - for (var event in events) { - print("[$_id] ${_formatEvent(event)}"); - } - } - var canonicalEvent = _canonicalEvent(events); events = canonicalEvent == null ? _eventsBasedOnFileSystem(path) : [canonicalEvent]; - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] canonical event for $relativePath: " - "${_formatEvent(canonicalEvent)}"); - print("[$_id] actionable events for $relativePath: " - "${events.map(_formatEvent)}"); - } for (var event in events) { if (event is FileSystemCreateEvent) { @@ -207,9 +144,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); }, onError: (e, stackTrace) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] got error listing $relativePath: $e"); - } _emitError(e, stackTrace); }, onDone: () { _listSubscriptions.remove(subscription); @@ -226,10 +160,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { } } }); - - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] ======== batch complete"); - } } /// Sort all the events in a batch into sets based on their path. @@ -360,15 +290,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { var fileExists = new File(path).existsSync(); var dirExists = new Directory(path).existsSync(); - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] checking file system for " - "${p.relative(path, from: directory)}"); - print("[$_id] file existed: $fileExisted"); - print("[$_id] dir existed: $dirExisted"); - print("[$_id] file exists: $fileExists"); - print("[$_id] dir exists: $dirExists"); - } - var events = []; if (fileExisted) { if (fileExists) { @@ -399,17 +320,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// The callback that's run when the [Directory.watch] stream is closed. void _onDone() { - if (MacOSDirectoryWatcher.logDebugInfo) print("[$_id] stream closed"); - _watchSubscription = null; // If the directory still exists and we're still expecting bogus events, // this is probably issue 14849 rather than a real close event. We should // just restart the watcher. if (!isReady && new Directory(directory).existsSync()) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] fake closure (issue 14849), re-opening stream"); - } _startWatch(); return; } @@ -469,40 +385,12 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// Emit an event with the given [type] and [path]. void _emitEvent(ChangeType type, String path) { if (!isReady) return; - - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] emitting $type ${p.relative(path, from: directory)}"); - } - _eventsController.add(new WatchEvent(type, path)); } /// Emit an error, then close the watcher. void _emitError(error, StackTrace stackTrace) { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[$_id] emitting error: $error\n" + - "${new Chain.forTrace(stackTrace).terse}"); - } _eventsController.addError(error, stackTrace); close(); } - - // TODO(nweiz): remove this when issue 15042 is fixed. - /// Return a human-friendly string representation of [event]. - String _formatEvent(FileSystemEvent event) { - if (event == null) return 'null'; - - var path = p.relative(event.path, from: directory); - var type = event.isDirectory ? 'directory' : 'file'; - if (event is FileSystemCreateEvent) { - return "create $type $path"; - } else if (event is FileSystemDeleteEvent) { - return "delete $type $path"; - } else if (event is FileSystemModifyEvent) { - return "modify $type $path"; - } else if (event is FileSystemMoveEvent) { - return "move $type $path to " - "${p.relative(event.destination, from: directory)}"; - } - } } diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index a16a0200e..a0d3000a0 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.5 +version: 0.9.6-dev author: Dart Team homepage: http://github.com/dart-lang/watcher description: > diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index bbf966aa9..0320e3c57 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -11,8 +11,6 @@ import '../utils.dart'; void main() { initConfig(); - MacOSDirectoryWatcher.logDebugInfo = true; - watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); setUp(createSandbox); diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 6758dae0c..2953c988c 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -117,11 +117,6 @@ ScheduledStream _watcherEvents; /// /// If [dir] is provided, watches a subdirectory in the sandbox with that name. void startWatcher({String dir}) { - var testCase = currentTestCase.description; - if (MacOSDirectoryWatcher.logDebugInfo) { - print("starting watcher for $testCase (${new DateTime.now()})"); - } - // We want to wait until we're ready *after* we subscribe to the watcher's // events. _watcher = createWatcher(dir: dir, waitForReady: false); @@ -131,10 +126,6 @@ void startWatcher({String dir}) { // that it can be accessed synchronously after this. _watcherEvents = new ScheduledStream(futureStream(schedule(() { currentSchedule.onComplete.schedule(() { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("stopping watcher for $testCase (${new DateTime.now()})"); - } - _watcher = null; if (!_closePending) _watcherEvents.close(); @@ -301,9 +292,6 @@ void writeFile(String path, {String contents, bool updateModified}) { dir.createSync(recursive: true); } - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[test] writing file $path"); - } new File(fullPath).writeAsStringSync(contents); // Manually update the mock modification time for the file. @@ -320,9 +308,6 @@ void writeFile(String path, {String contents, bool updateModified}) { /// Schedules deleting a file in the sandbox at [path]. void deleteFile(String path) { schedule(() { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[test] deleting file $path"); - } new File(p.join(_sandboxDir, path)).deleteSync(); }, "delete file $path"); } @@ -332,10 +317,6 @@ void deleteFile(String path) { /// If [contents] is omitted, creates an empty file. void renameFile(String from, String to) { schedule(() { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[test] renaming file $from to $to"); - } - new File(p.join(_sandboxDir, from)).renameSync(p.join(_sandboxDir, to)); // Make sure we always use the same separator on Windows. @@ -350,9 +331,6 @@ void renameFile(String from, String to) { /// Schedules creating a directory in the sandbox at [path]. void createDir(String path) { schedule(() { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[test] creating directory $path"); - } new Directory(p.join(_sandboxDir, path)).createSync(); }, "create directory $path"); } @@ -360,9 +338,6 @@ void createDir(String path) { /// Schedules renaming a directory in the sandbox from [from] to [to]. void renameDir(String from, String to) { schedule(() { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[test] renaming directory $from to $to"); - } new Directory(p.join(_sandboxDir, from)) .renameSync(p.join(_sandboxDir, to)); }, "rename directory $from to $to"); @@ -371,9 +346,6 @@ void renameDir(String from, String to) { /// Schedules deleting a directory in the sandbox at [path]. void deleteDir(String path) { schedule(() { - if (MacOSDirectoryWatcher.logDebugInfo) { - print("[test] deleting directory $path"); - } new Directory(p.join(_sandboxDir, path)).deleteSync(recursive: true); }, "delete directory $path"); } From 16db63bb8d9d9bd0be68e792c1f65d051ba5daa3 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 12 Jun 2015 16:01:40 -0700 Subject: [PATCH 082/201] Fix analysis errors, warnings, and hints. R=rnystrom@google.com Review URL: https://codereview.chromium.org//1184763005. --- .../lib/src/directory_watcher/linux.dart | 8 +++----- .../lib/src/directory_watcher/mac_os.dart | 11 ++++------ .../lib/src/directory_watcher/polling.dart | 4 +--- .../lib/src/directory_watcher/windows.dart | 20 ++++++------------- pkgs/watcher/lib/src/stat.dart | 4 +--- 5 files changed, 15 insertions(+), 32 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index 870faa760..04d88e66c 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -7,8 +7,6 @@ library watcher.directory_watcher.linux; import 'dart:async'; import 'dart:io'; -import 'package:stack_trace/stack_trace.dart'; - import '../utils.dart'; import '../watch_event.dart'; import 'resubscribable.dart'; @@ -58,13 +56,13 @@ class _LinuxDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _LinuxDirectoryWatcher(String directory) : directory = directory { // Batch the inotify changes together so that we can dedup events. - var innerStream = Chain.track(new Directory(directory).watch()) + var innerStream = new Directory(directory).watch() .transform(new BatchedStreamTransformer()); _listen(innerStream, _onBatch, onError: _eventsController.addError, onDone: _onDone); - _listen(Chain.track(new Directory(directory).list()), (entity) { + _listen(new Directory(directory).list(), (entity) { _entries[entity.path] = new _EntryState(entity is Directory); if (entity is! Directory) return; _watchSubdir(entity.path); @@ -159,7 +157,7 @@ class _LinuxDirectoryWatcher implements ManuallyClosedDirectoryWatcher { // event for every new file. watcher.ready.then((_) { if (!isReady || _eventsController.isClosed) return; - _listen(Chain.track(new Directory(path).list(recursive: true)), (entry) { + _listen(new Directory(path).list(recursive: true), (entry) { if (entry is Directory) return; _eventsController.add(new WatchEvent(ChangeType.ADD, entry.path)); }, onError: (error, stackTrace) { diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 89ac905ab..f3de72711 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -7,8 +7,6 @@ library watcher.directory_watcher.mac_os; import 'dart:async'; import 'dart:io'; -import 'package:stack_trace/stack_trace.dart'; - import '../constructable_file_system_event.dart'; import '../path_set.dart'; import '../utils.dart'; @@ -136,7 +134,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (_files.containsDir(path)) continue; var subscription; - subscription = Chain.track(new Directory(path).list(recursive: true)) + subscription = new Directory(path).list(recursive: true) .listen((entity) { if (entity is Directory) return; if (_files.contains(path)) return; @@ -273,7 +271,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { case FileSystemEvent.MODIFY: return new ConstructableFileSystemModifyEvent( batch.first.path, isDir, false); - default: assert(false); + default: throw 'unreachable'; } } @@ -343,8 +341,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// Start or restart the underlying [Directory.watch] stream. void _startWatch() { // Batch the FSEvent changes together so that we can dedup events. - var innerStream = - Chain.track(new Directory(directory).watch(recursive: true)) + var innerStream = new Directory(directory).watch(recursive: true) .transform(new BatchedStreamTransformer()); _watchSubscription = innerStream.listen(_onBatch, onError: _eventsController.addError, @@ -359,7 +356,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _files.clear(); var completer = new Completer(); - var stream = Chain.track(new Directory(directory).list(recursive: true)); + var stream = new Directory(directory).list(recursive: true); _initialListSubscription = stream.listen((entity) { if (entity is! Directory) _files.add(entity.path); }, diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 12a32458e..144391fda 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -7,8 +7,6 @@ library watcher.directory_watcher.polling; import 'dart:async'; import 'dart:io'; -import 'package:stack_trace/stack_trace.dart'; - import '../async_queue.dart'; import '../stat.dart'; import '../utils.dart'; @@ -105,7 +103,7 @@ class _PollingDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _filesToProcess.add(null); } - var stream = Chain.track(new Directory(directory).list(recursive: true)); + var stream = new Directory(directory).list(recursive: true); _listSubscription = stream.listen((entity) { assert(!_events.isClosed); diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 4f41d3395..bd17cf48a 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -10,7 +10,6 @@ import 'dart:collection'; import 'dart:io'; import 'package:path/path.dart' as p; -import 'package:stack_trace/stack_trace.dart'; import '../constructable_file_system_event.dart'; import '../path_set.dart'; @@ -117,8 +116,7 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { var parent = p.dirname(absoluteDir); // Check if [directory] is already the root directory. if (FileSystemEntity.identicalSync(parent, directory)) return; - var parentStream = Chain.track( - new Directory(parent).watch(recursive: false)); + var parentStream = new Directory(parent).watch(recursive: false); _parentWatchSubscription = parentStream.listen((event) { // Only look at events for 'directory'. if (p.basename(event.path) != p.basename(absoluteDir)) return; @@ -159,7 +157,6 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// The callback that's run when [Directory.watch] emits a batch of events. void _onBatch(List batch) { _sortEvents(batch).forEach((path, events) { - var relativePath = p.relative(path, from: directory); var canonicalEvent = _canonicalEvent(events); events = canonicalEvent == null ? @@ -177,7 +174,7 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { if (_files.containsDir(path)) continue; - var stream = Chain.track(new Directory(path).list(recursive: true)); + var stream = new Directory(path).list(recursive: true); var sub; sub = stream.listen((entity) { if (entity is Directory) return; @@ -262,7 +259,6 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { var type = batch.first.type; var isDir = batch.first.isDirectory; - var hadModifyEvent = false; for (var event in batch.skip(1)) { // If one event reports that the file is a directory and another event @@ -273,10 +269,7 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { // safely assume the file was modified after a CREATE or before the // REMOVE; otherwise there will also be a REMOVE or CREATE event // (respectively) that will be contradictory. - if (event is FileSystemModifyEvent) { - hadModifyEvent = true; - continue; - } + if (event is FileSystemModifyEvent) continue; assert(event is FileSystemCreateEvent || event is FileSystemDeleteEvent || event is FileSystemMoveEvent); @@ -305,7 +298,7 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { batch.first.path, isDir, false); case FileSystemEvent.MOVE: return null; - default: assert(false); + default: throw 'unreachable'; } } @@ -367,8 +360,7 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// Start or restart the underlying [Directory.watch] stream. void _startWatch() { // Batch the events together so that we can dedup events. - var innerStream = - Chain.track(new Directory(directory).watch(recursive: true)); + var innerStream = new Directory(directory).watch(recursive: true); _watchSubscription = innerStream.listen(_onEvent, onError: _eventsController.addError, onDone: _onDone); @@ -382,7 +374,7 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _files.clear(); var completer = new Completer(); - var stream = Chain.track(new Directory(directory).list(recursive: true)); + var stream = new Directory(directory).list(recursive: true); void handleEntity(entity) { if (entity is! Directory) _files.add(entity.path); } diff --git a/pkgs/watcher/lib/src/stat.dart b/pkgs/watcher/lib/src/stat.dart index 166d78988..d36eff3bd 100644 --- a/pkgs/watcher/lib/src/stat.dart +++ b/pkgs/watcher/lib/src/stat.dart @@ -7,8 +7,6 @@ library watcher.stat; import 'dart:async'; import 'dart:io'; -import 'package:stack_trace/stack_trace.dart'; - /// A function that takes a file path and returns the last modified time for /// the file at that path. typedef DateTime MockTimeCallback(String path); @@ -31,5 +29,5 @@ Future getModificationTime(String path) { return new Future.value(_mockTimeCallback(path)); } - return Chain.track(FileStat.stat(path)).then((stat) => stat.modified); + return FileStat.stat(path).then((stat) => stat.modified); } From 22e2f6c7570b8f934db3c7a8ae5ea35621ae35bb Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 12 Jun 2015 16:02:41 -0700 Subject: [PATCH 083/201] Use the new test runner. Closes dart-lang/watcher#11 R=rnystrom@google.com Review URL: https://codereview.chromium.org//1183723003. --- pkgs/watcher/pubspec.yaml | 6 +++--- pkgs/watcher/test/directory_watcher/linux_test.dart | 4 ++-- pkgs/watcher/test/directory_watcher/mac_os_test.dart | 3 ++- pkgs/watcher/test/directory_watcher/polling_test.dart | 2 -- pkgs/watcher/test/directory_watcher/windows_test.dart | 4 ++-- pkgs/watcher/test/no_subscription/linux_test.dart | 4 ++-- pkgs/watcher/test/no_subscription/mac_os_test.dart | 4 ++-- pkgs/watcher/test/no_subscription/polling_test.dart | 2 -- pkgs/watcher/test/no_subscription/shared.dart | 4 ++-- pkgs/watcher/test/path_set_test.dart | 4 +--- pkgs/watcher/test/ready/linux_test.dart | 4 ++-- pkgs/watcher/test/ready/mac_os_test.dart | 4 ++-- pkgs/watcher/test/ready/polling_test.dart | 2 -- pkgs/watcher/test/utils.dart | 6 ------ 14 files changed, 20 insertions(+), 33 deletions(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index a0d3000a0..08695c98f 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -6,10 +6,10 @@ description: > A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. environment: - sdk: '>=1.0.0 <2.0.0' + sdk: '>=1.8.0 <2.0.0' dependencies: path: '>=0.9.0 <2.0.0' stack_trace: '>=0.9.1 <2.0.0' dev_dependencies: - scheduled_test: '>=0.9.3 <0.12.0' - unittest: '>=0.9.2 <0.12.0' + scheduled_test: '^0.12.0' + test: '^0.12.0' diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart index c17eb534c..15eb0cee3 100644 --- a/pkgs/watcher/test/directory_watcher/linux_test.dart +++ b/pkgs/watcher/test/directory_watcher/linux_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn('linux') + import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/directory_watcher/linux.dart'; import 'package:watcher/watcher.dart'; @@ -10,8 +12,6 @@ import 'shared.dart'; import '../utils.dart'; void main() { - initConfig(); - watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); setUp(createSandbox); diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 0320e3c57..e9a1696aa 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn('mac-os') + import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/directory_watcher/mac_os.dart'; import 'package:watcher/watcher.dart'; @@ -10,7 +12,6 @@ import 'shared.dart'; import '../utils.dart'; void main() { - initConfig(); watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); setUp(createSandbox); diff --git a/pkgs/watcher/test/directory_watcher/polling_test.dart b/pkgs/watcher/test/directory_watcher/polling_test.dart index 1ef49b57a..0192b2592 100644 --- a/pkgs/watcher/test/directory_watcher/polling_test.dart +++ b/pkgs/watcher/test/directory_watcher/polling_test.dart @@ -9,8 +9,6 @@ import 'shared.dart'; import '../utils.dart'; void main() { - initConfig(); - // Use a short delay to make the tests run quickly. watcherFactory = (dir) => new PollingDirectoryWatcher(dir, pollingDelay: new Duration(milliseconds: 100)); diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index ea5c8c5a5..4c77ced50 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn('windows') + import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/directory_watcher/windows.dart'; import 'package:watcher/watcher.dart'; @@ -10,8 +12,6 @@ import 'shared.dart'; import '../utils.dart'; void main() { - initConfig(); - watcherFactory = (dir) => new WindowsDirectoryWatcher(dir); setUp(createSandbox); diff --git a/pkgs/watcher/test/no_subscription/linux_test.dart b/pkgs/watcher/test/no_subscription/linux_test.dart index f7f1b49c1..c8e8ae91c 100644 --- a/pkgs/watcher/test/no_subscription/linux_test.dart +++ b/pkgs/watcher/test/no_subscription/linux_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn('linux') + import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/directory_watcher/linux.dart'; @@ -9,8 +11,6 @@ import 'shared.dart'; import '../utils.dart'; void main() { - initConfig(); - watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); setUp(createSandbox); diff --git a/pkgs/watcher/test/no_subscription/mac_os_test.dart b/pkgs/watcher/test/no_subscription/mac_os_test.dart index 721d3e7a0..d5b1c8e3f 100644 --- a/pkgs/watcher/test/no_subscription/mac_os_test.dart +++ b/pkgs/watcher/test/no_subscription/mac_os_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn('mac-os') + import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/directory_watcher/mac_os.dart'; @@ -9,8 +11,6 @@ import 'shared.dart'; import '../utils.dart'; void main() { - initConfig(); - watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); setUp(createSandbox); diff --git a/pkgs/watcher/test/no_subscription/polling_test.dart b/pkgs/watcher/test/no_subscription/polling_test.dart index c71b5ceec..5c99bc061 100644 --- a/pkgs/watcher/test/no_subscription/polling_test.dart +++ b/pkgs/watcher/test/no_subscription/polling_test.dart @@ -9,8 +9,6 @@ import 'shared.dart'; import '../utils.dart'; void main() { - initConfig(); - watcherFactory = (dir) => new PollingDirectoryWatcher(dir); setUp(createSandbox); diff --git a/pkgs/watcher/test/no_subscription/shared.dart b/pkgs/watcher/test/no_subscription/shared.dart index 9ba5c98ed..52649f1a7 100644 --- a/pkgs/watcher/test/no_subscription/shared.dart +++ b/pkgs/watcher/test/no_subscription/shared.dart @@ -18,7 +18,7 @@ void sharedTests() { // Subscribe to the events. var completer = new Completer(); - var subscription = watcher.events.listen(wrapAsync((event) { + var subscription = watcher.events.listen(expectAsync((event) { expect(event, isWatchEvent(ChangeType.ADD, "file.txt")); completer.complete(); })); @@ -39,7 +39,7 @@ void sharedTests() { // Then start listening again. schedule(() { completer = new Completer(); - subscription = watcher.events.listen(wrapAsync((event) { + subscription = watcher.events.listen(expectAsync((event) { // We should get an event for the third file, not the one added while // we weren't subscribed. expect(event, isWatchEvent(ChangeType.ADD, "added.txt")); diff --git a/pkgs/watcher/test/path_set_test.dart b/pkgs/watcher/test/path_set_test.dart index f433ad3c3..13e797c20 100644 --- a/pkgs/watcher/test/path_set_test.dart +++ b/pkgs/watcher/test/path_set_test.dart @@ -3,7 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:path/path.dart' as p; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; import 'package:watcher/src/path_set.dart'; import 'utils.dart'; @@ -17,8 +17,6 @@ Matcher containsDir(String path) => predicate((set) => 'set contains directory "$path"'); void main() { - initConfig(); - var set; setUp(() => set = new PathSet("root")); diff --git a/pkgs/watcher/test/ready/linux_test.dart b/pkgs/watcher/test/ready/linux_test.dart index f7f1b49c1..c8e8ae91c 100644 --- a/pkgs/watcher/test/ready/linux_test.dart +++ b/pkgs/watcher/test/ready/linux_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn('linux') + import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/directory_watcher/linux.dart'; @@ -9,8 +11,6 @@ import 'shared.dart'; import '../utils.dart'; void main() { - initConfig(); - watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); setUp(createSandbox); diff --git a/pkgs/watcher/test/ready/mac_os_test.dart b/pkgs/watcher/test/ready/mac_os_test.dart index 721d3e7a0..d5b1c8e3f 100644 --- a/pkgs/watcher/test/ready/mac_os_test.dart +++ b/pkgs/watcher/test/ready/mac_os_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn('mac-os') + import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/directory_watcher/mac_os.dart'; @@ -9,8 +11,6 @@ import 'shared.dart'; import '../utils.dart'; void main() { - initConfig(); - watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); setUp(createSandbox); diff --git a/pkgs/watcher/test/ready/polling_test.dart b/pkgs/watcher/test/ready/polling_test.dart index c71b5ceec..5c99bc061 100644 --- a/pkgs/watcher/test/ready/polling_test.dart +++ b/pkgs/watcher/test/ready/polling_test.dart @@ -9,8 +9,6 @@ import 'shared.dart'; import '../utils.dart'; void main() { - initConfig(); - watcherFactory = (dir) => new PollingDirectoryWatcher(dir); setUp(createSandbox); diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 2953c988c..85fe6d3c5 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -9,7 +9,6 @@ import 'dart:io'; import 'package:path/path.dart' as p; import 'package:scheduled_test/scheduled_stream.dart'; import 'package:scheduled_test/scheduled_test.dart'; -import 'package:unittest/compact_vm_config.dart'; import 'package:watcher/watcher.dart'; import 'package:watcher/src/stat.dart'; import 'package:watcher/src/utils.dart'; @@ -43,11 +42,6 @@ set watcherFactory(WatcherFactory factory) { } WatcherFactory _watcherFactory; -void initConfig() { - useCompactVMConfiguration(); - filterStacks = true; -} - /// Creates the sandbox directory the other functions in this library use and /// ensures it's deleted when the test ends. /// From 63de467a5a85f73563f6a641eb222fb80e72dbbe Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 12 Jun 2015 16:37:30 -0700 Subject: [PATCH 084/201] Add a Watcher interface. Unlike DirectoryWatcher, this interface isn't directory-specific. This also allows ResubscribableDirectoryWatcher to become ResubscribableWatcher, which will make it easier to implement single-file watching. See dart-lang/watcher#17 R=rnystrom@google.com Review URL: https://codereview.chromium.org//1180233003. --- pkgs/watcher/CHANGELOG.md | 7 ++++ pkgs/watcher/lib/src/directory_watcher.dart | 30 ++-------------- .../lib/src/directory_watcher/linux.dart | 23 +++++++----- .../lib/src/directory_watcher/mac_os.dart | 30 +++++++++------- .../lib/src/directory_watcher/polling.dart | 18 ++++++---- .../lib/src/directory_watcher/windows.dart | 35 +++++++++++-------- .../resubscribable.dart | 26 +++++++------- pkgs/watcher/lib/watcher.dart | 34 ++++++++++++++++++ pkgs/watcher/pubspec.yaml | 1 - .../test/directory_watcher/linux_test.dart | 2 +- .../test/directory_watcher/mac_os_test.dart | 4 +-- .../test/directory_watcher/shared.dart | 16 ++++----- pkgs/watcher/test/utils.dart | 35 +++++++++---------- 13 files changed, 150 insertions(+), 111 deletions(-) rename pkgs/watcher/lib/src/{directory_watcher => }/resubscribable.dart (74%) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 77729800c..f8c54bf6c 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,10 @@ +# 0.9.6 + +* Add a `Watcher` interface that encompasses watching both files and + directories. + +* Deprecate `DirectoryWatcher.directory`. Use `DirectoryWatcher.path` instead. + # 0.9.5 * Fix bugs where events could be added after watchers were closed. diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 605eaea48..6979536a9 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -4,10 +4,10 @@ library watcher.directory_watcher; -import 'dart:async'; import 'dart:io'; import 'watch_event.dart'; +import '../watcher.dart'; import 'directory_watcher/linux.dart'; import 'directory_watcher/mac_os.dart'; import 'directory_watcher/windows.dart'; @@ -15,35 +15,11 @@ import 'directory_watcher/polling.dart'; /// Watches the contents of a directory and emits [WatchEvent]s when something /// in the directory has changed. -abstract class DirectoryWatcher { +abstract class DirectoryWatcher implements Watcher { /// The directory whose contents are being monitored. + @Deprecated("Expires in 1.0.0. Use DirectoryWatcher.path instead.") String get directory; - /// The broadcast [Stream] of events that have occurred to files in - /// [directory]. - /// - /// Changes will only be monitored while this stream has subscribers. Any - /// file changes that occur during periods when there are no subscribers - /// will not be reported the next time a subscriber is added. - Stream get events; - - /// Whether the watcher is initialized and watching for file changes. - /// - /// This is true if and only if [ready] is complete. - bool get isReady; - - /// A [Future] that completes when the watcher is initialized and watching - /// for file changes. - /// - /// If the watcher is not currently monitoring the directory (because there - /// are no subscribers to [events]), this returns a future that isn't - /// complete yet. It will complete when a subscriber starts listening and - /// the watcher finishes any initialization work it needs to do. - /// - /// If the watcher is already monitoring, this returns an already complete - /// future. - Future get ready; - /// Creates a new [DirectoryWatcher] monitoring [directory]. /// /// If a native directory watcher is available for this platform, this will diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index 04d88e66c..a747839d2 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -7,9 +7,10 @@ library watcher.directory_watcher.linux; import 'dart:async'; import 'dart:io'; +import '../directory_watcher.dart'; +import '../resubscribable.dart'; import '../utils.dart'; import '../watch_event.dart'; -import 'resubscribable.dart'; /// Uses the inotify subsystem to watch for filesystem events. /// @@ -21,13 +22,18 @@ import 'resubscribable.dart'; /// [Directory.watch] producing multiple events for a single logical action /// (issue 14372) and providing insufficient information about move events /// (issue 14424). -class LinuxDirectoryWatcher extends ResubscribableDirectoryWatcher { +class LinuxDirectoryWatcher extends ResubscribableWatcher + implements DirectoryWatcher { + String get directory => path; + LinuxDirectoryWatcher(String directory) : super(directory, () => new _LinuxDirectoryWatcher(directory)); } -class _LinuxDirectoryWatcher implements ManuallyClosedDirectoryWatcher { - final String directory; +class _LinuxDirectoryWatcher + implements DirectoryWatcher, ManuallyClosedWatcher { + String get directory => path; + final String path; Stream get events => _eventsController.stream; final _eventsController = new StreamController.broadcast(); @@ -53,16 +59,15 @@ class _LinuxDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// watcher is closed. final _subscriptions = new Set(); - _LinuxDirectoryWatcher(String directory) - : directory = directory { + _LinuxDirectoryWatcher(this.path) { // Batch the inotify changes together so that we can dedup events. - var innerStream = new Directory(directory).watch() + var innerStream = new Directory(path).watch() .transform(new BatchedStreamTransformer()); _listen(innerStream, _onBatch, onError: _eventsController.addError, onDone: _onDone); - _listen(new Directory(directory).list(), (entity) { + _listen(new Directory(path).list(), (entity) { _entries[entity.path] = new _EntryState(entity is Directory); if (entity is! Directory) return; _watchSubdir(entity.path); @@ -182,7 +187,7 @@ class _LinuxDirectoryWatcher implements ManuallyClosedDirectoryWatcher { // If the watched directory is deleted or moved, we'll get a deletion // event for it. Ignore it; we handle closing [this] when the underlying // stream is closed. - if (event.path == directory) continue; + if (event.path == path) continue; changedEntries.add(event.path); diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index f3de72711..487225eb4 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -7,11 +7,12 @@ library watcher.directory_watcher.mac_os; import 'dart:async'; import 'dart:io'; +import '../directory_watcher.dart'; import '../constructable_file_system_event.dart'; import '../path_set.dart'; +import '../resubscribable.dart'; import '../utils.dart'; import '../watch_event.dart'; -import 'resubscribable.dart'; /// Uses the FSEvents subsystem to watch for filesystem events. /// @@ -23,13 +24,18 @@ import 'resubscribable.dart'; /// /// This also works around issues 16003 and 14849 in the implementation of /// [Directory.watch]. -class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher { +class MacOSDirectoryWatcher extends ResubscribableWatcher + implements DirectoryWatcher { + String get directory => path; + MacOSDirectoryWatcher(String directory) : super(directory, () => new _MacOSDirectoryWatcher(directory)); } -class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { - final String directory; +class _MacOSDirectoryWatcher + implements DirectoryWatcher, ManuallyClosedWatcher { + String get directory => path; + final String path; Stream get events => _eventsController.stream; final _eventsController = new StreamController.broadcast(); @@ -66,9 +72,9 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// events (see issue 14373). Timer _bogusEventTimer; - _MacOSDirectoryWatcher(String directory) - : directory = directory, - _files = new PathSet(directory) { + _MacOSDirectoryWatcher(String path) + : path = path, + _files = new PathSet(path) { _startWatch(); // Before we're ready to emit events, wait for [_listDir] to complete and @@ -167,14 +173,14 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// CREATE event for the destination. /// /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it - /// contain any events relating to [directory]. + /// contain any events relating to [path]. Map> _sortEvents(List batch) { var eventsForPaths = {}; // FSEvents can report past events, including events on the root directory // such as it being created. We want to ignore these. If the directory is // really deleted, that's handled by [_onDone]. - batch = batch.where((event) => event.path != directory).toList(); + batch = batch.where((event) => event.path != path).toList(); // Events within directories that already have events are superfluous; the // directory's full contents will be examined anyway, so we ignore such @@ -323,7 +329,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { // If the directory still exists and we're still expecting bogus events, // this is probably issue 14849 rather than a real close event. We should // just restart the watcher. - if (!isReady && new Directory(directory).existsSync()) { + if (!isReady && new Directory(path).existsSync()) { _startWatch(); return; } @@ -341,7 +347,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// Start or restart the underlying [Directory.watch] stream. void _startWatch() { // Batch the FSEvent changes together so that we can dedup events. - var innerStream = new Directory(directory).watch(recursive: true) + var innerStream = new Directory(path).watch(recursive: true) .transform(new BatchedStreamTransformer()); _watchSubscription = innerStream.listen(_onBatch, onError: _eventsController.addError, @@ -356,7 +362,7 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _files.clear(); var completer = new Completer(); - var stream = new Directory(directory).list(recursive: true); + var stream = new Directory(path).list(recursive: true); _initialListSubscription = stream.listen((entity) { if (entity is! Directory) _files.add(entity.path); }, diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 144391fda..7f417d630 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -8,13 +8,17 @@ import 'dart:async'; import 'dart:io'; import '../async_queue.dart'; +import '../directory_watcher.dart'; +import '../resubscribable.dart'; import '../stat.dart'; import '../utils.dart'; import '../watch_event.dart'; -import 'resubscribable.dart'; /// Periodically polls a directory for changes. -class PollingDirectoryWatcher extends ResubscribableDirectoryWatcher { +class PollingDirectoryWatcher extends ResubscribableWatcher + implements DirectoryWatcher { + String get directory => path; + /// Creates a new polling watcher monitoring [directory]. /// /// If [_pollingDelay] is passed, it specifies the amount of time the watcher @@ -28,8 +32,10 @@ class PollingDirectoryWatcher extends ResubscribableDirectoryWatcher { }); } -class _PollingDirectoryWatcher implements ManuallyClosedDirectoryWatcher { - final String directory; +class _PollingDirectoryWatcher + implements DirectoryWatcher, ManuallyClosedWatcher { + String get directory => path; + final String path; Stream get events => _events.stream; final _events = new StreamController.broadcast(); @@ -68,7 +74,7 @@ class _PollingDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// but not in here when a poll completes have been removed. final _polledFiles = new Set(); - _PollingDirectoryWatcher(this.directory, this._pollingDelay) { + _PollingDirectoryWatcher(this.path, this._pollingDelay) { _filesToProcess = new AsyncQueue(_processFile, onError: (e, stackTrace) { if (!_events.isClosed) _events.addError(e, stackTrace); @@ -103,7 +109,7 @@ class _PollingDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _filesToProcess.add(null); } - var stream = new Directory(directory).list(recursive: true); + var stream = new Directory(path).list(recursive: true); _listSubscription = stream.listen((entity) { assert(!_events.isClosed); diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index bd17cf48a..089951964 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -12,12 +12,16 @@ import 'dart:io'; import 'package:path/path.dart' as p; import '../constructable_file_system_event.dart'; +import '../directory_watcher.dart'; import '../path_set.dart'; +import '../resubscribable.dart'; import '../utils.dart'; import '../watch_event.dart'; -import 'resubscribable.dart'; -class WindowsDirectoryWatcher extends ResubscribableDirectoryWatcher { +class WindowsDirectoryWatcher extends ResubscribableWatcher + implements DirectoryWatcher { + String get directory => path; + WindowsDirectoryWatcher(String directory) : super(directory, () => new _WindowsDirectoryWatcher(directory)); } @@ -40,8 +44,10 @@ class _EventBatcher { } } -class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { - final String directory; +class _WindowsDirectoryWatcher + implements DirectoryWatcher, ManuallyClosedWatcher { + String get directory => path; + final String path; Stream get events => _eventsController.stream; final _eventsController = new StreamController.broadcast(); @@ -79,8 +85,9 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { final Set> _listSubscriptions = new HashSet>(); - _WindowsDirectoryWatcher(String directory) - : directory = directory, _files = new PathSet(directory) { + _WindowsDirectoryWatcher(String path) + : path = path, + _files = new PathSet(path) { // Before we're ready to emit events, wait for [_listDir] to complete. _listDir().then((_) { _startWatch(); @@ -110,12 +117,12 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// On Windows, if [directory] is deleted, we will not receive any event. /// /// Instead, we add a watcher on the parent folder (if any), that can notify - /// us about [directory]. This also includes events such as moves. + /// us about [path]. This also includes events such as moves. void _startParentWatcher() { - var absoluteDir = p.absolute(directory); + var absoluteDir = p.absolute(path); var parent = p.dirname(absoluteDir); - // Check if [directory] is already the root directory. - if (FileSystemEntity.identicalSync(parent, directory)) return; + // Check if [path] is already the root directory. + if (FileSystemEntity.identicalSync(parent, path)) return; var parentStream = new Directory(parent).watch(recursive: false); _parentWatchSubscription = parentStream.listen((event) { // Only look at events for 'directory'. @@ -127,7 +134,7 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { // the directory is now gone. if (event is FileSystemMoveEvent || event is FileSystemDeleteEvent || - (FileSystemEntity.typeSync(directory) == + (FileSystemEntity.typeSync(path) == FileSystemEntityType.NOT_FOUND)) { for (var path in _files.toSet()) { _emitEvent(ChangeType.REMOVE, path); @@ -210,7 +217,7 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// CREATE event for the destination. /// /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it - /// contain any events relating to [directory]. + /// contain any events relating to [path]. Map> _sortEvents(List batch) { var eventsForPaths = {}; @@ -360,7 +367,7 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { /// Start or restart the underlying [Directory.watch] stream. void _startWatch() { // Batch the events together so that we can dedup events. - var innerStream = new Directory(directory).watch(recursive: true); + var innerStream = new Directory(path).watch(recursive: true); _watchSubscription = innerStream.listen(_onEvent, onError: _eventsController.addError, onDone: _onDone); @@ -374,7 +381,7 @@ class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher { _files.clear(); var completer = new Completer(); - var stream = new Directory(directory).list(recursive: true); + var stream = new Directory(path).list(recursive: true); void handleEntity(entity) { if (entity is! Directory) _files.add(entity.path); } diff --git a/pkgs/watcher/lib/src/directory_watcher/resubscribable.dart b/pkgs/watcher/lib/src/resubscribable.dart similarity index 74% rename from pkgs/watcher/lib/src/directory_watcher/resubscribable.dart rename to pkgs/watcher/lib/src/resubscribable.dart index 7d99fc05b..2844c1e2c 100644 --- a/pkgs/watcher/lib/src/directory_watcher/resubscribable.dart +++ b/pkgs/watcher/lib/src/resubscribable.dart @@ -2,33 +2,33 @@ // 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.directory_watcher.resubscribable; +library watcher.resubscribable; import 'dart:async'; -import '../directory_watcher.dart'; -import '../watch_event.dart'; +import '../watcher.dart'; +import 'watch_event.dart'; -typedef ManuallyClosedDirectoryWatcher WatcherFactory(); +typedef ManuallyClosedWatcher WatcherFactory(); -/// A wrapper for [ManuallyClosedDirectoryWatcher] that encapsulates support for -/// closing the watcher when it has no subscribers and re-opening it when it's +/// A wrapper for [ManuallyClosedWatcher] that encapsulates support for closing +/// the watcher when it has no subscribers and re-opening it when it's /// re-subscribed. /// /// It's simpler to implement watchers without worrying about this behavior. /// This class wraps a watcher class which can be written with the simplifying /// assumption that it can continue emitting events until an explicit `close` /// method is called, at which point it will cease emitting events entirely. The -/// [ManuallyClosedDirectoryWatcher] interface is used for these watchers. +/// [ManuallyClosedWatcher] interface is used for these watchers. /// /// This would be more cleanly implemented as a function that takes a class and /// emits a new class, but Dart doesn't support that sort of thing. Instead it /// takes a factory function that produces instances of the inner class. -abstract class ResubscribableDirectoryWatcher implements DirectoryWatcher { +abstract class ResubscribableWatcher implements Watcher { /// The factory function that produces instances of the inner class. final WatcherFactory _factory; - final String directory; + final String path; Stream get events => _eventsController.stream; StreamController _eventsController; @@ -38,9 +38,9 @@ abstract class ResubscribableDirectoryWatcher implements DirectoryWatcher { Future get ready => _readyCompleter.future; var _readyCompleter = new Completer(); - /// Creates a new [ResubscribableDirectoryWatcher] wrapping the watchers + /// Creates a new [ResubscribableWatcher] wrapping the watchers /// emitted by [_factory]. - ResubscribableDirectoryWatcher(this.directory, this._factory) { + ResubscribableWatcher(this.path, this._factory) { var watcher; var subscription; @@ -67,8 +67,8 @@ abstract class ResubscribableDirectoryWatcher implements DirectoryWatcher { /// An interface for watchers with an explicit, manual [close] method. /// -/// See [ResubscribableDirectoryWatcher]. -abstract class ManuallyClosedDirectoryWatcher implements DirectoryWatcher { +/// See [ResubscribableWatcher]. +abstract class ManuallyClosedWatcher implements Watcher { /// Closes the watcher. /// /// Subclasses should close their [events] stream and release any internal diff --git a/pkgs/watcher/lib/watcher.dart b/pkgs/watcher/lib/watcher.dart index 88531f203..a07805824 100644 --- a/pkgs/watcher/lib/watcher.dart +++ b/pkgs/watcher/lib/watcher.dart @@ -4,6 +4,40 @@ library watcher; +import 'dart:async'; + +import 'src/watch_event.dart'; + export 'src/watch_event.dart'; export 'src/directory_watcher.dart'; export 'src/directory_watcher/polling.dart'; + +abstract class Watcher { + /// The path to the file or directory whose contents are being monitored. + String get path; + + /// The broadcast [Stream] of events that have occurred to the watched file or + /// files in the watched directory. + /// + /// Changes will only be monitored while this stream has subscribers. Any + /// changes that occur during periods when there are no subscribers will not + /// be reported the next time a subscriber is added. + Stream get events; + + /// Whether the watcher is initialized and watching for changes. + /// + /// This is true if and only if [ready] is complete. + bool get isReady; + + /// A [Future] that completes when the watcher is initialized and watching for + /// changes. + /// + /// If the watcher is not currently monitoring the file or directory (because + /// there are no subscribers to [events]), this returns a future that isn't + /// complete yet. It will complete when a subscriber starts listening and the + /// watcher finishes any initialization work it needs to do. + /// + /// If the watcher is already monitoring, this returns an already complete + /// future. + Future get ready; +} diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 08695c98f..88539956f 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -9,7 +9,6 @@ environment: sdk: '>=1.8.0 <2.0.0' dependencies: path: '>=0.9.0 <2.0.0' - stack_trace: '>=0.9.1 <2.0.0' dev_dependencies: scheduled_test: '^0.12.0' test: '^0.12.0' diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart index 15eb0cee3..897b1301b 100644 --- a/pkgs/watcher/test/directory_watcher/linux_test.dart +++ b/pkgs/watcher/test/directory_watcher/linux_test.dart @@ -27,7 +27,7 @@ void main() { () { withPermutations((i, j, k) => writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); - startWatcher(dir: "dir"); + startWatcher(path: "dir"); renameDir("dir/sub", "sub"); renameDir("sub", "dir/sub"); diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index e9a1696aa..2c82f34c5 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -30,7 +30,7 @@ void main() { deleteDir("dir"); createDir("dir"); - startWatcher(dir: "dir"); + startWatcher(path: "dir"); writeFile("dir/newer.txt"); expectAddEvent("dir/newer.txt"); }); @@ -40,7 +40,7 @@ void main() { withPermutations((i, j, k) => writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); - startWatcher(dir: "dir"); + startWatcher(path: "dir"); renameDir("dir/sub", "sub"); renameDir("sub", "dir/sub"); diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index a4cec5e11..ff48cb2aa 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -69,7 +69,7 @@ void sharedTests() { writeFile("dir/a.txt"); writeFile("dir/b.txt"); - startWatcher(dir: "dir"); + startWatcher(path: "dir"); deleteDir("dir"); inAnyOrder([ @@ -82,7 +82,7 @@ void sharedTests() { writeFile("dir/a.txt"); writeFile("dir/b.txt"); - startWatcher(dir: "dir"); + startWatcher(path: "dir"); renameDir("dir", "moved_dir"); createDir("dir"); @@ -108,7 +108,7 @@ void sharedTests() { () { writeFile("old.txt"); createDir("dir"); - startWatcher(dir: "dir"); + startWatcher(path: "dir"); renameFile("old.txt", "dir/new.txt"); expectAddEvent("dir/new.txt"); @@ -116,7 +116,7 @@ void sharedTests() { test('notifies when a file is moved outside the watched directory', () { writeFile("dir/old.txt"); - startWatcher(dir: "dir"); + startWatcher(path: "dir"); renameFile("dir/old.txt", "new.txt"); expectRemoveEvent("dir/old.txt"); @@ -251,7 +251,7 @@ void sharedTests() { writeFile("sub/sub-$i/sub-$j/file-$k.txt")); createDir("dir"); - startWatcher(dir: "dir"); + startWatcher(path: "dir"); renameDir("sub", "dir/sub"); inAnyOrder(withPermutations((i, j, k) => @@ -263,7 +263,7 @@ void sharedTests() { writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); createDir("dir"); - startWatcher(dir: "dir"); + startWatcher(path: "dir"); // Rename the directory rather than deleting it because native watchers // report a rename as a single DELETE event for the directory, whereas @@ -280,7 +280,7 @@ void sharedTests() { writeFile("dir/old/sub-$i/sub-$j/file-$k.txt")); createDir("dir"); - startWatcher(dir: "dir"); + startWatcher(path: "dir"); renameDir("dir/old", "dir/new"); inAnyOrder(unionAll(withPermutations((i, j, k) { @@ -296,7 +296,7 @@ void sharedTests() { writeFile("dir/sub"); withPermutations((i, j, k) => writeFile("old/sub-$i/sub-$j/file-$k.txt")); - startWatcher(dir: "dir"); + startWatcher(path: "dir"); deleteFile("dir/sub"); renameDir("old", "dir/sub"); diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 85fe6d3c5..7dd833231 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -20,8 +20,8 @@ import 'package:watcher/src/directory_watcher/mac_os.dart'; /// operations are implicitly relative to this directory. String _sandboxDir; -/// The [DirectoryWatcher] being used for the current scheduled test. -DirectoryWatcher _watcher; +/// The [Watcher] being used for the current scheduled test. +Watcher _watcher; /// The mock modification times (in milliseconds since epoch) for each file. /// @@ -34,9 +34,9 @@ DirectoryWatcher _watcher; /// increment the mod time for that file instantly. Map _mockFileModificationTimes; -typedef DirectoryWatcher WatcherFactory(String directory); +typedef Watcher WatcherFactory(String directory); -/// Sets the function used to create the directory watcher. +/// Sets the function used to create the watcher. set watcherFactory(WatcherFactory factory) { _watcherFactory = factory; } @@ -78,21 +78,21 @@ void createSandbox() { }, "delete sandbox"); } -/// Creates a new [DirectoryWatcher] that watches a temporary directory. +/// Creates a new [Watcher] that watches a temporary file or directory. /// /// 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. /// -/// If [dir] is provided, watches a subdirectory in the sandbox with that name. -DirectoryWatcher createWatcher({String dir, bool waitForReady}) { - if (dir == null) { - dir = _sandboxDir; +/// If [path] is provided, watches a subdirectory in the sandbox with that name. +Watcher createWatcher({String path, bool waitForReady}) { + if (path == null) { + path = _sandboxDir; } else { - dir = p.join(_sandboxDir, dir); + path = p.join(_sandboxDir, path); } - var watcher = _watcherFactory(dir); + var watcher = _watcherFactory(path); // Wait until the scan is finished so that we don't miss changes to files // that could occur before the scan completes. @@ -106,17 +106,17 @@ DirectoryWatcher createWatcher({String dir, bool waitForReady}) { /// The stream of events from the watcher started with [startWatcher]. ScheduledStream _watcherEvents; -/// Creates a new [DirectoryWatcher] that watches a temporary directory and +/// Creates a new [Watcher] that watches a temporary file or directory and /// starts monitoring it for events. /// -/// If [dir] is provided, watches a subdirectory in the sandbox with that name. -void startWatcher({String dir}) { +/// If [path] is provided, watches a path in the sandbox with that name. +void startWatcher({String path}) { // We want to wait until we're ready *after* we subscribe to the watcher's // events. - _watcher = createWatcher(dir: dir, waitForReady: false); + _watcher = createWatcher(path: path, waitForReady: false); // Schedule [_watcher.events.listen] so that the watcher doesn't start - // watching [dir] before it exists. Expose [_watcherEvents] immediately so + // watching [path] before it exists. Expose [_watcherEvents] immediately so // that it can be accessed synchronously after this. _watcherEvents = new ScheduledStream(futureStream(schedule(() { currentSchedule.onComplete.schedule(() { @@ -139,8 +139,7 @@ void startWatcher({String dir}) { /// Whether an event to close [_watcherEvents] has been scheduled. bool _closePending = false; -/// Schedule closing the directory watcher stream after the event queue has been -/// pumped. +/// Schedule closing the watcher stream after the event queue has been pumped. /// /// This is necessary when events are allowed to occur, but don't have to occur, /// at the end of a test. Otherwise, if they don't occur, the test will wait From 0ecfafab1ae85aeac1fcced5ad0f08f20b33c4de Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 12 Jun 2015 16:52:24 -0700 Subject: [PATCH 085/201] Add support for watching individual files. Closes dart-lang/watcher#17 R=rnystrom@google.com Review URL: https://codereview.chromium.org//1187553007. --- pkgs/watcher/CHANGELOG.md | 3 + pkgs/watcher/lib/src/directory_watcher.dart | 2 +- pkgs/watcher/lib/src/file_watcher.dart | 43 +++++++++ pkgs/watcher/lib/src/file_watcher/native.dart | 80 +++++++++++++++++ .../watcher/lib/src/file_watcher/polling.dart | 87 +++++++++++++++++++ pkgs/watcher/lib/watcher.dart | 26 ++++++ pkgs/watcher/pubspec.yaml | 4 +- .../test/file_watcher/native_test.dart | 23 +++++ .../test/file_watcher/polling_test.dart | 23 +++++ pkgs/watcher/test/file_watcher/shared.dart | 58 +++++++++++++ 10 files changed, 346 insertions(+), 3 deletions(-) create mode 100644 pkgs/watcher/lib/src/file_watcher.dart create mode 100644 pkgs/watcher/lib/src/file_watcher/native.dart create mode 100644 pkgs/watcher/lib/src/file_watcher/polling.dart create mode 100644 pkgs/watcher/test/file_watcher/native_test.dart create mode 100644 pkgs/watcher/test/file_watcher/polling_test.dart create mode 100644 pkgs/watcher/test/file_watcher/shared.dart diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index f8c54bf6c..4ff6cfaf6 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -3,6 +3,9 @@ * Add a `Watcher` interface that encompasses watching both files and directories. +* Add `FileWatcher` and `PollingFileWatcher` classes for watching changes to + individual files. + * Deprecate `DirectoryWatcher.directory`. Use `DirectoryWatcher.path` instead. # 0.9.5 diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 6979536a9..82837855b 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -25,7 +25,7 @@ abstract class DirectoryWatcher implements Watcher { /// If a native directory watcher is available for this platform, this will /// use it. Otherwise, it will fall back to a [PollingDirectoryWatcher]. /// - /// If [_pollingDelay] is passed, it specifies the amount of time the watcher + /// If [pollingDelay] is passed, it specifies the amount of time the watcher /// will pause between successive polls of the directory contents. Making this /// shorter will give more immediate feedback at the expense of doing more IO /// and higher CPU usage. Defaults to one second. Ignored for non-polling diff --git a/pkgs/watcher/lib/src/file_watcher.dart b/pkgs/watcher/lib/src/file_watcher.dart new file mode 100644 index 000000000..9b315378d --- /dev/null +++ b/pkgs/watcher/lib/src/file_watcher.dart @@ -0,0 +1,43 @@ +// Copyright (c) 2015, 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.file_watcher; + +import 'dart:io'; + +import 'watch_event.dart'; +import '../watcher.dart'; +import 'file_watcher/native.dart'; +import 'file_watcher/polling.dart'; + +/// Watches a file and emits [WatchEvent]s when the file has changed. +/// +/// Note that since each watcher only watches a single file, it will only emit +/// [ChangeType.MODIFY] events, except when the file is deleted at which point +/// it will emit a single [ChangeType.REMOVE] event and then close the stream. +/// +/// If the file is deleted and quickly replaced (when a new file is moved in its +/// place, for example) this will emit a [ChangeTime.MODIFY] event. +abstract class FileWatcher implements Watcher { + /// Creates a new [FileWatcher] monitoring [file]. + /// + /// If a native file watcher is available for this platform, this will use it. + /// Otherwise, it will fall back to a [PollingFileWatcher]. Notably, native + /// file watching is *not* supported on Windows. + /// + /// If [pollingDelay] is passed, it specifies the amount of time the watcher + /// will pause between successive polls of the directory contents. Making this + /// shorter will give more immediate feedback at the expense of doing more IO + /// and higher CPU usage. Defaults to one second. Ignored for non-polling + /// watchers. + factory FileWatcher(String file, {Duration pollingDelay}) { + // [File.watch] doesn't work on Windows, but + // [FileSystemEntity.isWatchSupported] is still true because directory + // watching does work. + if (FileSystemEntity.isWatchSupported && !Platform.isWindows) { + return new NativeFileWatcher(file); + } + return new PollingFileWatcher(file, pollingDelay: pollingDelay); + } +} diff --git a/pkgs/watcher/lib/src/file_watcher/native.dart b/pkgs/watcher/lib/src/file_watcher/native.dart new file mode 100644 index 000000000..f5699bbb4 --- /dev/null +++ b/pkgs/watcher/lib/src/file_watcher/native.dart @@ -0,0 +1,80 @@ +// Copyright (c) 2015, 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.file_watcher.native; + +import 'dart:async'; +import 'dart:io'; + +import '../file_watcher.dart'; +import '../resubscribable.dart'; +import '../utils.dart'; +import '../watch_event.dart'; + +/// Uses the native file system notifications to watch for filesystem events. +/// +/// Single-file notifications are much simpler than those for multiple files, so +/// this doesn't need to be split out into multiple OS-specific classes. +class NativeFileWatcher extends ResubscribableWatcher implements FileWatcher { + NativeFileWatcher(String path) + : super(path, () => new _NativeFileWatcher(path)); +} + +class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { + final String path; + + Stream get events => _eventsController.stream; + final _eventsController = new StreamController.broadcast(); + + bool get isReady => _readyCompleter.isCompleted; + + Future get ready => _readyCompleter.future; + final _readyCompleter = new Completer(); + + StreamSubscription _subscription; + + _NativeFileWatcher(this.path) { + _listen(); + + // We don't need to do any initial set-up, so we're ready immediately after + // being listened to. + _readyCompleter.complete(); + } + + void _listen() { + // Batch the events together so that we can dedup them. + _subscription = new File(path).watch() + .transform(new BatchedStreamTransformer()) + .listen(_onBatch, onError: _eventsController.addError, onDone: _onDone); + } + + void _onBatch(List batch) { + if (batch.any((event) => event.type == FileSystemEvent.DELETE)) { + // If the file is deleted, the underlying stream will close. We handle + // emitting our own REMOVE event in [_onDone]. + return; + } + + _eventsController.add(new WatchEvent(ChangeType.MODIFY, path)); + } + + _onDone() async { + // If the file exists now, it was probably removed and quickly replaced; + // this can happen for example when another file is moved on top of it. + // Re-subscribe and report a modify event. + if (await new File(path).exists()) { + _eventsController.add(new WatchEvent(ChangeType.MODIFY, path)); + _listen(); + } else { + _eventsController.add(new WatchEvent(ChangeType.REMOVE, path)); + close(); + } + } + + void close() { + if (_subscription != null) _subscription.cancel(); + _subscription = null; + _eventsController.close(); + } +} diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart new file mode 100644 index 000000000..a44f80cdb --- /dev/null +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -0,0 +1,87 @@ +// Copyright (c) 2015, 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.file_watcher.polling; + +import 'dart:async'; +import 'dart:io'; + +import '../file_watcher.dart'; +import '../resubscribable.dart'; +import '../stat.dart'; +import '../watch_event.dart'; + +/// Periodically polls a file for changes. +class PollingFileWatcher extends ResubscribableWatcher implements FileWatcher { + PollingFileWatcher(String path, {Duration pollingDelay}) + : super(path, () { + return new _PollingFileWatcher(path, + pollingDelay != null ? pollingDelay : new Duration(seconds: 1)); + }); +} + +class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { + final String path; + + Stream get events => _eventsController.stream; + final _eventsController = new StreamController.broadcast(); + + bool get isReady => _readyCompleter.isCompleted; + + Future get ready => _readyCompleter.future; + final _readyCompleter = new Completer(); + + /// The timer that controls polling. + Timer _timer; + + /// The previous modification time of the file. + /// + /// Used to tell when the file was modified. This is `null` before the file's + /// mtime has first been checked. + DateTime _lastModified; + + _PollingFileWatcher(this.path, Duration pollingDelay) { + _timer = new Timer.periodic(pollingDelay, (_) => _poll()); + _poll(); + } + + /// Checks the mtime of the file and whether it's been removed. + Future _poll() async { + // We don't mark the file as removed if this is the first poll (indicated by + // [_lastModified] being null). Instead, below we forward the dart:io error + // that comes from trying to read the mtime below. + if (_lastModified != null && !await new File(path).exists()) { + _eventsController.add(new WatchEvent(ChangeType.REMOVE, path)); + close(); + return; + } + + var modified; + try { + modified = await getModificationTime(path); + } on FileSystemException catch (error, stackTrace) { + _eventsController.addError(error, stackTrace); + close(); + return; + } + + if (_eventsController.isClosed) return; + if (_lastModified == modified) return; + + if (_lastModified == null) { + // If this is the first poll, don't emit an event, just set the last mtime + // and complete the completer. + _lastModified = modified; + _readyCompleter.complete(); + } else { + _lastModified = modified; + _eventsController.add(new WatchEvent(ChangeType.MODIFY, path)); + } + } + + void close() { + _timer.cancel(); + _eventsController.close(); + } +} diff --git a/pkgs/watcher/lib/watcher.dart b/pkgs/watcher/lib/watcher.dart index a07805824..79dcc0dae 100644 --- a/pkgs/watcher/lib/watcher.dart +++ b/pkgs/watcher/lib/watcher.dart @@ -5,12 +5,17 @@ library watcher; import 'dart:async'; +import 'dart:io'; import 'src/watch_event.dart'; +import 'src/directory_watcher.dart'; +import 'src/file_watcher.dart'; export 'src/watch_event.dart'; export 'src/directory_watcher.dart'; export 'src/directory_watcher/polling.dart'; +export 'src/file_watcher.dart'; +export 'src/file_watcher/polling.dart'; abstract class Watcher { /// The path to the file or directory whose contents are being monitored. @@ -40,4 +45,25 @@ abstract class Watcher { /// If the watcher is already monitoring, this returns an already complete /// future. Future get ready; + + /// Creates a new [DirectoryWatcher] or [FileWatcher] monitoring [path], + /// depending on whether it's a file or directory. + /// + /// If a native watcher is available for this platform, this will use it. + /// Otherwise, it will fall back to a polling watcher. Notably, watching + /// individual files is not natively supported on Windows, although watching + /// directories is. + /// + /// If [pollingDelay] is passed, it specifies the amount of time the watcher + /// will pause between successive polls of the contents of [path]. Making this + /// shorter will give more immediate feedback at the expense of doing more IO + /// and higher CPU usage. Defaults to one second. Ignored for non-polling + /// watchers. + factory Watcher(String path, {Duration pollingDelay}) { + if (new File(path).existsSync()) { + return new FileWatcher(path, pollingDelay: pollingDelay); + } else { + return new DirectoryWatcher(path, pollingDelay: pollingDelay); + } + } } diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 88539956f..f62248da2 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,12 +1,12 @@ name: watcher -version: 0.9.6-dev +version: 0.9.6 author: Dart Team homepage: http://github.com/dart-lang/watcher description: > A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. environment: - sdk: '>=1.8.0 <2.0.0' + sdk: '>=1.9.0 <2.0.0' dependencies: path: '>=0.9.0 <2.0.0' dev_dependencies: diff --git a/pkgs/watcher/test/file_watcher/native_test.dart b/pkgs/watcher/test/file_watcher/native_test.dart new file mode 100644 index 000000000..30d0eca42 --- /dev/null +++ b/pkgs/watcher/test/file_watcher/native_test.dart @@ -0,0 +1,23 @@ +// Copyright (c) 2015, 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. + +@TestOn('linux || mac-os') + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/src/file_watcher/native.dart'; +import 'package:watcher/watcher.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +void main() { + watcherFactory = (file) => new NativeFileWatcher(file); + + setUp(() { + createSandbox(); + writeFile("file.txt"); + }); + + sharedTests(); +} diff --git a/pkgs/watcher/test/file_watcher/polling_test.dart b/pkgs/watcher/test/file_watcher/polling_test.dart new file mode 100644 index 000000000..e5025447f --- /dev/null +++ b/pkgs/watcher/test/file_watcher/polling_test.dart @@ -0,0 +1,23 @@ +// Copyright (c) 2015, 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. + +@TestOn('linux || mac-os') + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/watcher.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +void main() { + watcherFactory = (file) => new PollingFileWatcher(file, + pollingDelay: new Duration(milliseconds: 100)); + + setUp(() { + createSandbox(); + writeFile("file.txt"); + }); + + sharedTests(); +} diff --git a/pkgs/watcher/test/file_watcher/shared.dart b/pkgs/watcher/test/file_watcher/shared.dart new file mode 100644 index 000000000..2931d8022 --- /dev/null +++ b/pkgs/watcher/test/file_watcher/shared.dart @@ -0,0 +1,58 @@ +// Copyright (c) 2015, 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. + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/src/utils.dart'; + +import '../utils.dart'; + +void sharedTests() { + test("doesn't notify if the file isn't modified", () { + startWatcher(path: "file.txt"); + // Give the watcher time to fire events if it's going to. + schedule(() => pumpEventQueue()); + deleteFile("file.txt"); + expectRemoveEvent("file.txt"); + }); + + test("notifies when a file is modified", () { + startWatcher(path: "file.txt"); + writeFile("file.txt", contents: "modified"); + expectModifyEvent("file.txt"); + }); + + test("notifies when a file is removed", () { + startWatcher(path: "file.txt"); + deleteFile("file.txt"); + expectRemoveEvent("file.txt"); + }); + + test("notifies when a file is modified multiple times", () { + startWatcher(path: "file.txt"); + writeFile("file.txt", contents: "modified"); + expectModifyEvent("file.txt"); + writeFile("file.txt", contents: "modified again"); + expectModifyEvent("file.txt"); + }); + + test("notifies even if the file contents are unchanged", () { + startWatcher(path: "file.txt"); + writeFile("file.txt"); + expectModifyEvent("file.txt"); + }); + + test("emits a remove event when the watched file is moved away", () { + startWatcher(path: "file.txt"); + renameFile("file.txt", "new.txt"); + expectRemoveEvent("file.txt"); + }); + + test("emits a modify event when another file is moved on top of the watched " + "file", () { + writeFile("old.txt"); + startWatcher(path: "file.txt"); + renameFile("old.txt", "file.txt"); + expectModifyEvent("file.txt"); + }); +} From d976b9cfedcadcdb32de359bbac6bcf4b1e7f581 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 16 Jul 2015 13:45:18 -0700 Subject: [PATCH 086/201] Use the new test runner on the bots. R=rnystrom@google.com Review URL: https://codereview.chromium.org//1228353011 . --- pkgs/watcher/.gitignore | 1 + pkgs/watcher/.status | 30 ------------------------------ pkgs/watcher/.test_config | 5 +++++ 3 files changed, 6 insertions(+), 30 deletions(-) delete mode 100644 pkgs/watcher/.status create mode 100644 pkgs/watcher/.test_config diff --git a/pkgs/watcher/.gitignore b/pkgs/watcher/.gitignore index 388eff0ba..7dbf0350d 100644 --- a/pkgs/watcher/.gitignore +++ b/pkgs/watcher/.gitignore @@ -3,6 +3,7 @@ .pub/ build/ packages +.packages # Or the files created by dart2js. *.dart.js diff --git a/pkgs/watcher/.status b/pkgs/watcher/.status deleted file mode 100644 index e79b8e04a..000000000 --- a/pkgs/watcher/.status +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2014, 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. - -# Skip non-test files ending with "_test". -packages/*: Skip -*/packages/*: Skip -*/*/packages/*: Skip -*/*/*/packages/*: Skip -*/*/*/*packages/*: Skip -*/*/*/*/*packages/*: Skip - -# Only run tests from the build directory, since we don't care about the -# difference between transformed an untransformed code. -test/*: Skip - -[ $browser ] -*: Fail, OK # Uses dart:io. - -[ $arch == simarm && $checked ] -build/test/directory_watcher/linux_test: Skip # Issue 16118 - -[ $runtime == vm && ($system == windows || $system == macos) ] -build/test/*: Skip - -[ $runtime == vm && ($system == windows || $system == linux) ] -build/test/*: Skip - -[ $runtime == vm && ($system == macos || $system == linux) ] -build/test/*: Skip diff --git a/pkgs/watcher/.test_config b/pkgs/watcher/.test_config new file mode 100644 index 000000000..531426abb --- /dev/null +++ b/pkgs/watcher/.test_config @@ -0,0 +1,5 @@ +{ + "test_package": { + "platforms": ["vm"] + } +} \ No newline at end of file From 517a7f2f7c3dc66741b119d07d6053ced9f23aae Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 16 Jul 2015 16:24:57 -0700 Subject: [PATCH 087/201] Fix a race condition in file watcher. It was possible for events to be added to the events controller after it had been closed if the call to close() came in while a method was waiting on an asynchronous callback. R=cbracken@google.com Review URL: https://codereview.chromium.org//1228703007 . --- pkgs/watcher/CHANGELOG.md | 5 +++++ pkgs/watcher/lib/src/file_watcher/native.dart | 15 +++++++++++---- pkgs/watcher/lib/src/file_watcher/polling.dart | 12 +++++++++--- pkgs/watcher/pubspec.yaml | 2 +- pkgs/watcher/test/file_watcher/shared.dart | 17 +++++++++++++++++ 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 4ff6cfaf6..30762a034 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.9.7 + +* Fix a bug in `FileWatcher` where events could be added after watchers were + closed. + # 0.9.6 * Add a `Watcher` interface that encompasses watching both files and diff --git a/pkgs/watcher/lib/src/file_watcher/native.dart b/pkgs/watcher/lib/src/file_watcher/native.dart index f5699bbb4..1862e7b05 100644 --- a/pkgs/watcher/lib/src/file_watcher/native.dart +++ b/pkgs/watcher/lib/src/file_watcher/native.dart @@ -60,10 +60,17 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { } _onDone() async { - // If the file exists now, it was probably removed and quickly replaced; - // this can happen for example when another file is moved on top of it. - // Re-subscribe and report a modify event. - if (await new File(path).exists()) { + var fileExists = await new File(path).exists(); + + // Check for this after checking whether the file exists because it's + // possible that [close] was called between [File.exists] being called and + // it completing. + if (_eventsController.isClosed) return; + + if (fileExists) { + // If the file exists now, it was probably removed and quickly replaced; + // this can happen for example when another file is moved on top of it. + // Re-subscribe and report a modify event. _eventsController.add(new WatchEvent(ChangeType.MODIFY, path)); _listen(); } else { diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart index a44f80cdb..3480ae29e 100644 --- a/pkgs/watcher/lib/src/file_watcher/polling.dart +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -51,7 +51,10 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { // We don't mark the file as removed if this is the first poll (indicated by // [_lastModified] being null). Instead, below we forward the dart:io error // that comes from trying to read the mtime below. - if (_lastModified != null && !await new File(path).exists()) { + var pathExists = await new File(path).exists(); + if (_eventsController.isClosed) return; + + if (_lastModified != null && !pathExists) { _eventsController.add(new WatchEvent(ChangeType.REMOVE, path)); close(); return; @@ -59,14 +62,17 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { var modified; try { - modified = await getModificationTime(path); + try { + modified = await getModificationTime(path); + } finally { + if (_eventsController.isClosed) return; + } } on FileSystemException catch (error, stackTrace) { _eventsController.addError(error, stackTrace); close(); return; } - if (_eventsController.isClosed) return; if (_lastModified == modified) return; if (_lastModified == null) { diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index f62248da2..2e033f153 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.6 +version: 0.9.7 author: Dart Team homepage: http://github.com/dart-lang/watcher description: > diff --git a/pkgs/watcher/test/file_watcher/shared.dart b/pkgs/watcher/test/file_watcher/shared.dart index 2931d8022..9a4965ccc 100644 --- a/pkgs/watcher/test/file_watcher/shared.dart +++ b/pkgs/watcher/test/file_watcher/shared.dart @@ -2,6 +2,8 @@ // 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. +import 'dart:async'; + import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/utils.dart'; @@ -55,4 +57,19 @@ void sharedTests() { renameFile("old.txt", "file.txt"); expectModifyEvent("file.txt"); }); + + // Regression test for a race condition. + test("closes the watcher immediately after deleting the file", () { + writeFile("old.txt"); + var watcher = createWatcher(path: "file.txt", waitForReady: false); + var sub = schedule(() => watcher.events.listen(null)); + + deleteFile("file.txt"); + schedule(() async { + // Reproducing the race condition will always be flaky, but this sleep + // helped it reproduce more consistently on my machine. + await new Future.delayed(new Duration(milliseconds: 10)); + (await sub).cancel(); + }); + }); } From 7baed2ff52ef3d076f21e65e52b3820cebaaae0d Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 20 Jul 2015 15:30:13 -0700 Subject: [PATCH 088/201] Backport fixes from master to 0.9.7+x. --- pkgs/watcher/benchmark/path_set.dart | 147 +++++++++ pkgs/watcher/lib/src/async_queue.dart | 2 - .../src/constructable_file_system_event.dart | 2 - pkgs/watcher/lib/src/directory_watcher.dart | 3 - .../lib/src/directory_watcher/linux.dart | 301 ++++++++---------- .../lib/src/directory_watcher/mac_os.dart | 24 +- .../lib/src/directory_watcher/polling.dart | 2 - .../lib/src/directory_watcher/windows.dart | 32 +- pkgs/watcher/lib/src/file_watcher.dart | 3 - pkgs/watcher/lib/src/file_watcher/native.dart | 2 - .../watcher/lib/src/file_watcher/polling.dart | 2 - pkgs/watcher/lib/src/path_set.dart | 144 +++++---- pkgs/watcher/lib/src/resubscribable.dart | 2 - pkgs/watcher/lib/src/stat.dart | 2 - pkgs/watcher/lib/src/utils.dart | 6 +- pkgs/watcher/lib/src/watch_event.dart | 2 - pkgs/watcher/lib/watcher.dart | 2 - pkgs/watcher/pubspec.yaml | 7 +- .../test/directory_watcher/shared.dart | 31 ++ .../test/file_watcher/native_test.dart | 1 - .../test/no_subscription/mac_os_test.dart | 1 + pkgs/watcher/test/path_set_test.dart | 24 +- pkgs/watcher/test/utils.dart | 14 +- 23 files changed, 432 insertions(+), 324 deletions(-) create mode 100644 pkgs/watcher/benchmark/path_set.dart diff --git a/pkgs/watcher/benchmark/path_set.dart b/pkgs/watcher/benchmark/path_set.dart new file mode 100644 index 000000000..aba3ed750 --- /dev/null +++ b/pkgs/watcher/benchmark/path_set.dart @@ -0,0 +1,147 @@ +// Copyright (c) 2015, 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. + +/// Benchmarks for the PathSet class. +library watcher.benchmark.path_set; + +import 'dart:io'; +import 'dart:math' as math; + +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:path/path.dart' as p; + +import 'package:watcher/src/path_set.dart'; + +final String root = Platform.isWindows ? r"C:\root" : "/root"; + +/// Base class for benchmarks on [PathSet]. +abstract class PathSetBenchmark extends BenchmarkBase { + PathSetBenchmark(String method) : super("PathSet.$method"); + + final PathSet pathSet = new PathSet(root); + + /// Use a fixed [Random] with a constant seed to ensure the tests are + /// deterministic. + final math.Random random = new math.Random(1234); + + /// Walks over a virtual directory [depth] levels deep invoking [callback] + /// for each "file". + /// + /// Each virtual directory contains ten entries: either subdirectories or + /// files. + void walkTree(int depth, callback(String path)) { + recurse(path, remainingDepth) { + for (var i = 0; i < 10; i++) { + var padded = i.toString().padLeft(2, '0'); + if (remainingDepth == 0) { + callback(p.join(path, "file_$padded.txt")); + } else { + var subdir = p.join(path, "subdirectory_$padded"); + recurse(subdir, remainingDepth - 1); + } + } + } + + recurse(root, depth); + } +} + +class AddBenchmark extends PathSetBenchmark { + AddBenchmark() : super("add()"); + + final List paths = []; + + void setup() { + // Make a bunch of paths in about the same order we expect to get them from + // Directory.list(). + walkTree(3, paths.add); + } + + void run() { + for (var path in paths) pathSet.add(path); + } +} + +class ContainsBenchmark extends PathSetBenchmark { + ContainsBenchmark() : super("contains()"); + + final List paths = []; + + void setup() { + // Add a bunch of paths to the set. + walkTree(3, (path) { + pathSet.add(path); + paths.add(path); + }); + + // Add some non-existent paths to test the false case. + for (var i = 0; i < 100; i++) { + paths.addAll([ + "/nope", + "/root/nope", + "/root/subdirectory_04/nope", + "/root/subdirectory_04/subdirectory_04/nope", + "/root/subdirectory_04/subdirectory_04/subdirectory_04/nope", + "/root/subdirectory_04/subdirectory_04/subdirectory_04/nope/file_04.txt", + ]); + } + } + + void run() { + var contained = 0; + for (var path in paths) { + if (pathSet.contains(path)) contained++; + } + + if (contained != 10000) throw "Wrong result: $contained"; + } +} + +class PathsBenchmark extends PathSetBenchmark { + PathsBenchmark() : super("toSet()"); + + void setup() { + walkTree(3, pathSet.add); + } + + void run() { + var count = 0; + for (var _ in pathSet.paths) { + count++; + } + + if (count != 10000) throw "Wrong result: $count"; + } +} + +class RemoveBenchmark extends PathSetBenchmark { + RemoveBenchmark() : super("remove()"); + + final List paths = []; + + void setup() { + // Make a bunch of paths. Do this here so that we don't spend benchmarked + // time synthesizing paths. + walkTree(3, (path) { + pathSet.add(path); + paths.add(path); + }); + + // Shuffle the paths so that we delete them in a random order that + // hopefully mimics real-world file system usage. Do the shuffling here so + // that we don't spend benchmarked time shuffling. + paths.shuffle(random); + } + + void run() { + for (var path in paths) pathSet.remove(path); + } +} + +main() { + new AddBenchmark().report(); + new ContainsBenchmark().report(); + new PathsBenchmark().report(); + new RemoveBenchmark().report(); +} diff --git a/pkgs/watcher/lib/src/async_queue.dart b/pkgs/watcher/lib/src/async_queue.dart index b83493d73..adf667101 100644 --- a/pkgs/watcher/lib/src/async_queue.dart +++ b/pkgs/watcher/lib/src/async_queue.dart @@ -2,8 +2,6 @@ // 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.async_queue; - import 'dart:async'; import 'dart:collection'; diff --git a/pkgs/watcher/lib/src/constructable_file_system_event.dart b/pkgs/watcher/lib/src/constructable_file_system_event.dart index d00a1dce7..63b51c192 100644 --- a/pkgs/watcher/lib/src/constructable_file_system_event.dart +++ b/pkgs/watcher/lib/src/constructable_file_system_event.dart @@ -2,8 +2,6 @@ // 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.constructable_file_system_event; - import 'dart:io'; abstract class _ConstructableFileSystemEvent implements FileSystemEvent { diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 82837855b..6beebd0a4 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -2,11 +2,8 @@ // 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.directory_watcher; - import 'dart:io'; -import 'watch_event.dart'; import '../watcher.dart'; import 'directory_watcher/linux.dart'; import 'directory_watcher/mac_os.dart'; diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index a747839d2..f327bb0d3 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -2,12 +2,13 @@ // 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.directory_watcher.linux; - import 'dart:async'; import 'dart:io'; +import 'package:async/async.dart'; + import '../directory_watcher.dart'; +import '../path_set.dart'; import '../resubscribable.dart'; import '../utils.dart'; import '../watch_event.dart'; @@ -32,8 +33,8 @@ class LinuxDirectoryWatcher extends ResubscribableWatcher class _LinuxDirectoryWatcher implements DirectoryWatcher, ManuallyClosedWatcher { - String get directory => path; - final String path; + String get directory => _files.root; + String get path => _files.root; Stream get events => _eventsController.stream; final _eventsController = new StreamController.broadcast(); @@ -43,15 +44,17 @@ class _LinuxDirectoryWatcher Future get ready => _readyCompleter.future; final _readyCompleter = new Completer(); - /// The last known state for each entry in this directory. - /// - /// The keys in this map are the paths to the directory entries; the values - /// are [_EntryState]s indicating whether the entries are files or - /// directories. - final _entries = new Map(); + /// A stream group for the [Directory.watch] events of [path] and all its + /// subdirectories. + var _nativeEvents = new StreamGroup(); - /// The watchers for subdirectories of [directory]. - final _subWatchers = new Map(); + /// All known files recursively within [path]. + final PathSet _files; + + /// [Directory.watch] streams for [path]'s subdirectories, indexed by name. + /// + /// A stream is in this map if and only if it's also in [_nativeEvents]. + final _subdirStreams = >{}; /// A set of all subscriptions that this watcher subscribes to. /// @@ -59,93 +62,51 @@ class _LinuxDirectoryWatcher /// watcher is closed. final _subscriptions = new Set(); - _LinuxDirectoryWatcher(this.path) { + _LinuxDirectoryWatcher(String path) + : _files = new PathSet(path) { + _nativeEvents.add(new Directory(path).watch().transform( + new StreamTransformer.fromHandlers(handleDone: (sink) { + // Once the root directory is deleted, no more new subdirectories will be + // watched. + _nativeEvents.close(); + sink.close(); + }))); + // Batch the inotify changes together so that we can dedup events. - var innerStream = new Directory(path).watch() + var innerStream = _nativeEvents.stream .transform(new BatchedStreamTransformer()); _listen(innerStream, _onBatch, onError: _eventsController.addError, onDone: _onDone); - _listen(new Directory(path).list(), (entity) { - _entries[entity.path] = new _EntryState(entity is Directory); - if (entity is! Directory) return; - _watchSubdir(entity.path); + _listen(new Directory(path).list(recursive: true), (entity) { + if (entity is Directory) { + _watchSubdir(entity.path); + } else { + _files.add(entity.path); + } }, onError: (error, stackTrace) { _eventsController.addError(error, stackTrace); close(); }, onDone: () { - _waitUntilReady().then((_) => _readyCompleter.complete()); + _readyCompleter.complete(); }, cancelOnError: true); } - /// Returns a [Future] that completes once all the subdirectory watchers are - /// fully initialized. - Future _waitUntilReady() { - return Future.wait(_subWatchers.values.map((watcher) => watcher.ready)) - .then((_) { - if (_subWatchers.values.every((watcher) => watcher.isReady)) return null; - return _waitUntilReady(); - }); - } - void close() { for (var subscription in _subscriptions) { subscription.cancel(); } - for (var watcher in _subWatchers.values) { - watcher.close(); - } - _subWatchers.clear(); _subscriptions.clear(); + _subdirStreams.clear(); + _files.clear(); + _nativeEvents.close(); _eventsController.close(); } - /// Returns all files (not directories) that this watcher knows of are - /// recursively in the watched directory. - Set get _allFiles { - var files = new Set(); - _getAllFiles(files); - return files; - } - - /// Helper function for [_allFiles]. - /// - /// Adds all files that this watcher knows of to [files]. - void _getAllFiles(Set files) { - files.addAll(_entries.keys - .where((path) => _entries[path] == _EntryState.FILE).toSet()); - for (var watcher in _subWatchers.values) { - watcher._getAllFiles(files); - } - } - /// Watch a subdirectory of [directory] for changes. - /// - /// If the subdirectory was added after [this] began emitting events, its - /// contents will be emitted as ADD events. void _watchSubdir(String path) { - if (_subWatchers.containsKey(path)) return; - var watcher = new _LinuxDirectoryWatcher(path); - _subWatchers[path] = watcher; - - // TODO(nweiz): Catch any errors here that indicate that the directory in - // question doesn't exist and silently stop watching it instead of - // propagating the errors. - _listen(watcher.events, (event) { - if (isReady) _eventsController.add(event); - }, onError: (error, stackTrace) { - _eventsController.addError(error, stackTrace); - close(); - }, onDone: () { - if (_subWatchers[path] == watcher) _subWatchers.remove(path); - - // It's possible that a directory was removed and recreated very quickly. - // If so, make sure we're still watching it. - if (new Directory(path).existsSync()) _watchSubdir(path); - }); - // TODO(nweiz): Right now it's possible for the watcher to emit an event for // a file before the directory list is complete. This could lead to the user // seeing a MODIFY or REMOVE event for a file before they see an ADD event, @@ -157,96 +118,110 @@ class _LinuxDirectoryWatcher // top-level clients such as barback as well, and could be implemented with // a wrapper similar to how listening/canceling works now. - // If a directory is added after we're finished with the initial scan, emit - // an event for each entry in it. This gives the user consistently gets an - // event for every new file. - watcher.ready.then((_) { - if (!isReady || _eventsController.isClosed) return; - _listen(new Directory(path).list(recursive: true), (entry) { - if (entry is Directory) return; - _eventsController.add(new WatchEvent(ChangeType.ADD, entry.path)); - }, onError: (error, stackTrace) { - // Ignore an exception caused by the dir not existing. It's fine if it - // was added and then quickly removed. - if (error is FileSystemException) return; - - _eventsController.addError(error, stackTrace); - close(); - }, cancelOnError: true); - }); + // TODO(nweiz): Catch any errors here that indicate that the directory in + // question doesn't exist and silently stop watching it instead of + // propagating the errors. + var stream = new Directory(path).watch(); + _subdirStreams[path] = stream; + _nativeEvents.add(stream); } /// The callback that's run when a batch of changes comes in. void _onBatch(List batch) { - var changedEntries = new Set(); - var oldEntries = new Map.from(_entries); + var files = new Set(); + var dirs = new Set(); + var changed = new Set(); // inotify event batches are ordered by occurrence, so we treat them as a - // log of what happened to a file. + // log of what happened to a file. We only emit events based on the + // difference between the state before the batch and the state after it, not + // the intermediate state. for (var event in batch) { // If the watched directory is deleted or moved, we'll get a deletion // event for it. Ignore it; we handle closing [this] when the underlying // stream is closed. if (event.path == path) continue; - changedEntries.add(event.path); + changed.add(event.path); if (event is FileSystemMoveEvent) { - changedEntries.add(event.destination); - _changeEntryState(event.path, ChangeType.REMOVE, event.isDirectory); - _changeEntryState(event.destination, ChangeType.ADD, event.isDirectory); + files.remove(event.path); + dirs.remove(event.path); + + changed.add(event.destination); + if (event.isDirectory) { + files.remove(event.destination); + dirs.add(event.destination); + } else { + files.add(event.destination); + dirs.remove(event.destination); + } + } else if (event is FileSystemDeleteEvent) { + files.remove(event.path); + dirs.remove(event.path); + } else if (event.isDirectory) { + files.remove(event.path); + dirs.add(event.path); } else { - _changeEntryState(event.path, _changeTypeFor(event), event.isDirectory); + files.add(event.path); + dirs.remove(event.path); } } - for (var path in changedEntries) { - emitEvent(ChangeType type) { - if (isReady) _eventsController.add(new WatchEvent(type, path)); - } - - var oldState = oldEntries[path]; - var newState = _entries[path]; + _applyChanges(files, dirs, changed); + } - if (oldState != _EntryState.FILE && newState == _EntryState.FILE) { - emitEvent(ChangeType.ADD); - } else if (oldState == _EntryState.FILE && newState == _EntryState.FILE) { - emitEvent(ChangeType.MODIFY); - } else if (oldState == _EntryState.FILE && newState != _EntryState.FILE) { - emitEvent(ChangeType.REMOVE); + /// Applies the net changes computed for a batch. + /// + /// The [files] and [dirs] sets contain the files and directories that now + /// exist, respectively. The [changed] set contains all files and directories + /// that have changed (including being removed), and so is a superset of + /// [files] and [dirs]. + void _applyChanges(Set files, Set dirs, Set changed) { + for (var path in changed) { + var stream = _subdirStreams.remove(path); + if (stream != null) _nativeEvents.add(stream); + + // Unless [path] was a file and still is, emit REMOVE events for it or its + // contents, + if (files.contains(path) && _files.contains(path)) continue; + for (var file in _files.remove(path)) { + _emit(ChangeType.REMOVE, file); } + } - if (oldState == _EntryState.DIRECTORY) { - var watcher = _subWatchers.remove(path); - if (watcher == null) continue; - for (var path in watcher._allFiles) { - _eventsController.add(new WatchEvent(ChangeType.REMOVE, path)); - } - watcher.close(); + for (var file in files) { + if (_files.contains(file)) { + _emit(ChangeType.MODIFY, file); + } else { + _emit(ChangeType.ADD, file); + _files.add(file); } - - if (newState == _EntryState.DIRECTORY) _watchSubdir(path); } - } - /// Changes the known state of the entry at [path] based on [change] and - /// [isDir]. - void _changeEntryState(String path, ChangeType change, bool isDir) { - if (change == ChangeType.ADD || change == ChangeType.MODIFY) { - _entries[path] = new _EntryState(isDir); - } else { - assert(change == ChangeType.REMOVE); - _entries.remove(path); + for (var dir in dirs) { + _watchSubdir(dir); + _addSubdir(dir); } } - /// Determines the [ChangeType] associated with [event]. - ChangeType _changeTypeFor(FileSystemEvent event) { - if (event is FileSystemDeleteEvent) return ChangeType.REMOVE; - if (event is FileSystemCreateEvent) return ChangeType.ADD; + /// Emits [ChangeType.ADD] events for the recursive contents of [path]. + void _addSubdir(String path) { + _listen(new Directory(path).list(recursive: true), (entity) { + if (entity is Directory) { + _watchSubdir(entity.path); + } else { + _files.add(entity.path); + _emit(ChangeType.ADD, entity.path); + } + }, onError: (error, stackTrace) { + // Ignore an exception caused by the dir not existing. It's fine if it + // was added and then quickly removed. + if (error is FileSystemException) return; - assert(event is FileSystemModifyEvent); - return ChangeType.MODIFY; + _eventsController.addError(error, stackTrace); + close(); + }, cancelOnError: true); } /// Handles the underlying event stream closing, indicating that the directory @@ -254,28 +229,23 @@ class _LinuxDirectoryWatcher void _onDone() { // Most of the time when a directory is removed, its contents will get // individual REMOVE events before the watch stream is closed -- in that - // case, [_entries] will be empty here. However, if the directory's removal - // is caused by a MOVE, we need to manually emit events. + // case, [_files] will be empty here. However, if the directory's removal is + // caused by a MOVE, we need to manually emit events. if (isReady) { - _entries.forEach((path, state) { - if (state == _EntryState.DIRECTORY) return; - _eventsController.add(new WatchEvent(ChangeType.REMOVE, path)); - }); + for (var file in _files.paths) { + _emit(ChangeType.REMOVE, file); + } } - // The parent directory often gets a close event before the subdirectories - // are done emitting events. We wait for them to finish before we close - // [events] so that we can be sure to emit a remove event for every file - // that used to exist. - Future.wait(_subWatchers.values.map((watcher) { - try { - return watcher.events.toList(); - } on StateError catch (_) { - // It's possible that [watcher.events] is closed but the onDone event - // hasn't reached us yet. It's fine if so. - return new Future.value(); - } - })).then((_) => close()); + close(); + } + + /// Emits a [WatchEvent] with [type] and [path] if this watcher is in a state + /// to emit events. + void _emit(ChangeType type, String path) { + if (!isReady) return; + if (_eventsController.isClosed) return; + _eventsController.add(new WatchEvent(type, path)); } /// Like [Stream.listen], but automatically adds the subscription to @@ -290,22 +260,3 @@ class _LinuxDirectoryWatcher _subscriptions.add(subscription); } } - -/// An enum for the possible states of entries in a watched directory. -class _EntryState { - final String _name; - - /// The entry is a file. - static const FILE = const _EntryState._("file"); - - /// The entry is a directory. - static const DIRECTORY = const _EntryState._("directory"); - - const _EntryState._(this._name); - - /// Returns [DIRECTORY] if [isDir] is true, and [FILE] otherwise. - factory _EntryState(bool isDir) => - isDir ? _EntryState.DIRECTORY : _EntryState.FILE; - - String toString() => _name; -} diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 487225eb4..8a17e2e79 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -2,8 +2,6 @@ // 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.directory_watcher.mac_os; - import 'dart:async'; import 'dart:io'; @@ -58,7 +56,7 @@ class _MacOSDirectoryWatcher /// /// This is separate from [_subscriptions] because this stream occasionally /// needs to be resubscribed in order to work around issue 14849. - StreamSubscription _watchSubscription; + StreamSubscription> _watchSubscription; /// The subscription to the [Directory.list] call for the initial listing of /// the directory to determine its initial state. @@ -116,9 +114,9 @@ class _MacOSDirectoryWatcher return; } - _sortEvents(batch).forEach((path, events) { - var canonicalEvent = _canonicalEvent(events); - events = canonicalEvent == null ? + _sortEvents(batch).forEach((path, eventSet) { + var canonicalEvent = _canonicalEvent(eventSet); + var events = canonicalEvent == null ? _eventsBasedOnFileSystem(path) : [canonicalEvent]; for (var event in events) { @@ -139,7 +137,7 @@ class _MacOSDirectoryWatcher if (_files.containsDir(path)) continue; - var subscription; + StreamSubscription subscription; subscription = new Directory(path).list(recursive: true) .listen((entity) { if (entity is Directory) return; @@ -175,7 +173,7 @@ class _MacOSDirectoryWatcher /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it /// contain any events relating to [path]. Map> _sortEvents(List batch) { - var eventsForPaths = {}; + var eventsForPaths = {}; // FSEvents can report past events, including events on the root directory // such as it being created. We want to ignore these. If the directory is @@ -187,8 +185,10 @@ class _MacOSDirectoryWatcher // events. Emitting them could cause useless or out-of-order events. var directories = unionAll(batch.map((event) { if (!event.isDirectory) return new Set(); - if (event is! FileSystemMoveEvent) return new Set.from([event.path]); - return new Set.from([event.path, event.destination]); + if (event is FileSystemMoveEvent) { + return new Set.from([event.path, event.destination]); + } + return new Set.from([event.path]); })); isInModifiedDirectory(path) => @@ -294,7 +294,7 @@ class _MacOSDirectoryWatcher var fileExists = new File(path).existsSync(); var dirExists = new Directory(path).existsSync(); - var events = []; + var events = []; if (fileExisted) { if (fileExists) { events.add(new ConstructableFileSystemModifyEvent(path, false, false)); @@ -337,7 +337,7 @@ class _MacOSDirectoryWatcher // FSEvents can fail to report the contents of the directory being removed // when the directory itself is removed, so we need to manually mark the // files as removed. - for (var file in _files.toSet()) { + for (var file in _files.paths) { _emitEvent(ChangeType.REMOVE, file); } _files.clear(); diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 7f417d630..ebc170963 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -2,8 +2,6 @@ // 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.directory_watcher.polling; - import 'dart:async'; import 'dart:io'; diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 089951964..67a274198 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -3,8 +3,6 @@ // BSD-style license that can be found in the LICENSE file. // TODO(rnystrom): Merge with mac_os version. -library watcher.directory_watcher.windows; - import 'dart:async'; import 'dart:collection'; import 'dart:io'; @@ -136,7 +134,7 @@ class _WindowsDirectoryWatcher event is FileSystemDeleteEvent || (FileSystemEntity.typeSync(path) == FileSystemEntityType.NOT_FOUND)) { - for (var path in _files.toSet()) { + for (var path in _files.paths) { _emitEvent(ChangeType.REMOVE, path); } _files.clear(); @@ -163,10 +161,10 @@ class _WindowsDirectoryWatcher /// The callback that's run when [Directory.watch] emits a batch of events. void _onBatch(List batch) { - _sortEvents(batch).forEach((path, events) { + _sortEvents(batch).forEach((path, eventSet) { - var canonicalEvent = _canonicalEvent(events); - events = canonicalEvent == null ? + var canonicalEvent = _canonicalEvent(eventSet); + var events = canonicalEvent == null ? _eventsBasedOnFileSystem(path) : [canonicalEvent]; for (var event in events) { @@ -182,20 +180,20 @@ class _WindowsDirectoryWatcher if (_files.containsDir(path)) continue; var stream = new Directory(path).list(recursive: true); - var sub; - sub = stream.listen((entity) { + StreamSubscription subscription; + subscription = stream.listen((entity) { if (entity is Directory) return; if (_files.contains(path)) return; _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); }, onDone: () { - _listSubscriptions.remove(sub); + _listSubscriptions.remove(subscription); }, onError: (e, stackTrace) { - _listSubscriptions.remove(sub); + _listSubscriptions.remove(subscription); _emitError(e, stackTrace); }, cancelOnError: true); - _listSubscriptions.add(sub); + _listSubscriptions.add(subscription); } else if (event is FileSystemModifyEvent) { if (!event.isDirectory) { _emitEvent(ChangeType.MODIFY, path); @@ -219,15 +217,17 @@ class _WindowsDirectoryWatcher /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it /// contain any events relating to [path]. Map> _sortEvents(List batch) { - var eventsForPaths = {}; + var eventsForPaths = {}; // Events within directories that already have events are superfluous; the // directory's full contents will be examined anyway, so we ignore such // events. Emitting them could cause useless or out-of-order events. var directories = unionAll(batch.map((event) { if (!event.isDirectory) return new Set(); - if (event is! FileSystemMoveEvent) return new Set.from([event.path]); - return new Set.from([event.path, event.destination]); + if (event is FileSystemMoveEvent) { + return new Set.from([event.path, event.destination]); + } + return new Set.from([event.path]); })); isInModifiedDirectory(path) => @@ -322,7 +322,7 @@ class _WindowsDirectoryWatcher var fileExists = new File(path).existsSync(); var dirExists = new Directory(path).existsSync(); - var events = []; + var events = []; if (fileExisted) { if (fileExists) { events.add(new ConstructableFileSystemModifyEvent(path, false, false)); @@ -357,7 +357,7 @@ class _WindowsDirectoryWatcher _watchSubscription = null; // Emit remove events for any remaining files. - for (var file in _files.toSet()) { + for (var file in _files.paths) { _emitEvent(ChangeType.REMOVE, file); } _files.clear(); diff --git a/pkgs/watcher/lib/src/file_watcher.dart b/pkgs/watcher/lib/src/file_watcher.dart index 9b315378d..17c5f2efb 100644 --- a/pkgs/watcher/lib/src/file_watcher.dart +++ b/pkgs/watcher/lib/src/file_watcher.dart @@ -2,11 +2,8 @@ // 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.file_watcher; - import 'dart:io'; -import 'watch_event.dart'; import '../watcher.dart'; import 'file_watcher/native.dart'; import 'file_watcher/polling.dart'; diff --git a/pkgs/watcher/lib/src/file_watcher/native.dart b/pkgs/watcher/lib/src/file_watcher/native.dart index 1862e7b05..f413a72c4 100644 --- a/pkgs/watcher/lib/src/file_watcher/native.dart +++ b/pkgs/watcher/lib/src/file_watcher/native.dart @@ -2,8 +2,6 @@ // 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.file_watcher.native; - import 'dart:async'; import 'dart:io'; diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart index 3480ae29e..3f2e9f143 100644 --- a/pkgs/watcher/lib/src/file_watcher/polling.dart +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -2,8 +2,6 @@ // 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.file_watcher.polling; - import 'dart:async'; import 'dart:io'; diff --git a/pkgs/watcher/lib/src/path_set.dart b/pkgs/watcher/lib/src/path_set.dart index e9f7d32d2..3726e1fd8 100644 --- a/pkgs/watcher/lib/src/path_set.dart +++ b/pkgs/watcher/lib/src/path_set.dart @@ -2,8 +2,6 @@ // 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.path_dart; - import 'dart:collection'; import 'package:path/path.dart' as p; @@ -21,32 +19,24 @@ class PathSet { /// The path set's directory hierarchy. /// - /// Each level of this hierarchy has the same structure: a map from strings to - /// other maps, which are further levels of the hierarchy. A map with no - /// elements indicates a path that was added to the set that has no paths - /// beneath it. Such a path should not be treated as a directory by - /// [containsDir]. - final _entries = new Map(); - - /// The set of paths that were explicitly added to this set. - /// - /// This is needed to disambiguate a directory that was explicitly added to - /// the set from a directory that was implicitly added by adding a path - /// beneath it. - final _paths = new Set(); + /// Each entry represents a directory or file. It may be a file or directory + /// that was explicitly added, or a parent directory that was implicitly + /// added in order to add a child. + final _Entry _entries = new _Entry(); PathSet(this.root); /// Adds [path] to the set. void add(String path) { path = _normalize(path); - _paths.add(path); - var parts = _split(path); - var dir = _entries; + var parts = p.split(path); + var entry = _entries; for (var part in parts) { - dir = dir.putIfAbsent(part, () => {}); + entry = entry.contents.putIfAbsent(part, () => new _Entry()); } + + entry.isExplicit = true; } /// Removes [path] and any paths beneath it from the set and returns the @@ -59,110 +49,140 @@ class PathSet { /// empty set. Set remove(String path) { path = _normalize(path); - var parts = new Queue.from(_split(path)); + var parts = new Queue.from(p.split(path)); // Remove the children of [dir], as well as [dir] itself if necessary. // // [partialPath] is the path to [dir], and a prefix of [path]; the remaining // components of [path] are in [parts]. - recurse(dir, partialPath) { + Set recurse(dir, partialPath) { if (parts.length > 1) { // If there's more than one component left in [path], recurse down to // the next level. var part = parts.removeFirst(); - var entry = dir[part]; - if (entry == null || entry.isEmpty) return new Set(); + var entry = dir.contents[part]; + if (entry == null || entry.contents.isEmpty) return new Set(); partialPath = p.join(partialPath, part); var paths = recurse(entry, partialPath); // After removing this entry's children, if it has no more children and // it's not in the set in its own right, remove it as well. - if (entry.isEmpty && !_paths.contains(partialPath)) dir.remove(part); + if (entry.contents.isEmpty && !entry.isExplicit) { + dir.contents.remove(part); + } return paths; } // If there's only one component left in [path], we should remove it. - var entry = dir.remove(parts.first); + var entry = dir.contents.remove(parts.first); if (entry == null) return new Set(); - if (entry.isEmpty) { - _paths.remove(path); - return new Set.from([path]); + if (entry.contents.isEmpty) { + return new Set.from([p.join(root, path)]); } - var set = _removePathsIn(entry, path); - if (_paths.contains(path)) { - _paths.remove(path); - set.add(path); + var set = _explicitPathsWithin(entry, path); + if (entry.isExplicit) { + set.add(p.join(root, path)); } + return set; } return recurse(_entries, root); } - /// Recursively removes and returns all paths in [dir]. + /// Recursively lists all of the explicit paths within [dir]. /// - /// [root] should be the path to [dir]. - Set _removePathsIn(Map dir, String root) { - var removedPaths = new Set(); + /// [dirPath] should be the path to [dir]. + Set _explicitPathsWithin(_Entry dir, String dirPath) { + var paths = new Set(); recurse(dir, path) { - dir.forEach((name, entry) { + dir.contents.forEach((name, entry) { var entryPath = p.join(path, name); - if (_paths.remove(entryPath)) removedPaths.add(entryPath); + if (entry.isExplicit) paths.add(p.join(root, entryPath)); + recurse(entry, entryPath); }); } - recurse(dir, root); - return removedPaths; + recurse(dir, dirPath); + return paths; } /// Returns whether [this] contains [path]. /// /// This only returns true for paths explicitly added to [this]. /// Implicitly-added directories can be inspected using [containsDir]. - bool contains(String path) => _paths.contains(_normalize(path)); + bool contains(String path) { + path = _normalize(path); + var entry = _entries; + + for (var part in p.split(path)) { + entry = entry.contents[part]; + if (entry == null) return false; + } + + return entry.isExplicit; + } /// Returns whether [this] contains paths beneath [path]. bool containsDir(String path) { path = _normalize(path); - var dir = _entries; + var entry = _entries; - for (var part in _split(path)) { - dir = dir[part]; - if (dir == null) return false; + for (var part in p.split(path)) { + entry = entry.contents[part]; + if (entry == null) return false; } - return !dir.isEmpty; + return !entry.contents.isEmpty; } - /// Returns a [Set] of all paths in [this]. - Set toSet() => _paths.toSet(); + /// All of the paths explicitly added to this set. + List get paths { + var result = []; + + recurse(dir, path) { + for (var name in dir.contents.keys) { + var entry = dir.contents[name]; + var entryPath = p.join(path, name); + if (entry.isExplicit) result.add(entryPath); + recurse(entry, entryPath); + } + } + + recurse(_entries, root); + return result; + } /// Removes all paths from [this]. void clear() { - _paths.clear(); - _entries.clear(); + _entries.contents.clear(); } - String toString() => _paths.toString(); - /// Returns a normalized version of [path]. /// /// This removes any extra ".." or "."s and ensure that the returned path /// begins with [root]. It's an error if [path] isn't within [root]. String _normalize(String path) { - var relative = p.relative(p.normalize(path), from: root); - var parts = p.split(relative); - // TODO(nweiz): replace this with [p.isWithin] when that exists (issue - // 14980). - if (!p.isRelative(relative) || parts.first == '..' || parts.first == '.') { - throw new ArgumentError('Path "$path" is not inside "$root".'); - } - return p.join(root, relative); + assert(p.isWithin(root, path)); + + return p.relative(p.normalize(path), from: root); } +} - /// Returns the segments of [path] beneath [root]. - List _split(String path) => p.split(p.relative(path, from: root)); +/// A virtual file system entity tracked by the [PathSet]. +/// +/// It may have child entries in [contents], which implies it's a directory. +class _Entry { + /// The child entries contained in this directory. + final Map contents = {}; + + /// If this entry was explicitly added as a leaf file system entity, this + /// will be true. + /// + /// Otherwise, it represents a parent directory that was implicitly added + /// when added some child of it. + bool isExplicit = false; } diff --git a/pkgs/watcher/lib/src/resubscribable.dart b/pkgs/watcher/lib/src/resubscribable.dart index 2844c1e2c..aeefe9366 100644 --- a/pkgs/watcher/lib/src/resubscribable.dart +++ b/pkgs/watcher/lib/src/resubscribable.dart @@ -2,8 +2,6 @@ // 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.resubscribable; - import 'dart:async'; import '../watcher.dart'; diff --git a/pkgs/watcher/lib/src/stat.dart b/pkgs/watcher/lib/src/stat.dart index d36eff3bd..05ee9ba0a 100644 --- a/pkgs/watcher/lib/src/stat.dart +++ b/pkgs/watcher/lib/src/stat.dart @@ -2,8 +2,6 @@ // 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.stat; - import 'dart:async'; import 'dart:io'; diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index 007c84c19..d263f2f7c 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -2,8 +2,6 @@ // 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:async'; import 'dart:io'; import 'dart:collection'; @@ -33,7 +31,7 @@ Set unionAll(Iterable sets) => /// [broadcast] defaults to false. Stream futureStream(Future future, {bool broadcast: false}) { var subscription; - var controller; + StreamController controller; future = future.catchError((e, stackTrace) { // Since [controller] is synchronous, it's likely that emitting an error @@ -94,7 +92,7 @@ Future pumpEventQueue([int times = 20]) { /// microtasks. class BatchedStreamTransformer implements StreamTransformer> { Stream> bind(Stream input) { - var batch = new Queue(); + var batch = new Queue(); return new StreamTransformer>.fromHandlers( handleData: (event, sink) { batch.add(event); diff --git a/pkgs/watcher/lib/src/watch_event.dart b/pkgs/watcher/lib/src/watch_event.dart index be6d70c92..54093a5d7 100644 --- a/pkgs/watcher/lib/src/watch_event.dart +++ b/pkgs/watcher/lib/src/watch_event.dart @@ -2,8 +2,6 @@ // 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.watch_event; - /// An event describing a single change to the file system. class WatchEvent { /// The manner in which the file at [path] has changed. diff --git a/pkgs/watcher/lib/watcher.dart b/pkgs/watcher/lib/watcher.dart index 79dcc0dae..b3cebe665 100644 --- a/pkgs/watcher/lib/watcher.dart +++ b/pkgs/watcher/lib/watcher.dart @@ -2,8 +2,6 @@ // 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; - import 'dart:async'; import 'dart:io'; diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 2e033f153..c0152e032 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,14 +1,17 @@ name: watcher -version: 0.9.7 +version: 0.9.8-dev author: Dart Team -homepage: http://github.com/dart-lang/watcher +homepage: https://github.com/dart-lang/watcher description: > A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. environment: sdk: '>=1.9.0 <2.0.0' dependencies: + async: '^1.2.0' + collection: '^1.0.0' path: '>=0.9.0 <2.0.0' dev_dependencies: + benchmark_harness: '^1.0.4' scheduled_test: '^0.12.0' test: '^0.12.0' diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index ff48cb2aa..c95ee39f4 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -246,6 +246,37 @@ void sharedTests() { expectModifyEvent("new/file.txt"); }); + test('notifies when a file is replaced by a subdirectory', () { + writeFile("new"); + writeFile("old/file.txt"); + startWatcher(); + + deleteFile("new"); + renameDir("old", "new"); + inAnyOrder([ + isRemoveEvent("new"), + isRemoveEvent("old/file.txt"), + isAddEvent("new/file.txt") + ]); + }); + + test('notifies when a subdirectory is replaced by a file', () { + writeFile("old"); + writeFile("new/file.txt"); + startWatcher(); + + renameDir("new", "newer"); + renameFile("old", "new"); + inAnyOrder([ + isRemoveEvent("new/file.txt"), + isAddEvent("newer/file.txt"), + isRemoveEvent("old"), + isAddEvent("new") + ]); + }, onPlatform: { + "mac-os": new Skip("https://github.com/dart-lang/watcher/issues/21") + }); + test('emits events for many nested files added at once', () { withPermutations((i, j, k) => writeFile("sub/sub-$i/sub-$j/file-$k.txt")); diff --git a/pkgs/watcher/test/file_watcher/native_test.dart b/pkgs/watcher/test/file_watcher/native_test.dart index 30d0eca42..cbf11b6cd 100644 --- a/pkgs/watcher/test/file_watcher/native_test.dart +++ b/pkgs/watcher/test/file_watcher/native_test.dart @@ -6,7 +6,6 @@ import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/file_watcher/native.dart'; -import 'package:watcher/watcher.dart'; import 'shared.dart'; import '../utils.dart'; diff --git a/pkgs/watcher/test/no_subscription/mac_os_test.dart b/pkgs/watcher/test/no_subscription/mac_os_test.dart index d5b1c8e3f..499aff3ca 100644 --- a/pkgs/watcher/test/no_subscription/mac_os_test.dart +++ b/pkgs/watcher/test/no_subscription/mac_os_test.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('mac-os') +@Skip("Flaky due to sdk#23877") import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/src/directory_watcher/mac_os.dart'; diff --git a/pkgs/watcher/test/path_set_test.dart b/pkgs/watcher/test/path_set_test.dart index 13e797c20..d3420d377 100644 --- a/pkgs/watcher/test/path_set_test.dart +++ b/pkgs/watcher/test/path_set_test.dart @@ -6,8 +6,6 @@ import 'package:path/path.dart' as p; import 'package:test/test.dart'; import 'package:watcher/src/path_set.dart'; -import 'utils.dart'; - Matcher containsPath(String path) => predicate((set) => set is PathSet && set.contains(path), 'set contains "$path"'); @@ -42,10 +40,6 @@ void main() { set.add(p.absolute("root/path/to/file")); expect(set, containsPath("root/path/to/file")); }); - - test("that's not beneath the root throws an error", () { - expect(() => set.add("path/to/file"), throwsArgumentError); - }); }); group("removing a path", () { @@ -78,7 +72,7 @@ void main() { expect(set, isNot(containsPath("root/path/to/two"))); expect(set, isNot(containsPath("root/path/to/sub/three"))); }); - + test("that's a directory in the set removes and returns it and all files " "beneath it", () { set.add("root/path"); @@ -110,10 +104,6 @@ void main() { expect(set.remove(p.absolute("root/path/to/file")), unorderedEquals([p.normalize("root/path/to/file")])); }); - - test("that's not beneath the root throws an error", () { - expect(() => set.remove("path/to/file"), throwsArgumentError); - }); }); group("containsPath()", () { @@ -143,10 +133,6 @@ void main() { set.add("root/path/to/file"); expect(set, containsPath(p.absolute("root/path/to/file"))); }); - - test("with a path that's not beneath the root throws an error", () { - expect(() => set.contains("path/to/file"), throwsArgumentError); - }); }); group("containsDir()", () { @@ -198,13 +184,13 @@ void main() { }); }); - group("toSet", () { + group("paths", () { test("returns paths added to the set", () { set.add("root/path"); set.add("root/path/to/one"); set.add("root/path/to/two"); - expect(set.toSet(), unorderedEquals([ + expect(set.paths, unorderedEquals([ "root/path", "root/path/to/one", "root/path/to/two", @@ -216,7 +202,7 @@ void main() { set.add("root/path/to/two"); set.remove("root/path/to/two"); - expect(set.toSet(), unorderedEquals([p.normalize("root/path/to/one")])); + expect(set.paths, unorderedEquals([p.normalize("root/path/to/one")])); }); }); @@ -227,7 +213,7 @@ void main() { set.add("root/path/to/two"); set.clear(); - expect(set.toSet(), isEmpty); + expect(set.paths, isEmpty); }); }); } diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 7dd833231..e3c2a1b4d 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -2,8 +2,6 @@ // 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.test.utils; - import 'dart:io'; import 'package:path/path.dart' as p; @@ -13,9 +11,6 @@ import 'package:watcher/watcher.dart'; import 'package:watcher/src/stat.dart'; import 'package:watcher/src/utils.dart'; -// TODO(nweiz): remove this when issue 15042 is fixed. -import 'package:watcher/src/directory_watcher/mac_os.dart'; - /// The path to the temporary sandbox created for each test. All file /// operations are implicitly relative to this directory. String _sandboxDir; @@ -292,7 +287,7 @@ void writeFile(String path, {String contents, bool updateModified}) { // Make sure we always use the same separator on Windows. path = p.normalize(path); - var milliseconds = _mockFileModificationTimes.putIfAbsent(path, () => 0); + _mockFileModificationTimes.putIfAbsent(path, () => 0); _mockFileModificationTimes[path]++; } }, "write file $path"); @@ -316,7 +311,7 @@ void renameFile(String from, String to) { to = p.normalize(to); // Manually update the mock modification time for the file. - var milliseconds = _mockFileModificationTimes.putIfAbsent(to, () => 0); + _mockFileModificationTimes.putIfAbsent(to, () => 0); _mockFileModificationTimes[to]++; }, "rename file $from to $to"); } @@ -349,9 +344,10 @@ void deleteDir(String path) { /// Returns a set of all values returns by [callback]. /// /// [limit] defaults to 3. -Set withPermutations(callback(int i, int j, int k), {int limit}) { +Set/**/ withPermutations/**/(/*=S*/ callback(int i, int j, int k), + {int limit}) { if (limit == null) limit = 3; - var results = new Set(); + var results = new Set/**/(); for (var i = 0; i < limit; i++) { for (var j = 0; j < limit; j++) { for (var k = 0; k < limit; k++) { From f256c7d89b8f30026fd60a3dd3f40df590d56071 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 2 May 2016 14:48:23 -0700 Subject: [PATCH 089/201] Fix all strong-mode warnings. --- pkgs/watcher/.analysis_options | 2 ++ pkgs/watcher/CHANGELOG.md | 4 ++++ pkgs/watcher/lib/src/directory_watcher/linux.dart | 6 +++--- pkgs/watcher/lib/src/utils.dart | 11 +++++++---- pkgs/watcher/pubspec.yaml | 3 ++- 5 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 pkgs/watcher/.analysis_options diff --git a/pkgs/watcher/.analysis_options b/pkgs/watcher/.analysis_options new file mode 100644 index 000000000..a10d4c5a0 --- /dev/null +++ b/pkgs/watcher/.analysis_options @@ -0,0 +1,2 @@ +analyzer: + strong-mode: true diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 30762a034..dc0b45b91 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.9.7+1 + +* Fix all strong-mode warnings. + # 0.9.7 * Fix a bug in `FileWatcher` where events could be added after watchers were diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index f327bb0d3..f33a55476 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -128,9 +128,9 @@ class _LinuxDirectoryWatcher /// The callback that's run when a batch of changes comes in. void _onBatch(List batch) { - var files = new Set(); - var dirs = new Set(); - var changed = new Set(); + var files = new Set(); + var dirs = new Set(); + var changed = new Set(); // inotify event batches are ordered by occurrence, so we treat them as a // log of what happened to a file. We only emit events based on the diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index d263f2f7c..6f3ff0215 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -6,6 +6,8 @@ import 'dart:async'; import 'dart:io'; import 'dart:collection'; +import 'package:async/async.dart'; + /// Returns `true` if [error] is a [FileSystemException] for a missing /// directory. bool isDirectoryNotFoundException(error) { @@ -29,17 +31,18 @@ Set unionAll(Iterable sets) => /// If [broadcast] is true, a broadcast stream is returned. This assumes that /// the stream returned by [future] will be a broadcast stream as well. /// [broadcast] defaults to false. -Stream futureStream(Future future, {bool broadcast: false}) { +Stream/**/ futureStream/**/(Future*/> future, + {bool broadcast: false}) { var subscription; - StreamController controller; + StreamController/**/ controller; - future = future.catchError((e, stackTrace) { + future = DelegatingFuture.typed(future.catchError((e, stackTrace) { // Since [controller] is synchronous, it's likely that emitting an error // will cause it to be cancelled before we call close. if (controller != null) controller.addError(e, stackTrace); if (controller != null) controller.close(); controller = null; - }); + })); onListen() { future.then((stream) { diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index c0152e032..ce644785a 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.8-dev +version: 0.9.7+1 author: Dart Team homepage: https://github.com/dart-lang/watcher description: > @@ -12,6 +12,7 @@ dependencies: collection: '^1.0.0' path: '>=0.9.0 <2.0.0' dev_dependencies: + async: '^1.8.0' benchmark_harness: '^1.0.4' scheduled_test: '^0.12.0' test: '^0.12.0' From a60efc6d5afeaf9adcbf1b5183e3ac8d6382f272 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 2 May 2016 14:57:15 -0700 Subject: [PATCH 090/201] Remove async from dev_dependencies. --- pkgs/watcher/pubspec.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index ce644785a..71a94dfad 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -8,11 +8,10 @@ description: > environment: sdk: '>=1.9.0 <2.0.0' dependencies: - async: '^1.2.0' + async: '^1.8.0' collection: '^1.0.0' path: '>=0.9.0 <2.0.0' dev_dependencies: - async: '^1.8.0' benchmark_harness: '^1.0.4' scheduled_test: '^0.12.0' test: '^0.12.0' From 264b5d6194b4b6923d55e1d7346221a88bd08164 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 3 May 2016 14:53:01 -0700 Subject: [PATCH 091/201] Narrow the constraint on async. --- pkgs/watcher/CHANGELOG.md | 5 +++++ pkgs/watcher/pubspec.yaml | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index dc0b45b91..aa6752e45 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.9.7+2 + +* Narrow the constraint on `async` to reflect the APIs this package is actually + using. + # 0.9.7+1 * Fix all strong-mode warnings. diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 71a94dfad..57e75e66c 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+1 +version: 0.9.7+2 author: Dart Team homepage: https://github.com/dart-lang/watcher description: > @@ -8,7 +8,7 @@ description: > environment: sdk: '>=1.9.0 <2.0.0' dependencies: - async: '^1.8.0' + async: '^1.10.0' collection: '^1.0.0' path: '>=0.9.0 <2.0.0' dev_dependencies: From 0b1902052632bf68b8e619f2304aa7643ef5b703 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 9 Aug 2016 17:59:00 -0700 Subject: [PATCH 092/201] Fix a crashing bug on Linux. When a directory was removed immediately after a subdirectory was created, the subdirectory event would be buffered. We'd then attempt to watch the subdirectory even though the stream group was already closed. Fixed b/30768513. --- pkgs/watcher/CHANGELOG.md | 4 ++++ .../lib/src/directory_watcher/linux.dart | 14 +++++++------- pkgs/watcher/pubspec.yaml | 2 +- pkgs/watcher/test/directory_watcher/shared.dart | 17 +++++++++++++++++ 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index aa6752e45..c3acaf53e 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.9.7+3 + +* Fix a crashing bug on Linux. + # 0.9.7+2 * Narrow the constraint on `async` to reflect the APIs this package is actually diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index f33a55476..df1365c76 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -66,18 +66,18 @@ class _LinuxDirectoryWatcher : _files = new PathSet(path) { _nativeEvents.add(new Directory(path).watch().transform( new StreamTransformer.fromHandlers(handleDone: (sink) { - // Once the root directory is deleted, no more new subdirectories will be - // watched. - _nativeEvents.close(); - sink.close(); + // Handle the done event here rather than in the call to [_listen] because + // [innerStream] won't close until we close the [StreamGroup]. However, if + // we close the [StreamGroup] here, we run the risk of new-directory + // events being fired after the group is closed, since batching delays + // those events. See b/30768513. + _onDone(); }))); // Batch the inotify changes together so that we can dedup events. var innerStream = _nativeEvents.stream .transform(new BatchedStreamTransformer()); - _listen(innerStream, _onBatch, - onError: _eventsController.addError, - onDone: _onDone); + _listen(innerStream, _onBatch, onError: _eventsController.addError); _listen(new Directory(path).list(recursive: true), (entity) { if (entity is Directory) { diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 57e75e66c..35b651745 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+2 +version: 0.9.8-dev author: Dart Team homepage: https://github.com/dart-lang/watcher description: > diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index c95ee39f4..148eee263 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -92,6 +92,23 @@ void sharedTests() { ]); }); + // Regression test for b/30768513. + test("doesn't crash when the directory is moved immediately after a subdir " + "is added", () { + writeFile("dir/a.txt"); + writeFile("dir/b.txt"); + + startWatcher(path: "dir"); + + createDir("dir/subdir"); + renameDir("dir", "moved_dir"); + createDir("dir"); + inAnyOrder([ + isRemoveEvent("dir/a.txt"), + isRemoveEvent("dir/b.txt") + ]); + }); + group("moves", () { test('notifies when a file is moved within the watched directory', () { writeFile("old.txt"); From 1f4c3612807614f9863f3d30aca0c5459c84ff92 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 10 Aug 2016 12:02:03 -0700 Subject: [PATCH 093/201] Release 0.9.7+2. --- pkgs/watcher/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 35b651745..57e75e66c 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.8-dev +version: 0.9.7+2 author: Dart Team homepage: https://github.com/dart-lang/watcher description: > From dd8f02975b57908b70674617b4e5f8dbcd28d86c Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 10 Aug 2016 12:09:22 -0700 Subject: [PATCH 094/201] Oops, that was supposed to be 0.9.7+3. --- pkgs/watcher/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 57e75e66c..bee354602 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+2 +version: 0.9.7+3 author: Dart Team homepage: https://github.com/dart-lang/watcher description: > From 0f1b3e35a43f54ef07cde3c077826f02a8381105 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 10 Aug 2016 16:00:52 -0700 Subject: [PATCH 095/201] Convert all files to Unix newlines. --- .../lib/src/directory_watcher/windows.dart | 816 +++++++++--------- pkgs/watcher/pubspec.yaml | 2 +- .../test/directory_watcher/windows_test.dart | 50 +- 3 files changed, 434 insertions(+), 434 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 67a274198..ec119f75e 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -1,408 +1,408 @@ -// Copyright (c) 2014, 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. -// TODO(rnystrom): Merge with mac_os version. - -import 'dart:async'; -import 'dart:collection'; -import 'dart:io'; - -import 'package:path/path.dart' as p; - -import '../constructable_file_system_event.dart'; -import '../directory_watcher.dart'; -import '../path_set.dart'; -import '../resubscribable.dart'; -import '../utils.dart'; -import '../watch_event.dart'; - -class WindowsDirectoryWatcher extends ResubscribableWatcher - implements DirectoryWatcher { - String get directory => path; - - WindowsDirectoryWatcher(String directory) - : super(directory, () => new _WindowsDirectoryWatcher(directory)); -} - -class _EventBatcher { - static const Duration _BATCH_DELAY = const Duration(milliseconds: 100); - final List events = []; - Timer timer; - - void addEvent(FileSystemEvent event, void callback()) { - events.add(event); - if (timer != null) { - timer.cancel(); - } - timer = new Timer(_BATCH_DELAY, callback); - } - - void cancelTimer() { - timer.cancel(); - } -} - -class _WindowsDirectoryWatcher - implements DirectoryWatcher, ManuallyClosedWatcher { - String get directory => path; - final String path; - - Stream get events => _eventsController.stream; - final _eventsController = new StreamController.broadcast(); - - bool get isReady => _readyCompleter.isCompleted; - - Future get ready => _readyCompleter.future; - final _readyCompleter = new Completer(); - - final Map _eventBatchers = - new HashMap(); - - /// The set of files that are known to exist recursively within the watched - /// directory. - /// - /// The state of files on the filesystem is compared against this to determine - /// the real change that occurred. This is also used to emit REMOVE events - /// when subdirectories are moved out of the watched directory. - final PathSet _files; - - /// The subscription to the stream returned by [Directory.watch]. - StreamSubscription _watchSubscription; - - /// The subscription to the stream returned by [Directory.watch] of the - /// parent directory to [directory]. This is needed to detect changes to - /// [directory], as they are not included on Windows. - StreamSubscription _parentWatchSubscription; - - /// The subscription to the [Directory.list] call for the initial listing of - /// the directory to determine its initial state. - StreamSubscription _initialListSubscription; - - /// The subscriptions to the [Directory.list] calls for listing the contents - /// of subdirectories that were moved into the watched directory. - final Set> _listSubscriptions - = new HashSet>(); - - _WindowsDirectoryWatcher(String path) - : path = path, - _files = new PathSet(path) { - // Before we're ready to emit events, wait for [_listDir] to complete. - _listDir().then((_) { - _startWatch(); - _startParentWatcher(); - _readyCompleter.complete(); - }); - } - - void close() { - if (_watchSubscription != null) _watchSubscription.cancel(); - if (_parentWatchSubscription != null) _parentWatchSubscription.cancel(); - if (_initialListSubscription != null) _initialListSubscription.cancel(); - for (var sub in _listSubscriptions) { - sub.cancel(); - } - _listSubscriptions.clear(); - for (var batcher in _eventBatchers.values) { - batcher.cancelTimer(); - } - _eventBatchers.clear(); - _watchSubscription = null; - _parentWatchSubscription = null; - _initialListSubscription = null; - _eventsController.close(); - } - - /// On Windows, if [directory] is deleted, we will not receive any event. - /// - /// Instead, we add a watcher on the parent folder (if any), that can notify - /// us about [path]. This also includes events such as moves. - void _startParentWatcher() { - var absoluteDir = p.absolute(path); - var parent = p.dirname(absoluteDir); - // Check if [path] is already the root directory. - if (FileSystemEntity.identicalSync(parent, path)) return; - var parentStream = new Directory(parent).watch(recursive: false); - _parentWatchSubscription = parentStream.listen((event) { - // Only look at events for 'directory'. - if (p.basename(event.path) != p.basename(absoluteDir)) return; - // Test if the directory is removed. FileSystemEntity.typeSync will - // return NOT_FOUND if it's unable to decide upon the type, including - // access denied issues, which may happen when the directory is deleted. - // FileSystemMoveEvent and FileSystemDeleteEvent events will always mean - // the directory is now gone. - if (event is FileSystemMoveEvent || - event is FileSystemDeleteEvent || - (FileSystemEntity.typeSync(path) == - FileSystemEntityType.NOT_FOUND)) { - for (var path in _files.paths) { - _emitEvent(ChangeType.REMOVE, path); - } - _files.clear(); - close(); - } - }, onError: (error) { - // Ignore errors, simply close the stream. The user listens on - // [directory], and while it can fail to listen on the parent, we may - // still be able to listen on the path requested. - _parentWatchSubscription.cancel(); - _parentWatchSubscription = null; - }); - } - - void _onEvent(FileSystemEvent event) { - assert(isReady); - final batcher = _eventBatchers.putIfAbsent( - event.path, () => new _EventBatcher()); - batcher.addEvent(event, () { - _eventBatchers.remove(event.path); - _onBatch(batcher.events); - }); - } - - /// The callback that's run when [Directory.watch] emits a batch of events. - void _onBatch(List batch) { - _sortEvents(batch).forEach((path, eventSet) { - - var canonicalEvent = _canonicalEvent(eventSet); - var events = canonicalEvent == null ? - _eventsBasedOnFileSystem(path) : [canonicalEvent]; - - for (var event in events) { - if (event is FileSystemCreateEvent) { - if (!event.isDirectory) { - if (_files.contains(path)) continue; - - _emitEvent(ChangeType.ADD, path); - _files.add(path); - continue; - } - - if (_files.containsDir(path)) continue; - - var stream = new Directory(path).list(recursive: true); - StreamSubscription subscription; - subscription = stream.listen((entity) { - if (entity is Directory) return; - if (_files.contains(path)) return; - - _emitEvent(ChangeType.ADD, entity.path); - _files.add(entity.path); - }, onDone: () { - _listSubscriptions.remove(subscription); - }, onError: (e, stackTrace) { - _listSubscriptions.remove(subscription); - _emitError(e, stackTrace); - }, cancelOnError: true); - _listSubscriptions.add(subscription); - } else if (event is FileSystemModifyEvent) { - if (!event.isDirectory) { - _emitEvent(ChangeType.MODIFY, path); - } - } else { - assert(event is FileSystemDeleteEvent); - for (var removedPath in _files.remove(path)) { - _emitEvent(ChangeType.REMOVE, removedPath); - } - } - } - }); - } - - /// Sort all the events in a batch into sets based on their path. - /// - /// A single input event may result in multiple events in the returned map; - /// for example, a MOVE event becomes a DELETE event for the source and a - /// CREATE event for the destination. - /// - /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it - /// contain any events relating to [path]. - Map> _sortEvents(List batch) { - var eventsForPaths = {}; - - // Events within directories that already have events are superfluous; the - // directory's full contents will be examined anyway, so we ignore such - // events. Emitting them could cause useless or out-of-order events. - var directories = unionAll(batch.map((event) { - if (!event.isDirectory) return new Set(); - if (event is FileSystemMoveEvent) { - return new Set.from([event.path, event.destination]); - } - return new Set.from([event.path]); - })); - - isInModifiedDirectory(path) => - directories.any((dir) => path != dir && path.startsWith(dir)); - - addEvent(path, event) { - if (isInModifiedDirectory(path)) return; - var set = eventsForPaths.putIfAbsent(path, () => new Set()); - set.add(event); - } - - for (var event in batch) { - if (event is FileSystemMoveEvent) { - addEvent(event.destination, event); - } - addEvent(event.path, event); - } - - return eventsForPaths; - } - - /// Returns the canonical event from a batch of events on the same path, if - /// one exists. - /// - /// If [batch] doesn't contain any contradictory events (e.g. DELETE and - /// CREATE, or events with different values for [isDirectory]), this returns a - /// single event that describes what happened to the path in question. - /// - /// If [batch] does contain contradictory events, this returns `null` to - /// indicate that the state of the path on the filesystem should be checked to - /// determine what occurred. - FileSystemEvent _canonicalEvent(Set batch) { - // An empty batch indicates that we've learned earlier that the batch is - // contradictory (e.g. because of a move). - if (batch.isEmpty) return null; - - var type = batch.first.type; - var isDir = batch.first.isDirectory; - - for (var event in batch.skip(1)) { - // If one event reports that the file is a directory and another event - // doesn't, that's a contradiction. - if (isDir != event.isDirectory) return null; - - // Modify events don't contradict either CREATE or REMOVE events. We can - // safely assume the file was modified after a CREATE or before the - // REMOVE; otherwise there will also be a REMOVE or CREATE event - // (respectively) that will be contradictory. - if (event is FileSystemModifyEvent) continue; - assert(event is FileSystemCreateEvent || - event is FileSystemDeleteEvent || - event is FileSystemMoveEvent); - - // If we previously thought this was a MODIFY, we now consider it to be a - // CREATE or REMOVE event. This is safe for the same reason as above. - if (type == FileSystemEvent.MODIFY) { - type = event.type; - continue; - } - - // A CREATE event contradicts a REMOVE event and vice versa. - assert(type == FileSystemEvent.CREATE || - type == FileSystemEvent.DELETE || - type == FileSystemEvent.MOVE); - if (type != event.type) return null; - } - - switch (type) { - case FileSystemEvent.CREATE: - return new ConstructableFileSystemCreateEvent(batch.first.path, isDir); - case FileSystemEvent.DELETE: - return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir); - case FileSystemEvent.MODIFY: - return new ConstructableFileSystemModifyEvent( - batch.first.path, isDir, false); - case FileSystemEvent.MOVE: - return null; - default: throw 'unreachable'; - } - } - - /// Returns one or more events that describe the change between the last known - /// state of [path] and its current state on the filesystem. - /// - /// This returns a list whose order should be reflected in the events emitted - /// to the user, unlike the batched events from [Directory.watch]. The - /// returned list may be empty, indicating that no changes occurred to [path] - /// (probably indicating that it was created and then immediately deleted). - List _eventsBasedOnFileSystem(String path) { - var fileExisted = _files.contains(path); - var dirExisted = _files.containsDir(path); - var fileExists = new File(path).existsSync(); - var dirExists = new Directory(path).existsSync(); - - var events = []; - if (fileExisted) { - if (fileExists) { - events.add(new ConstructableFileSystemModifyEvent(path, false, false)); - } else { - events.add(new ConstructableFileSystemDeleteEvent(path, false)); - } - } else if (dirExisted) { - if (dirExists) { - // If we got contradictory events for a directory that used to exist and - // still exists, we need to rescan the whole thing in case it was - // replaced with a different directory. - events.add(new ConstructableFileSystemDeleteEvent(path, true)); - events.add(new ConstructableFileSystemCreateEvent(path, true)); - } else { - events.add(new ConstructableFileSystemDeleteEvent(path, true)); - } - } - - if (!fileExisted && fileExists) { - events.add(new ConstructableFileSystemCreateEvent(path, false)); - } else if (!dirExisted && dirExists) { - events.add(new ConstructableFileSystemCreateEvent(path, true)); - } - - return events; - } - - /// The callback that's run when the [Directory.watch] stream is closed. - /// Note that this is unlikely to happen on Windows, unless the system itself - /// closes the handle. - void _onDone() { - _watchSubscription = null; - - // Emit remove events for any remaining files. - for (var file in _files.paths) { - _emitEvent(ChangeType.REMOVE, file); - } - _files.clear(); - close(); - } - - /// Start or restart the underlying [Directory.watch] stream. - void _startWatch() { - // Batch the events together so that we can dedup events. - var innerStream = new Directory(path).watch(recursive: true); - _watchSubscription = innerStream.listen(_onEvent, - onError: _eventsController.addError, - onDone: _onDone); - } - - /// Starts or restarts listing the watched directory to get an initial picture - /// of its state. - Future _listDir() { - assert(!isReady); - if (_initialListSubscription != null) _initialListSubscription.cancel(); - - _files.clear(); - var completer = new Completer(); - var stream = new Directory(path).list(recursive: true); - void handleEntity(entity) { - if (entity is! Directory) _files.add(entity.path); - } - _initialListSubscription = stream.listen( - handleEntity, - onError: _emitError, - onDone: completer.complete, - cancelOnError: true); - return completer.future; - } - - /// Emit an event with the given [type] and [path]. - void _emitEvent(ChangeType type, String path) { - if (!isReady) return; - - _eventsController.add(new WatchEvent(type, path)); - } - - /// Emit an error, then close the watcher. - void _emitError(error, StackTrace stackTrace) { - _eventsController.addError(error, stackTrace); - close(); - } -} +// Copyright (c) 2014, 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. +// TODO(rnystrom): Merge with mac_os version. + +import 'dart:async'; +import 'dart:collection'; +import 'dart:io'; + +import 'package:path/path.dart' as p; + +import '../constructable_file_system_event.dart'; +import '../directory_watcher.dart'; +import '../path_set.dart'; +import '../resubscribable.dart'; +import '../utils.dart'; +import '../watch_event.dart'; + +class WindowsDirectoryWatcher extends ResubscribableWatcher + implements DirectoryWatcher { + String get directory => path; + + WindowsDirectoryWatcher(String directory) + : super(directory, () => new _WindowsDirectoryWatcher(directory)); +} + +class _EventBatcher { + static const Duration _BATCH_DELAY = const Duration(milliseconds: 100); + final List events = []; + Timer timer; + + void addEvent(FileSystemEvent event, void callback()) { + events.add(event); + if (timer != null) { + timer.cancel(); + } + timer = new Timer(_BATCH_DELAY, callback); + } + + void cancelTimer() { + timer.cancel(); + } +} + +class _WindowsDirectoryWatcher + implements DirectoryWatcher, ManuallyClosedWatcher { + String get directory => path; + final String path; + + Stream get events => _eventsController.stream; + final _eventsController = new StreamController.broadcast(); + + bool get isReady => _readyCompleter.isCompleted; + + Future get ready => _readyCompleter.future; + final _readyCompleter = new Completer(); + + final Map _eventBatchers = + new HashMap(); + + /// The set of files that are known to exist recursively within the watched + /// directory. + /// + /// The state of files on the filesystem is compared against this to determine + /// the real change that occurred. This is also used to emit REMOVE events + /// when subdirectories are moved out of the watched directory. + final PathSet _files; + + /// The subscription to the stream returned by [Directory.watch]. + StreamSubscription _watchSubscription; + + /// The subscription to the stream returned by [Directory.watch] of the + /// parent directory to [directory]. This is needed to detect changes to + /// [directory], as they are not included on Windows. + StreamSubscription _parentWatchSubscription; + + /// The subscription to the [Directory.list] call for the initial listing of + /// the directory to determine its initial state. + StreamSubscription _initialListSubscription; + + /// The subscriptions to the [Directory.list] calls for listing the contents + /// of subdirectories that were moved into the watched directory. + final Set> _listSubscriptions + = new HashSet>(); + + _WindowsDirectoryWatcher(String path) + : path = path, + _files = new PathSet(path) { + // Before we're ready to emit events, wait for [_listDir] to complete. + _listDir().then((_) { + _startWatch(); + _startParentWatcher(); + _readyCompleter.complete(); + }); + } + + void close() { + if (_watchSubscription != null) _watchSubscription.cancel(); + if (_parentWatchSubscription != null) _parentWatchSubscription.cancel(); + if (_initialListSubscription != null) _initialListSubscription.cancel(); + for (var sub in _listSubscriptions) { + sub.cancel(); + } + _listSubscriptions.clear(); + for (var batcher in _eventBatchers.values) { + batcher.cancelTimer(); + } + _eventBatchers.clear(); + _watchSubscription = null; + _parentWatchSubscription = null; + _initialListSubscription = null; + _eventsController.close(); + } + + /// On Windows, if [directory] is deleted, we will not receive any event. + /// + /// Instead, we add a watcher on the parent folder (if any), that can notify + /// us about [path]. This also includes events such as moves. + void _startParentWatcher() { + var absoluteDir = p.absolute(path); + var parent = p.dirname(absoluteDir); + // Check if [path] is already the root directory. + if (FileSystemEntity.identicalSync(parent, path)) return; + var parentStream = new Directory(parent).watch(recursive: false); + _parentWatchSubscription = parentStream.listen((event) { + // Only look at events for 'directory'. + if (p.basename(event.path) != p.basename(absoluteDir)) return; + // Test if the directory is removed. FileSystemEntity.typeSync will + // return NOT_FOUND if it's unable to decide upon the type, including + // access denied issues, which may happen when the directory is deleted. + // FileSystemMoveEvent and FileSystemDeleteEvent events will always mean + // the directory is now gone. + if (event is FileSystemMoveEvent || + event is FileSystemDeleteEvent || + (FileSystemEntity.typeSync(path) == + FileSystemEntityType.NOT_FOUND)) { + for (var path in _files.paths) { + _emitEvent(ChangeType.REMOVE, path); + } + _files.clear(); + close(); + } + }, onError: (error) { + // Ignore errors, simply close the stream. The user listens on + // [directory], and while it can fail to listen on the parent, we may + // still be able to listen on the path requested. + _parentWatchSubscription.cancel(); + _parentWatchSubscription = null; + }); + } + + void _onEvent(FileSystemEvent event) { + assert(isReady); + final batcher = _eventBatchers.putIfAbsent( + event.path, () => new _EventBatcher()); + batcher.addEvent(event, () { + _eventBatchers.remove(event.path); + _onBatch(batcher.events); + }); + } + + /// The callback that's run when [Directory.watch] emits a batch of events. + void _onBatch(List batch) { + _sortEvents(batch).forEach((path, eventSet) { + + var canonicalEvent = _canonicalEvent(eventSet); + var events = canonicalEvent == null ? + _eventsBasedOnFileSystem(path) : [canonicalEvent]; + + for (var event in events) { + if (event is FileSystemCreateEvent) { + if (!event.isDirectory) { + if (_files.contains(path)) continue; + + _emitEvent(ChangeType.ADD, path); + _files.add(path); + continue; + } + + if (_files.containsDir(path)) continue; + + var stream = new Directory(path).list(recursive: true); + StreamSubscription subscription; + subscription = stream.listen((entity) { + if (entity is Directory) return; + if (_files.contains(path)) return; + + _emitEvent(ChangeType.ADD, entity.path); + _files.add(entity.path); + }, onDone: () { + _listSubscriptions.remove(subscription); + }, onError: (e, stackTrace) { + _listSubscriptions.remove(subscription); + _emitError(e, stackTrace); + }, cancelOnError: true); + _listSubscriptions.add(subscription); + } else if (event is FileSystemModifyEvent) { + if (!event.isDirectory) { + _emitEvent(ChangeType.MODIFY, path); + } + } else { + assert(event is FileSystemDeleteEvent); + for (var removedPath in _files.remove(path)) { + _emitEvent(ChangeType.REMOVE, removedPath); + } + } + } + }); + } + + /// Sort all the events in a batch into sets based on their path. + /// + /// A single input event may result in multiple events in the returned map; + /// for example, a MOVE event becomes a DELETE event for the source and a + /// CREATE event for the destination. + /// + /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it + /// contain any events relating to [path]. + Map> _sortEvents(List batch) { + var eventsForPaths = {}; + + // Events within directories that already have events are superfluous; the + // directory's full contents will be examined anyway, so we ignore such + // events. Emitting them could cause useless or out-of-order events. + var directories = unionAll(batch.map((event) { + if (!event.isDirectory) return new Set(); + if (event is FileSystemMoveEvent) { + return new Set.from([event.path, event.destination]); + } + return new Set.from([event.path]); + })); + + isInModifiedDirectory(path) => + directories.any((dir) => path != dir && path.startsWith(dir)); + + addEvent(path, event) { + if (isInModifiedDirectory(path)) return; + var set = eventsForPaths.putIfAbsent(path, () => new Set()); + set.add(event); + } + + for (var event in batch) { + if (event is FileSystemMoveEvent) { + addEvent(event.destination, event); + } + addEvent(event.path, event); + } + + return eventsForPaths; + } + + /// Returns the canonical event from a batch of events on the same path, if + /// one exists. + /// + /// If [batch] doesn't contain any contradictory events (e.g. DELETE and + /// CREATE, or events with different values for [isDirectory]), this returns a + /// single event that describes what happened to the path in question. + /// + /// If [batch] does contain contradictory events, this returns `null` to + /// indicate that the state of the path on the filesystem should be checked to + /// determine what occurred. + FileSystemEvent _canonicalEvent(Set batch) { + // An empty batch indicates that we've learned earlier that the batch is + // contradictory (e.g. because of a move). + if (batch.isEmpty) return null; + + var type = batch.first.type; + var isDir = batch.first.isDirectory; + + for (var event in batch.skip(1)) { + // If one event reports that the file is a directory and another event + // doesn't, that's a contradiction. + if (isDir != event.isDirectory) return null; + + // Modify events don't contradict either CREATE or REMOVE events. We can + // safely assume the file was modified after a CREATE or before the + // REMOVE; otherwise there will also be a REMOVE or CREATE event + // (respectively) that will be contradictory. + if (event is FileSystemModifyEvent) continue; + assert(event is FileSystemCreateEvent || + event is FileSystemDeleteEvent || + event is FileSystemMoveEvent); + + // If we previously thought this was a MODIFY, we now consider it to be a + // CREATE or REMOVE event. This is safe for the same reason as above. + if (type == FileSystemEvent.MODIFY) { + type = event.type; + continue; + } + + // A CREATE event contradicts a REMOVE event and vice versa. + assert(type == FileSystemEvent.CREATE || + type == FileSystemEvent.DELETE || + type == FileSystemEvent.MOVE); + if (type != event.type) return null; + } + + switch (type) { + case FileSystemEvent.CREATE: + return new ConstructableFileSystemCreateEvent(batch.first.path, isDir); + case FileSystemEvent.DELETE: + return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir); + case FileSystemEvent.MODIFY: + return new ConstructableFileSystemModifyEvent( + batch.first.path, isDir, false); + case FileSystemEvent.MOVE: + return null; + default: throw 'unreachable'; + } + } + + /// Returns one or more events that describe the change between the last known + /// state of [path] and its current state on the filesystem. + /// + /// This returns a list whose order should be reflected in the events emitted + /// to the user, unlike the batched events from [Directory.watch]. The + /// returned list may be empty, indicating that no changes occurred to [path] + /// (probably indicating that it was created and then immediately deleted). + List _eventsBasedOnFileSystem(String path) { + var fileExisted = _files.contains(path); + var dirExisted = _files.containsDir(path); + var fileExists = new File(path).existsSync(); + var dirExists = new Directory(path).existsSync(); + + var events = []; + if (fileExisted) { + if (fileExists) { + events.add(new ConstructableFileSystemModifyEvent(path, false, false)); + } else { + events.add(new ConstructableFileSystemDeleteEvent(path, false)); + } + } else if (dirExisted) { + if (dirExists) { + // If we got contradictory events for a directory that used to exist and + // still exists, we need to rescan the whole thing in case it was + // replaced with a different directory. + events.add(new ConstructableFileSystemDeleteEvent(path, true)); + events.add(new ConstructableFileSystemCreateEvent(path, true)); + } else { + events.add(new ConstructableFileSystemDeleteEvent(path, true)); + } + } + + if (!fileExisted && fileExists) { + events.add(new ConstructableFileSystemCreateEvent(path, false)); + } else if (!dirExisted && dirExists) { + events.add(new ConstructableFileSystemCreateEvent(path, true)); + } + + return events; + } + + /// The callback that's run when the [Directory.watch] stream is closed. + /// Note that this is unlikely to happen on Windows, unless the system itself + /// closes the handle. + void _onDone() { + _watchSubscription = null; + + // Emit remove events for any remaining files. + for (var file in _files.paths) { + _emitEvent(ChangeType.REMOVE, file); + } + _files.clear(); + close(); + } + + /// Start or restart the underlying [Directory.watch] stream. + void _startWatch() { + // Batch the events together so that we can dedup events. + var innerStream = new Directory(path).watch(recursive: true); + _watchSubscription = innerStream.listen(_onEvent, + onError: _eventsController.addError, + onDone: _onDone); + } + + /// Starts or restarts listing the watched directory to get an initial picture + /// of its state. + Future _listDir() { + assert(!isReady); + if (_initialListSubscription != null) _initialListSubscription.cancel(); + + _files.clear(); + var completer = new Completer(); + var stream = new Directory(path).list(recursive: true); + void handleEntity(entity) { + if (entity is! Directory) _files.add(entity.path); + } + _initialListSubscription = stream.listen( + handleEntity, + onError: _emitError, + onDone: completer.complete, + cancelOnError: true); + return completer.future; + } + + /// Emit an event with the given [type] and [path]. + void _emitEvent(ChangeType type, String path) { + if (!isReady) return; + + _eventsController.add(new WatchEvent(type, path)); + } + + /// Emit an error, then close the watcher. + void _emitError(error, StackTrace stackTrace) { + _eventsController.addError(error, stackTrace); + close(); + } +} diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index bee354602..35b651745 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+3 +version: 0.9.8-dev author: Dart Team homepage: https://github.com/dart-lang/watcher description: > diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 4c77ced50..55e40a937 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -1,25 +1,25 @@ -// Copyright (c) 2014, 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. - -@TestOn('windows') - -import 'package:scheduled_test/scheduled_test.dart'; -import 'package:watcher/src/directory_watcher/windows.dart'; -import 'package:watcher/watcher.dart'; - -import 'shared.dart'; -import '../utils.dart'; - -void main() { - watcherFactory = (dir) => new WindowsDirectoryWatcher(dir); - - setUp(createSandbox); - - sharedTests(); - - test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () { - expect(new DirectoryWatcher('.'), - new isInstanceOf()); - }); -} +// Copyright (c) 2014, 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. + +@TestOn('windows') + +import 'package:scheduled_test/scheduled_test.dart'; +import 'package:watcher/src/directory_watcher/windows.dart'; +import 'package:watcher/watcher.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +void main() { + watcherFactory = (dir) => new WindowsDirectoryWatcher(dir); + + setUp(createSandbox); + + sharedTests(); + + test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () { + expect(new DirectoryWatcher('.'), + new isInstanceOf()); + }); +} From dd4154e1f42f9094956d93fcc3b7c3ba9bcd3e12 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 10 Aug 2016 16:02:13 -0700 Subject: [PATCH 096/201] Remove an unused dependency on collection. --- pkgs/watcher/pubspec.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index bee354602..90f96f5a7 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+3 +version: 0.9.8-dev author: Dart Team homepage: https://github.com/dart-lang/watcher description: > @@ -9,7 +9,6 @@ environment: sdk: '>=1.9.0 <2.0.0' dependencies: async: '^1.10.0' - collection: '^1.0.0' path: '>=0.9.0 <2.0.0' dev_dependencies: benchmark_harness: '^1.0.4' From 5ecc1d63c2938d4fdd8dd5f852b854f5996d142f Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 21 Sep 2017 15:50:46 -0700 Subject: [PATCH 097/201] Support async 2.0.0. (dart-lang/watcher#37) --- pkgs/watcher/CHANGELOG.md | 4 ++++ pkgs/watcher/pubspec.yaml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index c3acaf53e..f07ceaf0a 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.9.7+4 + +* Declare support for `async` 2.0.0. + # 0.9.7+3 * Fix a crashing bug on Linux. diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 90f96f5a7..15c51c6ae 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.8-dev +version: 0.9.7+4 author: Dart Team homepage: https://github.com/dart-lang/watcher description: > @@ -8,7 +8,7 @@ description: > environment: sdk: '>=1.9.0 <2.0.0' dependencies: - async: '^1.10.0' + async: '>=1.10.0 <3.0.0' path: '>=0.9.0 <2.0.0' dev_dependencies: benchmark_harness: '^1.0.4' From 47bec762667f343bbd260215cde5233204ada2b6 Mon Sep 17 00:00:00 2001 From: Leaf Petersen Date: Tue, 17 Oct 2017 14:24:33 -0700 Subject: [PATCH 098/201] Modify type on _onBatch to reflect call argument types --- pkgs/watcher/CHANGELOG.md | 5 +++++ pkgs/watcher/lib/src/directory_watcher/linux.dart | 3 ++- pkgs/watcher/pubspec.yaml | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index f07ceaf0a..89da684fa 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.9.7+5 + +* Change the type on a local function (_onBatch) to reflect the fact that its + caller does not statically guarantee its contract. + # 0.9.7+4 * Declare support for `async` 2.0.0. diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index df1365c76..c291aad2d 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -127,7 +127,8 @@ class _LinuxDirectoryWatcher } /// The callback that's run when a batch of changes comes in. - void _onBatch(List batch) { + void _onBatch(Object data) { + var batch = data as List; var files = new Set(); var dirs = new Set(); var changed = new Set(); diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 15c51c6ae..41fbd8b0c 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+4 +version: 0.9.7+5 author: Dart Team homepage: https://github.com/dart-lang/watcher description: > From c25297863a55aa1a1493654f92e853b0910bc415 Mon Sep 17 00:00:00 2001 From: Leaf Petersen Date: Tue, 17 Oct 2017 14:34:36 -0700 Subject: [PATCH 099/201] Update changelog per comments --- pkgs/watcher/CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 89da684fa..b4ca0bac9 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,7 +1,6 @@ # 0.9.7+5 -* Change the type on a local function (_onBatch) to reflect the fact that its - caller does not statically guarantee its contract. +* Fix an analysis warning. # 0.9.7+4 From b4fbbfd5968240deed75249a7f517490f8657606 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 19 Dec 2017 15:32:58 -0800 Subject: [PATCH 100/201] Use suggested analysis_options file name --- pkgs/watcher/{.analysis_options => analysis_options.yaml} | 0 pkgs/watcher/pubspec.yaml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename pkgs/watcher/{.analysis_options => analysis_options.yaml} (100%) diff --git a/pkgs/watcher/.analysis_options b/pkgs/watcher/analysis_options.yaml similarity index 100% rename from pkgs/watcher/.analysis_options rename to pkgs/watcher/analysis_options.yaml diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 41fbd8b0c..987e7b72a 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+5 +version: 0.9.7+6.dev author: Dart Team homepage: https://github.com/dart-lang/watcher description: > From e3b509a6d761f973b09f10a972e84223a7d25def Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 19 Dec 2017 15:34:43 -0800 Subject: [PATCH 101/201] migrate to the new function syntax --- pkgs/watcher/lib/src/utils.dart | 4 ++-- pkgs/watcher/pubspec.yaml | 2 +- pkgs/watcher/test/utils.dart | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index 6f3ff0215..022c8c1c1 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -31,10 +31,10 @@ Set unionAll(Iterable sets) => /// If [broadcast] is true, a broadcast stream is returned. This assumes that /// the stream returned by [future] will be a broadcast stream as well. /// [broadcast] defaults to false. -Stream/**/ futureStream/**/(Future*/> future, +Stream futureStream(Future> future, {bool broadcast: false}) { var subscription; - StreamController/**/ controller; + StreamController controller; future = DelegatingFuture.typed(future.catchError((e, stackTrace) { // Since [controller] is synchronous, it's likely that emitting an error diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 987e7b72a..6bdb39a46 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -6,7 +6,7 @@ description: > A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. environment: - sdk: '>=1.9.0 <2.0.0' + sdk: '>=1.21.0 <2.0.0' dependencies: async: '>=1.10.0 <3.0.0' path: '>=0.9.0 <2.0.0' diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index e3c2a1b4d..e91ed1578 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -344,10 +344,10 @@ void deleteDir(String path) { /// Returns a set of all values returns by [callback]. /// /// [limit] defaults to 3. -Set/**/ withPermutations/**/(/*=S*/ callback(int i, int j, int k), +Set withPermutations(S callback(int i, int j, int k), {int limit}) { if (limit == null) limit = 3; - var results = new Set/**/(); + var results = new Set(); for (var i = 0; i < limit; i++) { for (var j = 0; j < limit; j++) { for (var k = 0; k < limit; k++) { From dd1ecce0b0b8149992aa633eb6c46c147a56365e Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 19 Dec 2017 15:36:13 -0800 Subject: [PATCH 102/201] Add travis support --- pkgs/watcher/.travis.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 pkgs/watcher/.travis.yml diff --git a/pkgs/watcher/.travis.yml b/pkgs/watcher/.travis.yml new file mode 100644 index 000000000..deb901b6e --- /dev/null +++ b/pkgs/watcher/.travis.yml @@ -0,0 +1,25 @@ +language: dart +dart: + - dev + - stable + +dart_task: + - test + +matrix: + include: + - dart: dev + dart_task: dartfmt + # Only care about being analyzer clean for dev and stable + - dart: dev + dart_task: dartanalyzer + - dart: stable + dart_task: dartanalyzer + +# Only building master means that we don't run two builds for each pull request. +branches: + only: [master, 0-9-7] + +cache: + directories: + - $HOME/.pub-cache From ba07d4a61634229c4069143644042f6814b8d7f7 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 19 Dec 2017 15:36:57 -0800 Subject: [PATCH 103/201] dartfmt --- pkgs/watcher/lib/src/async_queue.dart | 3 +- .../src/constructable_file_system_event.dart | 8 +- .../lib/src/directory_watcher/linux.dart | 12 +-- .../lib/src/directory_watcher/mac_os.dart | 42 +++++----- .../lib/src/directory_watcher/polling.dart | 10 +-- .../lib/src/directory_watcher/windows.dart | 39 ++++----- pkgs/watcher/lib/src/file_watcher/native.dart | 3 +- .../watcher/lib/src/file_watcher/polling.dart | 6 +- pkgs/watcher/lib/src/resubscribable.dart | 32 +++---- pkgs/watcher/lib/src/utils.dart | 9 +- .../test/directory_watcher/linux_test.dart | 20 ++--- .../test/directory_watcher/mac_os_test.dart | 23 ++--- .../test/directory_watcher/shared.dart | 84 +++++++------------ .../test/directory_watcher/windows_test.dart | 4 +- pkgs/watcher/test/file_watcher/shared.dart | 3 +- pkgs/watcher/test/path_set_test.dart | 56 +++++++------ pkgs/watcher/test/utils.dart | 41 ++++----- 17 files changed, 190 insertions(+), 205 deletions(-) diff --git a/pkgs/watcher/lib/src/async_queue.dart b/pkgs/watcher/lib/src/async_queue.dart index adf667101..1895c804e 100644 --- a/pkgs/watcher/lib/src/async_queue.dart +++ b/pkgs/watcher/lib/src/async_queue.dart @@ -34,8 +34,7 @@ class AsyncQueue { /// Used to avoid top-leveling asynchronous errors. final Function _errorHandler; - AsyncQueue(this._processor, {Function onError}) - : _errorHandler = onError; + AsyncQueue(this._processor, {Function onError}) : _errorHandler = onError; /// Enqueues [item] to be processed and starts asynchronously processing it /// if a process isn't already running. diff --git a/pkgs/watcher/lib/src/constructable_file_system_event.dart b/pkgs/watcher/lib/src/constructable_file_system_event.dart index 63b51c192..a0f153e53 100644 --- a/pkgs/watcher/lib/src/constructable_file_system_event.dart +++ b/pkgs/watcher/lib/src/constructable_file_system_event.dart @@ -37,8 +37,8 @@ class ConstructableFileSystemModifyEvent extends _ConstructableFileSystemEvent final bool contentChanged; final type = FileSystemEvent.MODIFY; - ConstructableFileSystemModifyEvent(String path, bool isDirectory, - this.contentChanged) + ConstructableFileSystemModifyEvent( + String path, bool isDirectory, this.contentChanged) : super(path, isDirectory); String toString() => @@ -50,8 +50,8 @@ class ConstructableFileSystemMoveEvent extends _ConstructableFileSystemEvent final String destination; final type = FileSystemEvent.MOVE; - ConstructableFileSystemMoveEvent(String path, bool isDirectory, - this.destination) + ConstructableFileSystemMoveEvent( + String path, bool isDirectory, this.destination) : super(path, isDirectory); String toString() => "FileSystemMoveEvent('$path', '$destination')"; diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index c291aad2d..717e4209c 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -62,10 +62,10 @@ class _LinuxDirectoryWatcher /// watcher is closed. final _subscriptions = new Set(); - _LinuxDirectoryWatcher(String path) - : _files = new PathSet(path) { - _nativeEvents.add(new Directory(path).watch().transform( - new StreamTransformer.fromHandlers(handleDone: (sink) { + _LinuxDirectoryWatcher(String path) : _files = new PathSet(path) { + _nativeEvents.add(new Directory(path) + .watch() + .transform(new StreamTransformer.fromHandlers(handleDone: (sink) { // Handle the done event here rather than in the call to [_listen] because // [innerStream] won't close until we close the [StreamGroup]. However, if // we close the [StreamGroup] here, we run the risk of new-directory @@ -251,8 +251,8 @@ class _LinuxDirectoryWatcher /// Like [Stream.listen], but automatically adds the subscription to /// [_subscriptions] so that it can be canceled when [close] is called. - void _listen(Stream stream, void onData(event), {Function onError, - void onDone(), bool cancelOnError}) { + void _listen(Stream stream, void onData(event), + {Function onError, void onDone(), bool cancelOnError}) { var subscription; subscription = stream.listen(onData, onError: onError, onDone: () { _subscriptions.remove(subscription); diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 8a17e2e79..759765f72 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -81,10 +81,8 @@ class _MacOSDirectoryWatcher // // If we do receive a batch of events, [_onBatch] will ensure that these // futures don't fire and that the directory is re-listed. - Future.wait([ - _listDir(), - _waitForBogusEvents() - ]).then((_) => _readyCompleter.complete()); + Future.wait([_listDir(), _waitForBogusEvents()]).then( + (_) => _readyCompleter.complete()); } void close() { @@ -116,8 +114,9 @@ class _MacOSDirectoryWatcher _sortEvents(batch).forEach((path, eventSet) { var canonicalEvent = _canonicalEvent(eventSet); - var events = canonicalEvent == null ? - _eventsBasedOnFileSystem(path) : [canonicalEvent]; + var events = canonicalEvent == null + ? _eventsBasedOnFileSystem(path) + : [canonicalEvent]; for (var event in events) { if (event is FileSystemCreateEvent) { @@ -126,9 +125,8 @@ class _MacOSDirectoryWatcher // This can happen if a file is copied on top of an existing one. // We'll see an ADD event for the latter file when from the user's // perspective, the file's contents just changed. - var type = _files.contains(path) - ? ChangeType.MODIFY - : ChangeType.ADD; + var type = + _files.contains(path) ? ChangeType.MODIFY : ChangeType.ADD; _emitEvent(type, path); _files.add(path); @@ -138,8 +136,8 @@ class _MacOSDirectoryWatcher if (_files.containsDir(path)) continue; StreamSubscription subscription; - subscription = new Directory(path).list(recursive: true) - .listen((entity) { + subscription = + new Directory(path).list(recursive: true).listen((entity) { if (entity is Directory) return; if (_files.contains(path)) return; @@ -259,7 +257,8 @@ class _MacOSDirectoryWatcher // from FSEvents reporting an add that happened prior to the watch // beginning. If we also received a MODIFY event, we want to report that, // but not the CREATE. - if (type == FileSystemEvent.CREATE && hadModifyEvent && + if (type == FileSystemEvent.CREATE && + hadModifyEvent && _files.contains(batch.first.path)) { type = FileSystemEvent.MODIFY; } @@ -277,7 +276,8 @@ class _MacOSDirectoryWatcher case FileSystemEvent.MODIFY: return new ConstructableFileSystemModifyEvent( batch.first.path, isDir, false); - default: throw 'unreachable'; + default: + throw 'unreachable'; } } @@ -347,11 +347,11 @@ class _MacOSDirectoryWatcher /// Start or restart the underlying [Directory.watch] stream. void _startWatch() { // Batch the FSEvent changes together so that we can dedup events. - var innerStream = new Directory(path).watch(recursive: true) + var innerStream = new Directory(path) + .watch(recursive: true) .transform(new BatchedStreamTransformer()); _watchSubscription = innerStream.listen(_onBatch, - onError: _eventsController.addError, - onDone: _onDone); + onError: _eventsController.addError, onDone: _onDone); } /// Starts or restarts listing the watched directory to get an initial picture @@ -365,10 +365,7 @@ class _MacOSDirectoryWatcher var stream = new Directory(path).list(recursive: true); _initialListSubscription = stream.listen((entity) { if (entity is! Directory) _files.add(entity.path); - }, - onError: _emitError, - onDone: completer.complete, - cancelOnError: true); + }, onError: _emitError, onDone: completer.complete, cancelOnError: true); return completer.future; } @@ -379,9 +376,8 @@ class _MacOSDirectoryWatcher /// bogus events will be signaled in that time frame. Future _waitForBogusEvents() { var completer = new Completer(); - _bogusEventTimer = new Timer( - new Duration(milliseconds: 200), - completer.complete); + _bogusEventTimer = + new Timer(new Duration(milliseconds: 200), completer.complete); return completer.future; } diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index ebc170963..fa72a2f54 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -25,9 +25,9 @@ class PollingDirectoryWatcher extends ResubscribableWatcher /// and higher CPU usage. Defaults to one second. PollingDirectoryWatcher(String directory, {Duration pollingDelay}) : super(directory, () { - return new _PollingDirectoryWatcher(directory, - pollingDelay != null ? pollingDelay : new Duration(seconds: 1)); - }); + return new _PollingDirectoryWatcher(directory, + pollingDelay != null ? pollingDelay : new Duration(seconds: 1)); + }); } class _PollingDirectoryWatcher @@ -73,8 +73,8 @@ class _PollingDirectoryWatcher final _polledFiles = new Set(); _PollingDirectoryWatcher(this.path, this._pollingDelay) { - _filesToProcess = new AsyncQueue(_processFile, - onError: (e, stackTrace) { + _filesToProcess = + new AsyncQueue(_processFile, onError: (e, stackTrace) { if (!_events.isClosed) _events.addError(e, stackTrace); }); diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index ec119f75e..3352b68fe 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -80,8 +80,8 @@ class _WindowsDirectoryWatcher /// The subscriptions to the [Directory.list] calls for listing the contents /// of subdirectories that were moved into the watched directory. - final Set> _listSubscriptions - = new HashSet>(); + final Set> _listSubscriptions = + new HashSet>(); _WindowsDirectoryWatcher(String path) : path = path, @@ -132,8 +132,7 @@ class _WindowsDirectoryWatcher // the directory is now gone. if (event is FileSystemMoveEvent || event is FileSystemDeleteEvent || - (FileSystemEntity.typeSync(path) == - FileSystemEntityType.NOT_FOUND)) { + (FileSystemEntity.typeSync(path) == FileSystemEntityType.NOT_FOUND)) { for (var path in _files.paths) { _emitEvent(ChangeType.REMOVE, path); } @@ -151,8 +150,8 @@ class _WindowsDirectoryWatcher void _onEvent(FileSystemEvent event) { assert(isReady); - final batcher = _eventBatchers.putIfAbsent( - event.path, () => new _EventBatcher()); + final batcher = + _eventBatchers.putIfAbsent(event.path, () => new _EventBatcher()); batcher.addEvent(event, () { _eventBatchers.remove(event.path); _onBatch(batcher.events); @@ -162,10 +161,10 @@ class _WindowsDirectoryWatcher /// The callback that's run when [Directory.watch] emits a batch of events. void _onBatch(List batch) { _sortEvents(batch).forEach((path, eventSet) { - var canonicalEvent = _canonicalEvent(eventSet); - var events = canonicalEvent == null ? - _eventsBasedOnFileSystem(path) : [canonicalEvent]; + var events = canonicalEvent == null + ? _eventsBasedOnFileSystem(path) + : [canonicalEvent]; for (var event in events) { if (event is FileSystemCreateEvent) { @@ -278,8 +277,8 @@ class _WindowsDirectoryWatcher // (respectively) that will be contradictory. if (event is FileSystemModifyEvent) continue; assert(event is FileSystemCreateEvent || - event is FileSystemDeleteEvent || - event is FileSystemMoveEvent); + event is FileSystemDeleteEvent || + event is FileSystemMoveEvent); // If we previously thought this was a MODIFY, we now consider it to be a // CREATE or REMOVE event. This is safe for the same reason as above. @@ -290,8 +289,8 @@ class _WindowsDirectoryWatcher // A CREATE event contradicts a REMOVE event and vice versa. assert(type == FileSystemEvent.CREATE || - type == FileSystemEvent.DELETE || - type == FileSystemEvent.MOVE); + type == FileSystemEvent.DELETE || + type == FileSystemEvent.MOVE); if (type != event.type) return null; } @@ -305,7 +304,8 @@ class _WindowsDirectoryWatcher batch.first.path, isDir, false); case FileSystemEvent.MOVE: return null; - default: throw 'unreachable'; + default: + throw 'unreachable'; } } @@ -369,8 +369,7 @@ class _WindowsDirectoryWatcher // Batch the events together so that we can dedup events. var innerStream = new Directory(path).watch(recursive: true); _watchSubscription = innerStream.listen(_onEvent, - onError: _eventsController.addError, - onDone: _onDone); + onError: _eventsController.addError, onDone: _onDone); } /// Starts or restarts listing the watched directory to get an initial picture @@ -385,11 +384,9 @@ class _WindowsDirectoryWatcher void handleEntity(entity) { if (entity is! Directory) _files.add(entity.path); } - _initialListSubscription = stream.listen( - handleEntity, - onError: _emitError, - onDone: completer.complete, - cancelOnError: true); + + _initialListSubscription = stream.listen(handleEntity, + onError: _emitError, onDone: completer.complete, cancelOnError: true); return completer.future; } diff --git a/pkgs/watcher/lib/src/file_watcher/native.dart b/pkgs/watcher/lib/src/file_watcher/native.dart index f413a72c4..8e7dd0981 100644 --- a/pkgs/watcher/lib/src/file_watcher/native.dart +++ b/pkgs/watcher/lib/src/file_watcher/native.dart @@ -42,7 +42,8 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { void _listen() { // Batch the events together so that we can dedup them. - _subscription = new File(path).watch() + _subscription = new File(path) + .watch() .transform(new BatchedStreamTransformer()) .listen(_onBatch, onError: _eventsController.addError, onDone: _onDone); } diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart index 3f2e9f143..97a4f9533 100644 --- a/pkgs/watcher/lib/src/file_watcher/polling.dart +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -14,9 +14,9 @@ import '../watch_event.dart'; class PollingFileWatcher extends ResubscribableWatcher implements FileWatcher { PollingFileWatcher(String path, {Duration pollingDelay}) : super(path, () { - return new _PollingFileWatcher(path, - pollingDelay != null ? pollingDelay : new Duration(seconds: 1)); - }); + return new _PollingFileWatcher(path, + pollingDelay != null ? pollingDelay : new Duration(seconds: 1)); + }); } class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { diff --git a/pkgs/watcher/lib/src/resubscribable.dart b/pkgs/watcher/lib/src/resubscribable.dart index aeefe9366..28c425ff4 100644 --- a/pkgs/watcher/lib/src/resubscribable.dart +++ b/pkgs/watcher/lib/src/resubscribable.dart @@ -44,22 +44,24 @@ abstract class ResubscribableWatcher implements Watcher { _eventsController = new StreamController.broadcast( onListen: () { - watcher = _factory(); - subscription = watcher.events.listen(_eventsController.add, - onError: _eventsController.addError, - onDone: _eventsController.close); + watcher = _factory(); + subscription = watcher.events.listen(_eventsController.add, + onError: _eventsController.addError, + onDone: _eventsController.close); - // It's important that we complete the value of [_readyCompleter] at the - // time [onListen] is called, as opposed to the value when [watcher.ready] - // fires. A new completer may be created by that time. - watcher.ready.then(_readyCompleter.complete); - }, onCancel: () { - // Cancel the subscription before closing the watcher so that the - // watcher's `onDone` event doesn't close [events]. - subscription.cancel(); - watcher.close(); - _readyCompleter = new Completer(); - }, sync: true); + // It's important that we complete the value of [_readyCompleter] at the + // time [onListen] is called, as opposed to the value when [watcher.ready] + // fires. A new completer may be created by that time. + watcher.ready.then(_readyCompleter.complete); + }, + onCancel: () { + // Cancel the subscription before closing the watcher so that the + // watcher's `onDone` event doesn't close [events]. + subscription.cancel(); + watcher.close(); + _readyCompleter = new Completer(); + }, + sync: true); } } diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index 022c8c1c1..ef39eef48 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -31,8 +31,7 @@ Set unionAll(Iterable sets) => /// If [broadcast] is true, a broadcast stream is returned. This assumes that /// the stream returned by [future] will be a broadcast stream as well. /// [broadcast] defaults to false. -Stream futureStream(Future> future, - {bool broadcast: false}) { +Stream futureStream(Future> future, {bool broadcast: false}) { var subscription; StreamController controller; @@ -47,10 +46,8 @@ Stream futureStream(Future> future, onListen() { future.then((stream) { if (controller == null) return; - subscription = stream.listen( - controller.add, - onError: controller.addError, - onDone: controller.close); + subscription = stream.listen(controller.add, + onError: controller.addError, onDone: controller.close); }); } diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart index 897b1301b..34e689683 100644 --- a/pkgs/watcher/test/directory_watcher/linux_test.dart +++ b/pkgs/watcher/test/directory_watcher/linux_test.dart @@ -19,28 +19,28 @@ void main() { sharedTests(); test('DirectoryWatcher creates a LinuxDirectoryWatcher on Linux', () { - expect(new DirectoryWatcher('.'), - new isInstanceOf()); + expect( + new DirectoryWatcher('.'), new isInstanceOf()); }); test('emits events for many nested files moved out then immediately back in', () { - withPermutations((i, j, k) => - writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); + withPermutations( + (i, j, k) => writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); startWatcher(path: "dir"); renameDir("dir/sub", "sub"); renameDir("sub", "dir/sub"); allowEither(() { - inAnyOrder(withPermutations((i, j, k) => - isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + inAnyOrder(withPermutations( + (i, j, k) => isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); - inAnyOrder(withPermutations((i, j, k) => - isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + inAnyOrder(withPermutations( + (i, j, k) => isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); }, () { - inAnyOrder(withPermutations((i, j, k) => - isModifyEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + inAnyOrder(withPermutations( + (i, j, k) => isModifyEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); }); }); } diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 2c82f34c5..46b7ca2e1 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -19,11 +19,12 @@ void main() { sharedTests(); test('DirectoryWatcher creates a MacOSDirectoryWatcher on Mac OS', () { - expect(new DirectoryWatcher('.'), - new isInstanceOf()); + expect( + new DirectoryWatcher('.'), new isInstanceOf()); }); - test('does not notify about the watched directory being deleted and ' + test( + 'does not notify about the watched directory being deleted and ' 'recreated immediately before watching', () { createDir("dir"); writeFile("dir/old.txt"); @@ -37,8 +38,8 @@ void main() { test('emits events for many nested files moved out then immediately back in', () { - withPermutations((i, j, k) => - writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); + withPermutations( + (i, j, k) => writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); startWatcher(path: "dir"); @@ -46,14 +47,14 @@ void main() { renameDir("sub", "dir/sub"); allowEither(() { - inAnyOrder(withPermutations((i, j, k) => - isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + inAnyOrder(withPermutations( + (i, j, k) => isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); - inAnyOrder(withPermutations((i, j, k) => - isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + inAnyOrder(withPermutations( + (i, j, k) => isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); }, () { - inAnyOrder(withPermutations((i, j, k) => - isModifyEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + inAnyOrder(withPermutations( + (i, j, k) => isModifyEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); }); }); } diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index 148eee263..2bdc07714 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -59,10 +59,7 @@ void sharedTests() { writeFile("a.txt", contents: "same"); writeFile("b.txt", contents: "after"); - inAnyOrder([ - isModifyEvent("a.txt"), - isModifyEvent("b.txt") - ]); + inAnyOrder([isModifyEvent("a.txt"), isModifyEvent("b.txt")]); }); test('when the watched directory is deleted, removes all files', () { @@ -72,10 +69,7 @@ void sharedTests() { startWatcher(path: "dir"); deleteDir("dir"); - inAnyOrder([ - isRemoveEvent("dir/a.txt"), - isRemoveEvent("dir/b.txt") - ]); + inAnyOrder([isRemoveEvent("dir/a.txt"), isRemoveEvent("dir/b.txt")]); }); test('when the watched directory is moved, removes all files', () { @@ -86,14 +80,12 @@ void sharedTests() { renameDir("dir", "moved_dir"); createDir("dir"); - inAnyOrder([ - isRemoveEvent("dir/a.txt"), - isRemoveEvent("dir/b.txt") - ]); + inAnyOrder([isRemoveEvent("dir/a.txt"), isRemoveEvent("dir/b.txt")]); }); // Regression test for b/30768513. - test("doesn't crash when the directory is moved immediately after a subdir " + test( + "doesn't crash when the directory is moved immediately after a subdir " "is added", () { writeFile("dir/a.txt"); writeFile("dir/b.txt"); @@ -103,10 +95,7 @@ void sharedTests() { createDir("dir/subdir"); renameDir("dir", "moved_dir"); createDir("dir"); - inAnyOrder([ - isRemoveEvent("dir/a.txt"), - isRemoveEvent("dir/b.txt") - ]); + inAnyOrder([isRemoveEvent("dir/a.txt"), isRemoveEvent("dir/b.txt")]); }); group("moves", () { @@ -115,10 +104,7 @@ void sharedTests() { startWatcher(); renameFile("old.txt", "new.txt"); - inAnyOrder([ - isAddEvent("new.txt"), - isRemoveEvent("old.txt") - ]); + inAnyOrder([isAddEvent("new.txt"), isRemoveEvent("old.txt")]); }); test('notifies when a file is moved from outside the watched directory', @@ -145,10 +131,7 @@ void sharedTests() { startWatcher(); renameFile("from.txt", "to.txt"); - inAnyOrder([ - isRemoveEvent("from.txt"), - isModifyEvent("to.txt") - ]); + inAnyOrder([isRemoveEvent("from.txt"), isModifyEvent("to.txt")]); }); }); @@ -174,7 +157,8 @@ void sharedTests() { }); }); - test("reports a modification when a file is deleted and then immediately " + test( + "reports a modification when a file is deleted and then immediately " "recreated", () { writeFile("file.txt"); startWatcher(); @@ -191,7 +175,8 @@ void sharedTests() { }); }); - test("reports a modification when a file is moved and then immediately " + test( + "reports a modification when a file is moved and then immediately " "recreated", () { writeFile("old.txt"); startWatcher(); @@ -200,10 +185,7 @@ void sharedTests() { writeFile("old.txt", contents: "re-created"); allowEither(() { - inAnyOrder([ - isModifyEvent("old.txt"), - isAddEvent("new.txt") - ]); + inAnyOrder([isModifyEvent("old.txt"), isAddEvent("new.txt")]); }, () { // Backup case. expectRemoveEvent("old.txt"); @@ -212,7 +194,8 @@ void sharedTests() { }); }); - test("reports a removal when a file is modified and then immediately " + test( + "reports a removal when a file is modified and then immediately " "removed", () { writeFile("file.txt"); startWatcher(); @@ -248,16 +231,14 @@ void sharedTests() { expectAddEvent("a/b/c/d/file.txt"); }); - test('notifies when a subdirectory is moved within the watched directory ' + test( + 'notifies when a subdirectory is moved within the watched directory ' 'and then its contents are modified', () { writeFile("old/file.txt"); startWatcher(); renameDir("old", "new"); - inAnyOrder([ - isRemoveEvent("old/file.txt"), - isAddEvent("new/file.txt") - ]); + inAnyOrder([isRemoveEvent("old/file.txt"), isAddEvent("new/file.txt")]); writeFile("new/file.txt", contents: "modified"); expectModifyEvent("new/file.txt"); @@ -295,20 +276,19 @@ void sharedTests() { }); test('emits events for many nested files added at once', () { - withPermutations((i, j, k) => - writeFile("sub/sub-$i/sub-$j/file-$k.txt")); + withPermutations((i, j, k) => writeFile("sub/sub-$i/sub-$j/file-$k.txt")); createDir("dir"); startWatcher(path: "dir"); renameDir("sub", "dir/sub"); - inAnyOrder(withPermutations((i, j, k) => - isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + inAnyOrder(withPermutations( + (i, j, k) => isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); }); test('emits events for many nested files removed at once', () { - withPermutations((i, j, k) => - writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); + withPermutations( + (i, j, k) => writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); createDir("dir"); startWatcher(path: "dir"); @@ -319,13 +299,13 @@ void sharedTests() { // directory. renameDir("dir/sub", "sub"); - inAnyOrder(withPermutations((i, j, k) => - isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + inAnyOrder(withPermutations( + (i, j, k) => isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); }); test('emits events for many nested files moved at once', () { - withPermutations((i, j, k) => - writeFile("dir/old/sub-$i/sub-$j/file-$k.txt")); + withPermutations( + (i, j, k) => writeFile("dir/old/sub-$i/sub-$j/file-$k.txt")); createDir("dir"); startWatcher(path: "dir"); @@ -339,18 +319,18 @@ void sharedTests() { }))); }); - test("emits events for many files added at once in a subdirectory with the " + test( + "emits events for many files added at once in a subdirectory with the " "same name as a removed file", () { writeFile("dir/sub"); - withPermutations((i, j, k) => - writeFile("old/sub-$i/sub-$j/file-$k.txt")); + withPermutations((i, j, k) => writeFile("old/sub-$i/sub-$j/file-$k.txt")); startWatcher(path: "dir"); deleteFile("dir/sub"); renameDir("old", "dir/sub"); - var events = withPermutations((i, j, k) => - isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); + var events = withPermutations( + (i, j, k) => isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); events.add(isRemoveEvent("dir/sub")); inAnyOrder(events); }); diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 55e40a937..176fe2d71 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -19,7 +19,7 @@ void main() { sharedTests(); test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () { - expect(new DirectoryWatcher('.'), - new isInstanceOf()); + expect( + new DirectoryWatcher('.'), new isInstanceOf()); }); } diff --git a/pkgs/watcher/test/file_watcher/shared.dart b/pkgs/watcher/test/file_watcher/shared.dart index 9a4965ccc..50f7aebba 100644 --- a/pkgs/watcher/test/file_watcher/shared.dart +++ b/pkgs/watcher/test/file_watcher/shared.dart @@ -50,7 +50,8 @@ void sharedTests() { expectRemoveEvent("file.txt"); }); - test("emits a modify event when another file is moved on top of the watched " + test( + "emits a modify event when another file is moved on top of the watched " "file", () { writeFile("old.txt"); startWatcher(path: "file.txt"); diff --git a/pkgs/watcher/test/path_set_test.dart b/pkgs/watcher/test/path_set_test.dart index d3420d377..be15c77c4 100644 --- a/pkgs/watcher/test/path_set_test.dart +++ b/pkgs/watcher/test/path_set_test.dart @@ -6,12 +6,11 @@ import 'package:path/path.dart' as p; import 'package:test/test.dart'; import 'package:watcher/src/path_set.dart'; -Matcher containsPath(String path) => predicate((set) => - set is PathSet && set.contains(path), - 'set contains "$path"'); +Matcher containsPath(String path) => predicate( + (set) => set is PathSet && set.contains(path), 'set contains "$path"'); -Matcher containsDir(String path) => predicate((set) => - set is PathSet && set.containsDir(path), +Matcher containsDir(String path) => predicate( + (set) => set is PathSet && set.containsDir(path), 'set contains directory "$path"'); void main() { @@ -61,11 +60,13 @@ void main() { set.add("root/path/to/two"); set.add("root/path/to/sub/three"); - expect(set.remove("root/path"), unorderedEquals([ - "root/path/to/one", - "root/path/to/two", - "root/path/to/sub/three" - ].map(p.normalize))); + expect( + set.remove("root/path"), + unorderedEquals([ + "root/path/to/one", + "root/path/to/two", + "root/path/to/sub/three" + ].map(p.normalize))); expect(set, containsPath("root/outside")); expect(set, isNot(containsPath("root/path/to/one"))); @@ -73,19 +74,22 @@ void main() { expect(set, isNot(containsPath("root/path/to/sub/three"))); }); - test("that's a directory in the set removes and returns it and all files " + test( + "that's a directory in the set removes and returns it and all files " "beneath it", () { set.add("root/path"); set.add("root/path/to/one"); set.add("root/path/to/two"); set.add("root/path/to/sub/three"); - expect(set.remove("root/path"), unorderedEquals([ - "root/path", - "root/path/to/one", - "root/path/to/two", - "root/path/to/sub/three" - ].map(p.normalize))); + expect( + set.remove("root/path"), + unorderedEquals([ + "root/path", + "root/path/to/one", + "root/path/to/two", + "root/path/to/sub/three" + ].map(p.normalize))); expect(set, isNot(containsPath("root/path"))); expect(set, isNot(containsPath("root/path/to/one"))); @@ -157,14 +161,16 @@ void main() { expect(set, isNot(containsDir("root/path/to/file"))); }); - test("returns false for a directory that was added implicitly and then " + test( + "returns false for a directory that was added implicitly and then " "removed implicitly", () { set.add("root/path/to/file"); set.remove("root/path/to/file"); expect(set, isNot(containsDir("root/path"))); }); - test("returns false for a directory that was added explicitly whose " + test( + "returns false for a directory that was added explicitly whose " "children were then removed", () { set.add("root/path"); set.add("root/path/to/file"); @@ -190,11 +196,13 @@ void main() { set.add("root/path/to/one"); set.add("root/path/to/two"); - expect(set.paths, unorderedEquals([ - "root/path", - "root/path/to/one", - "root/path/to/two", - ].map(p.normalize))); + expect( + set.paths, + unorderedEquals([ + "root/path", + "root/path/to/one", + "root/path/to/two", + ].map(p.normalize))); }); test("doesn't return paths removed from the set", () { diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index e91ed1578..6700500ad 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -35,6 +35,7 @@ typedef Watcher WatcherFactory(String directory); set watcherFactory(WatcherFactory factory) { _watcherFactory = factory; } + WatcherFactory _watcherFactory; /// Creates the sandbox directory the other functions in this library use and @@ -113,20 +114,22 @@ void startWatcher({String path}) { // Schedule [_watcher.events.listen] so that the watcher doesn't start // watching [path] before it exists. Expose [_watcherEvents] immediately so // that it can be accessed synchronously after this. - _watcherEvents = new ScheduledStream(futureStream(schedule(() { - currentSchedule.onComplete.schedule(() { - _watcher = null; - if (!_closePending) _watcherEvents.close(); - - // If there are already errors, don't add this to the output and make - // people think it might be the root cause. - if (currentSchedule.errors.isEmpty) { - _watcherEvents.expect(isDone); - } - }, "reset watcher"); - - return _watcher.events; - }, "create watcher"), broadcast: true)); + _watcherEvents = new ScheduledStream(futureStream( + schedule(() { + currentSchedule.onComplete.schedule(() { + _watcher = null; + if (!_closePending) _watcherEvents.close(); + + // If there are already errors, don't add this to the output and make + // people think it might be the root cause. + if (currentSchedule.errors.isEmpty) { + _watcherEvents.expect(isDone); + } + }, "reset watcher"); + + return _watcher.events; + }, "create watcher"), + broadcast: true)); schedule(() => _watcher.ready, "wait for watcher to be ready"); } @@ -193,8 +196,8 @@ void inAnyOrder(Iterable matchers) { /// /// If both blocks match, the one that consumed more events will be used. void allowEither(block1(), block2()) { - _expectOrCollect(either( - _collectStreamMatcher(block1), _collectStreamMatcher(block2))); + _expectOrCollect( + either(_collectStreamMatcher(block1), _collectStreamMatcher(block2))); } /// Allows the expectations established in [block] to match the emitted events. @@ -210,7 +213,8 @@ void allowEvents(block()) { /// [path]. Matcher isWatchEvent(ChangeType type, String path) { return predicate((e) { - return e is WatchEvent && e.type == type && + return e is WatchEvent && + e.type == type && e.path == p.join(_sandboxDir, p.normalize(path)); }, "is $type $path"); } @@ -344,8 +348,7 @@ void deleteDir(String path) { /// Returns a set of all values returns by [callback]. /// /// [limit] defaults to 3. -Set withPermutations(S callback(int i, int j, int k), - {int limit}) { +Set withPermutations(S callback(int i, int j, int k), {int limit}) { if (limit == null) limit = 3; var results = new Set(); for (var i = 0; i < limit; i++) { From 63df4ac5425597a55be6684c2dc57e4b72858e9a Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 19 Dec 2017 15:40:54 -0800 Subject: [PATCH 104/201] Stop using deprecated pkg/test apis --- pkgs/watcher/pubspec.yaml | 2 +- pkgs/watcher/test/no_subscription/shared.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 6bdb39a46..d1260d56c 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -13,4 +13,4 @@ dependencies: dev_dependencies: benchmark_harness: '^1.0.4' scheduled_test: '^0.12.0' - test: '^0.12.0' + test: '^0.12.18+1' diff --git a/pkgs/watcher/test/no_subscription/shared.dart b/pkgs/watcher/test/no_subscription/shared.dart index 52649f1a7..58586567a 100644 --- a/pkgs/watcher/test/no_subscription/shared.dart +++ b/pkgs/watcher/test/no_subscription/shared.dart @@ -18,7 +18,7 @@ void sharedTests() { // Subscribe to the events. var completer = new Completer(); - var subscription = watcher.events.listen(expectAsync((event) { + var subscription = watcher.events.listen(expectAsync1((event) { expect(event, isWatchEvent(ChangeType.ADD, "file.txt")); completer.complete(); })); @@ -39,7 +39,7 @@ void sharedTests() { // Then start listening again. schedule(() { completer = new Completer(); - subscription = watcher.events.listen(expectAsync((event) { + subscription = watcher.events.listen(expectAsync1((event) { // We should get an event for the third file, not the one added while // we weren't subscribed. expect(event, isWatchEvent(ChangeType.ADD, "added.txt")); From 4890963844b7671c549a518a773c780c37ed1742 Mon Sep 17 00:00:00 2001 From: Gary Roumanis Date: Fri, 5 Jan 2018 13:16:34 -0800 Subject: [PATCH 105/201] Remove scheduled test (dart-lang/watcher#44) --- pkgs/watcher/.travis.yml | 18 +- pkgs/watcher/CHANGELOG.md | 4 + pkgs/watcher/lib/src/utils.dart | 12 - pkgs/watcher/pubspec.yaml | 6 +- .../test/directory_watcher/linux_test.dart | 10 +- .../test/directory_watcher/mac_os_test.dart | 16 +- .../test/directory_watcher/polling_test.dart | 10 +- .../test/directory_watcher/shared.dart | 164 ++++++------ .../test/directory_watcher/windows_test.dart | 10 +- .../test/file_watcher/native_test.dart | 3 +- .../test/file_watcher/polling_test.dart | 3 +- pkgs/watcher/test/file_watcher/shared.dart | 64 +++-- .../test/no_subscription/linux_test.dart | 4 +- .../test/no_subscription/mac_os_test.dart | 4 +- .../test/no_subscription/polling_test.dart | 3 - pkgs/watcher/test/no_subscription/shared.dart | 51 ++-- pkgs/watcher/test/ready/linux_test.dart | 4 +- pkgs/watcher/test/ready/mac_os_test.dart | 4 +- pkgs/watcher/test/ready/polling_test.dart | 3 - pkgs/watcher/test/ready/shared.dart | 86 ++----- pkgs/watcher/test/utils.dart | 239 ++++++------------ 21 files changed, 275 insertions(+), 443 deletions(-) diff --git a/pkgs/watcher/.travis.yml b/pkgs/watcher/.travis.yml index deb901b6e..4c8c5ff75 100644 --- a/pkgs/watcher/.travis.yml +++ b/pkgs/watcher/.travis.yml @@ -1,20 +1,10 @@ language: dart -dart: - - dev - - stable +dart: dev dart_task: - - test - -matrix: - include: - - dart: dev - dart_task: dartfmt - # Only care about being analyzer clean for dev and stable - - dart: dev - dart_task: dartanalyzer - - dart: stable - dart_task: dartanalyzer + - test + - dartfmt + - dartanalyzer # Only building master means that we don't run two builds for each pull request. branches: diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index b4ca0bac9..5e6c3d21f 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.9.7+6 + +* Internal changes only, namely removing dep on scheduled test. + # 0.9.7+5 * Fix an analysis warning. diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index ef39eef48..18b53c9e6 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -71,18 +71,6 @@ Stream futureStream(Future> future, {bool broadcast: false}) { /// under the covers. Future newFuture(callback()) => new Future.value().then((_) => callback()); -/// Returns a [Future] that completes after pumping the event queue [times] -/// times. By default, this should pump the event queue enough times to allow -/// any code to run, as long as it's not waiting on some external event. -Future pumpEventQueue([int times = 20]) { - if (times == 0) return new Future.value(); - // We use a delayed future to allow microtask events to finish. The - // Future.value or Future() constructors use scheduleMicrotask themselves and - // would therefore not wait for microtask callbacks that are scheduled after - // invoking this method. - return new Future.delayed(Duration.ZERO, () => pumpEventQueue(times - 1)); -} - /// A stream transformer that batches all events that are sent at the same time. /// /// When multiple events are synchronously added to a stream controller, the diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index d1260d56c..62abc69bc 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+6.dev +version: 0.9.7+6 author: Dart Team homepage: https://github.com/dart-lang/watcher description: > @@ -12,5 +12,5 @@ dependencies: path: '>=0.9.0 <2.0.0' dev_dependencies: benchmark_harness: '^1.0.4' - scheduled_test: '^0.12.0' - test: '^0.12.18+1' + test: '^0.12.29' + test_descriptor: '^1.0.0' diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart index 34e689683..25c550468 100644 --- a/pkgs/watcher/test/directory_watcher/linux_test.dart +++ b/pkgs/watcher/test/directory_watcher/linux_test.dart @@ -4,7 +4,7 @@ @TestOn('linux') -import 'package:scheduled_test/scheduled_test.dart'; +import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/linux.dart'; import 'package:watcher/watcher.dart'; @@ -14,8 +14,6 @@ import '../utils.dart'; void main() { watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); - setUp(createSandbox); - sharedTests(); test('DirectoryWatcher creates a LinuxDirectoryWatcher on Linux', () { @@ -24,15 +22,15 @@ void main() { }); test('emits events for many nested files moved out then immediately back in', - () { + () async { withPermutations( (i, j, k) => writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); - startWatcher(path: "dir"); + await startWatcher(path: "dir"); renameDir("dir/sub", "sub"); renameDir("sub", "dir/sub"); - allowEither(() { + await allowEither(() { inAnyOrder(withPermutations( (i, j, k) => isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 46b7ca2e1..8fa76fd2d 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -4,7 +4,7 @@ @TestOn('mac-os') -import 'package:scheduled_test/scheduled_test.dart'; +import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/mac_os.dart'; import 'package:watcher/watcher.dart'; @@ -14,8 +14,6 @@ import '../utils.dart'; void main() { watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); - setUp(createSandbox); - sharedTests(); test('DirectoryWatcher creates a MacOSDirectoryWatcher on Mac OS', () { @@ -25,28 +23,28 @@ void main() { test( 'does not notify about the watched directory being deleted and ' - 'recreated immediately before watching', () { + 'recreated immediately before watching', () async { createDir("dir"); writeFile("dir/old.txt"); deleteDir("dir"); createDir("dir"); - startWatcher(path: "dir"); + await startWatcher(path: "dir"); writeFile("dir/newer.txt"); - expectAddEvent("dir/newer.txt"); + await expectAddEvent("dir/newer.txt"); }); test('emits events for many nested files moved out then immediately back in', - () { + () async { withPermutations( (i, j, k) => writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); - startWatcher(path: "dir"); + await startWatcher(path: "dir"); renameDir("dir/sub", "sub"); renameDir("sub", "dir/sub"); - allowEither(() { + await allowEither(() { inAnyOrder(withPermutations( (i, j, k) => isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); diff --git a/pkgs/watcher/test/directory_watcher/polling_test.dart b/pkgs/watcher/test/directory_watcher/polling_test.dart index 0192b2592..39bbbef0b 100644 --- a/pkgs/watcher/test/directory_watcher/polling_test.dart +++ b/pkgs/watcher/test/directory_watcher/polling_test.dart @@ -2,7 +2,7 @@ // 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. -import 'package:scheduled_test/scheduled_test.dart'; +import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; import 'shared.dart'; @@ -13,16 +13,14 @@ void main() { watcherFactory = (dir) => new PollingDirectoryWatcher(dir, pollingDelay: new Duration(milliseconds: 100)); - setUp(createSandbox); - sharedTests(); - test('does not notify if the modification time did not change', () { + test('does not notify if the modification time did not change', () async { writeFile("a.txt", contents: "before"); writeFile("b.txt", contents: "before"); - startWatcher(); + await startWatcher(); writeFile("a.txt", contents: "after", updateModified: false); writeFile("b.txt", contents: "after"); - expectModifyEvent("b.txt"); + await expectModifyEvent("b.txt"); }); } diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index 2bdc07714..2c0f44116 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -2,136 +2,138 @@ // 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. -import 'package:scheduled_test/scheduled_test.dart'; +import 'package:test/test.dart'; import 'package:watcher/src/utils.dart'; import '../utils.dart'; void sharedTests() { - test('does not notify for files that already exist when started', () { + test('does not notify for files that already exist when started', () async { // Make some pre-existing files. writeFile("a.txt"); writeFile("b.txt"); - startWatcher(); + await startWatcher(); // Change one after the watcher is running. writeFile("b.txt", contents: "modified"); // We should get a modify event for the changed file, but no add events // for them before this. - expectModifyEvent("b.txt"); + await expectModifyEvent("b.txt"); }); - test('notifies when a file is added', () { - startWatcher(); + test('notifies when a file is added', () async { + await startWatcher(); writeFile("file.txt"); - expectAddEvent("file.txt"); + await expectAddEvent("file.txt"); }); - test('notifies when a file is modified', () { + test('notifies when a file is modified', () async { writeFile("file.txt"); - startWatcher(); + await startWatcher(); writeFile("file.txt", contents: "modified"); - expectModifyEvent("file.txt"); + await expectModifyEvent("file.txt"); }); - test('notifies when a file is removed', () { + test('notifies when a file is removed', () async { writeFile("file.txt"); - startWatcher(); + await startWatcher(); deleteFile("file.txt"); - expectRemoveEvent("file.txt"); + await expectRemoveEvent("file.txt"); }); - test('notifies when a file is modified multiple times', () { + test('notifies when a file is modified multiple times', () async { writeFile("file.txt"); - startWatcher(); + await startWatcher(); writeFile("file.txt", contents: "modified"); - expectModifyEvent("file.txt"); + await expectModifyEvent("file.txt"); writeFile("file.txt", contents: "modified again"); - expectModifyEvent("file.txt"); + await expectModifyEvent("file.txt"); }); - test('notifies even if the file contents are unchanged', () { + test('notifies even if the file contents are unchanged', () async { writeFile("a.txt", contents: "same"); writeFile("b.txt", contents: "before"); - startWatcher(); + await startWatcher(); writeFile("a.txt", contents: "same"); writeFile("b.txt", contents: "after"); - inAnyOrder([isModifyEvent("a.txt"), isModifyEvent("b.txt")]); + await inAnyOrder([isModifyEvent("a.txt"), isModifyEvent("b.txt")]); }); - test('when the watched directory is deleted, removes all files', () { + test('when the watched directory is deleted, removes all files', () async { writeFile("dir/a.txt"); writeFile("dir/b.txt"); - startWatcher(path: "dir"); + await startWatcher(path: "dir"); deleteDir("dir"); - inAnyOrder([isRemoveEvent("dir/a.txt"), isRemoveEvent("dir/b.txt")]); + await inAnyOrder([isRemoveEvent("dir/a.txt"), isRemoveEvent("dir/b.txt")]); }); - test('when the watched directory is moved, removes all files', () { + test('when the watched directory is moved, removes all files', () async { writeFile("dir/a.txt"); writeFile("dir/b.txt"); - startWatcher(path: "dir"); + await startWatcher(path: "dir"); renameDir("dir", "moved_dir"); createDir("dir"); - inAnyOrder([isRemoveEvent("dir/a.txt"), isRemoveEvent("dir/b.txt")]); + await inAnyOrder([isRemoveEvent("dir/a.txt"), isRemoveEvent("dir/b.txt")]); }); // Regression test for b/30768513. test( "doesn't crash when the directory is moved immediately after a subdir " - "is added", () { + "is added", () async { writeFile("dir/a.txt"); writeFile("dir/b.txt"); - startWatcher(path: "dir"); + await startWatcher(path: "dir"); createDir("dir/subdir"); renameDir("dir", "moved_dir"); createDir("dir"); - inAnyOrder([isRemoveEvent("dir/a.txt"), isRemoveEvent("dir/b.txt")]); + await inAnyOrder([isRemoveEvent("dir/a.txt"), isRemoveEvent("dir/b.txt")]); }); group("moves", () { - test('notifies when a file is moved within the watched directory', () { + test('notifies when a file is moved within the watched directory', + () async { writeFile("old.txt"); - startWatcher(); + await startWatcher(); renameFile("old.txt", "new.txt"); - inAnyOrder([isAddEvent("new.txt"), isRemoveEvent("old.txt")]); + await inAnyOrder([isAddEvent("new.txt"), isRemoveEvent("old.txt")]); }); test('notifies when a file is moved from outside the watched directory', - () { + () async { writeFile("old.txt"); createDir("dir"); - startWatcher(path: "dir"); + await startWatcher(path: "dir"); renameFile("old.txt", "dir/new.txt"); expectAddEvent("dir/new.txt"); }); - test('notifies when a file is moved outside the watched directory', () { + test('notifies when a file is moved outside the watched directory', + () async { writeFile("dir/old.txt"); - startWatcher(path: "dir"); + await startWatcher(path: "dir"); renameFile("dir/old.txt", "new.txt"); expectRemoveEvent("dir/old.txt"); }); - test('notifies when a file is moved onto an existing one', () { + test('notifies when a file is moved onto an existing one', () async { writeFile("from.txt"); writeFile("to.txt"); - startWatcher(); + await startWatcher(); renameFile("from.txt", "to.txt"); - inAnyOrder([isRemoveEvent("from.txt"), isModifyEvent("to.txt")]); + await inAnyOrder([isRemoveEvent("from.txt"), isModifyEvent("to.txt")]); }); }); @@ -144,14 +146,15 @@ void sharedTests() { // that as well. group("clustered changes", () { test("doesn't notify when a file is created and then immediately removed", - () { - startWatcher(); + () async { + writeFile("test.txt"); + await startWatcher(); writeFile("file.txt"); deleteFile("file.txt"); // Backup case. startClosingEventStream(); - allowEvents(() { + await allowEvents(() { expectAddEvent("file.txt"); expectRemoveEvent("file.txt"); }); @@ -159,14 +162,14 @@ void sharedTests() { test( "reports a modification when a file is deleted and then immediately " - "recreated", () { + "recreated", () async { writeFile("file.txt"); - startWatcher(); + await startWatcher(); deleteFile("file.txt"); writeFile("file.txt", contents: "re-created"); - allowEither(() { + await allowEither(() { expectModifyEvent("file.txt"); }, () { // Backup case. @@ -177,14 +180,14 @@ void sharedTests() { test( "reports a modification when a file is moved and then immediately " - "recreated", () { + "recreated", () async { writeFile("old.txt"); - startWatcher(); + await startWatcher(); renameFile("old.txt", "new.txt"); writeFile("old.txt", contents: "re-created"); - allowEither(() { + await allowEither(() { inAnyOrder([isModifyEvent("old.txt"), isAddEvent("new.txt")]); }, () { // Backup case. @@ -196,76 +199,77 @@ void sharedTests() { test( "reports a removal when a file is modified and then immediately " - "removed", () { + "removed", () async { writeFile("file.txt"); - startWatcher(); + await startWatcher(); writeFile("file.txt", contents: "modified"); deleteFile("file.txt"); // Backup case. - allowModifyEvent("file.txt"); + await allowModifyEvent("file.txt"); - expectRemoveEvent("file.txt"); + await expectRemoveEvent("file.txt"); }); test("reports an add when a file is added and then immediately modified", - () { - startWatcher(); + () async { + await startWatcher(); writeFile("file.txt"); writeFile("file.txt", contents: "modified"); - expectAddEvent("file.txt"); + await expectAddEvent("file.txt"); // Backup case. startClosingEventStream(); - allowModifyEvent("file.txt"); + await allowModifyEvent("file.txt"); }); }); group("subdirectories", () { - test('watches files in subdirectories', () { - startWatcher(); + test('watches files in subdirectories', () async { + await startWatcher(); writeFile("a/b/c/d/file.txt"); expectAddEvent("a/b/c/d/file.txt"); }); test( 'notifies when a subdirectory is moved within the watched directory ' - 'and then its contents are modified', () { + 'and then its contents are modified', () async { writeFile("old/file.txt"); - startWatcher(); + await startWatcher(); renameDir("old", "new"); - inAnyOrder([isRemoveEvent("old/file.txt"), isAddEvent("new/file.txt")]); + await inAnyOrder( + [isRemoveEvent("old/file.txt"), isAddEvent("new/file.txt")]); writeFile("new/file.txt", contents: "modified"); - expectModifyEvent("new/file.txt"); + await expectModifyEvent("new/file.txt"); }); - test('notifies when a file is replaced by a subdirectory', () { + test('notifies when a file is replaced by a subdirectory', () async { writeFile("new"); writeFile("old/file.txt"); - startWatcher(); + await startWatcher(); deleteFile("new"); renameDir("old", "new"); - inAnyOrder([ + await inAnyOrder([ isRemoveEvent("new"), isRemoveEvent("old/file.txt"), isAddEvent("new/file.txt") ]); }); - test('notifies when a subdirectory is replaced by a file', () { + test('notifies when a subdirectory is replaced by a file', () async { writeFile("old"); writeFile("new/file.txt"); - startWatcher(); + await startWatcher(); renameDir("new", "newer"); renameFile("old", "new"); - inAnyOrder([ + await inAnyOrder([ isRemoveEvent("new/file.txt"), isAddEvent("newer/file.txt"), isRemoveEvent("old"), @@ -275,23 +279,23 @@ void sharedTests() { "mac-os": new Skip("https://github.com/dart-lang/watcher/issues/21") }); - test('emits events for many nested files added at once', () { + test('emits events for many nested files added at once', () async { withPermutations((i, j, k) => writeFile("sub/sub-$i/sub-$j/file-$k.txt")); createDir("dir"); - startWatcher(path: "dir"); + await startWatcher(path: "dir"); renameDir("sub", "dir/sub"); - inAnyOrder(withPermutations( + await inAnyOrder(withPermutations( (i, j, k) => isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); }); - test('emits events for many nested files removed at once', () { + test('emits events for many nested files removed at once', () async { withPermutations( (i, j, k) => writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); createDir("dir"); - startWatcher(path: "dir"); + await startWatcher(path: "dir"); // Rename the directory rather than deleting it because native watchers // report a rename as a single DELETE event for the directory, whereas @@ -299,19 +303,19 @@ void sharedTests() { // directory. renameDir("dir/sub", "sub"); - inAnyOrder(withPermutations( + await inAnyOrder(withPermutations( (i, j, k) => isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); }); - test('emits events for many nested files moved at once', () { + test('emits events for many nested files moved at once', () async { withPermutations( (i, j, k) => writeFile("dir/old/sub-$i/sub-$j/file-$k.txt")); createDir("dir"); - startWatcher(path: "dir"); + await startWatcher(path: "dir"); renameDir("dir/old", "dir/new"); - inAnyOrder(unionAll(withPermutations((i, j, k) { + await inAnyOrder(unionAll(withPermutations((i, j, k) { return new Set.from([ isRemoveEvent("dir/old/sub-$i/sub-$j/file-$k.txt"), isAddEvent("dir/new/sub-$i/sub-$j/file-$k.txt") @@ -321,10 +325,10 @@ void sharedTests() { test( "emits events for many files added at once in a subdirectory with the " - "same name as a removed file", () { + "same name as a removed file", () async { writeFile("dir/sub"); withPermutations((i, j, k) => writeFile("old/sub-$i/sub-$j/file-$k.txt")); - startWatcher(path: "dir"); + await startWatcher(path: "dir"); deleteFile("dir/sub"); renameDir("old", "dir/sub"); @@ -332,7 +336,7 @@ void sharedTests() { var events = withPermutations( (i, j, k) => isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); events.add(isRemoveEvent("dir/sub")); - inAnyOrder(events); + await inAnyOrder(events); }); }); } diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 176fe2d71..3696f9cb9 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -4,7 +4,7 @@ @TestOn('windows') -import 'package:scheduled_test/scheduled_test.dart'; +import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/windows.dart'; import 'package:watcher/watcher.dart'; @@ -14,9 +14,11 @@ import '../utils.dart'; void main() { watcherFactory = (dir) => new WindowsDirectoryWatcher(dir); - setUp(createSandbox); - - sharedTests(); + // TODO(grouma) - renable when https://github.com/dart-lang/sdk/issues/31760 + // is resolved. + group("Shared Tests:", () { + sharedTests(); + }, skip: "SDK issue see - https://github.com/dart-lang/sdk/issues/31760"); test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () { expect( diff --git a/pkgs/watcher/test/file_watcher/native_test.dart b/pkgs/watcher/test/file_watcher/native_test.dart index cbf11b6cd..b6ed901bc 100644 --- a/pkgs/watcher/test/file_watcher/native_test.dart +++ b/pkgs/watcher/test/file_watcher/native_test.dart @@ -4,7 +4,7 @@ @TestOn('linux || mac-os') -import 'package:scheduled_test/scheduled_test.dart'; +import 'package:test/test.dart'; import 'package:watcher/src/file_watcher/native.dart'; import 'shared.dart'; @@ -14,7 +14,6 @@ void main() { watcherFactory = (file) => new NativeFileWatcher(file); setUp(() { - createSandbox(); writeFile("file.txt"); }); diff --git a/pkgs/watcher/test/file_watcher/polling_test.dart b/pkgs/watcher/test/file_watcher/polling_test.dart index e5025447f..01d579a9f 100644 --- a/pkgs/watcher/test/file_watcher/polling_test.dart +++ b/pkgs/watcher/test/file_watcher/polling_test.dart @@ -4,7 +4,7 @@ @TestOn('linux || mac-os') -import 'package:scheduled_test/scheduled_test.dart'; +import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; import 'shared.dart'; @@ -15,7 +15,6 @@ void main() { pollingDelay: new Duration(milliseconds: 100)); setUp(() { - createSandbox(); writeFile("file.txt"); }); diff --git a/pkgs/watcher/test/file_watcher/shared.dart b/pkgs/watcher/test/file_watcher/shared.dart index 50f7aebba..286a04210 100644 --- a/pkgs/watcher/test/file_watcher/shared.dart +++ b/pkgs/watcher/test/file_watcher/shared.dart @@ -4,73 +4,67 @@ import 'dart:async'; -import 'package:scheduled_test/scheduled_test.dart'; -import 'package:watcher/src/utils.dart'; +import 'package:test/test.dart'; import '../utils.dart'; void sharedTests() { - test("doesn't notify if the file isn't modified", () { - startWatcher(path: "file.txt"); - // Give the watcher time to fire events if it's going to. - schedule(() => pumpEventQueue()); + test("doesn't notify if the file isn't modified", () async { + await startWatcher(path: "file.txt"); + await pumpEventQueue(); deleteFile("file.txt"); - expectRemoveEvent("file.txt"); + await expectRemoveEvent("file.txt"); }); - test("notifies when a file is modified", () { - startWatcher(path: "file.txt"); + test("notifies when a file is modified", () async { + await startWatcher(path: "file.txt"); writeFile("file.txt", contents: "modified"); - expectModifyEvent("file.txt"); + await expectModifyEvent("file.txt"); }); - test("notifies when a file is removed", () { - startWatcher(path: "file.txt"); + test("notifies when a file is removed", () async { + await startWatcher(path: "file.txt"); deleteFile("file.txt"); - expectRemoveEvent("file.txt"); + await expectRemoveEvent("file.txt"); }); - test("notifies when a file is modified multiple times", () { - startWatcher(path: "file.txt"); + test("notifies when a file is modified multiple times", () async { + await startWatcher(path: "file.txt"); writeFile("file.txt", contents: "modified"); - expectModifyEvent("file.txt"); + await expectModifyEvent("file.txt"); writeFile("file.txt", contents: "modified again"); - expectModifyEvent("file.txt"); + await expectModifyEvent("file.txt"); }); - test("notifies even if the file contents are unchanged", () { - startWatcher(path: "file.txt"); + test("notifies even if the file contents are unchanged", () async { + await startWatcher(path: "file.txt"); writeFile("file.txt"); - expectModifyEvent("file.txt"); + await expectModifyEvent("file.txt"); }); - test("emits a remove event when the watched file is moved away", () { - startWatcher(path: "file.txt"); + test("emits a remove event when the watched file is moved away", () async { + await startWatcher(path: "file.txt"); renameFile("file.txt", "new.txt"); - expectRemoveEvent("file.txt"); + await expectRemoveEvent("file.txt"); }); test( "emits a modify event when another file is moved on top of the watched " - "file", () { + "file", () async { writeFile("old.txt"); - startWatcher(path: "file.txt"); + await startWatcher(path: "file.txt"); renameFile("old.txt", "file.txt"); - expectModifyEvent("file.txt"); + await expectModifyEvent("file.txt"); }); // Regression test for a race condition. - test("closes the watcher immediately after deleting the file", () { + test("closes the watcher immediately after deleting the file", () async { writeFile("old.txt"); - var watcher = createWatcher(path: "file.txt", waitForReady: false); - var sub = schedule(() => watcher.events.listen(null)); + var watcher = createWatcher(path: "file.txt"); + var sub = watcher.events.listen(null); deleteFile("file.txt"); - schedule(() async { - // Reproducing the race condition will always be flaky, but this sleep - // helped it reproduce more consistently on my machine. - await new Future.delayed(new Duration(milliseconds: 10)); - (await sub).cancel(); - }); + await new Future.delayed(new Duration(milliseconds: 10)); + await sub.cancel(); }); } diff --git a/pkgs/watcher/test/no_subscription/linux_test.dart b/pkgs/watcher/test/no_subscription/linux_test.dart index c8e8ae91c..e9bfd69ca 100644 --- a/pkgs/watcher/test/no_subscription/linux_test.dart +++ b/pkgs/watcher/test/no_subscription/linux_test.dart @@ -4,7 +4,7 @@ @TestOn('linux') -import 'package:scheduled_test/scheduled_test.dart'; +import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/linux.dart'; import 'shared.dart'; @@ -13,7 +13,5 @@ import '../utils.dart'; void main() { watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); - setUp(createSandbox); - sharedTests(); } diff --git a/pkgs/watcher/test/no_subscription/mac_os_test.dart b/pkgs/watcher/test/no_subscription/mac_os_test.dart index 499aff3ca..fc14ebf10 100644 --- a/pkgs/watcher/test/no_subscription/mac_os_test.dart +++ b/pkgs/watcher/test/no_subscription/mac_os_test.dart @@ -5,7 +5,7 @@ @TestOn('mac-os') @Skip("Flaky due to sdk#23877") -import 'package:scheduled_test/scheduled_test.dart'; +import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/mac_os.dart'; import 'shared.dart'; @@ -14,7 +14,5 @@ import '../utils.dart'; void main() { watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); - setUp(createSandbox); - sharedTests(); } diff --git a/pkgs/watcher/test/no_subscription/polling_test.dart b/pkgs/watcher/test/no_subscription/polling_test.dart index 5c99bc061..75fa3a7a0 100644 --- a/pkgs/watcher/test/no_subscription/polling_test.dart +++ b/pkgs/watcher/test/no_subscription/polling_test.dart @@ -2,7 +2,6 @@ // 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. -import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/watcher.dart'; import 'shared.dart'; @@ -11,7 +10,5 @@ import '../utils.dart'; void main() { watcherFactory = (dir) => new PollingDirectoryWatcher(dir); - setUp(createSandbox); - sharedTests(); } diff --git a/pkgs/watcher/test/no_subscription/shared.dart b/pkgs/watcher/test/no_subscription/shared.dart index 58586567a..c7e050151 100644 --- a/pkgs/watcher/test/no_subscription/shared.dart +++ b/pkgs/watcher/test/no_subscription/shared.dart @@ -4,60 +4,51 @@ import 'dart:async'; -import 'package:scheduled_test/scheduled_test.dart'; +import 'package:async/async.dart'; +import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; import '../utils.dart'; void sharedTests() { - test('does not notify for changes when there are no subscribers', () { + test('does not notify for changes when there are no subscribers', () async { // Note that this test doesn't rely as heavily on the test functions in // utils.dart because it needs to be very explicit about when the event // stream is and is not subscribed. var watcher = createWatcher(); + var queue = new StreamQueue(watcher.events); + queue.hasNext; - // Subscribe to the events. - var completer = new Completer(); - var subscription = watcher.events.listen(expectAsync1((event) { - expect(event, isWatchEvent(ChangeType.ADD, "file.txt")); - completer.complete(); - })); + var future = + expectLater(queue, emits(isWatchEvent(ChangeType.ADD, "file.txt"))); + expect(queue, neverEmits(anything)); - writeFile("file.txt"); + await watcher.ready; - // Then wait until we get an event for it. - schedule(() => completer.future); + writeFile('file.txt'); + + await future; // Unsubscribe. - schedule(() { - subscription.cancel(); - }); + await queue.cancel(immediate: true); // Now write a file while we aren't listening. writeFile("unwatched.txt"); - // Then start listening again. - schedule(() { - completer = new Completer(); - subscription = watcher.events.listen(expectAsync1((event) { - // We should get an event for the third file, not the one added while - // we weren't subscribed. - expect(event, isWatchEvent(ChangeType.ADD, "added.txt")); - completer.complete(); - })); + queue = new StreamQueue(watcher.events); + future = + expectLater(queue, emits(isWatchEvent(ChangeType.ADD, "added.txt"))); + expect(queue, neverEmits(isWatchEvent(ChangeType.ADD, "unwatched.txt"))); - // Wait until the watcher is ready to dispatch events again. - return watcher.ready; - }); + // Wait until the watcher is ready to dispatch events again. + await watcher.ready; // And add a third file. writeFile("added.txt"); // Wait until we get an event for the third file. - schedule(() => completer.future); + await future; - schedule(() { - subscription.cancel(); - }); + await queue.cancel(immediate: true); }); } diff --git a/pkgs/watcher/test/ready/linux_test.dart b/pkgs/watcher/test/ready/linux_test.dart index c8e8ae91c..e9bfd69ca 100644 --- a/pkgs/watcher/test/ready/linux_test.dart +++ b/pkgs/watcher/test/ready/linux_test.dart @@ -4,7 +4,7 @@ @TestOn('linux') -import 'package:scheduled_test/scheduled_test.dart'; +import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/linux.dart'; import 'shared.dart'; @@ -13,7 +13,5 @@ import '../utils.dart'; void main() { watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); - setUp(createSandbox); - sharedTests(); } diff --git a/pkgs/watcher/test/ready/mac_os_test.dart b/pkgs/watcher/test/ready/mac_os_test.dart index d5b1c8e3f..9533cc818 100644 --- a/pkgs/watcher/test/ready/mac_os_test.dart +++ b/pkgs/watcher/test/ready/mac_os_test.dart @@ -4,7 +4,7 @@ @TestOn('mac-os') -import 'package:scheduled_test/scheduled_test.dart'; +import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/mac_os.dart'; import 'shared.dart'; @@ -13,7 +13,5 @@ import '../utils.dart'; void main() { watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); - setUp(createSandbox); - sharedTests(); } diff --git a/pkgs/watcher/test/ready/polling_test.dart b/pkgs/watcher/test/ready/polling_test.dart index 5c99bc061..75fa3a7a0 100644 --- a/pkgs/watcher/test/ready/polling_test.dart +++ b/pkgs/watcher/test/ready/polling_test.dart @@ -2,7 +2,6 @@ // 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. -import 'package:scheduled_test/scheduled_test.dart'; import 'package:watcher/watcher.dart'; import 'shared.dart'; @@ -11,7 +10,5 @@ import '../utils.dart'; void main() { watcherFactory = (dir) => new PollingDirectoryWatcher(dir); - setUp(createSandbox); - sharedTests(); } diff --git a/pkgs/watcher/test/ready/shared.dart b/pkgs/watcher/test/ready/shared.dart index 7be48331e..730d57953 100644 --- a/pkgs/watcher/test/ready/shared.dart +++ b/pkgs/watcher/test/ready/shared.dart @@ -2,97 +2,61 @@ // 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. -import 'package:scheduled_test/scheduled_test.dart'; +import 'package:test/test.dart'; import '../utils.dart'; void sharedTests() { - test('ready does not complete until after subscription', () { - var watcher = createWatcher(waitForReady: false); + test('ready does not complete until after subscription', () async { + var watcher = createWatcher(); var ready = false; watcher.ready.then((_) { ready = true; }); + await pumpEventQueue(); - // Should not be ready yet. - schedule(() { - expect(ready, isFalse); - }); + expect(ready, isFalse); // Subscribe to the events. - schedule(() { - var subscription = watcher.events.listen((event) {}); + watcher.events.listen((event) {}); - currentSchedule.onComplete.schedule(() { - subscription.cancel(); - }); - }); + await watcher.ready; // Should eventually be ready. - schedule(() => watcher.ready); - - schedule(() { - expect(ready, isTrue); - }); + expect(watcher.isReady, isTrue); }); - test('ready completes immediately when already ready', () { - var watcher = createWatcher(waitForReady: false); + test('ready completes immediately when already ready', () async { + var watcher = createWatcher(); // Subscribe to the events. - schedule(() { - var subscription = watcher.events.listen((event) {}); - - currentSchedule.onComplete.schedule(() { - subscription.cancel(); - }); - }); - - // Should eventually be ready. - schedule(() => watcher.ready); + watcher.events.listen((event) {}); - // Now ready should be a future that immediately completes. - var ready = false; - schedule(() { - watcher.ready.then((_) { - ready = true; - }); - }); + // Allow watcher to become ready + await watcher.ready; - schedule(() { - expect(ready, isTrue); - }); + // Ensure ready completes immediately + expect( + watcher.ready.timeout(new Duration(milliseconds: 0), + onTimeout: () => throw 'Does not complete immedately'), + completes); }); - test('ready returns a future that does not complete after unsubscribing', () { - var watcher = createWatcher(waitForReady: false); + test('ready returns a future that does not complete after unsubscribing', + () async { + var watcher = createWatcher(); // Subscribe to the events. - var subscription; - schedule(() { - subscription = watcher.events.listen((event) {}); - }); - - var ready = false; + var subscription = watcher.events.listen((event) {}); // Wait until ready. - schedule(() => watcher.ready); + await watcher.ready; // Now unsubscribe. - schedule(() { - subscription.cancel(); - - // Track when it's ready again. - ready = false; - watcher.ready.then((_) { - ready = true; - }); - }); + await subscription.cancel(); // Should be back to not ready. - schedule(() { - expect(ready, isFalse); - }); + expect(watcher.ready, doesNotComplete); }); } diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 6700500ad..637eacff7 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -2,21 +2,23 @@ // 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. +import 'dart:async'; import 'dart:io'; +import 'package:async/async.dart'; import 'package:path/path.dart' as p; -import 'package:scheduled_test/scheduled_stream.dart'; -import 'package:scheduled_test/scheduled_test.dart'; -import 'package:watcher/watcher.dart'; +import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; + import 'package:watcher/src/stat.dart'; -import 'package:watcher/src/utils.dart'; +import 'package:watcher/watcher.dart'; -/// The path to the temporary sandbox created for each test. All file -/// operations are implicitly relative to this directory. -String _sandboxDir; +typedef Watcher WatcherFactory(String directory); -/// The [Watcher] being used for the current scheduled test. -Watcher _watcher; +/// Sets the function used to create the watcher. +set watcherFactory(WatcherFactory factory) { + _watcherFactory = factory; +} /// The mock modification times (in milliseconds since epoch) for each file. /// @@ -27,53 +29,10 @@ Watcher _watcher; /// /// Instead, we'll just mock that out. Each time a file is written, we manually /// increment the mod time for that file instantly. -Map _mockFileModificationTimes; - -typedef Watcher WatcherFactory(String directory); - -/// Sets the function used to create the watcher. -set watcherFactory(WatcherFactory factory) { - _watcherFactory = factory; -} +final _mockFileModificationTimes = {}; WatcherFactory _watcherFactory; -/// Creates the sandbox directory the other functions in this library use and -/// ensures it's deleted when the test ends. -/// -/// This should usually be called by [setUp]. -void createSandbox() { - var dir = Directory.systemTemp.createTempSync('watcher_test_'); - _sandboxDir = dir.path; - - _mockFileModificationTimes = new Map(); - mockGetModificationTime((path) { - path = p.normalize(p.relative(path, from: _sandboxDir)); - - // Make sure we got a path in the sandbox. - assert(p.isRelative(path) && !path.startsWith("..")); - - var mtime = _mockFileModificationTimes[path]; - return new DateTime.fromMillisecondsSinceEpoch(mtime == null ? 0 : mtime); - }); - - // Delete the sandbox when done. - currentSchedule.onComplete.schedule(() { - if (_sandboxDir != null) { - // TODO(rnystrom): Issue 19155. The watcher should already be closed when - // we clean up the sandbox. - if (_watcherEvents != null) { - _watcherEvents.close(); - } - new Directory(_sandboxDir).deleteSync(recursive: true); - _sandboxDir = null; - } - - _mockFileModificationTimes = null; - mockGetModificationTime(null); - }, "delete sandbox"); -} - /// Creates a new [Watcher] that watches a temporary file or directory. /// /// Normally, this will pause the schedule until the watcher is done scanning @@ -81,62 +40,43 @@ void createSandbox() { /// not schedule this delay. /// /// If [path] is provided, watches a subdirectory in the sandbox with that name. -Watcher createWatcher({String path, bool waitForReady}) { +Watcher createWatcher({String path}) { if (path == null) { - path = _sandboxDir; + path = d.sandbox; } else { - path = p.join(_sandboxDir, path); - } - - var watcher = _watcherFactory(path); - - // Wait until the scan is finished so that we don't miss changes to files - // that could occur before the scan completes. - if (waitForReady != false) { - schedule(() => watcher.ready, "wait for watcher to be ready"); + path = p.join(d.sandbox, path); } - return watcher; + return _watcherFactory(path); } /// The stream of events from the watcher started with [startWatcher]. -ScheduledStream _watcherEvents; +StreamQueue _watcherEvents; /// Creates a new [Watcher] that watches a temporary file or directory and /// starts monitoring it for events. /// /// If [path] is provided, watches a path in the sandbox with that name. -void startWatcher({String path}) { +Future startWatcher({String path}) async { + mockGetModificationTime((path) { + path = p.normalize(p.relative(path, from: d.sandbox)); + + // Make sure we got a path in the sandbox. + assert(p.isRelative(path) && !path.startsWith("..")); + + var mtime = _mockFileModificationTimes[path]; + return new DateTime.fromMillisecondsSinceEpoch(mtime ?? 0); + }); + // We want to wait until we're ready *after* we subscribe to the watcher's // events. - _watcher = createWatcher(path: path, waitForReady: false); - - // Schedule [_watcher.events.listen] so that the watcher doesn't start - // watching [path] before it exists. Expose [_watcherEvents] immediately so - // that it can be accessed synchronously after this. - _watcherEvents = new ScheduledStream(futureStream( - schedule(() { - currentSchedule.onComplete.schedule(() { - _watcher = null; - if (!_closePending) _watcherEvents.close(); - - // If there are already errors, don't add this to the output and make - // people think it might be the root cause. - if (currentSchedule.errors.isEmpty) { - _watcherEvents.expect(isDone); - } - }, "reset watcher"); - - return _watcher.events; - }, "create watcher"), - broadcast: true)); - - schedule(() => _watcher.ready, "wait for watcher to be ready"); + var watcher = createWatcher(path: path); + _watcherEvents = new StreamQueue(watcher.events); + // Forces a subscription to the underlying stream. + _watcherEvents.hasNext; + await watcher.ready; } -/// Whether an event to close [_watcherEvents] has been scheduled. -bool _closePending = false; - /// Schedule closing the watcher stream after the event queue has been pumped. /// /// This is necessary when events are allowed to occur, but don't have to occur, @@ -144,12 +84,7 @@ bool _closePending = false; /// indefinitely because they might in the future and because the watcher is /// normally only closed after the test completes. void startClosingEventStream() { - schedule(() { - _closePending = true; - pumpEventQueue().then((_) => _watcherEvents.close()).whenComplete(() { - _closePending = false; - }); - }, 'start closing event stream'); + pumpEventQueue().then((_) => _watcherEvents.cancel(immediate: true)); } /// A list of [StreamMatcher]s that have been collected using @@ -165,7 +100,7 @@ StreamMatcher _collectStreamMatcher(block()) { _collectedStreamMatchers = new List(); try { block(); - return inOrder(_collectedStreamMatchers); + return emitsInOrder(_collectedStreamMatchers); } finally { _collectedStreamMatchers = oldStreamMatchers; } @@ -175,47 +110,45 @@ StreamMatcher _collectStreamMatcher(block()) { /// it with [_collectStreamMatcher]. /// /// [streamMatcher] can be a [StreamMatcher], a [Matcher], or a value. -void _expectOrCollect(streamMatcher) { +Future _expectOrCollect(streamMatcher) { if (_collectedStreamMatchers != null) { - _collectedStreamMatchers.add(new StreamMatcher.wrap(streamMatcher)); + _collectedStreamMatchers.add(emits(streamMatcher)); + return null; } else { - _watcherEvents.expect(streamMatcher); + return expectLater(_watcherEvents, emits(streamMatcher)); } } /// Expects that [matchers] will match emitted events in any order. /// /// [matchers] may be [Matcher]s or values, but not [StreamMatcher]s. -void inAnyOrder(Iterable matchers) { +Future inAnyOrder(Iterable matchers) { matchers = matchers.toSet(); - _expectOrCollect(nextValues(matchers.length, unorderedMatches(matchers))); + return _expectOrCollect(emitsInAnyOrder(matchers)); } /// Expects that the expectations established in either [block1] or [block2] /// will match the emitted events. /// /// If both blocks match, the one that consumed more events will be used. -void allowEither(block1(), block2()) { - _expectOrCollect( - either(_collectStreamMatcher(block1), _collectStreamMatcher(block2))); -} +Future allowEither(block1(), block2()) => _expectOrCollect( + emitsAnyOf([_collectStreamMatcher(block1), _collectStreamMatcher(block2)])); /// Allows the expectations established in [block] to match the emitted events. /// /// If the expectations in [block] don't match, no error will be raised and no /// events will be consumed. If this is used at the end of a test, /// [startClosingEventStream] should be called before it. -void allowEvents(block()) { - _expectOrCollect(allow(_collectStreamMatcher(block))); -} +Future allowEvents(block()) => + _expectOrCollect(mayEmit(_collectStreamMatcher(block))); -/// Returns a matcher that matches a [WatchEvent] with the given [type] and -/// [path]. +/// Returns a StreamMatcher that matches a [WatchEvent] with the given [type] +/// and [path]. Matcher isWatchEvent(ChangeType type, String path) { return predicate((e) { return e is WatchEvent && e.type == type && - e.path == p.join(_sandboxDir, p.normalize(path)); + e.path == p.join(d.sandbox, p.normalize(path)); }, "is $type $path"); } @@ -231,16 +164,16 @@ Matcher isModifyEvent(String path) => isWatchEvent(ChangeType.MODIFY, path); Matcher isRemoveEvent(String path) => isWatchEvent(ChangeType.REMOVE, path); /// Expects that the next event emitted will be for an add event for [path]. -void expectAddEvent(String path) => +Future expectAddEvent(String path) => _expectOrCollect(isWatchEvent(ChangeType.ADD, path)); /// Expects that the next event emitted will be for a modification event for /// [path]. -void expectModifyEvent(String path) => +Future expectModifyEvent(String path) => _expectOrCollect(isWatchEvent(ChangeType.MODIFY, path)); /// Expects that the next event emitted will be for a removal event for [path]. -void expectRemoveEvent(String path) => +Future expectRemoveEvent(String path) => _expectOrCollect(isWatchEvent(ChangeType.REMOVE, path)); /// Consumes an add event for [path] if one is emitted at this point in the @@ -248,24 +181,24 @@ void expectRemoveEvent(String path) => /// /// If this is used at the end of a test, [startClosingEventStream] should be /// called before it. -void allowAddEvent(String path) => - _expectOrCollect(allow(isWatchEvent(ChangeType.ADD, path))); +Future allowAddEvent(String path) => + _expectOrCollect(mayEmit(isWatchEvent(ChangeType.ADD, path))); /// Consumes a modification event for [path] if one is emitted at this point in /// the schedule, but doesn't throw an error if it isn't. /// /// If this is used at the end of a test, [startClosingEventStream] should be /// called before it. -void allowModifyEvent(String path) => - _expectOrCollect(allow(isWatchEvent(ChangeType.MODIFY, path))); +Future allowModifyEvent(String path) => + _expectOrCollect(mayEmit(isWatchEvent(ChangeType.MODIFY, path))); /// Consumes a removal event for [path] if one is emitted at this point in the /// schedule, but doesn't throw an error if it isn't. /// /// If this is used at the end of a test, [startClosingEventStream] should be /// called before it. -void allowRemoveEvent(String path) => - _expectOrCollect(allow(isWatchEvent(ChangeType.REMOVE, path))); +Future allowRemoveEvent(String path) => + _expectOrCollect(mayEmit(isWatchEvent(ChangeType.REMOVE, path))); /// Schedules writing a file in the sandbox at [path] with [contents]. /// @@ -275,71 +208,55 @@ void writeFile(String path, {String contents, bool updateModified}) { if (contents == null) contents = ""; if (updateModified == null) updateModified = true; - schedule(() { - var fullPath = p.join(_sandboxDir, path); + var fullPath = p.join(d.sandbox, path); - // Create any needed subdirectories. - var dir = new Directory(p.dirname(fullPath)); - if (!dir.existsSync()) { - dir.createSync(recursive: true); - } + // Create any needed subdirectories. + var dir = new Directory(p.dirname(fullPath)); + if (!dir.existsSync()) { + dir.createSync(recursive: true); + } - new File(fullPath).writeAsStringSync(contents); + new File(fullPath).writeAsStringSync(contents); - // Manually update the mock modification time for the file. - if (updateModified) { - // Make sure we always use the same separator on Windows. - path = p.normalize(path); + if (updateModified) { + path = p.normalize(path); - _mockFileModificationTimes.putIfAbsent(path, () => 0); - _mockFileModificationTimes[path]++; - } - }, "write file $path"); + _mockFileModificationTimes.putIfAbsent(path, () => 0); + _mockFileModificationTimes[path]++; + } } /// Schedules deleting a file in the sandbox at [path]. void deleteFile(String path) { - schedule(() { - new File(p.join(_sandboxDir, path)).deleteSync(); - }, "delete file $path"); + new File(p.join(d.sandbox, path)).deleteSync(); } /// Schedules renaming a file in the sandbox from [from] to [to]. /// /// If [contents] is omitted, creates an empty file. void renameFile(String from, String to) { - schedule(() { - new File(p.join(_sandboxDir, from)).renameSync(p.join(_sandboxDir, to)); + new File(p.join(d.sandbox, from)).renameSync(p.join(d.sandbox, to)); - // Make sure we always use the same separator on Windows. - to = p.normalize(to); + // Make sure we always use the same separator on Windows. + to = p.normalize(to); - // Manually update the mock modification time for the file. - _mockFileModificationTimes.putIfAbsent(to, () => 0); - _mockFileModificationTimes[to]++; - }, "rename file $from to $to"); + _mockFileModificationTimes.putIfAbsent(to, () => 0); + _mockFileModificationTimes[to]++; } /// Schedules creating a directory in the sandbox at [path]. void createDir(String path) { - schedule(() { - new Directory(p.join(_sandboxDir, path)).createSync(); - }, "create directory $path"); + new Directory(p.join(d.sandbox, path)).createSync(); } /// Schedules renaming a directory in the sandbox from [from] to [to]. void renameDir(String from, String to) { - schedule(() { - new Directory(p.join(_sandboxDir, from)) - .renameSync(p.join(_sandboxDir, to)); - }, "rename directory $from to $to"); + new Directory(p.join(d.sandbox, from)).renameSync(p.join(d.sandbox, 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"); + new Directory(p.join(d.sandbox, path)).deleteSync(recursive: true); } /// Runs [callback] with every permutation of non-negative [i], [j], and [k] From 8d31f47847f357f8ab8ca0211cd304a3b857b1b5 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 5 Jan 2018 16:26:47 -0800 Subject: [PATCH 106/201] Make _LinuxDirectoryWatcher._listen generic (dart-lang/watcher#45) --- pkgs/watcher/lib/src/directory_watcher/linux.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index 717e4209c..354ddc020 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -127,8 +127,7 @@ class _LinuxDirectoryWatcher } /// The callback that's run when a batch of changes comes in. - void _onBatch(Object data) { - var batch = data as List; + void _onBatch(List batch) { var files = new Set(); var dirs = new Set(); var changed = new Set(); @@ -251,7 +250,7 @@ class _LinuxDirectoryWatcher /// Like [Stream.listen], but automatically adds the subscription to /// [_subscriptions] so that it can be canceled when [close] is called. - void _listen(Stream stream, void onData(event), + void _listen(Stream stream, void onData(T event), {Function onError, void onDone(), bool cancelOnError}) { var subscription; subscription = stream.listen(onData, onError: onError, onDone: () { From 1a43a63fdd506b26d4798cdf781d4ebc3c8f957c Mon Sep 17 00:00:00 2001 From: Leaf Petersen Date: Tue, 30 Jan 2018 18:00:34 -0800 Subject: [PATCH 107/201] Updates for Dart 2.0 corelib changes (wave 2.2) --- pkgs/watcher/CHANGELOG.md | 8 ++++++++ pkgs/watcher/lib/src/utils.dart | 2 +- pkgs/watcher/pubspec.yaml | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 5e6c3d21f..8f1ffc387 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,11 @@ +# 0.9.7+7 + +* Updates to support Dart 2.0 core library changes (wave 2.2). + See [issue 31847][sdk#31847] for details. + + [sdk#31847]: https://github.com/dart-lang/sdk/issues/31847 + + # 0.9.7+6 * Internal changes only, namely removing dep on scheduled test. diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index 18b53c9e6..0b2f79961 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -78,7 +78,7 @@ Future newFuture(callback()) => new Future.value().then((_) => callback()); /// asynchronous firing of each event. In order to recreate the synchronous /// batches, this collates all the events that are received in "nearby" /// microtasks. -class BatchedStreamTransformer implements StreamTransformer> { +class BatchedStreamTransformer extends StreamTransformerBase> { Stream> bind(Stream input) { var batch = new Queue(); return new StreamTransformer>.fromHandlers( diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 62abc69bc..daea75b00 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,12 +1,12 @@ name: watcher -version: 0.9.7+6 +version: 0.9.7+7 author: Dart Team homepage: https://github.com/dart-lang/watcher description: > A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. environment: - sdk: '>=1.21.0 <2.0.0' + sdk: '>=2.0.0-dev.20.0 <2.0.0' dependencies: async: '>=1.10.0 <3.0.0' path: '>=0.9.0 <2.0.0' From bc05d86f3d893e8cb1fecb702005ca171e6e1b81 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 14 Feb 2018 13:21:37 -0800 Subject: [PATCH 108/201] Run Travis against the stable branch (dart-lang/watcher#50) --- pkgs/watcher/.travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/.travis.yml b/pkgs/watcher/.travis.yml index 4c8c5ff75..c9774304b 100644 --- a/pkgs/watcher/.travis.yml +++ b/pkgs/watcher/.travis.yml @@ -8,7 +8,7 @@ dart_task: # Only building master means that we don't run two builds for each pull request. branches: - only: [master, 0-9-7] + only: [master, 0.9.7+x] cache: directories: From f147ac8d3021fbc81ad977aeabf8be167c47dd1c Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 23 May 2018 12:47:58 -0700 Subject: [PATCH 109/201] Add types required in Dart2 and prepare for release (dart-lang/watcher#56) Fixes https://github.com/dart-lang/watcher/issues/55 --- pkgs/watcher/CHANGELOG.md | 4 ++++ .../lib/src/directory_watcher/mac_os.dart | 17 +++++++++-------- .../lib/src/directory_watcher/windows.dart | 17 +++++++++-------- pkgs/watcher/lib/src/utils.dart | 4 ++-- pkgs/watcher/pubspec.yaml | 2 +- 5 files changed, 25 insertions(+), 19 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 8f1ffc387..fe30bc6cb 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.9.7+8 + +* Fix Dart 2.0 type issues on Mac and Windows. + # 0.9.7+7 * Updates to support Dart 2.0 core library changes (wave 2.2). diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 759765f72..5ad99847d 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -171,7 +171,7 @@ class _MacOSDirectoryWatcher /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it /// contain any events relating to [path]. Map> _sortEvents(List batch) { - var eventsForPaths = {}; + var eventsForPaths = >{}; // FSEvents can report past events, including events on the root directory // such as it being created. We want to ignore these. If the directory is @@ -182,20 +182,21 @@ class _MacOSDirectoryWatcher // directory's full contents will be examined anyway, so we ignore such // events. Emitting them could cause useless or out-of-order events. var directories = unionAll(batch.map((event) { - if (!event.isDirectory) return new Set(); + if (!event.isDirectory) return new Set(); if (event is FileSystemMoveEvent) { - return new Set.from([event.path, event.destination]); + return new Set.from([event.path, event.destination]); } - return new Set.from([event.path]); + return new Set.from([event.path]); })); - isInModifiedDirectory(path) => + isInModifiedDirectory(String path) => directories.any((dir) => path != dir && path.startsWith(dir)); - addEvent(path, event) { + addEvent(String path, FileSystemEvent event) { if (isInModifiedDirectory(path)) return; - var set = eventsForPaths.putIfAbsent(path, () => new Set()); - set.add(event); + eventsForPaths + .putIfAbsent(path, () => new Set()) + .add(event); } for (var event in batch) { diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 3352b68fe..db9ca8328 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -216,26 +216,27 @@ class _WindowsDirectoryWatcher /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it /// contain any events relating to [path]. Map> _sortEvents(List batch) { - var eventsForPaths = {}; + var eventsForPaths = >{}; // Events within directories that already have events are superfluous; the // directory's full contents will be examined anyway, so we ignore such // events. Emitting them could cause useless or out-of-order events. var directories = unionAll(batch.map((event) { - if (!event.isDirectory) return new Set(); + if (!event.isDirectory) return new Set(); if (event is FileSystemMoveEvent) { - return new Set.from([event.path, event.destination]); + return new Set.from([event.path, event.destination]); } - return new Set.from([event.path]); + return new Set.from([event.path]); })); - isInModifiedDirectory(path) => + isInModifiedDirectory(String path) => directories.any((dir) => path != dir && path.startsWith(dir)); - addEvent(path, event) { + addEvent(String path, FileSystemEvent event) { if (isInModifiedDirectory(path)) return; - var set = eventsForPaths.putIfAbsent(path, () => new Set()); - set.add(event); + eventsForPaths + .putIfAbsent(path, () => new Set()) + .add(event); } for (var event in batch) { diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index 0b2f79961..62d8e0ff8 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -19,8 +19,8 @@ bool isDirectoryNotFoundException(error) { } /// Returns the union of all elements in each set in [sets]. -Set unionAll(Iterable sets) => - sets.fold(new Set(), (union, set) => union.union(set)); +Set unionAll(Iterable> sets) => + sets.fold(new Set(), (union, set) => union.union(set)); /// Returns a buffered stream that will emit the same values as the stream /// returned by [future] once [future] completes. diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index daea75b00..3cb964370 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+7 +version: 0.9.7+8 author: Dart Team homepage: https://github.com/dart-lang/watcher description: > From 3d01b6e0703832511cfb97d8a9646aed58281cd8 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 23 May 2018 16:08:06 -0700 Subject: [PATCH 110/201] Update .travis.yml for branch change (dart-lang/watcher#57) --- pkgs/watcher/.travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/.travis.yml b/pkgs/watcher/.travis.yml index c9774304b..254764448 100644 --- a/pkgs/watcher/.travis.yml +++ b/pkgs/watcher/.travis.yml @@ -8,7 +8,7 @@ dart_task: # Only building master means that we don't run two builds for each pull request. branches: - only: [master, 0.9.7+x] + only: [master] cache: directories: From 6813e42ff097e885cd1dbe3cdeef975d6cce78a8 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 25 Jun 2018 13:26:44 -0700 Subject: [PATCH 111/201] Use Dart 2 constants (dart-lang/watcher#60) --- pkgs/watcher/CHANGELOG.md | 4 ++++ .../src/constructable_file_system_event.dart | 8 ++++---- .../lib/src/directory_watcher/mac_os.dart | 14 +++++++------- .../lib/src/directory_watcher/windows.dart | 18 +++++++++--------- pkgs/watcher/lib/src/file_watcher/native.dart | 2 +- pkgs/watcher/pubspec.yaml | 4 ++-- 6 files changed, 27 insertions(+), 23 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index fe30bc6cb..d8f2d5f99 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.9.7+9 + +* Internal changes only. + # 0.9.7+8 * Fix Dart 2.0 type issues on Mac and Windows. diff --git a/pkgs/watcher/lib/src/constructable_file_system_event.dart b/pkgs/watcher/lib/src/constructable_file_system_event.dart index a0f153e53..29b7c8ded 100644 --- a/pkgs/watcher/lib/src/constructable_file_system_event.dart +++ b/pkgs/watcher/lib/src/constructable_file_system_event.dart @@ -14,7 +14,7 @@ abstract class _ConstructableFileSystemEvent implements FileSystemEvent { class ConstructableFileSystemCreateEvent extends _ConstructableFileSystemEvent implements FileSystemCreateEvent { - final type = FileSystemEvent.CREATE; + final type = FileSystemEvent.create; ConstructableFileSystemCreateEvent(String path, bool isDirectory) : super(path, isDirectory); @@ -24,7 +24,7 @@ class ConstructableFileSystemCreateEvent extends _ConstructableFileSystemEvent class ConstructableFileSystemDeleteEvent extends _ConstructableFileSystemEvent implements FileSystemDeleteEvent { - final type = FileSystemEvent.DELETE; + final type = FileSystemEvent.delete; ConstructableFileSystemDeleteEvent(String path, bool isDirectory) : super(path, isDirectory); @@ -35,7 +35,7 @@ class ConstructableFileSystemDeleteEvent extends _ConstructableFileSystemEvent class ConstructableFileSystemModifyEvent extends _ConstructableFileSystemEvent implements FileSystemModifyEvent { final bool contentChanged; - final type = FileSystemEvent.MODIFY; + final type = FileSystemEvent.modify; ConstructableFileSystemModifyEvent( String path, bool isDirectory, this.contentChanged) @@ -48,7 +48,7 @@ class ConstructableFileSystemModifyEvent extends _ConstructableFileSystemEvent class ConstructableFileSystemMoveEvent extends _ConstructableFileSystemEvent implements FileSystemMoveEvent { final String destination; - final type = FileSystemEvent.MOVE; + final type = FileSystemEvent.move; ConstructableFileSystemMoveEvent( String path, bool isDirectory, this.destination) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 5ad99847d..9da413a62 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -244,13 +244,13 @@ class _MacOSDirectoryWatcher // If we previously thought this was a MODIFY, we now consider it to be a // CREATE or REMOVE event. This is safe for the same reason as above. - if (type == FileSystemEvent.MODIFY) { + if (type == FileSystemEvent.modify) { type = event.type; continue; } // A CREATE event contradicts a REMOVE event and vice versa. - assert(type == FileSystemEvent.CREATE || type == FileSystemEvent.DELETE); + assert(type == FileSystemEvent.create || type == FileSystemEvent.delete); if (type != event.type) return null; } @@ -258,23 +258,23 @@ class _MacOSDirectoryWatcher // from FSEvents reporting an add that happened prior to the watch // beginning. If we also received a MODIFY event, we want to report that, // but not the CREATE. - if (type == FileSystemEvent.CREATE && + if (type == FileSystemEvent.create && hadModifyEvent && _files.contains(batch.first.path)) { - type = FileSystemEvent.MODIFY; + type = FileSystemEvent.modify; } switch (type) { - case FileSystemEvent.CREATE: + case FileSystemEvent.create: // Issue 16003 means that a CREATE event for a directory can indicate // that the directory was moved and then re-created. // [_eventsBasedOnFileSystem] will handle this correctly by producing a // DELETE event followed by a CREATE event if the directory exists. if (isDir) return null; return new ConstructableFileSystemCreateEvent(batch.first.path, false); - case FileSystemEvent.DELETE: + case FileSystemEvent.delete: return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir); - case FileSystemEvent.MODIFY: + case FileSystemEvent.modify: return new ConstructableFileSystemModifyEvent( batch.first.path, isDir, false); default: diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index db9ca8328..0e550aecc 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -132,7 +132,7 @@ class _WindowsDirectoryWatcher // the directory is now gone. if (event is FileSystemMoveEvent || event is FileSystemDeleteEvent || - (FileSystemEntity.typeSync(path) == FileSystemEntityType.NOT_FOUND)) { + (FileSystemEntity.typeSync(path) == FileSystemEntityType.notFound)) { for (var path in _files.paths) { _emitEvent(ChangeType.REMOVE, path); } @@ -283,27 +283,27 @@ class _WindowsDirectoryWatcher // If we previously thought this was a MODIFY, we now consider it to be a // CREATE or REMOVE event. This is safe for the same reason as above. - if (type == FileSystemEvent.MODIFY) { + if (type == FileSystemEvent.modify) { type = event.type; continue; } // A CREATE event contradicts a REMOVE event and vice versa. - assert(type == FileSystemEvent.CREATE || - type == FileSystemEvent.DELETE || - type == FileSystemEvent.MOVE); + assert(type == FileSystemEvent.create || + type == FileSystemEvent.delete || + type == FileSystemEvent.move); if (type != event.type) return null; } switch (type) { - case FileSystemEvent.CREATE: + case FileSystemEvent.create: return new ConstructableFileSystemCreateEvent(batch.first.path, isDir); - case FileSystemEvent.DELETE: + case FileSystemEvent.delete: return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir); - case FileSystemEvent.MODIFY: + case FileSystemEvent.modify: return new ConstructableFileSystemModifyEvent( batch.first.path, isDir, false); - case FileSystemEvent.MOVE: + case FileSystemEvent.move: return null; default: throw 'unreachable'; diff --git a/pkgs/watcher/lib/src/file_watcher/native.dart b/pkgs/watcher/lib/src/file_watcher/native.dart index 8e7dd0981..5f56f739f 100644 --- a/pkgs/watcher/lib/src/file_watcher/native.dart +++ b/pkgs/watcher/lib/src/file_watcher/native.dart @@ -49,7 +49,7 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { } void _onBatch(List batch) { - if (batch.any((event) => event.type == FileSystemEvent.DELETE)) { + if (batch.any((event) => event.type == FileSystemEvent.delete)) { // If the file is deleted, the underlying stream will close. We handle // emitting our own REMOVE event in [_onDone]. return; diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 3cb964370..2f449ff75 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,12 +1,12 @@ name: watcher -version: 0.9.7+8 +version: 0.9.7+9 author: Dart Team homepage: https://github.com/dart-lang/watcher description: > A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. environment: - sdk: '>=2.0.0-dev.20.0 <2.0.0' + sdk: '>=2.0.0-dev.61.0 <2.0.0' dependencies: async: '>=1.10.0 <3.0.0' path: '>=0.9.0 <2.0.0' From a81b4a1b220c46dd5b632fadedb1c0a499283559 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 10 Jul 2018 11:23:33 -0700 Subject: [PATCH 112/201] dartfmt --- pkgs/watcher/lib/src/directory_watcher/mac_os.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 9da413a62..b0e3326fe 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -81,8 +81,8 @@ class _MacOSDirectoryWatcher // // If we do receive a batch of events, [_onBatch] will ensure that these // futures don't fire and that the directory is re-listed. - Future.wait([_listDir(), _waitForBogusEvents()]).then( - (_) => _readyCompleter.complete()); + Future.wait([_listDir(), _waitForBogusEvents()]) + .then((_) => _readyCompleter.complete()); } void close() { From 72e6065f216f7bb90a22a5c439ceb38a30890fa8 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Tue, 17 Jul 2018 17:47:30 -0400 Subject: [PATCH 113/201] chore: set max SDK version to <3.0.0 (dart-lang/watcher#61) --- pkgs/watcher/CHANGELOG.md | 4 ++++ pkgs/watcher/analysis_options.yaml | 2 -- pkgs/watcher/pubspec.yaml | 18 +++++++++++------- .../test/directory_watcher/linux_test.dart | 3 +-- .../test/directory_watcher/mac_os_test.dart | 3 +-- .../test/directory_watcher/windows_test.dart | 2 +- pkgs/watcher/test/no_subscription/shared.dart | 2 -- 7 files changed, 18 insertions(+), 16 deletions(-) delete mode 100644 pkgs/watcher/analysis_options.yaml diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index d8f2d5f99..7ea1cc1f3 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.9.7+10 + +* Set max SDK version to `<3.0.0`, and adjust other dependencies. + # 0.9.7+9 * Internal changes only. diff --git a/pkgs/watcher/analysis_options.yaml b/pkgs/watcher/analysis_options.yaml deleted file mode 100644 index a10d4c5a0..000000000 --- a/pkgs/watcher/analysis_options.yaml +++ /dev/null @@ -1,2 +0,0 @@ -analyzer: - strong-mode: true diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 2f449ff75..73ce19b36 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,16 +1,20 @@ name: watcher -version: 0.9.7+9 -author: Dart Team -homepage: https://github.com/dart-lang/watcher +version: 0.9.7+10 + description: > A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. +author: Dart Team +homepage: https://github.com/dart-lang/watcher + environment: - sdk: '>=2.0.0-dev.61.0 <2.0.0' + sdk: '>=2.0.0-dev.61.0 <3.0.0' + dependencies: async: '>=1.10.0 <3.0.0' path: '>=0.9.0 <2.0.0' + dev_dependencies: - benchmark_harness: '^1.0.4' - test: '^0.12.29' - test_descriptor: '^1.0.0' + benchmark_harness: ^1.0.4 + test: '>=0.12.42 <2.0.0' + test_descriptor: ^1.0.0 diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart index 25c550468..744045fcd 100644 --- a/pkgs/watcher/test/directory_watcher/linux_test.dart +++ b/pkgs/watcher/test/directory_watcher/linux_test.dart @@ -17,8 +17,7 @@ void main() { sharedTests(); test('DirectoryWatcher creates a LinuxDirectoryWatcher on Linux', () { - expect( - new DirectoryWatcher('.'), new isInstanceOf()); + expect(new DirectoryWatcher('.'), new TypeMatcher()); }); test('emits events for many nested files moved out then immediately back in', diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 8fa76fd2d..689d35318 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -17,8 +17,7 @@ void main() { sharedTests(); test('DirectoryWatcher creates a MacOSDirectoryWatcher on Mac OS', () { - expect( - new DirectoryWatcher('.'), new isInstanceOf()); + expect(new DirectoryWatcher('.'), new TypeMatcher()); }); test( diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 3696f9cb9..875f4ee81 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -22,6 +22,6 @@ void main() { test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () { expect( - new DirectoryWatcher('.'), new isInstanceOf()); + new DirectoryWatcher('.'), new TypeMatcher()); }); } diff --git a/pkgs/watcher/test/no_subscription/shared.dart b/pkgs/watcher/test/no_subscription/shared.dart index c7e050151..ba8468401 100644 --- a/pkgs/watcher/test/no_subscription/shared.dart +++ b/pkgs/watcher/test/no_subscription/shared.dart @@ -2,8 +2,6 @@ // 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. -import 'dart:async'; - import 'package:async/async.dart'; import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; From b579a49a247a42b2d833d2120d8f24395bac0967 Mon Sep 17 00:00:00 2001 From: Keerti Parthasarathy Date: Fri, 3 Aug 2018 16:26:02 -0700 Subject: [PATCH 114/201] Add missing return to fix analysis (dart-lang/watcher#62) * Add missing return to fix analysis * address comment * Update async_queue.dart --- pkgs/watcher/CHANGELOG.md | 4 ++++ pkgs/watcher/lib/src/async_queue.dart | 5 +++-- pkgs/watcher/pubspec.yaml | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 7ea1cc1f3..cd34d904e 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.9.7+11 + +* Fix an analysis hint. + # 0.9.7+10 * Set max SDK version to `<3.0.0`, and adjust other dependencies. diff --git a/pkgs/watcher/lib/src/async_queue.dart b/pkgs/watcher/lib/src/async_queue.dart index 1895c804e..1904192b4 100644 --- a/pkgs/watcher/lib/src/async_queue.dart +++ b/pkgs/watcher/lib/src/async_queue.dart @@ -59,12 +59,13 @@ class AsyncQueue { /// the process was cancelled. Future _processNextItem() { var item = _items.removeFirst(); - return _processor(item).then((_) { - if (_items.isNotEmpty) return _processNextItem(); + return _processor(item).then((_) async { + if (_items.isNotEmpty) return await _processNextItem(); // We have drained the queue, stop processing and wait until something // has been enqueued. _isProcessing = false; + return null; }); } } diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 73ce19b36..8f38a49f6 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+10 +version: 0.9.7+11 description: > A file system watcher. It monitors changes to contents of directories and From c5d255a80b961c3fa3c0898b2bb93e1fa3c78575 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Mon, 10 Sep 2018 17:38:18 -0700 Subject: [PATCH 115/201] Add some stricter analysis (dart-lang/watcher#64) - Enable errors that would fail internally - Add `comment_references` lint - this makes DartDocs output more usable and there isn't any reason to have unclickable references. - Add `prefer_typing_uninitialized_variables` lint - this prevents some unintentionally dynamic behavior. - Disable implicit casts. - Use function type syntax for the ManuallyClosedWatcher factory. - Remove some unused utilities. --- pkgs/watcher/analysis_options.yaml | 14 ++ pkgs/watcher/benchmark/path_set.dart | 2 +- .../lib/src/directory_watcher/linux.dart | 12 +- .../lib/src/directory_watcher/mac_os.dart | 8 +- .../lib/src/directory_watcher/polling.dart | 12 +- .../lib/src/directory_watcher/windows.dart | 6 +- pkgs/watcher/lib/src/file_watcher.dart | 2 +- .../watcher/lib/src/file_watcher/polling.dart | 2 +- pkgs/watcher/lib/src/path_set.dart | 16 +- pkgs/watcher/lib/src/resubscribable.dart | 8 +- pkgs/watcher/lib/src/utils.dart | 51 ------ pkgs/watcher/test/path_set_test.dart | 171 +++++++++--------- pkgs/watcher/test/utils.dart | 12 +- 13 files changed, 137 insertions(+), 179 deletions(-) create mode 100644 pkgs/watcher/analysis_options.yaml diff --git a/pkgs/watcher/analysis_options.yaml b/pkgs/watcher/analysis_options.yaml new file mode 100644 index 000000000..4c9f8113a --- /dev/null +++ b/pkgs/watcher/analysis_options.yaml @@ -0,0 +1,14 @@ +analyzer: + strong-mode: + implicit-casts: false + errors: + todo: ignore + unused_import: error + unused_element: error + unused_local_variable: error + dead_code: error + +linter: + rules: + - comment_references + - prefer_typing_uninitialized_variables diff --git a/pkgs/watcher/benchmark/path_set.dart b/pkgs/watcher/benchmark/path_set.dart index aba3ed750..bf9f5fc7c 100644 --- a/pkgs/watcher/benchmark/path_set.dart +++ b/pkgs/watcher/benchmark/path_set.dart @@ -31,7 +31,7 @@ abstract class PathSetBenchmark extends BenchmarkBase { /// Each virtual directory contains ten entries: either subdirectories or /// files. void walkTree(int depth, callback(String path)) { - recurse(path, remainingDepth) { + recurse(String path, remainingDepth) { for (var i = 0; i < 10; i++) { var padded = i.toString().padLeft(2, '0'); if (remainingDepth == 0) { diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index 354ddc020..5eeed2327 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -79,13 +79,14 @@ class _LinuxDirectoryWatcher .transform(new BatchedStreamTransformer()); _listen(innerStream, _onBatch, onError: _eventsController.addError); - _listen(new Directory(path).list(recursive: true), (entity) { + _listen(new Directory(path).list(recursive: true), + (FileSystemEntity entity) { if (entity is Directory) { _watchSubdir(entity.path); } else { _files.add(entity.path); } - }, onError: (error, stackTrace) { + }, onError: (error, StackTrace stackTrace) { _eventsController.addError(error, stackTrace); close(); }, onDone: () { @@ -207,14 +208,15 @@ class _LinuxDirectoryWatcher /// Emits [ChangeType.ADD] events for the recursive contents of [path]. void _addSubdir(String path) { - _listen(new Directory(path).list(recursive: true), (entity) { + _listen(new Directory(path).list(recursive: true), + (FileSystemEntity entity) { if (entity is Directory) { _watchSubdir(entity.path); } else { _files.add(entity.path); _emit(ChangeType.ADD, entity.path); } - }, onError: (error, stackTrace) { + }, onError: (error, StackTrace stackTrace) { // Ignore an exception caused by the dir not existing. It's fine if it // was added and then quickly removed. if (error is FileSystemException) return; @@ -252,7 +254,7 @@ class _LinuxDirectoryWatcher /// [_subscriptions] so that it can be canceled when [close] is called. void _listen(Stream stream, void onData(T event), {Function onError, void onDone(), bool cancelOnError}) { - var subscription; + StreamSubscription subscription; subscription = stream.listen(onData, onError: onError, onDone: () { _subscriptions.remove(subscription); if (onDone != null) onDone(); diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index b0e3326fe..61531c5d9 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -54,8 +54,8 @@ class _MacOSDirectoryWatcher /// The subscription to the stream returned by [Directory.watch]. /// - /// This is separate from [_subscriptions] because this stream occasionally - /// needs to be resubscribed in order to work around issue 14849. + /// This is separate from [_listSubscriptions] because this stream + /// occasionally needs to be resubscribed in order to work around issue 14849. StreamSubscription> _watchSubscription; /// The subscription to the [Directory.list] call for the initial listing of @@ -143,7 +143,7 @@ class _MacOSDirectoryWatcher _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); - }, onError: (e, stackTrace) { + }, onError: (e, StackTrace stackTrace) { _emitError(e, stackTrace); }, onDone: () { _listSubscriptions.remove(subscription); @@ -212,7 +212,7 @@ class _MacOSDirectoryWatcher /// one exists. /// /// If [batch] doesn't contain any contradictory events (e.g. DELETE and - /// CREATE, or events with different values for [isDirectory]), this returns a + /// CREATE, or events with different values for `isDirectory`), this returns a /// single event that describes what happened to the path in question. /// /// If [batch] does contain contradictory events, this returns `null` to diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index fa72a2f54..790d0b982 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -19,7 +19,7 @@ class PollingDirectoryWatcher extends ResubscribableWatcher /// Creates a new polling watcher monitoring [directory]. /// - /// If [_pollingDelay] is passed, it specifies the amount of time the watcher + /// If [pollingDelay] is passed, it specifies the amount of time the watcher /// will pause between successive polls of the directory contents. Making this /// shorter will give more immediate feedback at the expense of doing more IO /// and higher CPU usage. Defaults to one second. @@ -68,13 +68,13 @@ class _PollingDirectoryWatcher /// The set of files that have been seen in the current directory listing. /// - /// Used to tell which files have been removed: files that are in [_statuses] - /// but not in here when a poll completes have been removed. + /// Used to tell which files have been removed: files that are in + /// [_lastModifieds] but not in here when a poll completes have been removed. final _polledFiles = new Set(); _PollingDirectoryWatcher(this.path, this._pollingDelay) { - _filesToProcess = - new AsyncQueue(_processFile, onError: (e, stackTrace) { + _filesToProcess = new AsyncQueue(_processFile, + onError: (e, StackTrace stackTrace) { if (!_events.isClosed) _events.addError(e, stackTrace); }); @@ -113,7 +113,7 @@ class _PollingDirectoryWatcher if (entity is! File) return; _filesToProcess.add(entity.path); - }, onError: (error, stackTrace) { + }, onError: (error, StackTrace stackTrace) { if (!isDirectoryNotFoundException(error)) { // It's some unknown error. Pipe it over to the event stream so the // user can see it. diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 0e550aecc..baeaf233a 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -188,7 +188,7 @@ class _WindowsDirectoryWatcher _files.add(entity.path); }, onDone: () { _listSubscriptions.remove(subscription); - }, onError: (e, stackTrace) { + }, onError: (e, StackTrace stackTrace) { _listSubscriptions.remove(subscription); _emitError(e, stackTrace); }, cancelOnError: true); @@ -253,7 +253,7 @@ class _WindowsDirectoryWatcher /// one exists. /// /// If [batch] doesn't contain any contradictory events (e.g. DELETE and - /// CREATE, or events with different values for [isDirectory]), this returns a + /// CREATE, or events with different values for `isDirectory`), this returns a /// single event that describes what happened to the path in question. /// /// If [batch] does contain contradictory events, this returns `null` to @@ -382,7 +382,7 @@ class _WindowsDirectoryWatcher _files.clear(); var completer = new Completer(); var stream = new Directory(path).list(recursive: true); - void handleEntity(entity) { + void handleEntity(FileSystemEntity entity) { if (entity is! Directory) _files.add(entity.path); } diff --git a/pkgs/watcher/lib/src/file_watcher.dart b/pkgs/watcher/lib/src/file_watcher.dart index 17c5f2efb..09065bc8b 100644 --- a/pkgs/watcher/lib/src/file_watcher.dart +++ b/pkgs/watcher/lib/src/file_watcher.dart @@ -15,7 +15,7 @@ import 'file_watcher/polling.dart'; /// it will emit a single [ChangeType.REMOVE] event and then close the stream. /// /// If the file is deleted and quickly replaced (when a new file is moved in its -/// place, for example) this will emit a [ChangeTime.MODIFY] event. +/// place, for example) this will emit a [ChangeType.MODIFY] event. abstract class FileWatcher implements Watcher { /// Creates a new [FileWatcher] monitoring [file]. /// diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart index 97a4f9533..960b11b9b 100644 --- a/pkgs/watcher/lib/src/file_watcher/polling.dart +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -58,7 +58,7 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { return; } - var modified; + DateTime modified; try { try { modified = await getModificationTime(path); diff --git a/pkgs/watcher/lib/src/path_set.dart b/pkgs/watcher/lib/src/path_set.dart index 3726e1fd8..77737f82e 100644 --- a/pkgs/watcher/lib/src/path_set.dart +++ b/pkgs/watcher/lib/src/path_set.dart @@ -49,13 +49,13 @@ class PathSet { /// empty set. Set remove(String path) { path = _normalize(path); - var parts = new Queue.from(p.split(path)); + var parts = new Queue.of(p.split(path)); // Remove the children of [dir], as well as [dir] itself if necessary. // // [partialPath] is the path to [dir], and a prefix of [path]; the remaining // components of [path] are in [parts]. - Set recurse(dir, partialPath) { + Set recurse(_Entry dir, String partialPath) { if (parts.length > 1) { // If there's more than one component left in [path], recurse down to // the next level. @@ -97,7 +97,7 @@ class PathSet { /// [dirPath] should be the path to [dir]. Set _explicitPathsWithin(_Entry dir, String dirPath) { var paths = new Set(); - recurse(dir, path) { + recurse(_Entry dir, String path) { dir.contents.forEach((name, entry) { var entryPath = p.join(path, name); if (entry.isExplicit) paths.add(p.join(root, entryPath)); @@ -110,9 +110,9 @@ class PathSet { return paths; } - /// Returns whether [this] contains [path]. + /// Returns whether this set contains [path]. /// - /// This only returns true for paths explicitly added to [this]. + /// This only returns true for paths explicitly added to this set. /// Implicitly-added directories can be inspected using [containsDir]. bool contains(String path) { path = _normalize(path); @@ -126,7 +126,7 @@ class PathSet { return entry.isExplicit; } - /// Returns whether [this] contains paths beneath [path]. + /// Returns whether this set contains paths beneath [path]. bool containsDir(String path) { path = _normalize(path); var entry = _entries; @@ -143,7 +143,7 @@ class PathSet { List get paths { var result = []; - recurse(dir, path) { + recurse(_Entry dir, String path) { for (var name in dir.contents.keys) { var entry = dir.contents[name]; var entryPath = p.join(path, name); @@ -156,7 +156,7 @@ class PathSet { return result; } - /// Removes all paths from [this]. + /// Removes all paths from this set. void clear() { _entries.contents.clear(); } diff --git a/pkgs/watcher/lib/src/resubscribable.dart b/pkgs/watcher/lib/src/resubscribable.dart index 28c425ff4..e96b918dc 100644 --- a/pkgs/watcher/lib/src/resubscribable.dart +++ b/pkgs/watcher/lib/src/resubscribable.dart @@ -7,8 +7,6 @@ import 'dart:async'; import '../watcher.dart'; import 'watch_event.dart'; -typedef ManuallyClosedWatcher WatcherFactory(); - /// A wrapper for [ManuallyClosedWatcher] that encapsulates support for closing /// the watcher when it has no subscribers and re-opening it when it's /// re-subscribed. @@ -24,7 +22,7 @@ typedef ManuallyClosedWatcher WatcherFactory(); /// takes a factory function that produces instances of the inner class. abstract class ResubscribableWatcher implements Watcher { /// The factory function that produces instances of the inner class. - final WatcherFactory _factory; + final ManuallyClosedWatcher Function() _factory; final String path; @@ -39,8 +37,8 @@ abstract class ResubscribableWatcher implements Watcher { /// Creates a new [ResubscribableWatcher] wrapping the watchers /// emitted by [_factory]. ResubscribableWatcher(this.path, this._factory) { - var watcher; - var subscription; + ManuallyClosedWatcher watcher; + StreamSubscription subscription; _eventsController = new StreamController.broadcast( onListen: () { diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index 62d8e0ff8..30fbaae21 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -6,8 +6,6 @@ import 'dart:async'; import 'dart:io'; import 'dart:collection'; -import 'package:async/async.dart'; - /// Returns `true` if [error] is a [FileSystemException] for a missing /// directory. bool isDirectoryNotFoundException(error) { @@ -22,55 +20,6 @@ bool isDirectoryNotFoundException(error) { Set unionAll(Iterable> sets) => sets.fold(new Set(), (union, set) => union.union(set)); -/// Returns a buffered stream that will emit the same values as the stream -/// returned by [future] once [future] completes. -/// -/// If [future] completes to an error, the return value will emit that error and -/// then close. -/// -/// If [broadcast] is true, a broadcast stream is returned. This assumes that -/// the stream returned by [future] will be a broadcast stream as well. -/// [broadcast] defaults to false. -Stream futureStream(Future> future, {bool broadcast: false}) { - var subscription; - StreamController controller; - - future = DelegatingFuture.typed(future.catchError((e, stackTrace) { - // Since [controller] is synchronous, it's likely that emitting an error - // will cause it to be cancelled before we call close. - if (controller != null) controller.addError(e, stackTrace); - if (controller != null) controller.close(); - controller = null; - })); - - onListen() { - future.then((stream) { - if (controller == null) return; - subscription = stream.listen(controller.add, - onError: controller.addError, onDone: controller.close); - }); - } - - onCancel() { - if (subscription != null) subscription.cancel(); - subscription = null; - controller = null; - } - - if (broadcast) { - controller = new StreamController.broadcast( - sync: true, onListen: onListen, onCancel: onCancel); - } else { - controller = new StreamController( - sync: true, onListen: onListen, onCancel: onCancel); - } - return controller.stream; -} - -/// Like [new Future], but avoids around issue 11911 by using [new Future.value] -/// under the covers. -Future newFuture(callback()) => new Future.value().then((_) => callback()); - /// A stream transformer that batches all events that are sent at the same time. /// /// When multiple events are synchronously added to a stream controller, the diff --git a/pkgs/watcher/test/path_set_test.dart b/pkgs/watcher/test/path_set_test.dart index be15c77c4..fe91d417f 100644 --- a/pkgs/watcher/test/path_set_test.dart +++ b/pkgs/watcher/test/path_set_test.dart @@ -7,83 +7,84 @@ import 'package:test/test.dart'; import 'package:watcher/src/path_set.dart'; Matcher containsPath(String path) => predicate( - (set) => set is PathSet && set.contains(path), 'set contains "$path"'); + (paths) => paths is PathSet && paths.contains(path), + 'set contains "$path"'); Matcher containsDir(String path) => predicate( - (set) => set is PathSet && set.containsDir(path), + (paths) => paths is PathSet && paths.containsDir(path), 'set contains directory "$path"'); void main() { - var set; - setUp(() => set = new PathSet("root")); + PathSet paths; + setUp(() => paths = new PathSet("root")); group("adding a path", () { test("stores the path in the set", () { - set.add("root/path/to/file"); - expect(set, containsPath("root/path/to/file")); + paths.add("root/path/to/file"); + expect(paths, containsPath("root/path/to/file")); }); test("that's a subdir of another path keeps both in the set", () { - set.add("root/path"); - set.add("root/path/to/file"); - expect(set, containsPath("root/path")); - expect(set, containsPath("root/path/to/file")); + paths.add("root/path"); + paths.add("root/path/to/file"); + expect(paths, containsPath("root/path")); + expect(paths, containsPath("root/path/to/file")); }); test("that's not normalized normalizes the path before storing it", () { - set.add("root/../root/path/to/../to/././file"); - expect(set, containsPath("root/path/to/file")); + paths.add("root/../root/path/to/../to/././file"); + expect(paths, containsPath("root/path/to/file")); }); test("that's absolute normalizes the path before storing it", () { - set.add(p.absolute("root/path/to/file")); - expect(set, containsPath("root/path/to/file")); + paths.add(p.absolute("root/path/to/file")); + expect(paths, containsPath("root/path/to/file")); }); }); group("removing a path", () { test("that's in the set removes and returns that path", () { - set.add("root/path/to/file"); - expect(set.remove("root/path/to/file"), + paths.add("root/path/to/file"); + expect(paths.remove("root/path/to/file"), unorderedEquals([p.normalize("root/path/to/file")])); - expect(set, isNot(containsPath("root/path/to/file"))); + expect(paths, isNot(containsPath("root/path/to/file"))); }); test("that's not in the set returns an empty set", () { - set.add("root/path/to/file"); - expect(set.remove("root/path/to/nothing"), isEmpty); + paths.add("root/path/to/file"); + expect(paths.remove("root/path/to/nothing"), isEmpty); }); test("that's a directory removes and returns all files beneath it", () { - set.add("root/outside"); - set.add("root/path/to/one"); - set.add("root/path/to/two"); - set.add("root/path/to/sub/three"); + paths.add("root/outside"); + paths.add("root/path/to/one"); + paths.add("root/path/to/two"); + paths.add("root/path/to/sub/three"); expect( - set.remove("root/path"), + paths.remove("root/path"), unorderedEquals([ "root/path/to/one", "root/path/to/two", "root/path/to/sub/three" ].map(p.normalize))); - expect(set, containsPath("root/outside")); - expect(set, isNot(containsPath("root/path/to/one"))); - expect(set, isNot(containsPath("root/path/to/two"))); - expect(set, isNot(containsPath("root/path/to/sub/three"))); + expect(paths, containsPath("root/outside")); + expect(paths, isNot(containsPath("root/path/to/one"))); + expect(paths, isNot(containsPath("root/path/to/two"))); + expect(paths, isNot(containsPath("root/path/to/sub/three"))); }); test( "that's a directory in the set removes and returns it and all files " "beneath it", () { - set.add("root/path"); - set.add("root/path/to/one"); - set.add("root/path/to/two"); - set.add("root/path/to/sub/three"); + paths.add("root/path"); + paths.add("root/path/to/one"); + paths.add("root/path/to/two"); + paths.add("root/path/to/sub/three"); expect( - set.remove("root/path"), + paths.remove("root/path"), unorderedEquals([ "root/path", "root/path/to/one", @@ -91,113 +92,113 @@ void main() { "root/path/to/sub/three" ].map(p.normalize))); - expect(set, isNot(containsPath("root/path"))); - expect(set, isNot(containsPath("root/path/to/one"))); - expect(set, isNot(containsPath("root/path/to/two"))); - expect(set, isNot(containsPath("root/path/to/sub/three"))); + expect(paths, isNot(containsPath("root/path"))); + expect(paths, isNot(containsPath("root/path/to/one"))); + expect(paths, isNot(containsPath("root/path/to/two"))); + expect(paths, isNot(containsPath("root/path/to/sub/three"))); }); test("that's not normalized removes and returns the normalized path", () { - set.add("root/path/to/file"); - expect(set.remove("root/../root/path/to/../to/./file"), + paths.add("root/path/to/file"); + expect(paths.remove("root/../root/path/to/../to/./file"), unorderedEquals([p.normalize("root/path/to/file")])); }); test("that's absolute removes and returns the normalized path", () { - set.add("root/path/to/file"); - expect(set.remove(p.absolute("root/path/to/file")), + paths.add("root/path/to/file"); + expect(paths.remove(p.absolute("root/path/to/file")), unorderedEquals([p.normalize("root/path/to/file")])); }); }); group("containsPath()", () { test("returns false for a non-existent path", () { - set.add("root/path/to/file"); - expect(set, isNot(containsPath("root/path/to/nothing"))); + paths.add("root/path/to/file"); + expect(paths, isNot(containsPath("root/path/to/nothing"))); }); test("returns false for a directory that wasn't added explicitly", () { - set.add("root/path/to/file"); - expect(set, isNot(containsPath("root/path"))); + paths.add("root/path/to/file"); + expect(paths, isNot(containsPath("root/path"))); }); test("returns true for a directory that was added explicitly", () { - set.add("root/path"); - set.add("root/path/to/file"); - expect(set, containsPath("root/path")); + paths.add("root/path"); + paths.add("root/path/to/file"); + expect(paths, containsPath("root/path")); }); test("with a non-normalized path normalizes the path before looking it up", () { - set.add("root/path/to/file"); - expect(set, containsPath("root/../root/path/to/../to/././file")); + paths.add("root/path/to/file"); + expect(paths, containsPath("root/../root/path/to/../to/././file")); }); test("with an absolute path normalizes the path before looking it up", () { - set.add("root/path/to/file"); - expect(set, containsPath(p.absolute("root/path/to/file"))); + paths.add("root/path/to/file"); + expect(paths, containsPath(p.absolute("root/path/to/file"))); }); }); group("containsDir()", () { test("returns true for a directory that was added implicitly", () { - set.add("root/path/to/file"); - expect(set, containsDir("root/path")); - expect(set, containsDir("root/path/to")); + paths.add("root/path/to/file"); + expect(paths, containsDir("root/path")); + expect(paths, containsDir("root/path/to")); }); test("returns true for a directory that was added explicitly", () { - set.add("root/path"); - set.add("root/path/to/file"); - expect(set, containsDir("root/path")); + paths.add("root/path"); + paths.add("root/path/to/file"); + expect(paths, containsDir("root/path")); }); test("returns false for a directory that wasn't added", () { - expect(set, isNot(containsDir("root/nothing"))); + expect(paths, isNot(containsDir("root/nothing"))); }); test("returns false for a non-directory path that was added", () { - set.add("root/path/to/file"); - expect(set, isNot(containsDir("root/path/to/file"))); + paths.add("root/path/to/file"); + expect(paths, isNot(containsDir("root/path/to/file"))); }); test( "returns false for a directory that was added implicitly and then " "removed implicitly", () { - set.add("root/path/to/file"); - set.remove("root/path/to/file"); - expect(set, isNot(containsDir("root/path"))); + paths.add("root/path/to/file"); + paths.remove("root/path/to/file"); + expect(paths, isNot(containsDir("root/path"))); }); test( "returns false for a directory that was added explicitly whose " "children were then removed", () { - set.add("root/path"); - set.add("root/path/to/file"); - set.remove("root/path/to/file"); - expect(set, isNot(containsDir("root/path"))); + paths.add("root/path"); + paths.add("root/path/to/file"); + paths.remove("root/path/to/file"); + expect(paths, isNot(containsDir("root/path"))); }); test("with a non-normalized path normalizes the path before looking it up", () { - set.add("root/path/to/file"); - expect(set, containsDir("root/../root/path/to/../to/.")); + paths.add("root/path/to/file"); + expect(paths, containsDir("root/../root/path/to/../to/.")); }); test("with an absolute path normalizes the path before looking it up", () { - set.add("root/path/to/file"); - expect(set, containsDir(p.absolute("root/path"))); + paths.add("root/path/to/file"); + expect(paths, containsDir(p.absolute("root/path"))); }); }); group("paths", () { test("returns paths added to the set", () { - set.add("root/path"); - set.add("root/path/to/one"); - set.add("root/path/to/two"); + paths.add("root/path"); + paths.add("root/path/to/one"); + paths.add("root/path/to/two"); expect( - set.paths, + paths.paths, unorderedEquals([ "root/path", "root/path/to/one", @@ -206,22 +207,22 @@ void main() { }); test("doesn't return paths removed from the set", () { - set.add("root/path/to/one"); - set.add("root/path/to/two"); - set.remove("root/path/to/two"); + paths.add("root/path/to/one"); + paths.add("root/path/to/two"); + paths.remove("root/path/to/two"); - expect(set.paths, unorderedEquals([p.normalize("root/path/to/one")])); + expect(paths.paths, unorderedEquals([p.normalize("root/path/to/one")])); }); }); group("clear", () { test("removes all paths from the set", () { - set.add("root/path"); - set.add("root/path/to/one"); - set.add("root/path/to/two"); + paths.add("root/path"); + paths.add("root/path/to/one"); + paths.add("root/path/to/two"); - set.clear(); - expect(set.paths, isEmpty); + paths.clear(); + expect(paths.paths, isEmpty); }); }); } diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 637eacff7..7462c991a 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -35,10 +35,6 @@ WatcherFactory _watcherFactory; /// Creates a new [Watcher] that watches a temporary file or directory. /// -/// 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. -/// /// If [path] is provided, watches a subdirectory in the sandbox with that name. Watcher createWatcher({String path}) { if (path == null) { @@ -202,7 +198,7 @@ Future allowRemoveEvent(String path) => /// Schedules writing a file in the sandbox at [path] with [contents]. /// -/// If [contents] is omitted, creates an empty file. If [updatedModified] is +/// If [contents] is omitted, creates an empty file. If [updateModified] is /// `false`, the mock file modification time is not changed. void writeFile(String path, {String contents, bool updateModified}) { if (contents == null) contents = ""; @@ -232,8 +228,6 @@ void deleteFile(String path) { } /// Schedules renaming a file in the sandbox from [from] to [to]. -/// -/// If [contents] is omitted, creates an empty file. void renameFile(String from, String to) { new File(p.join(d.sandbox, from)).renameSync(p.join(d.sandbox, to)); @@ -259,8 +253,8 @@ void deleteDir(String path) { new Directory(p.join(d.sandbox, path)).deleteSync(recursive: true); } -/// Runs [callback] with every permutation of non-negative [i], [j], and [k] -/// less than [limit]. +/// Runs [callback] with every permutation of non-negative numbers for each +/// argument less than [limit]. /// /// Returns a set of all values returns by [callback]. /// From e9823fa18490b3bdddff667cadd3f91aa5017137 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Mon, 10 Sep 2018 17:46:46 -0700 Subject: [PATCH 116/201] Run dartfmt --fix (dart-lang/watcher#65) Drops optional `new` and `const` Bump SDK constraint to `2.0.0` to ensure compatibility with omitted `new`. --- pkgs/watcher/benchmark/path_set.dart | 12 ++-- pkgs/watcher/example/watch.dart | 2 +- pkgs/watcher/lib/src/async_queue.dart | 2 +- pkgs/watcher/lib/src/directory_watcher.dart | 8 +-- .../lib/src/directory_watcher/linux.dart | 34 +++++----- .../lib/src/directory_watcher/mac_os.dart | 64 +++++++++---------- .../lib/src/directory_watcher/polling.dart | 24 +++---- .../lib/src/directory_watcher/windows.dart | 64 +++++++++---------- pkgs/watcher/lib/src/file_watcher.dart | 4 +- pkgs/watcher/lib/src/file_watcher/native.dart | 19 +++--- .../watcher/lib/src/file_watcher/polling.dart | 16 ++--- pkgs/watcher/lib/src/path_set.dart | 14 ++-- pkgs/watcher/lib/src/resubscribable.dart | 6 +- pkgs/watcher/lib/src/stat.dart | 2 +- pkgs/watcher/lib/src/utils.dart | 6 +- pkgs/watcher/lib/src/watch_event.dart | 6 +- pkgs/watcher/lib/watcher.dart | 6 +- pkgs/watcher/pubspec.yaml | 2 +- .../test/directory_watcher/linux_test.dart | 4 +- .../test/directory_watcher/mac_os_test.dart | 4 +- .../test/directory_watcher/polling_test.dart | 4 +- .../test/directory_watcher/shared.dart | 4 +- .../test/directory_watcher/windows_test.dart | 5 +- .../test/file_watcher/native_test.dart | 2 +- .../test/file_watcher/polling_test.dart | 4 +- pkgs/watcher/test/file_watcher/shared.dart | 2 +- .../test/no_subscription/linux_test.dart | 2 +- .../test/no_subscription/mac_os_test.dart | 2 +- .../test/no_subscription/polling_test.dart | 2 +- pkgs/watcher/test/no_subscription/shared.dart | 4 +- pkgs/watcher/test/path_set_test.dart | 2 +- pkgs/watcher/test/ready/linux_test.dart | 2 +- pkgs/watcher/test/ready/mac_os_test.dart | 2 +- pkgs/watcher/test/ready/polling_test.dart | 2 +- pkgs/watcher/test/ready/shared.dart | 2 +- pkgs/watcher/test/utils.dart | 22 +++---- 36 files changed, 176 insertions(+), 186 deletions(-) diff --git a/pkgs/watcher/benchmark/path_set.dart b/pkgs/watcher/benchmark/path_set.dart index bf9f5fc7c..8a5b843ef 100644 --- a/pkgs/watcher/benchmark/path_set.dart +++ b/pkgs/watcher/benchmark/path_set.dart @@ -19,11 +19,11 @@ final String root = Platform.isWindows ? r"C:\root" : "/root"; abstract class PathSetBenchmark extends BenchmarkBase { PathSetBenchmark(String method) : super("PathSet.$method"); - final PathSet pathSet = new PathSet(root); + final PathSet pathSet = PathSet(root); /// Use a fixed [Random] with a constant seed to ensure the tests are /// deterministic. - final math.Random random = new math.Random(1234); + final math.Random random = math.Random(1234); /// Walks over a virtual directory [depth] levels deep invoking [callback] /// for each "file". @@ -140,8 +140,8 @@ class RemoveBenchmark extends PathSetBenchmark { } main() { - new AddBenchmark().report(); - new ContainsBenchmark().report(); - new PathsBenchmark().report(); - new RemoveBenchmark().report(); + AddBenchmark().report(); + ContainsBenchmark().report(); + PathsBenchmark().report(); + RemoveBenchmark().report(); } diff --git a/pkgs/watcher/example/watch.dart b/pkgs/watcher/example/watch.dart index da3c2633c..1477e4282 100644 --- a/pkgs/watcher/example/watch.dart +++ b/pkgs/watcher/example/watch.dart @@ -14,7 +14,7 @@ main(List arguments) { return; } - var watcher = new DirectoryWatcher(p.absolute(arguments[0])); + var watcher = DirectoryWatcher(p.absolute(arguments[0])); watcher.events.listen((event) { print(event); }); diff --git a/pkgs/watcher/lib/src/async_queue.dart b/pkgs/watcher/lib/src/async_queue.dart index 1904192b4..de7b7188a 100644 --- a/pkgs/watcher/lib/src/async_queue.dart +++ b/pkgs/watcher/lib/src/async_queue.dart @@ -17,7 +17,7 @@ typedef Future ItemProcessor(T item); /// needed. When all items are processed, it stops processing until more items /// are added. class AsyncQueue { - final _items = new Queue(); + final _items = Queue(); /// Whether or not the queue is currently waiting on a processing future to /// complete. diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 6beebd0a4..8c52ed97a 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -29,10 +29,10 @@ abstract class DirectoryWatcher implements Watcher { /// watchers. factory DirectoryWatcher(String directory, {Duration pollingDelay}) { if (FileSystemEntity.isWatchSupported) { - if (Platform.isLinux) return new LinuxDirectoryWatcher(directory); - if (Platform.isMacOS) return new MacOSDirectoryWatcher(directory); - if (Platform.isWindows) return new WindowsDirectoryWatcher(directory); + if (Platform.isLinux) return LinuxDirectoryWatcher(directory); + if (Platform.isMacOS) return MacOSDirectoryWatcher(directory); + if (Platform.isWindows) return WindowsDirectoryWatcher(directory); } - return new PollingDirectoryWatcher(directory, pollingDelay: pollingDelay); + return PollingDirectoryWatcher(directory, pollingDelay: pollingDelay); } } diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index 5eeed2327..f3866c6c6 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -28,7 +28,7 @@ class LinuxDirectoryWatcher extends ResubscribableWatcher String get directory => path; LinuxDirectoryWatcher(String directory) - : super(directory, () => new _LinuxDirectoryWatcher(directory)); + : super(directory, () => _LinuxDirectoryWatcher(directory)); } class _LinuxDirectoryWatcher @@ -37,16 +37,16 @@ class _LinuxDirectoryWatcher String get path => _files.root; Stream get events => _eventsController.stream; - final _eventsController = new StreamController.broadcast(); + final _eventsController = StreamController.broadcast(); bool get isReady => _readyCompleter.isCompleted; Future get ready => _readyCompleter.future; - final _readyCompleter = new Completer(); + final _readyCompleter = Completer(); /// A stream group for the [Directory.watch] events of [path] and all its /// subdirectories. - var _nativeEvents = new StreamGroup(); + var _nativeEvents = StreamGroup(); /// All known files recursively within [path]. final PathSet _files; @@ -60,12 +60,12 @@ class _LinuxDirectoryWatcher /// /// These are gathered together so that they may all be canceled when the /// watcher is closed. - final _subscriptions = new Set(); + final _subscriptions = Set(); - _LinuxDirectoryWatcher(String path) : _files = new PathSet(path) { - _nativeEvents.add(new Directory(path) + _LinuxDirectoryWatcher(String path) : _files = PathSet(path) { + _nativeEvents.add(Directory(path) .watch() - .transform(new StreamTransformer.fromHandlers(handleDone: (sink) { + .transform(StreamTransformer.fromHandlers(handleDone: (sink) { // Handle the done event here rather than in the call to [_listen] because // [innerStream] won't close until we close the [StreamGroup]. However, if // we close the [StreamGroup] here, we run the risk of new-directory @@ -76,11 +76,10 @@ class _LinuxDirectoryWatcher // Batch the inotify changes together so that we can dedup events. var innerStream = _nativeEvents.stream - .transform(new BatchedStreamTransformer()); + .transform(BatchedStreamTransformer()); _listen(innerStream, _onBatch, onError: _eventsController.addError); - _listen(new Directory(path).list(recursive: true), - (FileSystemEntity entity) { + _listen(Directory(path).list(recursive: true), (FileSystemEntity entity) { if (entity is Directory) { _watchSubdir(entity.path); } else { @@ -122,16 +121,16 @@ class _LinuxDirectoryWatcher // TODO(nweiz): Catch any errors here that indicate that the directory in // question doesn't exist and silently stop watching it instead of // propagating the errors. - var stream = new Directory(path).watch(); + var stream = Directory(path).watch(); _subdirStreams[path] = stream; _nativeEvents.add(stream); } /// The callback that's run when a batch of changes comes in. void _onBatch(List batch) { - var files = new Set(); - var dirs = new Set(); - var changed = new Set(); + var files = Set(); + var dirs = Set(); + var changed = Set(); // inotify event batches are ordered by occurrence, so we treat them as a // log of what happened to a file. We only emit events based on the @@ -208,8 +207,7 @@ class _LinuxDirectoryWatcher /// Emits [ChangeType.ADD] events for the recursive contents of [path]. void _addSubdir(String path) { - _listen(new Directory(path).list(recursive: true), - (FileSystemEntity entity) { + _listen(Directory(path).list(recursive: true), (FileSystemEntity entity) { if (entity is Directory) { _watchSubdir(entity.path); } else { @@ -247,7 +245,7 @@ class _LinuxDirectoryWatcher void _emit(ChangeType type, String path) { if (!isReady) return; if (_eventsController.isClosed) return; - _eventsController.add(new WatchEvent(type, path)); + _eventsController.add(WatchEvent(type, path)); } /// Like [Stream.listen], but automatically adds the subscription to diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 61531c5d9..f593fbd51 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -27,7 +27,7 @@ class MacOSDirectoryWatcher extends ResubscribableWatcher String get directory => path; MacOSDirectoryWatcher(String directory) - : super(directory, () => new _MacOSDirectoryWatcher(directory)); + : super(directory, () => _MacOSDirectoryWatcher(directory)); } class _MacOSDirectoryWatcher @@ -36,12 +36,12 @@ class _MacOSDirectoryWatcher final String path; Stream get events => _eventsController.stream; - final _eventsController = new StreamController.broadcast(); + final _eventsController = StreamController.broadcast(); bool get isReady => _readyCompleter.isCompleted; Future get ready => _readyCompleter.future; - final _readyCompleter = new Completer(); + final _readyCompleter = Completer(); /// The set of files that are known to exist recursively within the watched /// directory. @@ -64,7 +64,7 @@ class _MacOSDirectoryWatcher /// The subscriptions to [Directory.list] calls for listing the contents of a /// subdirectory that was moved into the watched directory. - final _listSubscriptions = new Set>(); + final _listSubscriptions = Set>(); /// The timer for tracking how long we wait for an initial batch of bogus /// events (see issue 14373). @@ -72,7 +72,7 @@ class _MacOSDirectoryWatcher _MacOSDirectoryWatcher(String path) : path = path, - _files = new PathSet(path) { + _files = PathSet(path) { _startWatch(); // Before we're ready to emit events, wait for [_listDir] to complete and @@ -136,8 +136,7 @@ class _MacOSDirectoryWatcher if (_files.containsDir(path)) continue; StreamSubscription subscription; - subscription = - new Directory(path).list(recursive: true).listen((entity) { + subscription = Directory(path).list(recursive: true).listen((entity) { if (entity is Directory) return; if (_files.contains(path)) return; @@ -182,11 +181,11 @@ class _MacOSDirectoryWatcher // directory's full contents will be examined anyway, so we ignore such // events. Emitting them could cause useless or out-of-order events. var directories = unionAll(batch.map((event) { - if (!event.isDirectory) return new Set(); + if (!event.isDirectory) return Set(); if (event is FileSystemMoveEvent) { - return new Set.from([event.path, event.destination]); + return Set.from([event.path, event.destination]); } - return new Set.from([event.path]); + return Set.from([event.path]); })); isInModifiedDirectory(String path) => @@ -194,9 +193,7 @@ class _MacOSDirectoryWatcher addEvent(String path, FileSystemEvent event) { if (isInModifiedDirectory(path)) return; - eventsForPaths - .putIfAbsent(path, () => new Set()) - .add(event); + eventsForPaths.putIfAbsent(path, () => Set()).add(event); } for (var event in batch) { @@ -271,11 +268,11 @@ class _MacOSDirectoryWatcher // [_eventsBasedOnFileSystem] will handle this correctly by producing a // DELETE event followed by a CREATE event if the directory exists. if (isDir) return null; - return new ConstructableFileSystemCreateEvent(batch.first.path, false); + return ConstructableFileSystemCreateEvent(batch.first.path, false); case FileSystemEvent.delete: - return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir); + return ConstructableFileSystemDeleteEvent(batch.first.path, isDir); case FileSystemEvent.modify: - return new ConstructableFileSystemModifyEvent( + return ConstructableFileSystemModifyEvent( batch.first.path, isDir, false); default: throw 'unreachable'; @@ -292,32 +289,32 @@ class _MacOSDirectoryWatcher List _eventsBasedOnFileSystem(String path) { var fileExisted = _files.contains(path); var dirExisted = _files.containsDir(path); - var fileExists = new File(path).existsSync(); - var dirExists = new Directory(path).existsSync(); + var fileExists = File(path).existsSync(); + var dirExists = Directory(path).existsSync(); var events = []; if (fileExisted) { if (fileExists) { - events.add(new ConstructableFileSystemModifyEvent(path, false, false)); + events.add(ConstructableFileSystemModifyEvent(path, false, false)); } else { - events.add(new ConstructableFileSystemDeleteEvent(path, false)); + events.add(ConstructableFileSystemDeleteEvent(path, false)); } } else if (dirExisted) { if (dirExists) { // If we got contradictory events for a directory that used to exist and // still exists, we need to rescan the whole thing in case it was // replaced with a different directory. - events.add(new ConstructableFileSystemDeleteEvent(path, true)); - events.add(new ConstructableFileSystemCreateEvent(path, true)); + events.add(ConstructableFileSystemDeleteEvent(path, true)); + events.add(ConstructableFileSystemCreateEvent(path, true)); } else { - events.add(new ConstructableFileSystemDeleteEvent(path, true)); + events.add(ConstructableFileSystemDeleteEvent(path, true)); } } if (!fileExisted && fileExists) { - events.add(new ConstructableFileSystemCreateEvent(path, false)); + events.add(ConstructableFileSystemCreateEvent(path, false)); } else if (!dirExisted && dirExists) { - events.add(new ConstructableFileSystemCreateEvent(path, true)); + events.add(ConstructableFileSystemCreateEvent(path, true)); } return events; @@ -330,7 +327,7 @@ class _MacOSDirectoryWatcher // If the directory still exists and we're still expecting bogus events, // this is probably issue 14849 rather than a real close event. We should // just restart the watcher. - if (!isReady && new Directory(path).existsSync()) { + if (!isReady && Directory(path).existsSync()) { _startWatch(); return; } @@ -348,9 +345,9 @@ class _MacOSDirectoryWatcher /// Start or restart the underlying [Directory.watch] stream. void _startWatch() { // Batch the FSEvent changes together so that we can dedup events. - var innerStream = new Directory(path) + var innerStream = Directory(path) .watch(recursive: true) - .transform(new BatchedStreamTransformer()); + .transform(BatchedStreamTransformer()); _watchSubscription = innerStream.listen(_onBatch, onError: _eventsController.addError, onDone: _onDone); } @@ -362,8 +359,8 @@ class _MacOSDirectoryWatcher if (_initialListSubscription != null) _initialListSubscription.cancel(); _files.clear(); - var completer = new Completer(); - var stream = new Directory(path).list(recursive: true); + var completer = Completer(); + var stream = Directory(path).list(recursive: true); _initialListSubscription = stream.listen((entity) { if (entity is! Directory) _files.add(entity.path); }, onError: _emitError, onDone: completer.complete, cancelOnError: true); @@ -376,16 +373,15 @@ class _MacOSDirectoryWatcher /// watcher tests take on the bots, so it should be safe to assume that any /// bogus events will be signaled in that time frame. Future _waitForBogusEvents() { - var completer = new Completer(); - _bogusEventTimer = - new Timer(new Duration(milliseconds: 200), completer.complete); + var completer = Completer(); + _bogusEventTimer = Timer(Duration(milliseconds: 200), completer.complete); return completer.future; } /// Emit an event with the given [type] and [path]. void _emitEvent(ChangeType type, String path) { if (!isReady) return; - _eventsController.add(new WatchEvent(type, path)); + _eventsController.add(WatchEvent(type, path)); } /// Emit an error, then close the watcher. diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 790d0b982..735a80722 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -25,8 +25,8 @@ class PollingDirectoryWatcher extends ResubscribableWatcher /// and higher CPU usage. Defaults to one second. PollingDirectoryWatcher(String directory, {Duration pollingDelay}) : super(directory, () { - return new _PollingDirectoryWatcher(directory, - pollingDelay != null ? pollingDelay : new Duration(seconds: 1)); + return _PollingDirectoryWatcher(directory, + pollingDelay != null ? pollingDelay : Duration(seconds: 1)); }); } @@ -36,12 +36,12 @@ class _PollingDirectoryWatcher final String path; Stream get events => _events.stream; - final _events = new StreamController.broadcast(); + final _events = StreamController.broadcast(); bool get isReady => _ready.isCompleted; Future get ready => _ready.future; - final _ready = new Completer(); + final _ready = Completer(); /// The amount of time the watcher pauses between successive polls of the /// directory contents. @@ -50,7 +50,7 @@ class _PollingDirectoryWatcher /// The previous modification times of the files in the directory. /// /// Used to tell which files have been modified. - final _lastModifieds = new Map(); + final _lastModifieds = Map(); /// The subscription used while [directory] is being listed. /// @@ -70,11 +70,11 @@ class _PollingDirectoryWatcher /// /// Used to tell which files have been removed: files that are in /// [_lastModifieds] but not in here when a poll completes have been removed. - final _polledFiles = new Set(); + final _polledFiles = Set(); _PollingDirectoryWatcher(this.path, this._pollingDelay) { - _filesToProcess = new AsyncQueue(_processFile, - onError: (e, StackTrace stackTrace) { + _filesToProcess = + AsyncQueue(_processFile, onError: (e, StackTrace stackTrace) { if (!_events.isClosed) _events.addError(e, stackTrace); }); @@ -107,7 +107,7 @@ class _PollingDirectoryWatcher _filesToProcess.add(null); } - var stream = new Directory(path).list(recursive: true); + var stream = Directory(path).list(recursive: true); _listSubscription = stream.listen((entity) { assert(!_events.isClosed); @@ -154,7 +154,7 @@ class _PollingDirectoryWatcher if (!isReady) return null; var type = lastModified == null ? ChangeType.ADD : ChangeType.MODIFY; - _events.add(new WatchEvent(type, file)); + _events.add(WatchEvent(type, file)); }); } @@ -165,14 +165,14 @@ class _PollingDirectoryWatcher // status for must have been removed. var removedFiles = _lastModifieds.keys.toSet().difference(_polledFiles); for (var removed in removedFiles) { - if (isReady) _events.add(new WatchEvent(ChangeType.REMOVE, removed)); + if (isReady) _events.add(WatchEvent(ChangeType.REMOVE, removed)); _lastModifieds.remove(removed); } if (!isReady) _ready.complete(); // Wait and then poll again. - return new Future.delayed(_pollingDelay).then((_) { + return Future.delayed(_pollingDelay).then((_) { if (_events.isClosed) return; _poll(); }); diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index baeaf233a..021493995 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -21,11 +21,11 @@ class WindowsDirectoryWatcher extends ResubscribableWatcher String get directory => path; WindowsDirectoryWatcher(String directory) - : super(directory, () => new _WindowsDirectoryWatcher(directory)); + : super(directory, () => _WindowsDirectoryWatcher(directory)); } class _EventBatcher { - static const Duration _BATCH_DELAY = const Duration(milliseconds: 100); + static const Duration _BATCH_DELAY = Duration(milliseconds: 100); final List events = []; Timer timer; @@ -34,7 +34,7 @@ class _EventBatcher { if (timer != null) { timer.cancel(); } - timer = new Timer(_BATCH_DELAY, callback); + timer = Timer(_BATCH_DELAY, callback); } void cancelTimer() { @@ -48,15 +48,15 @@ class _WindowsDirectoryWatcher final String path; Stream get events => _eventsController.stream; - final _eventsController = new StreamController.broadcast(); + final _eventsController = StreamController.broadcast(); bool get isReady => _readyCompleter.isCompleted; Future get ready => _readyCompleter.future; - final _readyCompleter = new Completer(); + final _readyCompleter = Completer(); final Map _eventBatchers = - new HashMap(); + HashMap(); /// The set of files that are known to exist recursively within the watched /// directory. @@ -81,11 +81,11 @@ class _WindowsDirectoryWatcher /// The subscriptions to the [Directory.list] calls for listing the contents /// of subdirectories that were moved into the watched directory. final Set> _listSubscriptions = - new HashSet>(); + HashSet>(); _WindowsDirectoryWatcher(String path) : path = path, - _files = new PathSet(path) { + _files = PathSet(path) { // Before we're ready to emit events, wait for [_listDir] to complete. _listDir().then((_) { _startWatch(); @@ -121,7 +121,7 @@ class _WindowsDirectoryWatcher var parent = p.dirname(absoluteDir); // Check if [path] is already the root directory. if (FileSystemEntity.identicalSync(parent, path)) return; - var parentStream = new Directory(parent).watch(recursive: false); + var parentStream = Directory(parent).watch(recursive: false); _parentWatchSubscription = parentStream.listen((event) { // Only look at events for 'directory'. if (p.basename(event.path) != p.basename(absoluteDir)) return; @@ -151,7 +151,7 @@ class _WindowsDirectoryWatcher void _onEvent(FileSystemEvent event) { assert(isReady); final batcher = - _eventBatchers.putIfAbsent(event.path, () => new _EventBatcher()); + _eventBatchers.putIfAbsent(event.path, () => _EventBatcher()); batcher.addEvent(event, () { _eventBatchers.remove(event.path); _onBatch(batcher.events); @@ -178,7 +178,7 @@ class _WindowsDirectoryWatcher if (_files.containsDir(path)) continue; - var stream = new Directory(path).list(recursive: true); + var stream = Directory(path).list(recursive: true); StreamSubscription subscription; subscription = stream.listen((entity) { if (entity is Directory) return; @@ -222,11 +222,11 @@ class _WindowsDirectoryWatcher // directory's full contents will be examined anyway, so we ignore such // events. Emitting them could cause useless or out-of-order events. var directories = unionAll(batch.map((event) { - if (!event.isDirectory) return new Set(); + if (!event.isDirectory) return Set(); if (event is FileSystemMoveEvent) { - return new Set.from([event.path, event.destination]); + return Set.from([event.path, event.destination]); } - return new Set.from([event.path]); + return Set.from([event.path]); })); isInModifiedDirectory(String path) => @@ -234,9 +234,7 @@ class _WindowsDirectoryWatcher addEvent(String path, FileSystemEvent event) { if (isInModifiedDirectory(path)) return; - eventsForPaths - .putIfAbsent(path, () => new Set()) - .add(event); + eventsForPaths.putIfAbsent(path, () => Set()).add(event); } for (var event in batch) { @@ -297,11 +295,11 @@ class _WindowsDirectoryWatcher switch (type) { case FileSystemEvent.create: - return new ConstructableFileSystemCreateEvent(batch.first.path, isDir); + return ConstructableFileSystemCreateEvent(batch.first.path, isDir); case FileSystemEvent.delete: - return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir); + return ConstructableFileSystemDeleteEvent(batch.first.path, isDir); case FileSystemEvent.modify: - return new ConstructableFileSystemModifyEvent( + return ConstructableFileSystemModifyEvent( batch.first.path, isDir, false); case FileSystemEvent.move: return null; @@ -320,32 +318,32 @@ class _WindowsDirectoryWatcher List _eventsBasedOnFileSystem(String path) { var fileExisted = _files.contains(path); var dirExisted = _files.containsDir(path); - var fileExists = new File(path).existsSync(); - var dirExists = new Directory(path).existsSync(); + var fileExists = File(path).existsSync(); + var dirExists = Directory(path).existsSync(); var events = []; if (fileExisted) { if (fileExists) { - events.add(new ConstructableFileSystemModifyEvent(path, false, false)); + events.add(ConstructableFileSystemModifyEvent(path, false, false)); } else { - events.add(new ConstructableFileSystemDeleteEvent(path, false)); + events.add(ConstructableFileSystemDeleteEvent(path, false)); } } else if (dirExisted) { if (dirExists) { // If we got contradictory events for a directory that used to exist and // still exists, we need to rescan the whole thing in case it was // replaced with a different directory. - events.add(new ConstructableFileSystemDeleteEvent(path, true)); - events.add(new ConstructableFileSystemCreateEvent(path, true)); + events.add(ConstructableFileSystemDeleteEvent(path, true)); + events.add(ConstructableFileSystemCreateEvent(path, true)); } else { - events.add(new ConstructableFileSystemDeleteEvent(path, true)); + events.add(ConstructableFileSystemDeleteEvent(path, true)); } } if (!fileExisted && fileExists) { - events.add(new ConstructableFileSystemCreateEvent(path, false)); + events.add(ConstructableFileSystemCreateEvent(path, false)); } else if (!dirExisted && dirExists) { - events.add(new ConstructableFileSystemCreateEvent(path, true)); + events.add(ConstructableFileSystemCreateEvent(path, true)); } return events; @@ -368,7 +366,7 @@ class _WindowsDirectoryWatcher /// Start or restart the underlying [Directory.watch] stream. void _startWatch() { // Batch the events together so that we can dedup events. - var innerStream = new Directory(path).watch(recursive: true); + var innerStream = Directory(path).watch(recursive: true); _watchSubscription = innerStream.listen(_onEvent, onError: _eventsController.addError, onDone: _onDone); } @@ -380,8 +378,8 @@ class _WindowsDirectoryWatcher if (_initialListSubscription != null) _initialListSubscription.cancel(); _files.clear(); - var completer = new Completer(); - var stream = new Directory(path).list(recursive: true); + var completer = Completer(); + var stream = Directory(path).list(recursive: true); void handleEntity(FileSystemEntity entity) { if (entity is! Directory) _files.add(entity.path); } @@ -395,7 +393,7 @@ class _WindowsDirectoryWatcher void _emitEvent(ChangeType type, String path) { if (!isReady) return; - _eventsController.add(new WatchEvent(type, path)); + _eventsController.add(WatchEvent(type, path)); } /// Emit an error, then close the watcher. diff --git a/pkgs/watcher/lib/src/file_watcher.dart b/pkgs/watcher/lib/src/file_watcher.dart index 09065bc8b..c4abddd6c 100644 --- a/pkgs/watcher/lib/src/file_watcher.dart +++ b/pkgs/watcher/lib/src/file_watcher.dart @@ -33,8 +33,8 @@ abstract class FileWatcher implements Watcher { // [FileSystemEntity.isWatchSupported] is still true because directory // watching does work. if (FileSystemEntity.isWatchSupported && !Platform.isWindows) { - return new NativeFileWatcher(file); + return NativeFileWatcher(file); } - return new PollingFileWatcher(file, pollingDelay: pollingDelay); + return PollingFileWatcher(file, pollingDelay: pollingDelay); } } diff --git a/pkgs/watcher/lib/src/file_watcher/native.dart b/pkgs/watcher/lib/src/file_watcher/native.dart index 5f56f739f..ff25eb74e 100644 --- a/pkgs/watcher/lib/src/file_watcher/native.dart +++ b/pkgs/watcher/lib/src/file_watcher/native.dart @@ -15,20 +15,19 @@ import '../watch_event.dart'; /// Single-file notifications are much simpler than those for multiple files, so /// this doesn't need to be split out into multiple OS-specific classes. class NativeFileWatcher extends ResubscribableWatcher implements FileWatcher { - NativeFileWatcher(String path) - : super(path, () => new _NativeFileWatcher(path)); + NativeFileWatcher(String path) : super(path, () => _NativeFileWatcher(path)); } class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { final String path; Stream get events => _eventsController.stream; - final _eventsController = new StreamController.broadcast(); + final _eventsController = StreamController.broadcast(); bool get isReady => _readyCompleter.isCompleted; Future get ready => _readyCompleter.future; - final _readyCompleter = new Completer(); + final _readyCompleter = Completer(); StreamSubscription _subscription; @@ -42,9 +41,9 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { void _listen() { // Batch the events together so that we can dedup them. - _subscription = new File(path) + _subscription = File(path) .watch() - .transform(new BatchedStreamTransformer()) + .transform(BatchedStreamTransformer()) .listen(_onBatch, onError: _eventsController.addError, onDone: _onDone); } @@ -55,11 +54,11 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { return; } - _eventsController.add(new WatchEvent(ChangeType.MODIFY, path)); + _eventsController.add(WatchEvent(ChangeType.MODIFY, path)); } _onDone() async { - var fileExists = await new File(path).exists(); + var fileExists = await File(path).exists(); // Check for this after checking whether the file exists because it's // possible that [close] was called between [File.exists] being called and @@ -70,10 +69,10 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { // If the file exists now, it was probably removed and quickly replaced; // this can happen for example when another file is moved on top of it. // Re-subscribe and report a modify event. - _eventsController.add(new WatchEvent(ChangeType.MODIFY, path)); + _eventsController.add(WatchEvent(ChangeType.MODIFY, path)); _listen(); } else { - _eventsController.add(new WatchEvent(ChangeType.REMOVE, path)); + _eventsController.add(WatchEvent(ChangeType.REMOVE, path)); close(); } } diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart index 960b11b9b..777038fdf 100644 --- a/pkgs/watcher/lib/src/file_watcher/polling.dart +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -14,8 +14,8 @@ import '../watch_event.dart'; class PollingFileWatcher extends ResubscribableWatcher implements FileWatcher { PollingFileWatcher(String path, {Duration pollingDelay}) : super(path, () { - return new _PollingFileWatcher(path, - pollingDelay != null ? pollingDelay : new Duration(seconds: 1)); + return _PollingFileWatcher( + path, pollingDelay != null ? pollingDelay : Duration(seconds: 1)); }); } @@ -23,12 +23,12 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { final String path; Stream get events => _eventsController.stream; - final _eventsController = new StreamController.broadcast(); + final _eventsController = StreamController.broadcast(); bool get isReady => _readyCompleter.isCompleted; Future get ready => _readyCompleter.future; - final _readyCompleter = new Completer(); + final _readyCompleter = Completer(); /// The timer that controls polling. Timer _timer; @@ -40,7 +40,7 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { DateTime _lastModified; _PollingFileWatcher(this.path, Duration pollingDelay) { - _timer = new Timer.periodic(pollingDelay, (_) => _poll()); + _timer = Timer.periodic(pollingDelay, (_) => _poll()); _poll(); } @@ -49,11 +49,11 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { // We don't mark the file as removed if this is the first poll (indicated by // [_lastModified] being null). Instead, below we forward the dart:io error // that comes from trying to read the mtime below. - var pathExists = await new File(path).exists(); + var pathExists = await File(path).exists(); if (_eventsController.isClosed) return; if (_lastModified != null && !pathExists) { - _eventsController.add(new WatchEvent(ChangeType.REMOVE, path)); + _eventsController.add(WatchEvent(ChangeType.REMOVE, path)); close(); return; } @@ -80,7 +80,7 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { _readyCompleter.complete(); } else { _lastModified = modified; - _eventsController.add(new WatchEvent(ChangeType.MODIFY, path)); + _eventsController.add(WatchEvent(ChangeType.MODIFY, path)); } } diff --git a/pkgs/watcher/lib/src/path_set.dart b/pkgs/watcher/lib/src/path_set.dart index 77737f82e..9a2c03cc1 100644 --- a/pkgs/watcher/lib/src/path_set.dart +++ b/pkgs/watcher/lib/src/path_set.dart @@ -22,7 +22,7 @@ class PathSet { /// Each entry represents a directory or file. It may be a file or directory /// that was explicitly added, or a parent directory that was implicitly /// added in order to add a child. - final _Entry _entries = new _Entry(); + final _Entry _entries = _Entry(); PathSet(this.root); @@ -33,7 +33,7 @@ class PathSet { var parts = p.split(path); var entry = _entries; for (var part in parts) { - entry = entry.contents.putIfAbsent(part, () => new _Entry()); + entry = entry.contents.putIfAbsent(part, () => _Entry()); } entry.isExplicit = true; @@ -49,7 +49,7 @@ class PathSet { /// empty set. Set remove(String path) { path = _normalize(path); - var parts = new Queue.of(p.split(path)); + var parts = Queue.of(p.split(path)); // Remove the children of [dir], as well as [dir] itself if necessary. // @@ -61,7 +61,7 @@ class PathSet { // the next level. var part = parts.removeFirst(); var entry = dir.contents[part]; - if (entry == null || entry.contents.isEmpty) return new Set(); + if (entry == null || entry.contents.isEmpty) return Set(); partialPath = p.join(partialPath, part); var paths = recurse(entry, partialPath); @@ -75,10 +75,10 @@ class PathSet { // If there's only one component left in [path], we should remove it. var entry = dir.contents.remove(parts.first); - if (entry == null) return new Set(); + if (entry == null) return Set(); if (entry.contents.isEmpty) { - return new Set.from([p.join(root, path)]); + return Set.from([p.join(root, path)]); } var set = _explicitPathsWithin(entry, path); @@ -96,7 +96,7 @@ class PathSet { /// /// [dirPath] should be the path to [dir]. Set _explicitPathsWithin(_Entry dir, String dirPath) { - var paths = new Set(); + var paths = Set(); recurse(_Entry dir, String path) { dir.contents.forEach((name, entry) { var entryPath = p.join(path, name); diff --git a/pkgs/watcher/lib/src/resubscribable.dart b/pkgs/watcher/lib/src/resubscribable.dart index e96b918dc..e00ebb0d1 100644 --- a/pkgs/watcher/lib/src/resubscribable.dart +++ b/pkgs/watcher/lib/src/resubscribable.dart @@ -32,7 +32,7 @@ abstract class ResubscribableWatcher implements Watcher { bool get isReady => _readyCompleter.isCompleted; Future get ready => _readyCompleter.future; - var _readyCompleter = new Completer(); + var _readyCompleter = Completer(); /// Creates a new [ResubscribableWatcher] wrapping the watchers /// emitted by [_factory]. @@ -40,7 +40,7 @@ abstract class ResubscribableWatcher implements Watcher { ManuallyClosedWatcher watcher; StreamSubscription subscription; - _eventsController = new StreamController.broadcast( + _eventsController = StreamController.broadcast( onListen: () { watcher = _factory(); subscription = watcher.events.listen(_eventsController.add, @@ -57,7 +57,7 @@ abstract class ResubscribableWatcher implements Watcher { // watcher's `onDone` event doesn't close [events]. subscription.cancel(); watcher.close(); - _readyCompleter = new Completer(); + _readyCompleter = Completer(); }, sync: true); } diff --git a/pkgs/watcher/lib/src/stat.dart b/pkgs/watcher/lib/src/stat.dart index 05ee9ba0a..59a5eb6f0 100644 --- a/pkgs/watcher/lib/src/stat.dart +++ b/pkgs/watcher/lib/src/stat.dart @@ -24,7 +24,7 @@ void mockGetModificationTime(MockTimeCallback callback) { /// Gets the modification time for the file at [path]. Future getModificationTime(String path) { if (_mockTimeCallback != null) { - return new Future.value(_mockTimeCallback(path)); + return Future.value(_mockTimeCallback(path)); } return FileStat.stat(path).then((stat) => stat.modified); diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index 30fbaae21..676ae281b 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -18,7 +18,7 @@ bool isDirectoryNotFoundException(error) { /// Returns the union of all elements in each set in [sets]. Set unionAll(Iterable> sets) => - sets.fold(new Set(), (union, set) => union.union(set)); + sets.fold(Set(), (union, set) => union.union(set)); /// A stream transformer that batches all events that are sent at the same time. /// @@ -29,8 +29,8 @@ Set unionAll(Iterable> sets) => /// microtasks. class BatchedStreamTransformer extends StreamTransformerBase> { Stream> bind(Stream input) { - var batch = new Queue(); - return new StreamTransformer>.fromHandlers( + var batch = Queue(); + return StreamTransformer>.fromHandlers( handleData: (event, sink) { batch.add(event); diff --git a/pkgs/watcher/lib/src/watch_event.dart b/pkgs/watcher/lib/src/watch_event.dart index 54093a5d7..94ee5cb89 100644 --- a/pkgs/watcher/lib/src/watch_event.dart +++ b/pkgs/watcher/lib/src/watch_event.dart @@ -18,13 +18,13 @@ class WatchEvent { /// Enum for what kind of change has happened to a file. class ChangeType { /// A new file has been added. - static const ADD = const ChangeType("add"); + static const ADD = ChangeType("add"); /// A file has been removed. - static const REMOVE = const ChangeType("remove"); + static const REMOVE = ChangeType("remove"); /// The contents of a file have changed. - static const MODIFY = const ChangeType("modify"); + static const MODIFY = ChangeType("modify"); final String _name; const ChangeType(this._name); diff --git a/pkgs/watcher/lib/watcher.dart b/pkgs/watcher/lib/watcher.dart index b3cebe665..107ac8fae 100644 --- a/pkgs/watcher/lib/watcher.dart +++ b/pkgs/watcher/lib/watcher.dart @@ -58,10 +58,10 @@ abstract class Watcher { /// and higher CPU usage. Defaults to one second. Ignored for non-polling /// watchers. factory Watcher(String path, {Duration pollingDelay}) { - if (new File(path).existsSync()) { - return new FileWatcher(path, pollingDelay: pollingDelay); + if (File(path).existsSync()) { + return FileWatcher(path, pollingDelay: pollingDelay); } else { - return new DirectoryWatcher(path, pollingDelay: pollingDelay); + return DirectoryWatcher(path, pollingDelay: pollingDelay); } } } diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 8f38a49f6..552b13a9d 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -8,7 +8,7 @@ author: Dart Team homepage: https://github.com/dart-lang/watcher environment: - sdk: '>=2.0.0-dev.61.0 <3.0.0' + sdk: '>=2.0.0 <3.0.0' dependencies: async: '>=1.10.0 <3.0.0' diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart index 744045fcd..0b81919a2 100644 --- a/pkgs/watcher/test/directory_watcher/linux_test.dart +++ b/pkgs/watcher/test/directory_watcher/linux_test.dart @@ -12,12 +12,12 @@ import 'shared.dart'; import '../utils.dart'; void main() { - watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); + watcherFactory = (dir) => LinuxDirectoryWatcher(dir); sharedTests(); test('DirectoryWatcher creates a LinuxDirectoryWatcher on Linux', () { - expect(new DirectoryWatcher('.'), new TypeMatcher()); + expect(DirectoryWatcher('.'), TypeMatcher()); }); test('emits events for many nested files moved out then immediately back in', diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 689d35318..1470f71dc 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -12,12 +12,12 @@ import 'shared.dart'; import '../utils.dart'; void main() { - watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); + watcherFactory = (dir) => MacOSDirectoryWatcher(dir); sharedTests(); test('DirectoryWatcher creates a MacOSDirectoryWatcher on Mac OS', () { - expect(new DirectoryWatcher('.'), new TypeMatcher()); + expect(DirectoryWatcher('.'), TypeMatcher()); }); test( diff --git a/pkgs/watcher/test/directory_watcher/polling_test.dart b/pkgs/watcher/test/directory_watcher/polling_test.dart index 39bbbef0b..d64eb0706 100644 --- a/pkgs/watcher/test/directory_watcher/polling_test.dart +++ b/pkgs/watcher/test/directory_watcher/polling_test.dart @@ -10,8 +10,8 @@ import '../utils.dart'; void main() { // Use a short delay to make the tests run quickly. - watcherFactory = (dir) => new PollingDirectoryWatcher(dir, - pollingDelay: new Duration(milliseconds: 100)); + watcherFactory = (dir) => + PollingDirectoryWatcher(dir, pollingDelay: Duration(milliseconds: 100)); sharedTests(); diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index 2c0f44116..f6e451cbc 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -276,7 +276,7 @@ void sharedTests() { isAddEvent("new") ]); }, onPlatform: { - "mac-os": new Skip("https://github.com/dart-lang/watcher/issues/21") + "mac-os": Skip("https://github.com/dart-lang/watcher/issues/21") }); test('emits events for many nested files added at once', () async { @@ -316,7 +316,7 @@ void sharedTests() { renameDir("dir/old", "dir/new"); await inAnyOrder(unionAll(withPermutations((i, j, k) { - return new Set.from([ + return Set.from([ isRemoveEvent("dir/old/sub-$i/sub-$j/file-$k.txt"), isAddEvent("dir/new/sub-$i/sub-$j/file-$k.txt") ]); diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 875f4ee81..7931fa819 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -12,7 +12,7 @@ import 'shared.dart'; import '../utils.dart'; void main() { - watcherFactory = (dir) => new WindowsDirectoryWatcher(dir); + watcherFactory = (dir) => WindowsDirectoryWatcher(dir); // TODO(grouma) - renable when https://github.com/dart-lang/sdk/issues/31760 // is resolved. @@ -21,7 +21,6 @@ void main() { }, skip: "SDK issue see - https://github.com/dart-lang/sdk/issues/31760"); test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () { - expect( - new DirectoryWatcher('.'), new TypeMatcher()); + expect(DirectoryWatcher('.'), TypeMatcher()); }); } diff --git a/pkgs/watcher/test/file_watcher/native_test.dart b/pkgs/watcher/test/file_watcher/native_test.dart index b6ed901bc..2417dae60 100644 --- a/pkgs/watcher/test/file_watcher/native_test.dart +++ b/pkgs/watcher/test/file_watcher/native_test.dart @@ -11,7 +11,7 @@ import 'shared.dart'; import '../utils.dart'; void main() { - watcherFactory = (file) => new NativeFileWatcher(file); + watcherFactory = (file) => NativeFileWatcher(file); setUp(() { writeFile("file.txt"); diff --git a/pkgs/watcher/test/file_watcher/polling_test.dart b/pkgs/watcher/test/file_watcher/polling_test.dart index 01d579a9f..9492f658e 100644 --- a/pkgs/watcher/test/file_watcher/polling_test.dart +++ b/pkgs/watcher/test/file_watcher/polling_test.dart @@ -11,8 +11,8 @@ import 'shared.dart'; import '../utils.dart'; void main() { - watcherFactory = (file) => new PollingFileWatcher(file, - pollingDelay: new Duration(milliseconds: 100)); + watcherFactory = (file) => + PollingFileWatcher(file, pollingDelay: Duration(milliseconds: 100)); setUp(() { writeFile("file.txt"); diff --git a/pkgs/watcher/test/file_watcher/shared.dart b/pkgs/watcher/test/file_watcher/shared.dart index 286a04210..eefe5dfc4 100644 --- a/pkgs/watcher/test/file_watcher/shared.dart +++ b/pkgs/watcher/test/file_watcher/shared.dart @@ -64,7 +64,7 @@ void sharedTests() { var sub = watcher.events.listen(null); deleteFile("file.txt"); - await new Future.delayed(new Duration(milliseconds: 10)); + await Future.delayed(Duration(milliseconds: 10)); await sub.cancel(); }); } diff --git a/pkgs/watcher/test/no_subscription/linux_test.dart b/pkgs/watcher/test/no_subscription/linux_test.dart index e9bfd69ca..aa5763772 100644 --- a/pkgs/watcher/test/no_subscription/linux_test.dart +++ b/pkgs/watcher/test/no_subscription/linux_test.dart @@ -11,7 +11,7 @@ import 'shared.dart'; import '../utils.dart'; void main() { - watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); + watcherFactory = (dir) => LinuxDirectoryWatcher(dir); sharedTests(); } diff --git a/pkgs/watcher/test/no_subscription/mac_os_test.dart b/pkgs/watcher/test/no_subscription/mac_os_test.dart index fc14ebf10..5ffb11738 100644 --- a/pkgs/watcher/test/no_subscription/mac_os_test.dart +++ b/pkgs/watcher/test/no_subscription/mac_os_test.dart @@ -12,7 +12,7 @@ import 'shared.dart'; import '../utils.dart'; void main() { - watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); + watcherFactory = (dir) => MacOSDirectoryWatcher(dir); sharedTests(); } diff --git a/pkgs/watcher/test/no_subscription/polling_test.dart b/pkgs/watcher/test/no_subscription/polling_test.dart index 75fa3a7a0..633ca2eb4 100644 --- a/pkgs/watcher/test/no_subscription/polling_test.dart +++ b/pkgs/watcher/test/no_subscription/polling_test.dart @@ -8,7 +8,7 @@ import 'shared.dart'; import '../utils.dart'; void main() { - watcherFactory = (dir) => new PollingDirectoryWatcher(dir); + watcherFactory = (dir) => PollingDirectoryWatcher(dir); sharedTests(); } diff --git a/pkgs/watcher/test/no_subscription/shared.dart b/pkgs/watcher/test/no_subscription/shared.dart index ba8468401..2fa3353e0 100644 --- a/pkgs/watcher/test/no_subscription/shared.dart +++ b/pkgs/watcher/test/no_subscription/shared.dart @@ -14,7 +14,7 @@ void sharedTests() { // utils.dart because it needs to be very explicit about when the event // stream is and is not subscribed. var watcher = createWatcher(); - var queue = new StreamQueue(watcher.events); + var queue = StreamQueue(watcher.events); queue.hasNext; var future = @@ -33,7 +33,7 @@ void sharedTests() { // Now write a file while we aren't listening. writeFile("unwatched.txt"); - queue = new StreamQueue(watcher.events); + queue = StreamQueue(watcher.events); future = expectLater(queue, emits(isWatchEvent(ChangeType.ADD, "added.txt"))); expect(queue, neverEmits(isWatchEvent(ChangeType.ADD, "unwatched.txt"))); diff --git a/pkgs/watcher/test/path_set_test.dart b/pkgs/watcher/test/path_set_test.dart index fe91d417f..9ca418180 100644 --- a/pkgs/watcher/test/path_set_test.dart +++ b/pkgs/watcher/test/path_set_test.dart @@ -16,7 +16,7 @@ Matcher containsDir(String path) => predicate( void main() { PathSet paths; - setUp(() => paths = new PathSet("root")); + setUp(() => paths = PathSet("root")); group("adding a path", () { test("stores the path in the set", () { diff --git a/pkgs/watcher/test/ready/linux_test.dart b/pkgs/watcher/test/ready/linux_test.dart index e9bfd69ca..aa5763772 100644 --- a/pkgs/watcher/test/ready/linux_test.dart +++ b/pkgs/watcher/test/ready/linux_test.dart @@ -11,7 +11,7 @@ import 'shared.dart'; import '../utils.dart'; void main() { - watcherFactory = (dir) => new LinuxDirectoryWatcher(dir); + watcherFactory = (dir) => LinuxDirectoryWatcher(dir); sharedTests(); } diff --git a/pkgs/watcher/test/ready/mac_os_test.dart b/pkgs/watcher/test/ready/mac_os_test.dart index 9533cc818..4bfdc8d3e 100644 --- a/pkgs/watcher/test/ready/mac_os_test.dart +++ b/pkgs/watcher/test/ready/mac_os_test.dart @@ -11,7 +11,7 @@ import 'shared.dart'; import '../utils.dart'; void main() { - watcherFactory = (dir) => new MacOSDirectoryWatcher(dir); + watcherFactory = (dir) => MacOSDirectoryWatcher(dir); sharedTests(); } diff --git a/pkgs/watcher/test/ready/polling_test.dart b/pkgs/watcher/test/ready/polling_test.dart index 75fa3a7a0..633ca2eb4 100644 --- a/pkgs/watcher/test/ready/polling_test.dart +++ b/pkgs/watcher/test/ready/polling_test.dart @@ -8,7 +8,7 @@ import 'shared.dart'; import '../utils.dart'; void main() { - watcherFactory = (dir) => new PollingDirectoryWatcher(dir); + watcherFactory = (dir) => PollingDirectoryWatcher(dir); sharedTests(); } diff --git a/pkgs/watcher/test/ready/shared.dart b/pkgs/watcher/test/ready/shared.dart index 730d57953..76089e25e 100644 --- a/pkgs/watcher/test/ready/shared.dart +++ b/pkgs/watcher/test/ready/shared.dart @@ -38,7 +38,7 @@ void sharedTests() { // Ensure ready completes immediately expect( - watcher.ready.timeout(new Duration(milliseconds: 0), + watcher.ready.timeout(Duration(milliseconds: 0), onTimeout: () => throw 'Does not complete immedately'), completes); }); diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 7462c991a..95f594d8a 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -61,13 +61,13 @@ Future startWatcher({String path}) async { assert(p.isRelative(path) && !path.startsWith("..")); var mtime = _mockFileModificationTimes[path]; - return new DateTime.fromMillisecondsSinceEpoch(mtime ?? 0); + return DateTime.fromMillisecondsSinceEpoch(mtime ?? 0); }); // We want to wait until we're ready *after* we subscribe to the watcher's // events. var watcher = createWatcher(path: path); - _watcherEvents = new StreamQueue(watcher.events); + _watcherEvents = StreamQueue(watcher.events); // Forces a subscription to the underlying stream. _watcherEvents.hasNext; await watcher.ready; @@ -93,7 +93,7 @@ List _collectedStreamMatchers; /// The returned matcher will match each of the collected matchers in order. StreamMatcher _collectStreamMatcher(block()) { var oldStreamMatchers = _collectedStreamMatchers; - _collectedStreamMatchers = new List(); + _collectedStreamMatchers = List(); try { block(); return emitsInOrder(_collectedStreamMatchers); @@ -207,12 +207,12 @@ void writeFile(String path, {String contents, bool updateModified}) { var fullPath = p.join(d.sandbox, path); // Create any needed subdirectories. - var dir = new Directory(p.dirname(fullPath)); + var dir = Directory(p.dirname(fullPath)); if (!dir.existsSync()) { dir.createSync(recursive: true); } - new File(fullPath).writeAsStringSync(contents); + File(fullPath).writeAsStringSync(contents); if (updateModified) { path = p.normalize(path); @@ -224,12 +224,12 @@ void writeFile(String path, {String contents, bool updateModified}) { /// Schedules deleting a file in the sandbox at [path]. void deleteFile(String path) { - new File(p.join(d.sandbox, path)).deleteSync(); + File(p.join(d.sandbox, path)).deleteSync(); } /// Schedules renaming a file in the sandbox from [from] to [to]. void renameFile(String from, String to) { - new File(p.join(d.sandbox, from)).renameSync(p.join(d.sandbox, to)); + File(p.join(d.sandbox, from)).renameSync(p.join(d.sandbox, to)); // Make sure we always use the same separator on Windows. to = p.normalize(to); @@ -240,17 +240,17 @@ void renameFile(String from, String to) { /// Schedules creating a directory in the sandbox at [path]. void createDir(String path) { - new Directory(p.join(d.sandbox, path)).createSync(); + Directory(p.join(d.sandbox, path)).createSync(); } /// Schedules renaming a directory in the sandbox from [from] to [to]. void renameDir(String from, String to) { - new Directory(p.join(d.sandbox, from)).renameSync(p.join(d.sandbox, to)); + Directory(p.join(d.sandbox, from)).renameSync(p.join(d.sandbox, to)); } /// Schedules deleting a directory in the sandbox at [path]. void deleteDir(String path) { - new Directory(p.join(d.sandbox, path)).deleteSync(recursive: true); + Directory(p.join(d.sandbox, path)).deleteSync(recursive: true); } /// Runs [callback] with every permutation of non-negative numbers for each @@ -261,7 +261,7 @@ void deleteDir(String path) { /// [limit] defaults to 3. Set withPermutations(S callback(int i, int j, int k), {int limit}) { if (limit == null) limit = 3; - var results = new Set(); + var results = Set(); for (var i = 0; i < limit; i++) { for (var j = 0; j < limit; j++) { for (var k = 0; k < limit; k++) { From a6b1bfba998a10117bf3ff9836007d8c3af9a24c Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Tue, 11 Sep 2018 12:08:22 -0700 Subject: [PATCH 117/201] Add lints from package:pedantic (dart-lang/watcher#66) - Fix a control_flow_in_finally by removing the nested try/catch which was set up for ordering of behavior and instead check for a closed controller in both places. - Fix a prefer_is_not_empty. --- pkgs/watcher/analysis_options.yaml | 1 + pkgs/watcher/lib/src/file_watcher/polling.dart | 18 ++++++++---------- pkgs/watcher/lib/src/path_set.dart | 2 +- pkgs/watcher/pubspec.yaml | 1 + 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pkgs/watcher/analysis_options.yaml b/pkgs/watcher/analysis_options.yaml index 4c9f8113a..3155c7fc0 100644 --- a/pkgs/watcher/analysis_options.yaml +++ b/pkgs/watcher/analysis_options.yaml @@ -1,3 +1,4 @@ +include: package:pedantic/analysis_options.yaml analyzer: strong-mode: implicit-casts: false diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart index 777038fdf..877031ecb 100644 --- a/pkgs/watcher/lib/src/file_watcher/polling.dart +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -60,16 +60,14 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { DateTime modified; try { - try { - modified = await getModificationTime(path); - } finally { - if (_eventsController.isClosed) return; - } + modified = await getModificationTime(path); } on FileSystemException catch (error, stackTrace) { - _eventsController.addError(error, stackTrace); - close(); - return; + if (!_eventsController.isClosed) { + _eventsController.addError(error, stackTrace); + await close(); + } } + if (_eventsController.isClosed) return; if (_lastModified == modified) return; @@ -84,8 +82,8 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { } } - void close() { + Future close() async { _timer.cancel(); - _eventsController.close(); + await _eventsController.close(); } } diff --git a/pkgs/watcher/lib/src/path_set.dart b/pkgs/watcher/lib/src/path_set.dart index 9a2c03cc1..d6983ffb1 100644 --- a/pkgs/watcher/lib/src/path_set.dart +++ b/pkgs/watcher/lib/src/path_set.dart @@ -136,7 +136,7 @@ class PathSet { if (entry == null) return false; } - return !entry.contents.isEmpty; + return entry.contents.isNotEmpty; } /// All of the paths explicitly added to this set. diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 552b13a9d..c107856ec 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -16,5 +16,6 @@ dependencies: dev_dependencies: benchmark_harness: ^1.0.4 + pedantic: ^1.1.0 test: '>=0.12.42 <2.0.0' test_descriptor: ^1.0.0 From 85aef339f1458cce2ac8044232cf61edf015d703 Mon Sep 17 00:00:00 2001 From: Konstantin Scheglov Date: Wed, 7 Nov 2018 13:12:14 -0800 Subject: [PATCH 118/201] Catch FileSystemException during existsSync() on Windows. (dart-lang/watcher#67) --- pkgs/watcher/CHANGELOG.md | 4 ++++ .../lib/src/directory_watcher/windows.dart | 15 +++++++++++---- pkgs/watcher/pubspec.yaml | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index cd34d904e..251c13e96 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.9.7+12 + +* Catch `FileSystemException` during `existsSync()` on Windows. + # 0.9.7+11 * Fix an analysis hint. diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 021493995..7294c170d 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -308,8 +308,8 @@ class _WindowsDirectoryWatcher } } - /// Returns one or more events that describe the change between the last known - /// state of [path] and its current state on the filesystem. + /// Returns zero or more events that describe the change between the last + /// known state of [path] and its current state on the filesystem. /// /// This returns a list whose order should be reflected in the events emitted /// to the user, unlike the batched events from [Directory.watch]. The @@ -318,8 +318,15 @@ class _WindowsDirectoryWatcher List _eventsBasedOnFileSystem(String path) { var fileExisted = _files.contains(path); var dirExisted = _files.containsDir(path); - var fileExists = File(path).existsSync(); - var dirExists = Directory(path).existsSync(); + + bool fileExists; + bool dirExists; + try { + fileExists = File(path).existsSync(); + dirExists = Directory(path).existsSync(); + } on FileSystemException { + return const []; + } var events = []; if (fileExisted) { diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index c107856ec..163e70ec1 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+11 +version: 0.9.7+12 description: > A file system watcher. It monitors changes to contents of directories and From 3b98d7f8fab111e4bf6abe392e958fbebd1f6393 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Thu, 16 May 2019 16:22:37 -0600 Subject: [PATCH 119/201] Enable and fix lints, test on oldest supported Dart SDK, verify lints on Travis (dart-lang/watcher#68) --- pkgs/watcher/.travis.yml | 20 +++++++++++++------ pkgs/watcher/analysis_options.yaml | 3 +++ pkgs/watcher/benchmark/path_set.dart | 10 +++++++--- pkgs/watcher/lib/src/async_queue.dart | 2 +- .../watcher/lib/src/file_watcher/polling.dart | 4 +++- pkgs/watcher/lib/src/stat.dart | 2 +- pkgs/watcher/pubspec.yaml | 6 +++--- .../test/directory_watcher/shared.dart | 6 +++--- pkgs/watcher/test/no_subscription/shared.dart | 3 ++- pkgs/watcher/test/ready/shared.dart | 5 +++-- pkgs/watcher/test/utils.dart | 6 +++--- 11 files changed, 43 insertions(+), 24 deletions(-) diff --git a/pkgs/watcher/.travis.yml b/pkgs/watcher/.travis.yml index 254764448..c15590ba4 100644 --- a/pkgs/watcher/.travis.yml +++ b/pkgs/watcher/.travis.yml @@ -1,15 +1,23 @@ language: dart -dart: dev + +dart: +- dev +- 2.0.0 dart_task: - - test - - dartfmt - - dartanalyzer +- test +- dartanalyzer: --fatal-warnings --fatal-infos . + +matrix: + include: + # Only validate formatting using the dev release + - dart: dev + dart_task: dartfmt # Only building master means that we don't run two builds for each pull request. branches: only: [master] cache: - directories: - - $HOME/.pub-cache + directories: + - $HOME/.pub-cache diff --git a/pkgs/watcher/analysis_options.yaml b/pkgs/watcher/analysis_options.yaml index 3155c7fc0..dd6881e3f 100644 --- a/pkgs/watcher/analysis_options.yaml +++ b/pkgs/watcher/analysis_options.yaml @@ -12,4 +12,7 @@ analyzer: linter: rules: - comment_references + - prefer_generic_function_type_aliases - prefer_typing_uninitialized_variables + - unnecessary_const + - unnecessary_new diff --git a/pkgs/watcher/benchmark/path_set.dart b/pkgs/watcher/benchmark/path_set.dart index 8a5b843ef..1ec333626 100644 --- a/pkgs/watcher/benchmark/path_set.dart +++ b/pkgs/watcher/benchmark/path_set.dart @@ -21,7 +21,7 @@ abstract class PathSetBenchmark extends BenchmarkBase { final PathSet pathSet = PathSet(root); - /// Use a fixed [Random] with a constant seed to ensure the tests are + /// Use a fixed [math.Random] with a constant seed to ensure the tests are /// deterministic. final math.Random random = math.Random(1234); @@ -59,7 +59,9 @@ class AddBenchmark extends PathSetBenchmark { } void run() { - for (var path in paths) pathSet.add(path); + for (var path in paths) { + pathSet.add(path); + } } } @@ -135,7 +137,9 @@ class RemoveBenchmark extends PathSetBenchmark { } void run() { - for (var path in paths) pathSet.remove(path); + for (var path in paths) { + pathSet.remove(path); + } } } diff --git a/pkgs/watcher/lib/src/async_queue.dart b/pkgs/watcher/lib/src/async_queue.dart index de7b7188a..d864b1bea 100644 --- a/pkgs/watcher/lib/src/async_queue.dart +++ b/pkgs/watcher/lib/src/async_queue.dart @@ -5,7 +5,7 @@ import 'dart:async'; import 'dart:collection'; -typedef Future ItemProcessor(T item); +typedef ItemProcessor = Future Function(T item); /// A queue of items that are sequentially, asynchronously processed. /// diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart index 877031ecb..a0466b599 100644 --- a/pkgs/watcher/lib/src/file_watcher/polling.dart +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -5,6 +5,8 @@ import 'dart:async'; import 'dart:io'; +import 'package:pedantic/pedantic.dart'; + import '../file_watcher.dart'; import '../resubscribable.dart'; import '../stat.dart'; @@ -54,7 +56,7 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { if (_lastModified != null && !pathExists) { _eventsController.add(WatchEvent(ChangeType.REMOVE, path)); - close(); + unawaited(close()); return; } diff --git a/pkgs/watcher/lib/src/stat.dart b/pkgs/watcher/lib/src/stat.dart index 59a5eb6f0..a569208ac 100644 --- a/pkgs/watcher/lib/src/stat.dart +++ b/pkgs/watcher/lib/src/stat.dart @@ -7,7 +7,7 @@ import 'dart:io'; /// A function that takes a file path and returns the last modified time for /// the file at that path. -typedef DateTime MockTimeCallback(String path); +typedef MockTimeCallback = DateTime Function(String path); MockTimeCallback _mockTimeCallback; diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 163e70ec1..3722c8419 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,7 +1,7 @@ name: watcher -version: 0.9.7+12 +version: 0.9.8-dev -description: > +description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. author: Dart Team @@ -13,9 +13,9 @@ environment: dependencies: async: '>=1.10.0 <3.0.0' path: '>=0.9.0 <2.0.0' + pedantic: ^1.1.0 dev_dependencies: benchmark_harness: ^1.0.4 - pedantic: ^1.1.0 test: '>=0.12.42 <2.0.0' test_descriptor: ^1.0.0 diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index f6e451cbc..a302c933e 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -115,7 +115,7 @@ void sharedTests() { await startWatcher(path: "dir"); renameFile("old.txt", "dir/new.txt"); - expectAddEvent("dir/new.txt"); + await expectAddEvent("dir/new.txt"); }); test('notifies when a file is moved outside the watched directory', @@ -124,7 +124,7 @@ void sharedTests() { await startWatcher(path: "dir"); renameFile("dir/old.txt", "new.txt"); - expectRemoveEvent("dir/old.txt"); + await expectRemoveEvent("dir/old.txt"); }); test('notifies when a file is moved onto an existing one', () async { @@ -231,7 +231,7 @@ void sharedTests() { test('watches files in subdirectories', () async { await startWatcher(); writeFile("a/b/c/d/file.txt"); - expectAddEvent("a/b/c/d/file.txt"); + await expectAddEvent("a/b/c/d/file.txt"); }); test( diff --git a/pkgs/watcher/test/no_subscription/shared.dart b/pkgs/watcher/test/no_subscription/shared.dart index 2fa3353e0..e82692ec4 100644 --- a/pkgs/watcher/test/no_subscription/shared.dart +++ b/pkgs/watcher/test/no_subscription/shared.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:async/async.dart'; +import 'package:pedantic/pedantic.dart'; import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; @@ -15,7 +16,7 @@ void sharedTests() { // stream is and is not subscribed. var watcher = createWatcher(); var queue = StreamQueue(watcher.events); - queue.hasNext; + unawaited(queue.hasNext); var future = expectLater(queue, emits(isWatchEvent(ChangeType.ADD, "file.txt"))); diff --git a/pkgs/watcher/test/ready/shared.dart b/pkgs/watcher/test/ready/shared.dart index 76089e25e..ebe2f0cf4 100644 --- a/pkgs/watcher/test/ready/shared.dart +++ b/pkgs/watcher/test/ready/shared.dart @@ -2,6 +2,7 @@ // 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. +import 'package:pedantic/pedantic.dart'; import 'package:test/test.dart'; import '../utils.dart'; @@ -11,9 +12,9 @@ void sharedTests() { var watcher = createWatcher(); var ready = false; - watcher.ready.then((_) { + unawaited(watcher.ready.then((_) { ready = true; - }); + })); await pumpEventQueue(); expect(ready, isFalse); diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 95f594d8a..57b4563f9 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -7,13 +7,13 @@ import 'dart:io'; import 'package:async/async.dart'; import 'package:path/path.dart' as p; +import 'package:pedantic/pedantic.dart'; import 'package:test/test.dart'; import 'package:test_descriptor/test_descriptor.dart' as d; - import 'package:watcher/src/stat.dart'; import 'package:watcher/watcher.dart'; -typedef Watcher WatcherFactory(String directory); +typedef WatcherFactory = Watcher Function(String directory); /// Sets the function used to create the watcher. set watcherFactory(WatcherFactory factory) { @@ -69,7 +69,7 @@ Future startWatcher({String path}) async { var watcher = createWatcher(path: path); _watcherEvents = StreamQueue(watcher.events); // Forces a subscription to the underlying stream. - _watcherEvents.hasNext; + unawaited(_watcherEvents.hasNext); await watcher.ready; } From 1ebb78828050a6b65feadfc9b1c4dc396372bb20 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 22 May 2019 16:18:01 -0700 Subject: [PATCH 120/201] Use more async/await (dart-lang/watcher#70) * Use more async/await - Change some bare `Future` into `Future` to make sure we aren't silently passing state where we don't expect. - Change some methods with `.then` calls inside to `async`. * Add another await --- pkgs/watcher/lib/src/async_queue.dart | 17 +++--- .../lib/src/directory_watcher/polling.dart | 54 ++++++++++--------- .../lib/src/directory_watcher/windows.dart | 4 +- .../watcher/lib/src/file_watcher/polling.dart | 2 +- pkgs/watcher/lib/src/resubscribable.dart | 15 +++--- pkgs/watcher/lib/src/stat.dart | 7 +-- pkgs/watcher/test/utils.dart | 5 +- 7 files changed, 54 insertions(+), 50 deletions(-) diff --git a/pkgs/watcher/lib/src/async_queue.dart b/pkgs/watcher/lib/src/async_queue.dart index d864b1bea..9f8bedf54 100644 --- a/pkgs/watcher/lib/src/async_queue.dart +++ b/pkgs/watcher/lib/src/async_queue.dart @@ -5,7 +5,7 @@ import 'dart:async'; import 'dart:collection'; -typedef ItemProcessor = Future Function(T item); +typedef ItemProcessor = Future Function(T item); /// A queue of items that are sequentially, asynchronously processed. /// @@ -57,15 +57,14 @@ class AsyncQueue { /// /// When complete, recursively calls itself to continue processing unless /// the process was cancelled. - Future _processNextItem() { + Future _processNextItem() async { var item = _items.removeFirst(); - return _processor(item).then((_) async { - if (_items.isNotEmpty) return await _processNextItem(); + await _processor(item); + if (_items.isNotEmpty) return _processNextItem(); - // We have drained the queue, stop processing and wait until something - // has been enqueued. - _isProcessing = false; - return null; - }); + // We have drained the queue, stop processing and wait until something + // has been enqueued. + _isProcessing = false; + return null; } } diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 735a80722..f21a239e9 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -40,8 +40,8 @@ class _PollingDirectoryWatcher bool get isReady => _ready.isCompleted; - Future get ready => _ready.future; - final _ready = Completer(); + Future get ready => _ready.future; + final _ready = Completer(); /// The amount of time the watcher pauses between successive polls of the /// directory contents. @@ -129,38 +129,41 @@ class _PollingDirectoryWatcher /// Processes [file] to determine if it has been modified since the last /// time it was scanned. - Future _processFile(String file) { + Future _processFile(String file) async { // `null` is the sentinel which means the directory listing is complete. - if (file == null) return _completePoll(); - - return getModificationTime(file).then((modified) { - if (_events.isClosed) return null; + if (file == null) { + await _completePoll(); + return; + } - var lastModified = _lastModifieds[file]; + final modified = await modificationTime(file); - // If its modification time hasn't changed, assume the file is unchanged. - if (lastModified != null && lastModified == modified) { - // The file is still here. - _polledFiles.add(file); - return null; - } + if (_events.isClosed) return; - if (_events.isClosed) return null; + var lastModified = _lastModifieds[file]; - _lastModifieds[file] = modified; + // If its modification time hasn't changed, assume the file is unchanged. + if (lastModified != null && lastModified == modified) { + // The file is still here. _polledFiles.add(file); + return; + } - // Only notify if we're ready to emit events. - if (!isReady) return null; + if (_events.isClosed) return; - var type = lastModified == null ? ChangeType.ADD : ChangeType.MODIFY; - _events.add(WatchEvent(type, file)); - }); + _lastModifieds[file] = modified; + _polledFiles.add(file); + + // Only notify if we're ready to emit events. + if (!isReady) return; + + var type = lastModified == null ? ChangeType.ADD : ChangeType.MODIFY; + _events.add(WatchEvent(type, file)); } /// After the directory listing is complete, this determines which files were /// removed and then restarts the next poll. - Future _completePoll() { + Future _completePoll() async { // Any files that were not seen in the last poll but that we have a // status for must have been removed. var removedFiles = _lastModifieds.keys.toSet().difference(_polledFiles); @@ -172,9 +175,8 @@ class _PollingDirectoryWatcher if (!isReady) _ready.complete(); // Wait and then poll again. - return Future.delayed(_pollingDelay).then((_) { - if (_events.isClosed) return; - _poll(); - }); + await Future.delayed(_pollingDelay); + if (_events.isClosed) return; + _poll(); } } diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 7294c170d..85fef5f54 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -52,7 +52,7 @@ class _WindowsDirectoryWatcher bool get isReady => _readyCompleter.isCompleted; - Future get ready => _readyCompleter.future; + Future get ready => _readyCompleter.future; final _readyCompleter = Completer(); final Map _eventBatchers = @@ -380,7 +380,7 @@ class _WindowsDirectoryWatcher /// Starts or restarts listing the watched directory to get an initial picture /// of its state. - Future _listDir() { + Future _listDir() { assert(!isReady); if (_initialListSubscription != null) _initialListSubscription.cancel(); diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart index a0466b599..e2bf5dd95 100644 --- a/pkgs/watcher/lib/src/file_watcher/polling.dart +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -62,7 +62,7 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { DateTime modified; try { - modified = await getModificationTime(path); + modified = await modificationTime(path); } on FileSystemException catch (error, stackTrace) { if (!_eventsController.isClosed) { _eventsController.addError(error, stackTrace); diff --git a/pkgs/watcher/lib/src/resubscribable.dart b/pkgs/watcher/lib/src/resubscribable.dart index e00ebb0d1..8de3dfb5a 100644 --- a/pkgs/watcher/lib/src/resubscribable.dart +++ b/pkgs/watcher/lib/src/resubscribable.dart @@ -31,8 +31,8 @@ abstract class ResubscribableWatcher implements Watcher { bool get isReady => _readyCompleter.isCompleted; - Future get ready => _readyCompleter.future; - var _readyCompleter = Completer(); + Future get ready => _readyCompleter.future; + var _readyCompleter = Completer(); /// Creates a new [ResubscribableWatcher] wrapping the watchers /// emitted by [_factory]. @@ -41,16 +41,17 @@ abstract class ResubscribableWatcher implements Watcher { StreamSubscription subscription; _eventsController = StreamController.broadcast( - onListen: () { + onListen: () async { watcher = _factory(); subscription = watcher.events.listen(_eventsController.add, onError: _eventsController.addError, onDone: _eventsController.close); - // It's important that we complete the value of [_readyCompleter] at the - // time [onListen] is called, as opposed to the value when [watcher.ready] - // fires. A new completer may be created by that time. - watcher.ready.then(_readyCompleter.complete); + // It's important that we complete the value of [_readyCompleter] at + // the time [onListen] is called, as opposed to the value when + // [watcher.ready] fires. A new completer may be created by that time. + await watcher.ready; + _readyCompleter.complete(); }, onCancel: () { // Cancel the subscription before closing the watcher so that the diff --git a/pkgs/watcher/lib/src/stat.dart b/pkgs/watcher/lib/src/stat.dart index a569208ac..6430d0b56 100644 --- a/pkgs/watcher/lib/src/stat.dart +++ b/pkgs/watcher/lib/src/stat.dart @@ -22,10 +22,11 @@ void mockGetModificationTime(MockTimeCallback callback) { } /// Gets the modification time for the file at [path]. -Future getModificationTime(String path) { +Future modificationTime(String path) async { if (_mockTimeCallback != null) { - return Future.value(_mockTimeCallback(path)); + return _mockTimeCallback(path); } - return FileStat.stat(path).then((stat) => stat.modified); + final stat = await FileStat.stat(path); + return stat.modified; } diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 57b4563f9..2e0ad014e 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -79,8 +79,9 @@ Future startWatcher({String path}) async { /// at the end of a test. Otherwise, if they don't occur, the test will wait /// indefinitely because they might in the future and because the watcher is /// normally only closed after the test completes. -void startClosingEventStream() { - pumpEventQueue().then((_) => _watcherEvents.cancel(immediate: true)); +void startClosingEventStream() async { + await pumpEventQueue(); + await _watcherEvents.cancel(immediate: true); } /// A list of [StreamMatcher]s that have been collected using From b2919b3402080b891f20674f8dd141dfbafdae3b Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Fri, 12 Jul 2019 16:08:39 -0700 Subject: [PATCH 121/201] Prepare to publish 0.9.7+12 (dart-lang/watcher#72) --- pkgs/watcher/CHANGELOG.md | 1 + pkgs/watcher/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 251c13e96..44fbda520 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,6 +1,7 @@ # 0.9.7+12 * Catch `FileSystemException` during `existsSync()` on Windows. +* Internal cleanup. # 0.9.7+11 diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 3722c8419..f999c2a91 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.8-dev +version: 0.9.7+12 description: >- A file system watcher. It monitors changes to contents of directories and From a6d082eb189f1eb285e9f68bbfdcb52985b23d8b Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 31 Jul 2019 21:06:41 -0700 Subject: [PATCH 122/201] Delete codereview.settings --- pkgs/watcher/codereview.settings | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 pkgs/watcher/codereview.settings diff --git a/pkgs/watcher/codereview.settings b/pkgs/watcher/codereview.settings deleted file mode 100644 index 25367dd29..000000000 --- a/pkgs/watcher/codereview.settings +++ /dev/null @@ -1,3 +0,0 @@ -CODE_REVIEW_SERVER: http://codereview.chromium.org/ -VIEW_VC: https://github.com/dart-lang/watcher/commit/ -CC_LIST: reviews@dartlang.org \ No newline at end of file From b8da2340f61c97dce1b3a0e6e895aca36ec598c2 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 20 Nov 2019 11:27:01 -0800 Subject: [PATCH 123/201] Temporarily disable comment_references lint (dart-lang/watcher#76) There was a regression in the SDK and some references that used to be valid, and should still work in Dart doc, are marked invalid. See https://github.com/dart-lang/sdk/issues/39467 --- pkgs/watcher/analysis_options.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/analysis_options.yaml b/pkgs/watcher/analysis_options.yaml index dd6881e3f..6eccbf845 100644 --- a/pkgs/watcher/analysis_options.yaml +++ b/pkgs/watcher/analysis_options.yaml @@ -11,7 +11,7 @@ analyzer: linter: rules: - - comment_references + # comment_references # https://github.com/dart-lang/sdk/issues/39467 - prefer_generic_function_type_aliases - prefer_typing_uninitialized_variables - unnecessary_const From 983431b3860e49af1e1b349dae57db333ee45e7a Mon Sep 17 00:00:00 2001 From: Michael R Fairhurst Date: Wed, 20 Nov 2019 16:34:46 -0800 Subject: [PATCH 124/201] Handle file watcher closed exception (dart-lang/watcher#75) Handle file watcher closed exception on windows --- pkgs/watcher/CHANGELOG.md | 5 +++++ .../lib/src/directory_watcher/windows.dart | 20 +++++++++++++++---- pkgs/watcher/pubspec.yaml | 2 +- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 44fbda520..2b3cd35b4 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.9.7+13 + +* Catch & forward `FileSystemException` from unexpectedly closed file watchers + on windows; the watcher will also be automatically restarted when this occurs. + # 0.9.7+12 * Catch `FileSystemException` during `existsSync()` on Windows. diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 85fef5f54..8bf664261 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -372,10 +372,22 @@ class _WindowsDirectoryWatcher /// Start or restart the underlying [Directory.watch] stream. void _startWatch() { - // Batch the events together so that we can dedup events. - var innerStream = Directory(path).watch(recursive: true); - _watchSubscription = innerStream.listen(_onEvent, - onError: _eventsController.addError, onDone: _onDone); + // Note: "watcher closed" exceptions do not get sent over the stream + // returned by watch, and must be caught via a zone handler. + runZoned(() { + var innerStream = Directory(path).watch(recursive: true); + _watchSubscription = innerStream.listen(_onEvent, + onError: _eventsController.addError, onDone: _onDone); + }, onError: (error, StackTrace stackTrace) { + if (error is FileSystemException && + error.message.startsWith('Directory watcher closed unexpectedly')) { + _watchSubscription.cancel(); + _eventsController.addError(error, stackTrace); + _startWatch(); + } else { + throw error; + } + }); } /// Starts or restarts listing the watched directory to get an initial picture diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index f999c2a91..f8315bc63 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+12 +version: 0.9.7+13 description: >- A file system watcher. It monitors changes to contents of directories and From 390790f2782b568c3d9372eb3de159929d31de25 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 20 Nov 2019 17:04:08 -0800 Subject: [PATCH 125/201] Update .gitignore (dart-lang/watcher#77) --- pkgs/watcher/.gitignore | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/pkgs/watcher/.gitignore b/pkgs/watcher/.gitignore index 7dbf0350d..ac98e87d1 100644 --- a/pkgs/watcher/.gitignore +++ b/pkgs/watcher/.gitignore @@ -1,15 +1,4 @@ # Don’t commit the following directories created by pub. -.buildlog -.pub/ -build/ -packages +.dart_tool .packages - -# Or the files created by dart2js. -*.dart.js -*.js_ -*.js.deps -*.js.map - -# Include when developing application packages. -pubspec.lock \ No newline at end of file +pubspec.lock From 680cc493698b17bb96410766b1085b658b0bb5b6 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Fri, 6 Dec 2019 13:27:17 -0800 Subject: [PATCH 126/201] Fix newly enforced package:pedantic lints (dart-lang/watcher#78) - always_declare_return_types - annotate_overrides - prefer_collection_literals - prefer_conditional_assignment - prefer_final_fields - prefer_if_null_operators - prefer_single_quotes - use_function_type_syntax_for_parameters Bump min SDK to 2.2.0 to allow Set literals. --- pkgs/watcher/.travis.yml | 2 +- pkgs/watcher/benchmark/path_set.dart | 46 +-- pkgs/watcher/example/watch.dart | 4 +- .../src/constructable_file_system_event.dart | 13 + pkgs/watcher/lib/src/directory_watcher.dart | 2 +- .../lib/src/directory_watcher/linux.dart | 21 +- .../lib/src/directory_watcher/mac_os.dart | 21 +- .../lib/src/directory_watcher/polling.dart | 17 +- .../lib/src/directory_watcher/windows.dart | 21 +- pkgs/watcher/lib/src/file_watcher/native.dart | 7 +- .../watcher/lib/src/file_watcher/polling.dart | 7 +- pkgs/watcher/lib/src/path_set.dart | 12 +- pkgs/watcher/lib/src/resubscribable.dart | 4 + pkgs/watcher/lib/src/utils.dart | 5 +- pkgs/watcher/lib/src/watch_event.dart | 10 +- pkgs/watcher/pubspec.yaml | 4 +- .../test/directory_watcher/linux_test.dart | 14 +- .../test/directory_watcher/mac_os_test.dart | 30 +- .../test/directory_watcher/polling_test.dart | 10 +- .../test/directory_watcher/shared.dart | 288 +++++++++--------- .../test/directory_watcher/windows_test.dart | 4 +- .../test/file_watcher/native_test.dart | 2 +- .../test/file_watcher/polling_test.dart | 2 +- pkgs/watcher/test/file_watcher/shared.dart | 70 ++--- .../test/no_subscription/mac_os_test.dart | 2 +- pkgs/watcher/test/no_subscription/shared.dart | 10 +- pkgs/watcher/test/path_set_test.dart | 230 +++++++------- pkgs/watcher/test/utils.dart | 25 +- 28 files changed, 475 insertions(+), 408 deletions(-) diff --git a/pkgs/watcher/.travis.yml b/pkgs/watcher/.travis.yml index c15590ba4..9871d25bb 100644 --- a/pkgs/watcher/.travis.yml +++ b/pkgs/watcher/.travis.yml @@ -2,7 +2,7 @@ language: dart dart: - dev -- 2.0.0 +- 2.2.0 dart_task: - test diff --git a/pkgs/watcher/benchmark/path_set.dart b/pkgs/watcher/benchmark/path_set.dart index 1ec333626..858df3cc0 100644 --- a/pkgs/watcher/benchmark/path_set.dart +++ b/pkgs/watcher/benchmark/path_set.dart @@ -13,11 +13,11 @@ import 'package:path/path.dart' as p; import 'package:watcher/src/path_set.dart'; -final String root = Platform.isWindows ? r"C:\root" : "/root"; +final String root = Platform.isWindows ? r'C:\root' : '/root'; /// Base class for benchmarks on [PathSet]. abstract class PathSetBenchmark extends BenchmarkBase { - PathSetBenchmark(String method) : super("PathSet.$method"); + PathSetBenchmark(String method) : super('PathSet.$method'); final PathSet pathSet = PathSet(root); @@ -30,14 +30,14 @@ abstract class PathSetBenchmark extends BenchmarkBase { /// /// Each virtual directory contains ten entries: either subdirectories or /// files. - void walkTree(int depth, callback(String path)) { - recurse(String path, remainingDepth) { + void walkTree(int depth, void Function(String) callback) { + void recurse(String path, remainingDepth) { for (var i = 0; i < 10; i++) { var padded = i.toString().padLeft(2, '0'); if (remainingDepth == 0) { - callback(p.join(path, "file_$padded.txt")); + callback(p.join(path, 'file_$padded.txt')); } else { - var subdir = p.join(path, "subdirectory_$padded"); + var subdir = p.join(path, 'subdirectory_$padded'); recurse(subdir, remainingDepth - 1); } } @@ -48,16 +48,18 @@ abstract class PathSetBenchmark extends BenchmarkBase { } class AddBenchmark extends PathSetBenchmark { - AddBenchmark() : super("add()"); + AddBenchmark() : super('add()'); final List paths = []; + @override void setup() { // Make a bunch of paths in about the same order we expect to get them from // Directory.list(). walkTree(3, paths.add); } + @override void run() { for (var path in paths) { pathSet.add(path); @@ -66,10 +68,11 @@ class AddBenchmark extends PathSetBenchmark { } class ContainsBenchmark extends PathSetBenchmark { - ContainsBenchmark() : super("contains()"); + ContainsBenchmark() : super('contains()'); final List paths = []; + @override void setup() { // Add a bunch of paths to the set. walkTree(3, (path) { @@ -80,48 +83,52 @@ class ContainsBenchmark extends PathSetBenchmark { // Add some non-existent paths to test the false case. for (var i = 0; i < 100; i++) { paths.addAll([ - "/nope", - "/root/nope", - "/root/subdirectory_04/nope", - "/root/subdirectory_04/subdirectory_04/nope", - "/root/subdirectory_04/subdirectory_04/subdirectory_04/nope", - "/root/subdirectory_04/subdirectory_04/subdirectory_04/nope/file_04.txt", + '/nope', + '/root/nope', + '/root/subdirectory_04/nope', + '/root/subdirectory_04/subdirectory_04/nope', + '/root/subdirectory_04/subdirectory_04/subdirectory_04/nope', + '/root/subdirectory_04/subdirectory_04/subdirectory_04/nope/file_04.txt', ]); } } + @override void run() { var contained = 0; for (var path in paths) { if (pathSet.contains(path)) contained++; } - if (contained != 10000) throw "Wrong result: $contained"; + if (contained != 10000) throw 'Wrong result: $contained'; } } class PathsBenchmark extends PathSetBenchmark { - PathsBenchmark() : super("toSet()"); + PathsBenchmark() : super('toSet()'); + @override void setup() { walkTree(3, pathSet.add); } + @override void run() { var count = 0; for (var _ in pathSet.paths) { count++; } - if (count != 10000) throw "Wrong result: $count"; + if (count != 10000) throw 'Wrong result: $count'; } } class RemoveBenchmark extends PathSetBenchmark { - RemoveBenchmark() : super("remove()"); + RemoveBenchmark() : super('remove()'); final List paths = []; + @override void setup() { // Make a bunch of paths. Do this here so that we don't spend benchmarked // time synthesizing paths. @@ -136,6 +143,7 @@ class RemoveBenchmark extends PathSetBenchmark { paths.shuffle(random); } + @override void run() { for (var path in paths) { pathSet.remove(path); @@ -143,7 +151,7 @@ class RemoveBenchmark extends PathSetBenchmark { } } -main() { +void main() { AddBenchmark().report(); ContainsBenchmark().report(); PathsBenchmark().report(); diff --git a/pkgs/watcher/example/watch.dart b/pkgs/watcher/example/watch.dart index 1477e4282..650a4b877 100644 --- a/pkgs/watcher/example/watch.dart +++ b/pkgs/watcher/example/watch.dart @@ -8,9 +8,9 @@ library watch; import 'package:path/path.dart' as p; import 'package:watcher/watcher.dart'; -main(List arguments) { +void main(List arguments) { if (arguments.length != 1) { - print("Usage: watch "); + print('Usage: watch '); return; } diff --git a/pkgs/watcher/lib/src/constructable_file_system_event.dart b/pkgs/watcher/lib/src/constructable_file_system_event.dart index 29b7c8ded..0011a8d64 100644 --- a/pkgs/watcher/lib/src/constructable_file_system_event.dart +++ b/pkgs/watcher/lib/src/constructable_file_system_event.dart @@ -5,8 +5,11 @@ import 'dart:io'; abstract class _ConstructableFileSystemEvent implements FileSystemEvent { + @override final bool isDirectory; + @override final String path; + @override int get type; _ConstructableFileSystemEvent(this.path, this.isDirectory); @@ -14,45 +17,55 @@ abstract class _ConstructableFileSystemEvent implements FileSystemEvent { class ConstructableFileSystemCreateEvent extends _ConstructableFileSystemEvent implements FileSystemCreateEvent { + @override final type = FileSystemEvent.create; ConstructableFileSystemCreateEvent(String path, bool isDirectory) : super(path, isDirectory); + @override String toString() => "FileSystemCreateEvent('$path')"; } class ConstructableFileSystemDeleteEvent extends _ConstructableFileSystemEvent implements FileSystemDeleteEvent { + @override final type = FileSystemEvent.delete; ConstructableFileSystemDeleteEvent(String path, bool isDirectory) : super(path, isDirectory); + @override String toString() => "FileSystemDeleteEvent('$path')"; } class ConstructableFileSystemModifyEvent extends _ConstructableFileSystemEvent implements FileSystemModifyEvent { + @override final bool contentChanged; + @override final type = FileSystemEvent.modify; ConstructableFileSystemModifyEvent( String path, bool isDirectory, this.contentChanged) : super(path, isDirectory); + @override String toString() => "FileSystemModifyEvent('$path', contentChanged=$contentChanged)"; } class ConstructableFileSystemMoveEvent extends _ConstructableFileSystemEvent implements FileSystemMoveEvent { + @override final String destination; + @override final type = FileSystemEvent.move; ConstructableFileSystemMoveEvent( String path, bool isDirectory, this.destination) : super(path, isDirectory); + @override String toString() => "FileSystemMoveEvent('$path', '$destination')"; } diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 8c52ed97a..e0ef3fcc0 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -14,7 +14,7 @@ import 'directory_watcher/polling.dart'; /// in the directory has changed. abstract class DirectoryWatcher implements Watcher { /// The directory whose contents are being monitored. - @Deprecated("Expires in 1.0.0. Use DirectoryWatcher.path instead.") + @Deprecated('Expires in 1.0.0. Use DirectoryWatcher.path instead.') String get directory; /// Creates a new [DirectoryWatcher] monitoring [directory]. diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index f3866c6c6..0a66a1291 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -25,6 +25,7 @@ import '../watch_event.dart'; /// (issue 14424). class LinuxDirectoryWatcher extends ResubscribableWatcher implements DirectoryWatcher { + @override String get directory => path; LinuxDirectoryWatcher(String directory) @@ -33,20 +34,25 @@ class LinuxDirectoryWatcher extends ResubscribableWatcher class _LinuxDirectoryWatcher implements DirectoryWatcher, ManuallyClosedWatcher { + @override String get directory => _files.root; + @override String get path => _files.root; + @override Stream get events => _eventsController.stream; final _eventsController = StreamController.broadcast(); + @override bool get isReady => _readyCompleter.isCompleted; + @override Future get ready => _readyCompleter.future; final _readyCompleter = Completer(); /// A stream group for the [Directory.watch] events of [path] and all its /// subdirectories. - var _nativeEvents = StreamGroup(); + final _nativeEvents = StreamGroup(); /// All known files recursively within [path]. final PathSet _files; @@ -60,7 +66,7 @@ class _LinuxDirectoryWatcher /// /// These are gathered together so that they may all be canceled when the /// watcher is closed. - final _subscriptions = Set(); + final _subscriptions = {}; _LinuxDirectoryWatcher(String path) : _files = PathSet(path) { _nativeEvents.add(Directory(path) @@ -93,6 +99,7 @@ class _LinuxDirectoryWatcher }, cancelOnError: true); } + @override void close() { for (var subscription in _subscriptions) { subscription.cancel(); @@ -128,9 +135,9 @@ class _LinuxDirectoryWatcher /// The callback that's run when a batch of changes comes in. void _onBatch(List batch) { - var files = Set(); - var dirs = Set(); - var changed = Set(); + var files = {}; + var dirs = {}; + var changed = {}; // inotify event batches are ordered by occurrence, so we treat them as a // log of what happened to a file. We only emit events based on the @@ -250,8 +257,8 @@ class _LinuxDirectoryWatcher /// Like [Stream.listen], but automatically adds the subscription to /// [_subscriptions] so that it can be canceled when [close] is called. - void _listen(Stream stream, void onData(T event), - {Function onError, void onDone(), bool cancelOnError}) { + void _listen(Stream stream, void Function(T) onData, + {Function onError, void Function() onDone, bool cancelOnError}) { StreamSubscription subscription; subscription = stream.listen(onData, onError: onError, onDone: () { _subscriptions.remove(subscription); diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index f593fbd51..73703cb4d 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -24,6 +24,7 @@ import '../watch_event.dart'; /// [Directory.watch]. class MacOSDirectoryWatcher extends ResubscribableWatcher implements DirectoryWatcher { + @override String get directory => path; MacOSDirectoryWatcher(String directory) @@ -32,14 +33,19 @@ class MacOSDirectoryWatcher extends ResubscribableWatcher class _MacOSDirectoryWatcher implements DirectoryWatcher, ManuallyClosedWatcher { + @override String get directory => path; + @override final String path; + @override Stream get events => _eventsController.stream; final _eventsController = StreamController.broadcast(); + @override bool get isReady => _readyCompleter.isCompleted; + @override Future get ready => _readyCompleter.future; final _readyCompleter = Completer(); @@ -64,7 +70,7 @@ class _MacOSDirectoryWatcher /// The subscriptions to [Directory.list] calls for listing the contents of a /// subdirectory that was moved into the watched directory. - final _listSubscriptions = Set>(); + final _listSubscriptions = >{}; /// The timer for tracking how long we wait for an initial batch of bogus /// events (see issue 14373). @@ -85,6 +91,7 @@ class _MacOSDirectoryWatcher .then((_) => _readyCompleter.complete()); } + @override void close() { if (_watchSubscription != null) _watchSubscription.cancel(); if (_initialListSubscription != null) _initialListSubscription.cancel(); @@ -181,19 +188,19 @@ class _MacOSDirectoryWatcher // directory's full contents will be examined anyway, so we ignore such // events. Emitting them could cause useless or out-of-order events. var directories = unionAll(batch.map((event) { - if (!event.isDirectory) return Set(); + if (!event.isDirectory) return {}; if (event is FileSystemMoveEvent) { - return Set.from([event.path, event.destination]); + return {event.path, event.destination}; } - return Set.from([event.path]); + return {event.path}; })); - isInModifiedDirectory(String path) => + bool isInModifiedDirectory(String path) => directories.any((dir) => path != dir && path.startsWith(dir)); - addEvent(String path, FileSystemEvent event) { + void addEvent(String path, FileSystemEvent event) { if (isInModifiedDirectory(path)) return; - eventsForPaths.putIfAbsent(path, () => Set()).add(event); + eventsForPaths.putIfAbsent(path, () => {}).add(event); } for (var event in batch) { diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index f21a239e9..388f28add 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -15,6 +15,7 @@ import '../watch_event.dart'; /// Periodically polls a directory for changes. class PollingDirectoryWatcher extends ResubscribableWatcher implements DirectoryWatcher { + @override String get directory => path; /// Creates a new polling watcher monitoring [directory]. @@ -25,21 +26,26 @@ class PollingDirectoryWatcher extends ResubscribableWatcher /// and higher CPU usage. Defaults to one second. PollingDirectoryWatcher(String directory, {Duration pollingDelay}) : super(directory, () { - return _PollingDirectoryWatcher(directory, - pollingDelay != null ? pollingDelay : Duration(seconds: 1)); + return _PollingDirectoryWatcher( + directory, pollingDelay ?? Duration(seconds: 1)); }); } class _PollingDirectoryWatcher implements DirectoryWatcher, ManuallyClosedWatcher { + @override String get directory => path; + @override final String path; + @override Stream get events => _events.stream; final _events = StreamController.broadcast(); + @override bool get isReady => _ready.isCompleted; + @override Future get ready => _ready.future; final _ready = Completer(); @@ -50,7 +56,7 @@ class _PollingDirectoryWatcher /// The previous modification times of the files in the directory. /// /// Used to tell which files have been modified. - final _lastModifieds = Map(); + final _lastModifieds = {}; /// The subscription used while [directory] is being listed. /// @@ -70,7 +76,7 @@ class _PollingDirectoryWatcher /// /// Used to tell which files have been removed: files that are in /// [_lastModifieds] but not in here when a poll completes have been removed. - final _polledFiles = Set(); + final _polledFiles = {}; _PollingDirectoryWatcher(this.path, this._pollingDelay) { _filesToProcess = @@ -81,6 +87,7 @@ class _PollingDirectoryWatcher _poll(); } + @override void close() { _events.close(); @@ -99,7 +106,7 @@ class _PollingDirectoryWatcher _filesToProcess.clear(); _polledFiles.clear(); - endListing() { + void endListing() { assert(!_events.isClosed); _listSubscription = null; diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 8bf664261..2a70edced 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -18,6 +18,7 @@ import '../watch_event.dart'; class WindowsDirectoryWatcher extends ResubscribableWatcher implements DirectoryWatcher { + @override String get directory => path; WindowsDirectoryWatcher(String directory) @@ -29,7 +30,7 @@ class _EventBatcher { final List events = []; Timer timer; - void addEvent(FileSystemEvent event, void callback()) { + void addEvent(FileSystemEvent event, void Function() callback) { events.add(event); if (timer != null) { timer.cancel(); @@ -44,14 +45,19 @@ class _EventBatcher { class _WindowsDirectoryWatcher implements DirectoryWatcher, ManuallyClosedWatcher { + @override String get directory => path; + @override final String path; + @override Stream get events => _eventsController.stream; final _eventsController = StreamController.broadcast(); + @override bool get isReady => _readyCompleter.isCompleted; + @override Future get ready => _readyCompleter.future; final _readyCompleter = Completer(); @@ -94,6 +100,7 @@ class _WindowsDirectoryWatcher }); } + @override void close() { if (_watchSubscription != null) _watchSubscription.cancel(); if (_parentWatchSubscription != null) _parentWatchSubscription.cancel(); @@ -222,19 +229,19 @@ class _WindowsDirectoryWatcher // directory's full contents will be examined anyway, so we ignore such // events. Emitting them could cause useless or out-of-order events. var directories = unionAll(batch.map((event) { - if (!event.isDirectory) return Set(); + if (!event.isDirectory) return {}; if (event is FileSystemMoveEvent) { - return Set.from([event.path, event.destination]); + return {event.path, event.destination}; } - return Set.from([event.path]); + return {event.path}; })); - isInModifiedDirectory(String path) => + bool isInModifiedDirectory(String path) => directories.any((dir) => path != dir && path.startsWith(dir)); - addEvent(String path, FileSystemEvent event) { + void addEvent(String path, FileSystemEvent event) { if (isInModifiedDirectory(path)) return; - eventsForPaths.putIfAbsent(path, () => Set()).add(event); + eventsForPaths.putIfAbsent(path, () => {}).add(event); } for (var event in batch) { diff --git a/pkgs/watcher/lib/src/file_watcher/native.dart b/pkgs/watcher/lib/src/file_watcher/native.dart index ff25eb74e..7f466af3f 100644 --- a/pkgs/watcher/lib/src/file_watcher/native.dart +++ b/pkgs/watcher/lib/src/file_watcher/native.dart @@ -19,13 +19,17 @@ class NativeFileWatcher extends ResubscribableWatcher implements FileWatcher { } class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { + @override final String path; + @override Stream get events => _eventsController.stream; final _eventsController = StreamController.broadcast(); + @override bool get isReady => _readyCompleter.isCompleted; + @override Future get ready => _readyCompleter.future; final _readyCompleter = Completer(); @@ -57,7 +61,7 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { _eventsController.add(WatchEvent(ChangeType.MODIFY, path)); } - _onDone() async { + void _onDone() async { var fileExists = await File(path).exists(); // Check for this after checking whether the file exists because it's @@ -77,6 +81,7 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { } } + @override void close() { if (_subscription != null) _subscription.cancel(); _subscription = null; diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart index e2bf5dd95..11c0c6d9f 100644 --- a/pkgs/watcher/lib/src/file_watcher/polling.dart +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -17,18 +17,22 @@ class PollingFileWatcher extends ResubscribableWatcher implements FileWatcher { PollingFileWatcher(String path, {Duration pollingDelay}) : super(path, () { return _PollingFileWatcher( - path, pollingDelay != null ? pollingDelay : Duration(seconds: 1)); + path, pollingDelay ?? Duration(seconds: 1)); }); } class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { + @override final String path; + @override Stream get events => _eventsController.stream; final _eventsController = StreamController.broadcast(); + @override bool get isReady => _readyCompleter.isCompleted; + @override Future get ready => _readyCompleter.future; final _readyCompleter = Completer(); @@ -84,6 +88,7 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { } } + @override Future close() async { _timer.cancel(); await _eventsController.close(); diff --git a/pkgs/watcher/lib/src/path_set.dart b/pkgs/watcher/lib/src/path_set.dart index d6983ffb1..41a0a390b 100644 --- a/pkgs/watcher/lib/src/path_set.dart +++ b/pkgs/watcher/lib/src/path_set.dart @@ -61,7 +61,7 @@ class PathSet { // the next level. var part = parts.removeFirst(); var entry = dir.contents[part]; - if (entry == null || entry.contents.isEmpty) return Set(); + if (entry == null || entry.contents.isEmpty) return {}; partialPath = p.join(partialPath, part); var paths = recurse(entry, partialPath); @@ -75,10 +75,10 @@ class PathSet { // If there's only one component left in [path], we should remove it. var entry = dir.contents.remove(parts.first); - if (entry == null) return Set(); + if (entry == null) return {}; if (entry.contents.isEmpty) { - return Set.from([p.join(root, path)]); + return {p.join(root, path)}; } var set = _explicitPathsWithin(entry, path); @@ -96,8 +96,8 @@ class PathSet { /// /// [dirPath] should be the path to [dir]. Set _explicitPathsWithin(_Entry dir, String dirPath) { - var paths = Set(); - recurse(_Entry dir, String path) { + var paths = {}; + void recurse(_Entry dir, String path) { dir.contents.forEach((name, entry) { var entryPath = p.join(path, name); if (entry.isExplicit) paths.add(p.join(root, entryPath)); @@ -143,7 +143,7 @@ class PathSet { List get paths { var result = []; - recurse(_Entry dir, String path) { + void recurse(_Entry dir, String path) { for (var name in dir.contents.keys) { var entry = dir.contents[name]; var entryPath = p.join(path, name); diff --git a/pkgs/watcher/lib/src/resubscribable.dart b/pkgs/watcher/lib/src/resubscribable.dart index 8de3dfb5a..071909607 100644 --- a/pkgs/watcher/lib/src/resubscribable.dart +++ b/pkgs/watcher/lib/src/resubscribable.dart @@ -24,13 +24,17 @@ abstract class ResubscribableWatcher implements Watcher { /// The factory function that produces instances of the inner class. final ManuallyClosedWatcher Function() _factory; + @override final String path; + @override Stream get events => _eventsController.stream; StreamController _eventsController; + @override bool get isReady => _readyCompleter.isCompleted; + @override Future get ready => _readyCompleter.future; var _readyCompleter = Completer(); diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index 676ae281b..24b8184c2 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -12,13 +12,13 @@ bool isDirectoryNotFoundException(error) { if (error is! FileSystemException) return false; // See dartbug.com/12461 and tests/standalone/io/directory_error_test.dart. - var notFoundCode = Platform.operatingSystem == "windows" ? 3 : 2; + var notFoundCode = Platform.operatingSystem == 'windows' ? 3 : 2; return error.osError.errorCode == notFoundCode; } /// Returns the union of all elements in each set in [sets]. Set unionAll(Iterable> sets) => - sets.fold(Set(), (union, set) => union.union(set)); + sets.fold({}, (union, set) => union.union(set)); /// A stream transformer that batches all events that are sent at the same time. /// @@ -28,6 +28,7 @@ Set unionAll(Iterable> sets) => /// batches, this collates all the events that are received in "nearby" /// microtasks. class BatchedStreamTransformer extends StreamTransformerBase> { + @override Stream> bind(Stream input) { var batch = Queue(); return StreamTransformer>.fromHandlers( diff --git a/pkgs/watcher/lib/src/watch_event.dart b/pkgs/watcher/lib/src/watch_event.dart index 94ee5cb89..8b3fabb77 100644 --- a/pkgs/watcher/lib/src/watch_event.dart +++ b/pkgs/watcher/lib/src/watch_event.dart @@ -12,22 +12,24 @@ class WatchEvent { WatchEvent(this.type, this.path); - String toString() => "$type $path"; + @override + String toString() => '$type $path'; } /// Enum for what kind of change has happened to a file. class ChangeType { /// A new file has been added. - static const ADD = ChangeType("add"); + static const ADD = ChangeType('add'); /// A file has been removed. - static const REMOVE = ChangeType("remove"); + static const REMOVE = ChangeType('remove'); /// The contents of a file have changed. - static const MODIFY = ChangeType("modify"); + static const MODIFY = ChangeType('modify'); final String _name; const ChangeType(this._name); + @override String toString() => _name; } diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index f8315bc63..9aa0eaf9f 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+13 +version: 0.9.7+14-dev description: >- A file system watcher. It monitors changes to contents of directories and @@ -8,7 +8,7 @@ author: Dart Team homepage: https://github.com/dart-lang/watcher environment: - sdk: '>=2.0.0 <3.0.0' + sdk: '>=2.2.0 <3.0.0' dependencies: async: '>=1.10.0 <3.0.0' diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart index 0b81919a2..b4745a33f 100644 --- a/pkgs/watcher/test/directory_watcher/linux_test.dart +++ b/pkgs/watcher/test/directory_watcher/linux_test.dart @@ -23,21 +23,21 @@ void main() { test('emits events for many nested files moved out then immediately back in', () async { withPermutations( - (i, j, k) => writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); - await startWatcher(path: "dir"); + (i, j, k) => writeFile('dir/sub/sub-$i/sub-$j/file-$k.txt')); + await startWatcher(path: 'dir'); - renameDir("dir/sub", "sub"); - renameDir("sub", "dir/sub"); + renameDir('dir/sub', 'sub'); + renameDir('sub', 'dir/sub'); await allowEither(() { inAnyOrder(withPermutations( - (i, j, k) => isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + (i, j, k) => isRemoveEvent('dir/sub/sub-$i/sub-$j/file-$k.txt'))); inAnyOrder(withPermutations( - (i, j, k) => isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + (i, j, k) => isAddEvent('dir/sub/sub-$i/sub-$j/file-$k.txt'))); }, () { inAnyOrder(withPermutations( - (i, j, k) => isModifyEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + (i, j, k) => isModifyEvent('dir/sub/sub-$i/sub-$j/file-$k.txt'))); }); }); } diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 1470f71dc..b100f5970 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -23,35 +23,35 @@ void main() { test( 'does not notify about the watched directory being deleted and ' 'recreated immediately before watching', () async { - createDir("dir"); - writeFile("dir/old.txt"); - deleteDir("dir"); - createDir("dir"); - - await startWatcher(path: "dir"); - writeFile("dir/newer.txt"); - await expectAddEvent("dir/newer.txt"); + createDir('dir'); + writeFile('dir/old.txt'); + deleteDir('dir'); + createDir('dir'); + + await startWatcher(path: 'dir'); + writeFile('dir/newer.txt'); + await expectAddEvent('dir/newer.txt'); }); test('emits events for many nested files moved out then immediately back in', () async { withPermutations( - (i, j, k) => writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); + (i, j, k) => writeFile('dir/sub/sub-$i/sub-$j/file-$k.txt')); - await startWatcher(path: "dir"); + await startWatcher(path: 'dir'); - renameDir("dir/sub", "sub"); - renameDir("sub", "dir/sub"); + renameDir('dir/sub', 'sub'); + renameDir('sub', 'dir/sub'); await allowEither(() { inAnyOrder(withPermutations( - (i, j, k) => isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + (i, j, k) => isRemoveEvent('dir/sub/sub-$i/sub-$j/file-$k.txt'))); inAnyOrder(withPermutations( - (i, j, k) => isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + (i, j, k) => isAddEvent('dir/sub/sub-$i/sub-$j/file-$k.txt'))); }, () { inAnyOrder(withPermutations( - (i, j, k) => isModifyEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + (i, j, k) => isModifyEvent('dir/sub/sub-$i/sub-$j/file-$k.txt'))); }); }); } diff --git a/pkgs/watcher/test/directory_watcher/polling_test.dart b/pkgs/watcher/test/directory_watcher/polling_test.dart index d64eb0706..261d0e998 100644 --- a/pkgs/watcher/test/directory_watcher/polling_test.dart +++ b/pkgs/watcher/test/directory_watcher/polling_test.dart @@ -16,11 +16,11 @@ void main() { sharedTests(); test('does not notify if the modification time did not change', () async { - writeFile("a.txt", contents: "before"); - writeFile("b.txt", contents: "before"); + writeFile('a.txt', contents: 'before'); + writeFile('b.txt', contents: 'before'); await startWatcher(); - writeFile("a.txt", contents: "after", updateModified: false); - writeFile("b.txt", contents: "after"); - await expectModifyEvent("b.txt"); + writeFile('a.txt', contents: 'after', updateModified: false); + writeFile('b.txt', contents: 'after'); + await expectModifyEvent('b.txt'); }); } diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index a302c933e..ebce488db 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -10,130 +10,130 @@ import '../utils.dart'; void sharedTests() { test('does not notify for files that already exist when started', () async { // Make some pre-existing files. - writeFile("a.txt"); - writeFile("b.txt"); + writeFile('a.txt'); + writeFile('b.txt'); await startWatcher(); // Change one after the watcher is running. - writeFile("b.txt", contents: "modified"); + writeFile('b.txt', contents: 'modified'); // We should get a modify event for the changed file, but no add events // for them before this. - await expectModifyEvent("b.txt"); + await expectModifyEvent('b.txt'); }); test('notifies when a file is added', () async { await startWatcher(); - writeFile("file.txt"); - await expectAddEvent("file.txt"); + writeFile('file.txt'); + await expectAddEvent('file.txt'); }); test('notifies when a file is modified', () async { - writeFile("file.txt"); + writeFile('file.txt'); await startWatcher(); - writeFile("file.txt", contents: "modified"); - await expectModifyEvent("file.txt"); + writeFile('file.txt', contents: 'modified'); + await expectModifyEvent('file.txt'); }); test('notifies when a file is removed', () async { - writeFile("file.txt"); + writeFile('file.txt'); await startWatcher(); - deleteFile("file.txt"); - await expectRemoveEvent("file.txt"); + deleteFile('file.txt'); + await expectRemoveEvent('file.txt'); }); test('notifies when a file is modified multiple times', () async { - writeFile("file.txt"); + writeFile('file.txt'); await startWatcher(); - writeFile("file.txt", contents: "modified"); - await expectModifyEvent("file.txt"); - writeFile("file.txt", contents: "modified again"); - await expectModifyEvent("file.txt"); + writeFile('file.txt', contents: 'modified'); + await expectModifyEvent('file.txt'); + writeFile('file.txt', contents: 'modified again'); + await expectModifyEvent('file.txt'); }); test('notifies even if the file contents are unchanged', () async { - writeFile("a.txt", contents: "same"); - writeFile("b.txt", contents: "before"); + writeFile('a.txt', contents: 'same'); + writeFile('b.txt', contents: 'before'); await startWatcher(); - writeFile("a.txt", contents: "same"); - writeFile("b.txt", contents: "after"); - await inAnyOrder([isModifyEvent("a.txt"), isModifyEvent("b.txt")]); + writeFile('a.txt', contents: 'same'); + writeFile('b.txt', contents: 'after'); + await inAnyOrder([isModifyEvent('a.txt'), isModifyEvent('b.txt')]); }); test('when the watched directory is deleted, removes all files', () async { - writeFile("dir/a.txt"); - writeFile("dir/b.txt"); + writeFile('dir/a.txt'); + writeFile('dir/b.txt'); - await startWatcher(path: "dir"); + await startWatcher(path: 'dir'); - deleteDir("dir"); - await inAnyOrder([isRemoveEvent("dir/a.txt"), isRemoveEvent("dir/b.txt")]); + deleteDir('dir'); + await inAnyOrder([isRemoveEvent('dir/a.txt'), isRemoveEvent('dir/b.txt')]); }); test('when the watched directory is moved, removes all files', () async { - writeFile("dir/a.txt"); - writeFile("dir/b.txt"); + writeFile('dir/a.txt'); + writeFile('dir/b.txt'); - await startWatcher(path: "dir"); + await startWatcher(path: 'dir'); - renameDir("dir", "moved_dir"); - createDir("dir"); - await inAnyOrder([isRemoveEvent("dir/a.txt"), isRemoveEvent("dir/b.txt")]); + renameDir('dir', 'moved_dir'); + createDir('dir'); + await inAnyOrder([isRemoveEvent('dir/a.txt'), isRemoveEvent('dir/b.txt')]); }); // Regression test for b/30768513. test( "doesn't crash when the directory is moved immediately after a subdir " - "is added", () async { - writeFile("dir/a.txt"); - writeFile("dir/b.txt"); + 'is added', () async { + writeFile('dir/a.txt'); + writeFile('dir/b.txt'); - await startWatcher(path: "dir"); + await startWatcher(path: 'dir'); - createDir("dir/subdir"); - renameDir("dir", "moved_dir"); - createDir("dir"); - await inAnyOrder([isRemoveEvent("dir/a.txt"), isRemoveEvent("dir/b.txt")]); + createDir('dir/subdir'); + renameDir('dir', 'moved_dir'); + createDir('dir'); + await inAnyOrder([isRemoveEvent('dir/a.txt'), isRemoveEvent('dir/b.txt')]); }); - group("moves", () { + group('moves', () { test('notifies when a file is moved within the watched directory', () async { - writeFile("old.txt"); + writeFile('old.txt'); await startWatcher(); - renameFile("old.txt", "new.txt"); + renameFile('old.txt', 'new.txt'); - await inAnyOrder([isAddEvent("new.txt"), isRemoveEvent("old.txt")]); + await inAnyOrder([isAddEvent('new.txt'), isRemoveEvent('old.txt')]); }); test('notifies when a file is moved from outside the watched directory', () async { - writeFile("old.txt"); - createDir("dir"); - await startWatcher(path: "dir"); + writeFile('old.txt'); + createDir('dir'); + await startWatcher(path: 'dir'); - renameFile("old.txt", "dir/new.txt"); - await expectAddEvent("dir/new.txt"); + renameFile('old.txt', 'dir/new.txt'); + await expectAddEvent('dir/new.txt'); }); test('notifies when a file is moved outside the watched directory', () async { - writeFile("dir/old.txt"); - await startWatcher(path: "dir"); + writeFile('dir/old.txt'); + await startWatcher(path: 'dir'); - renameFile("dir/old.txt", "new.txt"); - await expectRemoveEvent("dir/old.txt"); + renameFile('dir/old.txt', 'new.txt'); + await expectRemoveEvent('dir/old.txt'); }); test('notifies when a file is moved onto an existing one', () async { - writeFile("from.txt"); - writeFile("to.txt"); + writeFile('from.txt'); + writeFile('to.txt'); await startWatcher(); - renameFile("from.txt", "to.txt"); - await inAnyOrder([isRemoveEvent("from.txt"), isModifyEvent("to.txt")]); + renameFile('from.txt', 'to.txt'); + await inAnyOrder([isRemoveEvent('from.txt'), isModifyEvent('to.txt')]); }); }); @@ -144,198 +144,198 @@ void sharedTests() { // separate batches, and the watcher will report them as though they occurred // far apart in time, so each of these tests has a "backup case" to allow for // that as well. - group("clustered changes", () { + group('clustered changes', () { test("doesn't notify when a file is created and then immediately removed", () async { - writeFile("test.txt"); + writeFile('test.txt'); await startWatcher(); - writeFile("file.txt"); - deleteFile("file.txt"); + writeFile('file.txt'); + deleteFile('file.txt'); // Backup case. startClosingEventStream(); await allowEvents(() { - expectAddEvent("file.txt"); - expectRemoveEvent("file.txt"); + expectAddEvent('file.txt'); + expectRemoveEvent('file.txt'); }); }); test( - "reports a modification when a file is deleted and then immediately " - "recreated", () async { - writeFile("file.txt"); + 'reports a modification when a file is deleted and then immediately ' + 'recreated', () async { + writeFile('file.txt'); await startWatcher(); - deleteFile("file.txt"); - writeFile("file.txt", contents: "re-created"); + deleteFile('file.txt'); + writeFile('file.txt', contents: 're-created'); await allowEither(() { - expectModifyEvent("file.txt"); + expectModifyEvent('file.txt'); }, () { // Backup case. - expectRemoveEvent("file.txt"); - expectAddEvent("file.txt"); + expectRemoveEvent('file.txt'); + expectAddEvent('file.txt'); }); }); test( - "reports a modification when a file is moved and then immediately " - "recreated", () async { - writeFile("old.txt"); + 'reports a modification when a file is moved and then immediately ' + 'recreated', () async { + writeFile('old.txt'); await startWatcher(); - renameFile("old.txt", "new.txt"); - writeFile("old.txt", contents: "re-created"); + renameFile('old.txt', 'new.txt'); + writeFile('old.txt', contents: 're-created'); await allowEither(() { - inAnyOrder([isModifyEvent("old.txt"), isAddEvent("new.txt")]); + inAnyOrder([isModifyEvent('old.txt'), isAddEvent('new.txt')]); }, () { // Backup case. - expectRemoveEvent("old.txt"); - expectAddEvent("new.txt"); - expectAddEvent("old.txt"); + expectRemoveEvent('old.txt'); + expectAddEvent('new.txt'); + expectAddEvent('old.txt'); }); }); test( - "reports a removal when a file is modified and then immediately " - "removed", () async { - writeFile("file.txt"); + 'reports a removal when a file is modified and then immediately ' + 'removed', () async { + writeFile('file.txt'); await startWatcher(); - writeFile("file.txt", contents: "modified"); - deleteFile("file.txt"); + writeFile('file.txt', contents: 'modified'); + deleteFile('file.txt'); // Backup case. - await allowModifyEvent("file.txt"); + await allowModifyEvent('file.txt'); - await expectRemoveEvent("file.txt"); + await expectRemoveEvent('file.txt'); }); - test("reports an add when a file is added and then immediately modified", + test('reports an add when a file is added and then immediately modified', () async { await startWatcher(); - writeFile("file.txt"); - writeFile("file.txt", contents: "modified"); + writeFile('file.txt'); + writeFile('file.txt', contents: 'modified'); - await expectAddEvent("file.txt"); + await expectAddEvent('file.txt'); // Backup case. startClosingEventStream(); - await allowModifyEvent("file.txt"); + await allowModifyEvent('file.txt'); }); }); - group("subdirectories", () { + group('subdirectories', () { test('watches files in subdirectories', () async { await startWatcher(); - writeFile("a/b/c/d/file.txt"); - await expectAddEvent("a/b/c/d/file.txt"); + writeFile('a/b/c/d/file.txt'); + await expectAddEvent('a/b/c/d/file.txt'); }); test( 'notifies when a subdirectory is moved within the watched directory ' 'and then its contents are modified', () async { - writeFile("old/file.txt"); + writeFile('old/file.txt'); await startWatcher(); - renameDir("old", "new"); + renameDir('old', 'new'); await inAnyOrder( - [isRemoveEvent("old/file.txt"), isAddEvent("new/file.txt")]); + [isRemoveEvent('old/file.txt'), isAddEvent('new/file.txt')]); - writeFile("new/file.txt", contents: "modified"); - await expectModifyEvent("new/file.txt"); + writeFile('new/file.txt', contents: 'modified'); + await expectModifyEvent('new/file.txt'); }); test('notifies when a file is replaced by a subdirectory', () async { - writeFile("new"); - writeFile("old/file.txt"); + writeFile('new'); + writeFile('old/file.txt'); await startWatcher(); - deleteFile("new"); - renameDir("old", "new"); + deleteFile('new'); + renameDir('old', 'new'); await inAnyOrder([ - isRemoveEvent("new"), - isRemoveEvent("old/file.txt"), - isAddEvent("new/file.txt") + isRemoveEvent('new'), + isRemoveEvent('old/file.txt'), + isAddEvent('new/file.txt') ]); }); test('notifies when a subdirectory is replaced by a file', () async { - writeFile("old"); - writeFile("new/file.txt"); + writeFile('old'); + writeFile('new/file.txt'); await startWatcher(); - renameDir("new", "newer"); - renameFile("old", "new"); + renameDir('new', 'newer'); + renameFile('old', 'new'); await inAnyOrder([ - isRemoveEvent("new/file.txt"), - isAddEvent("newer/file.txt"), - isRemoveEvent("old"), - isAddEvent("new") + isRemoveEvent('new/file.txt'), + isAddEvent('newer/file.txt'), + isRemoveEvent('old'), + isAddEvent('new') ]); }, onPlatform: { - "mac-os": Skip("https://github.com/dart-lang/watcher/issues/21") + 'mac-os': Skip('https://github.com/dart-lang/watcher/issues/21') }); test('emits events for many nested files added at once', () async { - withPermutations((i, j, k) => writeFile("sub/sub-$i/sub-$j/file-$k.txt")); + withPermutations((i, j, k) => writeFile('sub/sub-$i/sub-$j/file-$k.txt')); - createDir("dir"); - await startWatcher(path: "dir"); - renameDir("sub", "dir/sub"); + createDir('dir'); + await startWatcher(path: 'dir'); + renameDir('sub', 'dir/sub'); await inAnyOrder(withPermutations( - (i, j, k) => isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + (i, j, k) => isAddEvent('dir/sub/sub-$i/sub-$j/file-$k.txt'))); }); test('emits events for many nested files removed at once', () async { withPermutations( - (i, j, k) => writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt")); + (i, j, k) => writeFile('dir/sub/sub-$i/sub-$j/file-$k.txt')); - createDir("dir"); - await startWatcher(path: "dir"); + createDir('dir'); + await startWatcher(path: 'dir'); // Rename the directory rather than deleting it because native watchers // report a rename as a single DELETE event for the directory, whereas // they report recursive deletion with DELETE events for every file in the // directory. - renameDir("dir/sub", "sub"); + renameDir('dir/sub', 'sub'); await inAnyOrder(withPermutations( - (i, j, k) => isRemoveEvent("dir/sub/sub-$i/sub-$j/file-$k.txt"))); + (i, j, k) => isRemoveEvent('dir/sub/sub-$i/sub-$j/file-$k.txt'))); }); test('emits events for many nested files moved at once', () async { withPermutations( - (i, j, k) => writeFile("dir/old/sub-$i/sub-$j/file-$k.txt")); + (i, j, k) => writeFile('dir/old/sub-$i/sub-$j/file-$k.txt')); - createDir("dir"); - await startWatcher(path: "dir"); - renameDir("dir/old", "dir/new"); + createDir('dir'); + await startWatcher(path: 'dir'); + renameDir('dir/old', 'dir/new'); await inAnyOrder(unionAll(withPermutations((i, j, k) { - return Set.from([ - isRemoveEvent("dir/old/sub-$i/sub-$j/file-$k.txt"), - isAddEvent("dir/new/sub-$i/sub-$j/file-$k.txt") - ]); + return { + isRemoveEvent('dir/old/sub-$i/sub-$j/file-$k.txt'), + isAddEvent('dir/new/sub-$i/sub-$j/file-$k.txt') + }; }))); }); test( - "emits events for many files added at once in a subdirectory with the " - "same name as a removed file", () async { - writeFile("dir/sub"); - withPermutations((i, j, k) => writeFile("old/sub-$i/sub-$j/file-$k.txt")); - await startWatcher(path: "dir"); + 'emits events for many files added at once in a subdirectory with the ' + 'same name as a removed file', () async { + writeFile('dir/sub'); + withPermutations((i, j, k) => writeFile('old/sub-$i/sub-$j/file-$k.txt')); + await startWatcher(path: 'dir'); - deleteFile("dir/sub"); - renameDir("old", "dir/sub"); + deleteFile('dir/sub'); + renameDir('old', 'dir/sub'); var events = withPermutations( - (i, j, k) => isAddEvent("dir/sub/sub-$i/sub-$j/file-$k.txt")); - events.add(isRemoveEvent("dir/sub")); + (i, j, k) => isAddEvent('dir/sub/sub-$i/sub-$j/file-$k.txt')); + events.add(isRemoveEvent('dir/sub')); await inAnyOrder(events); }); }); diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 7931fa819..6ea412f77 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -16,9 +16,9 @@ void main() { // TODO(grouma) - renable when https://github.com/dart-lang/sdk/issues/31760 // is resolved. - group("Shared Tests:", () { + group('Shared Tests:', () { sharedTests(); - }, skip: "SDK issue see - https://github.com/dart-lang/sdk/issues/31760"); + }, skip: 'SDK issue see - https://github.com/dart-lang/sdk/issues/31760'); test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () { expect(DirectoryWatcher('.'), TypeMatcher()); diff --git a/pkgs/watcher/test/file_watcher/native_test.dart b/pkgs/watcher/test/file_watcher/native_test.dart index 2417dae60..b59d4ed95 100644 --- a/pkgs/watcher/test/file_watcher/native_test.dart +++ b/pkgs/watcher/test/file_watcher/native_test.dart @@ -14,7 +14,7 @@ void main() { watcherFactory = (file) => NativeFileWatcher(file); setUp(() { - writeFile("file.txt"); + writeFile('file.txt'); }); sharedTests(); diff --git a/pkgs/watcher/test/file_watcher/polling_test.dart b/pkgs/watcher/test/file_watcher/polling_test.dart index 9492f658e..b83d44fd1 100644 --- a/pkgs/watcher/test/file_watcher/polling_test.dart +++ b/pkgs/watcher/test/file_watcher/polling_test.dart @@ -15,7 +15,7 @@ void main() { PollingFileWatcher(file, pollingDelay: Duration(milliseconds: 100)); setUp(() { - writeFile("file.txt"); + writeFile('file.txt'); }); sharedTests(); diff --git a/pkgs/watcher/test/file_watcher/shared.dart b/pkgs/watcher/test/file_watcher/shared.dart index eefe5dfc4..c837a2136 100644 --- a/pkgs/watcher/test/file_watcher/shared.dart +++ b/pkgs/watcher/test/file_watcher/shared.dart @@ -10,60 +10,60 @@ import '../utils.dart'; void sharedTests() { test("doesn't notify if the file isn't modified", () async { - await startWatcher(path: "file.txt"); + await startWatcher(path: 'file.txt'); await pumpEventQueue(); - deleteFile("file.txt"); - await expectRemoveEvent("file.txt"); + deleteFile('file.txt'); + await expectRemoveEvent('file.txt'); }); - test("notifies when a file is modified", () async { - await startWatcher(path: "file.txt"); - writeFile("file.txt", contents: "modified"); - await expectModifyEvent("file.txt"); + test('notifies when a file is modified', () async { + await startWatcher(path: 'file.txt'); + writeFile('file.txt', contents: 'modified'); + await expectModifyEvent('file.txt'); }); - test("notifies when a file is removed", () async { - await startWatcher(path: "file.txt"); - deleteFile("file.txt"); - await expectRemoveEvent("file.txt"); + test('notifies when a file is removed', () async { + await startWatcher(path: 'file.txt'); + deleteFile('file.txt'); + await expectRemoveEvent('file.txt'); }); - test("notifies when a file is modified multiple times", () async { - await startWatcher(path: "file.txt"); - writeFile("file.txt", contents: "modified"); - await expectModifyEvent("file.txt"); - writeFile("file.txt", contents: "modified again"); - await expectModifyEvent("file.txt"); + test('notifies when a file is modified multiple times', () async { + await startWatcher(path: 'file.txt'); + writeFile('file.txt', contents: 'modified'); + await expectModifyEvent('file.txt'); + writeFile('file.txt', contents: 'modified again'); + await expectModifyEvent('file.txt'); }); - test("notifies even if the file contents are unchanged", () async { - await startWatcher(path: "file.txt"); - writeFile("file.txt"); - await expectModifyEvent("file.txt"); + test('notifies even if the file contents are unchanged', () async { + await startWatcher(path: 'file.txt'); + writeFile('file.txt'); + await expectModifyEvent('file.txt'); }); - test("emits a remove event when the watched file is moved away", () async { - await startWatcher(path: "file.txt"); - renameFile("file.txt", "new.txt"); - await expectRemoveEvent("file.txt"); + test('emits a remove event when the watched file is moved away', () async { + await startWatcher(path: 'file.txt'); + renameFile('file.txt', 'new.txt'); + await expectRemoveEvent('file.txt'); }); test( - "emits a modify event when another file is moved on top of the watched " - "file", () async { - writeFile("old.txt"); - await startWatcher(path: "file.txt"); - renameFile("old.txt", "file.txt"); - await expectModifyEvent("file.txt"); + 'emits a modify event when another file is moved on top of the watched ' + 'file', () async { + writeFile('old.txt'); + await startWatcher(path: 'file.txt'); + renameFile('old.txt', 'file.txt'); + await expectModifyEvent('file.txt'); }); // Regression test for a race condition. - test("closes the watcher immediately after deleting the file", () async { - writeFile("old.txt"); - var watcher = createWatcher(path: "file.txt"); + test('closes the watcher immediately after deleting the file', () async { + writeFile('old.txt'); + var watcher = createWatcher(path: 'file.txt'); var sub = watcher.events.listen(null); - deleteFile("file.txt"); + deleteFile('file.txt'); await Future.delayed(Duration(milliseconds: 10)); await sub.cancel(); }); diff --git a/pkgs/watcher/test/no_subscription/mac_os_test.dart b/pkgs/watcher/test/no_subscription/mac_os_test.dart index 5ffb11738..f2270770c 100644 --- a/pkgs/watcher/test/no_subscription/mac_os_test.dart +++ b/pkgs/watcher/test/no_subscription/mac_os_test.dart @@ -3,7 +3,7 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('mac-os') -@Skip("Flaky due to sdk#23877") +@Skip('Flaky due to sdk#23877') import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/mac_os.dart'; diff --git a/pkgs/watcher/test/no_subscription/shared.dart b/pkgs/watcher/test/no_subscription/shared.dart index e82692ec4..bcdba5f16 100644 --- a/pkgs/watcher/test/no_subscription/shared.dart +++ b/pkgs/watcher/test/no_subscription/shared.dart @@ -19,7 +19,7 @@ void sharedTests() { unawaited(queue.hasNext); var future = - expectLater(queue, emits(isWatchEvent(ChangeType.ADD, "file.txt"))); + expectLater(queue, emits(isWatchEvent(ChangeType.ADD, 'file.txt'))); expect(queue, neverEmits(anything)); await watcher.ready; @@ -32,18 +32,18 @@ void sharedTests() { await queue.cancel(immediate: true); // Now write a file while we aren't listening. - writeFile("unwatched.txt"); + writeFile('unwatched.txt'); queue = StreamQueue(watcher.events); future = - expectLater(queue, emits(isWatchEvent(ChangeType.ADD, "added.txt"))); - expect(queue, neverEmits(isWatchEvent(ChangeType.ADD, "unwatched.txt"))); + expectLater(queue, emits(isWatchEvent(ChangeType.ADD, 'added.txt'))); + expect(queue, neverEmits(isWatchEvent(ChangeType.ADD, 'unwatched.txt'))); // Wait until the watcher is ready to dispatch events again. await watcher.ready; // And add a third file. - writeFile("added.txt"); + writeFile('added.txt'); // Wait until we get an event for the third file. await future; diff --git a/pkgs/watcher/test/path_set_test.dart b/pkgs/watcher/test/path_set_test.dart index 9ca418180..25cf96943 100644 --- a/pkgs/watcher/test/path_set_test.dart +++ b/pkgs/watcher/test/path_set_test.dart @@ -16,210 +16,210 @@ Matcher containsDir(String path) => predicate( void main() { PathSet paths; - setUp(() => paths = PathSet("root")); + setUp(() => paths = PathSet('root')); - group("adding a path", () { - test("stores the path in the set", () { - paths.add("root/path/to/file"); - expect(paths, containsPath("root/path/to/file")); + group('adding a path', () { + test('stores the path in the set', () { + paths.add('root/path/to/file'); + expect(paths, containsPath('root/path/to/file')); }); test("that's a subdir of another path keeps both in the set", () { - paths.add("root/path"); - paths.add("root/path/to/file"); - expect(paths, containsPath("root/path")); - expect(paths, containsPath("root/path/to/file")); + paths.add('root/path'); + paths.add('root/path/to/file'); + expect(paths, containsPath('root/path')); + expect(paths, containsPath('root/path/to/file')); }); test("that's not normalized normalizes the path before storing it", () { - paths.add("root/../root/path/to/../to/././file"); - expect(paths, containsPath("root/path/to/file")); + paths.add('root/../root/path/to/../to/././file'); + expect(paths, containsPath('root/path/to/file')); }); test("that's absolute normalizes the path before storing it", () { - paths.add(p.absolute("root/path/to/file")); - expect(paths, containsPath("root/path/to/file")); + paths.add(p.absolute('root/path/to/file')); + expect(paths, containsPath('root/path/to/file')); }); }); - group("removing a path", () { + group('removing a path', () { test("that's in the set removes and returns that path", () { - paths.add("root/path/to/file"); - expect(paths.remove("root/path/to/file"), - unorderedEquals([p.normalize("root/path/to/file")])); - expect(paths, isNot(containsPath("root/path/to/file"))); + paths.add('root/path/to/file'); + expect(paths.remove('root/path/to/file'), + unorderedEquals([p.normalize('root/path/to/file')])); + expect(paths, isNot(containsPath('root/path/to/file'))); }); test("that's not in the set returns an empty set", () { - paths.add("root/path/to/file"); - expect(paths.remove("root/path/to/nothing"), isEmpty); + paths.add('root/path/to/file'); + expect(paths.remove('root/path/to/nothing'), isEmpty); }); test("that's a directory removes and returns all files beneath it", () { - paths.add("root/outside"); - paths.add("root/path/to/one"); - paths.add("root/path/to/two"); - paths.add("root/path/to/sub/three"); + paths.add('root/outside'); + paths.add('root/path/to/one'); + paths.add('root/path/to/two'); + paths.add('root/path/to/sub/three'); expect( - paths.remove("root/path"), + paths.remove('root/path'), unorderedEquals([ - "root/path/to/one", - "root/path/to/two", - "root/path/to/sub/three" + 'root/path/to/one', + 'root/path/to/two', + 'root/path/to/sub/three' ].map(p.normalize))); - expect(paths, containsPath("root/outside")); - expect(paths, isNot(containsPath("root/path/to/one"))); - expect(paths, isNot(containsPath("root/path/to/two"))); - expect(paths, isNot(containsPath("root/path/to/sub/three"))); + expect(paths, containsPath('root/outside')); + expect(paths, isNot(containsPath('root/path/to/one'))); + expect(paths, isNot(containsPath('root/path/to/two'))); + expect(paths, isNot(containsPath('root/path/to/sub/three'))); }); test( "that's a directory in the set removes and returns it and all files " - "beneath it", () { - paths.add("root/path"); - paths.add("root/path/to/one"); - paths.add("root/path/to/two"); - paths.add("root/path/to/sub/three"); + 'beneath it', () { + paths.add('root/path'); + paths.add('root/path/to/one'); + paths.add('root/path/to/two'); + paths.add('root/path/to/sub/three'); expect( - paths.remove("root/path"), + paths.remove('root/path'), unorderedEquals([ - "root/path", - "root/path/to/one", - "root/path/to/two", - "root/path/to/sub/three" + 'root/path', + 'root/path/to/one', + 'root/path/to/two', + 'root/path/to/sub/three' ].map(p.normalize))); - expect(paths, isNot(containsPath("root/path"))); - expect(paths, isNot(containsPath("root/path/to/one"))); - expect(paths, isNot(containsPath("root/path/to/two"))); - expect(paths, isNot(containsPath("root/path/to/sub/three"))); + expect(paths, isNot(containsPath('root/path'))); + expect(paths, isNot(containsPath('root/path/to/one'))); + expect(paths, isNot(containsPath('root/path/to/two'))); + expect(paths, isNot(containsPath('root/path/to/sub/three'))); }); test("that's not normalized removes and returns the normalized path", () { - paths.add("root/path/to/file"); - expect(paths.remove("root/../root/path/to/../to/./file"), - unorderedEquals([p.normalize("root/path/to/file")])); + paths.add('root/path/to/file'); + expect(paths.remove('root/../root/path/to/../to/./file'), + unorderedEquals([p.normalize('root/path/to/file')])); }); test("that's absolute removes and returns the normalized path", () { - paths.add("root/path/to/file"); - expect(paths.remove(p.absolute("root/path/to/file")), - unorderedEquals([p.normalize("root/path/to/file")])); + paths.add('root/path/to/file'); + expect(paths.remove(p.absolute('root/path/to/file')), + unorderedEquals([p.normalize('root/path/to/file')])); }); }); - group("containsPath()", () { - test("returns false for a non-existent path", () { - paths.add("root/path/to/file"); - expect(paths, isNot(containsPath("root/path/to/nothing"))); + group('containsPath()', () { + test('returns false for a non-existent path', () { + paths.add('root/path/to/file'); + expect(paths, isNot(containsPath('root/path/to/nothing'))); }); test("returns false for a directory that wasn't added explicitly", () { - paths.add("root/path/to/file"); - expect(paths, isNot(containsPath("root/path"))); + paths.add('root/path/to/file'); + expect(paths, isNot(containsPath('root/path'))); }); - test("returns true for a directory that was added explicitly", () { - paths.add("root/path"); - paths.add("root/path/to/file"); - expect(paths, containsPath("root/path")); + test('returns true for a directory that was added explicitly', () { + paths.add('root/path'); + paths.add('root/path/to/file'); + expect(paths, containsPath('root/path')); }); - test("with a non-normalized path normalizes the path before looking it up", + test('with a non-normalized path normalizes the path before looking it up', () { - paths.add("root/path/to/file"); - expect(paths, containsPath("root/../root/path/to/../to/././file")); + paths.add('root/path/to/file'); + expect(paths, containsPath('root/../root/path/to/../to/././file')); }); - test("with an absolute path normalizes the path before looking it up", () { - paths.add("root/path/to/file"); - expect(paths, containsPath(p.absolute("root/path/to/file"))); + test('with an absolute path normalizes the path before looking it up', () { + paths.add('root/path/to/file'); + expect(paths, containsPath(p.absolute('root/path/to/file'))); }); }); - group("containsDir()", () { - test("returns true for a directory that was added implicitly", () { - paths.add("root/path/to/file"); - expect(paths, containsDir("root/path")); - expect(paths, containsDir("root/path/to")); + group('containsDir()', () { + test('returns true for a directory that was added implicitly', () { + paths.add('root/path/to/file'); + expect(paths, containsDir('root/path')); + expect(paths, containsDir('root/path/to')); }); - test("returns true for a directory that was added explicitly", () { - paths.add("root/path"); - paths.add("root/path/to/file"); - expect(paths, containsDir("root/path")); + test('returns true for a directory that was added explicitly', () { + paths.add('root/path'); + paths.add('root/path/to/file'); + expect(paths, containsDir('root/path')); }); test("returns false for a directory that wasn't added", () { - expect(paths, isNot(containsDir("root/nothing"))); + expect(paths, isNot(containsDir('root/nothing'))); }); - test("returns false for a non-directory path that was added", () { - paths.add("root/path/to/file"); - expect(paths, isNot(containsDir("root/path/to/file"))); + test('returns false for a non-directory path that was added', () { + paths.add('root/path/to/file'); + expect(paths, isNot(containsDir('root/path/to/file'))); }); test( - "returns false for a directory that was added implicitly and then " - "removed implicitly", () { - paths.add("root/path/to/file"); - paths.remove("root/path/to/file"); - expect(paths, isNot(containsDir("root/path"))); + 'returns false for a directory that was added implicitly and then ' + 'removed implicitly', () { + paths.add('root/path/to/file'); + paths.remove('root/path/to/file'); + expect(paths, isNot(containsDir('root/path'))); }); test( - "returns false for a directory that was added explicitly whose " - "children were then removed", () { - paths.add("root/path"); - paths.add("root/path/to/file"); - paths.remove("root/path/to/file"); - expect(paths, isNot(containsDir("root/path"))); + 'returns false for a directory that was added explicitly whose ' + 'children were then removed', () { + paths.add('root/path'); + paths.add('root/path/to/file'); + paths.remove('root/path/to/file'); + expect(paths, isNot(containsDir('root/path'))); }); - test("with a non-normalized path normalizes the path before looking it up", + test('with a non-normalized path normalizes the path before looking it up', () { - paths.add("root/path/to/file"); - expect(paths, containsDir("root/../root/path/to/../to/.")); + paths.add('root/path/to/file'); + expect(paths, containsDir('root/../root/path/to/../to/.')); }); - test("with an absolute path normalizes the path before looking it up", () { - paths.add("root/path/to/file"); - expect(paths, containsDir(p.absolute("root/path"))); + test('with an absolute path normalizes the path before looking it up', () { + paths.add('root/path/to/file'); + expect(paths, containsDir(p.absolute('root/path'))); }); }); - group("paths", () { - test("returns paths added to the set", () { - paths.add("root/path"); - paths.add("root/path/to/one"); - paths.add("root/path/to/two"); + group('paths', () { + test('returns paths added to the set', () { + paths.add('root/path'); + paths.add('root/path/to/one'); + paths.add('root/path/to/two'); expect( paths.paths, unorderedEquals([ - "root/path", - "root/path/to/one", - "root/path/to/two", + 'root/path', + 'root/path/to/one', + 'root/path/to/two', ].map(p.normalize))); }); test("doesn't return paths removed from the set", () { - paths.add("root/path/to/one"); - paths.add("root/path/to/two"); - paths.remove("root/path/to/two"); + paths.add('root/path/to/one'); + paths.add('root/path/to/two'); + paths.remove('root/path/to/two'); - expect(paths.paths, unorderedEquals([p.normalize("root/path/to/one")])); + expect(paths.paths, unorderedEquals([p.normalize('root/path/to/one')])); }); }); - group("clear", () { - test("removes all paths from the set", () { - paths.add("root/path"); - paths.add("root/path/to/one"); - paths.add("root/path/to/two"); + group('clear', () { + test('removes all paths from the set', () { + paths.add('root/path'); + paths.add('root/path/to/one'); + paths.add('root/path/to/two'); paths.clear(); expect(paths.paths, isEmpty); diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 2e0ad014e..fe86407e2 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -58,7 +58,7 @@ Future startWatcher({String path}) async { path = p.normalize(p.relative(path, from: d.sandbox)); // Make sure we got a path in the sandbox. - assert(p.isRelative(path) && !path.startsWith("..")); + assert(p.isRelative(path) && !path.startsWith('..')); var mtime = _mockFileModificationTimes[path]; return DateTime.fromMillisecondsSinceEpoch(mtime ?? 0); @@ -92,9 +92,9 @@ List _collectedStreamMatchers; /// single stream matcher. /// /// The returned matcher will match each of the collected matchers in order. -StreamMatcher _collectStreamMatcher(block()) { +StreamMatcher _collectStreamMatcher(void Function() block) { var oldStreamMatchers = _collectedStreamMatchers; - _collectedStreamMatchers = List(); + _collectedStreamMatchers = []; try { block(); return emitsInOrder(_collectedStreamMatchers); @@ -128,15 +128,16 @@ Future inAnyOrder(Iterable matchers) { /// will match the emitted events. /// /// If both blocks match, the one that consumed more events will be used. -Future allowEither(block1(), block2()) => _expectOrCollect( - emitsAnyOf([_collectStreamMatcher(block1), _collectStreamMatcher(block2)])); +Future allowEither(void Function() block1, void Function() block2) => + _expectOrCollect(emitsAnyOf( + [_collectStreamMatcher(block1), _collectStreamMatcher(block2)])); /// Allows the expectations established in [block] to match the emitted events. /// /// If the expectations in [block] don't match, no error will be raised and no /// events will be consumed. If this is used at the end of a test, /// [startClosingEventStream] should be called before it. -Future allowEvents(block()) => +Future allowEvents(void Function() block) => _expectOrCollect(mayEmit(_collectStreamMatcher(block))); /// Returns a StreamMatcher that matches a [WatchEvent] with the given [type] @@ -146,7 +147,7 @@ Matcher isWatchEvent(ChangeType type, String path) { return e is WatchEvent && e.type == type && e.path == p.join(d.sandbox, p.normalize(path)); - }, "is $type $path"); + }, 'is $type $path'); } /// Returns a [Matcher] that matches a [WatchEvent] for an add event for [path]. @@ -202,8 +203,8 @@ Future allowRemoveEvent(String path) => /// If [contents] is omitted, creates an empty file. If [updateModified] is /// `false`, the mock file modification time is not changed. void writeFile(String path, {String contents, bool updateModified}) { - if (contents == null) contents = ""; - if (updateModified == null) updateModified = true; + contents ??= ''; + updateModified ??= true; var fullPath = p.join(d.sandbox, path); @@ -260,9 +261,9 @@ void deleteDir(String path) { /// Returns a set of all values returns by [callback]. /// /// [limit] defaults to 3. -Set withPermutations(S callback(int i, int j, int k), {int limit}) { - if (limit == null) limit = 3; - var results = Set(); +Set withPermutations(S Function(int, int, int) callback, {int limit}) { + limit ??= 3; + var results = {}; for (var i = 0; i < limit; i++) { for (var j = 0; j < limit; j++) { for (var k = 0; k < limit; k++) { From c1a13133966bfb9feb405e6d70ade9ff75ccbeef Mon Sep 17 00:00:00 2001 From: Jonas Termansen Date: Fri, 6 Mar 2020 17:53:46 +0100 Subject: [PATCH 127/201] Check for FileSystemEntityType.notFound (dart-lang/watcher#80) The Dart breaking change will change the timestamps on the notFound object from null to the epoch timestamp. --- pkgs/watcher/lib/src/stat.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/watcher/lib/src/stat.dart b/pkgs/watcher/lib/src/stat.dart index 6430d0b56..fb157a571 100644 --- a/pkgs/watcher/lib/src/stat.dart +++ b/pkgs/watcher/lib/src/stat.dart @@ -28,5 +28,6 @@ Future modificationTime(String path) async { } final stat = await FileStat.stat(path); + if (stat.type == FileSystemEntityType.notFound) return null; return stat.modified; } From 51c389da0aeb645666c784a6271e2fac896334bd Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Fri, 6 Mar 2020 09:31:04 -0800 Subject: [PATCH 128/201] Prepare to publish, tighten pub constraints (dart-lang/watcher#81) - Add a changelog entry. - Remove unused author key from pubspec. - Tighten the constraints on dependencies for any which had a lower major version bound than the package versions which support Dart 2. --- pkgs/watcher/CHANGELOG.md | 5 +++++ pkgs/watcher/pubspec.yaml | 9 ++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 2b3cd35b4..65ab5dfaf 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.9.7+14 + +* Prepare for breaking change in SDK where modified times for not found files + becomes meaningless instead of null. + # 0.9.7+13 * Catch & forward `FileSystemException` from unexpectedly closed file watchers diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 9aa0eaf9f..3982775c1 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,21 +1,20 @@ name: watcher -version: 0.9.7+14-dev +version: 0.9.7+14 description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. -author: Dart Team homepage: https://github.com/dart-lang/watcher environment: sdk: '>=2.2.0 <3.0.0' dependencies: - async: '>=1.10.0 <3.0.0' - path: '>=0.9.0 <2.0.0' + async: ^2.0.0 + path: ^1.0.0 pedantic: ^1.1.0 dev_dependencies: benchmark_harness: ^1.0.4 - test: '>=0.12.42 <2.0.0' + test: ^1.0.0 test_descriptor: ^1.0.0 From 303f88d2ff444468df8a408173ed780557e07aef Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 15 Apr 2020 17:38:26 -0700 Subject: [PATCH 129/201] Fix missing events for files with directory prefix (dart-lang/watcher#84) Fixes dart-lang/watcher#83 If any event had the same leading characters as a directory that was also changed it would get filtered out. For example if both `lib/b/` and `lib/b.dart` have change events we'd lose the events for `lib/b.dart` because it shared a prefix with a directory, even though it isn't contained within that directory. --- pkgs/watcher/CHANGELOG.md | 5 +++++ pkgs/watcher/lib/src/directory_watcher/mac_os.dart | 4 +++- pkgs/watcher/lib/src/directory_watcher/windows.dart | 2 +- pkgs/watcher/pubspec.yaml | 2 +- pkgs/watcher/test/directory_watcher/mac_os_test.dart | 11 +++++++++++ 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 65ab5dfaf..74565902a 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.9.7+15-dev + +* Fix a bug on Mac where modifying a directory with a path exactly matching a + prefix of a modified file would suppress change events for that file. + # 0.9.7+14 * Prepare for breaking change in SDK where modified times for not found files diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 73703cb4d..0aa056635 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -5,6 +5,8 @@ import 'dart:async'; import 'dart:io'; +import 'package:path/path.dart' as p; + import '../directory_watcher.dart'; import '../constructable_file_system_event.dart'; import '../path_set.dart'; @@ -196,7 +198,7 @@ class _MacOSDirectoryWatcher })); bool isInModifiedDirectory(String path) => - directories.any((dir) => path != dir && path.startsWith(dir)); + directories.any((dir) => path != dir && p.isWithin(dir, path)); void addEvent(String path, FileSystemEvent event) { if (isInModifiedDirectory(path)) return; diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 2a70edced..9a7de5f37 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -237,7 +237,7 @@ class _WindowsDirectoryWatcher })); bool isInModifiedDirectory(String path) => - directories.any((dir) => path != dir && path.startsWith(dir)); + directories.any((dir) => path != dir && p.isWithin(dir, path)); void addEvent(String path, FileSystemEvent event) { if (isInModifiedDirectory(path)) return; diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 3982775c1..230b00b2c 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+14 +version: 0.9.7+15-dev description: >- A file system watcher. It monitors changes to contents of directories and diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index b100f5970..58ba31aaa 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -54,4 +54,15 @@ void main() { (i, j, k) => isModifyEvent('dir/sub/sub-$i/sub-$j/file-$k.txt'))); }); }); + test('does not suppress files with the same prefix as a directory', () async { + // Regression test for https://github.com/dart-lang/watcher/issues/83 + writeFile('some_name.txt'); + + await startWatcher(); + + writeFile('some_name/some_name.txt'); + deleteFile('some_name.txt'); + + await expectRemoveEvent('some_name.txt'); + }); } From 82476532a57e78564e8edf1b90a4490ae1754cd0 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 15 Apr 2020 17:38:49 -0700 Subject: [PATCH 130/201] Use ?. over explicit null check (dart-lang/watcher#85) --- pkgs/watcher/lib/src/directory_watcher/linux.dart | 2 +- pkgs/watcher/lib/src/directory_watcher/mac_os.dart | 6 +++--- pkgs/watcher/lib/src/directory_watcher/polling.dart | 2 +- pkgs/watcher/lib/src/directory_watcher/windows.dart | 12 +++++------- pkgs/watcher/lib/src/file_watcher/native.dart | 2 +- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index 0a66a1291..7348cb45f 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -262,7 +262,7 @@ class _LinuxDirectoryWatcher StreamSubscription subscription; subscription = stream.listen(onData, onError: onError, onDone: () { _subscriptions.remove(subscription); - if (onDone != null) onDone(); + onDone?.call(); }, cancelOnError: cancelOnError); _subscriptions.add(subscription); } diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 0aa056635..4408ff1eb 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -95,8 +95,8 @@ class _MacOSDirectoryWatcher @override void close() { - if (_watchSubscription != null) _watchSubscription.cancel(); - if (_initialListSubscription != null) _initialListSubscription.cancel(); + _watchSubscription?.cancel(); + _initialListSubscription?.cancel(); _watchSubscription = null; _initialListSubscription = null; @@ -365,7 +365,7 @@ class _MacOSDirectoryWatcher /// of its state. Future _listDir() { assert(!isReady); - if (_initialListSubscription != null) _initialListSubscription.cancel(); + _initialListSubscription?.cancel(); _files.clear(); var completer = Completer(); diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 388f28add..902e8b861 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -92,7 +92,7 @@ class _PollingDirectoryWatcher _events.close(); // If we're in the middle of listing the directory, stop. - if (_listSubscription != null) _listSubscription.cancel(); + _listSubscription?.cancel(); // Don't process any remaining files. _filesToProcess.clear(); diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 9a7de5f37..f5093c5dc 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -32,9 +32,7 @@ class _EventBatcher { void addEvent(FileSystemEvent event, void Function() callback) { events.add(event); - if (timer != null) { - timer.cancel(); - } + timer?.cancel(); timer = Timer(_BATCH_DELAY, callback); } @@ -102,9 +100,9 @@ class _WindowsDirectoryWatcher @override void close() { - if (_watchSubscription != null) _watchSubscription.cancel(); - if (_parentWatchSubscription != null) _parentWatchSubscription.cancel(); - if (_initialListSubscription != null) _initialListSubscription.cancel(); + _watchSubscription?.cancel(); + _parentWatchSubscription?.cancel(); + _initialListSubscription?.cancel(); for (var sub in _listSubscriptions) { sub.cancel(); } @@ -401,7 +399,7 @@ class _WindowsDirectoryWatcher /// of its state. Future _listDir() { assert(!isReady); - if (_initialListSubscription != null) _initialListSubscription.cancel(); + _initialListSubscription?.cancel(); _files.clear(); var completer = Completer(); diff --git a/pkgs/watcher/lib/src/file_watcher/native.dart b/pkgs/watcher/lib/src/file_watcher/native.dart index 7f466af3f..0b42cb9b3 100644 --- a/pkgs/watcher/lib/src/file_watcher/native.dart +++ b/pkgs/watcher/lib/src/file_watcher/native.dart @@ -83,7 +83,7 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { @override void close() { - if (_subscription != null) _subscription.cancel(); + _subscription?.cancel(); _subscription = null; _eventsController.close(); } From 9faf03618142c358c5a18e216f3399648b4c26c3 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 16 Apr 2020 11:41:51 -0700 Subject: [PATCH 131/201] Run tests on multiple platforms (dart-lang/watcher#86) - Only use `--fatal-warnings` for the oldest supported SDK. Use `--fatal-infos` on the dev SDK. - Run tests on all 3 OSes. --- pkgs/watcher/.travis.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.travis.yml b/pkgs/watcher/.travis.yml index 9871d25bb..a3bc0703a 100644 --- a/pkgs/watcher/.travis.yml +++ b/pkgs/watcher/.travis.yml @@ -4,15 +4,24 @@ dart: - dev - 2.2.0 +os: +- linux +- windows +- osx + dart_task: - test -- dartanalyzer: --fatal-warnings --fatal-infos . matrix: include: - # Only validate formatting using the dev release - dart: dev dart_task: dartfmt + - dart: 2.2.0 + dart_task: + dartanalyzer: --fatal-warnings . + - dart: dev + dart_task: + dartanalyzer: --fatal-warnings --fatal-infos . # Only building master means that we don't run two builds for each pull request. branches: From 2131174b92a31faabcc8136ad88b41917c6912ee Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 22 Apr 2020 15:36:12 -0700 Subject: [PATCH 132/201] Prepare to publish (dart-lang/watcher#89) --- pkgs/watcher/CHANGELOG.md | 6 +++--- pkgs/watcher/pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 74565902a..a65a6e3b2 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.9.7+15-dev +# 0.9.7+15 * Fix a bug on Mac where modifying a directory with a path exactly matching a prefix of a modified file would suppress change events for that file. @@ -36,7 +36,7 @@ # 0.9.7+7 -* Updates to support Dart 2.0 core library changes (wave 2.2). +* Updates to support Dart 2.0 core library changes (wave 2.2). See [issue 31847][sdk#31847] for details. [sdk#31847]: https://github.com/dart-lang/sdk/issues/31847 @@ -44,7 +44,7 @@ # 0.9.7+6 -* Internal changes only, namely removing dep on scheduled test. +* Internal changes only, namely removing dep on scheduled test. # 0.9.7+5 diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 230b00b2c..e86192dd8 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.7+15-dev +version: 0.9.7+15 description: >- A file system watcher. It monitors changes to contents of directories and From 4ddf84d408247fb5198fbd480a93c6eb28011f96 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Tue, 28 Apr 2020 16:10:01 -0700 Subject: [PATCH 133/201] Add a reason to a flaky assert (dart-lang/watcher#88) We have seen a case of this assert failing on linux on travis. Add a reason which includes the path to hopefully get more information if we see it again. --- pkgs/watcher/test/utils.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index fe86407e2..06d1a1267 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -55,12 +55,13 @@ StreamQueue _watcherEvents; /// If [path] is provided, watches a path in the sandbox with that name. Future startWatcher({String path}) async { mockGetModificationTime((path) { - path = p.normalize(p.relative(path, from: d.sandbox)); + final normalized = p.normalize(p.relative(path, from: d.sandbox)); // Make sure we got a path in the sandbox. - assert(p.isRelative(path) && !path.startsWith('..')); + assert(p.isRelative(normalized) && !normalized.startsWith('..'), + 'Path is not in the sandbox: $path not in ${d.sandbox}'); - var mtime = _mockFileModificationTimes[path]; + var mtime = _mockFileModificationTimes[normalized]; return DateTime.fromMillisecondsSinceEpoch(mtime ?? 0); }); From 61480192638ae9550e2cb19bda3d6e69140a2b77 Mon Sep 17 00:00:00 2001 From: Michal Terepeta Date: Wed, 23 Sep 2020 15:42:22 +0200 Subject: [PATCH 134/201] Implement the ability to register custom watcher implementations This allows registering a special-purpose factory that returns its own `Watcher` implementation that will take precedence over the default ones. The main motivation for this is handling of file systems that need custom code to watch for changes. --- .../lib/src/custom_watcher_factory.dart | 46 +++++++ pkgs/watcher/lib/src/directory_watcher.dart | 11 +- pkgs/watcher/lib/src/file_watcher.dart | 8 ++ pkgs/watcher/lib/watcher.dart | 9 +- .../test/custom_watcher_factory_test.dart | 130 ++++++++++++++++++ 5 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 pkgs/watcher/lib/src/custom_watcher_factory.dart create mode 100644 pkgs/watcher/test/custom_watcher_factory_test.dart diff --git a/pkgs/watcher/lib/src/custom_watcher_factory.dart b/pkgs/watcher/lib/src/custom_watcher_factory.dart new file mode 100644 index 000000000..ffac06bba --- /dev/null +++ b/pkgs/watcher/lib/src/custom_watcher_factory.dart @@ -0,0 +1,46 @@ +import '../watcher.dart'; + +/// Defines a way to create a custom watcher instead of the default ones. +/// +/// This will be used when a [DirectoryWatcher] or [FileWatcher] would be +/// created and will take precedence over the default ones. +abstract class CustomWatcherFactory { + /// Uniquely identify this watcher. + String get id; + + /// Tries to create a [DirectoryWatcher] for the provided path. + /// + /// Should return `null` if the path is not supported by this factory. + DirectoryWatcher createDirectoryWatcher(String path, {Duration pollingDelay}); + + /// Tries to create a [FileWatcher] for the provided path. + /// + /// Should return `null` if the path is not supported by this factory. + FileWatcher createFileWatcher(String path, {Duration pollingDelay}); +} + +/// Registers a custom watcher. +/// +/// It's only allowed to register a watcher once per [id]. The [supportsPath] +/// will be called to determine if the [createWatcher] should be used instead of +/// the built-in watchers. +/// +/// Note that we will try [CustomWatcherFactory] one by one in the order they +/// were registered. +void registerCustomWatcherFactory(CustomWatcherFactory customFactory) { + if (_customWatcherFactories.containsKey(customFactory.id)) { + throw ArgumentError('A custom watcher with id `${customFactory.id}` ' + 'has already been registered'); + } + _customWatcherFactories[customFactory.id] = customFactory; +} + +/// Unregisters a custom watcher and returns it (returns `null` if it was never +/// registered). +CustomWatcherFactory unregisterCustomWatcherFactory(String id) => + _customWatcherFactories.remove(id); + +Iterable get customWatcherFactories => + _customWatcherFactories.values; + +final _customWatcherFactories = {}; diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index e0ef3fcc0..858d02009 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -5,10 +5,11 @@ import 'dart:io'; import '../watcher.dart'; +import 'custom_watcher_factory.dart'; import 'directory_watcher/linux.dart'; import 'directory_watcher/mac_os.dart'; -import 'directory_watcher/windows.dart'; import 'directory_watcher/polling.dart'; +import 'directory_watcher/windows.dart'; /// Watches the contents of a directory and emits [WatchEvent]s when something /// in the directory has changed. @@ -29,6 +30,14 @@ abstract class DirectoryWatcher implements Watcher { /// watchers. factory DirectoryWatcher(String directory, {Duration pollingDelay}) { if (FileSystemEntity.isWatchSupported) { + for (var custom in customWatcherFactories) { + var watcher = custom.createDirectoryWatcher(directory, + pollingDelay: pollingDelay); + if (watcher != null) { + return watcher; + } + } + if (Platform.isLinux) return LinuxDirectoryWatcher(directory); if (Platform.isMacOS) return MacOSDirectoryWatcher(directory); if (Platform.isWindows) return WindowsDirectoryWatcher(directory); diff --git a/pkgs/watcher/lib/src/file_watcher.dart b/pkgs/watcher/lib/src/file_watcher.dart index c4abddd6c..0b7afc787 100644 --- a/pkgs/watcher/lib/src/file_watcher.dart +++ b/pkgs/watcher/lib/src/file_watcher.dart @@ -5,6 +5,7 @@ import 'dart:io'; import '../watcher.dart'; +import 'custom_watcher_factory.dart'; import 'file_watcher/native.dart'; import 'file_watcher/polling.dart'; @@ -29,6 +30,13 @@ abstract class FileWatcher implements Watcher { /// and higher CPU usage. Defaults to one second. Ignored for non-polling /// watchers. factory FileWatcher(String file, {Duration pollingDelay}) { + for (var custom in customWatcherFactories) { + var watcher = custom.createFileWatcher(file, pollingDelay: pollingDelay); + if (watcher != null) { + return watcher; + } + } + // [File.watch] doesn't work on Windows, but // [FileSystemEntity.isWatchSupported] is still true because directory // watching does work. diff --git a/pkgs/watcher/lib/watcher.dart b/pkgs/watcher/lib/watcher.dart index 107ac8fae..f5c7d3ec8 100644 --- a/pkgs/watcher/lib/watcher.dart +++ b/pkgs/watcher/lib/watcher.dart @@ -5,15 +5,20 @@ import 'dart:async'; import 'dart:io'; -import 'src/watch_event.dart'; import 'src/directory_watcher.dart'; import 'src/file_watcher.dart'; +import 'src/watch_event.dart'; -export 'src/watch_event.dart'; +export 'src/custom_watcher_factory.dart' + show + CustomWatcherFactory, + registerCustomWatcherFactory, + unregisterCustomWatcherFactory; export 'src/directory_watcher.dart'; export 'src/directory_watcher/polling.dart'; export 'src/file_watcher.dart'; export 'src/file_watcher/polling.dart'; +export 'src/watch_event.dart'; abstract class Watcher { /// The path to the file or directory whose contents are being monitored. diff --git a/pkgs/watcher/test/custom_watcher_factory_test.dart b/pkgs/watcher/test/custom_watcher_factory_test.dart new file mode 100644 index 000000000..488b60796 --- /dev/null +++ b/pkgs/watcher/test/custom_watcher_factory_test.dart @@ -0,0 +1,130 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:test/test.dart'; +import 'package:watcher/watcher.dart'; + +void main() { + _MemFs memFs; + + setUp(() { + memFs = _MemFs(); + registerCustomWatcherFactory(_MemFsWatcherFactory(memFs)); + }); + + tearDown(() async { + unregisterCustomWatcherFactory('MemFs'); + }); + + test('notifes for files', () async { + var watcher = FileWatcher('file.txt'); + + var completer = Completer(); + watcher.events.listen((event) => completer.complete(event)); + await watcher.ready; + memFs.add('file.txt'); + var event = await completer.future; + + expect(event.type, ChangeType.ADD); + expect(event.path, 'file.txt'); + }); + + test('notifes for directories', () async { + var watcher = DirectoryWatcher('dir'); + + var completer = Completer(); + watcher.events.listen((event) => completer.complete(event)); + await watcher.ready; + memFs.add('dir'); + var event = await completer.future; + + expect(event.type, ChangeType.ADD); + expect(event.path, 'dir'); + }); + + test('unregister works', () async { + var memFactory = _MemFsWatcherFactory(memFs); + unregisterCustomWatcherFactory(memFactory.id); + + var completer = Completer(); + var watcher = FileWatcher('file.txt'); + watcher.events.listen((e) {}, onError: (e) => completer.complete(e)); + await watcher.ready; + memFs.add('file.txt'); + var result = await completer.future; + + expect(result, isA()); + }); + + test('registering twice throws', () async { + expect(() => registerCustomWatcherFactory(_MemFsWatcherFactory(memFs)), + throwsA(isA())); + }); +} + +class _MemFs { + final _streams = >>{}; + + StreamController watchStream(String path) { + var controller = StreamController(); + _streams.putIfAbsent(path, () => {}).add(controller); + return controller; + } + + void add(String path) { + var controllers = _streams[path]; + if (controllers != null) { + for (var controller in controllers) { + controller.add(WatchEvent(ChangeType.ADD, path)); + } + } + } + + void remove(String path) { + var controllers = _streams[path]; + if (controllers != null) { + for (var controller in controllers) { + controller.add(WatchEvent(ChangeType.REMOVE, path)); + } + } + } +} + +class _MemFsWatcher implements FileWatcher, DirectoryWatcher, Watcher { + final String _path; + final StreamController _controller; + + _MemFsWatcher(this._path, this._controller); + + @override + String get path => _path; + + @override + String get directory => throw UnsupportedError('directory is not supported'); + + @override + Stream get events => _controller.stream; + + @override + bool get isReady => true; + + @override + Future get ready async {} +} + +class _MemFsWatcherFactory implements CustomWatcherFactory { + final _MemFs _memFs; + _MemFsWatcherFactory(this._memFs); + + @override + String get id => 'MemFs'; + + @override + DirectoryWatcher createDirectoryWatcher(String path, + {Duration pollingDelay}) => + _MemFsWatcher(path, _memFs.watchStream(path)); + + @override + FileWatcher createFileWatcher(String path, {Duration pollingDelay}) => + _MemFsWatcher(path, _memFs.watchStream(path)); +} From 66a9b5cfe615a04a28d25067e64d6fa0dd6e9f5c Mon Sep 17 00:00:00 2001 From: Michal Terepeta Date: Thu, 24 Sep 2020 12:33:53 +0200 Subject: [PATCH 135/201] Fix the test that was timing out --- pkgs/watcher/test/custom_watcher_factory_test.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pkgs/watcher/test/custom_watcher_factory_test.dart b/pkgs/watcher/test/custom_watcher_factory_test.dart index 488b60796..784769c2a 100644 --- a/pkgs/watcher/test/custom_watcher_factory_test.dart +++ b/pkgs/watcher/test/custom_watcher_factory_test.dart @@ -46,14 +46,16 @@ void main() { var memFactory = _MemFsWatcherFactory(memFs); unregisterCustomWatcherFactory(memFactory.id); - var completer = Completer(); + var events = []; var watcher = FileWatcher('file.txt'); - watcher.events.listen((e) {}, onError: (e) => completer.complete(e)); + watcher.events.listen((e) => events.add(e)); await watcher.ready; - memFs.add('file.txt'); - var result = await completer.future; + memFs.add('a.txt'); + memFs.add('b.txt'); + memFs.add('c.txt'); + await Future.delayed(Duration(seconds: 1)); - expect(result, isA()); + expect(events, isEmpty); }); test('registering twice throws', () async { From b0d81fbf553b08dc61e029d54d2eeda1e924e3ed Mon Sep 17 00:00:00 2001 From: Michal Terepeta Date: Thu, 24 Sep 2020 14:54:44 +0200 Subject: [PATCH 136/201] Throw when more than one custom factory is applicable --- .../lib/src/custom_watcher_factory.dart | 57 +++++++++++++++---- pkgs/watcher/lib/src/directory_watcher.dart | 11 +--- pkgs/watcher/lib/src/file_watcher.dart | 9 +-- .../test/custom_watcher_factory_test.dart | 27 +++++---- 4 files changed, 70 insertions(+), 34 deletions(-) diff --git a/pkgs/watcher/lib/src/custom_watcher_factory.dart b/pkgs/watcher/lib/src/custom_watcher_factory.dart index ffac06bba..b7f81fa96 100644 --- a/pkgs/watcher/lib/src/custom_watcher_factory.dart +++ b/pkgs/watcher/lib/src/custom_watcher_factory.dart @@ -10,23 +10,20 @@ abstract class CustomWatcherFactory { /// Tries to create a [DirectoryWatcher] for the provided path. /// - /// Should return `null` if the path is not supported by this factory. + /// Returns `null` if the path is not supported by this factory. DirectoryWatcher createDirectoryWatcher(String path, {Duration pollingDelay}); /// Tries to create a [FileWatcher] for the provided path. /// - /// Should return `null` if the path is not supported by this factory. + /// Returns `null` if the path is not supported by this factory. FileWatcher createFileWatcher(String path, {Duration pollingDelay}); } /// Registers a custom watcher. /// -/// It's only allowed to register a watcher once per [id]. The [supportsPath] -/// will be called to determine if the [createWatcher] should be used instead of -/// the built-in watchers. -/// -/// Note that we will try [CustomWatcherFactory] one by one in the order they -/// were registered. +/// It's only allowed to register a watcher factory once per [id] and at most +/// one factory should apply to any given file (creating a [Watcher] will fail +/// otherwise). void registerCustomWatcherFactory(CustomWatcherFactory customFactory) { if (_customWatcherFactories.containsKey(customFactory.id)) { throw ArgumentError('A custom watcher with id `${customFactory.id}` ' @@ -35,8 +32,48 @@ void registerCustomWatcherFactory(CustomWatcherFactory customFactory) { _customWatcherFactories[customFactory.id] = customFactory; } -/// Unregisters a custom watcher and returns it (returns `null` if it was never -/// registered). +/// Tries to create a custom [DirectoryWatcher] and returns it. +/// +/// Returns `null` if no custom watcher was applicable and throws a [StateError] +/// if more than one was. +DirectoryWatcher createCustomDirectoryWatcher(String path, + {Duration pollingDelay}) { + DirectoryWatcher customWatcher; + String customFactoryId; + for (var watcherFactory in customWatcherFactories) { + if (customWatcher != null) { + throw StateError('Two `CustomWatcherFactory`s applicable: ' + '`$customFactoryId` and `${watcherFactory.id}` for `$path`'); + } + customWatcher = + watcherFactory.createDirectoryWatcher(path, pollingDelay: pollingDelay); + customFactoryId = watcherFactory.id; + } + return customWatcher; +} + +/// Tries to create a custom [FileWatcher] and returns it. +/// +/// Returns `null` if no custom watcher was applicable and throws a [StateError] +/// if more than one was. +FileWatcher createCustomFileWatcher(String path, {Duration pollingDelay}) { + FileWatcher customWatcher; + String customFactoryId; + for (var watcherFactory in customWatcherFactories) { + if (customWatcher != null) { + throw StateError('Two `CustomWatcherFactory`s applicable: ' + '`$customFactoryId` and `${watcherFactory.id}` for `$path`'); + } + customWatcher = + watcherFactory.createFileWatcher(path, pollingDelay: pollingDelay); + customFactoryId = watcherFactory.id; + } + return customWatcher; +} + +/// Unregisters a custom watcher and returns it. +/// +/// Returns `null` if the id was never registered. CustomWatcherFactory unregisterCustomWatcherFactory(String id) => _customWatcherFactories.remove(id); diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 858d02009..3fe500455 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -30,14 +30,9 @@ abstract class DirectoryWatcher implements Watcher { /// watchers. factory DirectoryWatcher(String directory, {Duration pollingDelay}) { if (FileSystemEntity.isWatchSupported) { - for (var custom in customWatcherFactories) { - var watcher = custom.createDirectoryWatcher(directory, - pollingDelay: pollingDelay); - if (watcher != null) { - return watcher; - } - } - + var customWatcher = + createCustomDirectoryWatcher(directory, pollingDelay: pollingDelay); + if (customWatcher != null) return customWatcher; if (Platform.isLinux) return LinuxDirectoryWatcher(directory); if (Platform.isMacOS) return MacOSDirectoryWatcher(directory); if (Platform.isWindows) return WindowsDirectoryWatcher(directory); diff --git a/pkgs/watcher/lib/src/file_watcher.dart b/pkgs/watcher/lib/src/file_watcher.dart index 0b7afc787..c41e5e678 100644 --- a/pkgs/watcher/lib/src/file_watcher.dart +++ b/pkgs/watcher/lib/src/file_watcher.dart @@ -30,12 +30,9 @@ abstract class FileWatcher implements Watcher { /// and higher CPU usage. Defaults to one second. Ignored for non-polling /// watchers. factory FileWatcher(String file, {Duration pollingDelay}) { - for (var custom in customWatcherFactories) { - var watcher = custom.createFileWatcher(file, pollingDelay: pollingDelay); - if (watcher != null) { - return watcher; - } - } + var customWatcher = + createCustomFileWatcher(file, pollingDelay: pollingDelay); + if (customWatcher != null) return customWatcher; // [File.watch] doesn't work on Windows, but // [FileSystemEntity.isWatchSupported] is still true because directory diff --git a/pkgs/watcher/test/custom_watcher_factory_test.dart b/pkgs/watcher/test/custom_watcher_factory_test.dart index 784769c2a..6d9ed406f 100644 --- a/pkgs/watcher/test/custom_watcher_factory_test.dart +++ b/pkgs/watcher/test/custom_watcher_factory_test.dart @@ -1,19 +1,19 @@ import 'dart:async'; -import 'dart:io'; import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; void main() { _MemFs memFs; + final defaultFactoryId = 'MemFs'; setUp(() { memFs = _MemFs(); - registerCustomWatcherFactory(_MemFsWatcherFactory(memFs)); + registerCustomWatcherFactory(_MemFsWatcherFactory(defaultFactoryId, memFs)); }); tearDown(() async { - unregisterCustomWatcherFactory('MemFs'); + unregisterCustomWatcherFactory(defaultFactoryId); }); test('notifes for files', () async { @@ -43,8 +43,7 @@ void main() { }); test('unregister works', () async { - var memFactory = _MemFsWatcherFactory(memFs); - unregisterCustomWatcherFactory(memFactory.id); + unregisterCustomWatcherFactory(defaultFactoryId); var events = []; var watcher = FileWatcher('file.txt'); @@ -59,9 +58,19 @@ void main() { }); test('registering twice throws', () async { - expect(() => registerCustomWatcherFactory(_MemFsWatcherFactory(memFs)), + expect( + () => registerCustomWatcherFactory( + _MemFsWatcherFactory(defaultFactoryId, memFs)), throwsA(isA())); }); + + test('finding two applicable factories throws', () async { + // Note that _MemFsWatcherFactory always returns a watcher, so having two + // will always produce a conflict. + registerCustomWatcherFactory(_MemFsWatcherFactory('Different id', memFs)); + expect(() => FileWatcher('file.txt'), throwsA(isA())); + expect(() => DirectoryWatcher('dir'), throwsA(isA())); + }); } class _MemFs { @@ -115,11 +124,9 @@ class _MemFsWatcher implements FileWatcher, DirectoryWatcher, Watcher { } class _MemFsWatcherFactory implements CustomWatcherFactory { + final String id; final _MemFs _memFs; - _MemFsWatcherFactory(this._memFs); - - @override - String get id => 'MemFs'; + _MemFsWatcherFactory(this.id, this._memFs); @override DirectoryWatcher createDirectoryWatcher(String path, From dd23dc4d09cd666ecc4e99854899ae0288b09e7e Mon Sep 17 00:00:00 2001 From: Michal Terepeta Date: Thu, 24 Sep 2020 16:01:46 +0200 Subject: [PATCH 137/201] Another attempt to make the test work cross platform --- .../test/custom_watcher_factory_test.dart | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/pkgs/watcher/test/custom_watcher_factory_test.dart b/pkgs/watcher/test/custom_watcher_factory_test.dart index 6d9ed406f..964a49d7b 100644 --- a/pkgs/watcher/test/custom_watcher_factory_test.dart +++ b/pkgs/watcher/test/custom_watcher_factory_test.dart @@ -3,6 +3,8 @@ import 'dart:async'; import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; +import 'utils.dart'; + void main() { _MemFs memFs; final defaultFactoryId = 'MemFs'; @@ -45,16 +47,18 @@ void main() { test('unregister works', () async { unregisterCustomWatcherFactory(defaultFactoryId); - var events = []; - var watcher = FileWatcher('file.txt'); - watcher.events.listen((e) => events.add(e)); - await watcher.ready; - memFs.add('a.txt'); - memFs.add('b.txt'); - memFs.add('c.txt'); - await Future.delayed(Duration(seconds: 1)); + watcherFactory = (path) => FileWatcher(path); + try { + // This uses standard files, so it wouldn't trigger an event in + // _MemFsWatcher. + writeFile('file.txt'); + await startWatcher(path: 'file.txt'); + deleteFile('file.txt'); + } finally { + watcherFactory = null; + } - expect(events, isEmpty); + await expectRemoveEvent('file.txt'); }); test('registering twice throws', () async { From 9f639cd813f7f76b72032111bbee755259cd36bf Mon Sep 17 00:00:00 2001 From: Michal Terepeta Date: Fri, 25 Sep 2020 08:24:35 +0200 Subject: [PATCH 138/201] Minor fixes for earlier Dart versions and lint --- pkgs/watcher/test/custom_watcher_factory_test.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkgs/watcher/test/custom_watcher_factory_test.dart b/pkgs/watcher/test/custom_watcher_factory_test.dart index 964a49d7b..6c9ffd873 100644 --- a/pkgs/watcher/test/custom_watcher_factory_test.dart +++ b/pkgs/watcher/test/custom_watcher_factory_test.dart @@ -82,7 +82,9 @@ class _MemFs { StreamController watchStream(String path) { var controller = StreamController(); - _streams.putIfAbsent(path, () => {}).add(controller); + _streams + .putIfAbsent(path, () => >{}) + .add(controller); return controller; } @@ -128,6 +130,7 @@ class _MemFsWatcher implements FileWatcher, DirectoryWatcher, Watcher { } class _MemFsWatcherFactory implements CustomWatcherFactory { + @override final String id; final _MemFs _memFs; _MemFsWatcherFactory(this.id, this._memFs); From 9329e8edcda570c858a8e95e06f455ac7ef917c1 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Mon, 12 Oct 2020 09:49:43 -0700 Subject: [PATCH 139/201] Expose a smaller surface area for custom watchers (dart-lang/watcher#92) - Remove the function to unregister a custom watcher, we can add it later when we have a specific use case for it. - Remove the class `CustomFactoryWatcher` and take callbacks instead. - Bump the version and add the changelog that was missed. - Add missing copyright notice. --- pkgs/watcher/CHANGELOG.md | 4 + .../lib/src/custom_watcher_factory.dart | 74 ++++++++++--------- pkgs/watcher/lib/watcher.dart | 6 +- .../test/custom_watcher_factory_test.dart | 47 ++++-------- 4 files changed, 57 insertions(+), 74 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index a65a6e3b2..a98ae202a 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.9.8 + +* Add the ability to create custom Watcher types for specific file paths. + # 0.9.7+15 * Fix a bug on Mac where modifying a directory with a path exactly matching a diff --git a/pkgs/watcher/lib/src/custom_watcher_factory.dart b/pkgs/watcher/lib/src/custom_watcher_factory.dart index b7f81fa96..3dc1beafc 100644 --- a/pkgs/watcher/lib/src/custom_watcher_factory.dart +++ b/pkgs/watcher/lib/src/custom_watcher_factory.dart @@ -1,35 +1,48 @@ -import '../watcher.dart'; +// Copyright (c) 2020, 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. -/// Defines a way to create a custom watcher instead of the default ones. -/// -/// This will be used when a [DirectoryWatcher] or [FileWatcher] would be -/// created and will take precedence over the default ones. -abstract class CustomWatcherFactory { - /// Uniquely identify this watcher. - String get id; +import '../watcher.dart'; - /// Tries to create a [DirectoryWatcher] for the provided path. - /// - /// Returns `null` if the path is not supported by this factory. - DirectoryWatcher createDirectoryWatcher(String path, {Duration pollingDelay}); +/// A factory to produce custom watchers for specific file paths. +class _CustomWatcherFactory { + final String id; + final DirectoryWatcher Function(String path, {Duration pollingDelay}) + createDirectoryWatcher; + final FileWatcher Function(String path, {Duration pollingDelay}) + createFileWatcher; - /// Tries to create a [FileWatcher] for the provided path. - /// - /// Returns `null` if the path is not supported by this factory. - FileWatcher createFileWatcher(String path, {Duration pollingDelay}); + _CustomWatcherFactory( + this.id, this.createDirectoryWatcher, this.createFileWatcher); } /// Registers a custom watcher. /// -/// It's only allowed to register a watcher factory once per [id] and at most -/// one factory should apply to any given file (creating a [Watcher] will fail -/// otherwise). -void registerCustomWatcherFactory(CustomWatcherFactory customFactory) { - if (_customWatcherFactories.containsKey(customFactory.id)) { - throw ArgumentError('A custom watcher with id `${customFactory.id}` ' +/// Each custom watcher must have a unique [id] and the same watcher may not be +/// registered more than once. +/// [createDirectoryWatcher] and [createFileWatcher] should return watchers for +/// the file paths they are able to handle. If the custom watcher is not able to +/// handle the path it should reuturn null. +/// The paths handled by each custom watch may not overlap, at most one custom +/// matcher may return a non-null watcher for a given path. +/// +/// When a file or directory watcher is created the path is checked against each +/// registered custom watcher, and if exactly one custom watcher is available it +/// will be used instead of the default. +void registerCustomWatcher( + String id, + DirectoryWatcher Function(String path, {Duration pollingDelay}) + createDirectoryWatcher, + FileWatcher Function(String path, {Duration pollingDelay}) createFileWatcher, +) { + if (_customWatcherFactories.containsKey(id)) { + throw ArgumentError('A custom watcher with id `$id` ' 'has already been registered'); } - _customWatcherFactories[customFactory.id] = customFactory; + _customWatcherFactories[id] = _CustomWatcherFactory( + id, + createDirectoryWatcher ?? (_, {pollingDelay}) => null, + createFileWatcher ?? (_, {pollingDelay}) => null); } /// Tries to create a custom [DirectoryWatcher] and returns it. @@ -40,7 +53,7 @@ DirectoryWatcher createCustomDirectoryWatcher(String path, {Duration pollingDelay}) { DirectoryWatcher customWatcher; String customFactoryId; - for (var watcherFactory in customWatcherFactories) { + for (var watcherFactory in _customWatcherFactories.values) { if (customWatcher != null) { throw StateError('Two `CustomWatcherFactory`s applicable: ' '`$customFactoryId` and `${watcherFactory.id}` for `$path`'); @@ -59,7 +72,7 @@ DirectoryWatcher createCustomDirectoryWatcher(String path, FileWatcher createCustomFileWatcher(String path, {Duration pollingDelay}) { FileWatcher customWatcher; String customFactoryId; - for (var watcherFactory in customWatcherFactories) { + for (var watcherFactory in _customWatcherFactories.values) { if (customWatcher != null) { throw StateError('Two `CustomWatcherFactory`s applicable: ' '`$customFactoryId` and `${watcherFactory.id}` for `$path`'); @@ -71,13 +84,4 @@ FileWatcher createCustomFileWatcher(String path, {Duration pollingDelay}) { return customWatcher; } -/// Unregisters a custom watcher and returns it. -/// -/// Returns `null` if the id was never registered. -CustomWatcherFactory unregisterCustomWatcherFactory(String id) => - _customWatcherFactories.remove(id); - -Iterable get customWatcherFactories => - _customWatcherFactories.values; - -final _customWatcherFactories = {}; +final _customWatcherFactories = {}; diff --git a/pkgs/watcher/lib/watcher.dart b/pkgs/watcher/lib/watcher.dart index f5c7d3ec8..5ea8beb21 100644 --- a/pkgs/watcher/lib/watcher.dart +++ b/pkgs/watcher/lib/watcher.dart @@ -9,11 +9,7 @@ import 'src/directory_watcher.dart'; import 'src/file_watcher.dart'; import 'src/watch_event.dart'; -export 'src/custom_watcher_factory.dart' - show - CustomWatcherFactory, - registerCustomWatcherFactory, - unregisterCustomWatcherFactory; +export 'src/custom_watcher_factory.dart' show registerCustomWatcher; export 'src/directory_watcher.dart'; export 'src/directory_watcher/polling.dart'; export 'src/file_watcher.dart'; diff --git a/pkgs/watcher/test/custom_watcher_factory_test.dart b/pkgs/watcher/test/custom_watcher_factory_test.dart index 6c9ffd873..8210c065b 100644 --- a/pkgs/watcher/test/custom_watcher_factory_test.dart +++ b/pkgs/watcher/test/custom_watcher_factory_test.dart @@ -3,19 +3,17 @@ import 'dart:async'; import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; -import 'utils.dart'; - void main() { _MemFs memFs; final defaultFactoryId = 'MemFs'; - setUp(() { + setUpAll(() { memFs = _MemFs(); - registerCustomWatcherFactory(_MemFsWatcherFactory(defaultFactoryId, memFs)); - }); - - tearDown(() async { - unregisterCustomWatcherFactory(defaultFactoryId); + var watcherFactory = _MemFsWatcherFactory(memFs); + registerCustomWatcher( + defaultFactoryId, + watcherFactory.createDirectoryWatcher, + watcherFactory.createFileWatcher); }); test('notifes for files', () async { @@ -44,34 +42,19 @@ void main() { expect(event.path, 'dir'); }); - test('unregister works', () async { - unregisterCustomWatcherFactory(defaultFactoryId); - - watcherFactory = (path) => FileWatcher(path); - try { - // This uses standard files, so it wouldn't trigger an event in - // _MemFsWatcher. - writeFile('file.txt'); - await startWatcher(path: 'file.txt'); - deleteFile('file.txt'); - } finally { - watcherFactory = null; - } - - await expectRemoveEvent('file.txt'); - }); - test('registering twice throws', () async { expect( - () => registerCustomWatcherFactory( - _MemFsWatcherFactory(defaultFactoryId, memFs)), + () => registerCustomWatcher(defaultFactoryId, + (_, {pollingDelay}) => null, (_, {pollingDelay}) => null), throwsA(isA())); }); test('finding two applicable factories throws', () async { // Note that _MemFsWatcherFactory always returns a watcher, so having two // will always produce a conflict. - registerCustomWatcherFactory(_MemFsWatcherFactory('Different id', memFs)); + var watcherFactory = _MemFsWatcherFactory(memFs); + registerCustomWatcher('Different id', watcherFactory.createDirectoryWatcher, + watcherFactory.createFileWatcher); expect(() => FileWatcher('file.txt'), throwsA(isA())); expect(() => DirectoryWatcher('dir'), throwsA(isA())); }); @@ -129,18 +112,14 @@ class _MemFsWatcher implements FileWatcher, DirectoryWatcher, Watcher { Future get ready async {} } -class _MemFsWatcherFactory implements CustomWatcherFactory { - @override - final String id; +class _MemFsWatcherFactory { final _MemFs _memFs; - _MemFsWatcherFactory(this.id, this._memFs); + _MemFsWatcherFactory(this._memFs); - @override DirectoryWatcher createDirectoryWatcher(String path, {Duration pollingDelay}) => _MemFsWatcher(path, _memFs.watchStream(path)); - @override FileWatcher createFileWatcher(String path, {Duration pollingDelay}) => _MemFsWatcher(path, _memFs.watchStream(path)); } From 01d5ab2f3c31f07060b814b23f3e68b62c832456 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 20 Oct 2020 16:25:34 -0700 Subject: [PATCH 140/201] Remove unused dart:async import The only members of dart:async used here (Future and/or Stream) are exported from dart:core now. --- pkgs/watcher/lib/src/async_queue.dart | 1 - pkgs/watcher/lib/src/stat.dart | 1 - pkgs/watcher/lib/watcher.dart | 1 - 3 files changed, 3 deletions(-) diff --git a/pkgs/watcher/lib/src/async_queue.dart b/pkgs/watcher/lib/src/async_queue.dart index 9f8bedf54..37f13d61d 100644 --- a/pkgs/watcher/lib/src/async_queue.dart +++ b/pkgs/watcher/lib/src/async_queue.dart @@ -2,7 +2,6 @@ // 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. -import 'dart:async'; import 'dart:collection'; typedef ItemProcessor = Future Function(T item); diff --git a/pkgs/watcher/lib/src/stat.dart b/pkgs/watcher/lib/src/stat.dart index fb157a571..08cf935c7 100644 --- a/pkgs/watcher/lib/src/stat.dart +++ b/pkgs/watcher/lib/src/stat.dart @@ -2,7 +2,6 @@ // 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. -import 'dart:async'; import 'dart:io'; /// A function that takes a file path and returns the last modified time for diff --git a/pkgs/watcher/lib/watcher.dart b/pkgs/watcher/lib/watcher.dart index 5ea8beb21..01336ab5a 100644 --- a/pkgs/watcher/lib/watcher.dart +++ b/pkgs/watcher/lib/watcher.dart @@ -2,7 +2,6 @@ // 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. -import 'dart:async'; import 'dart:io'; import 'src/directory_watcher.dart'; From 1edd6fe89cf0e6b2dd917a4cdc95b8d078137ee7 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 9 Nov 2020 19:59:25 -0800 Subject: [PATCH 141/201] Remove unused dart:async imports. As of Dart 2.1, Future/Stream have been exported from dart:core. --- pkgs/watcher/test/file_watcher/shared.dart | 2 -- pkgs/watcher/test/utils.dart | 1 - 2 files changed, 3 deletions(-) diff --git a/pkgs/watcher/test/file_watcher/shared.dart b/pkgs/watcher/test/file_watcher/shared.dart index c837a2136..9b72f0ed6 100644 --- a/pkgs/watcher/test/file_watcher/shared.dart +++ b/pkgs/watcher/test/file_watcher/shared.dart @@ -2,8 +2,6 @@ // 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. -import 'dart:async'; - import 'package:test/test.dart'; import '../utils.dart'; diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 06d1a1267..6e7686c27 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -2,7 +2,6 @@ // 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. -import 'dart:async'; import 'dart:io'; import 'package:async/async.dart'; From 2c89b9dddb2d0f491c8b2136a0dd808786d73532 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Thu, 12 Nov 2020 10:50:23 -0800 Subject: [PATCH 142/201] Fix deprecated zone API usage (dart-lang/watcher#95) --- pkgs/watcher/.travis.yml | 4 ++-- pkgs/watcher/CHANGELOG.md | 3 ++- pkgs/watcher/lib/src/directory_watcher/windows.dart | 4 ++-- pkgs/watcher/pubspec.yaml | 6 +++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pkgs/watcher/.travis.yml b/pkgs/watcher/.travis.yml index a3bc0703a..2c6dc5a72 100644 --- a/pkgs/watcher/.travis.yml +++ b/pkgs/watcher/.travis.yml @@ -2,7 +2,7 @@ language: dart dart: - dev -- 2.2.0 +- 2.8.4 os: - linux @@ -16,7 +16,7 @@ matrix: include: - dart: dev dart_task: dartfmt - - dart: 2.2.0 + - dart: 2.8.4 dart_task: dartanalyzer: --fatal-warnings . - dart: dev diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index a98ae202a..881aa2f84 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,6 +1,7 @@ -# 0.9.8 +# 0.9.8-dev * Add the ability to create custom Watcher types for specific file paths. +* Require at least Dart 2.8.4. # 0.9.7+15 diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index f5093c5dc..ae4670d1d 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -379,11 +379,11 @@ class _WindowsDirectoryWatcher void _startWatch() { // Note: "watcher closed" exceptions do not get sent over the stream // returned by watch, and must be caught via a zone handler. - runZoned(() { + runZonedGuarded(() { var innerStream = Directory(path).watch(recursive: true); _watchSubscription = innerStream.listen(_onEvent, onError: _eventsController.addError, onDone: _onDone); - }, onError: (error, StackTrace stackTrace) { + }, (error, StackTrace stackTrace) { if (error is FileSystemException && error.message.startsWith('Directory watcher closed unexpectedly')) { _watchSubscription.cancel(); diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index e86192dd8..c4cafada7 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,13 +1,13 @@ name: watcher -version: 0.9.7+15 +version: 0.9.8-dev description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. -homepage: https://github.com/dart-lang/watcher +repository: https://github.com/dart-lang/watcher environment: - sdk: '>=2.2.0 <3.0.0' + sdk: '>=2.8.4 <3.0.0' dependencies: async: ^2.0.0 From 22a6cb37c4763d0ae8853b970ed5d65b10b21f6b Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 12 Nov 2020 14:06:05 -0800 Subject: [PATCH 143/201] Remove an inferrable argument type (dart-lang/watcher#96) The `runZonedGuarded` API has a more specific static type which allows argument types for function literals to be inferred. --- pkgs/watcher/lib/src/directory_watcher/windows.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index ae4670d1d..98e963945 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -383,7 +383,7 @@ class _WindowsDirectoryWatcher var innerStream = Directory(path).watch(recursive: true); _watchSubscription = innerStream.listen(_onEvent, onError: _eventsController.addError, onDone: _onDone); - }, (error, StackTrace stackTrace) { + }, (error, stackTrace) { if (error is FileSystemException && error.message.startsWith('Directory watcher closed unexpectedly')) { _watchSubscription.cancel(); From db7d36a49f3132e0c60d9c25bad70aac2ac2b625 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Fri, 11 Dec 2020 18:18:44 -0800 Subject: [PATCH 144/201] Migrate to null safety. --- pkgs/watcher/lib/src/async_queue.dart | 3 +- .../lib/src/custom_watcher_factory.dart | 25 +++++++------- pkgs/watcher/lib/src/directory_watcher.dart | 2 +- .../lib/src/directory_watcher/linux.dart | 23 ++++++++----- .../lib/src/directory_watcher/mac_os.dart | 19 ++++++----- .../lib/src/directory_watcher/polling.dart | 16 ++++----- .../lib/src/directory_watcher/windows.dart | 32 ++++++++++-------- pkgs/watcher/lib/src/file_watcher.dart | 2 +- pkgs/watcher/lib/src/file_watcher/native.dart | 2 +- .../watcher/lib/src/file_watcher/polling.dart | 8 ++--- pkgs/watcher/lib/src/path_set.dart | 16 +++++---- pkgs/watcher/lib/src/resubscribable.dart | 6 ++-- pkgs/watcher/lib/src/stat.dart | 10 +++--- pkgs/watcher/lib/src/utils.dart | 4 +-- pkgs/watcher/lib/watcher.dart | 2 +- pkgs/watcher/pubspec.yaml | 16 ++++----- .../test/custom_watcher_factory_test.dart | 12 +++---- pkgs/watcher/test/path_set_test.dart | 2 +- pkgs/watcher/test/utils.dart | 33 ++++++++++--------- 19 files changed, 127 insertions(+), 106 deletions(-) diff --git a/pkgs/watcher/lib/src/async_queue.dart b/pkgs/watcher/lib/src/async_queue.dart index 37f13d61d..93899e7c6 100644 --- a/pkgs/watcher/lib/src/async_queue.dart +++ b/pkgs/watcher/lib/src/async_queue.dart @@ -33,7 +33,8 @@ class AsyncQueue { /// Used to avoid top-leveling asynchronous errors. final Function _errorHandler; - AsyncQueue(this._processor, {Function onError}) : _errorHandler = onError; + AsyncQueue(this._processor, {required Function onError}) + : _errorHandler = onError; /// Enqueues [item] to be processed and starts asynchronously processing it /// if a process isn't already running. diff --git a/pkgs/watcher/lib/src/custom_watcher_factory.dart b/pkgs/watcher/lib/src/custom_watcher_factory.dart index 3dc1beafc..8f4132b99 100644 --- a/pkgs/watcher/lib/src/custom_watcher_factory.dart +++ b/pkgs/watcher/lib/src/custom_watcher_factory.dart @@ -7,9 +7,9 @@ import '../watcher.dart'; /// A factory to produce custom watchers for specific file paths. class _CustomWatcherFactory { final String id; - final DirectoryWatcher Function(String path, {Duration pollingDelay}) + final DirectoryWatcher? Function(String path, {Duration? pollingDelay}) createDirectoryWatcher; - final FileWatcher Function(String path, {Duration pollingDelay}) + final FileWatcher? Function(String path, {Duration? pollingDelay}) createFileWatcher; _CustomWatcherFactory( @@ -22,7 +22,7 @@ class _CustomWatcherFactory { /// registered more than once. /// [createDirectoryWatcher] and [createFileWatcher] should return watchers for /// the file paths they are able to handle. If the custom watcher is not able to -/// handle the path it should reuturn null. +/// handle the path it should return null. /// The paths handled by each custom watch may not overlap, at most one custom /// matcher may return a non-null watcher for a given path. /// @@ -31,9 +31,10 @@ class _CustomWatcherFactory { /// will be used instead of the default. void registerCustomWatcher( String id, - DirectoryWatcher Function(String path, {Duration pollingDelay}) + DirectoryWatcher Function(String path, {Duration? pollingDelay})? createDirectoryWatcher, - FileWatcher Function(String path, {Duration pollingDelay}) createFileWatcher, + FileWatcher Function(String path, {Duration? pollingDelay})? + createFileWatcher, ) { if (_customWatcherFactories.containsKey(id)) { throw ArgumentError('A custom watcher with id `$id` ' @@ -49,10 +50,10 @@ void registerCustomWatcher( /// /// Returns `null` if no custom watcher was applicable and throws a [StateError] /// if more than one was. -DirectoryWatcher createCustomDirectoryWatcher(String path, - {Duration pollingDelay}) { - DirectoryWatcher customWatcher; - String customFactoryId; +DirectoryWatcher? createCustomDirectoryWatcher(String path, + {Duration? pollingDelay}) { + DirectoryWatcher? customWatcher; + String? customFactoryId; for (var watcherFactory in _customWatcherFactories.values) { if (customWatcher != null) { throw StateError('Two `CustomWatcherFactory`s applicable: ' @@ -69,9 +70,9 @@ DirectoryWatcher createCustomDirectoryWatcher(String path, /// /// Returns `null` if no custom watcher was applicable and throws a [StateError] /// if more than one was. -FileWatcher createCustomFileWatcher(String path, {Duration pollingDelay}) { - FileWatcher customWatcher; - String customFactoryId; +FileWatcher? createCustomFileWatcher(String path, {Duration? pollingDelay}) { + FileWatcher? customWatcher; + String? customFactoryId; for (var watcherFactory in _customWatcherFactories.values) { if (customWatcher != null) { throw StateError('Two `CustomWatcherFactory`s applicable: ' diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 3fe500455..043ebab75 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -28,7 +28,7 @@ abstract class DirectoryWatcher implements Watcher { /// shorter will give more immediate feedback at the expense of doing more IO /// and higher CPU usage. Defaults to one second. Ignored for non-polling /// watchers. - factory DirectoryWatcher(String directory, {Duration pollingDelay}) { + factory DirectoryWatcher(String directory, {Duration? pollingDelay}) { if (FileSystemEntity.isWatchSupported) { var customWatcher = createCustomDirectoryWatcher(directory, pollingDelay: pollingDelay); diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index 7348cb45f..06d3508ef 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -91,7 +91,7 @@ class _LinuxDirectoryWatcher } else { _files.add(entity.path); } - }, onError: (error, StackTrace stackTrace) { + }, onError: (Object error, StackTrace stackTrace) { _eventsController.addError(error, stackTrace); close(); }, onDone: () { @@ -155,13 +155,16 @@ class _LinuxDirectoryWatcher files.remove(event.path); dirs.remove(event.path); - changed.add(event.destination); + var destination = event.destination; + if (destination == null) continue; + + changed.add(destination); if (event.isDirectory) { - files.remove(event.destination); - dirs.add(event.destination); + files.remove(destination); + dirs.add(destination); } else { - files.add(event.destination); - dirs.remove(event.destination); + files.add(destination); + dirs.remove(destination); } } else if (event is FileSystemDeleteEvent) { files.remove(event.path); @@ -221,7 +224,7 @@ class _LinuxDirectoryWatcher _files.add(entity.path); _emit(ChangeType.ADD, entity.path); } - }, onError: (error, StackTrace stackTrace) { + }, onError: (Object error, StackTrace stackTrace) { // Ignore an exception caused by the dir not existing. It's fine if it // was added and then quickly removed. if (error is FileSystemException) return; @@ -258,8 +261,10 @@ class _LinuxDirectoryWatcher /// Like [Stream.listen], but automatically adds the subscription to /// [_subscriptions] so that it can be canceled when [close] is called. void _listen(Stream stream, void Function(T) onData, - {Function onError, void Function() onDone, bool cancelOnError}) { - StreamSubscription subscription; + {Function? onError, + void Function()? onDone, + bool cancelOnError = false}) { + late StreamSubscription subscription; subscription = stream.listen(onData, onError: onError, onDone: () { _subscriptions.remove(subscription); onDone?.call(); diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 4408ff1eb..e44b7f54e 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -64,11 +64,11 @@ class _MacOSDirectoryWatcher /// /// This is separate from [_listSubscriptions] because this stream /// occasionally needs to be resubscribed in order to work around issue 14849. - StreamSubscription> _watchSubscription; + StreamSubscription>? _watchSubscription; /// The subscription to the [Directory.list] call for the initial listing of /// the directory to determine its initial state. - StreamSubscription _initialListSubscription; + StreamSubscription? _initialListSubscription; /// The subscriptions to [Directory.list] calls for listing the contents of a /// subdirectory that was moved into the watched directory. @@ -76,7 +76,7 @@ class _MacOSDirectoryWatcher /// The timer for tracking how long we wait for an initial batch of bogus /// events (see issue 14373). - Timer _bogusEventTimer; + late Timer _bogusEventTimer; _MacOSDirectoryWatcher(String path) : path = path, @@ -144,14 +144,14 @@ class _MacOSDirectoryWatcher if (_files.containsDir(path)) continue; - StreamSubscription subscription; + late StreamSubscription subscription; subscription = Directory(path).list(recursive: true).listen((entity) { if (entity is Directory) return; if (_files.contains(path)) return; _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); - }, onError: (e, StackTrace stackTrace) { + }, onError: (Object e, StackTrace stackTrace) { _emitError(e, stackTrace); }, onDone: () { _listSubscriptions.remove(subscription); @@ -192,7 +192,10 @@ class _MacOSDirectoryWatcher var directories = unionAll(batch.map((event) { if (!event.isDirectory) return {}; if (event is FileSystemMoveEvent) { - return {event.path, event.destination}; + var destination = event.destination; + if (destination != null) { + return {event.path, destination}; + } } return {event.path}; })); @@ -224,7 +227,7 @@ class _MacOSDirectoryWatcher /// If [batch] does contain contradictory events, this returns `null` to /// indicate that the state of the path on the filesystem should be checked to /// determine what occurred. - FileSystemEvent _canonicalEvent(Set batch) { + FileSystemEvent? _canonicalEvent(Set batch) { // An empty batch indicates that we've learned earlier that the batch is // contradictory (e.g. because of a move). if (batch.isEmpty) return null; @@ -394,7 +397,7 @@ class _MacOSDirectoryWatcher } /// Emit an error, then close the watcher. - void _emitError(error, StackTrace stackTrace) { + void _emitError(Object error, StackTrace stackTrace) { _eventsController.addError(error, stackTrace); close(); } diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 902e8b861..968c9c62f 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -24,7 +24,7 @@ class PollingDirectoryWatcher extends ResubscribableWatcher /// will pause between successive polls of the directory contents. Making this /// shorter will give more immediate feedback at the expense of doing more IO /// and higher CPU usage. Defaults to one second. - PollingDirectoryWatcher(String directory, {Duration pollingDelay}) + PollingDirectoryWatcher(String directory, {Duration? pollingDelay}) : super(directory, () { return _PollingDirectoryWatcher( directory, pollingDelay ?? Duration(seconds: 1)); @@ -56,12 +56,12 @@ class _PollingDirectoryWatcher /// The previous modification times of the files in the directory. /// /// Used to tell which files have been modified. - final _lastModifieds = {}; + final _lastModifieds = {}; /// The subscription used while [directory] is being listed. /// /// Will be `null` if a list is not currently happening. - StreamSubscription _listSubscription; + StreamSubscription? _listSubscription; /// The queue of files waiting to be processed to see if they have been /// modified. @@ -70,7 +70,7 @@ class _PollingDirectoryWatcher /// queue exists to let each of those proceed at their own rate. The lister /// will enqueue files as quickly as it can. Meanwhile, files are dequeued /// and processed sequentially. - AsyncQueue _filesToProcess; + late AsyncQueue _filesToProcess; /// The set of files that have been seen in the current directory listing. /// @@ -79,8 +79,8 @@ class _PollingDirectoryWatcher final _polledFiles = {}; _PollingDirectoryWatcher(this.path, this._pollingDelay) { - _filesToProcess = - AsyncQueue(_processFile, onError: (e, StackTrace stackTrace) { + _filesToProcess = AsyncQueue(_processFile, + onError: (Object e, StackTrace stackTrace) { if (!_events.isClosed) _events.addError(e, stackTrace); }); @@ -120,7 +120,7 @@ class _PollingDirectoryWatcher if (entity is! File) return; _filesToProcess.add(entity.path); - }, onError: (error, StackTrace stackTrace) { + }, onError: (Object error, StackTrace stackTrace) { if (!isDirectoryNotFoundException(error)) { // It's some unknown error. Pipe it over to the event stream so the // user can see it. @@ -136,7 +136,7 @@ class _PollingDirectoryWatcher /// Processes [file] to determine if it has been modified since the last /// time it was scanned. - Future _processFile(String file) async { + Future _processFile(String? file) async { // `null` is the sentinel which means the directory listing is complete. if (file == null) { await _completePoll(); diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 98e963945..764037377 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -28,7 +28,7 @@ class WindowsDirectoryWatcher extends ResubscribableWatcher class _EventBatcher { static const Duration _BATCH_DELAY = Duration(milliseconds: 100); final List events = []; - Timer timer; + Timer? timer; void addEvent(FileSystemEvent event, void Function() callback) { events.add(event); @@ -37,7 +37,7 @@ class _EventBatcher { } void cancelTimer() { - timer.cancel(); + timer?.cancel(); } } @@ -71,16 +71,16 @@ class _WindowsDirectoryWatcher final PathSet _files; /// The subscription to the stream returned by [Directory.watch]. - StreamSubscription _watchSubscription; + StreamSubscription? _watchSubscription; /// The subscription to the stream returned by [Directory.watch] of the /// parent directory to [directory]. This is needed to detect changes to /// [directory], as they are not included on Windows. - StreamSubscription _parentWatchSubscription; + StreamSubscription? _parentWatchSubscription; /// The subscription to the [Directory.list] call for the initial listing of /// the directory to determine its initial state. - StreamSubscription _initialListSubscription; + StreamSubscription? _initialListSubscription; /// The subscriptions to the [Directory.list] calls for listing the contents /// of subdirectories that were moved into the watched directory. @@ -148,7 +148,7 @@ class _WindowsDirectoryWatcher // Ignore errors, simply close the stream. The user listens on // [directory], and while it can fail to listen on the parent, we may // still be able to listen on the path requested. - _parentWatchSubscription.cancel(); + _parentWatchSubscription?.cancel(); _parentWatchSubscription = null; }); } @@ -184,7 +184,7 @@ class _WindowsDirectoryWatcher if (_files.containsDir(path)) continue; var stream = Directory(path).list(recursive: true); - StreamSubscription subscription; + late StreamSubscription subscription; subscription = stream.listen((entity) { if (entity is Directory) return; if (_files.contains(path)) return; @@ -193,7 +193,7 @@ class _WindowsDirectoryWatcher _files.add(entity.path); }, onDone: () { _listSubscriptions.remove(subscription); - }, onError: (e, StackTrace stackTrace) { + }, onError: (Object e, StackTrace stackTrace) { _listSubscriptions.remove(subscription); _emitError(e, stackTrace); }, cancelOnError: true); @@ -229,7 +229,10 @@ class _WindowsDirectoryWatcher var directories = unionAll(batch.map((event) { if (!event.isDirectory) return {}; if (event is FileSystemMoveEvent) { - return {event.path, event.destination}; + var destination = event.destination; + if (destination != null) { + return {event.path, destination}; + } } return {event.path}; })); @@ -244,7 +247,10 @@ class _WindowsDirectoryWatcher for (var event in batch) { if (event is FileSystemMoveEvent) { - addEvent(event.destination, event); + var destination = event.destination; + if (destination != null) { + addEvent(destination, event); + } } addEvent(event.path, event); } @@ -262,7 +268,7 @@ class _WindowsDirectoryWatcher /// If [batch] does contain contradictory events, this returns `null` to /// indicate that the state of the path on the filesystem should be checked to /// determine what occurred. - FileSystemEvent _canonicalEvent(Set batch) { + FileSystemEvent? _canonicalEvent(Set batch) { // An empty batch indicates that we've learned earlier that the batch is // contradictory (e.g. because of a move). if (batch.isEmpty) return null; @@ -386,7 +392,7 @@ class _WindowsDirectoryWatcher }, (error, stackTrace) { if (error is FileSystemException && error.message.startsWith('Directory watcher closed unexpectedly')) { - _watchSubscription.cancel(); + _watchSubscription?.cancel(); _eventsController.addError(error, stackTrace); _startWatch(); } else { @@ -421,7 +427,7 @@ class _WindowsDirectoryWatcher } /// Emit an error, then close the watcher. - void _emitError(error, StackTrace stackTrace) { + void _emitError(Object error, StackTrace stackTrace) { _eventsController.addError(error, stackTrace); close(); } diff --git a/pkgs/watcher/lib/src/file_watcher.dart b/pkgs/watcher/lib/src/file_watcher.dart index c41e5e678..9b8ecc4a5 100644 --- a/pkgs/watcher/lib/src/file_watcher.dart +++ b/pkgs/watcher/lib/src/file_watcher.dart @@ -29,7 +29,7 @@ abstract class FileWatcher implements Watcher { /// shorter will give more immediate feedback at the expense of doing more IO /// and higher CPU usage. Defaults to one second. Ignored for non-polling /// watchers. - factory FileWatcher(String file, {Duration pollingDelay}) { + factory FileWatcher(String file, {Duration? pollingDelay}) { var customWatcher = createCustomFileWatcher(file, pollingDelay: pollingDelay); if (customWatcher != null) return customWatcher; diff --git a/pkgs/watcher/lib/src/file_watcher/native.dart b/pkgs/watcher/lib/src/file_watcher/native.dart index 0b42cb9b3..f7d92d49e 100644 --- a/pkgs/watcher/lib/src/file_watcher/native.dart +++ b/pkgs/watcher/lib/src/file_watcher/native.dart @@ -33,7 +33,7 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { Future get ready => _readyCompleter.future; final _readyCompleter = Completer(); - StreamSubscription _subscription; + StreamSubscription? _subscription; _NativeFileWatcher(this.path) { _listen(); diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart index 11c0c6d9f..b208a4b0b 100644 --- a/pkgs/watcher/lib/src/file_watcher/polling.dart +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -14,7 +14,7 @@ import '../watch_event.dart'; /// Periodically polls a file for changes. class PollingFileWatcher extends ResubscribableWatcher implements FileWatcher { - PollingFileWatcher(String path, {Duration pollingDelay}) + PollingFileWatcher(String path, {Duration? pollingDelay}) : super(path, () { return _PollingFileWatcher( path, pollingDelay ?? Duration(seconds: 1)); @@ -37,13 +37,13 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { final _readyCompleter = Completer(); /// The timer that controls polling. - Timer _timer; + late Timer _timer; /// The previous modification time of the file. /// /// Used to tell when the file was modified. This is `null` before the file's /// mtime has first been checked. - DateTime _lastModified; + DateTime? _lastModified; _PollingFileWatcher(this.path, Duration pollingDelay) { _timer = Timer.periodic(pollingDelay, (_) => _poll()); @@ -64,7 +64,7 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { return; } - DateTime modified; + DateTime? modified; try { modified = await modificationTime(path); } on FileSystemException catch (error, stackTrace) { diff --git a/pkgs/watcher/lib/src/path_set.dart b/pkgs/watcher/lib/src/path_set.dart index 41a0a390b..090090eeb 100644 --- a/pkgs/watcher/lib/src/path_set.dart +++ b/pkgs/watcher/lib/src/path_set.dart @@ -119,8 +119,9 @@ class PathSet { var entry = _entries; for (var part in p.split(path)) { - entry = entry.contents[part]; - if (entry == null) return false; + var child = entry.contents[part]; + if (child == null) return false; + entry = child; } return entry.isExplicit; @@ -132,8 +133,9 @@ class PathSet { var entry = _entries; for (var part in p.split(path)) { - entry = entry.contents[part]; - if (entry == null) return false; + var child = entry.contents[part]; + if (child == null) return false; + entry = child; } return entry.contents.isNotEmpty; @@ -144,9 +146,9 @@ class PathSet { var result = []; void recurse(_Entry dir, String path) { - for (var name in dir.contents.keys) { - var entry = dir.contents[name]; - var entryPath = p.join(path, name); + for (var mapEntry in dir.contents.entries) { + var entry = mapEntry.value; + var entryPath = p.join(path, mapEntry.key); if (entry.isExplicit) result.add(entryPath); recurse(entry, entryPath); } diff --git a/pkgs/watcher/lib/src/resubscribable.dart b/pkgs/watcher/lib/src/resubscribable.dart index 071909607..1c4bb25d8 100644 --- a/pkgs/watcher/lib/src/resubscribable.dart +++ b/pkgs/watcher/lib/src/resubscribable.dart @@ -29,7 +29,7 @@ abstract class ResubscribableWatcher implements Watcher { @override Stream get events => _eventsController.stream; - StreamController _eventsController; + late StreamController _eventsController; @override bool get isReady => _readyCompleter.isCompleted; @@ -41,8 +41,8 @@ abstract class ResubscribableWatcher implements Watcher { /// Creates a new [ResubscribableWatcher] wrapping the watchers /// emitted by [_factory]. ResubscribableWatcher(this.path, this._factory) { - ManuallyClosedWatcher watcher; - StreamSubscription subscription; + late ManuallyClosedWatcher watcher; + late StreamSubscription subscription; _eventsController = StreamController.broadcast( onListen: () async { diff --git a/pkgs/watcher/lib/src/stat.dart b/pkgs/watcher/lib/src/stat.dart index 08cf935c7..06e3febf4 100644 --- a/pkgs/watcher/lib/src/stat.dart +++ b/pkgs/watcher/lib/src/stat.dart @@ -8,7 +8,7 @@ import 'dart:io'; /// the file at that path. typedef MockTimeCallback = DateTime Function(String path); -MockTimeCallback _mockTimeCallback; +MockTimeCallback? _mockTimeCallback; /// Overrides the default behavior for accessing a file's modification time /// with [callback]. @@ -21,9 +21,11 @@ void mockGetModificationTime(MockTimeCallback callback) { } /// Gets the modification time for the file at [path]. -Future modificationTime(String path) async { - if (_mockTimeCallback != null) { - return _mockTimeCallback(path); +/// Completes with `null` if the file does not exist. +Future modificationTime(String path) async { + var mockTimeCallback = _mockTimeCallback; + if (mockTimeCallback != null) { + return mockTimeCallback(path); } final stat = await FileStat.stat(path); diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index 24b8184c2..66c59d3e5 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -8,12 +8,12 @@ import 'dart:collection'; /// Returns `true` if [error] is a [FileSystemException] for a missing /// directory. -bool isDirectoryNotFoundException(error) { +bool isDirectoryNotFoundException(Object error) { if (error is! FileSystemException) 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; + return error.osError?.errorCode == notFoundCode; } /// Returns the union of all elements in each set in [sets]. diff --git a/pkgs/watcher/lib/watcher.dart b/pkgs/watcher/lib/watcher.dart index 01336ab5a..22e0d6e04 100644 --- a/pkgs/watcher/lib/watcher.dart +++ b/pkgs/watcher/lib/watcher.dart @@ -57,7 +57,7 @@ abstract class Watcher { /// shorter will give more immediate feedback at the expense of doing more IO /// and higher CPU usage. Defaults to one second. Ignored for non-polling /// watchers. - factory Watcher(String path, {Duration pollingDelay}) { + factory Watcher(String path, {Duration? pollingDelay}) { if (File(path).existsSync()) { return FileWatcher(path, pollingDelay: pollingDelay); } else { diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index c4cafada7..8567e9bc6 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.9.8-dev +version: 0.10.0-nullsafety.0 description: >- A file system watcher. It monitors changes to contents of directories and @@ -7,14 +7,14 @@ description: >- repository: https://github.com/dart-lang/watcher environment: - sdk: '>=2.8.4 <3.0.0' + sdk: '>=2.12.0-0 <3.0.0' dependencies: - async: ^2.0.0 - path: ^1.0.0 - pedantic: ^1.1.0 + async: ^2.5.0-nullsafety.3 + path: ^1.8.0-nullsafety.3 + pedantic: ^1.10.0-nullsafety.3 dev_dependencies: - benchmark_harness: ^1.0.4 - test: ^1.0.0 - test_descriptor: ^1.0.0 + benchmark_harness: ^2.0.0-nullsafety.0 + test: ^1.16.0-nullsafety.13 + test_descriptor: ^2.0.0-nullsafety diff --git a/pkgs/watcher/test/custom_watcher_factory_test.dart b/pkgs/watcher/test/custom_watcher_factory_test.dart index 8210c065b..89f8e3cd0 100644 --- a/pkgs/watcher/test/custom_watcher_factory_test.dart +++ b/pkgs/watcher/test/custom_watcher_factory_test.dart @@ -4,7 +4,7 @@ import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; void main() { - _MemFs memFs; + late _MemFs memFs; final defaultFactoryId = 'MemFs'; setUpAll(() { @@ -16,7 +16,7 @@ void main() { watcherFactory.createFileWatcher); }); - test('notifes for files', () async { + test('notifies for files', () async { var watcher = FileWatcher('file.txt'); var completer = Completer(); @@ -29,7 +29,7 @@ void main() { expect(event.path, 'file.txt'); }); - test('notifes for directories', () async { + test('notifies for directories', () async { var watcher = DirectoryWatcher('dir'); var completer = Completer(); @@ -45,7 +45,7 @@ void main() { test('registering twice throws', () async { expect( () => registerCustomWatcher(defaultFactoryId, - (_, {pollingDelay}) => null, (_, {pollingDelay}) => null), + (_, {pollingDelay}) => throw 0, (_, {pollingDelay}) => throw 0), throwsA(isA())); }); @@ -117,9 +117,9 @@ class _MemFsWatcherFactory { _MemFsWatcherFactory(this._memFs); DirectoryWatcher createDirectoryWatcher(String path, - {Duration pollingDelay}) => + {Duration? pollingDelay}) => _MemFsWatcher(path, _memFs.watchStream(path)); - FileWatcher createFileWatcher(String path, {Duration pollingDelay}) => + FileWatcher createFileWatcher(String path, {Duration? pollingDelay}) => _MemFsWatcher(path, _memFs.watchStream(path)); } diff --git a/pkgs/watcher/test/path_set_test.dart b/pkgs/watcher/test/path_set_test.dart index 25cf96943..61ab2cd64 100644 --- a/pkgs/watcher/test/path_set_test.dart +++ b/pkgs/watcher/test/path_set_test.dart @@ -15,7 +15,7 @@ Matcher containsDir(String path) => predicate( 'set contains directory "$path"'); void main() { - PathSet paths; + late PathSet paths; setUp(() => paths = PathSet('root')); group('adding a path', () { diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 6e7686c27..9b4fd2874 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -30,12 +30,12 @@ set watcherFactory(WatcherFactory factory) { /// increment the mod time for that file instantly. final _mockFileModificationTimes = {}; -WatcherFactory _watcherFactory; +late WatcherFactory _watcherFactory; /// Creates a new [Watcher] that watches a temporary file or directory. /// /// If [path] is provided, watches a subdirectory in the sandbox with that name. -Watcher createWatcher({String path}) { +Watcher createWatcher({String? path}) { if (path == null) { path = d.sandbox; } else { @@ -46,13 +46,13 @@ Watcher createWatcher({String path}) { } /// The stream of events from the watcher started with [startWatcher]. -StreamQueue _watcherEvents; +late StreamQueue _watcherEvents; /// Creates a new [Watcher] that watches a temporary file or directory and /// starts monitoring it for events. /// /// If [path] is provided, watches a path in the sandbox with that name. -Future startWatcher({String path}) async { +Future startWatcher({String? path}) async { mockGetModificationTime((path) { final normalized = p.normalize(p.relative(path, from: d.sandbox)); @@ -86,7 +86,7 @@ void startClosingEventStream() async { /// A list of [StreamMatcher]s that have been collected using /// [_collectStreamMatcher]. -List _collectedStreamMatchers; +List? _collectedStreamMatchers; /// Collects all stream matchers that are registered within [block] into a /// single stream matcher. @@ -94,10 +94,10 @@ List _collectedStreamMatchers; /// The returned matcher will match each of the collected matchers in order. StreamMatcher _collectStreamMatcher(void Function() block) { var oldStreamMatchers = _collectedStreamMatchers; - _collectedStreamMatchers = []; + var collectedStreamMatchers = _collectedStreamMatchers = []; try { block(); - return emitsInOrder(_collectedStreamMatchers); + return emitsInOrder(collectedStreamMatchers); } finally { _collectedStreamMatchers = oldStreamMatchers; } @@ -108,9 +108,10 @@ StreamMatcher _collectStreamMatcher(void Function() block) { /// /// [streamMatcher] can be a [StreamMatcher], a [Matcher], or a value. Future _expectOrCollect(streamMatcher) { - if (_collectedStreamMatchers != null) { - _collectedStreamMatchers.add(emits(streamMatcher)); - return null; + var collectedStreamMatchers = _collectedStreamMatchers; + if (collectedStreamMatchers != null) { + collectedStreamMatchers.add(emits(streamMatcher)); + return Future.sync(() {}); } else { return expectLater(_watcherEvents, emits(streamMatcher)); } @@ -202,7 +203,7 @@ Future allowRemoveEvent(String path) => /// /// If [contents] is omitted, creates an empty file. If [updateModified] is /// `false`, the mock file modification time is not changed. -void writeFile(String path, {String contents, bool updateModified}) { +void writeFile(String path, {String? contents, bool? updateModified}) { contents ??= ''; updateModified ??= true; @@ -219,8 +220,8 @@ void writeFile(String path, {String contents, bool updateModified}) { if (updateModified) { path = p.normalize(path); - _mockFileModificationTimes.putIfAbsent(path, () => 0); - _mockFileModificationTimes[path]++; + _mockFileModificationTimes.update(path, (value) => value + 1, + ifAbsent: () => 1); } } @@ -236,8 +237,8 @@ void renameFile(String from, String to) { // Make sure we always use the same separator on Windows. to = p.normalize(to); - _mockFileModificationTimes.putIfAbsent(to, () => 0); - _mockFileModificationTimes[to]++; + _mockFileModificationTimes.update(to, (value) => value + 1, + ifAbsent: () => 1); } /// Schedules creating a directory in the sandbox at [path]. @@ -261,7 +262,7 @@ void deleteDir(String path) { /// Returns a set of all values returns by [callback]. /// /// [limit] defaults to 3. -Set withPermutations(S Function(int, int, int) callback, {int limit}) { +Set withPermutations(S Function(int, int, int) callback, {int? limit}) { limit ??= 3; var results = {}; for (var i = 0; i < limit; i++) { From c30964f273a67f80c5a1dea992ea436ee0be3f48 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Mon, 14 Dec 2020 10:08:49 -0800 Subject: [PATCH 145/201] Add dependency override for analyzer. --- pkgs/watcher/pubspec.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 8567e9bc6..0309be578 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -18,3 +18,6 @@ dev_dependencies: benchmark_harness: ^2.0.0-nullsafety.0 test: ^1.16.0-nullsafety.13 test_descriptor: ^2.0.0-nullsafety + +dependency_overrides: + analyzer: ^0.41.1 From 42d3623342f063bb40caa1ed069673a0dde33303 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Mon, 14 Dec 2020 11:56:09 -0800 Subject: [PATCH 146/201] Stop testing on SDK 2.8.4 --- pkgs/watcher/.travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkgs/watcher/.travis.yml b/pkgs/watcher/.travis.yml index 2c6dc5a72..ca68b1759 100644 --- a/pkgs/watcher/.travis.yml +++ b/pkgs/watcher/.travis.yml @@ -2,7 +2,6 @@ language: dart dart: - dev -- 2.8.4 os: - linux @@ -16,9 +15,6 @@ matrix: include: - dart: dev dart_task: dartfmt - - dart: 2.8.4 - dart_task: - dartanalyzer: --fatal-warnings . - dart: dev dart_task: dartanalyzer: --fatal-warnings --fatal-infos . From cb570df39f7fe35219671a3709d71d2283184f3d Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Tue, 15 Dec 2020 15:15:18 -0800 Subject: [PATCH 147/201] Fixes for review comments. --- pkgs/watcher/lib/src/directory_watcher/mac_os.dart | 14 ++++++++------ .../watcher/lib/src/directory_watcher/polling.dart | 2 +- .../watcher/lib/src/directory_watcher/windows.dart | 11 ++++++----- pkgs/watcher/lib/src/file_watcher/polling.dart | 2 +- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index e44b7f54e..7dc6e7cfa 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -144,18 +144,20 @@ class _MacOSDirectoryWatcher if (_files.containsDir(path)) continue; - late StreamSubscription subscription; - subscription = Directory(path).list(recursive: true).listen((entity) { + var stream = Directory(path).list(recursive: true); + var subscription = stream.listen((entity) { if (entity is Directory) return; if (_files.contains(path)) return; _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); - }, onError: (Object e, StackTrace stackTrace) { - _emitError(e, stackTrace); - }, onDone: () { - _listSubscriptions.remove(subscription); }, cancelOnError: true); + subscription.onDone(() { + _listSubscriptions.remove(subscription); + }); + subscription.onError((Object e, StackTrace stackTrace) { + _emitError(e, stackTrace); + }); _listSubscriptions.add(subscription); } else if (event is FileSystemModifyEvent) { assert(!event.isDirectory); diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 968c9c62f..6baa49dc7 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -70,7 +70,7 @@ class _PollingDirectoryWatcher /// queue exists to let each of those proceed at their own rate. The lister /// will enqueue files as quickly as it can. Meanwhile, files are dequeued /// and processed sequentially. - late AsyncQueue _filesToProcess; + late final AsyncQueue _filesToProcess; /// The set of files that have been seen in the current directory listing. /// diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 764037377..a8200eed9 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -184,19 +184,20 @@ class _WindowsDirectoryWatcher if (_files.containsDir(path)) continue; var stream = Directory(path).list(recursive: true); - late StreamSubscription subscription; - subscription = stream.listen((entity) { + var subscription = stream.listen((entity) { if (entity is Directory) return; if (_files.contains(path)) return; _emitEvent(ChangeType.ADD, entity.path); _files.add(entity.path); - }, onDone: () { + }, cancelOnError: true); + subscription.onDone(() { _listSubscriptions.remove(subscription); - }, onError: (Object e, StackTrace stackTrace) { + }); + subscription.onError((Object e, StackTrace stackTrace) { _listSubscriptions.remove(subscription); _emitError(e, stackTrace); - }, cancelOnError: true); + }); _listSubscriptions.add(subscription); } else if (event is FileSystemModifyEvent) { if (!event.isDirectory) { diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart index b208a4b0b..9b9ac5be3 100644 --- a/pkgs/watcher/lib/src/file_watcher/polling.dart +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -37,7 +37,7 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { final _readyCompleter = Completer(); /// The timer that controls polling. - late Timer _timer; + late final Timer _timer; /// The previous modification time of the file. /// From 23206c06faaba67d768bacaff756063a32fb24db Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Tue, 15 Dec 2020 15:17:21 -0800 Subject: [PATCH 148/201] Move '_filesToProcess' initializer. --- pkgs/watcher/lib/src/directory_watcher/polling.dart | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 6baa49dc7..95fb683a8 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -70,7 +70,10 @@ class _PollingDirectoryWatcher /// queue exists to let each of those proceed at their own rate. The lister /// will enqueue files as quickly as it can. Meanwhile, files are dequeued /// and processed sequentially. - late final AsyncQueue _filesToProcess; + late final AsyncQueue _filesToProcess = AsyncQueue( + _processFile, onError: (Object e, StackTrace stackTrace) { + if (!_events.isClosed) _events.addError(e, stackTrace); + }); /// The set of files that have been seen in the current directory listing. /// @@ -79,11 +82,6 @@ class _PollingDirectoryWatcher final _polledFiles = {}; _PollingDirectoryWatcher(this.path, this._pollingDelay) { - _filesToProcess = AsyncQueue(_processFile, - onError: (Object e, StackTrace stackTrace) { - if (!_events.isClosed) _events.addError(e, stackTrace); - }); - _poll(); } From f5c13384698895da15cab0b021a4d623a4ef1ad7 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 16 Dec 2020 09:07:26 -0800 Subject: [PATCH 149/201] Add a better static Function type (dart-lang/watcher#100) Improves inference so it is no longer necessary to add types on the function literal at the usage point. --- pkgs/watcher/lib/src/async_queue.dart | 5 +++-- pkgs/watcher/lib/src/directory_watcher/polling.dart | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pkgs/watcher/lib/src/async_queue.dart b/pkgs/watcher/lib/src/async_queue.dart index 93899e7c6..eca28ad57 100644 --- a/pkgs/watcher/lib/src/async_queue.dart +++ b/pkgs/watcher/lib/src/async_queue.dart @@ -31,9 +31,10 @@ class AsyncQueue { /// The handler for errors thrown during processing. /// /// Used to avoid top-leveling asynchronous errors. - final Function _errorHandler; + final void Function(Object, StackTrace) _errorHandler; - AsyncQueue(this._processor, {required Function onError}) + AsyncQueue(this._processor, + {required void Function(Object, StackTrace) onError}) : _errorHandler = onError; /// Enqueues [item] to be processed and starts asynchronously processing it diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 95fb683a8..2a43937b8 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -70,9 +70,9 @@ class _PollingDirectoryWatcher /// queue exists to let each of those proceed at their own rate. The lister /// will enqueue files as quickly as it can. Meanwhile, files are dequeued /// and processed sequentially. - late final AsyncQueue _filesToProcess = AsyncQueue( - _processFile, onError: (Object e, StackTrace stackTrace) { - if (!_events.isClosed) _events.addError(e, stackTrace); + late final AsyncQueue _filesToProcess = + AsyncQueue(_processFile, onError: (error, stackTrace) { + if (!_events.isClosed) _events.addError(error, stackTrace); }); /// The set of files that have been seen in the current directory listing. From f7024e9090a8db77d9ded5ce3c966fce10e553c2 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 17 Dec 2020 14:13:03 -0800 Subject: [PATCH 150/201] Remove unused test utils (dart-lang/watcher#101) --- pkgs/watcher/test/utils.dart | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 9b4fd2874..aa23d50a5 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -175,14 +175,6 @@ Future expectModifyEvent(String path) => Future expectRemoveEvent(String path) => _expectOrCollect(isWatchEvent(ChangeType.REMOVE, path)); -/// Consumes an add event for [path] if one is emitted at this point in the -/// schedule, but doesn't throw an error if it isn't. -/// -/// If this is used at the end of a test, [startClosingEventStream] should be -/// called before it. -Future allowAddEvent(String path) => - _expectOrCollect(mayEmit(isWatchEvent(ChangeType.ADD, path))); - /// Consumes a modification event for [path] if one is emitted at this point in /// the schedule, but doesn't throw an error if it isn't. /// @@ -191,14 +183,6 @@ Future allowAddEvent(String path) => Future allowModifyEvent(String path) => _expectOrCollect(mayEmit(isWatchEvent(ChangeType.MODIFY, path))); -/// Consumes a removal event for [path] if one is emitted at this point in the -/// schedule, but doesn't throw an error if it isn't. -/// -/// If this is used at the end of a test, [startClosingEventStream] should be -/// called before it. -Future allowRemoveEvent(String path) => - _expectOrCollect(mayEmit(isWatchEvent(ChangeType.REMOVE, path))); - /// Schedules writing a file in the sandbox at [path] with [contents]. /// /// If [contents] is omitted, creates an empty file. If [updateModified] is From e83d89bbab68bc4637bccbaae4feb9935ad7bd75 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 17 Dec 2020 14:13:44 -0800 Subject: [PATCH 151/201] Rewrite a StreamTransformer as an extension (dart-lang/watcher#102) --- .../lib/src/directory_watcher/linux.dart | 3 +-- .../lib/src/directory_watcher/mac_os.dart | 4 +--- pkgs/watcher/lib/src/file_watcher/native.dart | 2 +- pkgs/watcher/lib/src/utils.dart | 21 +++++++++---------- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index 06d3508ef..1bf5efd33 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -81,8 +81,7 @@ class _LinuxDirectoryWatcher }))); // Batch the inotify changes together so that we can dedup events. - var innerStream = _nativeEvents.stream - .transform(BatchedStreamTransformer()); + var innerStream = _nativeEvents.stream.batchEvents(); _listen(innerStream, _onBatch, onError: _eventsController.addError); _listen(Directory(path).list(recursive: true), (FileSystemEntity entity) { diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 7dc6e7cfa..4ad94d44e 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -359,9 +359,7 @@ class _MacOSDirectoryWatcher /// Start or restart the underlying [Directory.watch] stream. void _startWatch() { // Batch the FSEvent changes together so that we can dedup events. - var innerStream = Directory(path) - .watch(recursive: true) - .transform(BatchedStreamTransformer()); + var innerStream = Directory(path).watch(recursive: true).batchEvents(); _watchSubscription = innerStream.listen(_onBatch, onError: _eventsController.addError, onDone: _onDone); } diff --git a/pkgs/watcher/lib/src/file_watcher/native.dart b/pkgs/watcher/lib/src/file_watcher/native.dart index f7d92d49e..48f12e672 100644 --- a/pkgs/watcher/lib/src/file_watcher/native.dart +++ b/pkgs/watcher/lib/src/file_watcher/native.dart @@ -47,7 +47,7 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { // Batch the events together so that we can dedup them. _subscription = File(path) .watch() - .transform(BatchedStreamTransformer()) + .batchEvents() .listen(_onBatch, onError: _eventsController.addError, onDone: _onDone); } diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index 66c59d3e5..ecf4e105c 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -20,16 +20,15 @@ bool isDirectoryNotFoundException(Object error) { Set unionAll(Iterable> sets) => sets.fold({}, (union, set) => union.union(set)); -/// A stream transformer that batches all events that are sent at the same time. -/// -/// When multiple events are synchronously added to a stream controller, the -/// [StreamController] implementation uses [scheduleMicrotask] to schedule the -/// asynchronous firing of each event. In order to recreate the synchronous -/// batches, this collates all the events that are received in "nearby" -/// microtasks. -class BatchedStreamTransformer extends StreamTransformerBase> { - @override - Stream> bind(Stream input) { +extension BatchEvents on Stream { + /// Batches all events that are sent at the same time. + /// + /// When multiple events are synchronously added to a stream controller, the + /// [StreamController] implementation uses [scheduleMicrotask] to schedule the + /// asynchronous firing of each event. In order to recreate the synchronous + /// batches, this collates all the events that are received in "nearby" + /// microtasks. + Stream> batchEvents() { var batch = Queue(); return StreamTransformer>.fromHandlers( handleData: (event, sink) { @@ -48,6 +47,6 @@ class BatchedStreamTransformer extends StreamTransformerBase> { batch.clear(); } sink.close(); - }).bind(input); + }).bind(this); } } From cd14204d7d52083b3217ce29a23e0d73d3eee1e4 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 30 Dec 2020 09:47:39 -0800 Subject: [PATCH 152/201] Remove unnecessary imports (dart-lang/watcher#104) --- pkgs/watcher/lib/src/directory_watcher.dart | 1 - pkgs/watcher/lib/src/file_watcher.dart | 1 - pkgs/watcher/lib/src/resubscribable.dart | 1 - 3 files changed, 3 deletions(-) diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart index 043ebab75..158b86b05 100644 --- a/pkgs/watcher/lib/src/directory_watcher.dart +++ b/pkgs/watcher/lib/src/directory_watcher.dart @@ -8,7 +8,6 @@ import '../watcher.dart'; import 'custom_watcher_factory.dart'; import 'directory_watcher/linux.dart'; import 'directory_watcher/mac_os.dart'; -import 'directory_watcher/polling.dart'; import 'directory_watcher/windows.dart'; /// Watches the contents of a directory and emits [WatchEvent]s when something diff --git a/pkgs/watcher/lib/src/file_watcher.dart b/pkgs/watcher/lib/src/file_watcher.dart index 9b8ecc4a5..143aa3172 100644 --- a/pkgs/watcher/lib/src/file_watcher.dart +++ b/pkgs/watcher/lib/src/file_watcher.dart @@ -7,7 +7,6 @@ import 'dart:io'; import '../watcher.dart'; import 'custom_watcher_factory.dart'; import 'file_watcher/native.dart'; -import 'file_watcher/polling.dart'; /// Watches a file and emits [WatchEvent]s when the file has changed. /// diff --git a/pkgs/watcher/lib/src/resubscribable.dart b/pkgs/watcher/lib/src/resubscribable.dart index 1c4bb25d8..91f509079 100644 --- a/pkgs/watcher/lib/src/resubscribable.dart +++ b/pkgs/watcher/lib/src/resubscribable.dart @@ -5,7 +5,6 @@ import 'dart:async'; import '../watcher.dart'; -import 'watch_event.dart'; /// A wrapper for [ManuallyClosedWatcher] that encapsulates support for closing /// the watcher when it has no subscribers and re-opening it when it's From 5e3fce1a80bff62be48c3ae4370c19523580a8a9 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Wed, 6 Jan 2021 08:38:48 -0800 Subject: [PATCH 153/201] Update changelog version following migration (dart-lang/watcher#106) Bump to version 1.0.0 and make changelog and pubspec agree. Add changelog entry for the null safety migration. --- pkgs/watcher/CHANGELOG.md | 4 ++-- pkgs/watcher/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 881aa2f84..832f0e7b9 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,7 +1,7 @@ -# 0.9.8-dev +# 1.0.0-nullsafety.0 +* Migrate to null safety. * Add the ability to create custom Watcher types for specific file paths. -* Require at least Dart 2.8.4. # 0.9.7+15 diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 0309be578..d09361af3 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 0.10.0-nullsafety.0 +version: 1.0.0-nullsafety.0 description: >- A file system watcher. It monitors changes to contents of directories and From 07b47f3c8cc565b22d343baeba3366acdababb84 Mon Sep 17 00:00:00 2001 From: Michal Terepeta Date: Thu, 7 Jan 2021 20:06:14 +0100 Subject: [PATCH 154/201] Fix registerCustomWatcher to allow nulls (dart-lang/watcher#107) The idea behind the closures passed to `registerCustomWatcher` is that they should return `null` if the particular implementation does not support the provided path (in which case we should fallback to other implementations). Modified a test to check this. --- .../lib/src/custom_watcher_factory.dart | 4 +-- .../test/custom_watcher_factory_test.dart | 30 ++++++++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/pkgs/watcher/lib/src/custom_watcher_factory.dart b/pkgs/watcher/lib/src/custom_watcher_factory.dart index 8f4132b99..fc4e3fb90 100644 --- a/pkgs/watcher/lib/src/custom_watcher_factory.dart +++ b/pkgs/watcher/lib/src/custom_watcher_factory.dart @@ -31,9 +31,9 @@ class _CustomWatcherFactory { /// will be used instead of the default. void registerCustomWatcher( String id, - DirectoryWatcher Function(String path, {Duration? pollingDelay})? + DirectoryWatcher? Function(String path, {Duration? pollingDelay})? createDirectoryWatcher, - FileWatcher Function(String path, {Duration? pollingDelay})? + FileWatcher? Function(String path, {Duration? pollingDelay})? createFileWatcher, ) { if (_customWatcherFactories.containsKey(id)) { diff --git a/pkgs/watcher/test/custom_watcher_factory_test.dart b/pkgs/watcher/test/custom_watcher_factory_test.dart index 89f8e3cd0..331d24382 100644 --- a/pkgs/watcher/test/custom_watcher_factory_test.dart +++ b/pkgs/watcher/test/custom_watcher_factory_test.dart @@ -5,15 +5,21 @@ import 'package:watcher/watcher.dart'; void main() { late _MemFs memFs; - final defaultFactoryId = 'MemFs'; + final memFsFactoryId = 'MemFs'; + final noOpFactoryId = 'NoOp'; setUpAll(() { memFs = _MemFs(); - var watcherFactory = _MemFsWatcherFactory(memFs); + var memFsWatcherFactory = _MemFsWatcherFactory(memFs); + var noOpWatcherFactory = _NoOpWatcherFactory(); registerCustomWatcher( - defaultFactoryId, - watcherFactory.createDirectoryWatcher, - watcherFactory.createFileWatcher); + noOpFactoryId, + noOpWatcherFactory.createDirectoryWatcher, + noOpWatcherFactory.createFileWatcher); + registerCustomWatcher( + memFsFactoryId, + memFsWatcherFactory.createDirectoryWatcher, + memFsWatcherFactory.createFileWatcher); }); test('notifies for files', () async { @@ -44,7 +50,7 @@ void main() { test('registering twice throws', () async { expect( - () => registerCustomWatcher(defaultFactoryId, + () => registerCustomWatcher(memFsFactoryId, (_, {pollingDelay}) => throw 0, (_, {pollingDelay}) => throw 0), throwsA(isA())); }); @@ -116,10 +122,18 @@ class _MemFsWatcherFactory { final _MemFs _memFs; _MemFsWatcherFactory(this._memFs); - DirectoryWatcher createDirectoryWatcher(String path, + DirectoryWatcher? createDirectoryWatcher(String path, {Duration? pollingDelay}) => _MemFsWatcher(path, _memFs.watchStream(path)); - FileWatcher createFileWatcher(String path, {Duration? pollingDelay}) => + FileWatcher? createFileWatcher(String path, {Duration? pollingDelay}) => _MemFsWatcher(path, _memFs.watchStream(path)); } + +class _NoOpWatcherFactory { + DirectoryWatcher? createDirectoryWatcher(String path, + {Duration? pollingDelay}) => + null; + + FileWatcher? createFileWatcher(String path, {Duration? pollingDelay}) => null; +} From 05d803db4d71d131e0e80ad825dcbd654082aead Mon Sep 17 00:00:00 2001 From: Alexander Thomas Date: Thu, 14 Jan 2021 17:36:55 +0100 Subject: [PATCH 155/201] Migrate to GitHub Actions (dart-lang/watcher#108) * Delete .travis.yml --- .../.github/workflows/test-package.yml | 60 +++++++++++++++++++ pkgs/watcher/.travis.yml | 28 --------- 2 files changed, 60 insertions(+), 28 deletions(-) create mode 100644 pkgs/watcher/.github/workflows/test-package.yml delete mode 100644 pkgs/watcher/.travis.yml diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml new file mode 100644 index 000000000..e5e47ea27 --- /dev/null +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -0,0 +1,60 @@ +name: Dart CI + +on: + # Run on PRs and pushes to the default branch. + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + - cron: "0 0 * * 0" + +env: + PUB_ENVIRONMENT: bot.github + +jobs: + # Check code formatting and static analysis on a single OS (linux) + # against Dart dev. + analyze: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sdk: [dev] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v0.1 + with: + channel: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Check formatting + run: dart format --output=none --set-exit-if-changed . + if: always() && steps.install.outcome == 'success' + - name: Analyze code + run: dart analyze --fatal-infos + if: always() && steps.install.outcome == 'success' + + # Run tests on a matrix consisting of two dimensions: + # 1. OS: ubuntu-latest, macos-latest, windows-latest + # 2. release channel: dev + test: + needs: analyze + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + sdk: [dev] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v0.1 + with: + channel: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Run VM tests + run: dart test --platform vm + if: always() && steps.install.outcome == 'success' diff --git a/pkgs/watcher/.travis.yml b/pkgs/watcher/.travis.yml deleted file mode 100644 index ca68b1759..000000000 --- a/pkgs/watcher/.travis.yml +++ /dev/null @@ -1,28 +0,0 @@ -language: dart - -dart: -- dev - -os: -- linux -- windows -- osx - -dart_task: -- test - -matrix: - include: - - dart: dev - dart_task: dartfmt - - dart: dev - dart_task: - dartanalyzer: --fatal-warnings --fatal-infos . - -# Only building master means that we don't run two builds for each pull request. -branches: - only: [master] - -cache: - directories: - - $HOME/.pub-cache From 8fd6d28a8d907004470ab9e377d6320ccadfa27d Mon Sep 17 00:00:00 2001 From: Jacob MacDonald Date: Fri, 5 Feb 2021 13:46:34 -0800 Subject: [PATCH 156/201] null safety stable release (dart-lang/watcher#109) --- pkgs/watcher/CHANGELOG.md | 4 ++++ pkgs/watcher/pubspec.yaml | 14 ++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 832f0e7b9..6b7785cc1 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.0.0 + +* Stable null safety release. + # 1.0.0-nullsafety.0 * Migrate to null safety. diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index d09361af3..bbb07a311 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 1.0.0-nullsafety.0 +version: 1.0.0 description: >- A file system watcher. It monitors changes to contents of directories and @@ -10,14 +10,12 @@ environment: sdk: '>=2.12.0-0 <3.0.0' dependencies: - async: ^2.5.0-nullsafety.3 - path: ^1.8.0-nullsafety.3 - pedantic: ^1.10.0-nullsafety.3 + async: ^2.5.0 + path: ^1.8.0 + pedantic: ^1.10.0 dev_dependencies: - benchmark_harness: ^2.0.0-nullsafety.0 - test: ^1.16.0-nullsafety.13 + benchmark_harness: ^2.0.0 + test: ^1.16.0 test_descriptor: ^2.0.0-nullsafety -dependency_overrides: - analyzer: ^0.41.1 From 4c0925597075481d10db9c80bef9966872781df4 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 30 Mar 2021 20:35:31 -0700 Subject: [PATCH 157/201] Latest setup, test on oldest supported SDK (dart-lang/watcher#110) --- pkgs/watcher/.github/workflows/test-package.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index e5e47ea27..36c2478d8 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -23,9 +23,9 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v0.1 + - uses: dart-lang/setup-dart@v1.0 with: - channel: ${{ matrix.sdk }} + sdk: ${{ matrix.sdk }} - id: install name: Install dependencies run: dart pub get @@ -46,12 +46,12 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - sdk: [dev] + sdk: [2.12.0, dev] steps: - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v0.1 + - uses: dart-lang/setup-dart@v1.0 with: - channel: ${{ matrix.sdk }} + sdk: ${{ matrix.sdk }} - id: install name: Install dependencies run: dart pub get From 8c0f82dccfa23b4f44f1dd1e8550e8a5d714dbf4 Mon Sep 17 00:00:00 2001 From: Franklin Yow <58489007+franklinyow@users.noreply.github.com> Date: Fri, 2 Apr 2021 15:53:43 -0700 Subject: [PATCH 158/201] Update LICENSE Changes to comply with internal review --- pkgs/watcher/LICENSE | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/LICENSE b/pkgs/watcher/LICENSE index 5c60afea3..000cd7bec 100644 --- a/pkgs/watcher/LICENSE +++ b/pkgs/watcher/LICENSE @@ -1,4 +1,5 @@ -Copyright 2014, the Dart project authors. All rights reserved. +Copyright 2014, the Dart project authors. + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -9,7 +10,7 @@ met: copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. From 94f545ce6e1dda69ab6a11bcb441219c933ae211 Mon Sep 17 00:00:00 2001 From: Jacob MacDonald Date: Thu, 30 Sep 2021 09:29:36 -0700 Subject: [PATCH 159/201] drop package:pedantic, use package:lints (dart-lang/watcher#120) Fixes dart-lang/watcher#119 --- pkgs/watcher/CHANGELOG.md | 4 ++++ pkgs/watcher/analysis_options.yaml | 3 ++- pkgs/watcher/pubspec.yaml | 6 +++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 6b7785cc1..fd5efe3a4 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.0.1 + +* Drop package:pedantic and use package:lints instead. + # 1.0.0 * Stable null safety release. diff --git a/pkgs/watcher/analysis_options.yaml b/pkgs/watcher/analysis_options.yaml index 6eccbf845..b9b528743 100644 --- a/pkgs/watcher/analysis_options.yaml +++ b/pkgs/watcher/analysis_options.yaml @@ -1,4 +1,4 @@ -include: package:pedantic/analysis_options.yaml +include: package:lints/recommended.yaml analyzer: strong-mode: implicit-casts: false @@ -12,6 +12,7 @@ analyzer: linter: rules: # comment_references # https://github.com/dart-lang/sdk/issues/39467 + - depend_on_referenced_packages - prefer_generic_function_type_aliases - prefer_typing_uninitialized_variables - unnecessary_const diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index bbb07a311..19e572cc3 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 1.0.0 +version: 1.0.1 description: >- A file system watcher. It monitors changes to contents of directories and @@ -7,14 +7,14 @@ description: >- repository: https://github.com/dart-lang/watcher environment: - sdk: '>=2.12.0-0 <3.0.0' + sdk: '>=2.14.0 <3.0.0' dependencies: async: ^2.5.0 path: ^1.8.0 - pedantic: ^1.10.0 dev_dependencies: + lints: ^1.0.0 benchmark_harness: ^2.0.0 test: ^1.16.0 test_descriptor: ^2.0.0-nullsafety From 66cf4d30a346fb858ebbe7b7deec6db026b1cfd9 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 5 Oct 2021 09:00:28 -0700 Subject: [PATCH 160/201] Fix lints/pedantic, require Dart 2.14 (dart-lang/watcher#122) --- pkgs/watcher/.github/workflows/test-package.yml | 2 +- pkgs/watcher/CHANGELOG.md | 4 ++++ pkgs/watcher/lib/src/async_queue.dart | 1 - pkgs/watcher/lib/src/directory_watcher/mac_os.dart | 6 ++---- pkgs/watcher/lib/src/directory_watcher/windows.dart | 8 +++----- pkgs/watcher/lib/src/file_watcher/polling.dart | 2 -- pkgs/watcher/lib/src/watch_event.dart | 3 +++ pkgs/watcher/pubspec.yaml | 4 ++-- pkgs/watcher/test/no_subscription/shared.dart | 3 ++- pkgs/watcher/test/ready/shared.dart | 3 ++- pkgs/watcher/test/utils.dart | 2 +- 11 files changed, 20 insertions(+), 18 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 36c2478d8..971c6f0c5 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -46,7 +46,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - sdk: [2.12.0, dev] + sdk: [2.14.0, dev] steps: - uses: actions/checkout@v2 - uses: dart-lang/setup-dart@v1.0 diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index fd5efe3a4..d987225e7 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.0.2-dev + +- Require Dart SDK >= 2.14 + # 1.0.1 * Drop package:pedantic and use package:lints instead. diff --git a/pkgs/watcher/lib/src/async_queue.dart b/pkgs/watcher/lib/src/async_queue.dart index eca28ad57..f6c76a9cb 100644 --- a/pkgs/watcher/lib/src/async_queue.dart +++ b/pkgs/watcher/lib/src/async_queue.dart @@ -66,6 +66,5 @@ class AsyncQueue { // We have drained the queue, stop processing and wait until something // has been enqueued. _isProcessing = false; - return null; } } diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 4ad94d44e..2046ce0d5 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -7,8 +7,8 @@ import 'dart:io'; import 'package:path/path.dart' as p; -import '../directory_watcher.dart'; import '../constructable_file_system_event.dart'; +import '../directory_watcher.dart'; import '../path_set.dart'; import '../resubscribable.dart'; import '../utils.dart'; @@ -78,9 +78,7 @@ class _MacOSDirectoryWatcher /// events (see issue 14373). late Timer _bogusEventTimer; - _MacOSDirectoryWatcher(String path) - : path = path, - _files = PathSet(path) { + _MacOSDirectoryWatcher(this.path) : _files = PathSet(path) { _startWatch(); // Before we're ready to emit events, wait for [_listDir] to complete and diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index a8200eed9..09b4b36b0 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -26,14 +26,14 @@ class WindowsDirectoryWatcher extends ResubscribableWatcher } class _EventBatcher { - static const Duration _BATCH_DELAY = Duration(milliseconds: 100); + static const Duration _batchDelay = Duration(milliseconds: 100); final List events = []; Timer? timer; void addEvent(FileSystemEvent event, void Function() callback) { events.add(event); timer?.cancel(); - timer = Timer(_BATCH_DELAY, callback); + timer = Timer(_batchDelay, callback); } void cancelTimer() { @@ -87,9 +87,7 @@ class _WindowsDirectoryWatcher final Set> _listSubscriptions = HashSet>(); - _WindowsDirectoryWatcher(String path) - : path = path, - _files = PathSet(path) { + _WindowsDirectoryWatcher(this.path) : _files = PathSet(path) { // Before we're ready to emit events, wait for [_listDir] to complete. _listDir().then((_) { _startWatch(); diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart index 9b9ac5be3..d0b219f36 100644 --- a/pkgs/watcher/lib/src/file_watcher/polling.dart +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -5,8 +5,6 @@ import 'dart:async'; import 'dart:io'; -import 'package:pedantic/pedantic.dart'; - import '../file_watcher.dart'; import '../resubscribable.dart'; import '../stat.dart'; diff --git a/pkgs/watcher/lib/src/watch_event.dart b/pkgs/watcher/lib/src/watch_event.dart index 8b3fabb77..b65afc2bd 100644 --- a/pkgs/watcher/lib/src/watch_event.dart +++ b/pkgs/watcher/lib/src/watch_event.dart @@ -19,12 +19,15 @@ class WatchEvent { /// Enum for what kind of change has happened to a file. class ChangeType { /// A new file has been added. + // ignore: constant_identifier_names static const ADD = ChangeType('add'); /// A file has been removed. + // ignore: constant_identifier_names static const REMOVE = ChangeType('remove'); /// The contents of a file have changed. + // ignore: constant_identifier_names static const MODIFY = ChangeType('modify'); final String _name; diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 19e572cc3..b4102c5cf 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 1.0.1 +version: 1.0.2-dev description: >- A file system watcher. It monitors changes to contents of directories and @@ -17,5 +17,5 @@ dev_dependencies: lints: ^1.0.0 benchmark_harness: ^2.0.0 test: ^1.16.0 - test_descriptor: ^2.0.0-nullsafety + test_descriptor: ^2.0.0 diff --git a/pkgs/watcher/test/no_subscription/shared.dart b/pkgs/watcher/test/no_subscription/shared.dart index bcdba5f16..e7a614454 100644 --- a/pkgs/watcher/test/no_subscription/shared.dart +++ b/pkgs/watcher/test/no_subscription/shared.dart @@ -2,8 +2,9 @@ // 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. +import 'dart:async'; + import 'package:async/async.dart'; -import 'package:pedantic/pedantic.dart'; import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; diff --git a/pkgs/watcher/test/ready/shared.dart b/pkgs/watcher/test/ready/shared.dart index ebe2f0cf4..b6b3cce08 100644 --- a/pkgs/watcher/test/ready/shared.dart +++ b/pkgs/watcher/test/ready/shared.dart @@ -2,7 +2,8 @@ // 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. -import 'package:pedantic/pedantic.dart'; +import 'dart:async'; + import 'package:test/test.dart'; import '../utils.dart'; diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index aa23d50a5..8d8981c39 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -2,11 +2,11 @@ // 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. +import 'dart:async'; import 'dart:io'; import 'package:async/async.dart'; import 'package:path/path.dart' as p; -import 'package:pedantic/pedantic.dart'; import 'package:test/test.dart'; import 'package:test_descriptor/test_descriptor.dart' as d; import 'package:watcher/src/stat.dart'; From a30730a7d203bfeb365ac06320ee0cb4c846ce46 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Wed, 12 Jan 2022 17:35:26 +0000 Subject: [PATCH 161/201] Ensure ready future completes (with error) in the case the watcher fails and closes. (dart-lang/watcher#123) See dart-lang/watcher#115. --- pkgs/watcher/CHANGELOG.md | 1 + .../lib/src/directory_watcher/linux.dart | 61 +++++++++++++------ .../lib/src/directory_watcher/mac_os.dart | 17 +++++- .../lib/src/directory_watcher/polling.dart | 12 ++-- .../lib/src/directory_watcher/windows.dart | 8 ++- pkgs/watcher/lib/src/utils.dart | 2 +- pkgs/watcher/lib/watcher.dart | 3 + pkgs/watcher/test/ready/shared.dart | 13 ++++ 8 files changed, 88 insertions(+), 29 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index d987225e7..af54045f6 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,6 +1,7 @@ # 1.0.2-dev - Require Dart SDK >= 2.14 +- Ensure `DirectoryWatcher.ready` completes even when errors occur that close the watcher. # 1.0.1 diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index 1bf5efd33..dafaeb19a 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -82,20 +82,32 @@ class _LinuxDirectoryWatcher // Batch the inotify changes together so that we can dedup events. var innerStream = _nativeEvents.stream.batchEvents(); - _listen(innerStream, _onBatch, onError: _eventsController.addError); - - _listen(Directory(path).list(recursive: true), (FileSystemEntity entity) { - if (entity is Directory) { - _watchSubdir(entity.path); - } else { - _files.add(entity.path); + _listen(innerStream, _onBatch, + onError: (Object error, StackTrace stackTrace) { + // Guarantee that ready always completes. + if (!isReady) { + _readyCompleter.complete(); } - }, onError: (Object error, StackTrace stackTrace) { _eventsController.addError(error, stackTrace); - close(); - }, onDone: () { - _readyCompleter.complete(); - }, cancelOnError: true); + }); + + _listen( + Directory(path).list(recursive: true), + (FileSystemEntity entity) { + if (entity is Directory) { + _watchSubdir(entity.path); + } else { + _files.add(entity.path); + } + }, + onError: _emitError, + onDone: () { + if (!isReady) { + _readyCompleter.complete(); + } + }, + cancelOnError: true, + ); } @override @@ -195,15 +207,15 @@ class _LinuxDirectoryWatcher // contents, if (files.contains(path) && _files.contains(path)) continue; for (var file in _files.remove(path)) { - _emit(ChangeType.REMOVE, file); + _emitEvent(ChangeType.REMOVE, file); } } for (var file in files) { if (_files.contains(file)) { - _emit(ChangeType.MODIFY, file); + _emitEvent(ChangeType.MODIFY, file); } else { - _emit(ChangeType.ADD, file); + _emitEvent(ChangeType.ADD, file); _files.add(file); } } @@ -221,15 +233,14 @@ class _LinuxDirectoryWatcher _watchSubdir(entity.path); } else { _files.add(entity.path); - _emit(ChangeType.ADD, entity.path); + _emitEvent(ChangeType.ADD, entity.path); } }, onError: (Object error, StackTrace stackTrace) { // Ignore an exception caused by the dir not existing. It's fine if it // was added and then quickly removed. if (error is FileSystemException) return; - _eventsController.addError(error, stackTrace); - close(); + _emitError(error, stackTrace); }, cancelOnError: true); } @@ -242,7 +253,7 @@ class _LinuxDirectoryWatcher // caused by a MOVE, we need to manually emit events. if (isReady) { for (var file in _files.paths) { - _emit(ChangeType.REMOVE, file); + _emitEvent(ChangeType.REMOVE, file); } } @@ -251,12 +262,22 @@ class _LinuxDirectoryWatcher /// Emits a [WatchEvent] with [type] and [path] if this watcher is in a state /// to emit events. - void _emit(ChangeType type, String path) { + void _emitEvent(ChangeType type, String path) { if (!isReady) return; if (_eventsController.isClosed) return; _eventsController.add(WatchEvent(type, path)); } + /// Emit an error, then close the watcher. + void _emitError(Object error, StackTrace stackTrace) { + // Guarantee that ready always completes. + if (!isReady) { + _readyCompleter.complete(); + } + _eventsController.addError(error, stackTrace); + close(); + } + /// Like [Stream.listen], but automatically adds the subscription to /// [_subscriptions] so that it can be canceled when [close] is called. void _listen(Stream stream, void Function(T) onData, diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 2046ce0d5..12648c8a6 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -87,8 +87,11 @@ class _MacOSDirectoryWatcher // // If we do receive a batch of events, [_onBatch] will ensure that these // futures don't fire and that the directory is re-listed. - Future.wait([_listDir(), _waitForBogusEvents()]) - .then((_) => _readyCompleter.complete()); + Future.wait([_listDir(), _waitForBogusEvents()]).then((_) { + if (!isReady) { + _readyCompleter.complete(); + } + }); } @override @@ -115,7 +118,11 @@ class _MacOSDirectoryWatcher // Cancel the timer because bogus events only occur in the first batch, so // we can fire [ready] as soon as we're done listing the directory. _bogusEventTimer.cancel(); - _listDir().then((_) => _readyCompleter.complete()); + _listDir().then((_) { + if (!isReady) { + _readyCompleter.complete(); + } + }); return; } @@ -396,6 +403,10 @@ class _MacOSDirectoryWatcher /// Emit an error, then close the watcher. void _emitError(Object error, StackTrace stackTrace) { + // Guarantee that ready always completes. + if (!isReady) { + _readyCompleter.complete(); + } _eventsController.addError(error, stackTrace); close(); } diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 2a43937b8..3dcec05a3 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -43,11 +43,11 @@ class _PollingDirectoryWatcher final _events = StreamController.broadcast(); @override - bool get isReady => _ready.isCompleted; + bool get isReady => _readyCompleter.isCompleted; @override - Future get ready => _ready.future; - final _ready = Completer(); + Future get ready => _readyCompleter.future; + final _readyCompleter = Completer(); /// The amount of time the watcher pauses between successive polls of the /// directory contents. @@ -119,6 +119,10 @@ class _PollingDirectoryWatcher if (entity is! File) return; _filesToProcess.add(entity.path); }, onError: (Object error, StackTrace stackTrace) { + // Guarantee that ready always completes. + if (!isReady) { + _readyCompleter.complete(); + } if (!isDirectoryNotFoundException(error)) { // It's some unknown error. Pipe it over to the event stream so the // user can see it. @@ -177,7 +181,7 @@ class _PollingDirectoryWatcher _lastModifieds.remove(removed); } - if (!isReady) _ready.complete(); + if (!isReady) _readyCompleter.complete(); // Wait and then poll again. await Future.delayed(_pollingDelay); diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 09b4b36b0..710caf54a 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -92,7 +92,9 @@ class _WindowsDirectoryWatcher _listDir().then((_) { _startWatch(); _startParentWatcher(); - _readyCompleter.complete(); + if (!isReady) { + _readyCompleter.complete(); + } }); } @@ -427,6 +429,10 @@ class _WindowsDirectoryWatcher /// Emit an error, then close the watcher. void _emitError(Object error, StackTrace stackTrace) { + // Guarantee that ready always completes. + if (!isReady) { + _readyCompleter.complete(); + } _eventsController.addError(error, stackTrace); close(); } diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart index ecf4e105c..c2e71b3c1 100644 --- a/pkgs/watcher/lib/src/utils.dart +++ b/pkgs/watcher/lib/src/utils.dart @@ -3,8 +3,8 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; -import 'dart:io'; import 'dart:collection'; +import 'dart:io'; /// Returns `true` if [error] is a [FileSystemException] for a missing /// directory. diff --git a/pkgs/watcher/lib/watcher.dart b/pkgs/watcher/lib/watcher.dart index 22e0d6e04..12a536961 100644 --- a/pkgs/watcher/lib/watcher.dart +++ b/pkgs/watcher/lib/watcher.dart @@ -42,6 +42,9 @@ abstract class Watcher { /// /// If the watcher is already monitoring, this returns an already complete /// future. + /// + /// This future always completes successfully as errors are provided through + /// the [events] stream. Future get ready; /// Creates a new [DirectoryWatcher] or [FileWatcher] monitoring [path], diff --git a/pkgs/watcher/test/ready/shared.dart b/pkgs/watcher/test/ready/shared.dart index b6b3cce08..6578c52cb 100644 --- a/pkgs/watcher/test/ready/shared.dart +++ b/pkgs/watcher/test/ready/shared.dart @@ -61,4 +61,17 @@ void sharedTests() { // Should be back to not ready. expect(watcher.ready, doesNotComplete); }); + + test('completes even if directory does not exist', () async { + var watcher = createWatcher(path: 'does/not/exist'); + + // Subscribe to the events (else ready will never fire). + var subscription = watcher.events.listen((event) {}, onError: (error) {}); + + // Expect ready still completes. + await watcher.ready; + + // Now unsubscribe. + await subscription.cancel(); + }); } From 67c6d6526dfdd6ccd69818e5bcd06c72d7385643 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Wed, 19 Jan 2022 16:10:14 +0000 Subject: [PATCH 162/201] Add/enable Windows tests (dart-lang/watcher#124) --- pkgs/watcher/test/directory_watcher/shared.dart | 5 ++++- .../test/directory_watcher/windows_test.dart | 4 +--- .../test/no_subscription/windows_test.dart | 17 +++++++++++++++++ pkgs/watcher/test/ready/shared.dart | 8 ++++++-- pkgs/watcher/test/ready/windows_test.dart | 17 +++++++++++++++++ pkgs/watcher/test/utils.dart | 12 ++++++++++++ 6 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 pkgs/watcher/test/no_subscription/windows_test.dart create mode 100644 pkgs/watcher/test/ready/windows_test.dart diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index ebce488db..07fbf9cf2 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -134,6 +134,8 @@ void sharedTests() { renameFile('from.txt', 'to.txt'); await inAnyOrder([isRemoveEvent('from.txt'), isModifyEvent('to.txt')]); + }, onPlatform: { + 'windows': Skip('https://github.com/dart-lang/watcher/issues/125') }); }); @@ -276,7 +278,8 @@ void sharedTests() { isAddEvent('new') ]); }, onPlatform: { - 'mac-os': Skip('https://github.com/dart-lang/watcher/issues/21') + 'mac-os': Skip('https://github.com/dart-lang/watcher/issues/21'), + 'windows': Skip('https://github.com/dart-lang/watcher/issues/21') }); test('emits events for many nested files added at once', () async { diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 6ea412f77..3ddb47e03 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -14,11 +14,9 @@ import '../utils.dart'; void main() { watcherFactory = (dir) => WindowsDirectoryWatcher(dir); - // TODO(grouma) - renable when https://github.com/dart-lang/sdk/issues/31760 - // is resolved. group('Shared Tests:', () { sharedTests(); - }, skip: 'SDK issue see - https://github.com/dart-lang/sdk/issues/31760'); + }); test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () { expect(DirectoryWatcher('.'), TypeMatcher()); diff --git a/pkgs/watcher/test/no_subscription/windows_test.dart b/pkgs/watcher/test/no_subscription/windows_test.dart new file mode 100644 index 000000000..eb381d025 --- /dev/null +++ b/pkgs/watcher/test/no_subscription/windows_test.dart @@ -0,0 +1,17 @@ +// Copyright (c) 2022, 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. + +@TestOn('windows') + +import 'package:test/test.dart'; +import 'package:watcher/src/directory_watcher/windows.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +void main() { + watcherFactory = (dir) => WindowsDirectoryWatcher(dir); + + sharedTests(); +} diff --git a/pkgs/watcher/test/ready/shared.dart b/pkgs/watcher/test/ready/shared.dart index 6578c52cb..d9bb5aefa 100644 --- a/pkgs/watcher/test/ready/shared.dart +++ b/pkgs/watcher/test/ready/shared.dart @@ -21,19 +21,21 @@ void sharedTests() { expect(ready, isFalse); // Subscribe to the events. - watcher.events.listen((event) {}); + var subscription = watcher.events.listen((event) {}); await watcher.ready; // Should eventually be ready. expect(watcher.isReady, isTrue); + + await subscription.cancel(); }); test('ready completes immediately when already ready', () async { var watcher = createWatcher(); // Subscribe to the events. - watcher.events.listen((event) {}); + var subscription = watcher.events.listen((event) {}); // Allow watcher to become ready await watcher.ready; @@ -43,6 +45,8 @@ void sharedTests() { watcher.ready.timeout(Duration(milliseconds: 0), onTimeout: () => throw 'Does not complete immedately'), completes); + + await subscription.cancel(); }); test('ready returns a future that does not complete after unsubscribing', diff --git a/pkgs/watcher/test/ready/windows_test.dart b/pkgs/watcher/test/ready/windows_test.dart new file mode 100644 index 000000000..eb381d025 --- /dev/null +++ b/pkgs/watcher/test/ready/windows_test.dart @@ -0,0 +1,17 @@ +// Copyright (c) 2022, 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. + +@TestOn('windows') + +import 'package:test/test.dart'; +import 'package:watcher/src/directory_watcher/windows.dart'; + +import 'shared.dart'; +import '../utils.dart'; + +void main() { + watcherFactory = (dir) => WindowsDirectoryWatcher(dir); + + sharedTests(); +} diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 8d8981c39..23adcbc40 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -48,6 +48,12 @@ Watcher createWatcher({String? path}) { /// The stream of events from the watcher started with [startWatcher]. late StreamQueue _watcherEvents; +/// Whether the event stream has been closed. +/// +/// If this is not done by a test (by calling [startClosingEventStream]) it will +/// be done automatically via [addTearDown] in [startWatcher]. +var _hasClosedStream = true; + /// Creates a new [Watcher] that watches a temporary file or directory and /// starts monitoring it for events. /// @@ -70,6 +76,10 @@ Future startWatcher({String? path}) async { _watcherEvents = StreamQueue(watcher.events); // Forces a subscription to the underlying stream. unawaited(_watcherEvents.hasNext); + + _hasClosedStream = false; + addTearDown(startClosingEventStream); + await watcher.ready; } @@ -80,6 +90,8 @@ Future startWatcher({String? path}) async { /// indefinitely because they might in the future and because the watcher is /// normally only closed after the test completes. void startClosingEventStream() async { + if (_hasClosedStream) return; + _hasClosedStream = true; await pumpEventQueue(); await _watcherEvents.cancel(immediate: true); } From 354a02b3e3de703c0e1859b4417d6ac303ad0691 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 4 Oct 2022 13:14:45 -0700 Subject: [PATCH 163/201] update CI; add markdown badges --- pkgs/watcher/.github/dependabot.yaml | 8 ++++++++ pkgs/watcher/.github/workflows/test-package.yml | 8 ++++---- pkgs/watcher/CHANGELOG.md | 2 +- pkgs/watcher/README.md | 10 ++++++++-- pkgs/watcher/analysis_options.yaml | 1 + pkgs/watcher/pubspec.yaml | 3 +-- 6 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 pkgs/watcher/.github/dependabot.yaml diff --git a/pkgs/watcher/.github/dependabot.yaml b/pkgs/watcher/.github/dependabot.yaml new file mode 100644 index 000000000..214481934 --- /dev/null +++ b/pkgs/watcher/.github/dependabot.yaml @@ -0,0 +1,8 @@ +# Dependabot configuration file. +version: 2 + +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 971c6f0c5..c63ec37f9 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,8 +22,8 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v1.0 + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} - id: install @@ -48,8 +48,8 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [2.14.0, dev] steps: - - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v1.0 + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} - id: install diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index af54045f6..a2e3e0bc5 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,4 +1,4 @@ -# 1.0.2-dev +# 1.0.2 - Require Dart SDK >= 2.14 - Ensure `DirectoryWatcher.ready` completes even when errors occur that close the watcher. diff --git a/pkgs/watcher/README.md b/pkgs/watcher/README.md index 61cc1f9f9..677ca35b2 100644 --- a/pkgs/watcher/README.md +++ b/pkgs/watcher/README.md @@ -1,4 +1,10 @@ +[![Dart CI](https://github.com/dart-lang/watcher/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/watcher/actions/workflows/test-package.yml) +[![pub package](https://img.shields.io/pub/v/watcher.svg)](https://pub.dev/packages/watcher) +[![package publisher](https://img.shields.io/pub/publisher/watcher.svg)](https://pub.dev/packages/watcher/publisher) + A file system watcher. -It monitors changes to contents of directories and sends notifications when -files have been added, removed, or modified. +## What's this? + +`package:watcher` monitors changes to contents of directories and sends +notifications when files have been added, removed, or modified. diff --git a/pkgs/watcher/analysis_options.yaml b/pkgs/watcher/analysis_options.yaml index b9b528743..29327a49a 100644 --- a/pkgs/watcher/analysis_options.yaml +++ b/pkgs/watcher/analysis_options.yaml @@ -1,4 +1,5 @@ include: package:lints/recommended.yaml + analyzer: strong-mode: implicit-casts: false diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index b4102c5cf..947237407 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,6 +1,5 @@ name: watcher -version: 1.0.2-dev - +version: 1.0.2 description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. From 61320128b4606eee3a4240c1479236ea7291d6bd Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 4 Oct 2022 13:15:22 -0700 Subject: [PATCH 164/201] add markdown badges --- pkgs/watcher/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index a2e3e0bc5..9104504d6 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -2,6 +2,7 @@ - Require Dart SDK >= 2.14 - Ensure `DirectoryWatcher.ready` completes even when errors occur that close the watcher. +- Add markdown badges to the readme. # 1.0.1 From 5b1b2b2227322d3bb0dd8aa58a166049dcc77d55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 13:04:12 -0800 Subject: [PATCH 165/201] Bump actions/checkout from 3.1.0 to 3.2.0 (dart-lang/watcher#132) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8...755da8c3cf115ac066823e79a1e1788f8940201b) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index c63ec37f9..908b06154 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} @@ -48,7 +48,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [2.14.0, dev] steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} From a6ebf3320c5515f2d1b6d2855600dcb73e0d4f2e Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 9 Jan 2023 19:25:03 -0800 Subject: [PATCH 166/201] Migrate from no-implicit-casts to strict-casts (dart-lang/watcher#134) --- pkgs/watcher/analysis_options.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/analysis_options.yaml b/pkgs/watcher/analysis_options.yaml index 29327a49a..43201629b 100644 --- a/pkgs/watcher/analysis_options.yaml +++ b/pkgs/watcher/analysis_options.yaml @@ -1,8 +1,8 @@ include: package:lints/recommended.yaml analyzer: - strong-mode: - implicit-casts: false + language: + strict-casts: true errors: todo: ignore unused_import: error From 4d585e6d833cde658c98a12fb5dd7854bf2b848c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Feb 2023 07:21:08 -0800 Subject: [PATCH 167/201] Bump dart-lang/setup-dart from 1.3 to 1.4 (dart-lang/watcher#136) Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.3 to 1.4. - [Release notes](https://github.com/dart-lang/setup-dart/releases) - [Changelog](https://github.com/dart-lang/setup-dart/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/setup-dart/compare/6a218f2413a3e78e9087f638a238f6b40893203d...a57a6c04cf7d4840e88432aad6281d1e125f0d46) --- updated-dependencies: - dependency-name: dart-lang/setup-dart dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 908b06154..ea4b20d28 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b - - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d + - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} - id: install @@ -49,7 +49,7 @@ jobs: sdk: [2.14.0, dev] steps: - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b - - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d + - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} - id: install From a208546819175283eb78f48ab37c887f0257c854 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Feb 2023 07:35:02 -0800 Subject: [PATCH 168/201] Bump actions/checkout from 3.2.0 to 3.3.0 (dart-lang/watcher#135) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.2.0 to 3.3.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/755da8c3cf115ac066823e79a1e1788f8940201b...ac593985615ec2ede58e132d2e21d2b1cbd6127c) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index ea4b20d28..6016675b0 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} @@ -48,7 +48,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [2.14.0, dev] steps: - - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} From 83d040c6540b143cf745c6de929611376084299e Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 7 Mar 2023 08:42:04 -0800 Subject: [PATCH 169/201] Require Dart 2.19, use new team lints (dart-lang/watcher#138) Enable some tests that now seem to be passing on mac --- pkgs/watcher/.github/workflows/test-package.yml | 2 +- pkgs/watcher/CHANGELOG.md | 4 ++++ pkgs/watcher/analysis_options.yaml | 16 +++------------- pkgs/watcher/benchmark/path_set.dart | 7 +++---- pkgs/watcher/example/watch.dart | 4 +--- .../lib/src/directory_watcher/mac_os.dart | 6 ++---- .../lib/src/directory_watcher/windows.dart | 6 +++--- pkgs/watcher/lib/src/path_set.dart | 2 +- pkgs/watcher/pubspec.yaml | 6 +++--- .../test/custom_watcher_factory_test.dart | 9 ++++++--- .../test/directory_watcher/linux_test.dart | 5 +++-- .../test/directory_watcher/mac_os_test.dart | 5 +++-- .../test/directory_watcher/polling_test.dart | 2 +- pkgs/watcher/test/directory_watcher/shared.dart | 1 - .../test/directory_watcher/windows_test.dart | 9 ++++----- pkgs/watcher/test/file_watcher/native_test.dart | 5 +++-- pkgs/watcher/test/file_watcher/polling_test.dart | 3 ++- .../watcher/test/no_subscription/linux_test.dart | 5 +++-- .../test/no_subscription/mac_os_test.dart | 6 +++--- .../test/no_subscription/polling_test.dart | 4 ++-- .../test/no_subscription/windows_test.dart | 5 +++-- pkgs/watcher/test/ready/linux_test.dart | 5 +++-- pkgs/watcher/test/ready/mac_os_test.dart | 5 +++-- pkgs/watcher/test/ready/polling_test.dart | 4 ++-- pkgs/watcher/test/ready/shared.dart | 9 ++++++--- pkgs/watcher/test/ready/windows_test.dart | 5 +++-- 26 files changed, 71 insertions(+), 69 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 6016675b0..0c0ce4994 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -46,7 +46,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - sdk: [2.14.0, dev] + sdk: [2.19.0, dev] steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 9104504d6..1271b6370 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.0.3-dev + +- Require Dart SDK >= 2.19 + # 1.0.2 - Require Dart SDK >= 2.14 diff --git a/pkgs/watcher/analysis_options.yaml b/pkgs/watcher/analysis_options.yaml index 43201629b..a1fc20f55 100644 --- a/pkgs/watcher/analysis_options.yaml +++ b/pkgs/watcher/analysis_options.yaml @@ -1,20 +1,10 @@ -include: package:lints/recommended.yaml +include: package:dart_flutter_team_lints/analysis_options.yaml analyzer: language: strict-casts: true - errors: - todo: ignore - unused_import: error - unused_element: error - unused_local_variable: error - dead_code: error linter: rules: - # comment_references # https://github.com/dart-lang/sdk/issues/39467 - - depend_on_referenced_packages - - prefer_generic_function_type_aliases - - prefer_typing_uninitialized_variables - - unnecessary_const - - unnecessary_new + - comment_references # https://github.com/dart-lang/sdk/issues/39467 + diff --git a/pkgs/watcher/benchmark/path_set.dart b/pkgs/watcher/benchmark/path_set.dart index 858df3cc0..82109b0ab 100644 --- a/pkgs/watcher/benchmark/path_set.dart +++ b/pkgs/watcher/benchmark/path_set.dart @@ -10,7 +10,6 @@ import 'dart:math' as math; import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:path/path.dart' as p; - import 'package:watcher/src/path_set.dart'; final String root = Platform.isWindows ? r'C:\root' : '/root'; @@ -31,7 +30,7 @@ abstract class PathSetBenchmark extends BenchmarkBase { /// Each virtual directory contains ten entries: either subdirectories or /// files. void walkTree(int depth, void Function(String) callback) { - void recurse(String path, remainingDepth) { + void recurse(String path, int remainingDepth) { for (var i = 0; i < 10; i++) { var padded = i.toString().padLeft(2, '0'); if (remainingDepth == 0) { @@ -100,7 +99,7 @@ class ContainsBenchmark extends PathSetBenchmark { if (pathSet.contains(path)) contained++; } - if (contained != 10000) throw 'Wrong result: $contained'; + if (contained != 10000) throw StateError('Wrong result: $contained'); } } @@ -119,7 +118,7 @@ class PathsBenchmark extends PathSetBenchmark { count++; } - if (count != 10000) throw 'Wrong result: $count'; + if (count != 10000) throw StateError('Wrong result: $count'); } } diff --git a/pkgs/watcher/example/watch.dart b/pkgs/watcher/example/watch.dart index 650a4b877..0a0b3577a 100644 --- a/pkgs/watcher/example/watch.dart +++ b/pkgs/watcher/example/watch.dart @@ -15,7 +15,5 @@ void main(List arguments) { } var watcher = DirectoryWatcher(p.absolute(arguments[0])); - watcher.events.listen((event) { - print(event); - }); + watcher.events.listen(print); } diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 12648c8a6..415d17a33 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -160,9 +160,7 @@ class _MacOSDirectoryWatcher subscription.onDone(() { _listSubscriptions.remove(subscription); }); - subscription.onError((Object e, StackTrace stackTrace) { - _emitError(e, stackTrace); - }); + subscription.onError(_emitError); _listSubscriptions.add(subscription); } else if (event is FileSystemModifyEvent) { assert(!event.isDirectory); @@ -294,7 +292,7 @@ class _MacOSDirectoryWatcher return ConstructableFileSystemModifyEvent( batch.first.path, isDir, false); default: - throw 'unreachable'; + throw StateError('unreachable'); } } diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 710caf54a..141545b79 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -155,8 +155,7 @@ class _WindowsDirectoryWatcher void _onEvent(FileSystemEvent event) { assert(isReady); - final batcher = - _eventBatchers.putIfAbsent(event.path, () => _EventBatcher()); + final batcher = _eventBatchers.putIfAbsent(event.path, _EventBatcher.new); batcher.addEvent(event, () { _eventBatchers.remove(event.path); _onBatch(batcher.events); @@ -316,7 +315,7 @@ class _WindowsDirectoryWatcher case FileSystemEvent.move: return null; default: - throw 'unreachable'; + throw StateError('unreachable'); } } @@ -397,6 +396,7 @@ class _WindowsDirectoryWatcher _eventsController.addError(error, stackTrace); _startWatch(); } else { + // ignore: only_throw_errors throw error; } }); diff --git a/pkgs/watcher/lib/src/path_set.dart b/pkgs/watcher/lib/src/path_set.dart index 090090eeb..4f41cf924 100644 --- a/pkgs/watcher/lib/src/path_set.dart +++ b/pkgs/watcher/lib/src/path_set.dart @@ -33,7 +33,7 @@ class PathSet { var parts = p.split(path); var entry = _entries; for (var part in parts) { - entry = entry.contents.putIfAbsent(part, () => _Entry()); + entry = entry.contents.putIfAbsent(part, _Entry.new); } entry.isExplicit = true; diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 947237407..e64e79d42 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,20 +1,20 @@ name: watcher -version: 1.0.2 +version: 1.0.3-dev description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. repository: https://github.com/dart-lang/watcher environment: - sdk: '>=2.14.0 <3.0.0' + sdk: '>=2.19.0 <3.0.0' dependencies: async: ^2.5.0 path: ^1.8.0 dev_dependencies: - lints: ^1.0.0 benchmark_harness: ^2.0.0 + dart_flutter_team_lints: ^1.0.0 test: ^1.16.0 test_descriptor: ^2.0.0 diff --git a/pkgs/watcher/test/custom_watcher_factory_test.dart b/pkgs/watcher/test/custom_watcher_factory_test.dart index 331d24382..e9d65bb55 100644 --- a/pkgs/watcher/test/custom_watcher_factory_test.dart +++ b/pkgs/watcher/test/custom_watcher_factory_test.dart @@ -50,9 +50,12 @@ void main() { test('registering twice throws', () async { expect( - () => registerCustomWatcher(memFsFactoryId, - (_, {pollingDelay}) => throw 0, (_, {pollingDelay}) => throw 0), - throwsA(isA())); + () => registerCustomWatcher( + memFsFactoryId, + (_, {pollingDelay}) => throw UnimplementedError(), + (_, {pollingDelay}) => throw UnimplementedError()), + throwsA(isA()), + ); }); test('finding two applicable factories throws', () async { diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart index b4745a33f..7b638a860 100644 --- a/pkgs/watcher/test/directory_watcher/linux_test.dart +++ b/pkgs/watcher/test/directory_watcher/linux_test.dart @@ -3,16 +3,17 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('linux') +library; import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/linux.dart'; import 'package:watcher/watcher.dart'; -import 'shared.dart'; import '../utils.dart'; +import 'shared.dart'; void main() { - watcherFactory = (dir) => LinuxDirectoryWatcher(dir); + watcherFactory = LinuxDirectoryWatcher.new; sharedTests(); diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 58ba31aaa..347c5e731 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -3,16 +3,17 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('mac-os') +library; import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/mac_os.dart'; import 'package:watcher/watcher.dart'; -import 'shared.dart'; import '../utils.dart'; +import 'shared.dart'; void main() { - watcherFactory = (dir) => MacOSDirectoryWatcher(dir); + watcherFactory = MacOSDirectoryWatcher.new; sharedTests(); diff --git a/pkgs/watcher/test/directory_watcher/polling_test.dart b/pkgs/watcher/test/directory_watcher/polling_test.dart index 261d0e998..0cfe738af 100644 --- a/pkgs/watcher/test/directory_watcher/polling_test.dart +++ b/pkgs/watcher/test/directory_watcher/polling_test.dart @@ -5,8 +5,8 @@ import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; -import 'shared.dart'; import '../utils.dart'; +import 'shared.dart'; void main() { // Use a short delay to make the tests run quickly. diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index 07fbf9cf2..8fd842a8e 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -278,7 +278,6 @@ void sharedTests() { isAddEvent('new') ]); }, onPlatform: { - 'mac-os': Skip('https://github.com/dart-lang/watcher/issues/21'), 'windows': Skip('https://github.com/dart-lang/watcher/issues/21') }); diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index 3ddb47e03..a0bdda7f4 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -3,20 +3,19 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('windows') +library; import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/windows.dart'; import 'package:watcher/watcher.dart'; -import 'shared.dart'; import '../utils.dart'; +import 'shared.dart'; void main() { - watcherFactory = (dir) => WindowsDirectoryWatcher(dir); + watcherFactory = WindowsDirectoryWatcher.new; - group('Shared Tests:', () { - sharedTests(); - }); + group('Shared Tests:', sharedTests); test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () { expect(DirectoryWatcher('.'), TypeMatcher()); diff --git a/pkgs/watcher/test/file_watcher/native_test.dart b/pkgs/watcher/test/file_watcher/native_test.dart index b59d4ed95..0d4ad6394 100644 --- a/pkgs/watcher/test/file_watcher/native_test.dart +++ b/pkgs/watcher/test/file_watcher/native_test.dart @@ -3,15 +3,16 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('linux || mac-os') +library; import 'package:test/test.dart'; import 'package:watcher/src/file_watcher/native.dart'; -import 'shared.dart'; import '../utils.dart'; +import 'shared.dart'; void main() { - watcherFactory = (file) => NativeFileWatcher(file); + watcherFactory = NativeFileWatcher.new; setUp(() { writeFile('file.txt'); diff --git a/pkgs/watcher/test/file_watcher/polling_test.dart b/pkgs/watcher/test/file_watcher/polling_test.dart index b83d44fd1..1bf9269fe 100644 --- a/pkgs/watcher/test/file_watcher/polling_test.dart +++ b/pkgs/watcher/test/file_watcher/polling_test.dart @@ -3,12 +3,13 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('linux || mac-os') +library; import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; -import 'shared.dart'; import '../utils.dart'; +import 'shared.dart'; void main() { watcherFactory = (file) => diff --git a/pkgs/watcher/test/no_subscription/linux_test.dart b/pkgs/watcher/test/no_subscription/linux_test.dart index aa5763772..aac08101b 100644 --- a/pkgs/watcher/test/no_subscription/linux_test.dart +++ b/pkgs/watcher/test/no_subscription/linux_test.dart @@ -3,15 +3,16 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('linux') +library; import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/linux.dart'; -import 'shared.dart'; import '../utils.dart'; +import 'shared.dart'; void main() { - watcherFactory = (dir) => LinuxDirectoryWatcher(dir); + watcherFactory = LinuxDirectoryWatcher.new; sharedTests(); } diff --git a/pkgs/watcher/test/no_subscription/mac_os_test.dart b/pkgs/watcher/test/no_subscription/mac_os_test.dart index f2270770c..55a83087c 100644 --- a/pkgs/watcher/test/no_subscription/mac_os_test.dart +++ b/pkgs/watcher/test/no_subscription/mac_os_test.dart @@ -3,16 +3,16 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('mac-os') -@Skip('Flaky due to sdk#23877') +library; import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/mac_os.dart'; -import 'shared.dart'; import '../utils.dart'; +import 'shared.dart'; void main() { - watcherFactory = (dir) => MacOSDirectoryWatcher(dir); + watcherFactory = MacOSDirectoryWatcher.new; sharedTests(); } diff --git a/pkgs/watcher/test/no_subscription/polling_test.dart b/pkgs/watcher/test/no_subscription/polling_test.dart index 633ca2eb4..bfd29588f 100644 --- a/pkgs/watcher/test/no_subscription/polling_test.dart +++ b/pkgs/watcher/test/no_subscription/polling_test.dart @@ -4,11 +4,11 @@ import 'package:watcher/watcher.dart'; -import 'shared.dart'; import '../utils.dart'; +import 'shared.dart'; void main() { - watcherFactory = (dir) => PollingDirectoryWatcher(dir); + watcherFactory = PollingDirectoryWatcher.new; sharedTests(); } diff --git a/pkgs/watcher/test/no_subscription/windows_test.dart b/pkgs/watcher/test/no_subscription/windows_test.dart index eb381d025..9f9e5a9c3 100644 --- a/pkgs/watcher/test/no_subscription/windows_test.dart +++ b/pkgs/watcher/test/no_subscription/windows_test.dart @@ -3,15 +3,16 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('windows') +library; import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/windows.dart'; -import 'shared.dart'; import '../utils.dart'; +import 'shared.dart'; void main() { - watcherFactory = (dir) => WindowsDirectoryWatcher(dir); + watcherFactory = WindowsDirectoryWatcher.new; sharedTests(); } diff --git a/pkgs/watcher/test/ready/linux_test.dart b/pkgs/watcher/test/ready/linux_test.dart index aa5763772..aac08101b 100644 --- a/pkgs/watcher/test/ready/linux_test.dart +++ b/pkgs/watcher/test/ready/linux_test.dart @@ -3,15 +3,16 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('linux') +library; import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/linux.dart'; -import 'shared.dart'; import '../utils.dart'; +import 'shared.dart'; void main() { - watcherFactory = (dir) => LinuxDirectoryWatcher(dir); + watcherFactory = LinuxDirectoryWatcher.new; sharedTests(); } diff --git a/pkgs/watcher/test/ready/mac_os_test.dart b/pkgs/watcher/test/ready/mac_os_test.dart index 4bfdc8d3e..55a83087c 100644 --- a/pkgs/watcher/test/ready/mac_os_test.dart +++ b/pkgs/watcher/test/ready/mac_os_test.dart @@ -3,15 +3,16 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('mac-os') +library; import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/mac_os.dart'; -import 'shared.dart'; import '../utils.dart'; +import 'shared.dart'; void main() { - watcherFactory = (dir) => MacOSDirectoryWatcher(dir); + watcherFactory = MacOSDirectoryWatcher.new; sharedTests(); } diff --git a/pkgs/watcher/test/ready/polling_test.dart b/pkgs/watcher/test/ready/polling_test.dart index 633ca2eb4..bfd29588f 100644 --- a/pkgs/watcher/test/ready/polling_test.dart +++ b/pkgs/watcher/test/ready/polling_test.dart @@ -4,11 +4,11 @@ import 'package:watcher/watcher.dart'; -import 'shared.dart'; import '../utils.dart'; +import 'shared.dart'; void main() { - watcherFactory = (dir) => PollingDirectoryWatcher(dir); + watcherFactory = PollingDirectoryWatcher.new; sharedTests(); } diff --git a/pkgs/watcher/test/ready/shared.dart b/pkgs/watcher/test/ready/shared.dart index d9bb5aefa..1b4dbaa0b 100644 --- a/pkgs/watcher/test/ready/shared.dart +++ b/pkgs/watcher/test/ready/shared.dart @@ -42,9 +42,12 @@ void sharedTests() { // Ensure ready completes immediately expect( - watcher.ready.timeout(Duration(milliseconds: 0), - onTimeout: () => throw 'Does not complete immedately'), - completes); + watcher.ready.timeout( + Duration(milliseconds: 0), + onTimeout: () => throw StateError('Does not complete immedately'), + ), + completes, + ); await subscription.cancel(); }); diff --git a/pkgs/watcher/test/ready/windows_test.dart b/pkgs/watcher/test/ready/windows_test.dart index eb381d025..9f9e5a9c3 100644 --- a/pkgs/watcher/test/ready/windows_test.dart +++ b/pkgs/watcher/test/ready/windows_test.dart @@ -3,15 +3,16 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('windows') +library; import 'package:test/test.dart'; import 'package:watcher/src/directory_watcher/windows.dart'; -import 'shared.dart'; import '../utils.dart'; +import 'shared.dart'; void main() { - watcherFactory = (dir) => WindowsDirectoryWatcher(dir); + watcherFactory = WindowsDirectoryWatcher.new; sharedTests(); } From 3ff8ef8f2d42bfd850e36a10cb2c64a11331cc69 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 7 Mar 2023 08:47:16 -0800 Subject: [PATCH 170/201] blast_repo fixes (dart-lang/watcher#139) auto-publish no-response * changelog tweaks --- .../watcher/.github/workflows/no-response.yml | 34 ++++++++++++ pkgs/watcher/.github/workflows/publish.yaml | 14 +++++ pkgs/watcher/CHANGELOG.md | 54 +++++++++---------- 3 files changed, 73 insertions(+), 29 deletions(-) create mode 100644 pkgs/watcher/.github/workflows/no-response.yml create mode 100644 pkgs/watcher/.github/workflows/publish.yaml diff --git a/pkgs/watcher/.github/workflows/no-response.yml b/pkgs/watcher/.github/workflows/no-response.yml new file mode 100644 index 000000000..ac3e456ec --- /dev/null +++ b/pkgs/watcher/.github/workflows/no-response.yml @@ -0,0 +1,34 @@ +# A workflow to close issues where the author hasn't responded to a request for +# more information; see https://github.com/godofredoc/no-response for docs. + +name: No Response + +# Both `issue_comment` and `scheduled` event types are required. +on: + issue_comment: + types: [created] + schedule: + # Every day at 8am + - cron: '0 8 * * *' + +# All permissions not specified are set to 'none'. +permissions: + issues: write + +jobs: + noResponse: + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'dart-lang' }} + steps: + - uses: godofredoc/no-response@0ce2dc0e63e1c7d2b87752ceed091f6d32c9df09 + with: + responseRequiredLabel: "needs-info" + responseRequiredColor: 4774bc + daysUntilClose: 14 + # Comment to post when closing an Issue for lack of response. + closeComment: > + Without additional information we're not able to resolve this issue, + so it will be closed at this time. You're still free to add more + info and respond to any questions above, though. We'll reopen the + issue if you do. Thanks for your contribution! + token: ${{ github.token }} diff --git a/pkgs/watcher/.github/workflows/publish.yaml b/pkgs/watcher/.github/workflows/publish.yaml new file mode 100644 index 000000000..2239b63d3 --- /dev/null +++ b/pkgs/watcher/.github/workflows/publish.yaml @@ -0,0 +1,14 @@ +# A CI configuration to auto-publish pub packages. + +name: Publish + +on: + pull_request: + branches: [ master ] + push: + tags: [ 'v[0-9]+.[0-9]+.[0-9]+' ] + +jobs: + publish: + if: ${{ github.repository_owner == 'dart-lang' }} + uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 1271b6370..c8aa26648 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,63 +1,59 @@ -# 1.0.3-dev +## 1.0.3-dev - Require Dart SDK >= 2.19 -# 1.0.2 +## 1.0.2 - Require Dart SDK >= 2.14 - Ensure `DirectoryWatcher.ready` completes even when errors occur that close the watcher. - Add markdown badges to the readme. -# 1.0.1 +## 1.0.1 * Drop package:pedantic and use package:lints instead. -# 1.0.0 +## 1.0.0 -* Stable null safety release. - -# 1.0.0-nullsafety.0 - -* Migrate to null safety. +* Require Dart SDK >= 2.12 * Add the ability to create custom Watcher types for specific file paths. -# 0.9.7+15 +## 0.9.7+15 * Fix a bug on Mac where modifying a directory with a path exactly matching a prefix of a modified file would suppress change events for that file. -# 0.9.7+14 +## 0.9.7+14 * Prepare for breaking change in SDK where modified times for not found files becomes meaningless instead of null. -# 0.9.7+13 +## 0.9.7+13 * Catch & forward `FileSystemException` from unexpectedly closed file watchers on windows; the watcher will also be automatically restarted when this occurs. -# 0.9.7+12 +## 0.9.7+12 * Catch `FileSystemException` during `existsSync()` on Windows. * Internal cleanup. -# 0.9.7+11 +## 0.9.7+11 * Fix an analysis hint. -# 0.9.7+10 +## 0.9.7+10 * Set max SDK version to `<3.0.0`, and adjust other dependencies. -# 0.9.7+9 +## 0.9.7+9 * Internal changes only. -# 0.9.7+8 +## 0.9.7+8 * Fix Dart 2.0 type issues on Mac and Windows. -# 0.9.7+7 +## 0.9.7+7 * Updates to support Dart 2.0 core library changes (wave 2.2). See [issue 31847][sdk#31847] for details. @@ -65,37 +61,37 @@ [sdk#31847]: https://github.com/dart-lang/sdk/issues/31847 -# 0.9.7+6 +## 0.9.7+6 * Internal changes only, namely removing dep on scheduled test. -# 0.9.7+5 +## 0.9.7+5 * Fix an analysis warning. -# 0.9.7+4 +## 0.9.7+4 * Declare support for `async` 2.0.0. -# 0.9.7+3 +## 0.9.7+3 * Fix a crashing bug on Linux. -# 0.9.7+2 +## 0.9.7+2 * Narrow the constraint on `async` to reflect the APIs this package is actually using. -# 0.9.7+1 +## 0.9.7+1 * Fix all strong-mode warnings. -# 0.9.7 +## 0.9.7 * Fix a bug in `FileWatcher` where events could be added after watchers were closed. -# 0.9.6 +## 0.9.6 * Add a `Watcher` interface that encompasses watching both files and directories. @@ -105,16 +101,16 @@ * Deprecate `DirectoryWatcher.directory`. Use `DirectoryWatcher.path` instead. -# 0.9.5 +## 0.9.5 * Fix bugs where events could be added after watchers were closed. -# 0.9.4 +## 0.9.4 * Treat add events for known files as modifications instead of discarding them on Mac OS. -# 0.9.3 +## 0.9.3 * Improved support for Windows via `WindowsDirectoryWatcher`. From ea3be5b897b32aac57870c367e8053ebbff776d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 15:40:22 -0700 Subject: [PATCH 171/201] Bump actions/checkout from 3.3.0 to 3.5.0 (dart-lang/watcher#141) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.3.0 to 3.5.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/ac593985615ec2ede58e132d2e21d2b1cbd6127c...8f4b7f84864484a7bf31766abe9204da3cbe65b3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 0c0ce4994..11d3a7077 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} @@ -48,7 +48,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} From f1fe85bb2ea4c1fdb57a6e862ac2f4640f6f8e6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 15:57:27 -0700 Subject: [PATCH 172/201] Bump dart-lang/setup-dart from 1.4.0 to 1.5.0 (dart-lang/watcher#140) Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.4.0 to 1.5.0. - [Release notes](https://github.com/dart-lang/setup-dart/releases) - [Changelog](https://github.com/dart-lang/setup-dart/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/setup-dart/compare/a57a6c04cf7d4840e88432aad6281d1e125f0d46...d6a63dab3335f427404425de0fbfed4686d93c4f) --- updated-dependencies: - dependency-name: dart-lang/setup-dart dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 11d3a7077..d4a42dc62 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 + - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} - id: install @@ -49,7 +49,7 @@ jobs: sdk: [2.19.0, dev] steps: - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 + - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} - id: install From 689550987d6f6478a5437a491c2635572c199cfc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 08:47:50 -0700 Subject: [PATCH 173/201] Bump actions/checkout from 3.5.0 to 3.5.2 (dart-lang/watcher#144) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.0 to 3.5.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/8f4b7f84864484a7bf31766abe9204da3cbe65b3...8e5e7e5ab8b370d6c329ec480221332ada57f0ab) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index d4a42dc62..12968232d 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -48,7 +48,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [2.19.0, dev] steps: - - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} From aa09575d8c4472faa83e2ad739b4cc419435d544 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Wed, 10 May 2023 14:06:54 -0700 Subject: [PATCH 174/201] Remove unnecessary ConstructableFileSystemEvent classes (dart-lang/watcher#143) --- .../.github/workflows/test-package.yml | 2 +- pkgs/watcher/CHANGELOG.md | 5 ++ .../src/constructable_file_system_event.dart | 71 ------------------- .../lib/src/directory_watcher/mac_os.dart | 22 +++--- .../lib/src/directory_watcher/windows.dart | 22 +++--- pkgs/watcher/pubspec.yaml | 4 +- 6 files changed, 28 insertions(+), 98 deletions(-) delete mode 100644 pkgs/watcher/lib/src/constructable_file_system_event.dart diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 12968232d..bc16dd1c4 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -46,7 +46,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - sdk: [2.19.0, dev] + sdk: [3.0.0, dev] steps: - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index c8aa26648..17b93f911 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.1.0-dev + +- Require Dart SDK >= 3.0.0 +- Remove usage of redundant ConstructableFileSystemEvent classes. +- ## 1.0.3-dev - Require Dart SDK >= 2.19 diff --git a/pkgs/watcher/lib/src/constructable_file_system_event.dart b/pkgs/watcher/lib/src/constructable_file_system_event.dart deleted file mode 100644 index 0011a8d64..000000000 --- a/pkgs/watcher/lib/src/constructable_file_system_event.dart +++ /dev/null @@ -1,71 +0,0 @@ -// 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. - -import 'dart:io'; - -abstract class _ConstructableFileSystemEvent implements FileSystemEvent { - @override - final bool isDirectory; - @override - final String path; - @override - int get type; - - _ConstructableFileSystemEvent(this.path, this.isDirectory); -} - -class ConstructableFileSystemCreateEvent extends _ConstructableFileSystemEvent - implements FileSystemCreateEvent { - @override - final type = FileSystemEvent.create; - - ConstructableFileSystemCreateEvent(String path, bool isDirectory) - : super(path, isDirectory); - - @override - String toString() => "FileSystemCreateEvent('$path')"; -} - -class ConstructableFileSystemDeleteEvent extends _ConstructableFileSystemEvent - implements FileSystemDeleteEvent { - @override - final type = FileSystemEvent.delete; - - ConstructableFileSystemDeleteEvent(String path, bool isDirectory) - : super(path, isDirectory); - - @override - String toString() => "FileSystemDeleteEvent('$path')"; -} - -class ConstructableFileSystemModifyEvent extends _ConstructableFileSystemEvent - implements FileSystemModifyEvent { - @override - final bool contentChanged; - @override - final type = FileSystemEvent.modify; - - ConstructableFileSystemModifyEvent( - String path, bool isDirectory, this.contentChanged) - : super(path, isDirectory); - - @override - String toString() => - "FileSystemModifyEvent('$path', contentChanged=$contentChanged)"; -} - -class ConstructableFileSystemMoveEvent extends _ConstructableFileSystemEvent - implements FileSystemMoveEvent { - @override - final String destination; - @override - final type = FileSystemEvent.move; - - ConstructableFileSystemMoveEvent( - String path, bool isDirectory, this.destination) - : super(path, isDirectory); - - @override - String toString() => "FileSystemMoveEvent('$path', '$destination')"; -} diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 415d17a33..d2db97524 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -7,7 +7,6 @@ import 'dart:io'; import 'package:path/path.dart' as p; -import '../constructable_file_system_event.dart'; import '../directory_watcher.dart'; import '../path_set.dart'; import '../resubscribable.dart'; @@ -285,12 +284,11 @@ class _MacOSDirectoryWatcher // [_eventsBasedOnFileSystem] will handle this correctly by producing a // DELETE event followed by a CREATE event if the directory exists. if (isDir) return null; - return ConstructableFileSystemCreateEvent(batch.first.path, false); + return FileSystemCreateEvent(batch.first.path, false); case FileSystemEvent.delete: - return ConstructableFileSystemDeleteEvent(batch.first.path, isDir); + return FileSystemDeleteEvent(batch.first.path, isDir); case FileSystemEvent.modify: - return ConstructableFileSystemModifyEvent( - batch.first.path, isDir, false); + return FileSystemModifyEvent(batch.first.path, isDir, false); default: throw StateError('unreachable'); } @@ -312,26 +310,26 @@ class _MacOSDirectoryWatcher var events = []; if (fileExisted) { if (fileExists) { - events.add(ConstructableFileSystemModifyEvent(path, false, false)); + events.add(FileSystemModifyEvent(path, false, false)); } else { - events.add(ConstructableFileSystemDeleteEvent(path, false)); + events.add(FileSystemDeleteEvent(path, false)); } } else if (dirExisted) { if (dirExists) { // If we got contradictory events for a directory that used to exist and // still exists, we need to rescan the whole thing in case it was // replaced with a different directory. - events.add(ConstructableFileSystemDeleteEvent(path, true)); - events.add(ConstructableFileSystemCreateEvent(path, true)); + events.add(FileSystemDeleteEvent(path, true)); + events.add(FileSystemCreateEvent(path, true)); } else { - events.add(ConstructableFileSystemDeleteEvent(path, true)); + events.add(FileSystemDeleteEvent(path, true)); } } if (!fileExisted && fileExists) { - events.add(ConstructableFileSystemCreateEvent(path, false)); + events.add(FileSystemCreateEvent(path, false)); } else if (!dirExisted && dirExists) { - events.add(ConstructableFileSystemCreateEvent(path, true)); + events.add(FileSystemCreateEvent(path, true)); } return events; diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 141545b79..aff7a748a 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -9,7 +9,6 @@ import 'dart:io'; import 'package:path/path.dart' as p; -import '../constructable_file_system_event.dart'; import '../directory_watcher.dart'; import '../path_set.dart'; import '../resubscribable.dart'; @@ -306,12 +305,11 @@ class _WindowsDirectoryWatcher switch (type) { case FileSystemEvent.create: - return ConstructableFileSystemCreateEvent(batch.first.path, isDir); + return FileSystemCreateEvent(batch.first.path, isDir); case FileSystemEvent.delete: - return ConstructableFileSystemDeleteEvent(batch.first.path, isDir); + return FileSystemDeleteEvent(batch.first.path, isDir); case FileSystemEvent.modify: - return ConstructableFileSystemModifyEvent( - batch.first.path, isDir, false); + return FileSystemModifyEvent(batch.first.path, isDir, false); case FileSystemEvent.move: return null; default: @@ -342,26 +340,26 @@ class _WindowsDirectoryWatcher var events = []; if (fileExisted) { if (fileExists) { - events.add(ConstructableFileSystemModifyEvent(path, false, false)); + events.add(FileSystemModifyEvent(path, false, false)); } else { - events.add(ConstructableFileSystemDeleteEvent(path, false)); + events.add(FileSystemDeleteEvent(path, false)); } } else if (dirExisted) { if (dirExists) { // If we got contradictory events for a directory that used to exist and // still exists, we need to rescan the whole thing in case it was // replaced with a different directory. - events.add(ConstructableFileSystemDeleteEvent(path, true)); - events.add(ConstructableFileSystemCreateEvent(path, true)); + events.add(FileSystemDeleteEvent(path, true)); + events.add(FileSystemCreateEvent(path, true)); } else { - events.add(ConstructableFileSystemDeleteEvent(path, true)); + events.add(FileSystemDeleteEvent(path, true)); } } if (!fileExisted && fileExists) { - events.add(ConstructableFileSystemCreateEvent(path, false)); + events.add(FileSystemCreateEvent(path, false)); } else if (!dirExisted && dirExists) { - events.add(ConstructableFileSystemCreateEvent(path, true)); + events.add(FileSystemCreateEvent(path, true)); } return events; diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index e64e79d42..5df994fa5 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,12 +1,12 @@ name: watcher -version: 1.0.3-dev +version: 1.1.0-dev description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. repository: https://github.com/dart-lang/watcher environment: - sdk: '>=2.19.0 <3.0.0' + sdk: ^3.0.0 dependencies: async: ^2.5.0 From 1cc846682e8f7b79308caedbd36f4f102388df9c Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Fri, 12 May 2023 11:28:00 -0700 Subject: [PATCH 175/201] Update to 1.1.0 in preparation for release. (dart-lang/watcher#145) --- pkgs/watcher/CHANGELOG.md | 4 ++-- pkgs/watcher/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 17b93f911..b7641e661 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,8 +1,8 @@ -## 1.1.0-dev +## 1.1.0 - Require Dart SDK >= 3.0.0 - Remove usage of redundant ConstructableFileSystemEvent classes. -- + ## 1.0.3-dev - Require Dart SDK >= 2.19 diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 5df994fa5..d86346881 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 1.1.0-dev +version: 1.1.0 description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. From 84cd19d95bc059d60e7265711b7fe086bb6dacbf Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Mon, 22 May 2023 16:22:46 -0700 Subject: [PATCH 176/201] blast_repo fixes (dart-lang/watcher#146) dependabot --- pkgs/watcher/.github/dependabot.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/.github/dependabot.yaml b/pkgs/watcher/.github/dependabot.yaml index 214481934..439e796b4 100644 --- a/pkgs/watcher/.github/dependabot.yaml +++ b/pkgs/watcher/.github/dependabot.yaml @@ -2,7 +2,9 @@ version: 2 updates: - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: github-actions + directory: / schedule: - interval: "monthly" + interval: monthly + labels: + - autosubmit From 2ee9cf52a1bd35b2f9e8ab91d8a3171bc6641e5f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:01:10 +0000 Subject: [PATCH 177/201] Bump actions/checkout from 3.5.2 to 3.5.3 (dart-lang/watcher#148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.2 to 3.5.3.
Release notes

Sourced from actions/checkout's releases.

v3.5.3

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3...v3.5.3

Changelog

Sourced from actions/checkout's changelog.

Changelog

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

v3.1.0

v3.0.2

v3.0.1

v3.0.0

v2.3.1

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3.5.2&new-version=3.5.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index bc16dd1c4..5fce4b21b 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -48,7 +48,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [3.0.0, dev] steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} From 4d48b99af6a0f01414d66fc268837dc07605dfc7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 08:26:53 +0000 Subject: [PATCH 178/201] Bump actions/checkout from 3.5.3 to 3.6.0 (dart-lang/watcher#149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.3 to 3.6.0.
Release notes

Sourced from actions/checkout's releases.

v3.6.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3.5.3...v3.6.0

Changelog

Sourced from actions/checkout's changelog.

Changelog

v3.6.0

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

v3.1.0

v3.0.2

v3.0.1

v3.0.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3.5.3&new-version=3.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 5fce4b21b..6986f9c90 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -48,7 +48,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [3.0.0, dev] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} From f5dcb04d5814f9c407c268786efb34913a3b49e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 09:02:19 +0000 Subject: [PATCH 179/201] Bump actions/checkout from 3.6.0 to 4.1.0 (dart-lang/watcher#151) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 3.6.0 to 4.1.0.
Release notes

Sourced from actions/checkout's releases.

v4.1.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.0.0...v4.1.0

v4.0.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3...v4.0.0

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.0

v4.0.0

v3.6.0

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

v3.1.0

v3.0.2

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3.6.0&new-version=4.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 6986f9c90..26f5da2b0 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -48,7 +48,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [3.0.0, dev] steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} From bffc449c5616ea088ff3df0f79b73d53de0b0b83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 07:12:29 -0700 Subject: [PATCH 180/201] Bump dart-lang/setup-dart from 1.5.0 to 1.5.1 (dart-lang/watcher#152) Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.5.0 to 1.5.1. - [Release notes](https://github.com/dart-lang/setup-dart/releases) - [Changelog](https://github.com/dart-lang/setup-dart/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/setup-dart/compare/d6a63dab3335f427404425de0fbfed4686d93c4f...8a4b97ea2017cc079571daec46542f76189836b1) --- updated-dependencies: - dependency-name: dart-lang/setup-dart dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 26f5da2b0..6f33e07c9 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f + - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} - id: install @@ -49,7 +49,7 @@ jobs: sdk: [3.0.0, dev] steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f + - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} - id: install From 1ef1d1ce7900727c9b887b11abfb5d8ee72a39cc Mon Sep 17 00:00:00 2001 From: Parker Lougheed Date: Thu, 19 Oct 2023 10:53:57 -0500 Subject: [PATCH 181/201] Migrate to dart_flutter_team_lints v2.1 (dart-lang/watcher#153) - Fix new lints. - Deduplicate lints in local config. - Add some missing generic types on futures and stream controllers. - Fix a typo in a test. - Remove unnecessary library names. --- pkgs/watcher/CHANGELOG.md | 2 ++ pkgs/watcher/analysis_options.yaml | 9 --------- pkgs/watcher/benchmark/path_set.dart | 2 +- pkgs/watcher/example/watch.dart | 2 +- pkgs/watcher/lib/src/directory_watcher/linux.dart | 6 +++--- .../watcher/lib/src/directory_watcher/mac_os.dart | 15 ++++++++------- .../lib/src/directory_watcher/polling.dart | 4 ++-- .../lib/src/directory_watcher/windows.dart | 4 ++-- pkgs/watcher/lib/src/file_watcher/native.dart | 6 +++--- pkgs/watcher/lib/src/file_watcher/polling.dart | 8 ++++---- pkgs/watcher/lib/src/resubscribable.dart | 2 +- pkgs/watcher/pubspec.yaml | 4 ++-- .../test/directory_watcher/linux_test.dart | 2 +- .../test/directory_watcher/mac_os_test.dart | 2 +- .../test/directory_watcher/polling_test.dart | 4 ++-- pkgs/watcher/test/directory_watcher/shared.dart | 4 ++-- .../test/directory_watcher/windows_test.dart | 2 +- pkgs/watcher/test/file_watcher/polling_test.dart | 2 +- pkgs/watcher/test/file_watcher/shared.dart | 2 +- pkgs/watcher/test/ready/shared.dart | 4 ++-- pkgs/watcher/test/utils.dart | 2 +- 21 files changed, 41 insertions(+), 47 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index b7641e661..7743fc7a3 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.1.1-wip + ## 1.1.0 - Require Dart SDK >= 3.0.0 diff --git a/pkgs/watcher/analysis_options.yaml b/pkgs/watcher/analysis_options.yaml index a1fc20f55..d978f811c 100644 --- a/pkgs/watcher/analysis_options.yaml +++ b/pkgs/watcher/analysis_options.yaml @@ -1,10 +1 @@ include: package:dart_flutter_team_lints/analysis_options.yaml - -analyzer: - language: - strict-casts: true - -linter: - rules: - - comment_references # https://github.com/dart-lang/sdk/issues/39467 - diff --git a/pkgs/watcher/benchmark/path_set.dart b/pkgs/watcher/benchmark/path_set.dart index 82109b0ab..e7929d8ec 100644 --- a/pkgs/watcher/benchmark/path_set.dart +++ b/pkgs/watcher/benchmark/path_set.dart @@ -3,7 +3,7 @@ // BSD-style license that can be found in the LICENSE file. /// Benchmarks for the PathSet class. -library watcher.benchmark.path_set; +library; import 'dart:io'; import 'dart:math' as math; diff --git a/pkgs/watcher/example/watch.dart b/pkgs/watcher/example/watch.dart index 0a0b3577a..37931d396 100644 --- a/pkgs/watcher/example/watch.dart +++ b/pkgs/watcher/example/watch.dart @@ -3,7 +3,7 @@ // BSD-style license that can be found in the LICENSE file. /// Watches the given directory and prints each modification to it. -library watch; +library; import 'package:path/path.dart' as p; import 'package:watcher/watcher.dart'; diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index dafaeb19a..cb1d07781 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -47,8 +47,8 @@ class _LinuxDirectoryWatcher bool get isReady => _readyCompleter.isCompleted; @override - Future get ready => _readyCompleter.future; - final _readyCompleter = Completer(); + Future get ready => _readyCompleter.future; + final _readyCompleter = Completer(); /// A stream group for the [Directory.watch] events of [path] and all its /// subdirectories. @@ -284,7 +284,7 @@ class _LinuxDirectoryWatcher {Function? onError, void Function()? onDone, bool cancelOnError = false}) { - late StreamSubscription subscription; + late StreamSubscription subscription; subscription = stream.listen(onData, onError: onError, onDone: () { _subscriptions.remove(subscription); onDone?.call(); diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index d2db97524..b46138347 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -47,8 +47,8 @@ class _MacOSDirectoryWatcher bool get isReady => _readyCompleter.isCompleted; @override - Future get ready => _readyCompleter.future; - final _readyCompleter = Completer(); + Future get ready => _readyCompleter.future; + final _readyCompleter = Completer(); /// The set of files that are known to exist recursively within the watched /// directory. @@ -367,12 +367,12 @@ class _MacOSDirectoryWatcher /// Starts or restarts listing the watched directory to get an initial picture /// of its state. - Future _listDir() { + Future _listDir() { assert(!isReady); _initialListSubscription?.cancel(); _files.clear(); - var completer = Completer(); + var completer = Completer(); var stream = Directory(path).list(recursive: true); _initialListSubscription = stream.listen((entity) { if (entity is! Directory) _files.add(entity.path); @@ -385,9 +385,10 @@ class _MacOSDirectoryWatcher /// 200ms is short in terms of human interaction, but longer than any Mac OS /// watcher tests take on the bots, so it should be safe to assume that any /// bogus events will be signaled in that time frame. - Future _waitForBogusEvents() { - var completer = Completer(); - _bogusEventTimer = Timer(Duration(milliseconds: 200), completer.complete); + Future _waitForBogusEvents() { + var completer = Completer(); + _bogusEventTimer = + Timer(const Duration(milliseconds: 200), completer.complete); return completer.future; } diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 3dcec05a3..207679b1a 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -27,7 +27,7 @@ class PollingDirectoryWatcher extends ResubscribableWatcher PollingDirectoryWatcher(String directory, {Duration? pollingDelay}) : super(directory, () { return _PollingDirectoryWatcher( - directory, pollingDelay ?? Duration(seconds: 1)); + directory, pollingDelay ?? const Duration(seconds: 1)); }); } @@ -184,7 +184,7 @@ class _PollingDirectoryWatcher if (!isReady) _readyCompleter.complete(); // Wait and then poll again. - await Future.delayed(_pollingDelay); + await Future.delayed(_pollingDelay); if (_events.isClosed) return; _poll(); } diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index aff7a748a..d1c98be1f 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -56,7 +56,7 @@ class _WindowsDirectoryWatcher @override Future get ready => _readyCompleter.future; - final _readyCompleter = Completer(); + final _readyCompleter = Completer(); final Map _eventBatchers = HashMap(); @@ -407,7 +407,7 @@ class _WindowsDirectoryWatcher _initialListSubscription?.cancel(); _files.clear(); - var completer = Completer(); + var completer = Completer(); var stream = Directory(path).list(recursive: true); void handleEntity(FileSystemEntity entity) { if (entity is! Directory) _files.add(entity.path); diff --git a/pkgs/watcher/lib/src/file_watcher/native.dart b/pkgs/watcher/lib/src/file_watcher/native.dart index 48f12e672..502aa1095 100644 --- a/pkgs/watcher/lib/src/file_watcher/native.dart +++ b/pkgs/watcher/lib/src/file_watcher/native.dart @@ -30,10 +30,10 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher { bool get isReady => _readyCompleter.isCompleted; @override - Future get ready => _readyCompleter.future; - final _readyCompleter = Completer(); + Future get ready => _readyCompleter.future; + final _readyCompleter = Completer(); - StreamSubscription? _subscription; + StreamSubscription>? _subscription; _NativeFileWatcher(this.path) { _listen(); diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart index d0b219f36..6f1eee4fd 100644 --- a/pkgs/watcher/lib/src/file_watcher/polling.dart +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -15,7 +15,7 @@ class PollingFileWatcher extends ResubscribableWatcher implements FileWatcher { PollingFileWatcher(String path, {Duration? pollingDelay}) : super(path, () { return _PollingFileWatcher( - path, pollingDelay ?? Duration(seconds: 1)); + path, pollingDelay ?? const Duration(seconds: 1)); }); } @@ -31,8 +31,8 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { bool get isReady => _readyCompleter.isCompleted; @override - Future get ready => _readyCompleter.future; - final _readyCompleter = Completer(); + Future get ready => _readyCompleter.future; + final _readyCompleter = Completer(); /// The timer that controls polling. late final Timer _timer; @@ -49,7 +49,7 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { } /// Checks the mtime of the file and whether it's been removed. - Future _poll() async { + Future _poll() async { // We don't mark the file as removed if this is the first poll (indicated by // [_lastModified] being null). Instead, below we forward the dart:io error // that comes from trying to read the mtime below. diff --git a/pkgs/watcher/lib/src/resubscribable.dart b/pkgs/watcher/lib/src/resubscribable.dart index 91f509079..b99e9d7b4 100644 --- a/pkgs/watcher/lib/src/resubscribable.dart +++ b/pkgs/watcher/lib/src/resubscribable.dart @@ -41,7 +41,7 @@ abstract class ResubscribableWatcher implements Watcher { /// emitted by [_factory]. ResubscribableWatcher(this.path, this._factory) { late ManuallyClosedWatcher watcher; - late StreamSubscription subscription; + late StreamSubscription subscription; _eventsController = StreamController.broadcast( onListen: () async { diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index d86346881..431e99b13 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 1.1.0 +version: 1.1.1-wip description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. @@ -14,7 +14,7 @@ dependencies: dev_dependencies: benchmark_harness: ^2.0.0 - dart_flutter_team_lints: ^1.0.0 + dart_flutter_team_lints: ^2.1.0 test: ^1.16.0 test_descriptor: ^2.0.0 diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart index 7b638a860..a10a72c33 100644 --- a/pkgs/watcher/test/directory_watcher/linux_test.dart +++ b/pkgs/watcher/test/directory_watcher/linux_test.dart @@ -18,7 +18,7 @@ void main() { sharedTests(); test('DirectoryWatcher creates a LinuxDirectoryWatcher on Linux', () { - expect(DirectoryWatcher('.'), TypeMatcher()); + expect(DirectoryWatcher('.'), const TypeMatcher()); }); test('emits events for many nested files moved out then immediately back in', diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart index 347c5e731..337662646 100644 --- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart +++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart @@ -18,7 +18,7 @@ void main() { sharedTests(); test('DirectoryWatcher creates a MacOSDirectoryWatcher on Mac OS', () { - expect(DirectoryWatcher('.'), TypeMatcher()); + expect(DirectoryWatcher('.'), const TypeMatcher()); }); test( diff --git a/pkgs/watcher/test/directory_watcher/polling_test.dart b/pkgs/watcher/test/directory_watcher/polling_test.dart index 0cfe738af..f4ec8f48a 100644 --- a/pkgs/watcher/test/directory_watcher/polling_test.dart +++ b/pkgs/watcher/test/directory_watcher/polling_test.dart @@ -10,8 +10,8 @@ import 'shared.dart'; void main() { // Use a short delay to make the tests run quickly. - watcherFactory = (dir) => - PollingDirectoryWatcher(dir, pollingDelay: Duration(milliseconds: 100)); + watcherFactory = (dir) => PollingDirectoryWatcher(dir, + pollingDelay: const Duration(milliseconds: 100)); sharedTests(); diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index 8fd842a8e..1ebc78d4b 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -135,7 +135,7 @@ void sharedTests() { renameFile('from.txt', 'to.txt'); await inAnyOrder([isRemoveEvent('from.txt'), isModifyEvent('to.txt')]); }, onPlatform: { - 'windows': Skip('https://github.com/dart-lang/watcher/issues/125') + 'windows': const Skip('https://github.com/dart-lang/watcher/issues/125') }); }); @@ -278,7 +278,7 @@ void sharedTests() { isAddEvent('new') ]); }, onPlatform: { - 'windows': Skip('https://github.com/dart-lang/watcher/issues/21') + 'windows': const Skip('https://github.com/dart-lang/watcher/issues/21') }); test('emits events for many nested files added at once', () async { diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart index a0bdda7f4..499e7fb16 100644 --- a/pkgs/watcher/test/directory_watcher/windows_test.dart +++ b/pkgs/watcher/test/directory_watcher/windows_test.dart @@ -18,6 +18,6 @@ void main() { group('Shared Tests:', sharedTests); test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () { - expect(DirectoryWatcher('.'), TypeMatcher()); + expect(DirectoryWatcher('.'), const TypeMatcher()); }); } diff --git a/pkgs/watcher/test/file_watcher/polling_test.dart b/pkgs/watcher/test/file_watcher/polling_test.dart index 1bf9269fe..6f2bfbcab 100644 --- a/pkgs/watcher/test/file_watcher/polling_test.dart +++ b/pkgs/watcher/test/file_watcher/polling_test.dart @@ -13,7 +13,7 @@ import 'shared.dart'; void main() { watcherFactory = (file) => - PollingFileWatcher(file, pollingDelay: Duration(milliseconds: 100)); + PollingFileWatcher(file, pollingDelay: const Duration(milliseconds: 100)); setUp(() { writeFile('file.txt'); diff --git a/pkgs/watcher/test/file_watcher/shared.dart b/pkgs/watcher/test/file_watcher/shared.dart index 9b72f0ed6..1e1718f45 100644 --- a/pkgs/watcher/test/file_watcher/shared.dart +++ b/pkgs/watcher/test/file_watcher/shared.dart @@ -62,7 +62,7 @@ void sharedTests() { var sub = watcher.events.listen(null); deleteFile('file.txt'); - await Future.delayed(Duration(milliseconds: 10)); + await Future.delayed(const Duration(milliseconds: 10)); await sub.cancel(); }); } diff --git a/pkgs/watcher/test/ready/shared.dart b/pkgs/watcher/test/ready/shared.dart index 1b4dbaa0b..c1b47ade0 100644 --- a/pkgs/watcher/test/ready/shared.dart +++ b/pkgs/watcher/test/ready/shared.dart @@ -43,8 +43,8 @@ void sharedTests() { // Ensure ready completes immediately expect( watcher.ready.timeout( - Duration(milliseconds: 0), - onTimeout: () => throw StateError('Does not complete immedately'), + const Duration(milliseconds: 0), + onTimeout: () => throw StateError('Does not complete immediately'), ), completes, ); diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 23adcbc40..214d66966 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -119,7 +119,7 @@ StreamMatcher _collectStreamMatcher(void Function() block) { /// it with [_collectStreamMatcher]. /// /// [streamMatcher] can be a [StreamMatcher], a [Matcher], or a value. -Future _expectOrCollect(streamMatcher) { +Future _expectOrCollect(Matcher streamMatcher) { var collectedStreamMatchers = _collectedStreamMatchers; if (collectedStreamMatchers != null) { collectedStreamMatchers.add(emits(streamMatcher)); From 432ff8e5b05b1991a0fcfedde9fe30f8c8dd00b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 08:48:01 +0000 Subject: [PATCH 182/201] Bump actions/checkout from 4.1.0 to 4.1.1 (dart-lang/watcher#155) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.0 to 4.1.1.
Release notes

Sourced from actions/checkout's releases.

v4.1.1

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.1.0...v4.1.1

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.0&new-version=4.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 6f33e07c9..f1dc5813e 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} @@ -48,7 +48,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [3.0.0, dev] steps: - - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} From 26eae9dbbbfc7f6b1322ee65b7a65f091017da84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 14:58:01 +0000 Subject: [PATCH 183/201] Bump dart-lang/setup-dart from 1.5.1 to 1.6.0 (dart-lang/watcher#154) Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.5.1 to 1.6.0.
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).
Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

  • Automatically create OIDC token for pub.dev.
  • Add a reusable workflow for publishing.

v1.3.0

  • The install location of the Dart SDK is now available in an environment variable, DART_HOME (dart-lang/watcher#43).
  • Fixed an issue where cached downloads could lead to unzip issues on self-hosted runners (dart-lang/watcher#35).

v1.2.0

  • Fixed a path issue impacting git dependencies on Windows.

v1.1.0

  • Added a flavor option setup.sh to allow downloading unpublished builds.

v1.0.0

  • Promoted to 1.0 stable.

v0.5

  • Fixed a Windows pub global activate path issue.

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dart-lang/setup-dart&package-manager=github_actions&previous-version=1.5.1&new-version=1.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index f1dc5813e..d72e17b92 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 + - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: sdk: ${{ matrix.sdk }} - id: install @@ -49,7 +49,7 @@ jobs: sdk: [3.0.0, dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 + - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: sdk: ${{ matrix.sdk }} - id: install From e826e96aa8e1dba8191bc703ff1db1ca8591f350 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Wed, 22 Nov 2023 16:50:36 +0000 Subject: [PATCH 184/201] Enable file watcher tests on Windows (dart-lang/watcher#156) * Enable file watcher tests on Windows * Add additional test --- pkgs/watcher/test/file_watcher/polling_test.dart | 3 --- pkgs/watcher/test/file_watcher/shared.dart | 5 +++++ pkgs/watcher/test/ready/shared.dart | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkgs/watcher/test/file_watcher/polling_test.dart b/pkgs/watcher/test/file_watcher/polling_test.dart index 6f2bfbcab..861fcb222 100644 --- a/pkgs/watcher/test/file_watcher/polling_test.dart +++ b/pkgs/watcher/test/file_watcher/polling_test.dart @@ -2,9 +2,6 @@ // 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. -@TestOn('linux || mac-os') -library; - import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; diff --git a/pkgs/watcher/test/file_watcher/shared.dart b/pkgs/watcher/test/file_watcher/shared.dart index 1e1718f45..081b92e11 100644 --- a/pkgs/watcher/test/file_watcher/shared.dart +++ b/pkgs/watcher/test/file_watcher/shared.dart @@ -65,4 +65,9 @@ void sharedTests() { await Future.delayed(const Duration(milliseconds: 10)); await sub.cancel(); }); + + test('ready completes even if file does not exist', () async { + // startWatcher awaits 'ready' + await startWatcher(path: 'foo/bar/baz'); + }); } diff --git a/pkgs/watcher/test/ready/shared.dart b/pkgs/watcher/test/ready/shared.dart index c1b47ade0..ab2c3e162 100644 --- a/pkgs/watcher/test/ready/shared.dart +++ b/pkgs/watcher/test/ready/shared.dart @@ -69,7 +69,7 @@ void sharedTests() { expect(watcher.ready, doesNotComplete); }); - test('completes even if directory does not exist', () async { + test('ready completes even if directory does not exist', () async { var watcher = createWatcher(path: 'does/not/exist'); // Subscribe to the events (else ready will never fire). From bcb8977e840566a37fa4aba96755190e2ce60b5c Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Fri, 1 Dec 2023 16:48:31 +0000 Subject: [PATCH 185/201] Fix PollingFileWatcher.ready for files that don't exist (dart-lang/watcher#157) There were a few issues here: - FileWatcher.ready never fired for files that don't exist because of logic inside FileWatcher existing early if the modification time was `null` - The test I recently added trying to catch this was incorrectly passing because the mock timestamp code was set so that files that had not been created would return a 0-mtime whereas in the real implementation they return `null` So this change a) updates the mock to return `null` for uncreated files (to match the real implementation) which breaks a bunch of tests b) fixes those tests by updating FileWatcher._poll() to handle `null` mtime separately from being the first poll. --- pkgs/watcher/CHANGELOG.md | 2 ++ .../watcher/lib/src/file_watcher/polling.dart | 36 ++++++++++++------- pkgs/watcher/lib/src/stat.dart | 2 +- pkgs/watcher/test/utils.dart | 22 ++++++++++-- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 7743fc7a3..7893dafa8 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,5 +1,7 @@ ## 1.1.1-wip +- Ensure `PollingFileWatcher.ready` completes for files that do not exist. + ## 1.1.0 - Require Dart SDK >= 3.0.0 diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart index 6f1eee4fd..15ff9ab8e 100644 --- a/pkgs/watcher/lib/src/file_watcher/polling.dart +++ b/pkgs/watcher/lib/src/file_watcher/polling.dart @@ -39,8 +39,7 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { /// The previous modification time of the file. /// - /// Used to tell when the file was modified. This is `null` before the file's - /// mtime has first been checked. + /// `null` indicates the file does not (or did not on the last poll) exist. DateTime? _lastModified; _PollingFileWatcher(this.path, Duration pollingDelay) { @@ -50,13 +49,14 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { /// Checks the mtime of the file and whether it's been removed. Future _poll() async { - // We don't mark the file as removed if this is the first poll (indicated by - // [_lastModified] being null). Instead, below we forward the dart:io error - // that comes from trying to read the mtime below. + // We don't mark the file as removed if this is the first poll. Instead, + // below we forward the dart:io error that comes from trying to read the + // mtime below. var pathExists = await File(path).exists(); if (_eventsController.isClosed) return; if (_lastModified != null && !pathExists) { + _flagReady(); _eventsController.add(WatchEvent(ChangeType.REMOVE, path)); unawaited(close()); return; @@ -67,22 +67,34 @@ class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher { modified = await modificationTime(path); } on FileSystemException catch (error, stackTrace) { if (!_eventsController.isClosed) { + _flagReady(); _eventsController.addError(error, stackTrace); await close(); } } - if (_eventsController.isClosed) return; - - if (_lastModified == modified) return; + if (_eventsController.isClosed) { + _flagReady(); + return; + } - if (_lastModified == null) { + if (!isReady) { // If this is the first poll, don't emit an event, just set the last mtime // and complete the completer. _lastModified = modified; + _flagReady(); + return; + } + + if (_lastModified == modified) return; + + _lastModified = modified; + _eventsController.add(WatchEvent(ChangeType.MODIFY, path)); + } + + /// Flags this watcher as ready if it has not already been done. + void _flagReady() { + if (!isReady) { _readyCompleter.complete(); - } else { - _lastModified = modified; - _eventsController.add(WatchEvent(ChangeType.MODIFY, path)); } } diff --git a/pkgs/watcher/lib/src/stat.dart b/pkgs/watcher/lib/src/stat.dart index 06e3febf4..fe0f15578 100644 --- a/pkgs/watcher/lib/src/stat.dart +++ b/pkgs/watcher/lib/src/stat.dart @@ -6,7 +6,7 @@ import 'dart:io'; /// A function that takes a file path and returns the last modified time for /// the file at that path. -typedef MockTimeCallback = DateTime Function(String path); +typedef MockTimeCallback = DateTime? Function(String path); MockTimeCallback? _mockTimeCallback; diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 214d66966..7867b9fc2 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -67,7 +67,7 @@ Future startWatcher({String? path}) async { 'Path is not in the sandbox: $path not in ${d.sandbox}'); var mtime = _mockFileModificationTimes[normalized]; - return DateTime.fromMillisecondsSinceEpoch(mtime ?? 0); + return mtime != null ? DateTime.fromMillisecondsSinceEpoch(mtime) : null; }); // We want to wait until we're ready *after* we subscribe to the watcher's @@ -195,6 +195,11 @@ Future expectRemoveEvent(String path) => Future allowModifyEvent(String path) => _expectOrCollect(mayEmit(isWatchEvent(ChangeType.MODIFY, path))); +/// Track a fake timestamp to be used when writing files. This always increases +/// so that files that are deleted and re-created do not have their timestamp +/// set back to a previously used value. +int _nextTimestamp = 1; + /// Schedules writing a file in the sandbox at [path] with [contents]. /// /// If [contents] is omitted, creates an empty file. If [updateModified] is @@ -216,14 +221,15 @@ void writeFile(String path, {String? contents, bool? updateModified}) { if (updateModified) { path = p.normalize(path); - _mockFileModificationTimes.update(path, (value) => value + 1, - ifAbsent: () => 1); + _mockFileModificationTimes[path] = _nextTimestamp++; } } /// Schedules deleting a file in the sandbox at [path]. void deleteFile(String path) { File(p.join(d.sandbox, path)).deleteSync(); + + _mockFileModificationTimes.remove(path); } /// Schedules renaming a file in the sandbox from [from] to [to]. @@ -245,6 +251,16 @@ void createDir(String path) { /// Schedules renaming a directory in the sandbox from [from] to [to]. void renameDir(String from, String to) { Directory(p.join(d.sandbox, from)).renameSync(p.join(d.sandbox, to)); + + // Migrate timestamps for any files in this folder. + final knownFilePaths = _mockFileModificationTimes.keys.toList(); + for (final filePath in knownFilePaths) { + if (p.isWithin(from, filePath)) { + _mockFileModificationTimes[filePath.replaceAll(from, to)] = + _mockFileModificationTimes[filePath]!; + _mockFileModificationTimes.remove(filePath); + } + } } /// Schedules deleting a directory in the sandbox at [path]. From 0dc94f552da3f473aee3fac93b4943abc6c2145c Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 20 Dec 2023 10:00:52 -0800 Subject: [PATCH 186/201] blast_repo fixes (dart-lang/watcher#160) no-response --- .../watcher/.github/workflows/no-response.yml | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/pkgs/watcher/.github/workflows/no-response.yml b/pkgs/watcher/.github/workflows/no-response.yml index ac3e456ec..8e5ed57cd 100644 --- a/pkgs/watcher/.github/workflows/no-response.yml +++ b/pkgs/watcher/.github/workflows/no-response.yml @@ -1,12 +1,10 @@ # A workflow to close issues where the author hasn't responded to a request for -# more information; see https://github.com/godofredoc/no-response for docs. +# more information; see https://github.com/actions/stale. name: No Response -# Both `issue_comment` and `scheduled` event types are required. +# Run as a daily cron. on: - issue_comment: - types: [created] schedule: # Every day at 8am - cron: '0 8 * * *' @@ -14,21 +12,26 @@ on: # All permissions not specified are set to 'none'. permissions: issues: write + pull-requests: write jobs: - noResponse: + no-response: runs-on: ubuntu-latest if: ${{ github.repository_owner == 'dart-lang' }} steps: - - uses: godofredoc/no-response@0ce2dc0e63e1c7d2b87752ceed091f6d32c9df09 + - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 with: - responseRequiredLabel: "needs-info" - responseRequiredColor: 4774bc - daysUntilClose: 14 - # Comment to post when closing an Issue for lack of response. - closeComment: > - Without additional information we're not able to resolve this issue, - so it will be closed at this time. You're still free to add more - info and respond to any questions above, though. We'll reopen the - issue if you do. Thanks for your contribution! - token: ${{ github.token }} + # Don't automatically mark inactive issues+PRs as stale. + days-before-stale: -1 + # Close needs-info issues and PRs after 14 days of inactivity. + days-before-close: 14 + stale-issue-label: "needs-info" + close-issue-message: > + Without additional information we're not able to resolve this issue. + Feel free to add more info or respond to any questions above and we + can reopen the case. Thanks for your contribution! + stale-pr-label: "needs-info" + close-pr-message: > + Without additional information we're not able to resolve this PR. + Feel free to add more info or respond to any questions above. + Thanks for your contribution! From 62c7735ea1f3dd7518fa4a7cf2b823dd3b193108 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 08:49:33 +0000 Subject: [PATCH 187/201] Bump actions/stale from 8.0.0 to 9.0.0 (dart-lang/watcher#161) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/stale](https://github.com/actions/stale) from 8.0.0 to 9.0.0.
Release notes

Sourced from actions/stale's releases.

v9.0.0

Breaking Changes

  1. Action is now stateful: If the action ends because of operations-per-run then the next run will start from the first unprocessed issue skipping the issues processed during the previous run(s). The state is reset when all the issues are processed. This should be considered for scheduling workflow runs.
  2. Version 9 of this action updated the runtime to Node.js 20. All scripts are now run with Node.js 20 instead of Node.js 16 and are affected by any breaking changes between Node.js 16 and 20.

What Else Changed

  1. Performance optimization that removes unnecessary API calls by @​dsame dart-lang/watcher#1033 fixes dart-lang/watcher#792
  2. Logs displaying current github API rate limit by @​dsame dart-lang/watcher#1032 addresses dart-lang/watcher#1029

For more information, please read the action documentation and its section about statefulness

New Contributors

Full Changelog: https://github.com/actions/stale/compare/v8...v9.0.0

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/stale&package-manager=github_actions&previous-version=8.0.0&new-version=9.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/watcher/.github/workflows/no-response.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/.github/workflows/no-response.yml b/pkgs/watcher/.github/workflows/no-response.yml index 8e5ed57cd..ab1ac4984 100644 --- a/pkgs/watcher/.github/workflows/no-response.yml +++ b/pkgs/watcher/.github/workflows/no-response.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.repository_owner == 'dart-lang' }} steps: - - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 + - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e with: # Don't automatically mark inactive issues+PRs as stale. days-before-stale: -1 From 13a1499706d215f1404f03ab93645b94c9c0aa2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 08:17:32 +0000 Subject: [PATCH 188/201] Bump dart-lang/setup-dart from 1.6.0 to 1.6.2 (dart-lang/watcher#162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.6.0 to 1.6.2.
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.6.2

v1.6.1

  • Updated the google storage url for main channel releases.
Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.2

v1.6.1

  • Updated the google storage url for main channel releases.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

  • Automatically create OIDC token for pub.dev.
  • Add a reusable workflow for publishing.

v1.3.0

  • The install location of the Dart SDK is now available in an environment variable, DART_HOME (dart-lang/watcher#43).
  • Fixed an issue where cached downloads could lead to unzip issues on self-hosted runners (dart-lang/watcher#35).

v1.2.0

  • Fixed a path issue impacting git dependencies on Windows.

v1.1.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dart-lang/setup-dart&package-manager=github_actions&previous-version=1.6.0&new-version=1.6.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index d72e17b92..771d535ae 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d + - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: ${{ matrix.sdk }} - id: install @@ -49,7 +49,7 @@ jobs: sdk: [3.0.0, dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d + - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: ${{ matrix.sdk }} - id: install From 19f39bef6b52c26e087a934698ef1a8f0c3bf809 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 08:30:19 +0000 Subject: [PATCH 189/201] Bump actions/checkout from 4.1.1 to 4.1.2 (dart-lang/watcher#163) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.2.
Release notes

Sourced from actions/checkout's releases.

v4.1.2

We are investigating the following issue with this release and have rolled-back the v4 tag to point to v4.1.1

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.1.1...v4.1.2

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.2

v4.1.1

v4.1.0

v4.0.0

v3.6.0

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.1&new-version=4.1.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 771d535ae..ee023e050 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: ${{ matrix.sdk }} @@ -48,7 +48,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [3.0.0, dev] steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: ${{ matrix.sdk }} From 14d8e8ec9db0860169f935f3f049d0b8d9b14a0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 08:08:52 +0000 Subject: [PATCH 190/201] Bump dart-lang/setup-dart from 1.6.2 to 1.6.4 (dart-lang/watcher#164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.6.2 to 1.6.4.
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.6.4

  • Rebuild JS code to include changes from v1.6.3

v1.6.3

Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.4

  • Rebuild JS code.

v1.6.3

v1.6.2

v1.6.1

  • Updated the google storage url for main channel releases.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

  • Automatically create OIDC token for pub.dev.
  • Add a reusable workflow for publishing.

v1.3.0

  • The install location of the Dart SDK is now available

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dart-lang/setup-dart&package-manager=github_actions&previous-version=1.6.2&new-version=1.6.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index ee023e050..94a146dd0 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 + - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} - id: install @@ -49,7 +49,7 @@ jobs: sdk: [3.0.0, dev] steps: - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 + - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} - id: install From 8a7cdbd56e2dd4d56491aaf5a9149cca894576d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 15:17:52 +0000 Subject: [PATCH 191/201] Bump actions/checkout from 4.1.2 to 4.1.4 (dart-lang/watcher#165) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.2 to 4.1.4.
Release notes

Sourced from actions/checkout's releases.

v4.1.4

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v4.1.3...v4.1.4

v4.1.3

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v4.1.2...v4.1.3

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.4

v4.1.3

v4.1.2

v4.1.1

v4.1.0

v4.0.0

v3.6.0

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.2&new-version=4.1.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 94a146dd0..7aab282af 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} @@ -48,7 +48,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [3.0.0, dev] steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} From 1f7531bda2e58c53bb4049d13ed2b1bcab4e1964 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Mon, 13 May 2024 10:35:39 -0700 Subject: [PATCH 192/201] blast_repo fixes (dart-lang/watcher#166) dependabot --- pkgs/watcher/.github/dependabot.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/watcher/.github/dependabot.yaml b/pkgs/watcher/.github/dependabot.yaml index 439e796b4..bf6b38a4d 100644 --- a/pkgs/watcher/.github/dependabot.yaml +++ b/pkgs/watcher/.github/dependabot.yaml @@ -8,3 +8,7 @@ updates: interval: monthly labels: - autosubmit + groups: + github-actions: + patterns: + - "*" From ad36f560ee27fd30f645af4ef0bb426217d3c674 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 17:38:27 +0000 Subject: [PATCH 193/201] Bump actions/checkout from 4.1.4 to 4.1.5 in the github-actions group (dart-lang/watcher#167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 4.1.4 to 4.1.5
Release notes

Sourced from actions/checkout's releases.

v4.1.5

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v4.1.4...v4.1.5

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.4&new-version=4.1.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 7aab282af..eb75dcaa6 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} @@ -48,7 +48,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [3.0.0, dev] steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} From 26d680b0e55eb18f58c781f373b95e7d0b0b0eb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 08:47:20 +0000 Subject: [PATCH 194/201] Bump actions/checkout from 4.1.5 to 4.1.6 in the github-actions group (dart-lang/watcher#168) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 4.1.5 to 4.1.6
Release notes

Sourced from actions/checkout's releases.

v4.1.6

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v4.1.5...v4.1.6

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.6

v4.1.5

v4.1.4

v4.1.3

v4.1.2

v4.1.1

v4.1.0

v4.0.0

v3.6.0

v3.5.3

v3.5.2

v3.5.1

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.5&new-version=4.1.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index eb75dcaa6..a00813b1b 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} @@ -48,7 +48,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [3.0.0, dev] steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} From 63af0323a62b9b54e167c91fe90cd2f8f161a99c Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 24 Jun 2024 18:38:31 -0700 Subject: [PATCH 195/201] update lints (dart-lang/watcher#169) --- pkgs/watcher/.github/workflows/test-package.yml | 2 +- pkgs/watcher/CHANGELOG.md | 1 + pkgs/watcher/pubspec.yaml | 7 +++---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index a00813b1b..4d2eef4c2 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -46,7 +46,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - sdk: [3.0.0, dev] + sdk: [3.1, dev] steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 7893dafa8..8f4d289d3 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,6 +1,7 @@ ## 1.1.1-wip - Ensure `PollingFileWatcher.ready` completes for files that do not exist. +- Require Dart SDK `^3.1.0` ## 1.1.0 diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 431e99b13..108be66df 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -6,7 +6,7 @@ description: >- repository: https://github.com/dart-lang/watcher environment: - sdk: ^3.0.0 + sdk: ^3.1.0 dependencies: async: ^2.5.0 @@ -14,7 +14,6 @@ dependencies: dev_dependencies: benchmark_harness: ^2.0.0 - dart_flutter_team_lints: ^2.1.0 - test: ^1.16.0 + dart_flutter_team_lints: ^3.0.0 + test: ^1.16.6 test_descriptor: ^2.0.0 - From d494a03f8806447c33e234b6a6efb770ad02bf67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 08:14:05 +0000 Subject: [PATCH 196/201] Bump the github-actions group with 2 updates (dart-lang/watcher#170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart). Updates `actions/checkout` from 4.1.6 to 4.1.7
Release notes

Sourced from actions/checkout's releases.

v4.1.7

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.1.6...v4.1.7

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.7

v4.1.6

v4.1.5

v4.1.4

v4.1.3

v4.1.2

v4.1.1

v4.1.0

v4.0.0

v3.6.0

v3.5.3

... (truncated)

Commits

Updates `dart-lang/setup-dart` from 1.6.4 to 1.6.5
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.6.5

dart-lang/watcher#118: dart-lang/setup-dartdart-lang/watcher#118

Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.5

dart-lang/watcher#118: dart-lang/setup-dartdart-lang/watcher#118

v1.6.4

  • Rebuild JS code.

v1.6.3

v1.6.2

v1.6.1

  • Updated the google storage url for main channel releases.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

... (truncated)

Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- pkgs/watcher/.github/workflows/test-package.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 4d2eef4c2..37e75dbc8 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,8 +22,8 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: ${{ matrix.sdk }} - id: install @@ -48,8 +48,8 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [3.1, dev] steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: ${{ matrix.sdk }} - id: install From d27c72f64c137209643458ec6cc5a41f4330c800 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 08:11:01 +0000 Subject: [PATCH 197/201] Bump actions/checkout from 4.1.7 to 4.2.0 in the github-actions group (dart-lang/watcher#171) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 4.1.7 to 4.2.0
Release notes

Sourced from actions/checkout's releases.

v4.2.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.1.7...v4.2.0

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.2.0

v4.1.7

v4.1.6

v4.1.5

v4.1.4

v4.1.3

v4.1.2

v4.1.1

v4.1.0

v4.0.0

v3.6.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.7&new-version=4.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 37e75dbc8..de0b6e9b0 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: ${{ matrix.sdk }} @@ -48,7 +48,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [3.1, dev] steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: ${{ matrix.sdk }} From 94fff3004900737da61f91831462a69e0f06a63d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 08:07:19 +0000 Subject: [PATCH 198/201] Bump actions/checkout from 4.2.0 to 4.2.2 in the github-actions group (dart-lang/watcher#172) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 4.2.0 to 4.2.2
Release notes

Sourced from actions/checkout's releases.

v4.2.2

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v4.2.1...v4.2.2

v4.2.1

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.2.0...v4.2.1

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.2.2

v4.2.1

v4.2.0

v4.1.7

v4.1.6

v4.1.5

v4.1.4

v4.1.3

v4.1.2

v4.1.1

v4.1.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.2.0&new-version=4.2.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index de0b6e9b0..13d140e76 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [dev] steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: ${{ matrix.sdk }} @@ -48,7 +48,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] sdk: [3.1, dev] steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: ${{ matrix.sdk }} From 0086feae01eacc2745287971fa93bb1f60115e0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Dec 2024 08:17:18 +0000 Subject: [PATCH 199/201] Bump dart-lang/setup-dart in the github-actions group (dart-lang/watcher#173) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 1 update: [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart). Updates `dart-lang/setup-dart` from 1.6.5 to 1.7.0
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.7.0

What's Changed

  • Install a Flutter SDK in the publish workflow allowing for publication of flutter packages.
Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.7.0

v1.6.5

dart-lang/watcher#118: dart-lang/setup-dartdart-lang/watcher#118

v1.6.4

  • Rebuild JS code.

v1.6.3

v1.6.2

v1.6.1

  • Updated the google storage url for main channel releases.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dart-lang/setup-dart&package-manager=github_actions&previous-version=1.6.5&new-version=1.7.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- pkgs/watcher/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/pkgs/watcher/.github/workflows/test-package.yml index 13d140e76..b501c983a 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/pkgs/watcher/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [dev] steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 with: sdk: ${{ matrix.sdk }} - id: install @@ -49,7 +49,7 @@ jobs: sdk: [3.1, dev] steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 with: sdk: ${{ matrix.sdk }} - id: install From 8a6a8185dcc9b4c01030d6189f8eab977f4af274 Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 11 Dec 2024 14:45:37 +0100 Subject: [PATCH 200/201] Add issue template and other fixes --- .github/ISSUE_TEMPLATE/watcher.md | 5 +++++ pkgs/watcher/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/watcher.md diff --git a/.github/ISSUE_TEMPLATE/watcher.md b/.github/ISSUE_TEMPLATE/watcher.md new file mode 100644 index 000000000..2578819b1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/watcher.md @@ -0,0 +1,5 @@ +--- +name: "package:watcher" +about: "Create a bug or file a feature request against package:watcher." +labels: "package:watcher" +--- \ No newline at end of file diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 108be66df..f96a74a2c 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -3,7 +3,7 @@ version: 1.1.1-wip description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. -repository: https://github.com/dart-lang/watcher +repository: https://github.com/dart-lang/tools/tree/main/pkgs/watcher environment: sdk: ^3.1.0 From 40494e78dd4322b23a89b9a7a25d5a9b9f7347b3 Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 11 Dec 2024 14:50:07 +0100 Subject: [PATCH 201/201] Moving fixes --- .github/labeler.yml | 4 ++ .../workflows/watcher.yaml | 17 +++++++-- README.md | 1 + pkgs/watcher/.github/dependabot.yaml | 14 ------- .../watcher/.github/workflows/no-response.yml | 37 ------------------- pkgs/watcher/.github/workflows/publish.yaml | 14 ------- pkgs/watcher/CHANGELOG.md | 3 +- pkgs/watcher/README.md | 2 +- pkgs/watcher/pubspec.yaml | 2 +- 9 files changed, 23 insertions(+), 71 deletions(-) rename pkgs/watcher/.github/workflows/test-package.yml => .github/workflows/watcher.yaml (85%) delete mode 100644 pkgs/watcher/.github/dependabot.yaml delete mode 100644 pkgs/watcher/.github/workflows/no-response.yml delete mode 100644 pkgs/watcher/.github/workflows/publish.yaml diff --git a/.github/labeler.yml b/.github/labeler.yml index 1cc4b2058..f69c1bc27 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -111,3 +111,7 @@ 'package:unified_analytics': - changed-files: - any-glob-to-any-file: 'pkgs/unified_analytics/**' + +'package:watcher': + - changed-files: + - any-glob-to-any-file: 'pkgs/watcher/**' diff --git a/pkgs/watcher/.github/workflows/test-package.yml b/.github/workflows/watcher.yaml similarity index 85% rename from pkgs/watcher/.github/workflows/test-package.yml rename to .github/workflows/watcher.yaml index b501c983a..a04483c4d 100644 --- a/pkgs/watcher/.github/workflows/test-package.yml +++ b/.github/workflows/watcher.yaml @@ -1,17 +1,28 @@ -name: Dart CI +name: package:watcher on: # Run on PRs and pushes to the default branch. push: - branches: [ master ] + branches: [ main ] + paths: + - '.github/workflows/watcher.yaml' + - 'pkgs/watcher/**' pull_request: - branches: [ master ] + branches: [ main ] + paths: + - '.github/workflows/watcher.yaml' + - 'pkgs/watcher/**' schedule: - cron: "0 0 * * 0" env: PUB_ENVIRONMENT: bot.github + +defaults: + run: + working-directory: pkgs/watcher/ + jobs: # Check code formatting and static analysis on a single OS (linux) # against Dart dev. diff --git a/README.md b/README.md index 79d1dde21..11ff12977 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ don't naturally belong to other topic monorepos (like | [source_span](pkgs/source_span/) | Provides a standard representation for source code locations and spans. | [![package issues](https://img.shields.io/badge/package:source_span-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Asource_span) | [![pub package](https://img.shields.io/pub/v/source_span.svg)](https://pub.dev/packages/source_span) | | [sse](pkgs/sse/) | Provides client and server functionality for setting up bi-directional communication through Server Sent Events (SSE) and corresponding POST requests. | [![package issues](https://img.shields.io/badge/package:sse-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Asse) | [![pub package](https://img.shields.io/pub/v/sse.svg)](https://pub.dev/packages/sse) | | [unified_analytics](pkgs/unified_analytics/) | A package for logging analytics for all Dart and Flutter related tooling to Google Analytics. | [![package issues](https://img.shields.io/badge/package:unified_analytics-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aunified_analytics) | [![pub package](https://img.shields.io/pub/v/unified_analytics.svg)](https://pub.dev/packages/unified_analytics) | +| [watcher](pkgs/watcher/) | Monitor directories and send notifications when the contents change. | [![package issues](https://img.shields.io/badge/package:watcher-4774bc)](https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Awatcher) | [![pub package](https://img.shields.io/pub/v/watcher.svg)](https://pub.dev/packages/watcher) | ## Publishing automation diff --git a/pkgs/watcher/.github/dependabot.yaml b/pkgs/watcher/.github/dependabot.yaml deleted file mode 100644 index bf6b38a4d..000000000 --- a/pkgs/watcher/.github/dependabot.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Dependabot configuration file. -version: 2 - -updates: - - package-ecosystem: github-actions - directory: / - schedule: - interval: monthly - labels: - - autosubmit - groups: - github-actions: - patterns: - - "*" diff --git a/pkgs/watcher/.github/workflows/no-response.yml b/pkgs/watcher/.github/workflows/no-response.yml deleted file mode 100644 index ab1ac4984..000000000 --- a/pkgs/watcher/.github/workflows/no-response.yml +++ /dev/null @@ -1,37 +0,0 @@ -# A workflow to close issues where the author hasn't responded to a request for -# more information; see https://github.com/actions/stale. - -name: No Response - -# Run as a daily cron. -on: - schedule: - # Every day at 8am - - cron: '0 8 * * *' - -# All permissions not specified are set to 'none'. -permissions: - issues: write - pull-requests: write - -jobs: - no-response: - runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'dart-lang' }} - steps: - - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e - with: - # Don't automatically mark inactive issues+PRs as stale. - days-before-stale: -1 - # Close needs-info issues and PRs after 14 days of inactivity. - days-before-close: 14 - stale-issue-label: "needs-info" - close-issue-message: > - Without additional information we're not able to resolve this issue. - Feel free to add more info or respond to any questions above and we - can reopen the case. Thanks for your contribution! - stale-pr-label: "needs-info" - close-pr-message: > - Without additional information we're not able to resolve this PR. - Feel free to add more info or respond to any questions above. - Thanks for your contribution! diff --git a/pkgs/watcher/.github/workflows/publish.yaml b/pkgs/watcher/.github/workflows/publish.yaml deleted file mode 100644 index 2239b63d3..000000000 --- a/pkgs/watcher/.github/workflows/publish.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# A CI configuration to auto-publish pub packages. - -name: Publish - -on: - pull_request: - branches: [ master ] - push: - tags: [ 'v[0-9]+.[0-9]+.[0-9]+' ] - -jobs: - publish: - if: ${{ github.repository_owner == 'dart-lang' }} - uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 8f4d289d3..ef3a7e2d3 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,7 +1,8 @@ -## 1.1.1-wip +## 1.1.1 - Ensure `PollingFileWatcher.ready` completes for files that do not exist. - Require Dart SDK `^3.1.0` +- Move to `dart-lang/tools` monorepo. ## 1.1.0 diff --git a/pkgs/watcher/README.md b/pkgs/watcher/README.md index 677ca35b2..83a0324b4 100644 --- a/pkgs/watcher/README.md +++ b/pkgs/watcher/README.md @@ -1,4 +1,4 @@ -[![Dart CI](https://github.com/dart-lang/watcher/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/watcher/actions/workflows/test-package.yml) +[![Build Status](https://github.com/dart-lang/tools/actions/workflows/watcher.yaml/badge.svg)](https://github.com/dart-lang/tools/actions/workflows/watcher.yaml) [![pub package](https://img.shields.io/pub/v/watcher.svg)](https://pub.dev/packages/watcher) [![package publisher](https://img.shields.io/pub/publisher/watcher.svg)](https://pub.dev/packages/watcher/publisher) diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index f96a74a2c..7781bd445 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 1.1.1-wip +version: 1.1.1 description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified.