| // 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: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', () async { |
| // Make some pre-existing files. |
| writeFile('a.txt'); |
| writeFile('b.txt'); |
| |
| 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. |
| await expectModifyEvent('b.txt'); |
| }); |
| |
| test('notifies when a file is added', () async { |
| await startWatcher(); |
| writeFile('file.txt'); |
| await expectAddEvent('file.txt'); |
| }); |
| |
| test('notifies when a file is modified', () async { |
| writeFile('file.txt'); |
| await startWatcher(); |
| writeFile('file.txt', contents: 'modified'); |
| await expectModifyEvent('file.txt'); |
| }); |
| |
| test('notifies when a file is removed', () async { |
| writeFile('file.txt'); |
| await startWatcher(); |
| deleteFile('file.txt'); |
| await expectRemoveEvent('file.txt'); |
| }); |
| |
| test('notifies when a file is modified multiple times', () async { |
| writeFile('file.txt'); |
| await startWatcher(); |
| 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'); |
| await startWatcher(); |
| |
| 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'); |
| |
| await startWatcher(path: 'dir'); |
| |
| 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'); |
| |
| await startWatcher(path: 'dir'); |
| |
| 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'); |
| |
| await startWatcher(path: 'dir'); |
| |
| createDir('dir/subdir'); |
| renameDir('dir', 'moved_dir'); |
| createDir('dir'); |
| await inAnyOrder([isRemoveEvent('dir/a.txt'), isRemoveEvent('dir/b.txt')]); |
| }); |
| |
| group('moves', () { |
| test('notifies when a file is moved within the watched directory', |
| () async { |
| writeFile('old.txt'); |
| await startWatcher(); |
| renameFile('old.txt', 'new.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'); |
| |
| 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'); |
| |
| 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'); |
| await startWatcher(); |
| |
| renameFile('from.txt', 'to.txt'); |
| await inAnyOrder([isRemoveEvent('from.txt'), isModifyEvent('to.txt')]); |
| }); |
| }); |
| |
| // 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", |
| () async { |
| writeFile('test.txt'); |
| await startWatcher(); |
| writeFile('file.txt'); |
| deleteFile('file.txt'); |
| |
| // Backup case. |
| startClosingEventStream(); |
| await allowEvents(() { |
| expectAddEvent('file.txt'); |
| expectRemoveEvent('file.txt'); |
| }); |
| }); |
| |
| test( |
| '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'); |
| |
| await allowEither(() { |
| expectModifyEvent('file.txt'); |
| }, () { |
| // Backup case. |
| expectRemoveEvent('file.txt'); |
| expectAddEvent('file.txt'); |
| }); |
| }); |
| |
| test( |
| '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'); |
| |
| await allowEither(() { |
| inAnyOrder([isModifyEvent('old.txt'), isAddEvent('new.txt')]); |
| }, () { |
| // Backup case. |
| 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'); |
| await startWatcher(); |
| |
| writeFile('file.txt', contents: 'modified'); |
| deleteFile('file.txt'); |
| |
| // Backup case. |
| await allowModifyEvent('file.txt'); |
| |
| await expectRemoveEvent('file.txt'); |
| }); |
| |
| test('reports an add when a file is added and then immediately modified', |
| () async { |
| await startWatcher(); |
| |
| writeFile('file.txt'); |
| writeFile('file.txt', contents: 'modified'); |
| |
| await expectAddEvent('file.txt'); |
| |
| // Backup case. |
| startClosingEventStream(); |
| await allowModifyEvent('file.txt'); |
| }); |
| }); |
| |
| 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'); |
| }); |
| |
| test( |
| 'notifies when a subdirectory is moved within the watched directory ' |
| 'and then its contents are modified', () async { |
| writeFile('old/file.txt'); |
| await startWatcher(); |
| |
| renameDir('old', 'new'); |
| await inAnyOrder( |
| [isRemoveEvent('old/file.txt'), isAddEvent('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'); |
| await startWatcher(); |
| |
| deleteFile('new'); |
| renameDir('old', 'new'); |
| await inAnyOrder([ |
| 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'); |
| await startWatcher(); |
| |
| renameDir('new', 'newer'); |
| renameFile('old', 'new'); |
| await inAnyOrder([ |
| isRemoveEvent('new/file.txt'), |
| isAddEvent('newer/file.txt'), |
| isRemoveEvent('old'), |
| isAddEvent('new') |
| ]); |
| }, onPlatform: { |
| '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')); |
| |
| 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'))); |
| }); |
| |
| 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'); |
| 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'); |
| |
| 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', () async { |
| withPermutations( |
| (i, j, k) => writeFile('dir/old/sub-$i/sub-$j/file-$k.txt')); |
| |
| createDir('dir'); |
| await startWatcher(path: 'dir'); |
| renameDir('dir/old', 'dir/new'); |
| |
| await inAnyOrder(unionAll(withPermutations((i, j, k) { |
| 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'); |
| |
| 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')); |
| await inAnyOrder(events); |
| }); |
| }); |
| } |