Sort DirectoryNode children to ensure validity.
InputTree will create DirectoryNodes with non-sequential additions of
directory children. These must be maintained in order for representation
within remote Directory messages (and to properly compute action keys).
Co-Author=buchgr
Closes #8008.
PiperOrigin-RevId: 243235156
diff --git a/src/main/java/com/google/devtools/build/lib/remote/merkletree/InputTree.java b/src/main/java/com/google/devtools/build/lib/remote/merkletree/InputTree.java
index 9e1c7aa..749ab27 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/merkletree/InputTree.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/merkletree/InputTree.java
@@ -16,6 +16,7 @@
import build.bazel.remote.execution.v2.Digest;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
import com.google.devtools.build.lib.actions.ActionInput;
import com.google.devtools.build.lib.actions.ActionInputHelper;
import com.google.devtools.build.lib.actions.FileArtifactValue;
@@ -34,6 +35,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.SortedMap;
+import java.util.SortedSet;
import java.util.TreeMap;
/**
@@ -46,7 +48,7 @@
void visitDirectory(PathFragment dirname, List<FileNode> files, List<DirectoryNode> dirs);
}
- abstract static class Node {
+ abstract static class Node implements Comparable<Node> {
private final String pathSegment;
Node(String pathSegment) {
@@ -58,6 +60,11 @@
}
@Override
+ public int compareTo(Node other) {
+ return pathSegment.compareTo(other.pathSegment);
+ }
+
+ @Override
public int hashCode() {
return pathSegment.hashCode();
}
@@ -108,7 +115,7 @@
}
static class DirectoryNode extends Node {
- private final List<Node> children = new ArrayList<>();
+ private final SortedSet<Node> children = Sets.newTreeSet();
DirectoryNode(String pathSegment) {
super(pathSegment);
diff --git a/src/test/java/com/google/devtools/build/lib/remote/merkletree/InputTreeTest.java b/src/test/java/com/google/devtools/build/lib/remote/merkletree/InputTreeTest.java
index bed2b4b..5f7663a 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/merkletree/InputTreeTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/merkletree/InputTreeTest.java
@@ -169,6 +169,30 @@
assertThat(fileNodesAtDepth(tree, 3)).containsExactly(expectedBuzzNode);
}
+ @Test
+ public void testLexicographicalOrder() throws Exception {
+ // Regression test for https://github.com/bazelbuild/bazel/pull/8008
+ //
+ // The issue was that before #8008 we wrongly assumed that a sorted full list of inputs would
+ // also lead to sorted tree nodes. Thereby not taking into account that the path separator '/'
+ // influences the sorting of the full list but not that of the tree nodes as its stripped there.
+ // For example, the below full list is lexicographically sorted
+ // srcs/system-root/bar.txt
+ // srcs/system/foo.txt
+ //
+ // However, the tree node [system-root, system] is not (note the missing / suffix).
+
+ SortedMap<PathFragment, ActionInput> sortedInputs = new TreeMap<>();
+ Map<ActionInput, FileArtifactValue> metadata = new HashMap<>();
+
+ addFile("srcs/system/foo.txt", "foo", sortedInputs, metadata);
+ addFile("srcs/system-root/bar.txt", "bar", sortedInputs, metadata);
+
+ InputTree tree =
+ InputTree.build(sortedInputs, new StaticMetadataProvider(metadata), execRoot, digestUtil);
+ assertLexicographicalOrder(tree);
+ }
+
private Artifact addFile(
String path,
String content,
@@ -193,28 +217,18 @@
}
private static void assertLexicographicalOrder(InputTree tree) {
- int depth = 0;
- while (true) {
- // String::compareTo implements lexicographical order
- List<String> files = filesAtDepth(depth, tree);
- assertThat(files).isStrictlyOrdered();
- List<String> directories = directoriesAtDepth(depth, tree);
- assertThat(directories).isStrictlyOrdered();
- if (directories.isEmpty()) {
- break;
- }
- depth++;
- }
+ // Assert the lexicographical order as defined by the remote execution protocol
+ tree.visit(
+ (PathFragment dirname, List<FileNode> files, List<DirectoryNode> dirs) -> {
+ assertThat(files).isStrictlyOrdered();
+ assertThat(dirs).isStrictlyOrdered();
+ });
}
private static List<String> directoriesAtDepth(int depth, InputTree tree) {
return asPathSegments(directoryNodesAtDepth(tree, depth));
}
- private static List<String> filesAtDepth(int depth, InputTree tree) {
- return asPathSegments(fileNodesAtDepth(tree, depth));
- }
-
private static List<String> asPathSegments(List<? extends Node> nodes) {
return nodes.stream().map(Node::getPathSegment).collect(Collectors.toList());
}