Skip to content

Commit 94f7985

Browse files
committed
Add DirectoryWatcher symlink tests.
1 parent 8b9a48c commit 94f7985

File tree

8 files changed

+319
-15
lines changed

8 files changed

+319
-15
lines changed

pkgs/watcher/test/directory_watcher/shared.dart renamed to pkgs/watcher/test/directory_watcher/file_tests.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
4+
45
import 'dart:io' as io;
56
import 'dart:isolate';
67

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

1112
import '../utils.dart';
1213

13-
void sharedTests() {
14+
void fileTests() {
1415
test('does not notify for files that already exist when started', () async {
1516
// Make some pre-existing files.
1617
writeFile('a.txt');
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'package:test/test.dart';
8+
9+
import '../utils.dart';
10+
11+
void linkTests({required bool isNative}) {
12+
test('notifies when a link is added', () async {
13+
createDir('targets');
14+
createDir('links');
15+
writeFile('targets/a.target');
16+
await startWatcher(path: 'links');
17+
18+
writeLink(link: 'links/a.link', target: 'targets/a.target');
19+
20+
await expectAddEvent('links/a.link');
21+
});
22+
23+
test(
24+
'notifies when a link is replaced with a link to a different target '
25+
'with the same contents', () async {
26+
createDir('targets');
27+
createDir('links');
28+
writeFile('targets/a.target');
29+
writeFile('targets/b.target');
30+
writeLink(link: 'links/a.link', target: 'targets/a.target');
31+
await startWatcher(path: 'links');
32+
33+
deleteLink('links/a.link');
34+
writeLink(link: 'links/a.link', target: 'targets/b.target');
35+
36+
await expectModifyEvent('links/a.link');
37+
});
38+
39+
test(
40+
'notifies when a link is replaced with a link to a different target '
41+
'with different contents', () async {
42+
writeFile('targets/a.target', contents: 'a');
43+
writeFile('targets/b.target', contents: 'b');
44+
writeLink(link: 'links/a.link', target: 'targets/a.target');
45+
await startWatcher(path: 'links');
46+
47+
deleteLink('links/a.link');
48+
writeLink(link: 'links/a.link', target: 'targets/b.target');
49+
50+
await expectModifyEvent('links/a.link');
51+
});
52+
53+
test('does not notify when a link target is modified', () async {
54+
createDir('targets');
55+
createDir('links');
56+
writeFile('targets/a.target');
57+
writeLink(link: 'links/a.link', target: 'targets/a.target');
58+
await startWatcher(path: 'links');
59+
writeFile('targets/a.target', contents: 'modified');
60+
61+
// TODO(davidmorgan): reconcile differences.
62+
if (isNative) {
63+
await expectNoEvents();
64+
} else {
65+
await expectModifyEvent('links/a.link');
66+
}
67+
});
68+
69+
test('does not notify when a link target is removed', () async {
70+
createDir('targets');
71+
createDir('links');
72+
writeFile('targets/a.target');
73+
writeLink(link: 'links/a.link', target: 'targets/a.target');
74+
await startWatcher(path: 'links');
75+
76+
deleteFile('targets/a.target');
77+
78+
// TODO(davidmorgan): reconcile differences.
79+
if (isNative) {
80+
await expectNoEvents();
81+
} else {
82+
await expectRemoveEvent('links/a.link');
83+
}
84+
});
85+
86+
test('notifies when a link is moved within the watched directory', () async {
87+
createDir('targets');
88+
createDir('links');
89+
writeFile('targets/a.target');
90+
writeLink(link: 'links/a.link', target: 'targets/a.target');
91+
await startWatcher(path: 'links');
92+
93+
renameLink('links/a.link', 'links/b.link');
94+
95+
await inAnyOrder(
96+
[isAddEvent('links/b.link'), isRemoveEvent('links/a.link')]);
97+
});
98+
99+
test('notifies when a link to an empty directory is added', () async {
100+
createDir('targets');
101+
createDir('links');
102+
createDir('targets/a.targetdir');
103+
await startWatcher(path: 'links');
104+
105+
writeLink(link: 'links/a.link', target: 'targets/a.targetdir');
106+
107+
// TODO(davidmorgan): reconcile differences.
108+
if (isNative) {
109+
await expectAddEvent('links/a.link');
110+
} else {
111+
await expectNoEvents();
112+
}
113+
});
114+
115+
test(
116+
'does not notify about directory contents '
117+
'when a link to a directory is added', () async {
118+
createDir('targets');
119+
createDir('links');
120+
createDir('targets/a.targetdir');
121+
writeFile('targets/a.targetdir/a.target');
122+
await startWatcher(path: 'links');
123+
124+
writeLink(link: 'links/a.link', target: 'targets/a.targetdir');
125+
126+
// TODO(davidmorgan): reconcile differences.
127+
if (isNative) {
128+
await expectAddEvent('links/a.link');
129+
} else {
130+
await expectAddEvent('links/a.link/a.target');
131+
}
132+
});
133+
134+
test('notifies when a file is added to a linked directory', () async {
135+
createDir('targets');
136+
createDir('links');
137+
createDir('targets/a.targetdir');
138+
writeLink(link: 'links/a.link', target: 'targets/a.targetdir');
139+
await startWatcher(path: 'links');
140+
141+
writeFile('targets/a.targetdir/a.txt');
142+
143+
// TODO(davidmorgan): reconcile differences.
144+
if (!isNative || Platform.isLinux) {
145+
await expectAddEvent('links/a.link/a.txt');
146+
} else {
147+
await expectNoEvents();
148+
}
149+
});
150+
151+
test(
152+
'notifies about linked directory contents when a directory with a linked '
153+
'subdirectory is moved in', () async {
154+
createDir('targets');
155+
createDir('links');
156+
createDir('targets/a.targetdir');
157+
createDir('watched');
158+
writeFile('targets/a.targetdir/a.txt');
159+
writeLink(link: 'links/a.link', target: 'targets/a.targetdir');
160+
await startWatcher(path: 'watched');
161+
162+
renameDir('links', 'watched/links');
163+
164+
await expectAddEvent('watched/links/a.link/a.txt');
165+
});
166+
167+
test(
168+
'notifies about linked directory contents when a directory with a linked '
169+
'subdirectory containing a link loop is moved in', () async {
170+
createDir('targets');
171+
createDir('links');
172+
createDir('targets/a.targetdir');
173+
createDir('watched');
174+
writeFile('targets/a.targetdir/a.txt');
175+
writeLink(link: 'links/a.link', target: 'targets/a.targetdir');
176+
writeLink(
177+
link: 'targets/a.targetdir/cycle.link', target: 'targets/a.targetdir');
178+
await startWatcher(path: 'watched');
179+
180+
renameDir('links', 'watched/links');
181+
182+
// TODO(davidmorgan): reconcile differences.
183+
if (isNative && (Platform.isLinux || Platform.isMacOS)) {
184+
await inAnyOrder([
185+
isAddEvent('watched/links/a.link/a.txt'),
186+
isAddEvent('watched/links/a.link/cycle.link/a.txt'),
187+
isAddEvent('watched/links/a.link/cycle.link/cycle.link'),
188+
]);
189+
await expectNoEvents();
190+
} else if (isNative && Platform.isWindows) {
191+
await inAnyOrder([
192+
isAddEvent('watched/links/a.link/a.txt'),
193+
isAddEvent('watched/links/a.link/cycle.link'),
194+
]);
195+
await expectNoEvents();
196+
} else if (!isNative && Platform.isWindows) {
197+
await inAnyOrder([
198+
isAddEvent('watched/links/a.link/a.txt'),
199+
]);
200+
await expectNoEvents();
201+
} else {
202+
assert(!isNative);
203+
await inAnyOrder([
204+
isAddEvent('watched/links/a.link/a.txt'),
205+
isAddEvent('watched/links/a.link/cycle.link/a.txt'),
206+
]);
207+
await expectNoEvents();
208+
}
209+
});
210+
211+
test(
212+
'notifies about linked directory contents when a directory with a linked '
213+
'subdirectory containing two link loops is moved in', () async {
214+
createDir('targets');
215+
createDir('links');
216+
createDir('targets/a.targetdir');
217+
createDir('watched');
218+
writeFile('targets/a.targetdir/a.txt');
219+
writeLink(link: 'links/a.link', target: 'targets/a.targetdir');
220+
writeLink(
221+
link: 'targets/a.targetdir/cycle1.link', target: 'targets/a.targetdir');
222+
writeLink(
223+
link: 'targets/a.targetdir/cycle2.link', target: 'targets/a.targetdir');
224+
await startWatcher(path: 'watched');
225+
226+
renameDir('links', 'watched/links');
227+
228+
// TODO(davidmorgan): reconcile differences.
229+
if (isNative && (Platform.isLinux || Platform.isMacOS)) {
230+
await inAnyOrder([
231+
isAddEvent('watched/links/a.link/a.txt'),
232+
isAddEvent('watched/links/a.link/cycle1.link/a.txt'),
233+
isAddEvent('watched/links/a.link/cycle1.link/cycle1.link'),
234+
isAddEvent('watched/links/a.link/cycle1.link/cycle2.link/a.txt'),
235+
isAddEvent('watched/links/a.link/cycle1.link/cycle2.link/cycle1.link'),
236+
isAddEvent('watched/links/a.link/cycle1.link/cycle2.link/cycle2.link'),
237+
isAddEvent('watched/links/a.link/cycle2.link/a.txt'),
238+
isAddEvent('watched/links/a.link/cycle2.link/cycle1.link/a.txt'),
239+
isAddEvent('watched/links/a.link/cycle2.link/cycle1.link/cycle1.link'),
240+
isAddEvent('watched/links/a.link/cycle2.link/cycle1.link/cycle2.link'),
241+
isAddEvent('watched/links/a.link/cycle2.link/cycle2.link'),
242+
]);
243+
await expectNoEvents();
244+
} else if (isNative && Platform.isWindows) {
245+
await inAnyOrder([
246+
isAddEvent('watched/links/a.link/a.txt'),
247+
isAddEvent('watched/links/a.link/cycle1.link'),
248+
isAddEvent('watched/links/a.link/cycle2.link'),
249+
]);
250+
await expectNoEvents();
251+
} else if (!isNative && Platform.isWindows) {
252+
await inAnyOrder([
253+
isAddEvent('watched/links/a.link/a.txt'),
254+
]);
255+
await expectNoEvents();
256+
} else {
257+
assert(!isNative);
258+
await inAnyOrder([
259+
isAddEvent('watched/links/a.link/a.txt'),
260+
isAddEvent('watched/links/a.link/cycle1.link/a.txt'),
261+
isAddEvent('watched/links/a.link/cycle1.link/cycle2.link/a.txt'),
262+
isAddEvent('watched/links/a.link/cycle2.link/a.txt'),
263+
isAddEvent('watched/links/a.link/cycle2.link/cycle1.link/a.txt'),
264+
]);
265+
await expectNoEvents();
266+
}
267+
});
268+
}

pkgs/watcher/test/directory_watcher/linux_test.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ import 'package:watcher/src/directory_watcher/linux.dart';
1010
import 'package:watcher/watcher.dart';
1111

1212
import '../utils.dart';
13-
import 'shared.dart';
13+
import 'file_tests.dart';
14+
import 'link_tests.dart';
1415

1516
void main() {
1617
watcherFactory = LinuxDirectoryWatcher.new;
1718

18-
sharedTests();
19+
fileTests();
20+
linkTests(isNative: true);
1921

2022
test('DirectoryWatcher creates a LinuxDirectoryWatcher on Linux', () {
2123
expect(DirectoryWatcher('.'), const TypeMatcher<LinuxDirectoryWatcher>());

pkgs/watcher/test/directory_watcher/mac_os_test.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ import 'package:watcher/src/directory_watcher/mac_os.dart';
1010
import 'package:watcher/watcher.dart';
1111

1212
import '../utils.dart';
13-
import 'shared.dart';
13+
import 'file_tests.dart';
14+
import 'link_tests.dart';
1415

1516
void main() {
1617
watcherFactory = MacOSDirectoryWatcher.new;
1718

18-
sharedTests();
19+
fileTests();
20+
linkTests(isNative: true);
1921

2022
test('DirectoryWatcher creates a MacOSDirectoryWatcher on Mac OS', () {
2123
expect(DirectoryWatcher('.'), const TypeMatcher<MacOSDirectoryWatcher>());

pkgs/watcher/test/directory_watcher/polling_test.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import 'package:test/test.dart';
99
import 'package:watcher/watcher.dart';
1010

1111
import '../utils.dart';
12-
import 'shared.dart';
12+
import 'file_tests.dart';
13+
import 'link_tests.dart';
1314

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

23-
sharedTests();
24+
fileTests();
25+
linkTests(isNative: false);
2426

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

39-
sharedTests();
41+
fileTests();
42+
linkTests(isNative: false);
4043
});
4144
}

pkgs/watcher/test/directory_watcher/windows_test.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ import 'package:watcher/src/directory_watcher/windows.dart';
1515
import 'package:watcher/watcher.dart';
1616

1717
import '../utils.dart';
18-
import 'shared.dart';
18+
import 'file_tests.dart';
19+
import 'link_tests.dart';
1920

2021
void main() {
2122
watcherFactory = WindowsDirectoryWatcher.new;
2223

23-
group('Shared Tests:', sharedTests);
24+
fileTests();
25+
linkTests(isNative: true);
2426

2527
test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () {
2628
expect(DirectoryWatcher('.'), const TypeMatcher<WindowsDirectoryWatcher>());

pkgs/watcher/test/file_watcher/link_tests.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ void linkTests({required bool isNative}) {
6060

6161
test('notifies when a link is removed', () async {
6262
await startWatcher(path: 'link.txt');
63-
deleteFile('link.txt');
63+
deleteLink('link.txt');
6464

6565
// TODO(davidmorgan): reconcile differences.
6666
if (isNative) {

0 commit comments

Comments
 (0)