package org.netbeans.gradle.project.util; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.TimeUnit; import org.jtrim.cancel.Cancellation; import org.jtrim.concurrent.SyncTaskExecutor; import org.jtrim.concurrent.WaitableSignal; import org.jtrim.event.ListenerRef; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; public class FileSystemWatcherTest { private static final long TIMEOUT_SEC = 5; @Rule public final TemporaryFolder tmpFolder = new TemporaryFolder(); private FileSystemWatcher watcher; @Before public void setupTest() { watcher = new FileSystemWatcher(FileSystems.getDefault(), SyncTaskExecutor.getSimpleExecutor()); } private void testModifications(WatchSetup setup, Modification... modifications) throws IOException { Path root = tmpFolder.newFolder("root").toPath(); TestListener listener = new TestListener(); Path watchedDir = setup.setupWatch(root); ListenerRef listenerRef = watcher.watchPath(watchedDir, listener); try { for (Modification modification: modifications) { listener.reset(); modification.doModification(watchedDir); listener.assertCalled(); } } finally { listenerRef.unregister(); } watcher.waitFor(TIMEOUT_SEC, TimeUnit.SECONDS); } private static Modification createDirAction() { return new Modification() { @Override public void doModification(Path watchedDir) throws IOException { Files.createDirectory(watchedDir); } }; } private static Modification createMultipleDirsAction() { return new Modification() { @Override public void doModification(Path watchedDir) throws IOException { Files.createDirectories(watchedDir); } }; } private static Modification deleteDirAction() { return new Modification() { @Override public void doModification(Path watchedDir) throws IOException { Files.delete(watchedDir); } }; } @Test public void testMultiActionLevel1() throws IOException { testModifications(new WatchSetup() { @Override public Path setupWatch(Path root) throws IOException { return root.resolve("subdir"); } }, createDirAction(), deleteDirAction(), createDirAction()); } @Test public void testCreateLevel1() throws IOException { testModifications(new WatchSetup() { @Override public Path setupWatch(Path root) throws IOException { return root.resolve("subdir"); } }, createDirAction()); } @Test public void testDeleteLevel1() throws IOException { testModifications(new WatchSetup() { @Override public Path setupWatch(Path root) throws IOException { Path watchedDir = root.resolve("subdir"); Files.createDirectory(watchedDir); return watchedDir; } }, deleteDirAction()); } @Test public void testCreateLevel2() throws IOException { testModifications(new WatchSetup() { @Override public Path setupWatch(Path root) throws IOException { return root.resolve("subdir").resolve("subdir2"); } }, createMultipleDirsAction()); } @Test public void testDeleteLevel2() throws IOException { testModifications(new WatchSetup() { @Override public Path setupWatch(Path root) throws IOException { Path watchedDir = root.resolve("subdir").resolve("subdir2"); Files.createDirectories(watchedDir); return watchedDir; } }, deleteDirAction()); } private static final class TestListener implements Runnable { private WaitableSignal signal; public TestListener() { this.signal = new WaitableSignal(); } public void reset() { signal = new WaitableSignal(); } @Override public void run() { signal.signal(); } public void assertCalled() { if (!signal.tryWaitSignal(Cancellation.UNCANCELABLE_TOKEN, TIMEOUT_SEC, TimeUnit.SECONDS)) { throw new AssertionError("waitCalled: Timeout"); } } } private interface WatchSetup { public Path setupWatch(Path root) throws IOException; } private interface Modification { public void doModification(Path watchedDir) throws IOException; } }