From 9a699208f96500746ab30ba893b701a9ea6ccd18 Mon Sep 17 00:00:00 2001 From: David Morgan Date: Thu, 23 Oct 2025 18:36:51 +0200 Subject: [PATCH] Adjust tests to show link creation races on windows, fix watcher. --- pkgs/watcher/CHANGELOG.md | 3 +++ .../lib/src/directory_watcher/windows.dart | 21 +++++++++++++++++++ .../test/directory_watcher/link_tests.dart | 13 +++++++++--- pkgs/watcher/test/utils.dart | 11 +++++++++- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index f9f0ea8b6..340de8b08 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -12,6 +12,9 @@ moved onto `b`, it would be reported as three events: delete `a`, delete `b`, create `b`. Now it's reported as two events: delete `a`, modify `b`. This matches the behavior of the Linux and MacOS watchers. +- Bug fix: with `DirectoryWatcher` on Windows, new links to direcories were + sometimes incorrectly handled as actual directories. Now they are reported + as files, matching the behavior of the Linux and MacOS watchers. - Bug fix: with `PollingDirectoryWatcher`, fix spurious modify event emitted because of a file delete during polling. diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index bbed14e40..c8d1af012 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -261,6 +261,27 @@ class _WindowsDirectoryWatcher Map> _sortEvents(List batch) { var eventsForPaths = >{}; + // On Windows new links to directories are sometimes reported by + // Directory.watch as directories. On all other platforms it reports them + // consistently as files. See https://github.com/dart-lang/sdk/issues/61797. + // + // The wrong type is because Windows creates links to directories as actual + // directories, then converts them to links. Directory.watch sometimes + // checks the type too early and gets the wrong result. + // + // The batch delay is plenty for the link to be fully created, so verify the + // file system entity type for all createDirectory` events, converting to + // `createFile` when needed. + for (var i = 0; i != batch.length; ++i) { + final event = batch[i]; + if (event.type == EventType.createDirectory) { + if (FileSystemEntity.typeSync(event.path, followLinks: false) == + FileSystemEntityType.link) { + batch[i] = Event.createFile(event.path); + } + } + } + // Events within directories that already have create events are not needed // as the directory's full content will be listed. var createdDirectories = unionAll(batch.map((event) { diff --git a/pkgs/watcher/test/directory_watcher/link_tests.dart b/pkgs/watcher/test/directory_watcher/link_tests.dart index 3ca07f780..51d0ec731 100644 --- a/pkgs/watcher/test/directory_watcher/link_tests.dart +++ b/pkgs/watcher/test/directory_watcher/link_tests.dart @@ -21,7 +21,8 @@ void _linkTests({required bool isNative}) { writeFile('targets/a.target'); await startWatcher(path: 'links'); - writeLink(link: 'links/a.link', target: 'targets/a.target'); + writeLink( + link: 'links/a.link', target: 'targets/a.target', unawaitedAsync: true); await expectAddEvent('links/a.link'); }); @@ -109,7 +110,10 @@ void _linkTests({required bool isNative}) { createDir('targets/a.targetdir'); await startWatcher(path: 'links'); - writeLink(link: 'links/a.link', target: 'targets/a.targetdir'); + writeLink( + link: 'links/a.link', + target: 'targets/a.targetdir', + unawaitedAsync: true); // TODO(davidmorgan): reconcile differences. if (isNative) { @@ -128,7 +132,10 @@ void _linkTests({required bool isNative}) { writeFile('targets/a.targetdir/a.target'); await startWatcher(path: 'links'); - writeLink(link: 'links/a.link', target: 'targets/a.targetdir'); + writeLink( + link: 'links/a.link', + target: 'targets/a.targetdir', + unawaitedAsync: true); // TODO(davidmorgan): reconcile differences. if (isNative) { diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 2f723037c..8d0a999c5 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -239,9 +239,14 @@ void writeFile(String path, {String? contents}) { /// Writes a file in the sandbox at [link] pointing to [target]. /// /// [target] is relative to the sandbox, not to [link]. +/// +/// If [unawaitedAsync], the link is written asynchronously and not awaited. +/// Otherwise, it's synchronous. See the note in `windows.dart` for issue 61797 +/// for why this is needed for testing on Windows. void writeLink({ required String link, required String target, + bool unawaitedAsync = false, }) { var fullPath = p.join(d.sandbox, link); @@ -251,7 +256,11 @@ void writeLink({ dir.createSync(recursive: true); } - Link(fullPath).createSync(p.join(d.sandbox, target)); + if (unawaitedAsync) { + unawaited(Link(fullPath).create(p.join(d.sandbox, target))); + } else { + Link(fullPath).createSync(p.join(d.sandbox, target)); + } } /// Deletes a file in the sandbox at [path].