From 289754210de91f9718080d2c494a8b52fcafee20 Mon Sep 17 00:00:00 2001 From: David Morgan Date: Mon, 29 Sep 2025 17:09:33 +0200 Subject: [PATCH] Add tests for file watcher behavior with links. --- .../watcher/test/file_watcher/link_tests.dart | 168 ++++++++++++++++++ pkgs/watcher/test/utils.dart | 28 +++ 2 files changed, 196 insertions(+) create mode 100644 pkgs/watcher/test/file_watcher/link_tests.dart diff --git a/pkgs/watcher/test/file_watcher/link_tests.dart b/pkgs/watcher/test/file_watcher/link_tests.dart new file mode 100644 index 000000000..a6eef1403 --- /dev/null +++ b/pkgs/watcher/test/file_watcher/link_tests.dart @@ -0,0 +1,168 @@ +// Copyright (c) 2025, 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 '../utils.dart'; + +void linkTests({required bool isNative}) { + setUp(() async { + writeFile('target.txt'); + writeLink(link: 'link.txt', target: 'target.txt'); + }); + + test("doesn't notify if nothing is modified", () async { + await startWatcher(path: 'link.txt'); + await expectNoEvents(); + }); + + test('notifies when a link is overwritten with an identical file', () async { + await startWatcher(path: 'link.txt'); + writeFile('link.txt'); + await expectModifyEvent('link.txt'); + }); + + test('notifies when a link is overwritten with a different file', () async { + await startWatcher(path: 'link.txt'); + writeFile('link.txt', contents: 'modified'); + await expectModifyEvent('link.txt'); + }); + + test( + 'notifies when a link target is overwritten with an identical file', + () async { + await startWatcher(path: 'link.txt'); + writeFile('target.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectModifyEvent('link.txt'); + } else { + await expectNoEvents(); + } + }, + ); + + test('notifies when a link target is modified', () async { + await startWatcher(path: 'link.txt'); + writeFile('target.txt', contents: 'modified'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectModifyEvent('link.txt'); + } else { + await expectNoEvents(); + } + }); + + test('notifies when a link is removed', () async { + await startWatcher(path: 'link.txt'); + deleteFile('link.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectNoEvents(); + } else { + await expectRemoveEvent('link.txt'); + } + }); + + test('notifies when a link target is removed', () async { + await startWatcher(path: 'link.txt'); + deleteFile('target.txt'); + await expectRemoveEvent('link.txt'); + }); + + test('notifies when a link target is modified multiple times', () async { + await startWatcher(path: 'link.txt'); + + writeFile('target.txt', contents: 'modified'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectModifyEvent('link.txt'); + } else { + await expectNoEvents(); + } + + writeFile('target.txt', contents: 'modified again'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectModifyEvent('link.txt'); + } else { + await expectNoEvents(); + } + }); + + test('notifies when a link is moved away', () async { + await startWatcher(path: 'link.txt'); + renameFile('link.txt', 'new.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectNoEvents(); + } else { + await expectRemoveEvent('link.txt'); + } + }); + + test('notifies when a link target is moved away', () async { + await startWatcher(path: 'link.txt'); + renameFile('target.txt', 'new.txt'); + await expectRemoveEvent('link.txt'); + }); + + test('notifies when an identical file is moved over the link', () async { + await startWatcher(path: 'link.txt'); + writeFile('old.txt'); + renameFile('old.txt', 'link.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectNoEvents(); + } else { + await expectModifyEvent('link.txt'); + } + }); + + test('notifies when an different file is moved over the link', () async { + await startWatcher(path: 'link.txt'); + writeFile('old.txt', contents: 'modified'); + renameFile('old.txt', 'link.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectNoEvents(); + } else { + await expectModifyEvent('link.txt'); + } + }); + + test('notifies when an identical file is moved over the target', () async { + await startWatcher(path: 'link.txt'); + writeFile('old.txt'); + renameFile('old.txt', 'target.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectModifyEvent('link.txt'); + } else { + await expectNoEvents(); + } + }); + + test('notifies when a different file is moved over the target', () async { + await startWatcher(path: 'link.txt'); + writeFile('old.txt', contents: 'modified'); + renameFile('old.txt', 'target.txt'); + + // TODO(davidmorgan): reconcile differences. + if (isNative) { + await expectModifyEvent('link.txt'); + } else { + await expectNoEvents(); + } + }); +} diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 12a277980..aa5cc2ea4 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -246,6 +246,34 @@ void writeFile(String path, {String? contents, bool? updateModified}) { } } +/// Schedules writing a file in the sandbox at [link] pointing to [target]. +/// +/// If [updateModified] is `false`, the mock file modification time is not +/// changed. +void writeLink({ + required String link, + required String target, + bool? updateModified, +}) { + updateModified ??= true; + + var fullPath = p.join(d.sandbox, link); + + // Create any needed subdirectories. + var dir = Directory(p.dirname(fullPath)); + if (!dir.existsSync()) { + dir.createSync(recursive: true); + } + + Link(fullPath).createSync(target); + + if (updateModified) { + link = p.normalize(link); + + _mockFileModificationTimes[link] = _nextTimestamp++; + } +} + /// Schedules deleting a file in the sandbox at [path]. void deleteFile(String path) { File(p.join(d.sandbox, path)).deleteSync();