IDEA-160580 When symlink target changes to another with different content but the...
[idea/community.git] / platform / platform-tests / testSrc / com / intellij / openapi / vfs / local / SymlinkHandlingTest.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.vfs.local;
17
18 import com.intellij.openapi.application.Result;
19 import com.intellij.openapi.application.WriteAction;
20 import com.intellij.openapi.util.SystemInfo;
21 import com.intellij.openapi.util.io.FileUtil;
22 import com.intellij.openapi.util.text.StringUtil;
23 import com.intellij.openapi.vfs.*;
24 import com.intellij.testFramework.fixtures.BareTestFixtureTestCase;
25 import com.intellij.testFramework.rules.TempDirectory;
26 import org.jetbrains.annotations.NotNull;
27 import org.jetbrains.annotations.Nullable;
28 import org.junit.Before;
29 import org.junit.Rule;
30 import org.junit.Test;
31
32 import java.io.File;
33 import java.io.IOException;
34 import java.util.Set;
35 import java.util.stream.Collectors;
36 import java.util.stream.Stream;
37
38 import static com.intellij.openapi.util.io.IoTestUtil.*;
39 import static com.intellij.testFramework.PlatformTestUtil.assertPathsEqual;
40 import static org.junit.Assert.*;
41 import static org.junit.Assume.assumeTrue;
42
43 public class SymlinkHandlingTest extends BareTestFixtureTestCase {
44   @Rule public TempDirectory myTempDir = new TempDirectory();
45
46   @Before
47   public void setUp() {
48     assumeTrue(SystemInfo.areSymLinksSupported);
49   }
50
51   @Test
52   public void testMissingLink() throws Exception {
53     File missingFile = new File(myTempDir.getRoot(), "missing_file");
54     assertTrue(missingFile.getPath(), !missingFile.exists() || missingFile.delete());
55     File missingLinkFile = createSymLink(missingFile.getPath(), myTempDir.getRoot() + "/missing_link", false);
56     VirtualFile missingLinkVFile = refreshAndFind(missingLinkFile);
57     assertNotNull(missingLinkVFile);
58     assertBrokenLink(missingLinkVFile);
59     assertVisitedPaths(missingLinkVFile.getPath());
60   }
61
62   @Test
63   public void testSelfLink() throws Exception {
64     String target = new File(myTempDir.getRoot(), "self_link").getPath();
65     File selfLinkFile = createSymLink(target, target, false);
66     VirtualFile selfLinkVFile = refreshAndFind(selfLinkFile);
67     assertNotNull(selfLinkVFile);
68     assertBrokenLink(selfLinkVFile);
69     assertVisitedPaths(selfLinkVFile.getPath());
70   }
71
72   @Test
73   public void testDotLink() throws Exception {
74     File dotLinkFile = createSymLink(".", myTempDir.getRoot() + "/dot_link");
75     VirtualFile dotLinkVFile = refreshAndFind(dotLinkFile);
76     assertNotNull(dotLinkVFile);
77     assertTrue(dotLinkVFile.is(VFileProperty.SYMLINK));
78     assertTrue(dotLinkVFile.isDirectory());
79     assertPathsEqual(myTempDir.getRoot().getPath(), dotLinkVFile.getCanonicalPath());
80     assertVisitedPaths(dotLinkVFile.getPath());
81   }
82
83   @Test
84   public void testCircularLink() throws Exception {
85     File upDir = myTempDir.newFolder("sub");
86     File upLinkFile = createSymLink(upDir.getPath(), upDir.getPath() + "/up_link");
87     VirtualFile upLinkVFile = refreshAndFind(upLinkFile);
88     assertNotNull(upLinkVFile);
89     assertTrue(upLinkVFile.is(VFileProperty.SYMLINK));
90     assertTrue(upLinkVFile.isDirectory());
91     assertPathsEqual(upDir.getPath(), upLinkVFile.getCanonicalPath());
92     assertVisitedPaths(upDir.getPath(), upLinkVFile.getPath());
93
94     File repeatedLinksFile = new File(upDir.getPath() + StringUtil.repeat(File.separator + upLinkFile.getName(), 4));
95     assertTrue(repeatedLinksFile.getPath(), repeatedLinksFile.isDirectory());
96     VirtualFile repeatedLinksVFile = refreshAndFind(repeatedLinksFile);
97     assertNotNull(repeatedLinksFile.getPath(), repeatedLinksVFile);
98     assertTrue(repeatedLinksVFile.is(VFileProperty.SYMLINK));
99     assertTrue(repeatedLinksVFile.isDirectory());
100     assertPathsEqual(upDir.getPath(), repeatedLinksVFile.getCanonicalPath());
101     assertEquals(upLinkVFile.getCanonicalFile(), repeatedLinksVFile.getCanonicalFile());
102   }
103
104   @Test
105   public void testMutualRecursiveLinks() throws Exception {
106     File circularDir1 = myTempDir.newFolder("dir1");
107     File circularDir2 = myTempDir.newFolder("dir2");
108     File circularLink1 = createSymLink(circularDir2.getPath(), circularDir1 + "/link1");
109     File circularLink2 = createSymLink(circularDir1.getPath(), circularDir2 + "/link2");
110     VirtualFile circularLink1VFile = refreshAndFind(circularLink1);
111     VirtualFile circularLink2VFile = refreshAndFind(circularLink2);
112     assertNotNull(circularLink1VFile);
113     assertNotNull(circularLink2VFile);
114     assertVisitedPaths(circularDir1.getPath(), circularLink1.getPath(), circularLink1.getPath() + "/" + circularLink2.getName(),
115                        circularDir2.getPath(), circularLink2.getPath(), circularLink2.getPath() + "/" + circularLink1.getName());
116   }
117
118   @Test
119   public void testDuplicateLinks() throws Exception {
120     File targetDir = myTempDir.newFolder("target");
121     File link1 = createSymLink(targetDir.getPath(), myTempDir.getRoot() + "/link1");
122     File link2 = createSymLink(targetDir.getPath(), myTempDir.getRoot() + "/link2");
123     assertVisitedPaths(targetDir.getPath(), link1.getPath(), link2.getPath());
124   }
125
126   @Test
127   public void testSidewaysRecursiveLink() throws Exception {
128     File target = myTempDir.newFolder("dir_a");
129     File link1Home = createTestDir(target, "dir_b");
130     File link1 = createSymLink(SystemInfo.isWindows ? target.getPath() : "../../" + target.getName(), link1Home.getPath() + "/link1");
131     File mainDir = myTempDir.newFolder("project");
132     File subDir = createTestDir(mainDir, "dir_c");
133     File link2Home = createTestDir(subDir, "dir_d");
134     File link2 = createSymLink(SystemInfo.isWindows ? target.getPath() : "../../../" + target.getName(), link2Home.getPath() + "/link2");
135     assertVisitedPaths(mainDir,
136                        subDir.getPath(), link2Home.getPath(), link2.getPath(), link2.getPath() + "/" + link1Home.getName(),
137                        link2.getPath() + "/" + link1Home.getName() + "/" + link1.getName());
138   }
139
140   @Test
141   public void testVisitAllNonRecursiveLinks() throws Exception {
142     File target = myTempDir.newFolder("target");
143     File child = createTestDir(target, "child");
144     File link1 = createSymLink(target.getPath(), myTempDir.getRoot() + "/link1");
145     File link2 = createSymLink(target.getPath(), myTempDir.getRoot() + "/link2");
146     assertVisitedPaths(target.getPath(), child.getPath(),
147                        link1.getPath(), link1.getPath() + "/child",
148                        link2.getPath(), link2.getPath() + "/child");
149   }
150
151   @Test
152   public void testTargetIsWritable() throws Exception {
153     File targetFile = myTempDir.newFile("target.txt");
154     File linkFile = createSymLink(targetFile.getPath(), myTempDir.getRoot() + "/link");
155     VirtualFile linkVFile = refreshAndFind(linkFile);
156     assertTrue("link=" + linkFile + ", vLink=" + linkVFile, linkVFile != null && !linkVFile.isDirectory() &&
157                                                             linkVFile.is(VFileProperty.SYMLINK));
158
159     setWritableAndCheck(targetFile, true);
160     refresh();
161     assertTrue(linkVFile.getPath(), linkVFile.isWritable());
162     setWritableAndCheck(targetFile, false);
163     refresh();
164     assertFalse(linkVFile.getPath(), linkVFile.isWritable());
165
166     File targetDir = myTempDir.newFolder("target");
167     File linkDir = createSymLink(targetDir.getPath(), myTempDir.getRoot() + "/linkDir");
168     VirtualFile linkVDir = refreshAndFind(linkDir);
169     assertTrue("link=" + linkDir + ", vLink=" + linkVDir, linkVDir != null && linkVDir.isDirectory() && linkVDir.is(VFileProperty.SYMLINK));
170
171     if (!SystemInfo.isWindows) {
172       setWritableAndCheck(targetDir, true);
173       refresh();
174       assertTrue(linkVDir.getPath(), linkVDir.isWritable());
175       setWritableAndCheck(targetDir, false);
176       refresh();
177       assertFalse(linkVDir.getPath(), linkVDir.isWritable());
178     }
179     else {
180       assertEquals(linkVDir.getPath(), targetDir.canWrite(), linkVDir.isWritable());
181     }
182   }
183
184   private static void setWritableAndCheck(File file, boolean writable) {
185     assertTrue(file.getPath(), file.setWritable(writable, false));
186     assertEquals(file.getPath(), writable, file.canWrite());
187   }
188
189   @Test
190   public void testLinkDeleteIsSafe() throws Exception {
191     File targetFile = myTempDir.newFile("target");
192     File linkFile = createSymLink(targetFile.getPath(), myTempDir.getRoot() + "/link");
193     VirtualFile linkVFile = refreshAndFind(linkFile);
194     assertTrue("link=" + linkFile + ", vLink=" + linkVFile,
195                linkVFile != null && !linkVFile.isDirectory() && linkVFile.is(VFileProperty.SYMLINK));
196
197     new WriteAction() {
198       @Override
199       protected void run(@NotNull Result result) throws Throwable {
200         linkVFile.delete(SymlinkHandlingTest.this);
201       }
202     }.execute();
203     assertFalse(linkVFile.toString(), linkVFile.isValid());
204     assertFalse(linkFile.exists());
205     assertTrue(targetFile.exists());
206
207     File targetDir = myTempDir.newFolder("targetDir");
208     File childFile = new File(targetDir, "child.txt");
209     assertTrue(childFile.getPath(), childFile.exists() || childFile.createNewFile());
210     File linkDir = createSymLink(targetDir.getPath(), myTempDir.getRoot() + "/linkDir");
211     VirtualFile linkVDir = refreshAndFind(linkDir);
212     assertTrue("link=" + linkDir + ", vLink=" + linkVDir,
213                linkVDir != null && linkVDir.isDirectory() && linkVDir.is(VFileProperty.SYMLINK) && linkVDir.getChildren().length == 1);
214
215     new WriteAction() {
216       @Override
217       protected void run(@NotNull Result result) throws Throwable {
218         linkVDir.delete(SymlinkHandlingTest.this);
219       }
220     }.execute();
221     assertFalse(linkVDir.toString(), linkVDir.isValid());
222     assertFalse(linkDir.exists());
223     assertTrue(targetDir.exists());
224     assertTrue(childFile.exists());
225   }
226
227   @Test
228   public void testTransGenderRefresh() throws Exception {
229     File targetFile = myTempDir.newFile("target");
230     File targetDir = myTempDir.newFolder("targetDir");
231
232     // file link
233     File link = createSymLink(targetFile.getPath(), myTempDir.getRoot() + "/link");
234     VirtualFile vFile1 = refreshAndFind(link);
235     assertTrue("link=" + link + ", vLink=" + vFile1,
236                vFile1 != null && !vFile1.isDirectory() && vFile1.is(VFileProperty.SYMLINK));
237
238     // file link => dir
239     assertTrue(link.getPath(), link.delete() && link.mkdir() && link.isDirectory());
240     VirtualFile vFile2 = refreshAndFind(link);
241     assertTrue("link=" + link + ", vLink=" + vFile2,
242                !vFile1.isValid() && vFile2 != null && vFile2.isDirectory() && !vFile2.is(VFileProperty.SYMLINK));
243
244     // dir => dir link
245     assertTrue(link.getPath(), link.delete());
246     link = createSymLink(targetDir.getPath(), myTempDir.getRoot() + "/link");
247     vFile1 = refreshAndFind(link);
248     assertTrue("link=" + link + ", vLink=" + vFile1,
249                !vFile2.isValid() && vFile1 != null && vFile1.isDirectory() && vFile1.is(VFileProperty.SYMLINK));
250
251     // dir link => file
252     assertTrue(link.getPath(), link.delete() && link.createNewFile() && link.isFile());
253     vFile2 = refreshAndFind(link);
254     assertTrue("link=" + link + ", vLink=" + vFile1,
255                !vFile1.isValid() && vFile2 != null && !vFile2.isDirectory() && !vFile2.is(VFileProperty.SYMLINK));
256
257     // file => file link
258     assertTrue(link.getPath(), link.delete());
259     link = createSymLink(targetFile.getPath(), myTempDir.getRoot() + "/link");
260     vFile1 = refreshAndFind(link);
261     assertTrue("link=" + link + ", vLink=" + vFile1,
262                !vFile2.isValid() && vFile1 != null && !vFile1.isDirectory() && vFile1.is(VFileProperty.SYMLINK));
263   }
264
265   @Test
266   public void testDirLinkSwitchWithDifferentlenghtContent() throws Exception {
267     doTestDirLinkSwitch("text", "longer text");
268   }
269
270   @Test
271   public void testDirLinkSwitchWithSameLengthContent() throws Exception {
272     doTestDirLinkSwitch("text 1", "text 2");
273   }
274
275   private void doTestDirLinkSwitch(String text1, String text2) throws Exception {
276     File targetDir1 = myTempDir.newFolder("target1");
277     File targetDir2 = myTempDir.newFolder("target2");
278     
279     File target1Child = new File(targetDir1, "child1.txt");
280     assertTrue(target1Child.createNewFile());
281     File target2Child = new File(targetDir2, "child1.txt");
282     assertTrue(target2Child.createNewFile());
283     assertTrue(new File(targetDir2, "child2.txt").createNewFile());
284     FileUtil.writeToFile(target1Child, text1);
285     FileUtil.writeToFile(target2Child, text2);
286
287     File link = createSymLink(targetDir1.getPath(), myTempDir.getRoot() + "/link");
288     VirtualFile vLink1 = refreshAndFind(link);
289     assertTrue("link=" + link + ", vLink=" + vLink1,
290                vLink1 != null && vLink1.isDirectory() && vLink1.is(VFileProperty.SYMLINK));
291     assertEquals(1, vLink1.getChildren().length);
292     assertPathsEqual(targetDir1.getPath(), vLink1.getCanonicalPath());
293     assertEquals(FileUtil.loadFile(target1Child), VfsUtilCore.loadText(vLink1.findChild("child1.txt")));
294
295     assertTrue(link.toString(), link.delete());
296     createSymLink(targetDir2.getPath(), myTempDir.getRoot() + "/" + link.getName());
297
298     refresh();
299     assertTrue(vLink1.isValid());
300     VirtualFile vLink2 = LocalFileSystem.getInstance().findFileByIoFile(link);
301     assertEquals(vLink1, vLink2);
302     assertTrue("link=" + link + ", vLink=" + vLink2,
303                vLink2 != null && vLink2.isDirectory() && vLink2.is(VFileProperty.SYMLINK));
304     assertEquals(2, vLink2.getChildren().length);
305     assertPathsEqual(targetDir2.getPath(), vLink1.getCanonicalPath());
306     assertEquals(FileUtil.loadFile(target2Child), VfsUtilCore.loadText(vLink1.findChild("child1.txt")));
307     assertEquals(FileUtil.loadFile(target2Child), VfsUtilCore.loadText(vLink2.findChild("child1.txt")));
308   }
309
310   @Test
311   public void testFileLinkSwitchWithDifferentlenghtContent() throws Exception {
312     doTestLinkSwitch("text", "longer text");
313   }
314
315   @Test
316   public void testFileLinkSwitchWithSameLengthContent() throws Exception {
317     doTestLinkSwitch("text 1", "text 2");
318   }
319
320   private void doTestLinkSwitch(String text1, String text2) throws IOException, InterruptedException {
321     File target1 = myTempDir.newFile("target1.txt");
322     FileUtil.writeToFile(target1, text1);
323     File target2 = myTempDir.newFile("target2.txt");
324     FileUtil.writeToFile(target2, text2);
325
326     File link = createSymLink(target1.getPath(), myTempDir.getRoot() + "/link");
327     VirtualFile vLink1 = refreshAndFind(link);
328     assertTrue("link=" + link + ", vLink=" + vLink1,
329                vLink1 != null && !vLink1.isDirectory() && vLink1.is(VFileProperty.SYMLINK));
330     assertEquals(FileUtil.loadFile(target1), VfsUtilCore.loadText(vLink1));
331     assertPathsEqual(target1.getPath(), vLink1.getCanonicalPath());
332
333     assertTrue(link.toString(), link.delete());
334     createSymLink(target2.getPath(), myTempDir.getRoot() + "/" + link.getName());
335
336     refresh();
337     assertTrue(vLink1.isValid());
338     VirtualFile vLink2 = LocalFileSystem.getInstance().findFileByIoFile(link);
339     VfsUtilCore.loadText(vLink2);
340     assertEquals(vLink1, vLink2);
341     assertTrue("link=" + link + ", vLink=" + vLink2,
342                vLink2 != null && !vLink2.isDirectory() && vLink2.is(VFileProperty.SYMLINK));
343     assertEquals(FileUtil.loadFile(target2), VfsUtilCore.loadText(vLink1));
344     assertEquals(FileUtil.loadFile(target2), VfsUtilCore.loadText(vLink2));
345     assertPathsEqual(target2.getPath(), vLink1.getCanonicalPath());
346   }
347
348   @Test
349   public void testTraversePathBehindLink() throws Exception {
350     File topDir = myTempDir.newFolder("top");
351     File subDir1 = createTestDir(topDir, "sub1");
352     File link = createSymLink(subDir1.getPath(), myTempDir.getRoot() + "/link");
353     VirtualFile vLink = refreshAndFind(link);
354     assertNotNull(link.getPath(), vLink);
355
356     File subDir2 = createTestDir(topDir, "sub2");
357     File subChild = createTestFile(subDir2, "subChild.txt");
358     VirtualFile vSubChild = refreshAndFind(subChild);
359     assertNotNull(subChild.getPath(), vSubChild);
360
361     String relPath = "../" + subDir2.getName() + "/" + subChild.getName();
362     VirtualFile vSubChildRel;
363     vSubChildRel = vLink.findFileByRelativePath(relPath);
364     assertEquals(vSubChild, vSubChildRel);
365     vSubChildRel = LocalFileSystem.getInstance().findFileByPath(vLink.getPath() + "/" + relPath);
366     assertEquals(vSubChild, vSubChildRel);
367   }
368
369
370   @Nullable
371   private VirtualFile refreshAndFind(File ioFile) {
372     refresh();
373     return LocalFileSystem.getInstance().findFileByPath(ioFile.getPath());
374   }
375
376   protected void refresh() {
377     VirtualFile tempDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(myTempDir.getRoot());
378     assertNotNull(myTempDir.getRoot().getPath(), tempDir);
379
380     tempDir.getChildren();
381     tempDir.refresh(false, true);
382     VfsUtilCore.visitChildrenRecursively(tempDir, new VirtualFileVisitor() { });
383   }
384
385   private static void assertBrokenLink(@NotNull VirtualFile link) {
386     assertTrue(link.is(VFileProperty.SYMLINK));
387     assertEquals(0, link.getLength());
388     assertNull(link.getCanonicalPath(), link.getCanonicalPath());
389   }
390
391   private void assertVisitedPaths(String... expected) {
392     assertVisitedPaths(myTempDir.getRoot(), expected);
393   }
394
395   private void assertVisitedPaths(File from, String... expected) {
396     VirtualFile vDir = refreshAndFind(from);
397     assertNotNull(vDir);
398
399     Set<String> expectedSet =
400       Stream.concat(Stream.of(expected).map(FileUtil::toSystemIndependentName), Stream.of(vDir.getPath())).collect(Collectors.toSet());
401
402     Set<String> actualSet = new java.util.HashSet<>();
403     VfsUtilCore.visitChildrenRecursively(vDir, new VirtualFileVisitor() {
404       @Override
405       public boolean visitFile(@NotNull VirtualFile file) {
406         actualSet.add(file.getPath());
407         return true;
408       }
409     });
410
411     assertEquals(expectedSet, actualSet);
412   }
413 }