package org.jooby.filewatcher; import static org.easymock.EasyMock.eq; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.isA; import java.io.IOException; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.Paths; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchEvent.Kind; import java.nio.file.WatchEvent.Modifier; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.function.Function; import org.easymock.IExpectationSetters; import org.jooby.Env; import org.jooby.test.MockUnit; import org.jooby.test.MockUnit.Block; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.inject.Injector; import javaslang.control.Try.CheckedRunnable; @RunWith(PowerMockRunner.class) @PrepareForTest({FileMonitor.class, Thread.class, Files.class, Executors.class }) public class FileMonitorTest { private Block newThread = unit -> { unit.mockStatic(Executors.class); ExecutorService executor = unit.mock(ExecutorService.class); expect(Executors.newSingleThreadExecutor(unit.capture(ThreadFactory.class))) .andReturn(executor); executor.execute(unit.capture(Runnable.class)); unit.registerMock(ExecutorService.class, executor); Env env = unit.get(Env.class); expect(env.onStop(unit.capture(CheckedRunnable.class))).andReturn(env); Thread thread = unit.constructor(Thread.class) .args(Runnable.class, String.class) .build(isA(Runnable.class), eq("file-watcher")); thread.setDaemon(true); }; private Block takeInterrupt = unit -> { WatchService watcher = unit.get(WatchService.class); expect(watcher.take()).andThrow(new InterruptedException()); }; private Block take = unit -> { WatchService watcher = unit.get(WatchService.class); expect(watcher.take()).andReturn(unit.get(WatchKey.class)); }; private Block takeIgnore = unit -> { WatchService watcher = unit.get(WatchService.class); expect(watcher.take()).andReturn(unit.mock(WatchKey.class)); }; @Test public void newFileMonitor() throws Exception { new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class) .expect(newThread) .expect(unit -> { ExecutorService executor = unit.get(ExecutorService.class); executor.shutdown(); }) .run(unit -> { FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class), unit.get(WatchService.class), ImmutableSet.of(unit.get(FileEventOptions.class))); unit.captured(ThreadFactory.class).get(0).newThread(monitor); unit.captured(CheckedRunnable.class).get(0).run(); ; }); } @SuppressWarnings("unchecked") @Test public void registerTreeRecursive() throws Exception { new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class, WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class) .expect(newThread) .expect(registerTree(true, false)) .expect(takeInterrupt) .run(unit -> { FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class), unit.get(WatchService.class), ImmutableSet.of(unit.get(FileEventOptions.class))); unit.captured(ThreadFactory.class).get(0).newThread(monitor); }, unit -> { unit.captured(Runnable.class).get(0).run(); unit.captured(FileVisitor.class).get(0).preVisitDirectory(unit.get(Path.class), null); }); } @Test public void registerTree() throws Exception { new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class, WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class) .expect(newThread) .expect(registerTree(false, false)) .expect(takeInterrupt) .run(unit -> { FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class), unit.get(WatchService.class), ImmutableSet.of(unit.get(FileEventOptions.class))); unit.captured(ThreadFactory.class).get(0).newThread(monitor); }, unit -> { unit.captured(Runnable.class).get(0).run(); }); } @Test public void registerTreeErr() throws Exception { new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class, WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class) .expect(newThread) .expect(registerTree(false, true)) .expect(takeInterrupt) .run(unit -> { FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class), unit.get(WatchService.class), ImmutableSet.of(unit.get(FileEventOptions.class))); unit.captured(ThreadFactory.class).get(0).newThread(monitor); }, unit -> { unit.captured(Runnable.class).get(0).run(); }); } @Test public void pollIgnoreMissing() throws Exception { new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class, WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class) .expect(newThread) .expect(registerTree(false, false)) .expect(takeIgnore) .expect(takeInterrupt) .run(unit -> { FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class), unit.get(WatchService.class), ImmutableSet.of(unit.get(FileEventOptions.class))); unit.captured(ThreadFactory.class).get(0).newThread(monitor); }, unit -> { unit.captured(Runnable.class).get(0).run(); }); } @Test public void pollEvents() throws Exception { Path path = Paths.get("target/foo.txt"); new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class, WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class, WatchEvent.class, PathMatcher.class, FileEventHandler.class) .expect(newThread) .expect(registerTree(false, false)) .expect(take) .expect(poll(StandardWatchEventKinds.ENTRY_MODIFY, path)) .expect(filter(true)) .expect(handler(StandardWatchEventKinds.ENTRY_MODIFY, false)) .expect(recursive(false, false)) .expect(reset(true)) .expect(takeInterrupt) .run(unit -> { FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class), unit.get(WatchService.class), ImmutableSet.of(unit.get(FileEventOptions.class))); unit.captured(ThreadFactory.class).get(0).newThread(monitor); }, unit -> { unit.captured(Runnable.class).get(0).run(); }); } @Test public void pollEventsInvalid() throws Exception { Path path = Paths.get("target/foo.txt"); new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class, WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class, WatchEvent.class, PathMatcher.class, FileEventHandler.class) .expect(newThread) .expect(registerTree(false, false)) .expect(take) .expect(poll(StandardWatchEventKinds.ENTRY_MODIFY, path)) .expect(filter(true)) .expect(handler(StandardWatchEventKinds.ENTRY_MODIFY, false)) .expect(recursive(false, false)) .expect(reset(false)) .run(unit -> { FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class), unit.get(WatchService.class), ImmutableSet.of(unit.get(FileEventOptions.class))); unit.captured(ThreadFactory.class).get(0).newThread(monitor); }, unit -> { unit.captured(Runnable.class).get(0).run(); }); } @Test public void pollEventsRecursive() throws Exception { Path path = Paths.get("target/foo.txt"); new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class, WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class, WatchEvent.class, PathMatcher.class, FileEventHandler.class) .expect(newThread) .expect(registerTree(false, false)) .expect(take) .expect(poll(StandardWatchEventKinds.ENTRY_CREATE, path)) .expect(filter(true)) .expect(handler(StandardWatchEventKinds.ENTRY_CREATE, false)) .expect(recursive(true, false)) .expect(reset(true)) .expect(takeInterrupt) .run(unit -> { FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class), unit.get(WatchService.class), ImmutableSet.of(unit.first(FileEventOptions.class))); unit.captured(ThreadFactory.class).get(0).newThread(monitor); }, unit -> { unit.captured(Runnable.class).get(0).run(); }); } @Test public void pollEventsRecursiveErr() throws Exception { Path path = Paths.get("target/foo.txt"); new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class, WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class, WatchEvent.class, PathMatcher.class, FileEventHandler.class) .expect(newThread) .expect(registerTree(false, false)) .expect(take) .expect(poll(StandardWatchEventKinds.ENTRY_CREATE, path)) .expect(filter(true)) .expect(handler(StandardWatchEventKinds.ENTRY_CREATE, false)) .expect(recursive(true, true)) .expect(reset(true)) .expect(takeInterrupt) .run(unit -> { FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class), unit.get(WatchService.class), ImmutableSet.of(unit.first(FileEventOptions.class))); unit.captured(ThreadFactory.class).get(0).newThread(monitor); }, unit -> { unit.captured(Runnable.class).get(0).run(); }); } @Test public void pollEventWithHandleErr() throws Exception { Path path = Paths.get("target/foo.txt"); new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class, WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class, WatchEvent.class, PathMatcher.class, FileEventHandler.class) .expect(newThread) .expect(registerTree(false, false)) .expect(take) .expect(poll(StandardWatchEventKinds.ENTRY_MODIFY, path)) .expect(filter(true)) .expect(handler(StandardWatchEventKinds.ENTRY_MODIFY, true)) .expect(recursive(false, false)) .expect(reset(true)) .expect(takeInterrupt) .run(unit -> { FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class), unit.get(WatchService.class), ImmutableSet.of(unit.get(FileEventOptions.class))); unit.captured(ThreadFactory.class).get(0).newThread(monitor); }, unit -> { unit.captured(Runnable.class).get(0).run(); }); } @Test public void pollEventsNoMatches() throws Exception { Path path = Paths.get("target/foo.txt"); new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class, WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class, WatchEvent.class, PathMatcher.class, FileEventHandler.class) .expect(newThread) .expect(registerTree(false, false)) .expect(take) .expect(poll(StandardWatchEventKinds.ENTRY_MODIFY, path)) .expect(filter(false)) .expect(recursive(false, false)) .expect(reset(true)) .expect(takeInterrupt) .run(unit -> { FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class), unit.get(WatchService.class), ImmutableSet.of(unit.get(FileEventOptions.class))); unit.captured(ThreadFactory.class).get(0).newThread(monitor); }, unit -> { unit.captured(Runnable.class).get(0).run(); }); } private Block reset(final boolean valid) { return unit -> { WatchKey key = unit.get(WatchKey.class); expect(key.reset()).andReturn(valid); }; } @SuppressWarnings("rawtypes") private Block poll(final Kind kind, final Path path) { return unit -> { WatchEvent event = unit.get(WatchEvent.class); expect(event.kind()).andReturn(kind); expect(event.context()).andReturn(path); Path source = unit.get(Path.class); Path resolved = unit.mock(Path.class); unit.registerMock(Path.class, resolved); expect(source.resolve(path)).andReturn(resolved); WatchEvent overflow = unit.mock(WatchEvent.class); expect(overflow.kind()).andReturn(StandardWatchEventKinds.OVERFLOW); expect(overflow.context()).andReturn(path); WatchKey key = unit.get(WatchKey.class); expect(key.pollEvents()).andReturn(ImmutableList.of(event, overflow)); }; } @SuppressWarnings("unchecked") private Block handler(final Kind<Path> kind, final boolean err) { return unit -> { FileEventHandler handler = unit.get(FileEventHandler.class); FileEventOptions options = unit.get(FileEventOptions.class); expect(options.handler(isA(Function.class))).andReturn(handler); handler.handle(kind, unit.get(Path.class)); if (err) { expectLastCall().andThrow(new IOException("intentional err")); } }; } private Block filter(final boolean matches) { return unit -> { Path resolved = unit.get(Path.class); PathMatcher filter = unit.get(PathMatcher.class); expect(filter.matches(resolved)).andReturn(matches); FileEventOptions options = unit.get(FileEventOptions.class); expect(options.filter()).andReturn(filter); }; } private Block registerTree(final boolean recursive, final boolean err) { return unit -> { register(unit, recursive, err); }; } @SuppressWarnings({"rawtypes", "unchecked" }) private void register(final MockUnit unit, final boolean recursive, final boolean err) throws IOException { FileEventOptions options = unit.get(FileEventOptions.class); expect(options.recursive()).andReturn(recursive); Path path = unit.get(Path.class); expect(options.path()).andReturn(path).times(1, 2); Kind kind = unit.get(WatchEvent.Kind.class); Kind[] kinds = {kind }; expect(options.kinds()).andReturn(kinds); Modifier mod = unit.get(WatchEvent.Modifier.class); expect(options.modifier()).andReturn(mod); if (recursive) { unit.mockStatic(Files.class); expect(Files.walkFileTree(eq(path), unit.capture(FileVisitor.class))).andReturn(path); } WatchKey key = unit.get(WatchKey.class); IExpectationSetters<WatchKey> register = expect( path.register(unit.get(WatchService.class), kinds, mod)); if (err) { register.andThrow(new IOException("intentional error")); } else { register.andReturn(key); } } @SuppressWarnings("rawtypes") private Block recursive(final boolean recursive, final boolean err) { return unit -> { FileEventOptions options = unit.get(FileEventOptions.class); expect(options.recursive()).andReturn(recursive); if (recursive) { unit.mockStatic(Files.class); expect(Files.isDirectory(unit.get(Path.class))).andReturn(true); expect(options.recursive()).andReturn(false); /// Path path = unit.get(Path.class); Kind kind = unit.get(WatchEvent.Kind.class); Kind[] kinds = {kind }; expect(options.kinds()).andReturn(kinds); Modifier mod = unit.get(WatchEvent.Modifier.class); expect(options.modifier()).andReturn(mod); WatchKey key = unit.get(WatchKey.class); IExpectationSetters<WatchKey> register = expect( path.register(unit.get(WatchService.class), kinds, mod)); if (err) { register.andThrow(new IOException("intentional error")); } else { register.andReturn(key); } } }; } }