diff --git a/.travis.yml b/.travis.yml index deb901b..4c8c5ff 100644 --- a/.travis.yml +++ b/.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/CHANGELOG.md b/CHANGELOG.md index b4ca0ba..5e6c3d2 100644 --- a/CHANGELOG.md +++ b/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/lib/src/utils.dart b/lib/src/utils.dart index ef39eef..18b53c9 100644 --- a/lib/src/utils.dart +++ b/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/pubspec.yaml b/pubspec.yaml index d1260d5..62abc69 100644 --- a/pubspec.yaml +++ b/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/test/directory_watcher/linux_test.dart b/test/directory_watcher/linux_test.dart index 34e6896..25c5504 100644 --- a/test/directory_watcher/linux_test.dart +++ b/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/test/directory_watcher/mac_os_test.dart b/test/directory_watcher/mac_os_test.dart index 46b7ca2..8fa76fd 100644 --- a/test/directory_watcher/mac_os_test.dart +++ b/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/test/directory_watcher/polling_test.dart b/test/directory_watcher/polling_test.dart index 0192b25..39bbbef 100644 --- a/test/directory_watcher/polling_test.dart +++ b/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/test/directory_watcher/shared.dart b/test/directory_watcher/shared.dart index 2bdc077..2c0f441 100644 --- a/test/directory_watcher/shared.dart +++ b/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/test/directory_watcher/windows_test.dart b/test/directory_watcher/windows_test.dart index 176fe2d..3696f9c 100644 --- a/test/directory_watcher/windows_test.dart +++ b/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/test/file_watcher/native_test.dart b/test/file_watcher/native_test.dart index cbf11b6..b6ed901 100644 --- a/test/file_watcher/native_test.dart +++ b/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/test/file_watcher/polling_test.dart b/test/file_watcher/polling_test.dart index e502544..01d579a 100644 --- a/test/file_watcher/polling_test.dart +++ b/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/test/file_watcher/shared.dart b/test/file_watcher/shared.dart index 50f7aeb..286a042 100644 --- a/test/file_watcher/shared.dart +++ b/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/test/no_subscription/linux_test.dart b/test/no_subscription/linux_test.dart index c8e8ae9..e9bfd69 100644 --- a/test/no_subscription/linux_test.dart +++ b/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/test/no_subscription/mac_os_test.dart b/test/no_subscription/mac_os_test.dart index 499aff3..fc14ebf 100644 --- a/test/no_subscription/mac_os_test.dart +++ b/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/test/no_subscription/polling_test.dart b/test/no_subscription/polling_test.dart index 5c99bc0..75fa3a7 100644 --- a/test/no_subscription/polling_test.dart +++ b/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/test/no_subscription/shared.dart b/test/no_subscription/shared.dart index 5858656..c7e0501 100644 --- a/test/no_subscription/shared.dart +++ b/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/test/ready/linux_test.dart b/test/ready/linux_test.dart index c8e8ae9..e9bfd69 100644 --- a/test/ready/linux_test.dart +++ b/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/test/ready/mac_os_test.dart b/test/ready/mac_os_test.dart index d5b1c8e..9533cc8 100644 --- a/test/ready/mac_os_test.dart +++ b/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/test/ready/polling_test.dart b/test/ready/polling_test.dart index 5c99bc0..75fa3a7 100644 --- a/test/ready/polling_test.dart +++ b/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/test/ready/shared.dart b/test/ready/shared.dart index 7be4833..730d579 100644 --- a/test/ready/shared.dart +++ b/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/test/utils.dart b/test/utils.dart index 6700500..637eacf 100644 --- a/test/utils.dart +++ b/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]