View Javadoc
1   /*
2    * Copyright (C) 2012-2013, Robin Rosenberg <robin.rosenberg@dewire.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 java.time.Instant.EPOCH;
14  import static org.junit.Assert.assertEquals;
15  import static org.junit.Assert.assertFalse;
16  import static org.junit.Assert.assertTrue;
17  import static org.junit.Assume.assumeNoException;
18  import static org.junit.Assume.assumeTrue;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.nio.charset.Charset;
23  import java.nio.file.Files;
24  import java.nio.file.InvalidPathException;
25  import java.nio.file.Path;
26  import java.nio.file.attribute.FileTime;
27  import java.nio.file.attribute.PosixFileAttributeView;
28  import java.nio.file.attribute.PosixFilePermission;
29  import java.time.Duration;
30  import java.time.ZoneId;
31  import java.time.format.DateTimeFormatter;
32  import java.util.Locale;
33  import java.util.Set;
34  import java.util.concurrent.TimeUnit;
35  
36  import org.eclipse.jgit.errors.CommandFailedException;
37  import org.eclipse.jgit.junit.MockSystemReader;
38  import org.eclipse.jgit.junit.RepositoryTestCase;
39  import org.eclipse.jgit.lib.RepositoryCache;
40  import org.junit.After;
41  import org.junit.Assume;
42  import org.junit.Before;
43  import org.junit.Test;
44  
45  public class FSTest {
46  	private File trash;
47  
48  	@Before
49  	public void setUp() throws Exception {
50  		SystemReader.setInstance(new MockSystemReader());
51  		trash = File.createTempFile("tmp_", "");
52  		trash.delete();
53  		assertTrue("mkdir " + trash, trash.mkdir());
54  	}
55  
56  	@After
57  	public void tearDown() throws Exception {
58  		FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.RETRY);
59  	}
60  
61  	/**
62  	 * The old File methods traverse symbolic links and look at the targets.
63  	 * With symbolic links we usually want to modify/look at the link. For some
64  	 * reason the executable attribute seems to always look at the target, but
65  	 * for the other attributes like lastModified, hidden and exists we must
66  	 * differ between the link and the target.
67  	 *
68  	 * @throws IOException
69  	 * @throws InterruptedException
70  	 */
71  	@Test
72  	public void testSymlinkAttributes() throws IOException, InterruptedException {
73  		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
74  		FS fs = FS.DETECTED;
75  		File link = new File(trash, "a");
76  		File target = new File(trash, "b");
77  		fs.createSymLink(link, "b");
78  		assertTrue(fs.exists(link));
79  		String targetName = fs.readSymLink(link);
80  		assertEquals("b", targetName);
81  		assertTrue(fs.lastModifiedInstant(link).compareTo(EPOCH) > 0);
82  		assertTrue(fs.exists(link));
83  		assertFalse(fs.canExecute(link));
84  		// The length of a symbolic link is a length of the target file path.
85  		assertEquals(1, fs.length(link));
86  		assertFalse(fs.exists(target));
87  		assertFalse(fs.isFile(target));
88  		assertFalse(fs.isDirectory(target));
89  		assertFalse(fs.canExecute(target));
90  
91  		RepositoryTestCase.fsTick(link);
92  		// Now create the link target
93  		FileUtils.createNewFile(target);
94  		assertTrue(fs.exists(link));
95  		assertTrue(fs.lastModifiedInstant(link).compareTo(EPOCH) > 0);
96  		assertTrue(fs.lastModifiedInstant(target)
97  				.compareTo(fs.lastModifiedInstant(link)) > 0);
98  		assertFalse(fs.canExecute(link));
99  		fs.setExecute(target, true);
100 		assertFalse(fs.canExecute(link));
101 		assumeTrue(fs.supportsExecute());
102 		assertTrue(fs.canExecute(target));
103 	}
104 
105 	@Test
106 	public void testUnicodeFilePath() throws IOException {
107 		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
108 		FS fs = FS.DETECTED;
109 		File link = new File(trash, "ä");
110 		File target = new File(trash, "å");
111 
112 		try {
113 			// Check if the runtime can support Unicode file paths.
114 			link.toPath();
115 			target.toPath();
116 		} catch (InvalidPathException e) {
117 			// When executing a test with LANG environment variable set to non
118 			// UTF-8 encoding, it seems that JRE cannot handle Unicode file
119 			// paths. This happens when this test is executed in Bazel as it
120 			// unsets LANG
121 			// (https://docs.bazel.build/versions/master/test-encyclopedia.html#initial-conditions).
122 			// Skip the test if the runtime cannot handle Unicode characters.
123 			assumeNoException(e);
124 		}
125 
126 		fs.createSymLink(link, "å");
127 		assertTrue(fs.exists(link));
128 		assertEquals("å", fs.readSymLink(link));
129 	}
130 
131 	@Test
132 	public void testExecutableAttributes() throws Exception {
133 		FS fs = FS.DETECTED.newInstance();
134 		// If this assumption fails the test is halted and ignored.
135 		assumeTrue(fs instanceof FS_POSIX);
136 		((FS_POSIX) fs).setUmask(0022);
137 
138 		File f = new File(trash, "bla");
139 		assertTrue(f.createNewFile());
140 		assertFalse(fs.canExecute(f));
141 
142 		Set<PosixFilePermission> permissions = readPermissions(f);
143 		assertTrue(!permissions.contains(PosixFilePermission.OTHERS_EXECUTE));
144 		assertTrue(!permissions.contains(PosixFilePermission.GROUP_EXECUTE));
145 		assertTrue(!permissions.contains(PosixFilePermission.OWNER_EXECUTE));
146 
147 		fs.setExecute(f, true);
148 
149 		permissions = readPermissions(f);
150 		assertTrue("'owner' execute permission not set",
151 				permissions.contains(PosixFilePermission.OWNER_EXECUTE));
152 		assertTrue("'group' execute permission not set",
153 				permissions.contains(PosixFilePermission.GROUP_EXECUTE));
154 		assertTrue("'others' execute permission not set",
155 				permissions.contains(PosixFilePermission.OTHERS_EXECUTE));
156 
157 		((FS_POSIX) fs).setUmask(0033);
158 		fs.setExecute(f, false);
159 		assertFalse(fs.canExecute(f));
160 		fs.setExecute(f, true);
161 
162 		permissions = readPermissions(f);
163 		assertTrue("'owner' execute permission not set",
164 				permissions.contains(PosixFilePermission.OWNER_EXECUTE));
165 		assertFalse("'group' execute permission set",
166 				permissions.contains(PosixFilePermission.GROUP_EXECUTE));
167 		assertFalse("'others' execute permission set",
168 				permissions.contains(PosixFilePermission.OTHERS_EXECUTE));
169 	}
170 
171 	private Set<PosixFilePermission> readPermissions(File f) throws IOException {
172 		return Files
173 				.getFileAttributeView(f.toPath(), PosixFileAttributeView.class)
174 				.readAttributes().permissions();
175 	}
176 
177 	@Test(expected = CommandFailedException.class)
178 	public void testReadPipePosixCommandFailure()
179 			throws CommandFailedException {
180 		FS fs = FS.DETECTED.newInstance();
181 		assumeTrue(fs instanceof FS_POSIX);
182 
183 		FS.readPipe(fs.userHome(),
184 				new String[] { "/bin/sh", "-c", "exit 1" },
185 				Charset.defaultCharset().name());
186 	}
187 
188 	@Test(expected = CommandFailedException.class)
189 	public void testReadPipeCommandStartFailure()
190 			throws CommandFailedException {
191 		FS fs = FS.DETECTED.newInstance();
192 
193 		FS.readPipe(fs.userHome(),
194 				  new String[] { "this-command-does-not-exist" },
195 				  Charset.defaultCharset().name());
196 	}
197 
198 	@Test
199 	public void testFsTimestampResolution() throws Exception {
200 		DateTimeFormatter formatter = DateTimeFormatter
201 				.ofPattern("uuuu-MMM-dd HH:mm:ss.nnnnnnnnn", Locale.ENGLISH)
202 				.withZone(ZoneId.systemDefault());
203 		Path dir = Files.createTempDirectory("probe-filesystem");
204 		Duration resolution = FS.getFileStoreAttributes(dir)
205 				.getFsTimestampResolution();
206 		long resolutionNs = resolution.toNanos();
207 		assertTrue(resolutionNs > 0);
208 		for (int i = 0; i < 10; i++) {
209 			Path f = null;
210 			try {
211 				f = dir.resolve("testTimestampResolution" + i);
212 				Files.createFile(f);
213 				FileUtils.touch(f);
214 				FileTime t1 = Files.getLastModifiedTime(f);
215 				TimeUnit.NANOSECONDS.sleep(resolutionNs);
216 				FileUtils.touch(f);
217 				FileTime t2 = Files.getLastModifiedTime(f);
218 				assertTrue(String.format(
219 						"expected t2=%s to be larger than t1=%s\nsince file timestamp resolution was measured to be %,d ns",
220 						formatter.format(t2.toInstant()),
221 						formatter.format(t1.toInstant()),
222 						Long.valueOf(resolutionNs)), t2.compareTo(t1) > 0);
223 			} finally {
224 				if (f != null) {
225 					Files.delete(f);
226 				}
227 			}
228 		}
229 	}
230 
231 	// bug 548682
232 	@Test
233 	public void testRepoCacheRelativePathUnbornRepo() {
234 		assertFalse(RepositoryCache.FileKey
235 				.isGitRepository(new File("repo.git"), FS.DETECTED));
236 	}
237 }