Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/engineering/swat/watch/Watcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ public ActiveWatch start() throws IOException {
}
}
case PATH_ONLY: {
var result = new JDKFileWatch(path, executor, eventHandler);
var result = new JDKFileWatch(path, executor, h);
Copy link
Contributor Author

@sungshik sungshik Mar 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing context: h is the compound event handler that consists of: (a) the user-defined eventHandler; (b) the overflow auto-handler that generates synthetic events

result.open();
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ public JDKFileWatch(Path file, Executor exec, BiConsumer<EventHandlingWatch, Wat
assert !parent.equals(file);

this.internal = new JDKDirectoryWatch(parent, exec, (w, e) -> {
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);
}
Expand Down
82 changes: 82 additions & 0 deletions src/test/java/engineering/swat/watch/SingleFileTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,34 @@
*/
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;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import engineering.swat.watch.impl.EventHandlingWatch;

class SingleFileTests {
private TestDirectory testDir;

Expand Down Expand Up @@ -117,4 +131,72 @@ void singleFileThatMonitorsOnlyADirectory() throws IOException, InterruptedExcep
.untilTrue(seen);
}
}

@Test
void noRescanOnOverflow() throws IOException, InterruptedException {
var bookkeeper = new Bookkeeper();
try (var watch = startWatchAndTriggerOverflow(OnOverflow.NONE, bookkeeper)) {
Thread.sleep(TestHelper.SHORT_WAIT.toMillis());

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")
.until(() -> bookkeeper.fullPaths(OVERFLOW).count() == 1);
}
}

@Test
void memorylessRescanOnOverflow() throws IOException, InterruptedException {
var bookkeeper = new Bookkeeper();
try (var watch = startWatchAndTriggerOverflow(OnOverflow.ALL, bookkeeper)) {
Thread.sleep(TestHelper.SHORT_WAIT.toMillis());

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")
.until(() -> bookkeeper.fullPaths(OVERFLOW).count() == 1);
}
}

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)
.approximate(whichFiles)
.on(bookkeeper)
.start();

var overflow = new WatchEvent(WatchEvent.Kind.OVERFLOW, parent);
((EventHandlingWatch) watch).handleEvent(overflow);
return watch;
}

private static class Bookkeeper implements Consumer<WatchEvent> {
private final List<WatchEvent> events = Collections.synchronizedList(new ArrayList<>());

public Stream<WatchEvent> 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<Path> fullPaths(WatchEvent.Kind... kinds) {
return events(kinds).map(WatchEvent::calculateFullPath);
}

@Override
public void accept(WatchEvent e) {
events.add(e);
}
}
}