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