2121import static org .eclipse .core .tests .resources .ResourceTestUtil .createInWorkspace ;
2222import static org .eclipse .core .tests .resources .ResourceTestUtil .createTestMonitor ;
2323import static org .eclipse .core .tests .resources .ResourceTestUtil .waitForRefresh ;
24- import static org .junit .Assert .assertEquals ;
25- import static org .junit .Assert .assertTrue ;
26- import static org .junit .Assume .assumeTrue ;
24+ import static org .junit .jupiter . api . Assertions .assertEquals ;
25+ import static org .junit .jupiter . api . Assertions .assertTrue ;
26+ import static org .junit .jupiter . api . Assumptions .assumeTrue ;
2727
2828import java .io .IOException ;
29+ import java .nio .file .Files ;
30+ import java .nio .file .Path ;
31+ import java .nio .file .Paths ;
2932import org .eclipse .core .filesystem .EFS ;
3033import org .eclipse .core .filesystem .IFileStore ;
34+ import org .eclipse .core .internal .localstore .UnifiedTree ;
3135import org .eclipse .core .resources .IProject ;
3236import org .eclipse .core .resources .IResource ;
3337import org .eclipse .core .resources .IResourceVisitor ;
3438import org .eclipse .core .resources .IWorkspaceRunnable ;
3539import org .eclipse .core .runtime .CoreException ;
3640import 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 ;
4046
47+ @ ExtendWith (WorkspaceResetExtension .class )
4148public class SymlinkResourceTest {
4249
43- @ Rule
44- public WorkspaceTestRule workspaceRule = new WorkspaceTestRule ();
45-
4650 private void mkLink (IFileStore dir , String src , String tgt , boolean isDir ) throws CoreException , IOException {
4751 createSymLink (dir .toLocalFile (EFS .NONE , createTestMonitor ()), src , tgt , isDir );
4852 }
@@ -70,6 +74,87 @@ protected void createBug358830Structure(IFileStore rootDir) throws CoreException
7074 mkLink (folderA , "link" , IPath .fromOSString ("../" ).toOSString (), true );
7175 }
7276
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 (canCreateSymLinks (), "only relevant for platforms supporting symbolic links" );
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 (resourceCount <= 15 , "Expected max 15 elements to visit, got: " + resourceCount );
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+
73158 /**
74159 * Test a very specific case of mutually recursive symbolic links:
75160 * <pre> {@code
@@ -85,7 +170,7 @@ protected void createBug358830Structure(IFileStore rootDir) throws CoreException
85170 */
86171 @ Test
87172 public void testBug232426 () throws Exception {
88- assumeTrue ("only relevant for platforms supporting symbolic links" , canCreateSymLinks () );
173+ assumeTrue (canCreateSymLinks (), "only relevant for platforms supporting symbolic links" );
89174
90175 IProject project = getWorkspace ().getRoot ().getProject ("Project" );
91176 createInWorkspace (project );
@@ -118,36 +203,42 @@ public boolean visit(IResource resource) {
118203 });
119204 }
120205
121- @ Test
122- public void testBug358830 () throws Exception {
123- 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 ]);
206+ @ ParameterizedTest
207+ @ ValueSource (booleans = { false , true })
208+ public void testBug358830 (boolean useAdvancedLinkCheck ) throws Exception {
209+ assumeTrue (canCreateSymLinks (), "only relevant for platforms supporting symbolic links" );
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+ }
151242 }
152243
153244}
0 commit comments