blob: d5b8ae406d0bbbfbe8102c43e60797e300c8376b [file] [log] [blame]
// Copyright 2021 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.skyframe;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.devtools.build.lib.actions.FileStateValue.DIRECTORY_FILE_STATE_NODE;
import static com.google.devtools.build.lib.actions.FileStateValue.NONEXISTENT_FILE_STATE_NODE;
import static com.google.devtools.build.lib.testing.common.DirectoryListingHelper.file;
import static com.google.devtools.build.lib.testing.common.DirectoryListingHelper.symlink;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Streams;
import com.google.devtools.build.lib.actions.FileStateValue;
import com.google.devtools.build.lib.server.FailureDetails.DiffAwareness.Code;
import com.google.devtools.build.lib.skyframe.DirtinessCheckerUtils.FileDirtinessChecker;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.vfs.FileStateKey;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.skyframe.Differencer.DiffWithDelta.Delta;
import com.google.devtools.build.skyframe.ImmutableDiff;
import com.google.devtools.build.skyframe.InMemoryNodeEntry;
import com.google.devtools.build.skyframe.NodeBatch;
import com.google.devtools.build.skyframe.NodeEntry.DirtyType;
import com.google.devtools.build.skyframe.QueryableGraph.Reason;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.build.skyframe.Version;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import java.io.IOException;
import java.util.Map.Entry;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit tests for {@link FileSystemValueCheckerInferringAncestors}. */
@RunWith(TestParameterInjector.class)
public final class FileSystemValueCheckerInferringAncestorsTest
extends FileSystemValueCheckerInferringAncestorsTestBase {
private static final Delta DIRECTORY_FILE_STATE_NODE_DELTA =
Delta.justNew(DIRECTORY_FILE_STATE_NODE);
private static final Delta NONEXISTENT_FILE_STATE_NODE_DELTA =
Delta.justNew(NONEXISTENT_FILE_STATE_NODE);
private final SkyValueDirtinessChecker skyValueDirtinessChecker = new FileDirtinessChecker();
@TestParameter({"1", "16"})
private int fsvcThreads;
@Test
public void getDiffWithInferredAncestors_unknownFileChanged_returnsFileAndDirs()
throws Exception {
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(fileStateValueKey("foo/file")),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
assertThat(diff.changedKeysWithoutNewValues())
.containsExactly(
fileStateValueKey(""),
fileStateValueKey("foo"),
fileStateValueKey("foo/file"),
directoryListingStateValueKey(""),
directoryListingStateValueKey("foo"));
assertThat(diff.changedKeysWithNewValues()).isEmpty();
assertThat(statedPaths).isEmpty();
}
@Test
public void getDiffWithInferredAncestors_fileModified_returnsFileWithValues() throws Exception {
scratch.file("file", "hello");
FileStateKey key = fileStateValueKey("file");
FileStateValue value = fileStateValue("file");
scratch.overwriteFile("file", "there");
addDoneNodesAndThenMarkChanged(ImmutableMap.of(key, value));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(key),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
Delta newValue = fileStateValueDelta("file");
assertThat(diff.changedKeysWithNewValues()).containsExactly(key, newValue);
assertThat(diff.changedKeysWithoutNewValues()).isEmpty();
assertThat(statedPaths).containsExactly("file");
}
@Test
public void getDiffWithInferredAncestors_fileAdded_returnsFileAndDirListing() throws Exception {
scratch.file("file");
FileStateKey key = fileStateValueKey("file");
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(
key, NONEXISTENT_FILE_STATE_NODE, fileStateValueKey(""), fileStateValue("")));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(key),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
Delta delta = fileStateValueDelta("file");
assertThat(diff.changedKeysWithNewValues()).containsExactly(key, delta);
assertThat(diff.changedKeysWithoutNewValues())
.containsExactly(directoryListingStateValueKey(""));
assertThat(statedPaths).containsExactly("file");
}
@Test
public void getDiffWithInferredAncestors_fileWithDirsAdded_returnsFileAndInjectsDirs()
throws Exception {
scratch.file("a/b/file");
FileStateKey fileKey = fileStateValueKey("a/b/file");
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(
fileStateValueKey(""),
fileStateValue(""),
fileStateValueKey("a"),
NONEXISTENT_FILE_STATE_NODE,
fileStateValueKey("a/b"),
NONEXISTENT_FILE_STATE_NODE,
fileKey,
NONEXISTENT_FILE_STATE_NODE));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(fileKey),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
Delta delta = fileStateValueDelta("a/b/file");
assertThat(diff.changedKeysWithNewValues())
.containsExactly(
fileKey,
delta,
fileStateValueKey("a"),
DIRECTORY_FILE_STATE_NODE_DELTA,
fileStateValueKey("a/b"),
DIRECTORY_FILE_STATE_NODE_DELTA);
assertThat(diff.changedKeysWithoutNewValues())
.containsExactly(
directoryListingStateValueKey(""),
directoryListingStateValueKey("a"),
directoryListingStateValueKey("a/b"));
assertThat(statedPaths).containsExactly("a/b/file");
}
@Test
public void getDiffWithInferredAncestors_addedFileWithReportedDirs_returnsFileAndInjectsDirs()
throws Exception {
scratch.file("a/b/file");
FileStateKey fileKey = fileStateValueKey("a/b/file");
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(
fileStateValueKey(""),
fileStateValue(""),
fileStateValueKey("a"),
NONEXISTENT_FILE_STATE_NODE,
fileStateValueKey("a/b"),
NONEXISTENT_FILE_STATE_NODE,
fileKey,
NONEXISTENT_FILE_STATE_NODE));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(fileKey, fileStateValueKey("a")),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
Delta newState = fileStateValueDelta("a/b/file");
assertThat(diff.changedKeysWithNewValues())
.containsExactly(
fileKey,
newState,
fileStateValueKey("a"),
DIRECTORY_FILE_STATE_NODE_DELTA,
fileStateValueKey("a/b"),
DIRECTORY_FILE_STATE_NODE_DELTA);
assertThat(diff.changedKeysWithoutNewValues())
.containsExactly(
directoryListingStateValueKey(""),
directoryListingStateValueKey("a"),
directoryListingStateValueKey("a/b"));
assertThat(statedPaths).containsExactly("a/b/file");
}
/**
* This is a degenerate case since we normally only know about a file if we checked all parents,
* but that is theoretically possible with this API.
*/
@Test
public void getDiffWithInferredAncestors_fileWithUnknownDirsAdded_returnsFileAndDirs()
throws Exception {
scratch.file("a/b/c/d");
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(
fileStateValueKey(""),
fileStateValue(""),
fileStateValueKey("a/b/c/d"),
NONEXISTENT_FILE_STATE_NODE));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(fileStateValueKey("a/b/c/d")),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
assertThat(diff.changedKeysWithoutNewValues())
.containsExactly(
fileStateValueKey("a"),
fileStateValueKey("a/b"),
fileStateValueKey("a/b/c"),
directoryListingStateValueKey(""),
directoryListingStateValueKey("a"),
directoryListingStateValueKey("a/b"),
directoryListingStateValueKey("a/b/c"));
assertThat(diff.changedKeysWithNewValues())
.containsExactly(fileStateValueKey("a/b/c/d"), fileStateValueDelta("a/b/c/d"));
assertThat(statedPaths).containsExactly("a/b/c/d");
}
@Test
public void getDiffWithInferredAncestors_addEmptyDir_returnsDirAndParentListing()
throws Exception {
scratch.dir("dir");
FileStateKey key = fileStateValueKey("dir");
Delta delta = fileStateValueDelta("dir");
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(
key, NONEXISTENT_FILE_STATE_NODE, fileStateValueKey(""), fileStateValue("")));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(key),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
assertThat(diff.changedKeysWithNewValues()).containsExactly(key, delta);
assertThat(diff.changedKeysWithoutNewValues())
.containsExactly(directoryListingStateValueKey(""));
assertThat(statedPaths).containsExactly("dir");
}
@Test
public void getDiffWithInferredAncestors_deleteFile_returnsFileParentListing() throws Exception {
Path file = scratch.file("dir/file1");
scratch.file("dir/file2");
FileStateKey key = fileStateValueKey("dir/file1");
FileStateValue oldValue = fileStateValue("dir/file1");
file.delete();
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(key, oldValue, fileStateValueKey("dir"), fileStateValue("dir")));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(key),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
assertThat(diff.changedKeysWithNewValues())
.containsExactly(key, NONEXISTENT_FILE_STATE_NODE_DELTA);
assertThat(diff.changedKeysWithoutNewValues())
.containsExactly(directoryListingStateValueKey("dir"));
assertThat(statedPaths).containsExactly("dir/file1", "dir");
}
@Test
public void getDiffWithInferredAncestors_deleteFileFromDirWithListing_skipsDirStat()
throws Exception {
Path file1 = scratch.file("dir/file1");
FileStateKey key = fileStateValueKey("dir/file1");
FileStateValue oldValue = fileStateValue("dir/file1");
file1.delete();
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(key, oldValue, fileStateValueKey("dir"), fileStateValue("dir")));
addDoneNodes(
ImmutableMap.of(
directoryListingStateValueKey("dir"),
directoryListingStateValue(file("file1"), file("file2"))));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(key),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
assertThat(diff.changedKeysWithNewValues())
.containsExactly(key, NONEXISTENT_FILE_STATE_NODE_DELTA);
assertThat(diff.changedKeysWithoutNewValues())
.containsExactly(directoryListingStateValueKey("dir"));
assertThat(statedPaths).containsExactly("dir/file1");
}
@Test
public void getDiffWithInferredAncestors_deleteLastFileFromDir_ignoresInvalidatedListing()
throws Exception {
Path file1 = scratch.file("dir/file1");
FileStateKey key = fileStateValueKey("dir/file1");
FileStateValue oldValue = fileStateValue("dir/file1");
file1.delete();
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(
key,
oldValue,
fileStateValueKey("dir"),
fileStateValue("dir"),
directoryListingStateValueKey("dir"),
directoryListingStateValue(file("file1"), file("file2"))));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(key),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
assertThat(diff.changedKeysWithNewValues())
.containsExactly(key, NONEXISTENT_FILE_STATE_NODE_DELTA);
assertThat(diff.changedKeysWithoutNewValues())
.containsExactly(directoryListingStateValueKey("dir"));
assertThat(statedPaths).containsExactly("dir/file1", "dir");
}
@Test
public void getDiffWithInferredAncestors_modifyAllUnknownEntriesInDirWithListing_skipsDir()
throws Exception {
Path file = scratch.file("dir/file");
file.getParentDirectory()
.getRelative("symlink")
.createSymbolicLink(PathFragment.create("file"));
FileStateKey fileKey = fileStateValueKey("dir/file");
FileStateKey symlinkKey = fileStateValueKey("dir/symlink");
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(fileStateValueKey("dir"), fileStateValue("dir")));
addDoneNodes(
ImmutableMap.of(
directoryListingStateValueKey("dir"),
directoryListingStateValue(file("file"), symlink("symlink"))));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(fileKey, symlinkKey),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
assertThat(diff.changedKeysWithNewValues())
.containsExactly(
fileKey,
fileStateValueDelta("dir/file"),
symlinkKey,
fileStateValueDelta("dir/symlink"));
assertThat(diff.changedKeysWithoutNewValues()).isEmpty();
assertThat(statedPaths).containsExactly("dir/file", "dir/symlink");
}
@Test
public void getDiffWithInferredAncestors_replaceUnknownEntriesInDirWithListing_skipsSiblingStat()
throws Exception {
scratch.dir("dir/file1");
scratch.dir("dir/file2");
FileStateKey file1Key = fileStateValueKey("dir/file1");
FileStateKey file2Key = fileStateValueKey("dir/file2");
DirectoryListingStateValue.Key dirKey = directoryListingStateValueKey("dir");
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(fileStateValueKey("dir"), fileStateValue("dir")));
addDoneNodes(ImmutableMap.of(dirKey, directoryListingStateValue(file("file1"), file("file2"))));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(file1Key, file2Key),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
assertIsSubsetOf(
diff.changedKeysWithNewValues().entrySet(),
Maps.immutableEntry(file1Key, fileStateValueDelta("dir/file1")),
Maps.immutableEntry(file2Key, fileStateValueDelta("dir/file2")));
assertThat(diff.changedKeysWithoutNewValues()).contains(dirKey);
assertThat(
Streams.concat(
diff.changedKeysWithoutNewValues().stream(),
diff.changedKeysWithNewValues().keySet().stream()))
.containsExactly(file1Key, file2Key, dirKey);
assertThat(statedPaths).isNotEmpty();
assertIsSubsetOf(statedPaths, "dir/file1", "dir/file2");
if (fsvcThreads == 1) {
// In case of single-threaded execution, we know that once we check dir/file1 or dir/file2, we
// will be able to skip stat on the other one.
assertThat(diff.changedKeysWithNewValues()).hasSize(1);
assertThat(diff.changedKeysWithoutNewValues()).hasSize(2);
assertThat(statedPaths).hasSize(1);
}
}
@Test
public void getDiffWithInferredAncestors_deleteAllFilesFromDir_returnsFilesAndDirListing()
throws Exception {
Path file1 = scratch.file("dir/file1");
Path file2 = scratch.file("dir/file2");
Path file3 = scratch.file("dir/file3");
FileStateKey key1 = fileStateValueKey("dir/file1");
FileStateValue oldValue1 = fileStateValue("dir/file1");
FileStateKey key2 = fileStateValueKey("dir/file2");
FileStateValue oldValue2 = fileStateValue("dir/file2");
FileStateKey key3 = fileStateValueKey("dir/file3");
FileStateValue oldValue3 = fileStateValue("dir/file3");
file1.delete();
file2.delete();
file3.delete();
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(
key1,
oldValue1,
key2,
oldValue2,
key3,
oldValue3,
fileStateValueKey("dir"),
fileStateValue("dir")));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(key1, key2, key3),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
assertThat(diff.changedKeysWithNewValues())
.containsExactly(
key1, NONEXISTENT_FILE_STATE_NODE_DELTA,
key2, NONEXISTENT_FILE_STATE_NODE_DELTA,
key3, NONEXISTENT_FILE_STATE_NODE_DELTA);
assertThat(diff.changedKeysWithoutNewValues())
.containsExactly(directoryListingStateValueKey("dir"));
assertThat(statedPaths).containsExactly("dir", "dir/file1", "dir/file2", "dir/file3");
}
@Test
public void getDiffWithInferredAncestors_deleteFileWithDirs_returnsFileAndDirs()
throws Exception {
scratch.file("a/b/c/file");
FileStateKey abKey = fileStateValueKey("a/b");
FileStateValue abValue = fileStateValue("a/b");
FileStateKey abcKey = fileStateValueKey("a/b/c");
FileStateValue abcValue = fileStateValue("a/b/c");
FileStateKey abcFileKey = fileStateValueKey("a/b/c/file");
FileStateValue abcFileValue = fileStateValue("a/b/c/file");
scratch.dir("a/b").deleteTree();
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(
fileStateValueKey("a"),
fileStateValue("a"),
abKey,
abValue,
abcKey,
abcValue,
abcFileKey,
abcFileValue));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(abcFileKey),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
assertThat(diff.changedKeysWithNewValues())
.containsExactly(
abKey, NONEXISTENT_FILE_STATE_NODE_DELTA,
abcKey, NONEXISTENT_FILE_STATE_NODE_DELTA,
abcFileKey, NONEXISTENT_FILE_STATE_NODE_DELTA);
assertThat(diff.changedKeysWithoutNewValues())
.containsExactly(
directoryListingStateValueKey("a"),
directoryListingStateValueKey("a/b"),
directoryListingStateValueKey("a/b/c"));
assertThat(statedPaths).containsExactly("a", "a/b", "a/b/c", "a/b/c/file");
}
@Test
public void getDiffWithInferredAncestors_deleteFileWithReportedDirs_returnsFileAndDirListings()
throws Exception {
scratch.file("a/b/c/file");
FileStateKey abKey = fileStateValueKey("a/b");
FileStateValue abValue = fileStateValue("a/b");
FileStateKey abcKey = fileStateValueKey("a/b/c");
FileStateValue abcValue = fileStateValue("a/b/c");
FileStateKey abcFileKey = fileStateValueKey("a/b/c/file");
FileStateValue abcFileValue = fileStateValue("a/b/c/file");
scratch.dir("a/b").deleteTree();
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(
fileStateValueKey("a"),
fileStateValue("a"),
abKey,
abValue,
abcKey,
abcValue,
abcFileKey,
abcFileValue));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(abcFileKey, abKey),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
assertThat(diff.changedKeysWithNewValues())
.containsExactly(
abKey, NONEXISTENT_FILE_STATE_NODE_DELTA,
abcKey, NONEXISTENT_FILE_STATE_NODE_DELTA,
abcFileKey, NONEXISTENT_FILE_STATE_NODE_DELTA);
assertThat(diff.changedKeysWithoutNewValues())
.containsExactly(
directoryListingStateValueKey("a"),
directoryListingStateValueKey("a/b"),
directoryListingStateValueKey("a/b/c"));
assertThat(statedPaths).containsExactly("a", "a/b", "a/b/c", "a/b/c/file");
}
@Test
public void getDiffWithInferredAncestors_deleteFile_infersDirFromModifiedSibling()
throws Exception {
Path file1 = scratch.file("dir/file1");
scratch.file("dir/file2", "1");
FileStateKey file1Key = fileStateValueKey("dir/file1");
FileStateValue file1Value = fileStateValue("dir/file1");
FileStateKey file2Key = fileStateValueKey("dir/file2");
FileStateValue file2Value = fileStateValue("dir/file2");
file1.delete();
scratch.overwriteFile("dir/file2", "12");
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(
fileStateValueKey("dir"),
fileStateValue("dir"),
file1Key,
file1Value,
file2Key,
file2Value));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(file1Key, file2Key, fileStateValueKey("dir")),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
Delta file2NewValue = fileStateValueDelta("dir/file2");
assertThat(diff.changedKeysWithNewValues())
.containsExactly(file1Key, NONEXISTENT_FILE_STATE_NODE_DELTA, file2Key, file2NewValue);
assertThat(diff.changedKeysWithoutNewValues())
.containsExactly(directoryListingStateValueKey("dir"));
assertThat(statedPaths).containsExactly("dir/file1", "dir/file2");
}
@Test
public void getDiffWithInferredAncestors_deleteDirReportDirOnly_returnsDir() throws Exception {
Path file1 = scratch.file("dir/file1");
scratch.file("dir/file2");
FileStateKey file1Key = fileStateValueKey("dir/file1");
FileStateValue file1Value = fileStateValue("dir/file1");
FileStateKey file2Key = fileStateValueKey("dir/file2");
FileStateValue file2Value = fileStateValue("dir/file2");
FileStateKey dirKey = fileStateValueKey("dir");
FileStateValue dirValue = fileStateValue("dir");
file1.getParentDirectory().deleteTree();
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(
file1Key,
file1Value,
file2Key,
file2Value,
dirKey,
dirValue,
fileStateValueKey(""),
fileStateValue("")));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(dirKey),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
assertThat(diff.changedKeysWithNewValues())
.containsExactly(dirKey, NONEXISTENT_FILE_STATE_NODE_DELTA);
assertThat(diff.changedKeysWithoutNewValues())
.containsExactly(directoryListingStateValueKey(""));
assertThat(statedPaths).containsExactly("dir", "");
}
@Test
public void getDiffWithInferredAncestors_phantomChangeForNonexistentEntry_returnsEmptyDiff()
throws Exception {
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(fileStateValueKey("file"), NONEXISTENT_FILE_STATE_NODE));
ImmutableDiff diff =
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(fileStateValueKey("file")),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker);
assertThat(diff.changedKeysWithoutNewValues()).isEmpty();
assertThat(diff.changedKeysWithNewValues()).isEmpty();
assertThat(statedPaths).containsExactly("file");
}
@Test
public void getDiffWithInferredAncestors_statFails_fails() throws Exception {
throwOnStat = new IOException("oh no");
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(fileStateValueKey("file"), NONEXISTENT_FILE_STATE_NODE));
AbruptExitException e =
assertThrows(
AbruptExitException.class,
() ->
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(fileStateValueKey("file")),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker));
assertThat(e.getDetailedExitCode().getFailureDetail().hasDiffAwareness()).isTrue();
assertThat(e.getDetailedExitCode().getFailureDetail().getDiffAwareness().getCode())
.isEqualTo(Code.DIFF_STAT_FAILED);
assertThat(e).hasMessageThat().isEqualTo("Failed to stat: '/src/file' while computing diff");
}
@Test
public void getDiffWithInferredAncestors_statCrashes_fails() throws Exception {
throwOnStat = new RuntimeException("oh no");
addDoneNodesAndThenMarkChanged(
ImmutableMap.of(fileStateValueKey("file"), NONEXISTENT_FILE_STATE_NODE));
assertThrows(
IllegalStateException.class,
() ->
FileSystemValueCheckerInferringAncestors.getDiffWithInferredAncestors(
/* tsgm= */ null,
inMemoryGraph,
/* modifiedKeys= */ ImmutableSet.of(fileStateValueKey("file")),
fsvcThreads,
syscallCache,
skyValueDirtinessChecker));
}
private Delta fileStateValueDelta(String relativePath) throws IOException {
return Delta.justNew(fileStateValue(relativePath));
}
private void addDoneNodesAndThenMarkChanged(ImmutableMap<SkyKey, SkyValue> values)
throws InterruptedException {
for (Entry<SkyKey, SkyValue> entry : values.entrySet()) {
InMemoryNodeEntry node = addDoneNode(entry.getKey(), entry.getValue());
node.markDirty(DirtyType.CHANGE);
}
}
private void addDoneNodes(ImmutableMap<SkyKey, SkyValue> values) throws InterruptedException {
for (Entry<SkyKey, SkyValue> entry : values.entrySet()) {
addDoneNode(entry.getKey(), entry.getValue());
}
}
@CanIgnoreReturnValue
private InMemoryNodeEntry addDoneNode(SkyKey key, SkyValue value) throws InterruptedException {
NodeBatch batch = inMemoryGraph.createIfAbsentBatch(null, Reason.OTHER, ImmutableList.of(key));
InMemoryNodeEntry entry = (InMemoryNodeEntry) batch.get(key);
entry.addReverseDepAndCheckIfDone(null);
entry.markRebuilding();
entry.setValue(value, Version.minimal(), /* maxTransitiveSourceVersion= */ null);
return entry;
}
}