|
26 | 26 | import static org.junit.Assume.assumeTrue; |
27 | 27 |
|
28 | 28 | import java.io.IOException; |
| 29 | +import java.nio.file.Files; |
| 30 | +import java.nio.file.Path; |
| 31 | +import java.nio.file.Paths; |
29 | 32 | import org.eclipse.core.filesystem.EFS; |
30 | 33 | import org.eclipse.core.filesystem.IFileStore; |
| 34 | +import org.eclipse.core.internal.localstore.UnifiedTree; |
31 | 35 | import org.eclipse.core.resources.IProject; |
32 | 36 | import org.eclipse.core.resources.IResource; |
33 | 37 | import org.eclipse.core.resources.IResourceVisitor; |
34 | 38 | import org.eclipse.core.resources.IWorkspaceRunnable; |
35 | 39 | import org.eclipse.core.runtime.CoreException; |
36 | 40 | import org.eclipse.core.runtime.IPath; |
37 | | -import org.eclipse.core.tests.resources.WorkspaceTestRule; |
38 | | -import org.junit.Rule; |
39 | | -import org.junit.Test; |
| 41 | +import org.eclipse.core.tests.resources.util.WorkspaceResetExtension; |
| 42 | +import org.junit.jupiter.api.Test; |
| 43 | +import org.junit.jupiter.api.extension.ExtendWith; |
| 44 | +import org.junit.jupiter.params.ParameterizedTest; |
| 45 | +import org.junit.jupiter.params.provider.ValueSource; |
40 | 46 |
|
| 47 | +@ExtendWith(WorkspaceResetExtension.class) |
41 | 48 | public class SymlinkResourceTest { |
42 | 49 |
|
43 | | - @Rule |
44 | | - public WorkspaceTestRule workspaceRule = new WorkspaceTestRule(); |
45 | | - |
46 | 50 | private void mkLink(IFileStore dir, String src, String tgt, boolean isDir) throws CoreException, IOException { |
47 | 51 | createSymLink(dir.toLocalFile(EFS.NONE, createTestMonitor()), src, tgt, isDir); |
48 | 52 | } |
@@ -70,6 +74,87 @@ protected void createBug358830Structure(IFileStore rootDir) throws CoreException |
70 | 74 | mkLink(folderA, "link", IPath.fromOSString("../").toOSString(), true); |
71 | 75 | } |
72 | 76 |
|
| 77 | + /** |
| 78 | + * Test a case of both recursive and non recursive symbolic links that uncovered |
| 79 | + * issue described in <a href= |
| 80 | + * "https://github.com/eclipse-platform/eclipse.platform/issues/2220">GitHub bug |
| 81 | + * 2220</a>. |
| 82 | + * |
| 83 | + * <pre>{@code |
| 84 | + * /A/B -> /X/Y/Z (B is symbolic link as precondition) |
| 85 | + * /A/B/C/D -> ../../D (non-recursive) |
| 86 | + * /A/B/C/E -> ../../Z (recursive) |
| 87 | + * }</pre> |
| 88 | + * |
| 89 | + * The starting path /A/B/C is already based on link. The real path of start is |
| 90 | + * /X/Y/Z/C so ../../Z points (recursive) to /X/Y/Z. /X/Y/D and /X/Y/Z is what |
| 91 | + * we expect to resolve but we fail in both cases with NoSuchFileException. |
| 92 | + */ |
| 93 | + @ParameterizedTest |
| 94 | + @ValueSource(booleans = { false, true }) |
| 95 | + public void testGithubBug2220(boolean useAdvancedLinkCheck) throws Exception { |
| 96 | + assumeTrue("only relevant for platforms supporting symbolic links", canCreateSymLinks()); |
| 97 | + final boolean originalValue = UnifiedTree.isAdvancedRecursiveLinkChecksEnabled(); |
| 98 | + try { |
| 99 | + UnifiedTree.enableAdvancedRecursiveLinkChecks(useAdvancedLinkCheck); |
| 100 | + IProject project = getWorkspace().getRoot().getProject("testGithubBug2220"); |
| 101 | + createInWorkspace(project); |
| 102 | + |
| 103 | + /* Re-use projects which are cleaned up automatically */ |
| 104 | + getWorkspace().run((IWorkspaceRunnable) monitor -> { |
| 105 | + /* delete open project because we must re-open with BACKGROUND_REFRESH */ |
| 106 | + project.delete(IResource.NEVER_DELETE_PROJECT_CONTENT, createTestMonitor()); |
| 107 | + project.create(null); |
| 108 | + try { |
| 109 | + createGithubBug2220Structure(EFS.getStore(project.getLocationURI())); |
| 110 | + } catch (IOException e) { |
| 111 | + throw new IllegalStateException("unexpected IOException occurred", e); |
| 112 | + } |
| 113 | + // Bug only happens with BACKGROUND_REFRESH. |
| 114 | + project.open(IResource.BACKGROUND_REFRESH, createTestMonitor()); |
| 115 | + }, null); |
| 116 | + |
| 117 | + // wait for BACKGROUND_REFRESH to complete. |
| 118 | + waitForRefresh(); |
| 119 | + project.accept(new IResourceVisitor() { |
| 120 | + int resourceCount = 0; |
| 121 | + |
| 122 | + @Override |
| 123 | + public boolean visit(IResource resource) { |
| 124 | + resourceCount++; |
| 125 | + // We have 1 root + .settings + prefs + + .project + 10 elements --> 14 elements |
| 126 | + // to visit at most |
| 127 | + System.out.println(resourceCount + " visited: " + resource.getFullPath()); |
| 128 | + assertTrue("Expected max 15 elements to visit, got: " + resourceCount, resourceCount <= 15); |
| 129 | + return true; |
| 130 | + } |
| 131 | + }); |
| 132 | + } finally { |
| 133 | + UnifiedTree.enableAdvancedRecursiveLinkChecks(originalValue); |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + /** |
| 138 | + * <pre>{@code |
| 139 | + * /A/B -> /X/Y/Z (B is symbolic link as precondition) |
| 140 | + * /A/B/C/D -> ../../D (non-recursive) |
| 141 | + * /A/B/C/E -> ../../Z (recursive) |
| 142 | + * }</pre> |
| 143 | + * |
| 144 | + * The starting path /A/B/C is already based on link. The real path of start is |
| 145 | + * /X/Y/Z/C so ../../Z points (recursive) to /X/Y/Z. |
| 146 | + */ |
| 147 | + protected void createGithubBug2220Structure(IFileStore rootDir) throws CoreException, IOException { |
| 148 | + Path root = rootDir.toLocalFile(EFS.NONE, createTestMonitor()).toPath(); |
| 149 | + Files.createDirectories(root.resolve("A")); |
| 150 | + Files.createDirectories(root.resolve("X/Y/Z")); |
| 151 | + Files.createDirectories(root.resolve("X/Y/D")); |
| 152 | + Files.createSymbolicLink(root.resolve("A/B"), root.resolve("X/Y/Z")); |
| 153 | + Files.createDirectories(root.resolve("A/B/C")); |
| 154 | + Files.createSymbolicLink(root.resolve("A/B/C/D"), Paths.get("../../D")); |
| 155 | + Files.createSymbolicLink(root.resolve("A/B/C/E"), Paths.get("../../Z")); |
| 156 | + } |
| 157 | + |
73 | 158 | /** |
74 | 159 | * Test a very specific case of mutually recursive symbolic links: |
75 | 160 | * <pre> {@code |
@@ -118,36 +203,42 @@ public boolean visit(IResource resource) { |
118 | 203 | }); |
119 | 204 | } |
120 | 205 |
|
121 | | - @Test |
122 | | - public void testBug358830() throws Exception { |
| 206 | + @ParameterizedTest |
| 207 | + @ValueSource(booleans = { false, true }) |
| 208 | + public void testBug358830(boolean useAdvancedLinkCheck) throws Exception { |
123 | 209 | assumeTrue("only relevant for platforms supporting symbolic links", canCreateSymLinks()); |
124 | | - |
125 | | - IProject project = getWorkspace().getRoot().getProject("Project"); |
126 | | - createInWorkspace(project); |
127 | | - /* Re-use projects which are cleaned up automatically */ |
128 | | - getWorkspace().run((IWorkspaceRunnable) monitor -> { |
129 | | - /* delete open project because we must re-open with BACKGROUND_REFRESH */ |
130 | | - project.delete(IResource.NEVER_DELETE_PROJECT_CONTENT, createTestMonitor()); |
131 | | - project.create(null); |
132 | | - try { |
133 | | - createBug358830Structure(EFS.getStore(project.getLocationURI())); |
134 | | - } catch (IOException e) { |
135 | | - throw new IllegalStateException("unexpected IOException occurred", e); |
136 | | - } |
137 | | - project.open(IResource.BACKGROUND_REFRESH, createTestMonitor()); |
138 | | - }, null); |
139 | | - |
140 | | - //wait for BACKGROUND_REFRESH to complete. |
141 | | - waitForRefresh(); |
142 | | - final int resourceCount[] = new int[] {0}; |
143 | | - project.accept(resource -> { |
144 | | - resourceCount[0]++; |
145 | | - return true; |
146 | | - }); |
147 | | - // We have 1 root + 1 folder + 1 file (.project) |
148 | | - // + .settings / resources prefs |
149 | | - // --> 5 elements to visit |
150 | | - assertEquals(5, resourceCount[0]); |
| 210 | + final boolean originalValue = UnifiedTree.isAdvancedRecursiveLinkChecksEnabled(); |
| 211 | + try { |
| 212 | + UnifiedTree.enableAdvancedRecursiveLinkChecks(useAdvancedLinkCheck); |
| 213 | + IProject project = getWorkspace().getRoot().getProject("Project"); |
| 214 | + createInWorkspace(project); |
| 215 | + /* Re-use projects which are cleaned up automatically */ |
| 216 | + getWorkspace().run((IWorkspaceRunnable) monitor -> { |
| 217 | + /* delete open project because we must re-open with BACKGROUND_REFRESH */ |
| 218 | + project.delete(IResource.NEVER_DELETE_PROJECT_CONTENT, createTestMonitor()); |
| 219 | + project.create(null); |
| 220 | + try { |
| 221 | + createBug358830Structure(EFS.getStore(project.getLocationURI())); |
| 222 | + } catch (IOException e) { |
| 223 | + throw new IllegalStateException("unexpected IOException occurred", e); |
| 224 | + } |
| 225 | + project.open(IResource.BACKGROUND_REFRESH, createTestMonitor()); |
| 226 | + }, null); |
| 227 | + |
| 228 | + // wait for BACKGROUND_REFRESH to complete. |
| 229 | + waitForRefresh(); |
| 230 | + final int resourceCount[] = new int[] { 0 }; |
| 231 | + project.accept(resource -> { |
| 232 | + resourceCount[0]++; |
| 233 | + return true; |
| 234 | + }); |
| 235 | + // We have 1 root + 1 folder + 1 file (.project) |
| 236 | + // + .settings / resources prefs |
| 237 | + // --> 5 elements to visit |
| 238 | + assertEquals(5, resourceCount[0]); |
| 239 | + } finally { |
| 240 | + UnifiedTree.enableAdvancedRecursiveLinkChecks(originalValue); |
| 241 | + } |
151 | 242 | } |
152 | 243 |
|
153 | 244 | } |
0 commit comments