// Copyright © 2011-2014, Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0
package fi.jumi.core.ipc.dirs;
import fi.jumi.core.Timeouts;
import fi.jumi.core.ipc.*;
import fi.jumi.core.util.TestingExecutor;
import org.apache.commons.io.FileUtils;
import org.hamcrest.Matcher;
import org.junit.*;
import org.junit.rules.*;
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import static java.nio.file.StandardWatchEventKinds.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
public class DirectoryObserverTest {
@Rule
public final TemporaryFolder tempDir = new TemporaryFolder();
@Rule
public final Timeout timeout = Timeouts.forUnitTest();
@Rule
public final TestingExecutor executor = new TestingExecutor();
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final BlockingQueue<Path> noticedFiles = new LinkedBlockingQueue<>();
private Path directory;
private DirectoryObserver observer;
private Future<?> observerFuture;
@Before
public void setup() {
directory = tempDir.getRoot().toPath();
observer = new DirectoryObserver(directory, noticedFiles::add);
}
@Test
public void notices_all_existing_files() throws Exception {
tempDir.newFile("existing1");
tempDir.newFile("existing2");
startObserverAsynchronously();
assertThat(takeAtLeast(2, noticedFiles), containsFiles("existing1", "existing2"));
}
@Test
public void notices_new_files_as_they_are_created() throws Exception {
startObserver();
tempDir.newFile("created1");
tempDir.newFile("created2");
assertThat(takeAtLeast(2, noticedFiles), containsFiles("created1", "created2"));
}
@Test
public void notices_new_files_even_when_the_notification_event_is_missed_due_to_an_overflow() throws Exception {
FakeWatchService watchService = new FakeWatchService();
observer = new DirectoryObserver(directory, noticedFiles::add) {
@Override
protected WatchService watchDirectory(Path directory, WatchEvent.Kind<?>... events) {
return watchService;
}
};
startObserver();
tempDir.newFile("created1");
tempDir.newFile("created2");
watchService.publish(
new FakeWatchKey()
.addEvent(ENTRY_CREATE, Paths.get("created1"))
.addEvent(OVERFLOW));
assertThat(takeAtLeast(2, noticedFiles), containsFiles("created1", "created2"));
}
@Test
public void stops_if_the_directory_becomes_inaccessible() throws Exception {
startObserver();
FileUtils.deleteDirectory(directory.toFile());
observerFuture.get(); // should not timeout
}
@Test
public void requires_the_directory_to_exist_before_observing_it() {
Path noSuchDirectory = directory.resolve("no-such-directory");
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Does not exist: " + noSuchDirectory);
new DirectoryObserver(noSuchDirectory, noticedFiles::add);
}
@Test
public void requires_the_path_to_be_a_directory() throws IOException {
Path regularFile = tempDir.newFile("regular-file").toPath();
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Not a directory: " + regularFile);
new DirectoryObserver(regularFile, noticedFiles::add);
}
// helpers
private void startObserver() throws Exception {
tempDir.newFile("existing");
startObserverAsynchronously();
takeAtLeast(1, noticedFiles); // wait for observer to start, skip existing files
}
private void startObserverAsynchronously() {
observerFuture = executor.submit(observer);
}
private static List<Path> takeAtLeast(int count, BlockingQueue<Path> src) throws InterruptedException {
List<Path> taken = new ArrayList<>();
for (int i = 0; i < count; i++) {
taken.add(src.take());
}
Path p;
while ((p = src.poll()) != null) {
taken.add(p);
}
return taken;
}
private Matcher<Iterable<? extends Path>> containsFiles(String... filenames) {
Path[] expected = new Path[filenames.length];
for (int i = 0; i < filenames.length; i++) {
expected[i] = directory.resolve(filenames[i]);
}
return containsInAnyOrder(expected);
}
}