blob: ca7de18880838cc1cd86255db093529bdffd8fd3 [file] [log] [blame]
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +00001// Copyright 2015 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.skyframe;
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +000015import static com.google.common.truth.Truth.assertThat;
16import static com.google.devtools.build.lib.skyframe.SkyframeExecutor.DEFAULT_THREAD_COUNT;
17import static org.junit.Assert.assertArrayEquals;
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +000018import static org.junit.Assert.assertEquals;
19import static org.junit.Assert.assertFalse;
20import static org.junit.Assert.assertNotSame;
21import static org.junit.Assert.assertNull;
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +000022import static org.junit.Assert.assertTrue;
23import static org.junit.Assert.fail;
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +000024
25import com.google.common.base.Function;
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +000026import com.google.common.base.Strings;
27import com.google.common.collect.ImmutableList;
28import com.google.common.collect.ImmutableMap;
29import com.google.common.collect.ImmutableSet;
30import com.google.common.collect.Iterables;
31import com.google.common.collect.Lists;
32import com.google.common.collect.Maps;
33import com.google.common.collect.Sets;
34import com.google.common.testing.EqualsTester;
Kristina Chodorowf9fdc8d2015-12-08 12:49:31 +000035import com.google.devtools.build.lib.analysis.BlazeDirectories;
36import com.google.devtools.build.lib.cmdline.PackageIdentifier;
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +000037import com.google.devtools.build.lib.events.NullEventHandler;
38import com.google.devtools.build.lib.events.StoredEventHandler;
Kristina Chodorowf9fdc8d2015-12-08 12:49:31 +000039import com.google.devtools.build.lib.packages.PackageFactory;
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +000040import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
41import com.google.devtools.build.lib.testutil.ManualClock;
Kristina Chodorowf9fdc8d2015-12-08 12:49:31 +000042import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +000043import com.google.devtools.build.lib.testutil.TestUtils;
44import com.google.devtools.build.lib.util.BlazeClock;
45import com.google.devtools.build.lib.util.Pair;
Mark Schaller6df81792015-12-10 18:47:47 +000046import com.google.devtools.build.lib.util.Preconditions;
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +000047import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
48import com.google.devtools.build.lib.vfs.FileStatus;
49import com.google.devtools.build.lib.vfs.FileSystem;
50import com.google.devtools.build.lib.vfs.FileSystemUtils;
51import com.google.devtools.build.lib.vfs.Path;
52import com.google.devtools.build.lib.vfs.PathFragment;
53import com.google.devtools.build.lib.vfs.RootedPath;
54import com.google.devtools.build.lib.vfs.UnixFileSystem;
55import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
56import com.google.devtools.build.skyframe.ErrorInfo;
57import com.google.devtools.build.skyframe.EvaluationResult;
58import com.google.devtools.build.skyframe.InMemoryMemoizingEvaluator;
59import com.google.devtools.build.skyframe.MemoizingEvaluator;
60import com.google.devtools.build.skyframe.RecordingDifferencer;
61import com.google.devtools.build.skyframe.SequentialBuildDriver;
Kristina Chodorowf9fdc8d2015-12-08 12:49:31 +000062import com.google.devtools.build.skyframe.SkyFunction;
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +000063import com.google.devtools.build.skyframe.SkyFunctionName;
64import com.google.devtools.build.skyframe.SkyKey;
65import com.google.devtools.build.skyframe.SkyValue;
66
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +000067import org.junit.Before;
68import org.junit.Test;
69import org.junit.runner.RunWith;
70import org.junit.runners.JUnit4;
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +000071
72import java.io.ByteArrayInputStream;
73import java.io.ByteArrayOutputStream;
74import java.io.IOException;
75import java.io.ObjectInputStream;
76import java.io.ObjectOutputStream;
77import java.io.OutputStream;
78import java.util.List;
79import java.util.Map;
80import java.util.Set;
81import java.util.UUID;
82import java.util.concurrent.atomic.AtomicInteger;
83import java.util.concurrent.atomic.AtomicReference;
84
85import javax.annotation.Nullable;
86
87/**
88 * Tests for {@link FileFunction}.
89 */
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +000090@RunWith(JUnit4.class)
91public class FileFunctionTest {
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +000092 private CustomInMemoryFs fs;
93 private Path pkgRoot;
Lukacs Berkid3262d12015-10-30 14:33:51 +000094 private Path outputBase;
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +000095 private PathPackageLocator pkgLocator;
96 private TimestampGranularityMonitor tsgm;
97 private boolean fastMd5;
98 private ManualClock manualClock;
99 private RecordingDifferencer differencer;
100
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000101 @Before
Florian Weikert92b22362015-12-03 10:17:18 +0000102 public final void createMonitor() throws Exception {
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000103 fastMd5 = true;
104 manualClock = new ManualClock();
105 createFsAndRoot(new CustomInMemoryFs(manualClock));
106 tsgm = new TimestampGranularityMonitor(BlazeClock.instance());
107 }
108
109 private void createFsAndRoot(CustomInMemoryFs fs) throws IOException {
110 this.fs = fs;
111 pkgRoot = fs.getRootDirectory().getRelative("root");
Lukacs Berkid3262d12015-10-30 14:33:51 +0000112 outputBase = fs.getRootDirectory().getRelative("output_base");
113 pkgLocator = new PathPackageLocator(outputBase, ImmutableList.of(pkgRoot));
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000114 FileSystemUtils.createDirectoryAndParents(pkgRoot);
115 }
116
117 private SequentialBuildDriver makeDriver() {
118 return makeDriver(/*errorOnExternalFiles=*/ false);
119 }
120
121 private SequentialBuildDriver makeDriver(boolean errorOnExternalFiles) {
122 AtomicReference<PathPackageLocator> pkgLocatorRef = new AtomicReference<>(pkgLocator);
123 ExternalFilesHelper externalFilesHelper =
124 new ExternalFilesHelper(pkgLocatorRef, errorOnExternalFiles);
125 differencer = new RecordingDifferencer();
126 MemoizingEvaluator evaluator =
127 new InMemoryMemoizingEvaluator(
Kristina Chodorowf9fdc8d2015-12-08 12:49:31 +0000128 ImmutableMap.<SkyFunctionName, SkyFunction>builder()
129 .put(SkyFunctions.FILE_STATE, new FileStateFunction(tsgm, externalFilesHelper))
130 .put(SkyFunctions.FILE_SYMLINK_CYCLE_UNIQUENESS,
131 new FileSymlinkCycleUniquenessFunction())
132 .put(SkyFunctions.FILE_SYMLINK_INFINITE_EXPANSION_UNIQUENESS,
133 new FileSymlinkInfiniteExpansionUniquenessFunction())
134 .put(SkyFunctions.FILE, new FileFunction(pkgLocatorRef))
135 .put(SkyFunctions.PACKAGE,
136 new PackageFunction(null, null, null, null, null, null, null))
137 .put(SkyFunctions.PACKAGE_LOOKUP,
138 new PackageLookupFunction(new AtomicReference<>(
139 ImmutableSet.<PackageIdentifier>of())))
140 .put(SkyFunctions.WORKSPACE_FILE,
141 new WorkspaceFileFunction(TestRuleClassProvider.getRuleClassProvider(),
142 new PackageFactory(TestRuleClassProvider.getRuleClassProvider()),
143 new BlazeDirectories(pkgRoot, outputBase, pkgRoot)))
144 .build(),
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000145 differencer);
146 PrecomputedValue.BUILD_ID.set(differencer, UUID.randomUUID());
Kristina Chodorowf9fdc8d2015-12-08 12:49:31 +0000147 PrecomputedValue.PATH_PACKAGE_LOCATOR.set(differencer, pkgLocator);
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000148 return new SequentialBuildDriver(evaluator);
149 }
150
151 private FileValue valueForPath(Path path) throws InterruptedException {
152 return valueForPathHelper(pkgRoot, path);
153 }
154
155 private FileValue valueForPathOutsidePkgRoot(Path path) throws InterruptedException {
156 return valueForPathHelper(fs.getRootDirectory(), path);
157 }
158
159 private FileValue valueForPathHelper(Path root, Path path) throws InterruptedException {
160 PathFragment pathFragment = path.relativeTo(root);
161 RootedPath rootedPath = RootedPath.toRootedPath(root, pathFragment);
162 SequentialBuildDriver driver = makeDriver();
163 SkyKey key = FileValue.key(rootedPath);
164 EvaluationResult<FileValue> result =
165 driver.evaluate(
166 ImmutableList.of(key), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
167 assertFalse(result.hasError());
168 return result.get(key);
169 }
170
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000171 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000172 public void testFileValueHashCodeAndEqualsContract() throws Exception {
173 Path pathA = file(pkgRoot + "a", "a");
174 Path pathB = file(pkgRoot + "b", "b");
175 FileValue valueA1 = valueForPathOutsidePkgRoot(pathA);
176 FileValue valueA2 = valueForPathOutsidePkgRoot(pathA);
177 FileValue valueB1 = valueForPathOutsidePkgRoot(pathB);
178 FileValue valueB2 = valueForPathOutsidePkgRoot(pathB);
179 new EqualsTester()
180 .addEqualityGroup(valueA1, valueA2)
181 .addEqualityGroup(valueB1, valueB2)
182 .testEquals();
183 }
184
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000185 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000186 public void testIsDirectory() throws Exception {
187 assertFalse(valueForPath(file("a")).isDirectory());
188 assertFalse(valueForPath(path("nonexistent")).isDirectory());
189 assertTrue(valueForPath(directory("dir")).isDirectory());
190
191 assertFalse(valueForPath(symlink("sa", "a")).isDirectory());
192 assertFalse(valueForPath(symlink("smissing", "missing")).isDirectory());
193 assertTrue(valueForPath(symlink("sdir", "dir")).isDirectory());
194 assertTrue(valueForPath(symlink("ssdir", "sdir")).isDirectory());
195 }
196
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000197 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000198 public void testIsFile() throws Exception {
199 assertTrue(valueForPath(file("a")).isFile());
200 assertFalse(valueForPath(path("nonexistent")).isFile());
201 assertFalse(valueForPath(directory("dir")).isFile());
202
203 assertTrue(valueForPath(symlink("sa", "a")).isFile());
204 assertFalse(valueForPath(symlink("smissing", "missing")).isFile());
205 assertFalse(valueForPath(symlink("sdir", "dir")).isFile());
206 assertTrue(valueForPath(symlink("ssfile", "sa")).isFile());
207 }
208
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000209 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000210 public void testSimpleIndependentFiles() throws Exception {
211 file("a");
212 file("b");
213
214 Set<RootedPath> seenFiles = Sets.newHashSet();
215 seenFiles.addAll(getFilesSeenAndAssertValueChangesIfContentsOfFileChanges("a", false, "b"));
216 seenFiles.addAll(getFilesSeenAndAssertValueChangesIfContentsOfFileChanges("b", false, "a"));
217 assertThat(seenFiles).containsExactly(rootedPath("a"), rootedPath("b"), rootedPath(""));
218 }
219
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000220 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000221 public void testSimpleSymlink() throws Exception {
222 symlink("a", "b");
223 file("b");
224
225 assertValueChangesIfContentsOfFileChanges("a", false, "b");
226 assertValueChangesIfContentsOfFileChanges("b", true, "a");
227 }
228
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000229 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000230 public void testTransitiveSymlink() throws Exception {
231 symlink("a", "b");
232 symlink("b", "c");
233 file("c");
234
235 assertValueChangesIfContentsOfFileChanges("a", false, "b");
236 assertValueChangesIfContentsOfFileChanges("a", false, "c");
237 assertValueChangesIfContentsOfFileChanges("b", true, "a");
238 assertValueChangesIfContentsOfFileChanges("c", true, "b");
239 assertValueChangesIfContentsOfFileChanges("c", true, "a");
240 }
241
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000242 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000243 public void testFileUnderDirectorySymlink() throws Exception {
244 symlink("a", "b/c");
245 symlink("b", "d");
246 assertValueChangesIfContentsOfDirectoryChanges("b", true, "a/e");
247 }
248
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000249 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000250 public void testSymlinkInDirectory() throws Exception {
251 symlink("a/aa", "ab");
252 file("a/ab");
253
254 assertValueChangesIfContentsOfFileChanges("a/aa", false, "a/ab");
255 assertValueChangesIfContentsOfFileChanges("a/ab", true, "a/aa");
256 }
257
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000258 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000259 public void testRelativeSymlink() throws Exception {
260 symlink("a/aa/aaa", "../ab/aba");
261 file("a/ab/aba");
262 assertValueChangesIfContentsOfFileChanges("a/ab/aba", true, "a/aa/aaa");
263 }
264
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000265 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000266 public void testDoubleRelativeSymlink() throws Exception {
267 symlink("a/b/c/d", "../../e/f");
268 file("a/e/f");
269 assertValueChangesIfContentsOfFileChanges("a/e/f", true, "a/b/c/d");
270 }
271
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000272 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000273 public void testExternalRelativeSymlink() throws Exception {
274 symlink("a", "../outside");
275 file("b");
276 file("../outside");
277 Set<RootedPath> seenFiles = Sets.newHashSet();
278 seenFiles.addAll(getFilesSeenAndAssertValueChangesIfContentsOfFileChanges("b", false, "a"));
279 seenFiles.addAll(
280 getFilesSeenAndAssertValueChangesIfContentsOfFileChanges("../outside", true, "a"));
281 assertThat(seenFiles)
282 .containsExactly(
Kristina Chodorowf9fdc8d2015-12-08 12:49:31 +0000283 rootedPath("WORKSPACE"),
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000284 rootedPath("a"),
285 rootedPath(""),
286 RootedPath.toRootedPath(fs.getRootDirectory(), PathFragment.EMPTY_FRAGMENT),
287 RootedPath.toRootedPath(fs.getRootDirectory(), new PathFragment("outside")));
288 }
289
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000290 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000291 public void testAbsoluteSymlink() throws Exception {
292 symlink("a", "/absolute");
293 file("b");
294 file("/absolute");
295 Set<RootedPath> seenFiles = Sets.newHashSet();
296 seenFiles.addAll(getFilesSeenAndAssertValueChangesIfContentsOfFileChanges("b", false, "a"));
297 seenFiles.addAll(
298 getFilesSeenAndAssertValueChangesIfContentsOfFileChanges("/absolute", true, "a"));
299 assertThat(seenFiles)
300 .containsExactly(
Kristina Chodorowf9fdc8d2015-12-08 12:49:31 +0000301 rootedPath("WORKSPACE"),
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000302 rootedPath("a"),
303 rootedPath(""),
304 RootedPath.toRootedPath(fs.getRootDirectory(), PathFragment.EMPTY_FRAGMENT),
305 RootedPath.toRootedPath(fs.getRootDirectory(), new PathFragment("absolute")));
306 }
307
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000308 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000309 public void testSymlinkAsAncestor() throws Exception {
310 file("a/b/c/d");
311 symlink("f", "a/b/c");
312 assertValueChangesIfContentsOfFileChanges("a/b/c/d", true, "f/d");
313 }
314
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000315 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000316 public void testSymlinkAsAncestorNested() throws Exception {
317 file("a/b/c/d");
318 symlink("f", "a/b");
319 assertValueChangesIfContentsOfFileChanges("a/b/c/d", true, "f/c/d");
320 }
321
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000322 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000323 public void testTwoSymlinksInAncestors() throws Exception {
324 file("a/aa/aaa/aaaa");
325 symlink("b/ba/baa", "../../a/aa");
326 symlink("c/ca", "../b/ba");
327
328 assertValueChangesIfContentsOfFileChanges("c/ca", true, "c/ca/baa/aaa/aaaa");
329 assertValueChangesIfContentsOfFileChanges("b/ba/baa", true, "c/ca/baa/aaa/aaaa");
330 assertValueChangesIfContentsOfFileChanges("a/aa/aaa/aaaa", true, "c/ca/baa/aaa/aaaa");
331 }
332
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000333 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000334 public void testSelfReferencingSymlink() throws Exception {
335 symlink("a", "a");
336 assertError("a");
337 }
338
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000339 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000340 public void testMutuallyReferencingSymlinks() throws Exception {
341 symlink("a", "b");
342 symlink("b", "a");
343 assertError("a");
344 }
345
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000346 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000347 public void testRecursiveNestingSymlink() throws Exception {
348 symlink("a/a", "../a");
349 assertError("a/a/b");
350 }
351
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000352 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000353 public void testBrokenSymlink() throws Exception {
354 symlink("a", "b");
355 Set<RootedPath> seenFiles = Sets.newHashSet();
356 seenFiles.addAll(getFilesSeenAndAssertValueChangesIfContentsOfFileChanges("b", true, "a"));
357 seenFiles.addAll(getFilesSeenAndAssertValueChangesIfContentsOfFileChanges("a", false, "b"));
358 assertThat(seenFiles).containsExactly(rootedPath("a"), rootedPath("b"), rootedPath(""));
359 }
360
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000361 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000362 public void testBrokenDirectorySymlink() throws Exception {
363 symlink("a", "b");
364 file("c");
365
366 assertValueChangesIfContentsOfDirectoryChanges("a", true, "a/aa");
367 // This just creates the directory "b", which doesn't change the value for "a/aa", since "a/aa"
368 // still has real path "b/aa" and still doesn't exist.
369 assertValueChangesIfContentsOfDirectoryChanges("b", false, "a/aa");
370 assertValueChangesIfContentsOfFileChanges("c", false, "a/aa");
371 }
372
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000373 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000374 public void testTraverseIntoVirtualNonDirectory() throws Exception {
375 file("dir/a");
376 symlink("vdir", "dir");
377 // The following evaluation should not throw IOExceptions.
378 assertNoError("vdir/a/aa/aaa");
379 }
380
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000381 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000382 public void testFileCreation() throws Exception {
383 FileValue a = valueForPath(path("file"));
384 Path p = file("file");
385 FileValue b = valueForPath(p);
386 assertFalse(a.equals(b));
387 }
388
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000389 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000390 public void testEmptyFile() throws Exception {
391 final byte[] digest = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
392 createFsAndRoot(
393 new CustomInMemoryFs(manualClock) {
394 @Override
395 protected String getFastDigestFunctionType(Path path) {
396 return "magic";
397 }
398
399 @Override
400 protected byte[] getFastDigest(Path path) throws IOException {
401 return digest;
402 }
403 });
404 Path p = file("file");
405 p.setLastModifiedTime(0L);
406 FileValue a = valueForPath(p);
407 p.setLastModifiedTime(1L);
408 assertThat(valueForPath(p)).isNotEqualTo(a);
409 p.setLastModifiedTime(0L);
410 assertEquals(a, valueForPath(p));
411 FileSystemUtils.writeContentAsLatin1(p, "content");
412 // Same digest, but now non-empty.
413 assertThat(valueForPath(p)).isNotEqualTo(a);
414 }
415
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000416 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000417 public void testFileModificationModTime() throws Exception {
418 fastMd5 = false;
419 Path p = file("file");
420 FileValue a = valueForPath(p);
421 p.setLastModifiedTime(42);
422 FileValue b = valueForPath(p);
423 assertFalse(a.equals(b));
424 }
425
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000426 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000427 public void testFileModificationDigest() throws Exception {
428 fastMd5 = true;
429 Path p = file("file");
430 FileValue a = valueForPath(p);
431 FileSystemUtils.writeContentAsLatin1(p, "goop");
432 FileValue b = valueForPath(p);
433 assertFalse(a.equals(b));
434 }
435
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000436 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000437 public void testModTimeVsDigest() throws Exception {
438 Path p = file("somefile", "fizzley");
439
440 fastMd5 = true;
441 FileValue aMd5 = valueForPath(p);
442 fastMd5 = false;
443 FileValue aModTime = valueForPath(p);
444 assertThat(aModTime).isNotEqualTo(aMd5);
445 new EqualsTester().addEqualityGroup(aMd5).addEqualityGroup(aModTime).testEquals();
446 }
447
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000448 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000449 public void testFileDeletion() throws Exception {
450 Path p = file("file");
451 FileValue a = valueForPath(p);
452 p.delete();
453 FileValue b = valueForPath(p);
454 assertFalse(a.equals(b));
455 }
456
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000457 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000458 public void testFileTypeChange() throws Exception {
459 Path p = file("file");
460 FileValue a = valueForPath(p);
461 p.delete();
462 p = symlink("file", "foo");
463 FileValue b = valueForPath(p);
464 p.delete();
465 FileSystemUtils.createDirectoryAndParents(pkgRoot.getRelative("file"));
466 FileValue c = valueForPath(p);
467 assertFalse(a.equals(b));
468 assertFalse(b.equals(c));
469 assertFalse(a.equals(c));
470 }
471
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000472 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000473 public void testSymlinkTargetChange() throws Exception {
474 Path p = symlink("symlink", "foo");
475 FileValue a = valueForPath(p);
476 p.delete();
477 p = symlink("symlink", "bar");
478 FileValue b = valueForPath(p);
479 assertThat(b).isNotEqualTo(a);
480 }
481
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000482 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000483 public void testSymlinkTargetContentsChangeModTime() throws Exception {
484 fastMd5 = false;
485 Path fooPath = file("foo");
486 FileSystemUtils.writeContentAsLatin1(fooPath, "foo");
487 Path p = symlink("symlink", "foo");
488 FileValue a = valueForPath(p);
489 fooPath.setLastModifiedTime(88);
490 FileValue b = valueForPath(p);
491 assertThat(b).isNotEqualTo(a);
492 }
493
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000494 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000495 public void testSymlinkTargetContentsChangeDigest() throws Exception {
496 fastMd5 = true;
497 Path fooPath = file("foo");
498 FileSystemUtils.writeContentAsLatin1(fooPath, "foo");
499 Path p = symlink("symlink", "foo");
500 FileValue a = valueForPath(p);
501 FileSystemUtils.writeContentAsLatin1(fooPath, "bar");
502 FileValue b = valueForPath(p);
503 assertThat(b).isNotEqualTo(a);
504 }
505
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000506 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000507 public void testRealPath() throws Exception {
508 file("file");
509 directory("directory");
510 file("directory/file");
511 symlink("directory/link", "file");
512 symlink("directory/doublelink", "link");
513 symlink("directory/parentlink", "../file");
514 symlink("directory/doubleparentlink", "../link");
515 symlink("link", "file");
516 symlink("deadlink", "missing_file");
517 symlink("dirlink", "directory");
518 symlink("doublelink", "link");
519 symlink("doubledirlink", "dirlink");
520
521 checkRealPath("file");
522 checkRealPath("link");
523 checkRealPath("doublelink");
524
525 for (String dir : new String[] {"directory", "dirlink", "doubledirlink"}) {
526 checkRealPath(dir);
527 checkRealPath(dir + "/file");
528 checkRealPath(dir + "/link");
529 checkRealPath(dir + "/doublelink");
530 checkRealPath(dir + "/parentlink");
531 }
532
533 assertRealPath("missing", "missing");
534 assertRealPath("deadlink", "missing_file");
535 }
536
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000537 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000538 public void testRealPathRelativeSymlink() throws Exception {
539 directory("dir");
540 symlink("dir/link", "../dir2");
541 directory("dir2");
542 symlink("dir2/filelink", "../dest");
543 file("dest");
544
545 checkRealPath("dir/link/filelink");
546 }
547
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000548 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000549 public void testSymlinkAcrossPackageRoots() throws Exception {
550 Path otherPkgRoot = fs.getRootDirectory().getRelative("other_root");
Lukacs Berkid3262d12015-10-30 14:33:51 +0000551 pkgLocator = new PathPackageLocator(outputBase, ImmutableList.of(pkgRoot, otherPkgRoot));
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000552 symlink("a", "/other_root/b");
553 assertValueChangesIfContentsOfFileChanges("/other_root/b", true, "a");
554 }
555
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000556 @Test
Kristina Chodorowf9fdc8d2015-12-08 12:49:31 +0000557 public void testFilesOutsideRootIsReEvaluated() throws Exception {
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000558 Path file = file("/outsideroot");
559 SequentialBuildDriver driver = makeDriver();
560 SkyKey key = skyKey("/outsideroot");
561 EvaluationResult<SkyValue> result;
562 result =
563 driver.evaluate(
564 ImmutableList.of(key), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
565 if (result.hasError()) {
566 fail(String.format("Evaluation error for %s: %s", key, result.getError()));
567 }
568 FileValue oldValue = (FileValue) result.get(key);
569 assertTrue(oldValue.exists());
570
571 file.delete();
Kristina Chodorowf9fdc8d2015-12-08 12:49:31 +0000572 differencer.invalidate(ImmutableList.of(fileStateSkyKey("/outsideroot")));
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000573 result =
574 driver.evaluate(
575 ImmutableList.of(key), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
576 if (result.hasError()) {
577 fail(String.format("Evaluation error for %s: %s", key, result.getError()));
578 }
579 FileValue newValue = (FileValue) result.get(key);
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000580 assertNotSame(oldValue, newValue);
581 assertFalse(newValue.exists());
582 }
583
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000584 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000585 public void testFilesOutsideRootWhenExternalDisallowed() throws Exception {
586 file("/outsideroot");
587
588 SequentialBuildDriver driver = makeDriver(/*errorOnExternalFiles=*/ true);
589 SkyKey key = skyKey("/outsideroot");
590 EvaluationResult<SkyValue> result =
591 driver.evaluate(
592 ImmutableList.of(key), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
593
594 assertTrue(result.hasError());
595 assertThat(result.getError(key).getException())
596 .isInstanceOf(FileOutsidePackageRootsException.class);
597 }
598
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000599 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000600 public void testAbsoluteSymlinksToFilesOutsideRootWhenExternalDisallowed() throws Exception {
601 file("/outsideroot");
602 symlink("a", "/outsideroot");
603
604 SequentialBuildDriver driver = makeDriver(/*errorOnExternalFiles=*/ true);
605 SkyKey key = skyKey("a");
606 EvaluationResult<SkyValue> result =
607 driver.evaluate(
608 ImmutableList.of(key), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
609
610 assertTrue(result.hasError());
611 assertThat(result.getError(key).getException())
612 .isInstanceOf(FileOutsidePackageRootsException.class);
613 }
614
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000615 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000616 public void testRelativeSymlinksToFilesOutsideRootWhenExternalDisallowed() throws Exception {
617 file("../outsideroot");
618 symlink("a", "../outsideroot");
619 SequentialBuildDriver driver = makeDriver(/*errorOnExternalFiles=*/ true);
620 SkyKey key = skyKey("a");
621 EvaluationResult<SkyValue> result =
622 driver.evaluate(
623 ImmutableList.of(key), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
624 assertTrue(result.hasError());
625 assertThat(result.getError(key).getException())
626 .isInstanceOf(FileOutsidePackageRootsException.class);
627 }
628
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000629 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000630 public void testAbsoluteSymlinksBackIntoSourcesOkWhenExternalDisallowed() throws Exception {
631 Path file = file("insideroot");
632 symlink("a", file.getPathString());
633
634 SequentialBuildDriver driver = makeDriver(/*allowExternalReferences=*/ false);
635 SkyKey key = skyKey("a");
636 EvaluationResult<SkyValue> result =
637 driver.evaluate(
638 ImmutableList.of(key), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
639
640 assertFalse(result.hasError());
641 }
642
643 @SuppressWarnings({"rawtypes", "unchecked"})
644 private static Set<RootedPath> filesSeen(MemoizingEvaluator graph) {
645 return ImmutableSet.copyOf(
646 (Iterable<RootedPath>)
647 (Iterable)
648 Iterables.transform(
649 Iterables.filter(
650 graph.getValues().keySet(),
651 SkyFunctionName.functionIs(SkyFunctions.FILE_STATE)),
652 SkyKey.NODE_NAME));
653 }
654
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000655 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000656 public void testSize() throws Exception {
657 Path file = file("file");
658 int fileSize = 20;
659 FileSystemUtils.writeContentAsLatin1(file, Strings.repeat("a", fileSize));
660 assertEquals(fileSize, valueForPath(file).getSize());
661 Path dir = directory("directory");
662 file(dir.getChild("child").getPathString());
663 try {
664 valueForPath(dir).getSize();
665 fail();
666 } catch (IllegalStateException e) {
667 // Expected.
668 }
669 Path nonexistent = fs.getPath("/root/noexist");
670 try {
671 valueForPath(nonexistent).getSize();
672 fail();
673 } catch (IllegalStateException e) {
674 // Expected.
675 }
676 Path symlink = symlink("link", "/root/file");
677 // Symlink stores size of target, not link.
678 assertEquals(fileSize, valueForPath(symlink).getSize());
679 assertTrue(symlink.delete());
680 symlink = symlink("link", "/root/directory");
681 try {
682 valueForPath(symlink).getSize();
683 fail();
684 } catch (IllegalStateException e) {
685 // Expected.
686 }
687 assertTrue(symlink.delete());
688 symlink = symlink("link", "/root/noexist");
689 try {
690 valueForPath(symlink).getSize();
691 fail();
692 } catch (IllegalStateException e) {
693 // Expected.
694 }
695 }
696
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000697 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000698 public void testDigest() throws Exception {
699 final AtomicInteger digestCalls = new AtomicInteger(0);
700 int expectedCalls = 0;
701 fs =
702 new CustomInMemoryFs(manualClock) {
703 @Override
704 protected byte[] getMD5Digest(Path path) throws IOException {
705 digestCalls.incrementAndGet();
706 return super.getMD5Digest(path);
707 }
708 };
709 pkgRoot = fs.getRootDirectory().getRelative("root");
710 Path file = file("file");
711 FileSystemUtils.writeContentAsLatin1(file, Strings.repeat("a", 20));
712 byte[] digest = file.getMD5Digest();
713 expectedCalls++;
714 assertEquals(expectedCalls, digestCalls.get());
715 FileValue value = valueForPath(file);
716 expectedCalls++;
717 assertEquals(expectedCalls, digestCalls.get());
718 assertArrayEquals(digest, value.getDigest());
719 // Digest is cached -- no filesystem access.
720 assertEquals(expectedCalls, digestCalls.get());
721 fastMd5 = false;
722 digestCalls.set(0);
723 value = valueForPath(file);
724 // No new digest calls.
725 assertEquals(0, digestCalls.get());
726 assertNull(value.getDigest());
727 assertEquals(0, digestCalls.get());
728 fastMd5 = true;
729 Path dir = directory("directory");
730 try {
731 assertNull(valueForPath(dir).getDigest());
732 fail();
733 } catch (IllegalStateException e) {
734 // Expected.
735 }
736 assertEquals(0, digestCalls.get()); // No digest calls made for directory.
737 Path nonexistent = fs.getPath("/root/noexist");
738 try {
739 assertNull(valueForPath(nonexistent).getDigest());
740 fail();
741 } catch (IllegalStateException e) {
742 // Expected.
743 }
744 assertEquals(0, digestCalls.get()); // No digest calls made for nonexistent file.
745 Path symlink = symlink("link", "/root/file");
746 value = valueForPath(symlink);
747 assertEquals(1, digestCalls.get());
748 // Symlink stores digest of target, not link.
749 assertArrayEquals(digest, value.getDigest());
750 assertEquals(1, digestCalls.get());
751 digestCalls.set(0);
752 assertTrue(symlink.delete());
753 symlink = symlink("link", "/root/directory");
754 // Symlink stores digest of target, not link, for directories too.
755 try {
756 assertNull(valueForPath(symlink).getDigest());
757 fail();
758 } catch (IllegalStateException e) {
759 // Expected.
760 }
761 assertEquals(0, digestCalls.get());
762 }
763
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000764 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000765 public void testFilesystemInconsistencies_ParentDoesntExistAndChildIsSymlink() throws Exception {
766 symlink("a/b", "doesntmatter");
767 // Our custom filesystem says "a/b" exists but "a" does not exist.
768 fs.stubStat(path("a"), null);
769 SequentialBuildDriver driver = makeDriver();
770 SkyKey skyKey = skyKey("a/b");
771 EvaluationResult<FileValue> result =
772 driver.evaluate(
773 ImmutableList.of(skyKey), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
774 assertTrue(result.hasError());
775 ErrorInfo errorInfo = result.getError(skyKey);
776 assertThat(errorInfo.getException()).isInstanceOf(InconsistentFilesystemException.class);
777 assertThat(errorInfo.getException().getMessage())
778 .contains(
779 "/root/a/b was a symlink to doesntmatter but others made us think it was a "
780 + "nonexistent path");
781 }
782
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000783 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000784 public void testFilesystemInconsistencies_ParentIsntADirectory() throws Exception {
785 file("a/b");
786 // Our custom filesystem says "a/b" exists but its parent "a" is a file.
787 FileStatus inconsistentParentFileStatus =
788 new FileStatus() {
789 @Override
790 public boolean isFile() {
791 return true;
792 }
793
794 @Override
795 public boolean isSpecialFile() {
796 return false;
797 }
798
799 @Override
800 public boolean isDirectory() {
801 return false;
802 }
803
804 @Override
805 public boolean isSymbolicLink() {
806 return false;
807 }
808
809 @Override
810 public long getSize() throws IOException {
811 return 0;
812 }
813
814 @Override
815 public long getLastModifiedTime() throws IOException {
816 return 0;
817 }
818
819 @Override
820 public long getLastChangeTime() throws IOException {
821 return 0;
822 }
823
824 @Override
825 public long getNodeId() throws IOException {
826 return 0;
827 }
828 };
829 fs.stubStat(path("a"), inconsistentParentFileStatus);
830 // Disable fast-path md5 so that we don't try try to md5 the "a" (since it actually physically
831 // is a directory).
832 fastMd5 = false;
833 SequentialBuildDriver driver = makeDriver();
834 SkyKey skyKey = skyKey("a/b");
835 EvaluationResult<FileValue> result =
836 driver.evaluate(
837 ImmutableList.of(skyKey), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
838 assertTrue(result.hasError());
839 ErrorInfo errorInfo = result.getError(skyKey);
840 assertThat(errorInfo.getException()).isInstanceOf(InconsistentFilesystemException.class);
841 assertThat(errorInfo.getException().getMessage())
842 .contains("file /root/a/b exists but its parent path /root/a isn't an existing directory");
843 }
844
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000845 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000846 public void testFilesystemInconsistencies_GetFastDigest() throws Exception {
847 file("a");
848 // Our custom filesystem says "a/b" exists but "a" does not exist.
849 fs.stubFastDigestError(path("a"), new IOException("nope"));
850 SequentialBuildDriver driver = makeDriver();
851 SkyKey skyKey = skyKey("a");
852 EvaluationResult<FileValue> result =
853 driver.evaluate(
854 ImmutableList.of(skyKey), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
855 assertTrue(result.hasError());
856 ErrorInfo errorInfo = result.getError(skyKey);
857 assertThat(errorInfo.getException()).isInstanceOf(InconsistentFilesystemException.class);
858 assertThat(errorInfo.getException().getMessage()).contains("encountered error 'nope'");
859 assertThat(errorInfo.getException().getMessage()).contains("/root/a is no longer a file");
860 }
861
862 private void runTestSymlinkCycle(boolean ancestorCycle, boolean startInCycle) throws Exception {
863 symlink("a", "b");
864 symlink("b", "c");
865 symlink("c", "d");
866 symlink("d", "e");
867 symlink("e", "c");
868 // We build multiple keys at once to make sure the cycle is reported exactly once.
869 Map<RootedPath, ImmutableList<RootedPath>> startToCycleMap =
870 ImmutableMap.<RootedPath, ImmutableList<RootedPath>>builder()
871 .put(
872 rootedPath("a"),
873 ImmutableList.of(rootedPath("c"), rootedPath("d"), rootedPath("e")))
874 .put(
875 rootedPath("b"),
876 ImmutableList.of(rootedPath("c"), rootedPath("d"), rootedPath("e")))
877 .put(
878 rootedPath("d"),
879 ImmutableList.<RootedPath>of(rootedPath("d"), rootedPath("e"), rootedPath("c")))
880 .put(
881 rootedPath("e"),
882 ImmutableList.<RootedPath>of(rootedPath("e"), rootedPath("c"), rootedPath("d")))
883 .put(
884 rootedPath("a/some/descendant"),
885 ImmutableList.of(rootedPath("c"), rootedPath("d"), rootedPath("e")))
886 .put(
887 rootedPath("b/some/descendant"),
888 ImmutableList.of(rootedPath("c"), rootedPath("d"), rootedPath("e")))
889 .put(
890 rootedPath("d/some/descendant"),
891 ImmutableList.<RootedPath>of(rootedPath("d"), rootedPath("e"), rootedPath("c")))
892 .put(
893 rootedPath("e/some/descendant"),
894 ImmutableList.<RootedPath>of(rootedPath("e"), rootedPath("c"), rootedPath("d")))
895 .build();
896 Map<RootedPath, ImmutableList<RootedPath>> startToPathToCycleMap =
897 ImmutableMap.<RootedPath, ImmutableList<RootedPath>>builder()
898 .put(rootedPath("a"), ImmutableList.of(rootedPath("a"), rootedPath("b")))
899 .put(rootedPath("b"), ImmutableList.of(rootedPath("b")))
900 .put(rootedPath("d"), ImmutableList.<RootedPath>of())
901 .put(rootedPath("e"), ImmutableList.<RootedPath>of())
902 .put(
903 rootedPath("a/some/descendant"), ImmutableList.of(rootedPath("a"), rootedPath("b")))
904 .put(rootedPath("b/some/descendant"), ImmutableList.of(rootedPath("b")))
905 .put(rootedPath("d/some/descendant"), ImmutableList.<RootedPath>of())
906 .put(rootedPath("e/some/descendant"), ImmutableList.<RootedPath>of())
907 .build();
908 ImmutableList<SkyKey> keys;
909 if (ancestorCycle && startInCycle) {
910 keys = ImmutableList.of(skyKey("d/some/descendant"), skyKey("e/some/descendant"));
911 } else if (ancestorCycle && !startInCycle) {
912 keys = ImmutableList.of(skyKey("a/some/descendant"), skyKey("b/some/descendant"));
913 } else if (!ancestorCycle && startInCycle) {
914 keys = ImmutableList.of(skyKey("d"), skyKey("e"));
915 } else {
916 keys = ImmutableList.of(skyKey("a"), skyKey("b"));
917 }
918 StoredEventHandler eventHandler = new StoredEventHandler();
919 SequentialBuildDriver driver = makeDriver();
920 EvaluationResult<FileValue> result =
921 driver.evaluate(keys, /*keepGoing=*/ true, DEFAULT_THREAD_COUNT, eventHandler);
922 assertTrue(result.hasError());
923 for (SkyKey key : keys) {
924 ErrorInfo errorInfo = result.getError(key);
925 // FileFunction detects symlink cycles explicitly.
926 assertThat(errorInfo.getCycleInfo()).isEmpty();
927 FileSymlinkCycleException fsce = (FileSymlinkCycleException) errorInfo.getException();
928 RootedPath start = (RootedPath) key.argument();
929 assertThat(fsce.getPathToCycle())
930 .containsExactlyElementsIn(startToPathToCycleMap.get(start))
931 .inOrder();
932 assertThat(fsce.getCycle()).containsExactlyElementsIn(startToCycleMap.get(start)).inOrder();
933 }
934 // Check that the unique cycle was reported exactly once.
935 assertThat(eventHandler.getEvents()).hasSize(1);
936 assertThat(Iterables.getOnlyElement(eventHandler.getEvents()).getMessage())
937 .contains("circular symlinks detected");
938 }
939
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000940 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000941 public void testSymlinkCycle_AncestorCycle_StartInCycle() throws Exception {
942 runTestSymlinkCycle(/*ancestorCycle=*/ true, /*startInCycle=*/ true);
943 }
944
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000945 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000946 public void testSymlinkCycle_AncestorCycle_StartOutOfCycle() throws Exception {
947 runTestSymlinkCycle(/*ancestorCycle=*/ true, /*startInCycle=*/ false);
948 }
949
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000950 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000951 public void testSymlinkCycle_RegularCycle_StartInCycle() throws Exception {
952 runTestSymlinkCycle(/*ancestorCycle=*/ false, /*startInCycle=*/ true);
953 }
954
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000955 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000956 public void testSymlinkCycle_RegularCycle_StartOutOfCycle() throws Exception {
957 runTestSymlinkCycle(/*ancestorCycle=*/ false, /*startInCycle=*/ false);
958 }
959
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +0000960 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +0000961 public void testSerialization() throws Exception {
962 ByteArrayOutputStream bos = new ByteArrayOutputStream();
963 ObjectOutputStream oos = new ObjectOutputStream(bos);
964
965 FileSystem oldFileSystem = Path.getFileSystemForSerialization();
966 try {
967 FileSystem fs = UnixFileSystem.INSTANCE; // InMemoryFS is not supported for serialization.
968 Path.setFileSystemForSerialization(fs);
969 pkgRoot = fs.getRootDirectory();
970
971 FileValue a = valueForPath(fs.getPath("/"));
972
973 Path tmp = fs.getPath(TestUtils.tmpDirFile().getAbsoluteFile() + "/file.txt");
974
975 FileSystemUtils.writeContentAsLatin1(tmp, "test contents");
976
977 FileValue b = valueForPath(tmp);
978 Preconditions.checkState(b.isFile());
979 FileValue c = valueForPath(fs.getPath("/does/not/exist"));
980 oos.writeObject(a);
981 oos.writeObject(b);
982 oos.writeObject(c);
983
984 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
985 ObjectInputStream ois = new ObjectInputStream(bis);
986
987 FileValue a2 = (FileValue) ois.readObject();
988 FileValue b2 = (FileValue) ois.readObject();
989 FileValue c2 = (FileValue) ois.readObject();
990
991 assertEquals(a, a2);
992 assertEquals(b, b2);
993 assertEquals(c, c2);
994 assertFalse(a2.equals(b2));
995 } finally {
996 Path.setFileSystemForSerialization(oldFileSystem);
997 }
998 }
999
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +00001000 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +00001001 public void testFileStateEquality() throws Exception {
1002 file("a");
1003 symlink("b1", "a");
1004 symlink("b2", "a");
1005 symlink("b3", "zzz");
1006 directory("d1");
1007 directory("d2");
1008 SkyKey file = fileStateSkyKey("a");
1009 SkyKey symlink1 = fileStateSkyKey("b1");
1010 SkyKey symlink2 = fileStateSkyKey("b2");
1011 SkyKey symlink3 = fileStateSkyKey("b3");
1012 SkyKey missing1 = fileStateSkyKey("c1");
1013 SkyKey missing2 = fileStateSkyKey("c2");
1014 SkyKey directory1 = fileStateSkyKey("d1");
1015 SkyKey directory2 = fileStateSkyKey("d2");
1016 ImmutableList<SkyKey> keys =
1017 ImmutableList.of(
1018 file, symlink1, symlink2, symlink3, missing1, missing2, directory1, directory2);
1019
1020 SequentialBuildDriver driver = makeDriver();
1021 EvaluationResult<SkyValue> result =
1022 driver.evaluate(keys, false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
1023
1024 new EqualsTester()
1025 .addEqualityGroup(result.get(file))
1026 .addEqualityGroup(result.get(symlink1), result.get(symlink2))
1027 .addEqualityGroup(result.get(symlink3))
1028 .addEqualityGroup(result.get(missing1), result.get(missing2))
1029 .addEqualityGroup(result.get(directory1), result.get(directory2))
1030 .testEquals();
1031 }
1032
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +00001033 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +00001034 public void testSymlinkToPackagePathBoundary() throws Exception {
1035 Path path = path("this/is/a/path");
1036 FileSystemUtils.ensureSymbolicLink(path, pkgRoot);
1037 assertError("this/is/a/path");
1038 }
1039
1040 private void runTestInfiniteSymlinkExpansion(boolean symlinkToAncestor, boolean absoluteSymlink)
1041 throws Exception {
1042 Path otherPath = path("other");
1043 RootedPath otherRootedPath = RootedPath.toRootedPath(pkgRoot, otherPath.relativeTo(pkgRoot));
1044 Path ancestorPath = path("a");
1045 RootedPath ancestorRootedPath =
1046 RootedPath.toRootedPath(pkgRoot, ancestorPath.relativeTo(pkgRoot));
1047 FileSystemUtils.ensureSymbolicLink(otherPath, ancestorPath);
1048 Path intermediatePath = path("inter");
1049 RootedPath intermediateRootedPath =
1050 RootedPath.toRootedPath(pkgRoot, intermediatePath.relativeTo(pkgRoot));
1051 Path descendantPath = path("a/b/c/d/e");
1052 RootedPath descendantRootedPath =
1053 RootedPath.toRootedPath(pkgRoot, descendantPath.relativeTo(pkgRoot));
1054 if (symlinkToAncestor) {
1055 FileSystemUtils.ensureSymbolicLink(descendantPath, intermediatePath);
1056 if (absoluteSymlink) {
1057 FileSystemUtils.ensureSymbolicLink(intermediatePath, ancestorPath);
1058 } else {
1059 FileSystemUtils.ensureSymbolicLink(intermediatePath, ancestorRootedPath.getRelativePath());
1060 }
1061 } else {
1062 FileSystemUtils.ensureSymbolicLink(ancestorPath, intermediatePath);
1063 if (absoluteSymlink) {
1064 FileSystemUtils.ensureSymbolicLink(intermediatePath, descendantPath);
1065 } else {
1066 FileSystemUtils.ensureSymbolicLink(
1067 intermediatePath, descendantRootedPath.getRelativePath());
1068 }
1069 }
1070 StoredEventHandler eventHandler = new StoredEventHandler();
1071 SequentialBuildDriver driver = makeDriver();
1072 SkyKey ancestorPathKey = FileValue.key(ancestorRootedPath);
1073 SkyKey descendantPathKey = FileValue.key(descendantRootedPath);
1074 SkyKey otherPathKey = FileValue.key(otherRootedPath);
1075 ImmutableList<SkyKey> keys;
1076 ImmutableList<SkyKey> errorKeys;
1077 ImmutableList<RootedPath> expectedChain;
1078 if (symlinkToAncestor) {
1079 keys = ImmutableList.of(descendantPathKey, otherPathKey);
1080 errorKeys = ImmutableList.of(descendantPathKey);
1081 expectedChain =
1082 ImmutableList.of(descendantRootedPath, intermediateRootedPath, ancestorRootedPath);
1083 } else {
1084 keys = ImmutableList.of(ancestorPathKey, otherPathKey);
1085 errorKeys = keys;
1086 expectedChain =
1087 ImmutableList.of(ancestorRootedPath, intermediateRootedPath, descendantRootedPath);
1088 }
1089 EvaluationResult<FileValue> result =
1090 driver.evaluate(keys, /*keepGoing=*/ true, DEFAULT_THREAD_COUNT, eventHandler);
1091 assertTrue(result.hasError());
1092 for (SkyKey key : errorKeys) {
1093 ErrorInfo errorInfo = result.getError(key);
1094 // FileFunction detects infinite symlink expansion explicitly.
1095 assertThat(errorInfo.getCycleInfo()).isEmpty();
1096 FileSymlinkInfiniteExpansionException fsiee =
1097 (FileSymlinkInfiniteExpansionException) errorInfo.getException();
1098 assertThat(fsiee.getMessage()).contains("Infinite symlink expansion");
1099 assertThat(fsiee.getChain()).containsExactlyElementsIn(expectedChain).inOrder();
1100 }
1101 // Check that the unique symlink expansion error was reported exactly once.
1102 assertThat(eventHandler.getEvents()).hasSize(1);
1103 assertThat(Iterables.getOnlyElement(eventHandler.getEvents()).getMessage())
1104 .contains("infinite symlink expansion detected");
1105 }
1106
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +00001107 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +00001108 public void testInfiniteSymlinkExpansion_AbsoluteSymlinkToDescendant() throws Exception {
1109 runTestInfiniteSymlinkExpansion(/*ancestor=*/ false, /*absoluteSymlink=*/ true);
1110 }
1111
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +00001112 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +00001113 public void testInfiniteSymlinkExpansion_RelativeSymlinkToDescendant() throws Exception {
1114 runTestInfiniteSymlinkExpansion(/*ancestor=*/ false, /*absoluteSymlink=*/ false);
1115 }
1116
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +00001117 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +00001118 public void testInfiniteSymlinkExpansion_AbsoluteSymlinkToAncestor() throws Exception {
1119 runTestInfiniteSymlinkExpansion(/*ancestor=*/ true, /*absoluteSymlink=*/ true);
1120 }
1121
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +00001122 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +00001123 public void testInfiniteSymlinkExpansion_RelativeSymlinkToAncestor() throws Exception {
1124 runTestInfiniteSymlinkExpansion(/*ancestor=*/ true, /*absoluteSymlink=*/ false);
1125 }
1126
Han-Wen Nienhuys3b2eae32015-10-28 16:35:08 +00001127 @Test
Han-Wen Nienhuyseb71ecc2015-10-28 15:48:36 +00001128 public void testChildOfNonexistentParent() throws Exception {
1129 Path ancestor = directory("this/is/an/ancestor");
1130 Path parent = ancestor.getChild("parent");
1131 Path child = parent.getChild("child");
1132 assertFalse(valueForPath(parent).exists());
1133 assertFalse(valueForPath(child).exists());
1134 }
1135
1136 private void checkRealPath(String pathString) throws Exception {
1137 Path realPath = pkgRoot.getRelative(pathString).resolveSymbolicLinks();
1138 assertRealPath(pathString, realPath.relativeTo(pkgRoot).toString());
1139 }
1140
1141 private void assertRealPath(String pathString, String expectedRealPathString) throws Exception {
1142 SequentialBuildDriver driver = makeDriver();
1143 SkyKey key = skyKey(pathString);
1144 EvaluationResult<SkyValue> result =
1145 driver.evaluate(
1146 ImmutableList.of(key), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
1147 if (result.hasError()) {
1148 fail(String.format("Evaluation error for %s: %s", key, result.getError()));
1149 }
1150 FileValue fileValue = (FileValue) result.get(key);
1151 assertEquals(
1152 pkgRoot.getRelative(expectedRealPathString).toString(),
1153 fileValue.realRootedPath().asPath().toString());
1154 }
1155
1156 /**
1157 * Returns a callback that, when executed, deletes the given path.
1158 * Not meant to be called directly by tests.
1159 */
1160 private Runnable makeDeletePathCallback(final Path toDelete) {
1161 return new Runnable() {
1162 @Override
1163 public void run() {
1164 try {
1165 toDelete.delete();
1166 } catch (IOException e) {
1167 e.printStackTrace();
1168 fail(e.getMessage());
1169 }
1170 }
1171 };
1172 }
1173
1174 /**
1175 * Returns a callback that, when executed, writes the given bytes to the given file path.
1176 * Not meant to be called directly by tests.
1177 */
1178 private Runnable makeWriteFileContentCallback(final Path toChange, final byte[] contents) {
1179 return new Runnable() {
1180 @Override
1181 public void run() {
1182 OutputStream outputStream;
1183 try {
1184 outputStream = toChange.getOutputStream();
1185 outputStream.write(contents);
1186 outputStream.close();
1187 } catch (IOException e) {
1188 e.printStackTrace();
1189 fail(e.getMessage());
1190 }
1191 }
1192 };
1193 }
1194
1195 /**
1196 * Returns a callback that, when executed, creates the given directory path.
1197 * Not meant to be called directly by tests.
1198 */
1199 private Runnable makeCreateDirectoryCallback(final Path toCreate) {
1200 return new Runnable() {
1201 @Override
1202 public void run() {
1203 try {
1204 toCreate.createDirectory();
1205 } catch (IOException e) {
1206 e.printStackTrace();
1207 fail(e.getMessage());
1208 }
1209 }
1210 };
1211 }
1212
1213 /**
1214 * Returns a callback that, when executed, makes {@code toLink} a symlink to {@code toTarget}.
1215 * Not meant to be called directly by tests.
1216 */
1217 private Runnable makeSymlinkCallback(final Path toLink, final PathFragment toTarget) {
1218 return new Runnable() {
1219 @Override
1220 public void run() {
1221 try {
1222 FileSystemUtils.ensureSymbolicLink(toLink, toTarget);
1223 } catch (IOException e) {
1224 e.printStackTrace();
1225 fail(e.getMessage());
1226 }
1227 }
1228 };
1229 }
1230
1231 /**
1232 * Returns the files that would be changed/created if {@code path} were to be changed/created.
1233 */
1234 private ImmutableList<String> filesTouchedIfTouched(Path path) {
1235 List<String> filesToBeTouched = Lists.newArrayList();
1236 do {
1237 filesToBeTouched.add(path.getPathString());
1238 path = path.getParentDirectory();
1239 } while (!path.exists());
1240 return ImmutableList.copyOf(filesToBeTouched);
1241 }
1242
1243 /**
1244 * Changes the contents of the FileValue for the given file in some way e.g.
1245 * <ul>
1246 * <li> If it's a regular file, the contents will be changed.
1247 * <li> If it's a non-existent file, it will be created.
1248 * <ul>
1249 * and then returns the file(s) changed paired with a callback to undo the change. Not meant to
1250 * be called directly by tests.
1251 */
1252 private Pair<ImmutableList<String>, Runnable> changeFile(String fileStringToChange)
1253 throws Exception {
1254 Path fileToChange = path(fileStringToChange);
1255 if (fileToChange.exists()) {
1256 final byte[] oldContents = FileSystemUtils.readContent(fileToChange);
1257 OutputStream outputStream = fileToChange.getOutputStream(/*append=*/ true);
1258 outputStream.write(new byte[] {(byte) 42}, 0, 1);
1259 outputStream.close();
1260 return Pair.of(
1261 ImmutableList.of(fileStringToChange),
1262 makeWriteFileContentCallback(fileToChange, oldContents));
1263 } else {
1264 ImmutableList<String> filesTouched = filesTouchedIfTouched(fileToChange);
1265 file(fileStringToChange, "new stuff");
1266 return Pair.of(ImmutableList.copyOf(filesTouched), makeDeletePathCallback(fileToChange));
1267 }
1268 }
1269
1270 /**
1271 * Changes the contents of the FileValue for the given directory in some way e.g.
1272 * <ul>
1273 * <li> If it exists, the directory will be deleted.
1274 * <li> If it doesn't exist, the directory will be created.
1275 * <ul>
1276 * and then returns the file(s) changed paired with a callback to undo the change. Not meant to
1277 * be called directly by tests.
1278 */
1279 private Pair<ImmutableList<String>, Runnable> changeDirectory(String directoryStringToChange)
1280 throws Exception {
1281 final Path directoryToChange = path(directoryStringToChange);
1282 if (directoryToChange.exists()) {
1283 directoryToChange.delete();
1284 return Pair.of(
1285 ImmutableList.of(directoryStringToChange),
1286 makeCreateDirectoryCallback(directoryToChange));
1287 } else {
1288 directoryToChange.createDirectory();
1289 return Pair.of(
1290 ImmutableList.of(directoryStringToChange), makeDeletePathCallback(directoryToChange));
1291 }
1292 }
1293
1294 /**
1295 * Performs filesystem operations to change the file or directory denoted by
1296 * {@code changedPathString} and returns the file(s) changed paired with a callback to undo the
1297 * change.
1298 * Not meant to be called directly by tests.
1299 *
1300 * @param isSupposedToBeFile whether the path denoted by the given string is supposed to be a
1301 * file or a directory. This is needed is the path doesn't exist yet,
1302 * and so the filesystem doesn't know.
1303 */
1304 private Pair<ImmutableList<String>, Runnable> change(
1305 String changedPathString, boolean isSupposedToBeFile) throws Exception {
1306 final Path changedPath = path(changedPathString);
1307 if (changedPath.isSymbolicLink()) {
1308 ImmutableList<String> filesTouched = filesTouchedIfTouched(changedPath);
1309 PathFragment oldTarget = changedPath.readSymbolicLink();
1310 FileSystemUtils.ensureSymbolicLink(changedPath, oldTarget.getChild("__different_target__"));
1311 return Pair.of(filesTouched, makeSymlinkCallback(changedPath, oldTarget));
1312 } else if (isSupposedToBeFile) {
1313 return changeFile(changedPathString);
1314 } else {
1315 return changeDirectory(changedPathString);
1316 }
1317 }
1318
1319 /**
1320 * Asserts that if the contents of {@code changedPathString} changes, then the FileValue
1321 * corresponding to {@code pathString} will change. Not meant to be called directly by tests.
1322 */
1323 private void assertValueChangesIfContentsOfFileChanges(
1324 String changedPathString, boolean changes, String pathString) throws Exception {
1325 getFilesSeenAndAssertValueChangesIfContentsOfFileChanges(
1326 changedPathString, changes, pathString);
1327 }
1328
1329 /**
1330 * Asserts that if the contents of {@code changedPathString} changes, then the FileValue
1331 * corresponding to {@code pathString} will change. Returns the paths of all files seen.
1332 */
1333 private Set<RootedPath> getFilesSeenAndAssertValueChangesIfContentsOfFileChanges(
1334 String changedPathString, boolean changes, String pathString) throws Exception {
1335 return assertChangesIfChanges(changedPathString, true, changes, pathString);
1336 }
1337
1338 /**
1339 * Asserts that if the directory {@code changedPathString} changes, then the FileValue
1340 * corresponding to {@code pathString} will change. Returns the paths of all files seen.
1341 */
1342 private Set<RootedPath> assertValueChangesIfContentsOfDirectoryChanges(
1343 String changedPathString, boolean changes, String pathString) throws Exception {
1344 return assertChangesIfChanges(changedPathString, false, changes, pathString);
1345 }
1346
1347 /**
1348 * Asserts that if the contents of {@code changedPathString} changes, then the FileValue
1349 * corresponding to {@code pathString} will change. Returns the paths of all files seen.
1350 * Not meant to be called directly by tests.
1351 */
1352 private Set<RootedPath> assertChangesIfChanges(
1353 String changedPathString, boolean isFile, boolean changes, String pathString)
1354 throws Exception {
1355 SequentialBuildDriver driver = makeDriver();
1356 SkyKey key = skyKey(pathString);
1357 EvaluationResult<SkyValue> result;
1358 result =
1359 driver.evaluate(
1360 ImmutableList.of(key), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
1361 if (result.hasError()) {
1362 fail(String.format("Evaluation error for %s: %s", key, result.getError()));
1363 }
1364 SkyValue oldValue = result.get(key);
1365
1366 Pair<ImmutableList<String>, Runnable> changeResult = change(changedPathString, isFile);
1367 ImmutableList<String> changedPathStrings = changeResult.first;
1368 Runnable undoCallback = changeResult.second;
1369 differencer.invalidate(
1370 Iterables.transform(
1371 changedPathStrings,
1372 new Function<String, SkyKey>() {
1373 @Override
1374 public SkyKey apply(String input) {
1375 return fileStateSkyKey(input);
1376 }
1377 }));
1378
1379 result =
1380 driver.evaluate(
1381 ImmutableList.of(key), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
1382 if (result.hasError()) {
1383 fail(String.format("Evaluation error for %s: %s", key, result.getError()));
1384 }
1385
1386 SkyValue newValue = result.get(key);
1387 assertTrue(
1388 String.format(
1389 "Changing the contents of %s %s should%s change the value for file %s.",
1390 isFile ? "file" : "directory",
1391 changedPathString,
1392 changes ? "" : " not",
1393 pathString),
1394 changes != newValue.equals(oldValue));
1395
1396 // Restore the original file.
1397 undoCallback.run();
1398 return filesSeen(driver.getGraphForTesting());
1399 }
1400
1401 /**
1402 * Asserts that trying to construct a FileValue for {@code path} succeeds. Returns the paths of
1403 * all files seen.
1404 */
1405 private Set<RootedPath> assertNoError(String pathString) throws Exception {
1406 SequentialBuildDriver driver = makeDriver();
1407 SkyKey key = skyKey(pathString);
1408 EvaluationResult<FileValue> result;
1409 result =
1410 driver.evaluate(
1411 ImmutableList.of(key), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
1412 assertFalse(
1413 "Did not expect error while evaluating " + pathString + ", got " + result.get(key),
1414 result.hasError());
1415 return filesSeen(driver.getGraphForTesting());
1416 }
1417
1418 /**
1419 * Asserts that trying to construct a FileValue for {@code path} fails. Returns the paths of all
1420 * files seen.
1421 */
1422 private Set<RootedPath> assertError(String pathString) throws Exception {
1423 SequentialBuildDriver driver = makeDriver();
1424 SkyKey key = skyKey(pathString);
1425 EvaluationResult<FileValue> result;
1426 result =
1427 driver.evaluate(
1428 ImmutableList.of(key), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
1429 assertTrue(
1430 "Expected error while evaluating " + pathString + ", got " + result.get(key),
1431 result.hasError());
1432 assertTrue(
1433 !Iterables.isEmpty(result.getError().getCycleInfo())
1434 || result.getError().getException() != null);
1435 return filesSeen(driver.getGraphForTesting());
1436 }
1437
1438 private Path file(String fileName) throws Exception {
1439 Path path = path(fileName);
1440 FileSystemUtils.createDirectoryAndParents(path.getParentDirectory());
1441 FileSystemUtils.createEmptyFile(path);
1442 return path;
1443 }
1444
1445 private Path file(String fileName, String contents) throws Exception {
1446 Path path = path(fileName);
1447 FileSystemUtils.createDirectoryAndParents(path.getParentDirectory());
1448 FileSystemUtils.writeContentAsLatin1(path, contents);
1449 return path;
1450 }
1451
1452 private Path directory(String directoryName) throws Exception {
1453 Path path = path(directoryName);
1454 FileSystemUtils.createDirectoryAndParents(path);
1455 return path;
1456 }
1457
1458 private Path symlink(String link, String target) throws Exception {
1459 Path path = path(link);
1460 FileSystemUtils.createDirectoryAndParents(path.getParentDirectory());
1461 path.createSymbolicLink(new PathFragment(target));
1462 return path;
1463 }
1464
1465 private Path path(String rootRelativePath) {
1466 return pkgRoot.getRelative(new PathFragment(rootRelativePath));
1467 }
1468
1469 private RootedPath rootedPath(String pathString) {
1470 Path path = path(pathString);
1471 for (Path root : pkgLocator.getPathEntries()) {
1472 if (path.startsWith(root)) {
1473 return RootedPath.toRootedPath(root, path);
1474 }
1475 }
1476 return RootedPath.toRootedPath(fs.getRootDirectory(), path);
1477 }
1478
1479 private SkyKey skyKey(String pathString) {
1480 return FileValue.key(rootedPath(pathString));
1481 }
1482
1483 private SkyKey fileStateSkyKey(String pathString) {
1484 return FileStateValue.key(rootedPath(pathString));
1485 }
1486
1487 private class CustomInMemoryFs extends InMemoryFileSystem {
1488
1489 private Map<Path, FileStatus> stubbedStats = Maps.newHashMap();
1490 private Map<Path, IOException> stubbedFastDigestErrors = Maps.newHashMap();
1491
1492 public CustomInMemoryFs(ManualClock manualClock) {
1493 super(manualClock);
1494 }
1495
1496 @Override
1497 protected String getFastDigestFunctionType(Path path) {
1498 return fastMd5 ? "MD5" : null;
1499 }
1500
1501 public void stubFastDigestError(Path path, IOException error) {
1502 stubbedFastDigestErrors.put(path, error);
1503 }
1504
1505 @Override
1506 protected byte[] getFastDigest(Path path) throws IOException {
1507 if (stubbedFastDigestErrors.containsKey(path)) {
1508 throw stubbedFastDigestErrors.get(path);
1509 }
1510 return fastMd5 ? getMD5Digest(path) : null;
1511 }
1512
1513 public void stubStat(Path path, @Nullable FileStatus stubbedResult) {
1514 stubbedStats.put(path, stubbedResult);
1515 }
1516
1517 @Override
1518 public FileStatus stat(Path path, boolean followSymlinks) throws IOException {
1519 if (stubbedStats.containsKey(path)) {
1520 return stubbedStats.get(path);
1521 }
1522 return super.stat(path, followSymlinks);
1523 }
1524 }
1525}