Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 8 additions & 36 deletions pkgs/watcher/test/directory_watcher/file_tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import 'package:watcher/src/utils.dart';
import '../utils.dart';

void fileTests() {
for (var i = 0; i != runsPerTest; ++i) {
_fileTests();
}
}

void _fileTests() {
test('does not notify for files that already exist when started', () async {
// Make some pre-existing files.
writeFile('a.txt');
Expand Down Expand Up @@ -141,27 +147,13 @@ void fileTests() {
});
});

// 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(
Expand All @@ -173,13 +165,7 @@ void fileTests() {
deleteFile('file.txt');
writeFile('file.txt', contents: 're-created');

await allowEither(() {
expectModifyEvent('file.txt');
}, () {
// Backup case.
expectRemoveEvent('file.txt');
expectAddEvent('file.txt');
});
await expectModifyEvent('file.txt');
});

test(
Expand All @@ -191,14 +177,7 @@ void fileTests() {
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');
});
await inAnyOrder([isModifyEvent('old.txt'), isAddEvent('new.txt')]);
});

test(
Expand All @@ -210,9 +189,6 @@ void fileTests() {
writeFile('file.txt', contents: 'modified');
deleteFile('file.txt');

// Backup case.
await allowModifyEvent('file.txt');

await expectRemoveEvent('file.txt');
});

Expand All @@ -224,10 +200,6 @@ void fileTests() {
writeFile('file.txt', contents: 'modified');

await expectAddEvent('file.txt');

// Backup case.
startClosingEventStream();
await allowModifyEvent('file.txt');
});
});

Expand Down
6 changes: 6 additions & 0 deletions pkgs/watcher/test/directory_watcher/link_tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import 'package:test/test.dart';
import '../utils.dart';

void linkTests({required bool isNative}) {
for (var i = 0; i != runsPerTest; ++i) {
_linkTests(isNative: isNative);
}
}

void _linkTests({required bool isNative}) {
test('notifies when a link is added', () async {
createDir('targets');
createDir('links');
Expand Down
14 changes: 4 additions & 10 deletions pkgs/watcher/test/directory_watcher/linux_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,9 @@ void main() {
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')));

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')));
});
await inAnyOrder(withPermutations(
(i, j, k) => isRemoveEvent('dir/sub/sub-$i/sub-$j/file-$k.txt')));
await inAnyOrder(withPermutations(
(i, j, k) => isAddEvent('dir/sub/sub-$i/sub-$j/file-$k.txt')));
});
}
14 changes: 4 additions & 10 deletions pkgs/watcher/test/directory_watcher/mac_os_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,10 @@ void main() {
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')));

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')));
});
await inAnyOrder(withPermutations(
(i, j, k) => isRemoveEvent('dir/sub/sub-$i/sub-$j/file-$k.txt')));
await inAnyOrder(withPermutations(
(i, j, k) => isAddEvent('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
Expand Down
10 changes: 6 additions & 4 deletions pkgs/watcher/test/file_watcher/file_tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ void fileTests({required bool isNative}) {
writeFile('file.txt');
});

for (var i = 0; i != runsPerTest; ++i) {
_fileTests(isNative: isNative);
}
}

void _fileTests({required bool isNative}) {
test("doesn't notify if the file isn't modified", () async {
// TODO(davidmorgan): fix startup race on MacOS.
if (isNative && Platform.isMacOS) {
await Future<void>.delayed(const Duration(milliseconds: 100));
}
await startWatcher(path: 'file.txt');
await expectNoEvents();
});
Expand Down
6 changes: 6 additions & 0 deletions pkgs/watcher/test/file_watcher/link_tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ void linkTests({required bool isNative}) {
writeLink(link: 'link.txt', target: 'target.txt');
});

for (var i = 0; i != runsPerTest; ++i) {
_linkTests(isNative: isNative);
}
}

void _linkTests({required bool isNative}) {
test("doesn't notify if nothing is modified", () async {
await startWatcher(path: 'link.txt');
await expectNoEvents();
Expand Down
71 changes: 11 additions & 60 deletions pkgs/watcher/test/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import 'package:test_descriptor/test_descriptor.dart' as d;
import 'package:watcher/src/stat.dart';
import 'package:watcher/watcher.dart';

/// Edit this to run fast-running tests many times.
int runsPerTest = 1;

typedef WatcherFactory = Watcher Function(String directory);

/// Sets the function used to create the watcher.
Expand Down Expand Up @@ -164,62 +167,18 @@ void startClosingEventStream() async {
await _watcherEvents.cancel(immediate: true);
}

/// A list of [StreamMatcher]s that have been collected using
/// [_collectStreamMatcher].
List<StreamMatcher>? _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(void Function() block) {
var oldStreamMatchers = _collectedStreamMatchers;
var collectedStreamMatchers = _collectedStreamMatchers = <StreamMatcher>[];
try {
block();
return emitsInOrder(collectedStreamMatchers);
} finally {
_collectedStreamMatchers = oldStreamMatchers;
}
}

/// Either add [streamMatcher] as an expectation to [_watcherEvents], or collect
/// it with [_collectStreamMatcher].
/// Add [streamMatcher] as an expectation to [_watcherEvents].
///
/// [streamMatcher] can be a [StreamMatcher], a [Matcher], or a value.
Future _expectOrCollect(Matcher streamMatcher) {
var collectedStreamMatchers = _collectedStreamMatchers;
if (collectedStreamMatchers != null) {
collectedStreamMatchers.add(emits(streamMatcher));
return Future.sync(() {});
} else {
return expectLater(_watcherEvents, emits(streamMatcher));
}
Future _expect(Matcher 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.
Future inAnyOrder(Iterable matchers) {
matchers = matchers.toSet();
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.
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(void Function() block) =>
_expectOrCollect(mayEmit(_collectStreamMatcher(block)));
Future inAnyOrder(Iterable matchers) =>
_expect(emitsInAnyOrder(matchers.toSet()));

/// Returns a StreamMatcher that matches a [WatchEvent] with the given [type]
/// and [path].
Expand Down Expand Up @@ -274,24 +233,16 @@ Future<List<WatchEvent>> takeEvents({required Duration duration}) async {

/// Expects that the next event emitted will be for an add event for [path].
Future expectAddEvent(String path) =>
_expectOrCollect(isWatchEvent(ChangeType.ADD, path));
_expect(isWatchEvent(ChangeType.ADD, path));

/// Expects that the next event emitted will be for a modification event for
/// [path].
Future expectModifyEvent(String path) =>
_expectOrCollect(isWatchEvent(ChangeType.MODIFY, path));
_expect(isWatchEvent(ChangeType.MODIFY, path));

/// Expects that the next event emitted will be for a removal event for [path].
Future expectRemoveEvent(String path) =>
_expectOrCollect(isWatchEvent(ChangeType.REMOVE, 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.
Future allowModifyEvent(String path) =>
_expectOrCollect(mayEmit(isWatchEvent(ChangeType.MODIFY, path)));
_expect(isWatchEvent(ChangeType.REMOVE, 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
Expand Down