1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 package libcore.java.nio.file;
17 
18 import org.junit.Test;
19 import org.junit.runner.RunWith;
20 import org.junit.runners.JUnit4;
21 import org.junit.Rule;
22 
23 import java.nio.file.FileSystems;
24 import java.nio.file.Files;
25 import java.nio.file.Path;
26 import java.nio.file.Paths;
27 import java.nio.file.WatchEvent;
28 import java.nio.file.WatchKey;
29 import java.nio.file.WatchService;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.HashMap;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.concurrent.TimeUnit;
37 
38 import static junit.framework.TestCase.assertEquals;
39 import static junit.framework.TestCase.assertFalse;
40 import static junit.framework.TestCase.assertNotNull;
41 import static junit.framework.TestCase.assertNull;
42 import static junit.framework.TestCase.assertTrue;
43 import static junit.framework.TestCase.fail;
44 
45 import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
46 import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
47 import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
48 
49 @RunWith(JUnit4.class)
50 public class WatchServiceTest {
51     private static final WatchEvent.Kind<?>[] ALL_EVENTS_KINDS =
52         {ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY};
53 
54     @Rule
55     public final FilesSetup filesSetup = new FilesSetup();
56 
57     static class WatchEventResult {
58         final WatchEvent.Kind<Path> expectedKind;
59         final int expectedCount;
60         final boolean testCount;
61 
WatchEventResult(WatchEvent.Kind<Path> expectedKind)62         public WatchEventResult(WatchEvent.Kind<Path> expectedKind) {
63             this.expectedKind = expectedKind;
64             this.expectedCount = 0;
65             this.testCount = false;
66         }
67 
WatchEventResult(WatchEvent.Kind<Path> expectedKind, int expectedCount)68         public WatchEventResult(WatchEvent.Kind<Path> expectedKind,
69                                 int expectedCount) {
70             this.expectedKind = expectedKind;
71             this.expectedCount = expectedCount;
72             this.testCount = true;
73         }
74     }
75 
checkWatchServiceEventMultipleKeys(WatchService watchService, Map<WatchKey, List<WatchEventResult>> expectedResults, boolean expectedResetResult)76     private static void checkWatchServiceEventMultipleKeys(WatchService watchService,
77             Map<WatchKey, List<WatchEventResult>> expectedResults,
78             boolean expectedResetResult) throws InterruptedException {
79 
80         // Make a deep copy
81         HashMap<WatchKey, ArrayList<WatchEventResult>> expectedResultsCopy
82                 = new HashMap<>();
83         for (Map.Entry<WatchKey, List<WatchEventResult>> entry : expectedResults.entrySet()) {
84             expectedResultsCopy.put(entry.getKey(), new ArrayList<>(entry.getValue()));
85         }
86 
87         while (!expectedResultsCopy.isEmpty()) {
88             WatchKey watchKey = watchService.poll(2, TimeUnit.SECONDS);
89             assertNotNull(watchKey);
90 
91             List<WatchEventResult> expectedEvents = expectedResultsCopy.get(watchKey);
92             assertNotNull(expectedEvents);
93 
94             Iterator<WatchEventResult> expectedEventsIterator = expectedEvents.iterator();
95             for (WatchEvent<?> event : watchKey.pollEvents()) {
96                 WatchEventResult expectedEventResult = expectedEventsIterator.next();
97                 assertNotNull(expectedEventResult);
98 
99                 assertEquals(expectedEventResult.expectedKind, event.kind());
100                 if (expectedEventResult.testCount) {
101                     assertEquals(expectedEventResult.expectedCount, event.count());
102                 }
103                 expectedEventsIterator.remove();
104             }
105             assertEquals(expectedResetResult, watchKey.reset());
106             if (!expectedEventsIterator.hasNext()) {
107                 expectedResultsCopy.remove(watchKey);
108             }
109         }
110     }
111 
checkWatchServiceEvent(WatchService watchService, WatchKey expectedWatchKey, List<WatchEventResult> expectedEvents, boolean expectedResetResult)112     private static void checkWatchServiceEvent(WatchService watchService,
113             WatchKey expectedWatchKey,
114             List<WatchEventResult> expectedEvents,
115             boolean expectedResetResult) throws InterruptedException {
116         Map<WatchKey, List<WatchEventResult>> expected = new HashMap<>();
117         expected.put(expectedWatchKey, expectedEvents);
118         checkWatchServiceEventMultipleKeys(watchService, expected, expectedResetResult);
119     }
120 
121     @Test
test_singleFile()122     public void test_singleFile() throws Exception {
123         WatchService watchService = FileSystems.getDefault().newWatchService();
124         Path file = Paths.get(filesSetup.getTestDir(), "directory/file");
125         Path directory = Paths.get(filesSetup.getTestDir(), "directory");
126         assertFalse(Files.exists(file));
127         Files.createDirectories(directory);
128         WatchKey directoryKey1 = directory.register(watchService, ALL_EVENTS_KINDS);
129 
130         // emit EVENT_CREATE
131         Files.createFile(file);
132         checkWatchServiceEvent(watchService, directoryKey1,
133             Arrays.asList(new WatchEventResult(ENTRY_CREATE, 1)), true);
134         assertNull(watchService.poll());
135 
136         // emit EVENT_MODIFY
137         Files.write(file, "hello1".getBytes());
138         checkWatchServiceEvent(watchService, directoryKey1,
139             Arrays.asList(new WatchEventResult(ENTRY_MODIFY)), true);
140 
141         // http:///b/35346596
142         // Sometimes we receive a second, latent EVENT_MODIFY that happens shortly
143         // after the first one. This will intercept it and make sure it won't
144         // mess with ENTRY_DELETE later.
145         Thread.sleep(500);
146         WatchKey doubleModifyKey = watchService.poll();
147         if (doubleModifyKey != null) {
148             List<WatchEvent<?>> event = doubleModifyKey.pollEvents();
149             assertEquals(ENTRY_MODIFY, event.get(0).kind());
150             doubleModifyKey.reset();
151         }
152         assertNull(watchService.poll());
153 
154         // emit EVENT_DELETE
155         Files.delete(file);
156         checkWatchServiceEvent(watchService, directoryKey1,
157             Arrays.asList(new WatchEventResult(ENTRY_DELETE, 1)), true);
158 
159         // Assert no more events
160         assertNull(watchService.poll());
161         watchService.close();
162     }
163 
164     @Test
test_EventMask()165     public void test_EventMask() throws Exception {
166         WatchService watchService = FileSystems.getDefault().newWatchService();
167         WatchEvent.Kind<?>[] events = {ENTRY_DELETE};
168         Path file = Paths.get(filesSetup.getTestDir(), "directory/file");
169         Path directory = Paths.get(filesSetup.getTestDir(), "directory");
170         assertFalse(Files.exists(file));
171         Files.createDirectories(directory);
172         WatchKey directoryKey1 = directory.register(watchService, events);
173 
174         // emit EVENT_CREATE
175         Files.createFile(file);
176         // emit EVENT_MODIFY (masked)
177         Files.write(file, "hello1".getBytes());
178         // emit EVENT_DELETE (masked)
179         Files.delete(file);
180 
181         checkWatchServiceEvent(watchService, directoryKey1,
182                 Arrays.asList(new WatchEventResult(ENTRY_DELETE, 1)), true);
183         assertNull(watchService.poll());
184         watchService.close();
185     }
186 
187     @Test
test_singleDirectory()188     public void test_singleDirectory() throws Exception {
189         WatchService watchService = FileSystems.getDefault().newWatchService();
190         Path dirInDir = Paths.get(filesSetup.getTestDir(), "directory/dir");
191         Path directory = Paths.get(filesSetup.getTestDir(), "directory");
192         assertFalse(Files.exists(dirInDir));
193         Files.createDirectories(directory);
194         WatchKey directoryKey1 = directory.register(watchService, ALL_EVENTS_KINDS);
195 
196         // emit EVENT_CREATE
197         Files.createDirectories(dirInDir);
198 
199         // Shouldn't emit EVENT_MODIFY
200         Path dirInDirInDir = Paths.get(filesSetup.getTestDir(), "directory/dir/dir");
201         Files.createDirectories(dirInDirInDir);
202         Files.delete(dirInDirInDir);
203 
204         // emit EVENT_DELETE
205         Files.delete(dirInDir);
206 
207         checkWatchServiceEvent(watchService, directoryKey1,
208                 Arrays.asList(new WatchEventResult(ENTRY_CREATE, 1),
209                               new WatchEventResult(ENTRY_DELETE, 1)), true);
210         assertNull(watchService.poll());
211         watchService.close();
212 
213         watchService.close();
214     }
215 
216     @Test
test_cancel()217     public void test_cancel() throws Exception {
218         WatchService watchService = FileSystems.getDefault().newWatchService();
219         Path file = Paths.get(filesSetup.getTestDir(), "directory/file");
220         Path directory = Paths.get(filesSetup.getTestDir(), "directory");
221         assertFalse(Files.exists(file));
222         Files.createDirectories(directory);
223         WatchKey directoryKey1 = directory.register(watchService, ALL_EVENTS_KINDS);
224 
225         // emit EVENT_CREATE
226         Files.createFile(file);
227 
228         // Canceling the key may prevent the EVENT_CREATE from being picked-up...
229         // TODO: Fix this (b/35190858).
230         Thread.sleep(500);
231 
232         // Cancel the key
233         directoryKey1.cancel();
234         assertFalse(directoryKey1.isValid());
235 
236         // Shouldn't emit EVENT_MODIFY and EVENT_DELETE
237         Files.write(file, "hello1".getBytes());
238         Files.delete(file);
239 
240         checkWatchServiceEvent(watchService, directoryKey1,
241                 Arrays.asList(new WatchEventResult(ENTRY_CREATE, 1)), false);
242         assertNull(watchService.poll());
243         watchService.close();
244     }
245 
246     @Test
test_removeTarget()247     public void test_removeTarget() throws Exception {
248         WatchService watchService = FileSystems.getDefault().newWatchService();
249         Path file = Paths.get(filesSetup.getTestDir(), "directory/file");
250         Path directory = Paths.get(filesSetup.getTestDir(), "directory");
251         assertFalse(Files.exists(file));
252         Files.createDirectories(directory);
253         WatchKey directoryKey1 = directory.register(watchService, ALL_EVENTS_KINDS);
254 
255         // emit EVENT_CREATE x1
256         Files.createFile(file);
257         Files.delete(file);
258 
259         // Delete underlying target.
260         assertTrue(directoryKey1.isValid());
261         Files.delete(directory);
262 
263         // We need to give some time to watch service thread to catch up with the
264         // deletion
265         while (directoryKey1.isValid()) {
266             Thread.sleep(500);
267         }
268 
269         checkWatchServiceEvent(watchService, directoryKey1,
270                 Arrays.asList(new WatchEventResult(ENTRY_CREATE, 1),
271                               new WatchEventResult(ENTRY_DELETE, 1)), false);
272         assertNull(watchService.poll());
273 
274         watchService.close();
275     }
276 
277     @Test
test_multipleKeys()278     public void test_multipleKeys() throws Exception {
279         WatchService watchService1 = FileSystems.getDefault().newWatchService();
280 
281         Path directory1 = Paths.get(filesSetup.getTestDir(), "directory1");
282         Path directory2 = Paths.get(filesSetup.getTestDir(), "directory2");
283 
284         Path dir1file1 = Paths.get(filesSetup.getTestDir(), "directory1/file1");
285         assertFalse(Files.exists(dir1file1));
286         Path dir2file1 = Paths.get(filesSetup.getTestDir(), "directory2/file1");
287         assertFalse(Files.exists(dir2file1));
288 
289         Files.createDirectories(directory1);
290         Files.createDirectories(directory2);
291         WatchKey directoryKey1 = directory1.register(watchService1, ALL_EVENTS_KINDS);
292         WatchKey directoryKey2 = directory2.register(watchService1, ALL_EVENTS_KINDS);
293 
294         // emit EVENT_CREATE/DELETE for all
295         Path[] allFiles = new Path[]{dir1file1, dir2file1};
296         for (Path path : allFiles) {
297             Files.createFile(path);
298             Files.delete(path);
299         }
300 
301         Map<WatchKey, List<WatchEventResult>> expected = new HashMap<>();
302         expected.put(directoryKey1,
303                 Arrays.asList(
304                         new WatchEventResult(ENTRY_CREATE, 1),
305                         new WatchEventResult(ENTRY_DELETE, 1)));
306         expected.put(directoryKey2,
307                 Arrays.asList(
308                         new WatchEventResult(ENTRY_CREATE, 1),
309                         new WatchEventResult(ENTRY_DELETE, 1)));
310 
311         checkWatchServiceEventMultipleKeys(watchService1, expected, true);
312         assertNull(watchService1.poll());
313         watchService1.close();
314     }
315 
316     @Test
test_multipleServices()317     public void test_multipleServices() throws Exception {
318         WatchService watchService1 = FileSystems.getDefault().newWatchService();
319         WatchService watchService2 = FileSystems.getDefault().newWatchService();
320 
321         Path directory1 = Paths.get(filesSetup.getTestDir(), "directory1");
322         Path directory2 = Paths.get(filesSetup.getTestDir(), "directory2");
323 
324         Path dir1file1 = Paths.get(filesSetup.getTestDir(), "directory1/file1");
325         assertFalse(Files.exists(dir1file1));
326         Path dir2file1 = Paths.get(filesSetup.getTestDir(), "directory2/file1");
327         assertFalse(Files.exists(dir2file1));
328 
329         Files.createDirectories(directory1);
330         Files.createDirectories(directory2);
331 
332         // 2 services listening for distinct directories
333         WatchKey directoryKey1 = directory1.register(watchService1, ALL_EVENTS_KINDS);
334         WatchKey directoryKey2 = directory2.register(watchService2, ALL_EVENTS_KINDS);
335         // emit EVENT_CREATE/DELETE for all
336         Path[] allFiles = new Path[]{dir1file1, dir2file1};
337         for (Path path : allFiles) {
338             Files.createFile(path);
339             Files.delete(path);
340         }
341 
342         checkWatchServiceEvent(watchService1, directoryKey1,
343                                 Arrays.asList(new WatchEventResult(ENTRY_CREATE, 1),
344                                               new WatchEventResult(ENTRY_DELETE, 1)), true);
345         checkWatchServiceEvent(watchService2, directoryKey2,
346                                 Arrays.asList(new WatchEventResult(ENTRY_CREATE, 1),
347                                               new WatchEventResult(ENTRY_DELETE, 1)), true);
348 
349         // 2 services listening for a same directory
350         WatchKey directoryKey3 = directory1.register(watchService2, ALL_EVENTS_KINDS);
351         {
352             Files.createFile(dir1file1);
353             Files.delete(dir1file1);
354         }
355         checkWatchServiceEvent(watchService1, directoryKey1,
356                                 Arrays.asList(new WatchEventResult(ENTRY_CREATE, 1),
357                                               new WatchEventResult(ENTRY_DELETE, 1)), true);
358         checkWatchServiceEvent(watchService2, directoryKey3,
359                                 Arrays.asList(new WatchEventResult(ENTRY_CREATE, 1),
360                                               new WatchEventResult(ENTRY_DELETE, 1)), true);
361 
362 
363 
364         assertNull(watchService1.poll());
365         watchService1.close();
366         assertNull(watchService2.poll());
367         watchService2.close();
368     }
369 }
370