| // 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"); |
| 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"); |
| 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"); |
| 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 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 " |
| "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); |
| }); |
| }); |
| } |