blob: fb47a56050a43776ab33184bc2d3e2613fa3efff [file] [log] [blame]
Googlerdce08002020-07-07 11:49:31 -07001// Copyright 2020 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;
15
16import static com.google.common.truth.Truth.assertThat;
jhorvitz0d0758a2020-07-14 12:50:24 -070017import static org.junit.Assert.assertThrows;
18import static org.junit.Assert.fail;
jhorvitz1397e1e2020-07-21 10:02:17 -070019import static org.mockito.Mockito.mock;
20import static org.mockito.Mockito.when;
Googlerdce08002020-07-07 11:49:31 -070021
jhorvitz0d0758a2020-07-14 12:50:24 -070022import com.google.common.collect.ImmutableList;
Googlerdce08002020-07-07 11:49:31 -070023import com.google.common.collect.ImmutableMap;
24import com.google.devtools.build.lib.actions.Artifact;
25import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
26import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
27import com.google.devtools.build.lib.actions.ArtifactRoot;
28import com.google.devtools.build.lib.actions.FileArtifactValue;
29import com.google.devtools.build.lib.actions.FileArtifactValue.RemoteFileArtifactValue;
30import com.google.devtools.build.lib.actions.cache.MetadataInjector;
31import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
jhorvitz0d0758a2020-07-14 12:50:24 -070032import com.google.devtools.build.lib.testutil.Scratch;
33import com.google.devtools.build.lib.util.Pair;
34import com.google.devtools.build.lib.vfs.Dirent;
35import com.google.devtools.build.lib.vfs.FileSystem;
36import com.google.devtools.build.lib.vfs.Path;
Googlerdce08002020-07-07 11:49:31 -070037import com.google.devtools.build.lib.vfs.PathFragment;
38import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
jhorvitz0d0758a2020-07-14 12:50:24 -070039import java.io.IOException;
40import java.util.ArrayList;
Googlerdce08002020-07-07 11:49:31 -070041import java.util.HashMap;
jhorvitz0d0758a2020-07-14 12:50:24 -070042import java.util.List;
Googlerdce08002020-07-07 11:49:31 -070043import java.util.Map;
44import org.junit.Test;
45import org.junit.runner.RunWith;
jhorvitz0d0758a2020-07-14 12:50:24 -070046import org.junit.runners.JUnit4;
Googlerdce08002020-07-07 11:49:31 -070047import org.junit.runners.Parameterized;
48import org.junit.runners.Parameterized.Parameter;
49import org.junit.runners.Parameterized.Parameters;
50
51/** Tests for {@link TreeArtifactValue}. */
jhorvitz0d0758a2020-07-14 12:50:24 -070052@RunWith(JUnit4.class)
Googlerdce08002020-07-07 11:49:31 -070053public final class TreeArtifactValueTest {
54
jhorvitz0d0758a2020-07-14 12:50:24 -070055 private final Scratch scratch = new Scratch();
jhorvitz1397e1e2020-07-21 10:02:17 -070056 private final ArtifactRoot root = ArtifactRoot.asDerivedRoot(scratch.resolve("root"), "bin");
57
58 @Test
59 public void orderIndependence() {
60 SpecialArtifact parent = createTreeArtifact("bin/tree");
61 TreeFileArtifact child1 = TreeFileArtifact.createTreeOutput(parent, "child1");
62 TreeFileArtifact child2 = TreeFileArtifact.createTreeOutput(parent, "child2");
63 FileArtifactValue metadata1 = metadataWithId(1);
64 FileArtifactValue metadata2 = metadataWithId(2);
65
66 TreeArtifactValue tree1 =
67 TreeArtifactValue.create(ImmutableMap.of(child1, metadata1, child2, metadata2));
68 TreeArtifactValue tree2 =
69 TreeArtifactValue.create(ImmutableMap.of(child2, metadata2, child1, metadata1));
70
71 assertThat(tree1).isEqualTo(tree2);
72 }
73
74 @Test
75 public void nullDigests_equal() {
76 SpecialArtifact parent = createTreeArtifact("bin/tree");
77 TreeFileArtifact child = TreeFileArtifact.createTreeOutput(parent, "child");
78 FileArtifactValue metadataNoDigest = metadataWithIdNoDigest(1);
79
80 TreeArtifactValue tree1 = TreeArtifactValue.create(ImmutableMap.of(child, metadataNoDigest));
81 TreeArtifactValue tree2 = TreeArtifactValue.create(ImmutableMap.of(child, metadataNoDigest));
82
83 assertThat(metadataNoDigest.getDigest()).isNull();
84 assertThat(tree1.getDigest()).isNotNull();
85 assertThat(tree2.getDigest()).isNotNull();
86 assertThat(tree1).isEqualTo(tree2);
87 }
88
89 @Test
90 public void nullDigests_notEqual() {
91 SpecialArtifact parent = createTreeArtifact("bin/tree");
92 TreeFileArtifact child = TreeFileArtifact.createTreeOutput(parent, "child");
93 FileArtifactValue metadataNoDigest1 = metadataWithIdNoDigest(1);
94 FileArtifactValue metadataNoDigest2 = metadataWithIdNoDigest(2);
95
96 TreeArtifactValue tree1 = TreeArtifactValue.create(ImmutableMap.of(child, metadataNoDigest1));
97 TreeArtifactValue tree2 = TreeArtifactValue.create(ImmutableMap.of(child, metadataNoDigest2));
98
99 assertThat(metadataNoDigest1.getDigest()).isNull();
100 assertThat(metadataNoDigest2.getDigest()).isNull();
101 assertThat(tree1.getDigest()).isNotNull();
102 assertThat(tree2.getDigest()).isNotNull();
103 assertThat(tree1.getDigest()).isNotEqualTo(tree2.getDigest());
104 }
Googlerdce08002020-07-07 11:49:31 -0700105
106 @Test
jhorvitz0d0758a2020-07-14 12:50:24 -0700107 public void visitTree_visitsEachChild() throws Exception {
108 Path treeDir = scratch.dir("tree");
109 scratch.file("tree/file1");
110 scratch.file("tree/a/file2");
111 scratch.file("tree/a/b/file3");
112 scratch.resolve("tree/file_link").createSymbolicLink(PathFragment.create("file1"));
113 scratch.resolve("tree/a/dir_link").createSymbolicLink(PathFragment.create("c"));
114 scratch.resolve("tree/a/b/dangling_link").createSymbolicLink(PathFragment.create("?"));
115 List<Pair<PathFragment, Dirent.Type>> children = new ArrayList<>();
Googlerdce08002020-07-07 11:49:31 -0700116
jhorvitz0d0758a2020-07-14 12:50:24 -0700117 TreeArtifactValue.visitTree(treeDir, (child, type) -> children.add(Pair.of(child, type)));
Googlerdce08002020-07-07 11:49:31 -0700118
jhorvitz0d0758a2020-07-14 12:50:24 -0700119 assertThat(children)
Googlerdce08002020-07-07 11:49:31 -0700120 .containsExactly(
jhorvitz0d0758a2020-07-14 12:50:24 -0700121 Pair.of(PathFragment.create("a"), Dirent.Type.DIRECTORY),
122 Pair.of(PathFragment.create("a/b"), Dirent.Type.DIRECTORY),
123 Pair.of(PathFragment.create("file1"), Dirent.Type.FILE),
124 Pair.of(PathFragment.create("a/file2"), Dirent.Type.FILE),
125 Pair.of(PathFragment.create("a/b/file3"), Dirent.Type.FILE),
126 Pair.of(PathFragment.create("file_link"), Dirent.Type.SYMLINK),
127 Pair.of(PathFragment.create("a/dir_link"), Dirent.Type.SYMLINK),
128 Pair.of(PathFragment.create("a/b/dangling_link"), Dirent.Type.SYMLINK));
Googlerdce08002020-07-07 11:49:31 -0700129 }
130
131 @Test
jhorvitz0d0758a2020-07-14 12:50:24 -0700132 public void visitTree_throwsOnUnknownDirentType() {
133 FileSystem fs =
134 new InMemoryFileSystem() {
135 @Override
136 public ImmutableList<Dirent> readdir(Path path, boolean followSymlinks) {
137 return ImmutableList.of(new Dirent("?", Dirent.Type.UNKNOWN));
138 }
139 };
140 Path treeDir = fs.getPath("/tree");
Googlerdce08002020-07-07 11:49:31 -0700141
jhorvitz0d0758a2020-07-14 12:50:24 -0700142 Exception e =
143 assertThrows(
144 IOException.class,
145 () ->
146 TreeArtifactValue.visitTree(
147 treeDir, (child, type) -> fail("Should not be called")));
148 assertThat(e).hasMessageThat().contains("Could not determine type of file for ? under /tree");
149 }
Googlerdce08002020-07-07 11:49:31 -0700150
jhorvitz0d0758a2020-07-14 12:50:24 -0700151 @Test
152 public void visitTree_propagatesIoExceptionFromVisitor() throws Exception {
153 Path treeDir = scratch.dir("tree");
154 scratch.file("tree/file");
155 IOException e = new IOException("From visitor");
156
157 IOException thrown =
158 assertThrows(
159 IOException.class,
160 () ->
161 TreeArtifactValue.visitTree(
162 treeDir,
163 (child, type) -> {
164 assertThat(child).isEqualTo(PathFragment.create("file"));
165 assertThat(type).isEqualTo(Dirent.Type.FILE);
166 throw e;
167 }));
168 assertThat(thrown).isSameInstanceAs(e);
169 }
170
171 @Test
172 public void visitTree_pemitsUpLevelSymlinkInsideTree() throws Exception {
173 Path treeDir = scratch.dir("tree");
174 scratch.file("tree/file");
175 scratch.dir("tree/a");
176 scratch.resolve("tree/a/up_link").createSymbolicLink(PathFragment.create("../file"));
177 List<Pair<PathFragment, Dirent.Type>> children = new ArrayList<>();
178
179 TreeArtifactValue.visitTree(treeDir, (child, type) -> children.add(Pair.of(child, type)));
180
181 assertThat(children)
Googlerdce08002020-07-07 11:49:31 -0700182 .containsExactly(
jhorvitz0d0758a2020-07-14 12:50:24 -0700183 Pair.of(PathFragment.create("file"), Dirent.Type.FILE),
184 Pair.of(PathFragment.create("a"), Dirent.Type.DIRECTORY),
185 Pair.of(PathFragment.create("a/up_link"), Dirent.Type.SYMLINK));
Googlerdce08002020-07-07 11:49:31 -0700186 }
187
jhorvitz0d0758a2020-07-14 12:50:24 -0700188 @Test
189 public void visitTree_permitsAbsoluteSymlink() throws Exception {
190 Path treeDir = scratch.dir("tree");
191 scratch.resolve("tree/absolute_link").createSymbolicLink(PathFragment.create("/tmp"));
192 List<Pair<PathFragment, Dirent.Type>> children = new ArrayList<>();
193
194 TreeArtifactValue.visitTree(treeDir, (child, type) -> children.add(Pair.of(child, type)));
195
196 assertThat(children)
197 .containsExactly(Pair.of(PathFragment.create("absolute_link"), Dirent.Type.SYMLINK));
Googlerdce08002020-07-07 11:49:31 -0700198 }
199
jhorvitz0d0758a2020-07-14 12:50:24 -0700200 @Test
201 public void visitTree_throwsOnSymlinkPointingOutsideTree() throws Exception {
202 Path treeDir = scratch.dir("tree");
203 scratch.file("outside");
204 scratch.resolve("tree/link").createSymbolicLink(PathFragment.create("../outside"));
205
206 Exception e =
207 assertThrows(
208 IOException.class,
209 () ->
210 TreeArtifactValue.visitTree(
211 treeDir, (child, type) -> fail("Should not be called")));
212 assertThat(e).hasMessageThat().contains("/tree/link pointing to ../outside");
Googlerdce08002020-07-07 11:49:31 -0700213 }
214
jhorvitz0d0758a2020-07-14 12:50:24 -0700215 @Test
216 public void visitTree_throwsOnSymlinkTraversingOutsideThenBackInsideTree() throws Exception {
217 Path treeDir = scratch.dir("tree");
218 scratch.file("tree/file");
219 scratch.resolve("tree/link").createSymbolicLink(PathFragment.create("../tree/file"));
Googlerdce08002020-07-07 11:49:31 -0700220
jhorvitz0d0758a2020-07-14 12:50:24 -0700221 Exception e =
222 assertThrows(
223 IOException.class,
224 () ->
225 TreeArtifactValue.visitTree(
226 treeDir,
227 (child, type) -> {
228 assertThat(child).isEqualTo(PathFragment.create("file"));
229 assertThat(type).isEqualTo(Dirent.Type.FILE);
230 }));
231 assertThat(e).hasMessageThat().contains("/tree/link pointing to ../tree/file");
232 }
233
234 /** Parameterized tests for {@link TreeArtifactValue.MultiBuilder}. */
235 @RunWith(Parameterized.class)
236 public static final class MultiBuilderTest {
237
238 private static final ArtifactRoot ROOT =
239 ArtifactRoot.asDerivedRoot(new InMemoryFileSystem().getPath("/root"), "bin");
240
241 private enum MultiBuilderType {
242 BASIC {
243 @Override
244 TreeArtifactValue.MultiBuilder newMultiBuilder() {
245 return TreeArtifactValue.newMultiBuilder();
246 }
247 },
248 CONCURRENT {
249 @Override
250 TreeArtifactValue.MultiBuilder newMultiBuilder() {
251 return TreeArtifactValue.newConcurrentMultiBuilder();
252 }
253 };
254
255 abstract TreeArtifactValue.MultiBuilder newMultiBuilder();
Googlerdce08002020-07-07 11:49:31 -0700256 }
257
jhorvitz0d0758a2020-07-14 12:50:24 -0700258 @Parameter public MultiBuilderType multiBuilderType;
259 private final FakeMetadataInjector metadataInjector = new FakeMetadataInjector();
260
261 @Parameters(name = "{0}")
262 public static MultiBuilderType[] params() {
263 return MultiBuilderType.values();
Googlerdce08002020-07-07 11:49:31 -0700264 }
265
jhorvitz0d0758a2020-07-14 12:50:24 -0700266 @Test
267 public void empty() {
268 TreeArtifactValue.MultiBuilder treeArtifacts = multiBuilderType.newMultiBuilder();
269
270 treeArtifacts.injectTo(metadataInjector);
271
272 assertThat(metadataInjector.injectedTreeArtifacts).isEmpty();
273 }
274
275 @Test
276 public void singleTreeArtifact() {
277 TreeArtifactValue.MultiBuilder treeArtifacts = multiBuilderType.newMultiBuilder();
jhorvitz1397e1e2020-07-21 10:02:17 -0700278 SpecialArtifact parent = createTreeArtifact("bin/tree");
jhorvitz0d0758a2020-07-14 12:50:24 -0700279 TreeFileArtifact child1 = TreeFileArtifact.createTreeOutput(parent, "child1");
280 TreeFileArtifact child2 = TreeFileArtifact.createTreeOutput(parent, "child2");
281
282 treeArtifacts.putChild(child1, metadataWithId(1));
283 treeArtifacts.putChild(child2, metadataWithId(2));
284 treeArtifacts.injectTo(metadataInjector);
285
286 assertThat(metadataInjector.injectedTreeArtifacts)
287 .containsExactly(
288 parent,
289 TreeArtifactValue.create(
290 ImmutableMap.of(child1, metadataWithId(1), child2, metadataWithId(2))));
291 }
292
293 @Test
294 public void multipleTreeArtifacts() {
295 TreeArtifactValue.MultiBuilder treeArtifacts = multiBuilderType.newMultiBuilder();
jhorvitz1397e1e2020-07-21 10:02:17 -0700296 SpecialArtifact parent1 = createTreeArtifact("bin/tree1");
jhorvitz0d0758a2020-07-14 12:50:24 -0700297 TreeFileArtifact parent1Child1 = TreeFileArtifact.createTreeOutput(parent1, "child1");
298 TreeFileArtifact parent1Child2 = TreeFileArtifact.createTreeOutput(parent1, "child2");
jhorvitz1397e1e2020-07-21 10:02:17 -0700299 SpecialArtifact parent2 = createTreeArtifact("bin/tree2");
jhorvitz0d0758a2020-07-14 12:50:24 -0700300 TreeFileArtifact parent2Child = TreeFileArtifact.createTreeOutput(parent2, "child");
301
302 treeArtifacts.putChild(parent1Child1, metadataWithId(1));
303 treeArtifacts.putChild(parent2Child, metadataWithId(3));
304 treeArtifacts.putChild(parent1Child2, metadataWithId(2));
305 treeArtifacts.injectTo(metadataInjector);
306
307 assertThat(metadataInjector.injectedTreeArtifacts)
308 .containsExactly(
309 parent1,
310 TreeArtifactValue.create(
311 ImmutableMap.of(
312 parent1Child1, metadataWithId(1), parent1Child2, metadataWithId(2))),
313 parent2,
314 TreeArtifactValue.create(ImmutableMap.of(parent2Child, metadataWithId(3))));
315 }
316
317 private static SpecialArtifact createTreeArtifact(String execPath) {
jhorvitz1397e1e2020-07-21 10:02:17 -0700318 return TreeArtifactValueTest.createTreeArtifact(execPath, ROOT);
jhorvitz0d0758a2020-07-14 12:50:24 -0700319 }
320
321 private static final class FakeMetadataInjector implements MetadataInjector {
322 private final Map<SpecialArtifact, TreeArtifactValue> injectedTreeArtifacts = new HashMap<>();
323
324 @Override
325 public void injectFile(Artifact output, FileArtifactValue metadata) {
326 throw new UnsupportedOperationException();
327 }
328
329 @Override
330 public void injectDirectory(
331 SpecialArtifact output, Map<TreeFileArtifact, FileArtifactValue> children) {
332 injectedTreeArtifacts.put(output, TreeArtifactValue.create(children));
333 }
Googlerdce08002020-07-07 11:49:31 -0700334 }
335 }
jhorvitz1397e1e2020-07-21 10:02:17 -0700336
337 private SpecialArtifact createTreeArtifact(String execPath) {
338 return createTreeArtifact(execPath, root);
339 }
340
341 private static SpecialArtifact createTreeArtifact(String execPath, ArtifactRoot root) {
342 return ActionsTestUtil.createTreeArtifactWithGeneratingAction(
343 root, PathFragment.create(execPath));
344 }
345
346 private static FileArtifactValue metadataWithId(int id) {
347 return new RemoteFileArtifactValue(new byte[] {(byte) id}, id, id);
348 }
349
350 private static FileArtifactValue metadataWithIdNoDigest(int id) {
351 FileArtifactValue value = mock(FileArtifactValue.class);
352 when(value.getDigest()).thenReturn(null);
353 when(value.getModifiedTime()).thenReturn((long) id);
354 return value;
355 }
Googlerdce08002020-07-07 11:49:31 -0700356}