blob: 4c1a6713cc1960a2c61e8999b574e7b4ad37ab17 [file] [log] [blame]
Googlerece75722016-02-11 17:55:41 +00001// Copyright 2016 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.base.Throwables.getRootCause;
17import static com.google.common.truth.Truth.assertThat;
Rumou Duana77f32c2016-04-13 21:59:21 +000018import static com.google.devtools.build.lib.actions.ActionInputHelper.treeFileArtifact;
Googlerece75722016-02-11 17:55:41 +000019import static org.junit.Assert.assertFalse;
20import static org.junit.Assert.assertTrue;
21import static org.junit.Assert.fail;
22
23import com.google.common.base.Function;
24import com.google.common.base.Preconditions;
25import com.google.common.collect.Collections2;
26import com.google.common.collect.ImmutableList;
27import com.google.common.collect.Lists;
28import com.google.common.hash.Hashing;
29import com.google.common.util.concurrent.Runnables;
30import com.google.devtools.build.lib.actions.ActionExecutionContext;
31import com.google.devtools.build.lib.actions.ActionExecutionException;
32import com.google.devtools.build.lib.actions.ActionInput;
33import com.google.devtools.build.lib.actions.ActionInputHelper;
34import com.google.devtools.build.lib.actions.Artifact;
35import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
36import com.google.devtools.build.lib.actions.Artifact.SpecialArtifactType;
Rumou Duana77f32c2016-04-13 21:59:21 +000037import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
Googlerece75722016-02-11 17:55:41 +000038import com.google.devtools.build.lib.actions.BuildFailedException;
39import com.google.devtools.build.lib.actions.Root;
40import com.google.devtools.build.lib.actions.TestExecException;
41import com.google.devtools.build.lib.actions.cache.InjectedStat;
42import com.google.devtools.build.lib.actions.cache.MetadataHandler;
43import com.google.devtools.build.lib.actions.util.TestAction;
44import com.google.devtools.build.lib.testutil.TestUtils;
45import com.google.devtools.build.lib.util.AbruptExitException;
46import com.google.devtools.build.lib.vfs.FileStatus;
47import com.google.devtools.build.lib.vfs.FileSystem;
48import com.google.devtools.build.lib.vfs.FileSystemUtils;
49import com.google.devtools.build.lib.vfs.Path;
50import com.google.devtools.build.lib.vfs.PathFragment;
51import com.google.devtools.build.lib.vfs.Symlinks;
52
53import org.junit.Before;
54import org.junit.Test;
55import org.junit.runner.RunWith;
56import org.junit.runners.JUnit4;
57
58import java.io.IOException;
59import java.nio.charset.Charset;
60import java.util.Arrays;
61import java.util.Collection;
62import java.util.Iterator;
63import java.util.List;
64
65import javax.annotation.Nullable;
66
67/** Timestamp builder tests for TreeArtifacts. */
68@RunWith(JUnit4.class)
69public class TreeArtifactBuildTest extends TimestampBuilderTestCase {
Rumou Duana77f32c2016-04-13 21:59:21 +000070 // Common Artifacts, TreeFileArtifact, and Buttons. These aren't all used in all tests, but
71 // they're used often enough that we can save ourselves a lot of copy-pasted code by creating them
Googlerece75722016-02-11 17:55:41 +000072 // in setUp().
73
74 Artifact in;
75
76 Artifact outOne;
Rumou Duana77f32c2016-04-13 21:59:21 +000077 TreeFileArtifact outOneFileOne;
78 TreeFileArtifact outOneFileTwo;
Googlerece75722016-02-11 17:55:41 +000079 Button buttonOne = new Button();
80
81 Artifact outTwo;
Rumou Duana77f32c2016-04-13 21:59:21 +000082 TreeFileArtifact outTwoFileOne;
83 TreeFileArtifact outTwoFileTwo;
Googlerece75722016-02-11 17:55:41 +000084 Button buttonTwo = new Button();
85
86 @Before
87 public void setUp() throws Exception {
88 in = createSourceArtifact("input");
89 writeFile(in, "input_content");
90
91 outOne = createTreeArtifact("outputOne");
Rumou Duana77f32c2016-04-13 21:59:21 +000092 outOneFileOne = treeFileArtifact(outOne, "out_one_file_one");
93 outOneFileTwo = treeFileArtifact(outOne, "out_one_file_two");
Googlerece75722016-02-11 17:55:41 +000094
95 outTwo = createTreeArtifact("outputTwo");
Rumou Duana77f32c2016-04-13 21:59:21 +000096 outTwoFileOne = treeFileArtifact(outTwo, "out_one_file_one");
97 outTwoFileTwo = treeFileArtifact(outTwo, "out_one_file_two");
Googlerece75722016-02-11 17:55:41 +000098 }
99
100 /** Simple smoke test. If this isn't passing, something is very wrong... */
101 @Test
102 public void testTreeArtifactSimpleCase() throws Exception {
103 TouchingTestAction action = new TouchingTestAction(outOneFileOne, outOneFileTwo);
104 registerAction(action);
105 buildArtifact(action.getSoleOutput());
106
107 assertTrue(outOneFileOne.getPath().exists());
108 assertTrue(outOneFileTwo.getPath().exists());
109 }
110
111 /** Simple test for the case with dependencies. */
112 @Test
113 public void testDependentTreeArtifacts() throws Exception {
114 TouchingTestAction actionOne = new TouchingTestAction(outOneFileOne, outOneFileTwo);
115 registerAction(actionOne);
116
117 CopyTreeAction actionTwo = new CopyTreeAction(
118 ImmutableList.of(outOneFileOne, outOneFileTwo),
119 ImmutableList.of(outTwoFileOne, outTwoFileTwo));
120 registerAction(actionTwo);
121
122 buildArtifact(outTwo);
123
124 assertTrue(outOneFileOne.getPath().exists());
125 assertTrue(outOneFileTwo.getPath().exists());
126 assertTrue(outTwoFileOne.getPath().exists());
127 assertTrue(outTwoFileTwo.getPath().exists());
128 }
129
130 /** Unchanged TreeArtifact outputs should not cause reexecution. */
131 @Test
132 public void testCacheCheckingForTreeArtifactsDoesNotCauseReexecution() throws Exception {
133 Artifact outOne = createTreeArtifact("outputOne");
134 Button buttonOne = new Button();
135
136 Artifact outTwo = createTreeArtifact("outputTwo");
137 Button buttonTwo = new Button();
138
139 TouchingTestAction actionOne = new TouchingTestAction(
140 buttonOne, outOne, "file_one", "file_two");
141 registerAction(actionOne);
142
143 CopyTreeAction actionTwo = new CopyTreeAction(
144 buttonTwo, outOne, outTwo, "file_one", "file_two");
145 registerAction(actionTwo);
146
147 buttonOne.pressed = buttonTwo.pressed = false;
148 buildArtifact(outTwo);
149 assertTrue(buttonOne.pressed); // built
150 assertTrue(buttonTwo.pressed); // built
151
152 buttonOne.pressed = buttonTwo.pressed = false;
153 buildArtifact(outTwo);
154 assertFalse(buttonOne.pressed); // not built
155 assertFalse(buttonTwo.pressed); // not built
156 }
157
158 /**
159 * Test rebuilding TreeArtifacts for inputs, outputs, and dependents.
160 * Also a test for caching.
161 */
162 @Test
163 public void testTransitiveReexecutionForTreeArtifacts() throws Exception {
164 WriteInputToFilesAction actionOne = new WriteInputToFilesAction(
165 buttonOne,
166 in,
167 outOneFileOne, outOneFileTwo);
168 registerAction(actionOne);
169
170 CopyTreeAction actionTwo = new CopyTreeAction(
171 buttonTwo,
172 ImmutableList.of(outOneFileOne, outOneFileTwo),
173 ImmutableList.of(outTwoFileOne, outTwoFileTwo));
174 registerAction(actionTwo);
175
176 buttonOne.pressed = buttonTwo.pressed = false;
177 buildArtifact(outTwo);
178 assertTrue(buttonOne.pressed); // built
179 assertTrue(buttonTwo.pressed); // built
180
181 buttonOne.pressed = buttonTwo.pressed = false;
182 writeFile(in, "modified_input");
183 buildArtifact(outTwo);
184 assertTrue(buttonOne.pressed); // built
185 assertTrue(buttonTwo.pressed); // not built
186
187 buttonOne.pressed = buttonTwo.pressed = false;
188 writeFile(outOneFileOne, "modified_output");
189 buildArtifact(outTwo);
190 assertTrue(buttonOne.pressed); // built
191 assertFalse(buttonTwo.pressed); // should have been cached
192
193 buttonOne.pressed = buttonTwo.pressed = false;
194 writeFile(outTwoFileOne, "more_modified_output");
195 buildArtifact(outTwo);
196 assertFalse(buttonOne.pressed); // not built
197 assertTrue(buttonTwo.pressed); // built
198 }
199
200 /** Tests that changing a TreeArtifact directory should cause reexeuction. */
201 @Test
202 public void testDirectoryContentsCachingForTreeArtifacts() throws Exception {
203 WriteInputToFilesAction actionOne = new WriteInputToFilesAction(
204 buttonOne,
205 in,
206 outOneFileOne, outOneFileTwo);
207 registerAction(actionOne);
208
209 CopyTreeAction actionTwo = new CopyTreeAction(
210 buttonTwo,
211 ImmutableList.of(outOneFileOne, outOneFileTwo),
212 ImmutableList.of(outTwoFileOne, outTwoFileTwo));
213 registerAction(actionTwo);
214
215 buttonOne.pressed = buttonTwo.pressed = false;
216 buildArtifact(outTwo);
217 // just a smoke test--if these aren't built we have bigger problems!
218 assertTrue(buttonOne.pressed);
219 assertTrue(buttonTwo.pressed);
220
221 // Adding a file to a directory should cause reexecution.
222 buttonOne.pressed = buttonTwo.pressed = false;
223 Path spuriousOutputOne = outOne.getPath().getRelative("spuriousOutput");
224 touchFile(spuriousOutputOne);
225 buildArtifact(outTwo);
226 // Should re-execute, and delete spurious output
227 assertFalse(spuriousOutputOne.exists());
228 assertTrue(buttonOne.pressed);
229 assertFalse(buttonTwo.pressed); // should have been cached
230
231 buttonOne.pressed = buttonTwo.pressed = false;
232 Path spuriousOutputTwo = outTwo.getPath().getRelative("anotherSpuriousOutput");
233 touchFile(spuriousOutputTwo);
234 buildArtifact(outTwo);
235 assertFalse(spuriousOutputTwo.exists());
236 assertFalse(buttonOne.pressed);
237 assertTrue(buttonTwo.pressed);
238
239 // Deleting should cause reexecution.
240 buttonOne.pressed = buttonTwo.pressed = false;
241 deleteFile(outOneFileOne);
242 buildArtifact(outTwo);
243 assertTrue(outOneFileOne.getPath().exists());
244 assertTrue(buttonOne.pressed);
245 assertFalse(buttonTwo.pressed); // should have been cached
246
247 buttonOne.pressed = buttonTwo.pressed = false;
248 deleteFile(outTwoFileOne);
249 buildArtifact(outTwo);
250 assertTrue(outTwoFileOne.getPath().exists());
251 assertFalse(buttonOne.pressed);
252 assertTrue(buttonTwo.pressed);
253 }
254
255 /**
256 * TreeArtifacts don't care about mtime, even when the file is empty.
257 * However, actions taking input non-Tree artifacts still care about mtime
258 * (although this behavior should go away).
259 */
260 @Test
261 public void testMTimeForTreeArtifactsDoesNotMatter() throws Exception {
262 // For this test, we only touch the input file.
263 Artifact in = createSourceArtifact("touchable_input");
264 touchFile(in);
265
266 WriteInputToFilesAction actionOne = new WriteInputToFilesAction(
267 buttonOne,
268 in,
269 outOneFileOne, outOneFileTwo);
270 registerAction(actionOne);
271
272 CopyTreeAction actionTwo = new CopyTreeAction(
273 buttonTwo,
274 ImmutableList.of(outOneFileOne, outOneFileTwo),
275 ImmutableList.of(outTwoFileOne, outTwoFileTwo));
276 registerAction(actionTwo);
277
278 buttonOne.pressed = buttonTwo.pressed = false;
279 buildArtifact(outTwo);
280 assertTrue(buttonOne.pressed); // built
281 assertTrue(buttonTwo.pressed); // built
282
283 buttonOne.pressed = buttonTwo.pressed = false;
284 touchFile(in);
285 buildArtifact(outTwo);
286 // Per existing behavior, mtime matters for empty file Artifacts.
287 assertTrue(buttonOne.pressed);
288 // But this should be cached.
289 assertFalse(buttonTwo.pressed);
290
291 // None of the below following should result in anything being built.
292 buttonOne.pressed = buttonTwo.pressed = false;
293 touchFile(outOneFileOne);
294 buildArtifact(outTwo);
295 // Nothing should be built.
296 assertFalse(buttonOne.pressed);
297 assertFalse(buttonTwo.pressed);
298
299 buttonOne.pressed = buttonTwo.pressed = false;
300 touchFile(outOneFileTwo);
301 buildArtifact(outTwo);
302 // Nothing should be built.
303 assertFalse(buttonOne.pressed);
304 assertFalse(buttonTwo.pressed);
305 }
306
307 /** Tests that the declared order of TreeArtifact contents does not matter. */
308 @Test
309 public void testOrderIndependenceOfTreeArtifactContents() throws Exception {
310 WriteInputToFilesAction actionOne = new WriteInputToFilesAction(
311 in,
312 // The design of WritingTestAction is s.t.
313 // these files will be registered in the given order.
314 outOneFileTwo, outOneFileOne);
315 registerAction(actionOne);
316
317 CopyTreeAction actionTwo = new CopyTreeAction(
318 ImmutableList.of(outOneFileOne, outOneFileTwo),
319 ImmutableList.of(outTwoFileOne, outTwoFileTwo));
320 registerAction(actionTwo);
321
322 buildArtifact(outTwo);
323 }
324
325 @Test
326 public void testActionExpansion() throws Exception {
327 WriteInputToFilesAction action = new WriteInputToFilesAction(in, outOneFileOne, outOneFileTwo);
328
329 CopyTreeAction actionTwo = new CopyTreeAction(
330 ImmutableList.of(outOneFileOne, outOneFileTwo),
331 ImmutableList.of(outTwoFileOne, outTwoFileTwo)) {
332 @Override
333 public void executeTestBehavior(ActionExecutionContext actionExecutionContext)
334 throws ActionExecutionException {
335 super.executeTestBehavior(actionExecutionContext);
336
337 Collection<ActionInput> expanded =
338 ActionInputHelper.expandArtifacts(ImmutableList.of(outOne),
339 actionExecutionContext.getArtifactExpander());
340 // Only files registered should show up here.
341 assertThat(expanded).containsExactly(outOneFileOne, outOneFileTwo);
342 }
343 };
344
345 registerAction(action);
346 registerAction(actionTwo);
347
348 buildArtifact(outTwo); // should not fail
349 }
350
351 @Test
352 public void testInvalidOutputRegistrations() throws Exception {
353 TreeArtifactTestAction failureOne = new TreeArtifactTestAction(
354 Runnables.doNothing(), outOneFileOne, outOneFileTwo) {
355 @Override
356 public void executeTestBehavior(ActionExecutionContext actionExecutionContext)
357 throws ActionExecutionException {
358 try {
359 writeFile(outOneFileOne, "one");
360 writeFile(outOneFileTwo, "two");
361 // In this test case, we only register one output. This will fail.
362 registerOutput(actionExecutionContext, "one");
363 } catch (IOException e) {
364 throw new RuntimeException(e);
365 }
366 }
367 };
368
369 registerAction(failureOne);
370 try {
371 buildArtifact(outOne);
372 fail(); // Should have thrown
373 } catch (Exception e) {
374 assertThat(getRootCause(e).getMessage()).contains("not present on disk");
375 }
376
377 TreeArtifactTestAction failureTwo = new TreeArtifactTestAction(
378 Runnables.doNothing(), outTwoFileOne, outTwoFileTwo) {
379 @Override
380 public void executeTestBehavior(ActionExecutionContext actionExecutionContext)
381 throws ActionExecutionException {
382 try {
383 writeFile(outTwoFileOne, "one");
384 writeFile(outTwoFileTwo, "two");
385 // In this test case, register too many outputs. This will fail.
386 registerOutput(actionExecutionContext, "one");
387 registerOutput(actionExecutionContext, "two");
388 registerOutput(actionExecutionContext, "three");
389 } catch (IOException e) {
390 throw new RuntimeException(e);
391 }
392 }
393 };
394
395 registerAction(failureTwo);
396 try {
397 buildArtifact(outTwo);
398 fail(); // Should have thrown
399 } catch (Exception e) {
400 assertThat(getRootCause(e).getMessage()).contains("not present on disk");
401 }
402 }
403
404 private static void checkDirectoryPermissions(Path path) throws IOException {
405 assertTrue(path.isDirectory());
406 assertTrue(path.isExecutable());
407 assertTrue(path.isReadable());
408 assertFalse(path.isWritable());
409 }
410
411 private static void checkFilePermissions(Path path) throws IOException {
412 assertFalse(path.isDirectory());
413 assertTrue(path.isExecutable());
414 assertTrue(path.isReadable());
415 assertFalse(path.isWritable());
416 }
417
418 @Test
419 public void testOutputsAreReadOnlyAndExecutable() throws Exception {
420 final Artifact out = createTreeArtifact("output");
421
422 TreeArtifactTestAction action = new TreeArtifactTestAction(out) {
423 @Override
424 public void execute(ActionExecutionContext actionExecutionContext)
425 throws ActionExecutionException {
426 try {
427 writeFile(out.getPath().getChild("one"), "one");
428 writeFile(out.getPath().getChild("two"), "two");
429 writeFile(out.getPath().getChild("three").getChild("four"), "three/four");
430 registerOutput(actionExecutionContext, "one");
431 registerOutput(actionExecutionContext, "two");
432 registerOutput(actionExecutionContext, "three/four");
433 } catch (Exception e) {
434 throw new RuntimeException(e);
435 }
436 }
437 };
438
439 registerAction(action);
440
441 buildArtifact(action.getSoleOutput());
442
443 checkDirectoryPermissions(out.getPath());
444 checkFilePermissions(out.getPath().getChild("one"));
445 checkFilePermissions(out.getPath().getChild("two"));
446 checkDirectoryPermissions(out.getPath().getChild("three"));
447 checkFilePermissions(out.getPath().getChild("three").getChild("four"));
448 }
449
450 // This is more a smoke test than anything, because it turns out that:
451 // 1) there is no easy way to turn fast digests on/off for these test cases, and
452 // 2) injectDigest() doesn't really complain if you inject bad digests or digests
453 // for nonexistent files. Instead some weird error shows up down the line.
454 // In fact, there are no tests for injectDigest anywhere in the codebase.
455 // So all we're really testing here is that injectDigest() doesn't throw a weird exception.
456 // TODO(bazel-team): write real tests for injectDigest, here and elsewhere.
457 @Test
458 public void testDigestInjection() throws Exception {
459 TreeArtifactTestAction action = new TreeArtifactTestAction(outOne) {
460 @Override
461 public void execute(ActionExecutionContext actionExecutionContext)
462 throws ActionExecutionException {
463 try {
464 writeFile(outOneFileOne, "one");
465 writeFile(outOneFileTwo, "two");
466
467 MetadataHandler md = actionExecutionContext.getMetadataHandler();
468 FileStatus stat = outOneFileOne.getPath().stat(Symlinks.NOFOLLOW);
469 md.injectDigest(outOneFileOne,
470 new InjectedStat(stat.getLastModifiedTime(), stat.getSize(), stat.getNodeId()),
471 Hashing.md5().hashString("one", Charset.forName("UTF-8")).asBytes());
472
473 stat = outOneFileTwo.getPath().stat(Symlinks.NOFOLLOW);
474 md.injectDigest(outOneFileTwo,
475 new InjectedStat(stat.getLastModifiedTime(), stat.getSize(), stat.getNodeId()),
476 Hashing.md5().hashString("two", Charset.forName("UTF-8")).asBytes());
477 } catch (Exception e) {
478 throw new RuntimeException(e);
479 }
480 }
481 };
482
483 registerAction(action);
484 buildArtifact(action.getSoleOutput());
485 }
486
487 /**
488 * A generic test action that takes at most one input TreeArtifact,
489 * exactly one output TreeArtifact, and some path fragment inputs/outputs.
490 */
491 private abstract static class TreeArtifactTestAction extends TestAction {
Rumou Duana77f32c2016-04-13 21:59:21 +0000492 final Iterable<TreeFileArtifact> inputFiles;
493 final Iterable<TreeFileArtifact> outputFiles;
Googlerece75722016-02-11 17:55:41 +0000494
495 TreeArtifactTestAction(final Artifact output, final String... subOutputs) {
496 this(Runnables.doNothing(),
497 null,
Rumou Duana77f32c2016-04-13 21:59:21 +0000498 ImmutableList.<TreeFileArtifact>of(),
Googlerece75722016-02-11 17:55:41 +0000499 output,
500 Collections2.transform(
501 Arrays.asList(subOutputs),
Rumou Duana77f32c2016-04-13 21:59:21 +0000502 new Function<String, TreeFileArtifact>() {
Googlerece75722016-02-11 17:55:41 +0000503 @Nullable
504 @Override
Rumou Duana77f32c2016-04-13 21:59:21 +0000505 public TreeFileArtifact apply(String s) {
506 return ActionInputHelper.treeFileArtifact(output, s);
Googlerece75722016-02-11 17:55:41 +0000507 }
508 }));
509 }
510
Rumou Duana77f32c2016-04-13 21:59:21 +0000511 TreeArtifactTestAction(Runnable effect, TreeFileArtifact... outputFiles) {
Googlerece75722016-02-11 17:55:41 +0000512 this(effect, Arrays.asList(outputFiles));
513 }
514
Rumou Duana77f32c2016-04-13 21:59:21 +0000515 TreeArtifactTestAction(Runnable effect, Collection<TreeFileArtifact> outputFiles) {
516 this(effect, null, ImmutableList.<TreeFileArtifact>of(),
Googlerece75722016-02-11 17:55:41 +0000517 outputFiles.iterator().next().getParent(), outputFiles);
518 }
519
520 TreeArtifactTestAction(Runnable effect, Artifact inputFile,
Rumou Duana77f32c2016-04-13 21:59:21 +0000521 Collection<TreeFileArtifact> outputFiles) {
522 this(effect, inputFile, ImmutableList.<TreeFileArtifact>of(),
Googlerece75722016-02-11 17:55:41 +0000523 outputFiles.iterator().next().getParent(), outputFiles);
524 }
525
Rumou Duana77f32c2016-04-13 21:59:21 +0000526 TreeArtifactTestAction(Runnable effect, Collection<TreeFileArtifact> inputFiles,
527 Collection<TreeFileArtifact> outputFiles) {
Googlerece75722016-02-11 17:55:41 +0000528 this(effect, inputFiles.iterator().next().getParent(), inputFiles,
529 outputFiles.iterator().next().getParent(), outputFiles);
530 }
531
532 TreeArtifactTestAction(
533 Runnable effect,
534 @Nullable Artifact input,
Rumou Duana77f32c2016-04-13 21:59:21 +0000535 Collection<TreeFileArtifact> inputFiles,
Googlerece75722016-02-11 17:55:41 +0000536 Artifact output,
Rumou Duana77f32c2016-04-13 21:59:21 +0000537 Collection<TreeFileArtifact> outputFiles) {
Googlerece75722016-02-11 17:55:41 +0000538 super(effect,
539 input == null ? ImmutableList.<Artifact>of() : ImmutableList.of(input),
540 ImmutableList.of(output));
541 Preconditions.checkArgument(
542 inputFiles.isEmpty() || (input != null && input.isTreeArtifact()));
543 Preconditions.checkArgument(output == null || output.isTreeArtifact());
544 this.inputFiles = ImmutableList.copyOf(inputFiles);
545 this.outputFiles = ImmutableList.copyOf(outputFiles);
Rumou Duana77f32c2016-04-13 21:59:21 +0000546 for (TreeFileArtifact inputFile : inputFiles) {
Googlerece75722016-02-11 17:55:41 +0000547 Preconditions.checkState(inputFile.getParent().equals(input));
548 }
Rumou Duana77f32c2016-04-13 21:59:21 +0000549 for (TreeFileArtifact outputFile : outputFiles) {
Googlerece75722016-02-11 17:55:41 +0000550 Preconditions.checkState(outputFile.getParent().equals(output));
551 }
552 }
553
554 @Override
555 public void execute(ActionExecutionContext actionExecutionContext)
556 throws ActionExecutionException {
557 if (getInputs().iterator().hasNext()) {
558 // Sanity check--verify all inputs exist.
559 Artifact input = getSoleInput();
560 if (!input.getPath().exists()) {
561 throw new IllegalStateException("action's input Artifact does not exist: "
562 + input.getPath());
563 }
Rumou Duana77f32c2016-04-13 21:59:21 +0000564 for (Artifact inputFile : inputFiles) {
Googlerece75722016-02-11 17:55:41 +0000565 if (!inputFile.getPath().exists()) {
566 throw new IllegalStateException("action's input does not exist: " + inputFile);
567 }
568 }
569 }
570
571 Artifact output = getSoleOutput();
572 assertTrue(output.getPath().exists());
573 try {
574 effect.call();
575 executeTestBehavior(actionExecutionContext);
Rumou Duana77f32c2016-04-13 21:59:21 +0000576 for (TreeFileArtifact outputFile : outputFiles) {
Googlerece75722016-02-11 17:55:41 +0000577 actionExecutionContext.getMetadataHandler().addExpandedTreeOutput(outputFile);
578 }
579 } catch (RuntimeException e) {
580 throw new RuntimeException(e);
581 } catch (Exception e) {
582 throw new ActionExecutionException("TestAction failed due to exception",
583 e, this, false);
584 }
585 }
586
587 void executeTestBehavior(ActionExecutionContext c) throws ActionExecutionException {
588 // Default: do nothing
589 }
590
591 /** Checks there's exactly one input, and returns it. */
592 // This prevents us from making testing mistakes, like
593 // assuming there's only one input when this isn't actually true.
594 Artifact getSoleInput() {
595 Iterator<Artifact> it = getInputs().iterator();
596 Artifact r = it.next();
597 Preconditions.checkNotNull(r);
598 Preconditions.checkState(!it.hasNext());
599 return r;
600 }
601
602 /** Checks there's exactly one output, and returns it. */
603 Artifact getSoleOutput() {
604 Iterator<Artifact> it = getOutputs().iterator();
605 Artifact r = it.next();
606 Preconditions.checkNotNull(r);
607 Preconditions.checkState(!it.hasNext());
608 Preconditions.checkState(r.equals(getPrimaryOutput()));
609 return r;
610 }
611
612 void registerOutput(ActionExecutionContext context, String outputName) throws IOException {
613 context.getMetadataHandler().addExpandedTreeOutput(
Rumou Duana77f32c2016-04-13 21:59:21 +0000614 treeFileArtifact(getSoleOutput(), new PathFragment(outputName)));
Googlerece75722016-02-11 17:55:41 +0000615 }
616
Rumou Duana77f32c2016-04-13 21:59:21 +0000617 static List<TreeFileArtifact> asTreeFileArtifacts(final Artifact parent, String... files) {
Googlerece75722016-02-11 17:55:41 +0000618 return Lists.transform(
619 Arrays.asList(files),
Rumou Duana77f32c2016-04-13 21:59:21 +0000620 new Function<String, TreeFileArtifact>() {
Googlerece75722016-02-11 17:55:41 +0000621 @Nullable
622 @Override
Rumou Duana77f32c2016-04-13 21:59:21 +0000623 public TreeFileArtifact apply(String s) {
624 return ActionInputHelper.treeFileArtifact(parent, s);
Googlerece75722016-02-11 17:55:41 +0000625 }
626 });
627 }
628 }
629
Rumou Duana77f32c2016-04-13 21:59:21 +0000630 /** An action that touches some output TreeFileArtifacts. Takes no inputs. */
Googlerece75722016-02-11 17:55:41 +0000631 private static class TouchingTestAction extends TreeArtifactTestAction {
Rumou Duana77f32c2016-04-13 21:59:21 +0000632 TouchingTestAction(TreeFileArtifact... outputPaths) {
Googlerece75722016-02-11 17:55:41 +0000633 super(Runnables.doNothing(), outputPaths);
634 }
635
636 TouchingTestAction(Runnable effect, Artifact output, String... outputPaths) {
Rumou Duana77f32c2016-04-13 21:59:21 +0000637 super(effect, asTreeFileArtifacts(output, outputPaths));
Googlerece75722016-02-11 17:55:41 +0000638 }
639
640 @Override
641 public void executeTestBehavior(ActionExecutionContext actionExecutionContext)
642 throws ActionExecutionException {
643 try {
Rumou Duana77f32c2016-04-13 21:59:21 +0000644 for (Artifact file : outputFiles) {
Googlerece75722016-02-11 17:55:41 +0000645 touchFile(file);
646 }
647 } catch (IOException e) {
648 throw new RuntimeException(e);
649 }
650 }
651 }
652
653 /** Takes an input file and populates several copies inside a TreeArtifact. */
654 private static class WriteInputToFilesAction extends TreeArtifactTestAction {
Rumou Duana77f32c2016-04-13 21:59:21 +0000655 WriteInputToFilesAction(Artifact input, TreeFileArtifact... outputs) {
Googlerece75722016-02-11 17:55:41 +0000656 this(Runnables.doNothing(), input, outputs);
657 }
658
659 WriteInputToFilesAction(
660 Runnable effect,
661 Artifact input,
Rumou Duana77f32c2016-04-13 21:59:21 +0000662 TreeFileArtifact... outputs) {
Googlerece75722016-02-11 17:55:41 +0000663 super(effect, input, Arrays.asList(outputs));
664 Preconditions.checkArgument(!input.isTreeArtifact());
665 }
666
667 @Override
668 public void executeTestBehavior(ActionExecutionContext actionExecutionContext)
669 throws ActionExecutionException {
670 try {
Rumou Duana77f32c2016-04-13 21:59:21 +0000671 for (Artifact file : outputFiles) {
Googlerece75722016-02-11 17:55:41 +0000672 FileSystemUtils.createDirectoryAndParents(file.getPath().getParentDirectory());
673 FileSystemUtils.copyFile(getSoleInput().getPath(), file.getPath());
674 }
675 } catch (IOException e) {
676 throw new RuntimeException(e);
677 }
678 }
679 }
680
Rumou Duana77f32c2016-04-13 21:59:21 +0000681 /** Copies the given TreeFileArtifact inputs to the given outputs, in respective order. */
Googlerece75722016-02-11 17:55:41 +0000682 private static class CopyTreeAction extends TreeArtifactTestAction {
683
684 CopyTreeAction(Runnable effect, Artifact input, Artifact output, String... sourcesAndDests) {
Rumou Duana77f32c2016-04-13 21:59:21 +0000685 super(effect, input, asTreeFileArtifacts(input, sourcesAndDests), output,
686 asTreeFileArtifacts(output, sourcesAndDests));
Googlerece75722016-02-11 17:55:41 +0000687 }
688
689 CopyTreeAction(
Rumou Duana77f32c2016-04-13 21:59:21 +0000690 Collection<TreeFileArtifact> inputPaths,
691 Collection<TreeFileArtifact> outputPaths) {
Googlerece75722016-02-11 17:55:41 +0000692 super(Runnables.doNothing(), inputPaths, outputPaths);
693 }
694
695 CopyTreeAction(
696 Runnable effect,
Rumou Duana77f32c2016-04-13 21:59:21 +0000697 Collection<TreeFileArtifact> inputPaths,
698 Collection<TreeFileArtifact> outputPaths) {
Googlerece75722016-02-11 17:55:41 +0000699 super(effect, inputPaths, outputPaths);
700 }
701
702 @Override
703 public void executeTestBehavior(ActionExecutionContext actionExecutionContext)
704 throws ActionExecutionException {
Rumou Duana77f32c2016-04-13 21:59:21 +0000705 Iterator<TreeFileArtifact> inputIterator = inputFiles.iterator();
706 Iterator<TreeFileArtifact> outputIterator = outputFiles.iterator();
Googlerece75722016-02-11 17:55:41 +0000707
708 try {
709 while (inputIterator.hasNext() || outputIterator.hasNext()) {
Rumou Duana77f32c2016-04-13 21:59:21 +0000710 Artifact input = inputIterator.next();
711 Artifact output = outputIterator.next();
Googlerece75722016-02-11 17:55:41 +0000712 FileSystemUtils.createDirectoryAndParents(output.getPath().getParentDirectory());
713 FileSystemUtils.copyFile(input.getPath(), output.getPath());
714 }
715 } catch (IOException e) {
716 throw new RuntimeException(e);
717 }
718
719 // both iterators must be of the same size
720 assertFalse(inputIterator.hasNext());
721 assertFalse(inputIterator.hasNext());
722 }
723 }
724
725 private Artifact createTreeArtifact(String name) {
726 FileSystem fs = scratch.getFileSystem();
727 Path execRoot = fs.getPath(TestUtils.tmpDir());
728 PathFragment execPath = new PathFragment("out").getRelative(name);
729 Path path = execRoot.getRelative(execPath);
730 return new SpecialArtifact(
731 path, Root.asDerivedRoot(execRoot, execRoot.getRelative("out")), execPath, ALL_OWNER,
732 SpecialArtifactType.TREE);
733 }
734
735 private void buildArtifact(Artifact artifact)
736 throws InterruptedException, BuildFailedException, TestExecException, AbruptExitException {
737 buildArtifacts(cachingBuilder(), artifact);
738 }
739
740 private static void writeFile(Path path, String contents) throws IOException {
741 FileSystemUtils.createDirectoryAndParents(path.getParentDirectory());
742 // sometimes we write read-only files
743 if (path.exists()) {
744 path.setWritable(true);
745 }
746 FileSystemUtils.writeContentAsLatin1(path, contents);
747 }
748
Rumou Duana77f32c2016-04-13 21:59:21 +0000749 private static void writeFile(Artifact file, String contents) throws IOException {
Googlerece75722016-02-11 17:55:41 +0000750 writeFile(file.getPath(), contents);
751 }
752
753 private static void touchFile(Path path) throws IOException {
754 FileSystemUtils.createDirectoryAndParents(path.getParentDirectory());
755 path.getParentDirectory().setWritable(true);
756 FileSystemUtils.touchFile(path);
757 }
758
Rumou Duana77f32c2016-04-13 21:59:21 +0000759 private static void touchFile(Artifact file) throws IOException {
Googlerece75722016-02-11 17:55:41 +0000760 touchFile(file.getPath());
761 }
762
Rumou Duana77f32c2016-04-13 21:59:21 +0000763 private static void deleteFile(Artifact file) throws IOException {
Googlerece75722016-02-11 17:55:41 +0000764 Path path = file.getPath();
765 // sometimes we write read-only files
766 if (path.exists()) {
767 path.setWritable(true);
768 // work around the sticky bit (this might depend on the behavior of the OS?)
769 path.getParentDirectory().setWritable(true);
770 path.delete();
771 }
772 }
773}