blob: a6bc14dec896c4f72895fa92bc4eb05f4835daff [file] [log] [blame]
// 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')
library;
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'package:watcher/src/directory_watcher/windows.dart';
import 'package:watcher/watcher.dart';
import '../utils.dart';
import 'shared.dart';
void main() {
watcherFactory = WindowsDirectoryWatcher.new;
group('Shared Tests:', sharedTests);
test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () {
expect(DirectoryWatcher('.'), const TypeMatcher<WindowsDirectoryWatcher>());
});
test(
'Regression test for https://github.com/dart-lang/tools/issues/2110',
() async {
late StreamSubscription<WatchEvent> sub;
try {
final temp = Directory.systemTemp.createTempSync();
final watcher = DirectoryWatcher(temp.path);
final events = <WatchEvent>[];
sub = watcher.events.listen(events.add);
await watcher.ready;
// Create a file in a directory that doesn't exist. This forces the
// directory to be created first before the child file.
//
// When directory creation is detected by the watcher, it calls
// `Directory.list` on the directory to determine if there's files that
// have been created or modified. It's possible that the watcher will
// have already detected the file creation event before `Directory.list`
// returns. Before https://github.com/dart-lang/tools/issues/2110 was
// resolved, the check to ensure an event hadn't already been emitted
// for the file creation was incorrect, leading to the event being
// emitted again in some circumstances.
final file = File(p.join(temp.path, 'foo', 'file.txt'))
..createSync(recursive: true);
// Introduce a short delay to allow for the directory watcher to detect
// the creation of foo/ and foo/file.txt.
await Future<void>.delayed(const Duration(seconds: 1));
// There should only be a single file added event.
expect(events, hasLength(1));
expect(
events.first.toString(),
WatchEvent(ChangeType.ADD, file.path).toString(),
);
} finally {
await sub.cancel();
}
},
);
// The Windows native watcher has a buffer that gets exhausted if events are
// not handled quickly enough. Then, it throws an error and stops watching.
// The exhaustion is reliably triggered if enough events arrive during a sync
// block. The `package:watcher` implementation tries to catch this and recover
// by starting a new watcher.
group('Buffer exhaustion', () {
late StreamSubscription<Object> subscription;
late Directory temp;
late int eventsSeen;
late int errorsSeen;
setUp(() async {
temp = Directory.systemTemp.createTempSync();
final watcher = DirectoryWatcher(temp.path);
eventsSeen = 0;
errorsSeen = 0;
subscription = watcher.events.listen(
(e) {
++eventsSeen;
},
onError: (_, __) {
++errorsSeen;
},
);
await watcher.ready;
});
tearDown(() {
subscription.cancel();
});
test('recovery', () async {
// Use a long filename to fill the buffer.
final file = File('${temp.path}\\file'.padRight(255, 'a'));
// Repeatedly trigger buffer exhaustion, to check that recovery is
// reliable.
for (var times = 0; times != 200; ++times) {
errorsSeen = 0;
eventsSeen = 0;
// Syncronously trigger 200 events. Because this is a sync block, the VM
// won't handle the events, so this has a very high chance of triggering
// a buffer exhaustion.
//
// If a buffer exhaustion happens, `package:watcher` turns this into an
// error on the event stream, so `errorsSeen` will get incremented once.
// The number of changes 200 is chosen so this is very likely to happen.
// If there is _not_ an exhaustion, the 200 events will show on the
// stream as a single event because they are changes of the same file.
// So, `eventsSeen` will instead be incremented once.
for (var i = 0; i != 200; ++i) {
file.writeAsStringSync('');
}
// Events only happen when there is an async gap, wait for such a gap.
await Future<void>.delayed(const Duration(milliseconds: 10));
// If everything is going well, there should have been either one event
// seen or one error seen.
if (errorsSeen == 0 && eventsSeen == 0) {
// It looks like the watcher is now broken: there were file changes
// but no event and no error. Do some non-sync writes to confirm
// whether the watcher really is now broken.
for (var i = 0; i != 5; ++i) {
await file.writeAsString('');
}
await Future<void>.delayed(const Duration(milliseconds: 10));
fail(
'On attempt ${times + 1}, watcher registered nothing. '
'On retry, it registered: $errorsSeen error(s), $eventsSeen '
'event(s).',
);
}
}
});
});
}