diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/CanonicalPathMap.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/CanonicalPathMap.java index f8a6e5d3e80c..641c818f1928 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/CanonicalPathMap.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/CanonicalPathMap.java @@ -141,6 +141,13 @@ class CanonicalPathMap { continue ext; } } + else { + String rootParent = new File(root).getParent(); + if (rootParent != null && FileUtil.namesEqual(path, rootParent)) { + changedPaths.add(root); + continue ext; + } + } } for (String root : myRecursiveWatchRoots) { @@ -149,8 +156,8 @@ class CanonicalPathMap { continue ext; } if (!isExact) { - String parentPath = new File(root).getParent(); - if (parentPath != null && FileUtil.namesEqual(path, parentPath)) { + String rootParent = new File(root).getParent(); + if (rootParent != null && FileUtil.namesEqual(path, rootParent)) { changedPaths.add(root); continue ext; } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/impl/local/CanonicalPathMapTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/impl/local/CanonicalPathMapTest.java index 05a5ae62a91b..db2cf395c6bb 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/impl/local/CanonicalPathMapTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/impl/local/CanonicalPathMapTest.java @@ -17,31 +17,130 @@ package com.intellij.openapi.vfs.impl.local; import com.intellij.openapi.util.io.IoTestUtil; import com.intellij.testFramework.rules.TempDirectory; +import com.intellij.util.PathUtil; import org.junit.Rule; import org.junit.Test; import java.io.File; +import java.io.IOException; import java.util.Collection; -import java.util.Collections; import static com.intellij.openapi.util.Pair.pair; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; public class CanonicalPathMapTest { @Rule public TempDirectory myTempDir = new TempDirectory(); @Test - public void testRemappedSymLinkReportsOriginalWatchedPath() throws Exception { + public void flatRootReportedExactlyViaParent() { + String root = "/parent/root"; + CanonicalPathMap map = new CanonicalPathMap(emptyList(), singletonList(root)); + Collection paths = map.getWatchedPaths(PathUtil.getParentPath(root), true); + assertThat(paths).isEmpty(); + } + + @Test + public void flatRootReportedExactlyViaItself() { + String root = "/parent/root.txt"; + CanonicalPathMap map = new CanonicalPathMap(emptyList(), singletonList(root)); + Collection paths = map.getWatchedPaths(root, true); + assertThat(paths).containsExactly(root); + } + + @Test + public void flatRootReportedExactlyViaChild() { + String root = "/parent/root", child = root + "/child.txt"; + CanonicalPathMap map = new CanonicalPathMap(emptyList(), singletonList(root)); + Collection paths = map.getWatchedPaths(child, true); + assertThat(paths).containsExactly(child); + } + + @Test + public void flatRootReportedInexactlyViaParent() { + String root = "/parent/root.txt"; + CanonicalPathMap map = new CanonicalPathMap(emptyList(), singletonList(root)); + Collection paths = map.getWatchedPaths(PathUtil.getParentPath(root), false); + assertThat(paths).containsExactly(root); + } + + @Test + public void flatRootReportedInexactlyViaItself() { + String root = "/parent/root"; + CanonicalPathMap map = new CanonicalPathMap(emptyList(), singletonList(root)); + Collection paths = map.getWatchedPaths(root, false); + assertThat(paths).containsExactly(root); + } + + @Test + public void flatRootReportedInexactlyViaChild() { + String root = "/parent/root", child = root + "/child_dir"; + CanonicalPathMap map = new CanonicalPathMap(emptyList(), singletonList(root)); + Collection paths = map.getWatchedPaths(child, false); + assertThat(paths).isEmpty(); + } + + @Test + public void recursiveRootReportedExactlyViaParent() { + String root = "/parent/root"; + CanonicalPathMap map = new CanonicalPathMap(singletonList(root), emptyList()); + Collection paths = map.getWatchedPaths(PathUtil.getParentPath(root), true); + assertThat(paths).isEmpty(); + } + + @Test + public void recursiveRootReportedExactlyViaItself() { + String root = "/parent/root"; + CanonicalPathMap map = new CanonicalPathMap(singletonList(root), emptyList()); + Collection paths = map.getWatchedPaths(root, true); + assertThat(paths).containsExactly(root); + } + + @Test + public void recursiveRootReportedExactlyViaChild() { + String root = "/parent/root", child = root + "/child.txt"; + CanonicalPathMap map = new CanonicalPathMap(singletonList(root), emptyList()); + Collection paths = map.getWatchedPaths(child, true); + assertThat(paths).containsExactly(child); + } + + @Test + public void recursiveRootReportedInexactlyViaParent() { + String root = "/parent/root"; + CanonicalPathMap map = new CanonicalPathMap(singletonList(root), emptyList()); + Collection paths = map.getWatchedPaths(PathUtil.getParentPath(root), false); + assertThat(paths).containsExactly(root); + } + + @Test + public void recursiveRootReportedInexactlyViaItself() { + String root = "/parent/root"; + CanonicalPathMap map = new CanonicalPathMap(singletonList(root), emptyList()); + Collection paths = map.getWatchedPaths(root, false); + assertThat(paths).containsExactly(root); + } + + @Test + public void recursiveRootReportedInexactlyViaChild() { + String root = "/parent/root", child = root + "/child"; + CanonicalPathMap map = new CanonicalPathMap(singletonList(root), emptyList()); + Collection paths = map.getWatchedPaths(child, false); + assertThat(paths).containsExactly(child); + } + + @Test + public void remappedSymLinkReportsOriginalWatchedPath() throws IOException, InterruptedException { // Tests the situation where the watch root is a symlink AND REMAPPED by the native file watcher. File realDir = myTempDir.newFolder("real"); File symLink = IoTestUtil.createSymLink(realDir.getPath(), myTempDir.getRoot() + "/link"); File mappedDir = new File(myTempDir.getRoot(), "mapped"); // Initial symlink map: /?/root/link_dir -> /?/root/real - CanonicalPathMap pathMap = new CanonicalPathMap(Collections.singletonList(symLink.getPath()), Collections.emptyList()); + CanonicalPathMap pathMap = new CanonicalPathMap(singletonList(symLink.getPath()), emptyList()); // REMAP from native file watcher: /?/root/mapped -> /?/root/real - pathMap.addMapping(Collections.singletonList(pair(mappedDir.getPath(), realDir.getPath()))); + pathMap.addMapping(singletonList(pair(mappedDir.getPath(), realDir.getPath()))); Collection watchedPaths = pathMap.getWatchedPaths(new File(mappedDir, "file.txt").getPath(), true); assertThat(watchedPaths).containsExactly(new File(symLink, "file.txt").getPath()); diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.kt b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.kt index ded2a07a94cb..752252dfe5c5 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.kt @@ -81,6 +81,8 @@ class FileWatcherTest : BareTestFixtureTestCase() { runInEdtAndWait { VirtualFileManager.getInstance().syncRefresh() } + alarm = Alarm(Alarm.ThreadToUse.POOLED_THREAD, testRootDisposable) + watcher = (fs as LocalFileSystemImpl).fileWatcher assertFalse(watcher.isOperational) watcher.startup { reset -> @@ -90,8 +92,6 @@ class FileWatcherTest : BareTestFixtureTestCase() { } wait { !watcher.isOperational } - alarm = Alarm(Alarm.ThreadToUse.POOLED_THREAD, testRootDisposable) - LOG.debug("================== setting up " + getTestName(false) + " ==================") } @@ -122,7 +122,17 @@ class FileWatcherTest : BareTestFixtureTestCase() { val file = tempDir.newFile("test.txt") refresh(file) - watch(file) + watch(file, false) + assertEvents({ file.writeText("new content") }, mapOf(file to 'U')) + assertEvents({ file.delete() }, mapOf(file to 'D')) + assertEvents({ file.writeText("re-creation") }, mapOf(file to 'C')) + } + + @Test fun testFileRootRecursive() { + val file = tempDir.newFile("test.txt") + refresh(file) + + watch(file, true) assertEvents({ file.writeText("new content") }, mapOf(file to 'U')) assertEvents({ file.delete() }, mapOf(file to 'D')) assertEvents({ file.writeText("re-creation") }, mapOf(file to 'C'))