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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2012, 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 'dart:io' as io;
import 'dart:isolate';

Expand All @@ -10,7 +11,7 @@ import 'package:watcher/src/utils.dart';

import '../utils.dart';

void sharedTests() {
void fileTests() {
test('does not notify for files that already exist when started', () async {
// Make some pre-existing files.
writeFile('a.txt');
Expand Down
268 changes: 268 additions & 0 deletions pkgs/watcher/test/directory_watcher/link_tests.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
// 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 'dart:io';

import 'package:test/test.dart';

import '../utils.dart';

void linkTests({required bool isNative}) {
test('notifies when a link is added', () async {
createDir('targets');
createDir('links');
writeFile('targets/a.target');
await startWatcher(path: 'links');

writeLink(link: 'links/a.link', target: 'targets/a.target');

await expectAddEvent('links/a.link');
});

test(
'notifies when a link is replaced with a link to a different target '
'with the same contents', () async {
createDir('targets');
createDir('links');
writeFile('targets/a.target');
writeFile('targets/b.target');
writeLink(link: 'links/a.link', target: 'targets/a.target');
await startWatcher(path: 'links');

deleteLink('links/a.link');
writeLink(link: 'links/a.link', target: 'targets/b.target');

await expectModifyEvent('links/a.link');
});

test(
'notifies when a link is replaced with a link to a different target '
'with different contents', () async {
writeFile('targets/a.target', contents: 'a');
writeFile('targets/b.target', contents: 'b');
writeLink(link: 'links/a.link', target: 'targets/a.target');
await startWatcher(path: 'links');

deleteLink('links/a.link');
writeLink(link: 'links/a.link', target: 'targets/b.target');

await expectModifyEvent('links/a.link');
});

test('does not notify when a link target is modified', () async {
createDir('targets');
createDir('links');
writeFile('targets/a.target');
writeLink(link: 'links/a.link', target: 'targets/a.target');
await startWatcher(path: 'links');
writeFile('targets/a.target', contents: 'modified');

// TODO(davidmorgan): reconcile differences.
if (isNative) {
await expectNoEvents();
} else {
await expectModifyEvent('links/a.link');
}
});

test('does not notify when a link target is removed', () async {
createDir('targets');
createDir('links');
writeFile('targets/a.target');
writeLink(link: 'links/a.link', target: 'targets/a.target');
await startWatcher(path: 'links');

deleteFile('targets/a.target');

// TODO(davidmorgan): reconcile differences.
if (isNative) {
await expectNoEvents();
} else {
await expectRemoveEvent('links/a.link');
}
});

test('notifies when a link is moved within the watched directory', () async {
createDir('targets');
createDir('links');
writeFile('targets/a.target');
writeLink(link: 'links/a.link', target: 'targets/a.target');
await startWatcher(path: 'links');

renameLink('links/a.link', 'links/b.link');

await inAnyOrder(
[isAddEvent('links/b.link'), isRemoveEvent('links/a.link')]);
});

test('notifies when a link to an empty directory is added', () async {
createDir('targets');
createDir('links');
createDir('targets/a.targetdir');
await startWatcher(path: 'links');

writeLink(link: 'links/a.link', target: 'targets/a.targetdir');

// TODO(davidmorgan): reconcile differences.
if (isNative) {
await expectAddEvent('links/a.link');
} else {
await expectNoEvents();
}
});

test(
'does not notify about directory contents '
'when a link to a directory is added', () async {
createDir('targets');
createDir('links');
createDir('targets/a.targetdir');
writeFile('targets/a.targetdir/a.target');
await startWatcher(path: 'links');

writeLink(link: 'links/a.link', target: 'targets/a.targetdir');

// TODO(davidmorgan): reconcile differences.
if (isNative) {
await expectAddEvent('links/a.link');
} else {
await expectAddEvent('links/a.link/a.target');
}
});

test('notifies when a file is added to a linked directory', () async {
createDir('targets');
createDir('links');
createDir('targets/a.targetdir');
writeLink(link: 'links/a.link', target: 'targets/a.targetdir');
await startWatcher(path: 'links');

writeFile('targets/a.targetdir/a.txt');

// TODO(davidmorgan): reconcile differences.
if (!isNative || Platform.isLinux) {
await expectAddEvent('links/a.link/a.txt');
} else {
await expectNoEvents();
}
});

test(
'notifies about linked directory contents when a directory with a linked '
'subdirectory is moved in', () async {
createDir('targets');
createDir('links');
createDir('targets/a.targetdir');
createDir('watched');
writeFile('targets/a.targetdir/a.txt');
writeLink(link: 'links/a.link', target: 'targets/a.targetdir');
await startWatcher(path: 'watched');

renameDir('links', 'watched/links');

await expectAddEvent('watched/links/a.link/a.txt');
});

test(
'notifies about linked directory contents when a directory with a linked '
'subdirectory containing a link loop is moved in', () async {
createDir('targets');
createDir('links');
createDir('targets/a.targetdir');
createDir('watched');
writeFile('targets/a.targetdir/a.txt');
writeLink(link: 'links/a.link', target: 'targets/a.targetdir');
writeLink(
link: 'targets/a.targetdir/cycle.link', target: 'targets/a.targetdir');
await startWatcher(path: 'watched');

renameDir('links', 'watched/links');

// TODO(davidmorgan): reconcile differences.
if (isNative && (Platform.isLinux || Platform.isMacOS)) {
await inAnyOrder([
isAddEvent('watched/links/a.link/a.txt'),
isAddEvent('watched/links/a.link/cycle.link/a.txt'),
isAddEvent('watched/links/a.link/cycle.link/cycle.link'),
]);
await expectNoEvents();
} else if (isNative && Platform.isWindows) {
await inAnyOrder([
isAddEvent('watched/links/a.link/a.txt'),
isAddEvent('watched/links/a.link/cycle.link'),
]);
await expectNoEvents();
} else if (!isNative && Platform.isWindows) {
await inAnyOrder([
isAddEvent('watched/links/a.link/a.txt'),
]);
await expectNoEvents();
} else {
assert(!isNative);
await inAnyOrder([
isAddEvent('watched/links/a.link/a.txt'),
isAddEvent('watched/links/a.link/cycle.link/a.txt'),
]);
await expectNoEvents();
}
});

test(
'notifies about linked directory contents when a directory with a linked '
'subdirectory containing two link loops is moved in', () async {
createDir('targets');
createDir('links');
createDir('targets/a.targetdir');
createDir('watched');
writeFile('targets/a.targetdir/a.txt');
writeLink(link: 'links/a.link', target: 'targets/a.targetdir');
writeLink(
link: 'targets/a.targetdir/cycle1.link', target: 'targets/a.targetdir');
writeLink(
link: 'targets/a.targetdir/cycle2.link', target: 'targets/a.targetdir');
await startWatcher(path: 'watched');

renameDir('links', 'watched/links');

// TODO(davidmorgan): reconcile differences.
if (isNative && (Platform.isLinux || Platform.isMacOS)) {
await inAnyOrder([
isAddEvent('watched/links/a.link/a.txt'),
isAddEvent('watched/links/a.link/cycle1.link/a.txt'),
isAddEvent('watched/links/a.link/cycle1.link/cycle1.link'),
isAddEvent('watched/links/a.link/cycle1.link/cycle2.link/a.txt'),
isAddEvent('watched/links/a.link/cycle1.link/cycle2.link/cycle1.link'),
isAddEvent('watched/links/a.link/cycle1.link/cycle2.link/cycle2.link'),
isAddEvent('watched/links/a.link/cycle2.link/a.txt'),
isAddEvent('watched/links/a.link/cycle2.link/cycle1.link/a.txt'),
isAddEvent('watched/links/a.link/cycle2.link/cycle1.link/cycle1.link'),
isAddEvent('watched/links/a.link/cycle2.link/cycle1.link/cycle2.link'),
isAddEvent('watched/links/a.link/cycle2.link/cycle2.link'),
]);
await expectNoEvents();
} else if (isNative && Platform.isWindows) {
await inAnyOrder([
isAddEvent('watched/links/a.link/a.txt'),
isAddEvent('watched/links/a.link/cycle1.link'),
isAddEvent('watched/links/a.link/cycle2.link'),
]);
await expectNoEvents();
} else if (!isNative && Platform.isWindows) {
await inAnyOrder([
isAddEvent('watched/links/a.link/a.txt'),
]);
await expectNoEvents();
} else {
assert(!isNative);
await inAnyOrder([
isAddEvent('watched/links/a.link/a.txt'),
isAddEvent('watched/links/a.link/cycle1.link/a.txt'),
isAddEvent('watched/links/a.link/cycle1.link/cycle2.link/a.txt'),
isAddEvent('watched/links/a.link/cycle2.link/a.txt'),
isAddEvent('watched/links/a.link/cycle2.link/cycle1.link/a.txt'),
]);
await expectNoEvents();
}
});
}
6 changes: 4 additions & 2 deletions pkgs/watcher/test/directory_watcher/linux_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import 'package:watcher/src/directory_watcher/linux.dart';
import 'package:watcher/watcher.dart';

import '../utils.dart';
import 'shared.dart';
import 'file_tests.dart';
import 'link_tests.dart';

void main() {
watcherFactory = LinuxDirectoryWatcher.new;

sharedTests();
fileTests();
linkTests(isNative: true);

test('DirectoryWatcher creates a LinuxDirectoryWatcher on Linux', () {
expect(DirectoryWatcher('.'), const TypeMatcher<LinuxDirectoryWatcher>());
Expand Down
6 changes: 4 additions & 2 deletions pkgs/watcher/test/directory_watcher/mac_os_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import 'package:watcher/src/directory_watcher/mac_os.dart';
import 'package:watcher/watcher.dart';

import '../utils.dart';
import 'shared.dart';
import 'file_tests.dart';
import 'link_tests.dart';

void main() {
watcherFactory = MacOSDirectoryWatcher.new;

sharedTests();
fileTests();
linkTests(isNative: true);

test('DirectoryWatcher creates a MacOSDirectoryWatcher on Mac OS', () {
expect(DirectoryWatcher('.'), const TypeMatcher<MacOSDirectoryWatcher>());
Expand Down
9 changes: 6 additions & 3 deletions pkgs/watcher/test/directory_watcher/polling_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import 'package:test/test.dart';
import 'package:watcher/watcher.dart';

import '../utils.dart';
import 'shared.dart';
import 'file_tests.dart';
import 'link_tests.dart';

void main() {
// Use a short delay to make the tests run quickly.
Expand All @@ -20,7 +21,8 @@ void main() {
group('with mock mtime', () {
setUp(enableMockModificationTimes);

sharedTests();
fileTests();
linkTests(isNative: false);

test('does not notify if the modification time did not change', () async {
writeFile('a.txt', contents: 'before');
Expand All @@ -36,6 +38,7 @@ void main() {
group('with real mtime', () {
setUp(enableWaitingForDifferentModificationTimes);

sharedTests();
fileTests();
linkTests(isNative: false);
});
}
6 changes: 4 additions & 2 deletions pkgs/watcher/test/directory_watcher/windows_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import 'package:watcher/src/directory_watcher/windows.dart';
import 'package:watcher/watcher.dart';

import '../utils.dart';
import 'shared.dart';
import 'file_tests.dart';
import 'link_tests.dart';

void main() {
watcherFactory = WindowsDirectoryWatcher.new;

group('Shared Tests:', sharedTests);
fileTests();
linkTests(isNative: true);

test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () {
expect(DirectoryWatcher('.'), const TypeMatcher<WindowsDirectoryWatcher>());
Expand Down
2 changes: 1 addition & 1 deletion pkgs/watcher/test/file_watcher/link_tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ void linkTests({required bool isNative}) {

test('notifies when a link is removed', () async {
await startWatcher(path: 'link.txt');
deleteFile('link.txt');
deleteLink('link.txt');

// TODO(davidmorgan): reconcile differences.
if (isNative) {
Expand Down
Loading
Loading