package restx.common.watch; import static org.assertj.core.api.Assertions.assertThat; import com.google.common.eventbus.EventBus; import org.junit.Test; import java.nio.file.Paths; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class FileWatchEventCoalescorTest { /** * A utility object for the test, that wrap a {@link FileWatchEventCoalescor} * and keep reference on events that are scheduled to be posted. It also permits * to clean the event queue manually. */ static class TestFileWatchEventCoalescor { final List<FileWatchEventCoalescor.EventReference> scheduledEvents = new ArrayList<>(); FileWatchEventCoalescor coalescor = new FileWatchEventCoalescor(new EventBus(), 50) { // event bus and coalesce time are ignored @Override void schedulePost(EventReference event) { scheduledEvents.add(event); } }; FileWatchEvent post(String filePath, WatchEvent.Kind<?> kind) { FileWatchEvent event = FileWatchEvent.newInstance(Paths.get("/"), Paths.get("/"), Paths.get(filePath), kind, 1); coalescor.post(event); return event; } /* remove noisy event, and return true if it manage to find it */ boolean removeNoise(FileWatchEvent noise) { for (Iterator<FileWatchEventCoalescor.EventReference> it = scheduledEvents.iterator(); it.hasNext(); ) { FileWatchEventCoalescor.EventReference ref = it.next(); if (ref.isPresent() && ref.getReference() == noise) { coalescor.dequeue(ref.getKey(), ref); it.remove(); return true; } } return false; } void clear() { coalescor.clear(); } } /* No merges here, just very basic events, on separate files */ @Test public void should_send_events() { TestFileWatchEventCoalescor watchEventCoalescor = new TestFileWatchEventCoalescor(); watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_CREATE); watchEventCoalescor.post("tmp/bar", StandardWatchEventKinds.ENTRY_DELETE); watchEventCoalescor.post("tmp/test", StandardWatchEventKinds.ENTRY_MODIFY); watchEventCoalescor.post("tmp/another_file.txt", StandardWatchEventKinds.ENTRY_CREATE); assertThat(watchEventCoalescor.scheduledEvents).extracting("reference").extracting("path") .containsOnly( Paths.get("tmp/foo"), Paths.get("tmp/bar"), Paths.get("tmp/test"), Paths.get("tmp/another_file.txt") ); } @Test public void should_merge_duplicate_events() { TestFileWatchEventCoalescor watchEventCoalescor = new TestFileWatchEventCoalescor(); FileWatchEvent noise; // try with some create events watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_CREATE); watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_CREATE); noise = watchEventCoalescor.post("tmp/bar", StandardWatchEventKinds.ENTRY_CREATE); // just to add some noise watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_CREATE); assertThat(watchEventCoalescor.removeNoise(noise)).isTrue(); assertThat(watchEventCoalescor.scheduledEvents).hasSize(1); FileWatchEvent event = watchEventCoalescor.scheduledEvents.get(0).getReference(); assertThat(event.getPath()).isEqualTo(Paths.get("tmp/foo")); assertThat(event.getKind()).isEqualTo(StandardWatchEventKinds.ENTRY_CREATE); watchEventCoalescor.clear(); // try with some delete events watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_DELETE); watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_DELETE); noise = watchEventCoalescor.post("tmp/bar", StandardWatchEventKinds.ENTRY_CREATE); // just to add some noise watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_DELETE); assertThat(watchEventCoalescor.removeNoise(noise)).isTrue(); // remove noisy event assertThat(watchEventCoalescor.scheduledEvents).hasSize(2); event = watchEventCoalescor.scheduledEvents.get(1).getReference(); assertThat(event.getPath()).isEqualTo(Paths.get("tmp/foo")); assertThat(event.getKind()).isEqualTo(StandardWatchEventKinds.ENTRY_DELETE); watchEventCoalescor.clear(); // try with some delete modify watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_MODIFY); noise = watchEventCoalescor.post("tmp/bar", StandardWatchEventKinds.ENTRY_CREATE); // just to add some noise watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_MODIFY); watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_MODIFY); assertThat(watchEventCoalescor.removeNoise(noise)).isTrue(); // remove noisy event assertThat(watchEventCoalescor.scheduledEvents).hasSize(3); event = watchEventCoalescor.scheduledEvents.get(2).getReference(); assertThat(event.getPath()).isEqualTo(Paths.get("tmp/foo")); assertThat(event.getKind()).isEqualTo(StandardWatchEventKinds.ENTRY_MODIFY); watchEventCoalescor.clear(); } @Test public void should_merge_delete_with_create_events() { TestFileWatchEventCoalescor watchEventCoalescor = new TestFileWatchEventCoalescor(); watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_DELETE); FileWatchEvent noise = watchEventCoalescor.post("tmp/bar", StandardWatchEventKinds.ENTRY_CREATE);// just to add some noise watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_CREATE); assertThat(watchEventCoalescor.removeNoise(noise)).isTrue(); // remove noisy event assertThat(watchEventCoalescor.scheduledEvents).hasSize(1); FileWatchEvent event = watchEventCoalescor.scheduledEvents.get(0).getReference(); assertThat(event.getPath()).isEqualTo(Paths.get("tmp/foo")); assertThat(event.getKind()).isEqualTo(StandardWatchEventKinds.ENTRY_MODIFY); } @Test public void should_merge_create_with_modify_events() { TestFileWatchEventCoalescor watchEventCoalescor = new TestFileWatchEventCoalescor(); watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_CREATE); FileWatchEvent noise = watchEventCoalescor.post("tmp/bar", StandardWatchEventKinds.ENTRY_CREATE);// just to add some noise watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_MODIFY); assertThat(watchEventCoalescor.removeNoise(noise)).isTrue(); // remove noisy event assertThat(watchEventCoalescor.scheduledEvents).hasSize(1); FileWatchEvent event = watchEventCoalescor.scheduledEvents.get(0).getReference(); assertThat(event.getPath()).isEqualTo(Paths.get("tmp/foo")); assertThat(event.getKind()).isEqualTo(StandardWatchEventKinds.ENTRY_CREATE); } @Test public void should_remove_consecutive_create_and_delete_events() { TestFileWatchEventCoalescor watchEventCoalescor = new TestFileWatchEventCoalescor(); watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_CREATE); FileWatchEvent noise = watchEventCoalescor.post("tmp/bar", StandardWatchEventKinds.ENTRY_CREATE);// just to add some noise watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_DELETE); assertThat(watchEventCoalescor.removeNoise(noise)).isTrue(); // remove noisy event assertThat(watchEventCoalescor.scheduledEvents).hasSize(1); assertThat(watchEventCoalescor.scheduledEvents.get(0).isPresent()).isFalse(); } @Test public void should_only_merge_consecutive_events_for_a_file() { TestFileWatchEventCoalescor watchEventCoalescor = new TestFileWatchEventCoalescor(); watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_MODIFY); FileWatchEvent noise1 = watchEventCoalescor.post("tmp/bar", StandardWatchEventKinds.ENTRY_CREATE);// just to add some noise watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_DELETE); watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_MODIFY); watchEventCoalescor.post("tmp/bar", StandardWatchEventKinds.ENTRY_CREATE);// just to add some noise watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_CREATE); watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_CREATE); assertThat(watchEventCoalescor.removeNoise(noise1)).isTrue(); // remove noisy event (only one, they have been merged) assertThat(watchEventCoalescor.scheduledEvents).hasSize(4); // MODIFY, DELETE, MODIFY, CREATE assertThat(watchEventCoalescor.scheduledEvents).extracting("reference").extracting("kind") .containsExactly( StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_CREATE ); } @Test public void should_merge_classic_idea_on_windows_behavior() { TestFileWatchEventCoalescor watchEventCoalescor = new TestFileWatchEventCoalescor(); watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_DELETE); FileWatchEvent noise1 = watchEventCoalescor.post("tmp/bar", StandardWatchEventKinds.ENTRY_CREATE);// just to add some noise watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_CREATE); watchEventCoalescor.post("tmp/bar", StandardWatchEventKinds.ENTRY_CREATE);// just to add some noise watchEventCoalescor.post("tmp/foo", StandardWatchEventKinds.ENTRY_MODIFY); assertThat(watchEventCoalescor.removeNoise(noise1)).isTrue(); // remove noisy event assertThat(watchEventCoalescor.scheduledEvents).hasSize(1); FileWatchEvent event = watchEventCoalescor.scheduledEvents.get(0).getReference(); assertThat(event.getPath()).isEqualTo(Paths.get("tmp/foo")); assertThat(event.getKind()).isEqualTo(StandardWatchEventKinds.ENTRY_MODIFY); } }