blob: 86f983237e5dcf7f101ca5330782d477e7af0cbe [file] [log] [blame]
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.jimfs;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableList;
import com.google.common.jimfs.AbstractWatchService.Event;
import com.google.common.jimfs.AbstractWatchService.Key;
import com.google.common.util.concurrent.Runnables;
import com.google.common.util.concurrent.Uninterruptibles;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link PollingWatchService}.
*
* @author Colin Decker
*/
@RunWith(JUnit4.class)
public class PollingWatchServiceTest {
private JimfsFileSystem fs;
private PollingWatchService watcher;
@Before
public void setUp() {
fs = (JimfsFileSystem) Jimfs.newFileSystem(Configuration.unix());
watcher =
new PollingWatchService(
fs.getDefaultView(),
fs.getPathService(),
new FileSystemState(Runnables.doNothing()),
4,
MILLISECONDS);
}
@After
public void tearDown() throws IOException {
watcher.close();
fs.close();
watcher = null;
fs = null;
}
@Test
public void testNewWatcher() {
assertThat(watcher.isOpen()).isTrue();
assertThat(watcher.isPolling()).isFalse();
}
@Test
public void testRegister() throws IOException {
Key key = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE));
assertThat(key.isValid()).isTrue();
assertThat(watcher.isPolling()).isTrue();
}
@Test
public void testRegister_fileDoesNotExist() throws IOException {
try {
watcher.register(fs.getPath("/a/b/c"), ImmutableList.of(ENTRY_CREATE));
fail();
} catch (NoSuchFileException expected) {
}
}
@Test
public void testRegister_fileIsNotDirectory() throws IOException {
Path path = fs.getPath("/a.txt");
Files.createFile(path);
try {
watcher.register(path, ImmutableList.of(ENTRY_CREATE));
fail();
} catch (NotDirectoryException expected) {
}
}
@Test
public void testCancellingLastKeyStopsPolling() throws IOException {
Key key = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE));
key.cancel();
assertThat(key.isValid()).isFalse();
assertThat(watcher.isPolling()).isFalse();
Key key2 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE));
Key key3 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_DELETE));
assertThat(watcher.isPolling()).isTrue();
key2.cancel();
assertThat(watcher.isPolling()).isTrue();
key3.cancel();
assertThat(watcher.isPolling()).isFalse();
}
@Test
public void testCloseCancelsAllKeysAndStopsPolling() throws IOException {
Key key1 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE));
Key key2 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_DELETE));
assertThat(key1.isValid()).isTrue();
assertThat(key2.isValid()).isTrue();
assertThat(watcher.isPolling()).isTrue();
watcher.close();
assertThat(key1.isValid()).isFalse();
assertThat(key2.isValid()).isFalse();
assertThat(watcher.isPolling()).isFalse();
}
@Test(timeout = 2000)
public void testWatchForOneEventType() throws IOException, InterruptedException {
JimfsPath path = createDirectory();
watcher.register(path, ImmutableList.of(ENTRY_CREATE));
Files.createFile(path.resolve("foo"));
assertWatcherHasEvents(new Event<>(ENTRY_CREATE, 1, fs.getPath("foo")));
Files.createFile(path.resolve("bar"));
Files.createFile(path.resolve("baz"));
assertWatcherHasEvents(
new Event<>(ENTRY_CREATE, 1, fs.getPath("bar")),
new Event<>(ENTRY_CREATE, 1, fs.getPath("baz")));
}
@Test(timeout = 2000)
public void testWatchForMultipleEventTypes() throws IOException, InterruptedException {
JimfsPath path = createDirectory();
watcher.register(path, ImmutableList.of(ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY));
Files.createDirectory(path.resolve("foo"));
Files.createFile(path.resolve("bar"));
assertWatcherHasEvents(
new Event<>(ENTRY_CREATE, 1, fs.getPath("bar")),
new Event<>(ENTRY_CREATE, 1, fs.getPath("foo")));
Files.createFile(path.resolve("baz"));
Files.delete(path.resolve("bar"));
Files.createFile(path.resolve("foo/bar"));
assertWatcherHasEvents(
new Event<>(ENTRY_CREATE, 1, fs.getPath("baz")),
new Event<>(ENTRY_DELETE, 1, fs.getPath("bar")),
new Event<>(ENTRY_MODIFY, 1, fs.getPath("foo")));
Files.delete(path.resolve("foo/bar"));
ensureTimeToPoll(); // watcher polls, seeing modification, then polls again, seeing delete
Files.delete(path.resolve("foo"));
assertWatcherHasEvents(
new Event<>(ENTRY_MODIFY, 1, fs.getPath("foo")),
new Event<>(ENTRY_DELETE, 1, fs.getPath("foo")));
Files.createDirectories(path.resolve("foo/bar"));
// polling here may either see just the creation of foo, or may first see the creation of foo
// and then the creation of foo/bar (modification of foo) since those don't happen atomically
assertWatcherHasEvents(
ImmutableList.<WatchEvent<?>>of(new Event<>(ENTRY_CREATE, 1, fs.getPath("foo"))),
// or
ImmutableList.<WatchEvent<?>>of(
new Event<>(ENTRY_CREATE, 1, fs.getPath("foo")),
new Event<>(ENTRY_MODIFY, 1, fs.getPath("foo"))));
Files.delete(path.resolve("foo/bar"));
Files.delete(path.resolve("foo"));
// polling here may either just see the deletion of foo, or may first see the deletion of bar
// (modification of foo) and then the deletion of foo
assertWatcherHasEvents(
ImmutableList.<WatchEvent<?>>of(new Event<>(ENTRY_DELETE, 1, fs.getPath("foo"))),
// or
ImmutableList.<WatchEvent<?>>of(
new Event<>(ENTRY_MODIFY, 1, fs.getPath("foo")),
new Event<>(ENTRY_DELETE, 1, fs.getPath("foo"))));
}
private void assertWatcherHasEvents(WatchEvent<?>... events) throws InterruptedException {
assertWatcherHasEvents(Arrays.asList(events), ImmutableList.<WatchEvent<?>>of());
}
private void assertWatcherHasEvents(List<WatchEvent<?>> expected, List<WatchEvent<?>> alternate)
throws InterruptedException {
ensureTimeToPoll(); // otherwise we could read 1 event but not all the events we're expecting
WatchKey key = watcher.take();
List<WatchEvent<?>> keyEvents = key.pollEvents();
if (keyEvents.size() == expected.size() || alternate.isEmpty()) {
assertThat(keyEvents).containsExactlyElementsIn(expected);
} else {
assertThat(keyEvents).containsExactlyElementsIn(alternate);
}
key.reset();
}
private static void ensureTimeToPoll() {
Uninterruptibles.sleepUninterruptibly(40, MILLISECONDS);
}
private JimfsPath createDirectory() throws IOException {
JimfsPath path = fs.getPath("/" + UUID.randomUUID().toString());
Files.createDirectory(path);
return path;
}
}