View Javadoc
1   /*
2    * Copyright (C) 2010, 2013 Matthias Sohn <matthias.sohn@sap.com> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.util;
12  
13  import static org.junit.Assert.assertEquals;
14  import static org.junit.Assert.assertFalse;
15  import static org.junit.Assert.assertTrue;
16  import static org.junit.Assert.fail;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.io.UnsupportedEncodingException;
21  import java.nio.file.Files;
22  import java.nio.file.StandardCopyOption;
23  import java.rmi.RemoteException;
24  import java.util.regex.Matcher;
25  
26  import javax.management.remote.JMXProviderException;
27  
28  import org.eclipse.jgit.junit.JGitTestUtil;
29  import org.junit.After;
30  import org.junit.Assume;
31  import org.junit.Before;
32  import org.junit.Test;
33  
34  public class FileUtilsTest {
35  	private static final String MSG = "Stale file handle";
36  
37  	private static final String SOME_ERROR_MSG = "some error message";
38  
39  	private static final IOException IO_EXCEPTION = new UnsupportedEncodingException(
40  			MSG);
41  
42  	private static final IOException IO_EXCEPTION_WITH_CAUSE = new RemoteException(
43  			SOME_ERROR_MSG,
44  			new JMXProviderException(SOME_ERROR_MSG, IO_EXCEPTION));
45  
46  	private File trash;
47  
48  	@Before
49  	public void setUp() throws Exception {
50  		trash = File.createTempFile("tmp_", "");
51  		trash.delete();
52  		assertTrue("mkdir " + trash, trash.mkdir());
53  	}
54  
55  	@After
56  	public void tearDown() throws Exception {
57  		FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.RETRY);
58  	}
59  
60  	@Test
61  	public void testDeleteFile() throws IOException {
62  		File f = new File(trash, "test");
63  		FileUtils.createNewFile(f);
64  		FileUtils.delete(f);
65  		assertFalse(f.exists());
66  
67  		try {
68  			FileUtils.delete(f);
69  			fail("deletion of non-existing file must fail");
70  		} catch (IOException e) {
71  			// expected
72  		}
73  
74  		try {
75  			FileUtils.delete(f, FileUtils.SKIP_MISSING);
76  		} catch (IOException e) {
77  			fail("deletion of non-existing file must not fail with option SKIP_MISSING");
78  		}
79  	}
80  
81  	@Test
82  	public void testDeleteReadOnlyFile() throws IOException {
83  		File f = new File(trash, "f");
84  		FileUtils.createNewFile(f);
85  		assertTrue(f.setReadOnly());
86  		FileUtils.delete(f);
87  		assertFalse(f.exists());
88  	}
89  
90  	@Test
91  	public void testDeleteRecursive() throws IOException {
92  		File f1 = new File(trash, "test/test/a");
93  		FileUtils.mkdirs(f1.getParentFile());
94  		FileUtils.createNewFile(f1);
95  		File f2 = new File(trash, "test/test/b");
96  		FileUtils.createNewFile(f2);
97  		File d = new File(trash, "test");
98  		FileUtils.delete(d, FileUtils.RECURSIVE);
99  		assertFalse(d.exists());
100 
101 		try {
102 			FileUtils.delete(d, FileUtils.RECURSIVE);
103 			fail("recursive deletion of non-existing directory must fail");
104 		} catch (IOException e) {
105 			// expected
106 		}
107 
108 		try {
109 			FileUtils.delete(d, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
110 		} catch (IOException e) {
111 			fail("recursive deletion of non-existing directory must not fail with option SKIP_MISSING");
112 		}
113 	}
114 
115 	@Test
116 	public void testDeleteRecursiveEmpty() throws IOException {
117 		File f1 = new File(trash, "test/test/a");
118 		File f2 = new File(trash, "test/a");
119 		File d1 = new File(trash, "test");
120 		File d2 = new File(trash, "test/test");
121 		File d3 = new File(trash, "test/b");
122 		FileUtils.mkdirs(f1.getParentFile());
123 		FileUtils.createNewFile(f2);
124 		FileUtils.createNewFile(f1);
125 		FileUtils.mkdirs(d3);
126 
127 		// Cannot delete hierarchy since files exist
128 		try {
129 			FileUtils.delete(d1, FileUtils.EMPTY_DIRECTORIES_ONLY);
130 			fail("delete should fail");
131 		} catch (IOException e1) {
132 			try {
133 				FileUtils.delete(d1, FileUtils.EMPTY_DIRECTORIES_ONLY|FileUtils.RECURSIVE);
134 				fail("delete should fail");
135 			} catch (IOException e2) {
136 				// Everything still there
137 				assertTrue(f1.exists());
138 				assertTrue(f2.exists());
139 				assertTrue(d1.exists());
140 				assertTrue(d2.exists());
141 				assertTrue(d3.exists());
142 			}
143 		}
144 
145 		// setup: delete files, only directories left
146 		assertTrue(f1.delete());
147 		assertTrue(f2.delete());
148 
149 		// Shall not delete hierarchy without recursive
150 		try {
151 			FileUtils.delete(d1, FileUtils.EMPTY_DIRECTORIES_ONLY);
152 			fail("delete should fail");
153 		} catch (IOException e2) {
154 			// Everything still there
155 			assertTrue(d1.exists());
156 			assertTrue(d2.exists());
157 			assertTrue(d3.exists());
158 		}
159 
160 		// Now delete the empty hierarchy
161 		FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY
162 				| FileUtils.RECURSIVE);
163 		assertFalse(d2.exists());
164 
165 		// Will fail to delete non-existing without SKIP_MISSING
166 		try {
167 			FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY);
168 			fail("Cannot delete non-existent entity");
169 		} catch (IOException e) {
170 			// ok
171 		}
172 
173 		// ..with SKIP_MISSING there is no exception
174 		FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY
175 				| FileUtils.SKIP_MISSING);
176 		FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY
177 				| FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
178 
179 		// essentially the same, using IGNORE_ERRORS
180 		FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY
181 				| FileUtils.IGNORE_ERRORS);
182 		FileUtils.delete(d2, FileUtils.EMPTY_DIRECTORIES_ONLY
183 				| FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS);
184 	}
185 
186 	@Test
187 	public void testDeleteRecursiveEmptyNeedsToCheckFilesFirst()
188 			throws IOException {
189 		File d1 = new File(trash, "test");
190 		File d2 = new File(trash, "test/a");
191 		File d3 = new File(trash, "test/b");
192 		File f1 = new File(trash, "test/c");
193 		File d4 = new File(trash, "test/d");
194 		FileUtils.mkdirs(d1);
195 		FileUtils.mkdirs(d2);
196 		FileUtils.mkdirs(d3);
197 		FileUtils.mkdirs(d4);
198 		FileUtils.createNewFile(f1);
199 
200 		// Cannot delete hierarchy since file exists
201 		try {
202 			FileUtils.delete(d1, FileUtils.EMPTY_DIRECTORIES_ONLY
203 					| FileUtils.RECURSIVE);
204 			fail("delete should fail");
205 		} catch (IOException e) {
206 			// Everything still there
207 			assertTrue(f1.exists());
208 			assertTrue(d1.exists());
209 			assertTrue(d2.exists());
210 			assertTrue(d3.exists());
211 			assertTrue(d4.exists());
212 		}
213 	}
214 
215 	@Test
216 	public void testDeleteRecursiveEmptyDirectoriesOnlyButIsFile()
217 			throws IOException {
218 		File f1 = new File(trash, "test/test/a");
219 		FileUtils.mkdirs(f1.getParentFile());
220 		FileUtils.createNewFile(f1);
221 		try {
222 			FileUtils.delete(f1, FileUtils.EMPTY_DIRECTORIES_ONLY);
223 			fail("delete should fail");
224 		} catch (IOException e) {
225 			assertTrue(f1.exists());
226 		}
227 	}
228 
229 	@Test
230 	public void testMkdir() throws IOException {
231 		File d = new File(trash, "test");
232 		FileUtils.mkdir(d);
233 		assertTrue(d.exists() && d.isDirectory());
234 
235 		try {
236 			FileUtils.mkdir(d);
237 			fail("creation of existing directory must fail");
238 		} catch (IOException e) {
239 			// expected
240 		}
241 
242 		FileUtils.mkdir(d, true);
243 		assertTrue(d.exists() && d.isDirectory());
244 
245 		assertTrue(d.delete());
246 		File f = new File(trash, "test");
247 		FileUtils.createNewFile(f);
248 		try {
249 			FileUtils.mkdir(d);
250 			fail("creation of directory having same path as existing file must"
251 					+ " fail");
252 		} catch (IOException e) {
253 			// expected
254 		}
255 		assertTrue(f.delete());
256 	}
257 
258 	@Test
259 	public void testMkdirs() throws IOException {
260 		File root = new File(trash, "test");
261 		assertTrue(root.mkdir());
262 
263 		File d = new File(root, "test/test");
264 		FileUtils.mkdirs(d);
265 		assertTrue(d.exists() && d.isDirectory());
266 
267 		try {
268 			FileUtils.mkdirs(d);
269 			fail("creation of existing directory hierarchy must fail");
270 		} catch (IOException e) {
271 			// expected
272 		}
273 
274 		FileUtils.mkdirs(d, true);
275 		assertTrue(d.exists() && d.isDirectory());
276 
277 		FileUtils.delete(root, FileUtils.RECURSIVE);
278 		File f = new File(trash, "test");
279 		FileUtils.createNewFile(f);
280 		try {
281 			FileUtils.mkdirs(d);
282 			fail("creation of directory having path conflicting with existing"
283 					+ " file must fail");
284 		} catch (IOException e) {
285 			// expected
286 		}
287 		assertTrue(f.delete());
288 	}
289 
290 	@Test
291 	public void testCreateNewFile() throws IOException {
292 		File f = new File(trash, "x");
293 		FileUtils.createNewFile(f);
294 		assertTrue(f.exists());
295 
296 		try {
297 			FileUtils.createNewFile(f);
298 			fail("creation of already existing file must fail");
299 		} catch (IOException e) {
300 			// expected
301 		}
302 
303 		FileUtils.delete(f);
304 	}
305 
306 	@Test
307 	public void testDeleteEmptyTreeOk() throws IOException {
308 		File t = new File(trash, "t");
309 		FileUtils.mkdir(t);
310 		FileUtils.mkdir(new File(t, "d"));
311 		FileUtils.mkdir(new File(new File(t, "d"), "e"));
312 		FileUtils.delete(t, FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.RECURSIVE);
313 		assertFalse(t.exists());
314 	}
315 
316 	@Test
317 	public void testDeleteNotEmptyTreeNotOk() throws IOException {
318 		File t = new File(trash, "t");
319 		FileUtils.mkdir(t);
320 		FileUtils.mkdir(new File(t, "d"));
321 		File f = new File(new File(t, "d"), "f");
322 		FileUtils.createNewFile(f);
323 		FileUtils.mkdir(new File(new File(t, "d"), "e"));
324 		try {
325 			FileUtils.delete(t, FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.RECURSIVE);
326 			fail("expected failure to delete f");
327 		} catch (IOException e) {
328 			assertTrue(e.getMessage().endsWith(f.getAbsolutePath()));
329 		}
330 		assertTrue(t.exists());
331 	}
332 
333 	@Test
334 	public void testDeleteNotEmptyTreeNotOkButIgnoreFail() throws IOException {
335 		File t = new File(trash, "t");
336 		FileUtils.mkdir(t);
337 		FileUtils.mkdir(new File(t, "d"));
338 		File f = new File(new File(t, "d"), "f");
339 		FileUtils.createNewFile(f);
340 		File e = new File(new File(t, "d"), "e");
341 		FileUtils.mkdir(e);
342 		FileUtils.delete(t, FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.RECURSIVE
343 				| FileUtils.IGNORE_ERRORS);
344 		// Should have deleted as much as possible, but not all
345 		assertTrue(t.exists());
346 		assertTrue(f.exists());
347 		assertFalse(e.exists());
348 	}
349 
350 	@Test
351 	public void testDeleteNonRecursiveTreeNotOk() throws IOException {
352 		File t = new File(trash, "t");
353 		FileUtils.mkdir(t);
354 		File f = new File(t, "f");
355 		FileUtils.createNewFile(f);
356 		try {
357 			FileUtils.delete(t, FileUtils.EMPTY_DIRECTORIES_ONLY);
358 			fail("expected failure to delete f");
359 		} catch (IOException e) {
360 			assertTrue(e.getMessage().endsWith(t.getAbsolutePath()));
361 		}
362 		assertTrue(f.exists());
363 		assertTrue(t.exists());
364 	}
365 
366 	@Test
367 	public void testDeleteNonRecursiveTreeIgnoreError() throws IOException {
368 		File t = new File(trash, "t");
369 		FileUtils.mkdir(t);
370 		File f = new File(t, "f");
371 		FileUtils.createNewFile(f);
372 		FileUtils.delete(t,
373 				FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.IGNORE_ERRORS);
374 		assertTrue(f.exists());
375 		assertTrue(t.exists());
376 	}
377 
378 	@Test
379 	public void testRenameOverNonExistingFile() throws IOException {
380 		File d = new File(trash, "d");
381 		FileUtils.mkdirs(d);
382 		File f1 = new File(trash, "d/f");
383 		File f2 = new File(trash, "d/g");
384 		JGitTestUtil.write(f1, "f1");
385 		// test
386 		FileUtils.rename(f1, f2);
387 		assertFalse(f1.exists());
388 		assertTrue(f2.exists());
389 		assertEquals("f1", JGitTestUtil.read(f2));
390 	}
391 
392 	@Test
393 	public void testRenameOverExistingFile() throws IOException {
394 		File d = new File(trash, "d");
395 		FileUtils.mkdirs(d);
396 		File f1 = new File(trash, "d/f");
397 		File f2 = new File(trash, "d/g");
398 		JGitTestUtil.write(f1, "f1");
399 		JGitTestUtil.write(f2, "f2");
400 		// test
401 		FileUtils.rename(f1, f2);
402 		assertFalse(f1.exists());
403 		assertTrue(f2.exists());
404 		assertEquals("f1", JGitTestUtil.read(f2));
405 	}
406 
407 	@Test
408 	public void testRenameOverExistingNonEmptyDirectory() throws IOException {
409 		File d = new File(trash, "d");
410 		FileUtils.mkdirs(d);
411 		File f1 = new File(trash, "d/f");
412 		File f2 = new File(trash, "d/g");
413 		File d1 = new File(trash, "d/g/h/i");
414 		File f3 = new File(trash, "d/g/h/f");
415 		FileUtils.mkdirs(d1);
416 		JGitTestUtil.write(f1, "f1");
417 		JGitTestUtil.write(f3, "f3");
418 		// test
419 		try {
420 			FileUtils.rename(f1, f2);
421 			fail("rename to non-empty directory should fail");
422 		} catch (IOException e) {
423 			assertEquals("f1", JGitTestUtil.read(f1)); // untouched source
424 			assertEquals("f3", JGitTestUtil.read(f3)); // untouched
425 			// empty directories within f2 may or may not have been deleted
426 		}
427 	}
428 
429 	@Test
430 	public void testRenameOverExistingEmptyDirectory() throws IOException {
431 		File d = new File(trash, "d");
432 		FileUtils.mkdirs(d);
433 		File f1 = new File(trash, "d/f");
434 		File f2 = new File(trash, "d/g");
435 		File d1 = new File(trash, "d/g/h/i");
436 		FileUtils.mkdirs(d1);
437 		JGitTestUtil.write(f1, "f1");
438 		// test
439 		FileUtils.rename(f1, f2);
440 		assertFalse(f1.exists());
441 		assertTrue(f2.exists());
442 		assertEquals("f1", JGitTestUtil.read(f2));
443 	}
444 
445 	@Test
446 	public void testCreateSymlink() throws IOException {
447 		FS fs = FS.DETECTED;
448 		// show test as ignored if the FS doesn't support symlinks
449 		Assume.assumeTrue(fs.supportsSymlinks());
450 		fs.createSymLink(new File(trash, "x"), "y");
451 		String target = fs.readSymLink(new File(trash, "x"));
452 		assertEquals("y", target);
453 	}
454 
455 	@Test
456 	public void testCreateSymlinkOverrideExisting() throws IOException {
457 		FS fs = FS.DETECTED;
458 		// show test as ignored if the FS doesn't support symlinks
459 		Assume.assumeTrue(fs.supportsSymlinks());
460 		File file = new File(trash, "x");
461 		fs.createSymLink(file, "y");
462 		String target = fs.readSymLink(file);
463 		assertEquals("y", target);
464 		fs.createSymLink(file, "z");
465 		target = fs.readSymLink(file);
466 		assertEquals("z", target);
467 	}
468 
469 	@Test
470 	public void testRelativize_doc() {
471 		// This is the example from the javadoc
472 		String base = toOSPathString("c:\\Users\\jdoe\\eclipse\\git\\project");
473 		String other = toOSPathString("c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml");
474 		String expected = toOSPathString("..\\another_project\\pom.xml");
475 
476 		String actual = FileUtils.relativizeNativePath(base, other);
477 		assertEquals(expected, actual);
478 	}
479 
480 	@Test
481 	public void testRelativize_mixedCase() {
482 		SystemReader systemReader = SystemReader.getInstance();
483 		String base = toOSPathString("C:\\git\\jgit");
484 		String other = toOSPathString("C:\\Git\\test\\d\\f.txt");
485 		String expectedCaseInsensitive = toOSPathString("..\\test\\d\\f.txt");
486 		String expectedCaseSensitive = toOSPathString("..\\..\\Git\\test\\d\\f.txt");
487 
488 		if (systemReader.isWindows()) {
489 			String actual = FileUtils.relativizeNativePath(base, other);
490 			assertEquals(expectedCaseInsensitive, actual);
491 		} else if (systemReader.isMacOS()) {
492 			String actual = FileUtils.relativizeNativePath(base, other);
493 			assertEquals(expectedCaseInsensitive, actual);
494 		} else {
495 			String actual = FileUtils.relativizeNativePath(base, other);
496 			assertEquals(expectedCaseSensitive, actual);
497 		}
498 	}
499 
500 	@Test
501 	public void testRelativize_scheme() {
502 		String base = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1/file.java");
503 		String other = toOSPathString("file:/home/eclipse/runtime-New_configuration/project");
504 		// 'file.java' is treated as a folder
505 		String expected = toOSPathString("../../project");
506 
507 		String actual = FileUtils.relativizeNativePath(base, other);
508 		assertEquals(expected, actual);
509 	}
510 
511 	@Test
512 	public void testRelativize_equalPaths() {
513 		String base = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1");
514 		String other = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1");
515 		String expected = "";
516 
517 		String actual = FileUtils.relativizeNativePath(base, other);
518 		assertEquals(expected, actual);
519 	}
520 
521 	@Test
522 	public void testRelativize_whitespaces() {
523 		String base = toOSPathString("/home/eclipse 3.4/runtime New_configuration/project_1");
524 		String other = toOSPathString("/home/eclipse 3.4/runtime New_configuration/project_1/file");
525 		String expected = "file";
526 
527 		String actual = FileUtils.relativizeNativePath(base, other);
528 		assertEquals(expected, actual);
529 	}
530 
531 	@Test
532 	public void testDeleteSymlinkToDirectoryDoesNotDeleteTarget()
533 			throws IOException {
534 		org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
535 		FS fs = FS.DETECTED;
536 		File dir = new File(trash, "dir");
537 		File file = new File(dir, "file");
538 		File link = new File(trash, "link");
539 		FileUtils.mkdirs(dir);
540 		FileUtils.createNewFile(file);
541 		fs.createSymLink(link, "dir");
542 		FileUtils.delete(link, FileUtils.RECURSIVE);
543 		assertFalse(link.exists());
544 		assertTrue(dir.exists());
545 		assertTrue(file.exists());
546 	}
547 
548 	@Test
549 	public void testAtomicMove() throws IOException {
550 		File src = new File(trash, "src");
551 		Files.createFile(src.toPath());
552 		File dst = new File(trash, "dst");
553 		FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE);
554 		assertFalse(Files.exists(src.toPath()));
555 		assertTrue(Files.exists(dst.toPath()));
556 	}
557 
558 	private String toOSPathString(String path) {
559 		return path.replaceAll("/|\\\\",
560 				Matcher.quoteReplacement(File.separator));
561 	}
562 
563 	@Test
564 	public void testIsStaleFileHandleWithDirectCause() throws Exception {
565 		assertTrue(FileUtils.isStaleFileHandle(IO_EXCEPTION));
566 	}
567 
568 	@Test
569 	public void testIsStaleFileHandleWithIndirectCause() throws Exception {
570 		assertFalse(
571 				FileUtils.isStaleFileHandle(IO_EXCEPTION_WITH_CAUSE));
572 	}
573 
574 	@Test
575 	public void testIsStaleFileHandleInCausalChainWithDirectCause()
576 			throws Exception {
577 		assertTrue(
578 				FileUtils.isStaleFileHandleInCausalChain(IO_EXCEPTION));
579 	}
580 
581 	@Test
582 	public void testIsStaleFileHandleInCausalChainWithIndirectCause()
583 			throws Exception {
584 		assertTrue(FileUtils
585 				.isStaleFileHandleInCausalChain(IO_EXCEPTION_WITH_CAUSE));
586 	}
587 }