f6dfb8b39b55ff96e88e61f87aeb3126b0938c46
[idea/community.git] / platform / platform-tests / testSrc / com / intellij / openapi / vfs / local / FileWatcherTest.java
1 /*
2  * Copyright 2000-2013 JetBrains s.r.o.
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 com.intellij.openapi.vfs.local;
17
18 import com.intellij.openapi.application.AccessToken;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.util.Ref;
22 import com.intellij.openapi.util.SystemInfo;
23 import com.intellij.openapi.util.io.FileUtil;
24 import com.intellij.openapi.util.io.IoTestUtil;
25 import com.intellij.openapi.vfs.*;
26 import com.intellij.openapi.vfs.impl.local.FileWatcher;
27 import com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl;
28 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
29 import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
30 import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent;
31 import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent;
32 import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent;
33 import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
34 import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl;
35 import com.intellij.testFramework.PlatformLangTestCase;
36 import com.intellij.util.Alarm;
37 import com.intellij.util.Function;
38 import com.intellij.util.TimeoutUtil;
39 import com.intellij.util.containers.ContainerUtil;
40 import com.intellij.util.messages.MessageBusConnection;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43
44 import java.io.File;
45 import java.io.IOException;
46 import java.util.*;
47
48 import static com.intellij.openapi.util.io.IoTestUtil.createSubst;
49 import static com.intellij.openapi.util.io.IoTestUtil.createTestDir;
50 import static com.intellij.openapi.util.io.IoTestUtil.createTestFile;
51
52 public class FileWatcherTest extends PlatformLangTestCase {
53   private static final int INTER_RESPONSE_DELAY = 500;  // time to wait for a next event in a sequence
54   private static final int NATIVE_PROCESS_DELAY = 60000;  // time to wait for a native watcher response
55
56   private static Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.local.FileWatcher");
57
58   private FileWatcher myWatcher;
59   private LocalFileSystem myFileSystem;
60   private MessageBusConnection myConnection;
61   private volatile boolean myAccept = false;
62   private Alarm myAlarm;
63   private final Runnable myNotifier = new Runnable() {
64     @Override
65     public void run() {
66       LOG.debug("-- (event, expected=" + myAccept + ")");
67       if (!myAccept) return;
68       myAlarm.cancelAllRequests();
69       myAlarm.addRequest(new Runnable() {
70         @Override
71         public void run() {
72           myAccept = false;
73           LOG.debug("** waiting finished");
74           synchronized (myWaiter) {
75             myWaiter.notifyAll();
76           }
77         }
78       }, INTER_RESPONSE_DELAY);
79     }
80   };
81   private final Object myWaiter = new Object();
82   private int myTimeout = NATIVE_PROCESS_DELAY;
83   private final List<VFileEvent> myEvents = new ArrayList<VFileEvent>();
84
85   @Override
86   protected void setUp() throws Exception {
87     LOG.debug("================== setting up " + getName() + " ==================");
88
89     super.setUp();
90
91     myFileSystem = LocalFileSystem.getInstance();
92     assertNotNull(myFileSystem);
93
94     myWatcher = ((LocalFileSystemImpl)myFileSystem).getFileWatcher();
95     assertNotNull(myWatcher);
96     assertFalse(myWatcher.isOperational());
97     myWatcher.startup(myNotifier);
98     assertTrue(myWatcher.isOperational());
99
100     myAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, getProject());
101     myTimeout = NATIVE_PROCESS_DELAY;
102
103     myConnection = ApplicationManager.getApplication().getMessageBus().connect();
104     myConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener.Adapter() {
105       @Override
106       public void after(@NotNull List<? extends VFileEvent> events) {
107         synchronized (myEvents) {
108           myEvents.addAll(events);
109         }
110       }
111     });
112
113     LOG = FileWatcher.getLog();
114     LOG.debug("================== setting up " + getName() + " ==================");
115   }
116
117   @Override
118   protected void tearDown() throws Exception {
119     LOG.debug("================== tearing down " + getName() + " ==================");
120
121     try {
122       myConnection.disconnect();
123       myWatcher.shutdown();
124       assertFalse(myWatcher.isOperational());
125     }
126     finally {
127       myFileSystem = null;
128       myWatcher = null;
129       super.tearDown();
130     }
131
132     LOG.debug("================== tearing down " + getName() + " ==================");
133   }
134
135
136   public void testFileRoot() throws Exception {
137     File file = createTestFile("test.txt");
138     refresh(file);
139
140     LocalFileSystem.WatchRequest request = watch(file);
141     try {
142       myAccept = true;
143       FileUtil.writeToFile(file, "new content");
144       assertEvent(VFileContentChangeEvent.class, file.getAbsolutePath());
145
146       myAccept = true;
147       FileUtil.delete(file);
148       assertEvent(VFileDeleteEvent.class, file.getAbsolutePath());
149
150       if (!SystemInfo.isLinux) {
151         // todo[r.sh] fix Linux watcher
152         myAccept = true;
153         FileUtil.writeToFile(file, "re-creation");
154         assertEvent(VFileCreateEvent.class, file.getAbsolutePath());
155       }
156     }
157     finally {
158       unwatch(request);
159       delete(file);
160     }
161   }
162
163   public void testNonCanonicallyNamedFileRoot() throws Exception {
164     if (SystemInfo.isFileSystemCaseSensitive) {
165       System.err.println("Ignored: case-insensitive FS required");
166       return;
167     }
168
169     File file = createTestFile("test.txt");
170     refresh(file);
171
172     String watchRoot = file.getAbsolutePath().toUpperCase(Locale.US);
173     LocalFileSystem.WatchRequest request = watch(new File(watchRoot));
174     try {
175       myAccept = true;
176       FileUtil.writeToFile(file, "new content");
177       assertEvent(VFileContentChangeEvent.class, file.getAbsolutePath());
178
179       myAccept = true;
180       FileUtil.delete(file);
181       assertEvent(VFileDeleteEvent.class, file.getAbsolutePath());
182
183       myAccept = true;
184       FileUtil.writeToFile(file, "re-creation");
185       assertEvent(VFileCreateEvent.class, file.getAbsolutePath());
186     }
187     finally {
188       unwatch(request);
189       delete(file);
190     }
191   }
192
193   public void testDirectoryRecursive() throws Exception {
194     File topDir = createTestDir("top");
195     refresh(topDir);
196
197     LocalFileSystem.WatchRequest request = watch(topDir);
198     try {
199       myAccept = true;
200       File subDir = createTestDir(topDir, "sub");
201       assertEvent(VFileCreateEvent.class, subDir.getAbsolutePath());
202       refresh(subDir);
203
204       myAccept = true;
205       File file = createTestFile(subDir, "test.txt");
206       assertEvent(VFileCreateEvent.class, file.getAbsolutePath());
207
208       myAccept = true;
209       FileUtil.writeToFile(file, "new content");
210       assertEvent(VFileContentChangeEvent.class, file.getAbsolutePath());
211
212       myAccept = true;
213       FileUtil.delete(file);
214       assertEvent(VFileDeleteEvent.class, file.getAbsolutePath());
215
216       myAccept = true;
217       FileUtil.writeToFile(file, "re-creation");
218       assertEvent(VFileCreateEvent.class, file.getAbsolutePath());
219     }
220     finally {
221       unwatch(request);
222       delete(topDir);
223     }
224   }
225
226   public void testDirectoryFlat() throws Exception {
227     File topDir = createTestDir("top");
228     File watchedFile = createTestFile(topDir, "test.txt");
229     File subDir = createTestDir(topDir, "sub");
230     File unwatchedFile = createTestFile(subDir, "test.txt");
231     refresh(topDir);
232
233     LocalFileSystem.WatchRequest request = watch(topDir, false);
234     try {
235       myAccept = true;
236       FileUtil.writeToFile(watchedFile, "new content");
237       assertEvent(VFileContentChangeEvent.class, watchedFile.getAbsolutePath());
238
239       myTimeout = 10 * INTER_RESPONSE_DELAY;
240       myAccept = true;
241       FileUtil.writeToFile(unwatchedFile, "new content");
242       assertEvent(VFileEvent.class);
243       myTimeout = NATIVE_PROCESS_DELAY;
244     }
245     finally {
246       unwatch(request);
247       delete(topDir);
248     }
249   }
250
251   public void testDirectoryMixed() throws Exception {
252     File topDir = createTestDir("top");
253     File watchedFile1 = createTestFile(topDir, "test.txt");
254     File sub1Dir = createTestDir(topDir, "sub1");
255     File unwatchedFile = createTestFile(sub1Dir, "test.txt");
256     File sub2Dir = createTestDir(topDir, "sub2");
257     File sub2subDir = createTestDir(sub2Dir, "sub");
258     File watchedFile2 = createTestFile(sub2subDir, "test.txt");
259     refresh(topDir);
260
261     LocalFileSystem.WatchRequest topRequest = watch(topDir, false);
262     LocalFileSystem.WatchRequest subRequest = watch(sub2Dir);
263     try {
264       myAccept = true;
265       FileUtil.writeToFile(watchedFile1, "new content");
266       FileUtil.writeToFile(watchedFile2, "new content");
267       FileUtil.writeToFile(unwatchedFile, "new content");
268       assertEvent(VFileContentChangeEvent.class, watchedFile1.getAbsolutePath(), watchedFile2.getAbsolutePath());
269     }
270     finally {
271       unwatch(subRequest, topRequest);
272       delete(topDir);
273     }
274   }
275
276   public void testDirectoryNonExisting() throws Exception {
277     if (SystemInfo.isLinux) {
278       // todo[r.sh]: fix Linux watcher
279       System.err.println("Ignored: to be fixed on Linux");
280       return;
281     }
282
283     File topDir = createTestDir("top");
284     File subDir = new File(topDir, "subDir");
285     File file = new File(subDir, "file.txt");
286     refresh(topDir);
287
288     LocalFileSystem.WatchRequest request = watch(subDir);
289     try {
290       myAccept = true;
291       assertTrue(subDir.toString(), subDir.mkdir());
292       assertEvent(VFileCreateEvent.class, subDir.getAbsolutePath());
293       refresh(subDir);
294
295       myAccept = true;
296       FileUtil.writeToFile(file, "new content");
297       assertEvent(VFileCreateEvent.class, file.getAbsolutePath());
298     }
299     finally {
300       unwatch(request);
301       delete(topDir);
302     }
303   }
304
305   public void testDirectoryOverlapping() throws Exception {
306     File topDir = createTestDir("top");
307     File fileInTopDir = createTestFile(topDir, "file1.txt");
308     File subDir = createTestDir(topDir, "sub");
309     File fileInSubDir = createTestFile(subDir, "file2.txt");
310     File sideDir = createTestDir("side");
311     File fileInSideDir = createTestFile(sideDir, "file3.txt");
312     refresh(topDir);
313     refresh(sideDir);
314
315     LocalFileSystem.WatchRequest requestForSubDir = watch(subDir);
316     LocalFileSystem.WatchRequest requestForSideDir = watch(sideDir);
317     try {
318       myAccept = true;
319       FileUtil.writeToFile(fileInTopDir, "new content");
320       FileUtil.writeToFile(fileInSubDir, "new content");
321       FileUtil.writeToFile(fileInSideDir, "new content");
322       assertEvent(VFileContentChangeEvent.class, fileInSubDir.getAbsolutePath(), fileInSideDir.getAbsolutePath());
323
324       LocalFileSystem.WatchRequest requestForTopDir = watch(topDir);
325       try {
326         myAccept = true;
327         FileUtil.writeToFile(fileInTopDir, "newer content");
328         FileUtil.writeToFile(fileInSubDir, "newer content");
329         FileUtil.writeToFile(fileInSideDir, "newer content");
330         assertEvent(VFileContentChangeEvent.class, fileInTopDir.getAbsolutePath(), fileInSubDir.getAbsolutePath(), fileInSideDir.getAbsolutePath());
331       }
332       finally {
333         unwatch(requestForTopDir);
334       }
335
336       myAccept = true;
337       FileUtil.writeToFile(fileInTopDir, "newest content");
338       FileUtil.writeToFile(fileInSubDir, "newest content");
339       FileUtil.writeToFile(fileInSideDir, "newest content");
340       assertEvent(VFileContentChangeEvent.class, fileInSubDir.getAbsolutePath(), fileInSideDir.getAbsolutePath());
341
342       myAccept = true;
343       FileUtil.delete(fileInTopDir);
344       FileUtil.delete(fileInSubDir);
345       FileUtil.delete(fileInSideDir);
346       assertEvent(VFileDeleteEvent.class, fileInTopDir.getAbsolutePath(), fileInSubDir.getAbsolutePath(), fileInSideDir.getAbsolutePath());
347     }
348     finally {
349       unwatch(requestForSubDir, requestForSideDir);
350       delete(topDir);
351     }
352   }
353
354 /*
355   public void testSymlinkAboveWatchRoot() throws Exception {
356     final File topDir = FileUtil.createTempDirectory("top.", null);
357     final File topLink = IoTestUtil.createTempLink(topDir.getAbsolutePath(), "link");
358     final File subDir = FileUtil.createTempDirectory(topDir, "sub.", null);
359     final File file = FileUtil.createTempFile(subDir, "test.", ".txt");
360     final File fileLink = new File(new File(topLink, subDir.getName()), file.getName());
361     refresh(topDir);
362     refresh(topLink);
363
364     final LocalFileSystem.WatchRequest request = watch(topLink);
365     try {
366       myAccept = true;
367       FileUtil.writeToFile(file, "new content");
368       assertEvent(VFileContentChangeEvent.class, fileLink.getAbsolutePath());
369
370       myAccept = true;
371       FileUtil.delete(file);
372       assertEvent(VFileDeleteEvent.class, fileLink.getAbsolutePath());
373
374       myAccept = true;
375       FileUtil.writeToFile(file, "re-creation");
376       assertEvent(VFileCreateEvent.class, fileLink.getAbsolutePath());
377     }
378     finally {
379       myFileSystem.removeWatchedRoot(request);
380       delete(topLink);
381       delete(topDir);
382     }
383   }
384
385   public void testSymlinkBelowWatchRoot() throws Exception {
386     final File targetDir = FileUtil.createTempDirectory("top.", null);
387     final File file = FileUtil.createTempFile(targetDir, "test.", ".txt");
388     final File linkDir = FileUtil.createTempDirectory("link.", null);
389     final File link = new File(linkDir, "link");
390     IoTestUtil.createTempLink(targetDir.getAbsolutePath(), link.getAbsolutePath());
391     final File fileLink = new File(link, file.getName());
392     refresh(targetDir);
393     refresh(linkDir);
394
395     final LocalFileSystem.WatchRequest request = watch(linkDir);
396     try {
397       myAccept = true;
398       FileUtil.writeToFile(file, "new content");
399       assertEvent(VFileContentChangeEvent.class, fileLink.getAbsolutePath());
400
401       myAccept = true;
402       FileUtil.delete(file);
403       assertEvent(VFileDeleteEvent.class, fileLink.getAbsolutePath());
404
405       myAccept = true;
406       FileUtil.writeToFile(file, "re-creation");
407       assertEvent(VFileCreateEvent.class, fileLink.getAbsolutePath());
408     }
409     finally {
410       myFileSystem.removeWatchedRoot(request);
411       delete(linkDir);
412       delete(targetDir);
413     }
414   }
415 */
416
417   public void testSubst() throws Exception {
418     if (!SystemInfo.isWindows) {
419       System.err.println("Ignored: Windows required");
420       return;
421     }
422
423     File targetDir = createTestDir("top");
424     File subDir = createTestDir(targetDir, "sub");
425     File file = createTestFile(subDir, "test.txt");
426     File rootFile = createSubst(targetDir.getAbsolutePath());
427     VirtualDirectoryImpl.allowRootAccess(rootFile.getPath());
428     VirtualFile vfsRoot = myFileSystem.findFileByIoFile(rootFile);
429
430     try {
431       assertNotNull(rootFile.getPath(), vfsRoot);
432       File substDir = new File(rootFile, subDir.getName());
433       File substFile = new File(substDir, file.getName());
434       refresh(targetDir);
435       refresh(substDir);
436
437       LocalFileSystem.WatchRequest request = watch(substDir);
438       try {
439         myAccept = true;
440         FileUtil.writeToFile(file, "new content");
441         assertEvent(VFileContentChangeEvent.class, substFile.getAbsolutePath());
442
443         LocalFileSystem.WatchRequest request2 = watch(targetDir);
444         try {
445           myAccept = true;
446           FileUtil.delete(file);
447           assertEvent(VFileDeleteEvent.class, file.getAbsolutePath(), substFile.getAbsolutePath());
448         }
449         finally {
450           unwatch(request2);
451         }
452
453         myAccept = true;
454         FileUtil.writeToFile(file, "re-creation");
455         assertEvent(VFileCreateEvent.class, substFile.getAbsolutePath());
456       }
457       finally {
458         unwatch(request);
459       }
460     }
461     finally {
462       delete(targetDir);
463       IoTestUtil.deleteSubst(rootFile.getPath());
464       if (vfsRoot != null) {
465         ((NewVirtualFile)vfsRoot).markDirty();
466         myFileSystem.refresh(false);
467       }
468       VirtualDirectoryImpl.disallowRootAccess(rootFile.getPath());
469     }
470   }
471
472   public void testDirectoryRecreation() throws Exception {
473     File rootDir = createTestDir("root");
474     File topDir = createTestDir(rootDir, "top");
475     File file1 = createTestFile(topDir, "file1.txt", "abc");
476     File file2 = createTestFile(topDir, "file2.txt", "123");
477     refresh(topDir);
478
479     LocalFileSystem.WatchRequest request = watch(rootDir);
480     try {
481       myAccept = true;
482       assertTrue(FileUtil.delete(topDir));
483       assertTrue(topDir.mkdir());
484       TimeoutUtil.sleep(100);
485       assertTrue(file1.createNewFile());
486       assertTrue(file2.createNewFile());
487       assertEvent(VFileContentChangeEvent.class, file1.getPath(), file2.getPath());
488     }
489     finally {
490       unwatch(request);
491       delete(topDir);
492     }
493   }
494
495   public void testWatchRootRecreation() throws Exception {
496     if (SystemInfo.isLinux) {
497       // todo[r.sh]: fix Linux watcher
498       System.err.println("Ignored: to be fixed on Linux");
499       return;
500     }
501
502     File rootDir = createTestDir("root");
503     File file1 = createTestFile(rootDir, "file1.txt", "abc");
504     File file2 = createTestFile(rootDir, "file2.txt", "123");
505     refresh(rootDir);
506
507     LocalFileSystem.WatchRequest request = watch(rootDir);
508     try {
509       myAccept = true;
510       assertTrue(FileUtil.delete(rootDir));
511       assertTrue(rootDir.mkdir());
512       assertTrue(file1.createNewFile());
513       assertTrue(file2.createNewFile());
514       assertEvent(VFileContentChangeEvent.class, file1.getPath(), file2.getPath());
515     }
516     finally {
517       unwatch(request);
518       delete(rootDir);
519     }
520   }
521
522   public void testWatchRootRenameRemove() throws Exception {
523     if (SystemInfo.isLinux) {
524       // todo[r.sh]: fix Linux watcher
525       System.err.println("Ignored: to be fixed on Linux");
526       return;
527     }
528
529     File topDir = createTestDir("top");
530     File rootDir = createTestDir(topDir, "root");
531     File rootDir2 = new File(topDir, "_" + rootDir.getName());
532     refresh(topDir);
533
534     LocalFileSystem.WatchRequest request = watch(rootDir);
535     try {
536       myAccept = true;
537       assertTrue(rootDir.renameTo(rootDir2));
538       assertEvent(VFileEvent.class, rootDir.getPath(), rootDir2.getPath());
539
540       myAccept = true;
541       assertTrue(rootDir2.renameTo(rootDir));
542       assertEvent(VFileEvent.class, rootDir.getPath(), rootDir2.getPath());
543
544       myAccept = true;
545       assertTrue(FileUtil.delete(topDir));
546       assertEvent(VFileDeleteEvent.class, topDir.getPath());
547
548       // todo[r.sh] current VFS implementation loses watch root once it's removed; this probably should be fixed
549       myAccept = true;
550       assertTrue(rootDir.mkdirs());
551       assertEvent(VFileCreateEvent.class);
552     }
553     finally {
554       unwatch(request);
555       delete(topDir);
556     }
557   }
558
559   public void testSwitchingToFsRoot() throws Exception {
560     File topDir = createTestDir("top");
561     File rootDir = createTestDir(topDir, "root");
562     File file1 = createTestFile(topDir, "1.txt");
563     File file2 = createTestFile(rootDir, "2.txt");
564     refresh(topDir);
565
566     File fsRoot = new File(SystemInfo.isUnix ? "/" : topDir.getPath().substring(0, topDir.getPath().indexOf(File.separatorChar)) + "\\");
567     assertTrue("can't guess root of " + topDir, fsRoot.exists());
568
569     LocalFileSystem.WatchRequest request = watch(rootDir);
570     try {
571       myAccept = true;
572       FileUtil.writeToFile(file1, "abc");
573       FileUtil.writeToFile(file2, "abc");
574       assertEvent(VFileContentChangeEvent.class, file2.getPath());
575
576       LocalFileSystem.WatchRequest rootRequest = watch(fsRoot);
577       try {
578         myTimeout = 10 * INTER_RESPONSE_DELAY;
579         myAccept = true;
580         FileUtil.writeToFile(file1, "12345");
581         FileUtil.writeToFile(file2, "12345");
582         assertEvent(VFileContentChangeEvent.class, file1.getPath(), file2.getPath());
583         myTimeout = NATIVE_PROCESS_DELAY;
584       }
585       finally {
586         unwatch(rootRequest);
587       }
588
589       myAccept = true;
590       FileUtil.writeToFile(file1, "");
591       FileUtil.writeToFile(file2, "");
592       assertEvent(VFileContentChangeEvent.class, file2.getPath());
593     }
594     finally {
595       unwatch(request);
596     }
597
598     myTimeout = 10 * INTER_RESPONSE_DELAY;
599     myAccept = true;
600     FileUtil.writeToFile(file1, "xyz");
601     FileUtil.writeToFile(file2, "xyz");
602     assertEvent(VFileEvent.class);
603     myTimeout = NATIVE_PROCESS_DELAY;
604   }
605
606   public void testLineBreaksInName() throws Exception {
607     if (!SystemInfo.isUnix) {
608       System.err.println("Ignored: Unix required");
609       return;
610     }
611
612     File topDir = createTestDir("topDir");
613     File testDir = createTestDir(topDir, "weird\ndir\nname");
614     File testFile = createTestFile(testDir, "weird\nfile\nname");
615     refresh(topDir);
616
617     LocalFileSystem.WatchRequest request = watch(topDir);
618     try {
619       myAccept = true;
620       FileUtil.writeToFile(testFile, "abc");
621       assertEvent(VFileContentChangeEvent.class, testFile.getPath());
622     }
623     finally {
624       unwatch(request);
625     }
626   }
627
628
629   @NotNull
630   private LocalFileSystem.WatchRequest watch(File watchFile) {
631     return watch(watchFile, true);
632   }
633
634   @NotNull
635   private LocalFileSystem.WatchRequest watch(final File watchFile, final boolean recursive) {
636     final Ref<LocalFileSystem.WatchRequest> request = Ref.create();
637     getEvents("events to add watch " + watchFile, new Runnable() {
638       @Override
639       public void run() {
640         request.set(myFileSystem.addRootToWatch(watchFile.getAbsolutePath(), recursive));
641       }
642     });
643     assertFalse(request.isNull());
644     assertFalse(myWatcher.isSettingRoots());
645     return request.get();
646   }
647
648   private void unwatch(final LocalFileSystem.WatchRequest... requests) {
649     getEvents("events to stop watching", new Runnable() {
650       @Override
651       public void run() {
652         myFileSystem.removeWatchedRoots(Arrays.asList(requests));
653       }
654     });
655   }
656
657   private VirtualFile refresh(File file) {
658     VirtualFile vFile = myFileSystem.refreshAndFindFileByIoFile(file);
659     assertNotNull(file.toString(), vFile);
660     VfsUtilCore.visitChildrenRecursively(vFile, new VirtualFileVisitor() {
661       @Override
662       public boolean visitFile(@NotNull VirtualFile file) {
663         file.getChildren();
664         return true;
665       }
666     });
667     return vFile;
668   }
669
670   private void delete(File file) throws IOException {
671     VirtualFile vFile = myFileSystem.findFileByIoFile(file);
672     if (vFile != null) {
673       AccessToken token = ApplicationManager.getApplication().acquireWriteActionLock(getClass());
674       try {
675         vFile.delete(this);
676       }
677       finally {
678         token.finish();
679       }
680     }
681     if (file.exists()) {
682       FileUtil.delete(file);
683     }
684   }
685
686   private List<VFileEvent> getEvents(String msg, @Nullable Runnable action) {
687     LOG.debug("** waiting for " + msg);
688     myAccept = true;
689
690     if (action != null) {
691       action.run();
692     }
693
694     int timeout = myTimeout;
695     try {
696       synchronized (myWaiter) {
697         //noinspection WaitNotInLoop
698         myWaiter.wait(timeout);
699       }
700     }
701     catch (InterruptedException e) {
702       LOG.warn(e);
703     }
704
705     LOG.debug("** waited for " + timeout);
706     myFileSystem.refresh(false);
707
708     ArrayList<VFileEvent> result;
709     synchronized (myEvents) {
710       result = new ArrayList<VFileEvent>(myEvents);
711       myEvents.clear();
712     }
713     LOG.debug("** events: " + result.size());
714     return result;
715   }
716
717   private void assertEvent(Class<? extends VFileEvent> type, String... paths) {
718     List<VFileEvent> events = getEvents(type.getSimpleName(), null);
719     assertEquals(events.toString(), paths.length, events.size());
720
721     Set<String> pathSet = ContainerUtil.map2Set(paths, new Function<String, String>() {
722       @Override
723       public String fun(final String path) {
724         return FileUtil.toSystemIndependentName(path);
725       }
726     });
727
728     for (VFileEvent event : events) {
729       assertTrue(event.toString(), type.isInstance(event));
730       VirtualFile eventFile = event.getFile();
731       assertNotNull(event.toString(), eventFile);
732       assertTrue(eventFile + " not in " + Arrays.toString(paths), pathSet.remove(eventFile.getPath()));
733     }
734   }
735 }