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