From d77f7b51fc25f2d0e152d75fac1072dd83620bbd Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Wed, 26 Feb 2025 15:47:29 +0100 Subject: [PATCH 1/4] Add support for propagation of overflow events from a parent dir to a file in `JDKFileWatch` (including tests) --- .../java/engineering/swat/watch/Watcher.java | 2 +- .../swat/watch/impl/jdk/JDKFileWatch.java | 4 ++ .../swat/watch/SingleFileTests.java | 72 +++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/main/java/engineering/swat/watch/Watcher.java b/src/main/java/engineering/swat/watch/Watcher.java index 045b4206..fe3a7fc6 100644 --- a/src/main/java/engineering/swat/watch/Watcher.java +++ b/src/main/java/engineering/swat/watch/Watcher.java @@ -191,7 +191,7 @@ public ActiveWatch start() throws IOException { } } case PATH_ONLY: { - var result = new JDKFileWatch(path, executor, eventHandler); + var result = new JDKFileWatch(path, executor, h); result.open(); return result; } diff --git a/src/main/java/engineering/swat/watch/impl/jdk/JDKFileWatch.java b/src/main/java/engineering/swat/watch/impl/jdk/JDKFileWatch.java index 520d4a16..852cefea 100644 --- a/src/main/java/engineering/swat/watch/impl/jdk/JDKFileWatch.java +++ b/src/main/java/engineering/swat/watch/impl/jdk/JDKFileWatch.java @@ -57,6 +57,10 @@ public JDKFileWatch(Path file, Executor exec, BiConsumer { + if (e.getKind() == WatchEvent.Kind.OVERFLOW) { + var overflow = new WatchEvent(WatchEvent.Kind.OVERFLOW, file); + eventHandler.accept(w, overflow); + } if (fileName.equals(e.getRelativePath())) { eventHandler.accept(w, e); } diff --git a/src/test/java/engineering/swat/watch/SingleFileTests.java b/src/test/java/engineering/swat/watch/SingleFileTests.java index 206bc9bf..4d07715d 100644 --- a/src/test/java/engineering/swat/watch/SingleFileTests.java +++ b/src/test/java/engineering/swat/watch/SingleFileTests.java @@ -33,6 +33,7 @@ import java.nio.file.attribute.FileTime; import java.time.Instant; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; import org.awaitility.Awaitility; import org.junit.jupiter.api.AfterEach; @@ -40,6 +41,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import engineering.swat.watch.impl.EventHandlingWatch; + class SingleFileTests { private TestDirectory testDir; @@ -117,4 +120,73 @@ void singleFileThatMonitorsOnlyADirectory() throws IOException, InterruptedExcep .untilTrue(seen); } } + + @Test + void noRescanOnOverflow() throws IOException, InterruptedException { + var overflowPolicy = OverflowPolicy.NO_RESCANS; + var bookkeeper = new Bookkeeper(); + try (var watch = startWatchAndTriggerOverflow(overflowPolicy, bookkeeper)) { + Thread.sleep(TestHelper.SHORT_WAIT.toMillis()); + + await("Overflow shouldn't trigger created events") + .untilFalse(bookkeeper.seenCreated); + await("Overflow shouldn't trigger modified events") + .untilFalse(bookkeeper.seenModified); + await("Overflow should be visible to user-defined event handler") + .untilTrue(bookkeeper.seenOverflow); + } + } + + @Test + void memorylessRescanOnOverflow() throws IOException, InterruptedException { + var overflowPolicy = OverflowPolicy.MEMORYLESS_RESCANS; + var bookkeeper = new Bookkeeper(); + try (var watch = startWatchAndTriggerOverflow(overflowPolicy, bookkeeper)) { + Thread.sleep(TestHelper.SHORT_WAIT.toMillis()); + + await("Overflow should trigger created event") + .untilTrue(bookkeeper.seenCreated); + await("Overflow shouldn't trigger modified event (empty file)") + .untilFalse(bookkeeper.seenModified); + await("Overflow shouldn't be visible to user-defined event handler") + .untilFalse(bookkeeper.seenOverflow); + } + } + + private ActiveWatch startWatchAndTriggerOverflow(OverflowPolicy overflowPolicy, Bookkeeper bookkeeper) throws IOException { + var parent = testDir.getTestDirectory(); + var file = parent.resolve("a.txt"); + + var watch = Watcher + .watch(file, WatchScope.PATH_ONLY, overflowPolicy) + .on(bookkeeper) + .start(); + + var overflow = new WatchEvent(WatchEvent.Kind.OVERFLOW, parent); + ((EventHandlingWatch) watch).handleEvent(overflow); + return watch; + } + + private static class Bookkeeper implements Consumer { + private final AtomicBoolean seenCreated = new AtomicBoolean(false); + private final AtomicBoolean seenModified = new AtomicBoolean(false); + private final AtomicBoolean seenOverflow = new AtomicBoolean(false); + + @Override + public void accept(WatchEvent e) { + switch (e.getKind()) { + case CREATED: + seenCreated.set(true); + break; + case MODIFIED: + seenModified.set(true); + break; + case OVERFLOW: + seenOverflow.set(true); + break; + default: + break; + } + } + } } From 1f7e3664ef6b322ca51c4adc071629cecaeb19d9 Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Wed, 5 Mar 2025 14:08:02 +0100 Subject: [PATCH 2/4] Update test after merge --- .../engineering/swat/watch/SingleFileTests.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/test/java/engineering/swat/watch/SingleFileTests.java b/src/test/java/engineering/swat/watch/SingleFileTests.java index 4d07715d..2492462f 100644 --- a/src/test/java/engineering/swat/watch/SingleFileTests.java +++ b/src/test/java/engineering/swat/watch/SingleFileTests.java @@ -123,9 +123,9 @@ void singleFileThatMonitorsOnlyADirectory() throws IOException, InterruptedExcep @Test void noRescanOnOverflow() throws IOException, InterruptedException { - var overflowPolicy = OverflowPolicy.NO_RESCANS; + var whichFiles = OnOverflow.NONE; var bookkeeper = new Bookkeeper(); - try (var watch = startWatchAndTriggerOverflow(overflowPolicy, bookkeeper)) { + try (var watch = startWatchAndTriggerOverflow(whichFiles, bookkeeper)) { Thread.sleep(TestHelper.SHORT_WAIT.toMillis()); await("Overflow shouldn't trigger created events") @@ -139,26 +139,27 @@ void noRescanOnOverflow() throws IOException, InterruptedException { @Test void memorylessRescanOnOverflow() throws IOException, InterruptedException { - var overflowPolicy = OverflowPolicy.MEMORYLESS_RESCANS; + var whichFiles = OnOverflow.ALL; var bookkeeper = new Bookkeeper(); - try (var watch = startWatchAndTriggerOverflow(overflowPolicy, bookkeeper)) { + try (var watch = startWatchAndTriggerOverflow(whichFiles, bookkeeper)) { Thread.sleep(TestHelper.SHORT_WAIT.toMillis()); await("Overflow should trigger created event") .untilTrue(bookkeeper.seenCreated); await("Overflow shouldn't trigger modified event (empty file)") .untilFalse(bookkeeper.seenModified); - await("Overflow shouldn't be visible to user-defined event handler") - .untilFalse(bookkeeper.seenOverflow); + await("Overflow should be visible to user-defined event handler") + .untilTrue(bookkeeper.seenOverflow); } } - private ActiveWatch startWatchAndTriggerOverflow(OverflowPolicy overflowPolicy, Bookkeeper bookkeeper) throws IOException { + private ActiveWatch startWatchAndTriggerOverflow(OnOverflow whichFiles, Bookkeeper bookkeeper) throws IOException { var parent = testDir.getTestDirectory(); var file = parent.resolve("a.txt"); var watch = Watcher - .watch(file, WatchScope.PATH_ONLY, overflowPolicy) + .watch(file, WatchScope.PATH_ONLY) + .approximate(whichFiles) .on(bookkeeper) .start(); From 767972de288d0eb8072863c8ef35df687a9c445e Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Fri, 7 Mar 2025 13:09:53 +0100 Subject: [PATCH 3/4] Improve test code --- .../swat/watch/SingleFileTests.java | 70 +++++++++++-------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/src/test/java/engineering/swat/watch/SingleFileTests.java b/src/test/java/engineering/swat/watch/SingleFileTests.java index 2492462f..7a3bcdb7 100644 --- a/src/test/java/engineering/swat/watch/SingleFileTests.java +++ b/src/test/java/engineering/swat/watch/SingleFileTests.java @@ -26,14 +26,25 @@ */ package engineering.swat.watch; +import static engineering.swat.watch.WatchEvent.Kind.CREATED; +import static engineering.swat.watch.WatchEvent.Kind.DELETED; +import static engineering.swat.watch.WatchEvent.Kind.MODIFIED; +import static engineering.swat.watch.WatchEvent.Kind.OVERFLOW; import static org.awaitility.Awaitility.await; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.attribute.FileTime; import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; import org.awaitility.Awaitility; import org.junit.jupiter.api.AfterEach; @@ -123,33 +134,36 @@ void singleFileThatMonitorsOnlyADirectory() throws IOException, InterruptedExcep @Test void noRescanOnOverflow() throws IOException, InterruptedException { - var whichFiles = OnOverflow.NONE; var bookkeeper = new Bookkeeper(); - try (var watch = startWatchAndTriggerOverflow(whichFiles, bookkeeper)) { + try (var watch = startWatchAndTriggerOverflow(OnOverflow.NONE, bookkeeper)) { Thread.sleep(TestHelper.SHORT_WAIT.toMillis()); - await("Overflow shouldn't trigger created events") - .untilFalse(bookkeeper.seenCreated); - await("Overflow shouldn't trigger modified events") - .untilFalse(bookkeeper.seenModified); + await("Overflow shouldn't trigger created, modified, or deleted events") + .until(() -> bookkeeper.fullPaths(CREATED, MODIFIED, DELETED).count() == 0); await("Overflow should be visible to user-defined event handler") - .untilTrue(bookkeeper.seenOverflow); + .until(() -> bookkeeper.fullPaths(OVERFLOW).count() == 1); } } @Test void memorylessRescanOnOverflow() throws IOException, InterruptedException { - var whichFiles = OnOverflow.ALL; var bookkeeper = new Bookkeeper(); - try (var watch = startWatchAndTriggerOverflow(whichFiles, bookkeeper)) { + try (var watch = startWatchAndTriggerOverflow(OnOverflow.ALL, bookkeeper)) { Thread.sleep(TestHelper.SHORT_WAIT.toMillis()); - await("Overflow should trigger created event") - .untilTrue(bookkeeper.seenCreated); - await("Overflow shouldn't trigger modified event (empty file)") - .untilFalse(bookkeeper.seenModified); + var isFile = Predicate.isEqual(watch.getPath()); + var isNotFile = Predicate.not(isFile); + + await("Overflow should trigger created event for `file`") + .until(() -> bookkeeper.fullPaths(CREATED).filter(isFile).count() == 1); + await("Overflow shouldn't trigger created events for other files") + .until(() -> bookkeeper.fullPaths(CREATED).filter(isNotFile).count() == 0); + await("Overflow shouldn't trigger modified events (`file` is empty)") + .until(() -> bookkeeper.fullPaths(MODIFIED).count() == 0); + await("Overflow shouldn't trigger deleted events") + .until(() -> bookkeeper.fullPaths(DELETED).count() == 0); await("Overflow should be visible to user-defined event handler") - .untilTrue(bookkeeper.seenOverflow); + .until(() -> bookkeeper.fullPaths(OVERFLOW).count() == 1); } } @@ -169,25 +183,21 @@ private ActiveWatch startWatchAndTriggerOverflow(OnOverflow whichFiles, Bookkeep } private static class Bookkeeper implements Consumer { - private final AtomicBoolean seenCreated = new AtomicBoolean(false); - private final AtomicBoolean seenModified = new AtomicBoolean(false); - private final AtomicBoolean seenOverflow = new AtomicBoolean(false); + private final List events = Collections.synchronizedList(new ArrayList<>()); + + public Stream events(WatchEvent.Kind... kinds) { + var list = Arrays.asList(kinds.length == 0 ? WatchEvent.Kind.values() : kinds); + return events.stream().filter(e -> list.contains(e.getKind())); + } + + public Stream fullPaths(WatchEvent.Kind... kinds) { + return events(kinds).map(WatchEvent::calculateFullPath); + } @Override public void accept(WatchEvent e) { - switch (e.getKind()) { - case CREATED: - seenCreated.set(true); - break; - case MODIFIED: - seenModified.set(true); - break; - case OVERFLOW: - seenOverflow.set(true); - break; - default: - break; - } + events.add(e); + System.out.println(e); } } } From c92d17ff83c8c7a6a3985c57b738fa1aad9dbca7 Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Fri, 7 Mar 2025 13:13:10 +0100 Subject: [PATCH 4/4] Remove debug println --- src/test/java/engineering/swat/watch/SingleFileTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/engineering/swat/watch/SingleFileTests.java b/src/test/java/engineering/swat/watch/SingleFileTests.java index 7a3bcdb7..19def650 100644 --- a/src/test/java/engineering/swat/watch/SingleFileTests.java +++ b/src/test/java/engineering/swat/watch/SingleFileTests.java @@ -197,7 +197,6 @@ public Stream fullPaths(WatchEvent.Kind... kinds) { @Override public void accept(WatchEvent e) { events.add(e); - System.out.println(e); } } }