blob: 49f075d295c24c6e1484da425ff372ca24345638 [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
Googlerece75722016-02-11 17:55:41 +000016import static com.google.common.truth.Truth.assertThat;
Rumou Duana77f32c2016-04-13 21:59:21 +000017import static com.google.devtools.build.lib.actions.ActionInputHelper.treeFileArtifact;
Googlerece75722016-02-11 17:55:41 +000018import static org.junit.Assert.fail;
19
20import com.google.common.base.Function;
21import com.google.common.base.Preconditions;
Rumou Duan9ad28cd2016-10-19 19:28:06 +000022import com.google.common.base.Predicate;
Googlerece75722016-02-11 17:55:41 +000023import com.google.common.collect.Collections2;
24import com.google.common.collect.ImmutableList;
Rumou Duan9ad28cd2016-10-19 19:28:06 +000025import com.google.common.collect.Iterables;
Googlerece75722016-02-11 17:55:41 +000026import com.google.common.collect.Lists;
27import com.google.common.hash.Hashing;
28import com.google.common.util.concurrent.Runnables;
Rumou Duan73876202016-06-06 18:52:08 +000029import com.google.devtools.build.lib.actions.Action;
tomlu9e91f202018-06-18 16:16:36 -070030import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
Googlerece75722016-02-11 17:55:41 +000031import com.google.devtools.build.lib.actions.ActionExecutionContext;
32import com.google.devtools.build.lib.actions.ActionExecutionException;
33import com.google.devtools.build.lib.actions.ActionInput;
34import com.google.devtools.build.lib.actions.ActionInputHelper;
tomlu3d1a1942017-11-29 14:01:21 -080035import com.google.devtools.build.lib.actions.ActionKeyContext;
ruperts841880d2017-10-18 00:58:29 +020036import com.google.devtools.build.lib.actions.ActionResult;
janakr0175ce32018-02-26 15:54:57 -080037import com.google.devtools.build.lib.actions.Actions;
Googlerece75722016-02-11 17:55:41 +000038import com.google.devtools.build.lib.actions.Artifact;
39import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
40import com.google.devtools.build.lib.actions.Artifact.SpecialArtifactType;
Rumou Duana77f32c2016-04-13 21:59:21 +000041import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
tomlu1cdcdf92018-01-16 11:07:51 -080042import com.google.devtools.build.lib.actions.ArtifactRoot;
Rumou Duan73876202016-06-06 18:52:08 +000043import com.google.devtools.build.lib.actions.BuildFailedException;
shahan499503b2018-06-07 18:57:07 -070044import com.google.devtools.build.lib.actions.MetadataProvider;
janakr0175ce32018-02-26 15:54:57 -080045import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException;
shahan2cbfb892018-03-28 09:00:11 -070046import com.google.devtools.build.lib.actions.OutputBaseSupplier;
Googlerece75722016-02-11 17:55:41 +000047import com.google.devtools.build.lib.actions.cache.MetadataHandler;
Rumou Duan73876202016-06-06 18:52:08 +000048import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
Googlerece75722016-02-11 17:55:41 +000049import com.google.devtools.build.lib.actions.util.TestAction;
Rumou Duan73876202016-06-06 18:52:08 +000050import com.google.devtools.build.lib.actions.util.TestAction.DummyAction;
51import com.google.devtools.build.lib.analysis.actions.SpawnActionTemplate;
Rumou Duan9ad28cd2016-10-19 19:28:06 +000052import com.google.devtools.build.lib.events.Event;
53import com.google.devtools.build.lib.events.EventKind;
54import com.google.devtools.build.lib.events.StoredEventHandler;
shahan20f35b42018-02-28 15:57:33 -080055import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester;
Googlerece75722016-02-11 17:55:41 +000056import com.google.devtools.build.lib.testutil.TestUtils;
Googlerece75722016-02-11 17:55:41 +000057import com.google.devtools.build.lib.vfs.FileStatus;
58import com.google.devtools.build.lib.vfs.FileSystem;
59import com.google.devtools.build.lib.vfs.FileSystemUtils;
60import com.google.devtools.build.lib.vfs.Path;
61import com.google.devtools.build.lib.vfs.PathFragment;
62import com.google.devtools.build.lib.vfs.Symlinks;
Rumou Duan73876202016-06-06 18:52:08 +000063import com.google.devtools.build.skyframe.SkyFunction;
64import com.google.devtools.build.skyframe.SkyKey;
65import com.google.devtools.build.skyframe.SkyValue;
Googlerece75722016-02-11 17:55:41 +000066import java.io.IOException;
67import java.nio.charset.Charset;
68import java.util.Arrays;
69import java.util.Collection;
70import java.util.Iterator;
71import java.util.List;
Googlerece75722016-02-11 17:55:41 +000072import javax.annotation.Nullable;
Rumou Duanbaeed332016-10-18 15:30:25 +000073import org.junit.Before;
74import org.junit.Test;
75import org.junit.runner.RunWith;
76import org.junit.runners.JUnit4;
Googlerece75722016-02-11 17:55:41 +000077
78/** Timestamp builder tests for TreeArtifacts. */
79@RunWith(JUnit4.class)
80public class TreeArtifactBuildTest extends TimestampBuilderTestCase {
Rumou Duan11730a02016-10-24 20:27:58 +000081
82 private static final Predicate<Event> IS_ERROR_EVENT = new Predicate<Event>() {
83 @Override
84 public boolean apply(Event event) {
85 return event.getKind().equals(EventKind.ERROR);
86 }
87 };
88
Rumou Duana77f32c2016-04-13 21:59:21 +000089 // Common Artifacts, TreeFileArtifact, and Buttons. These aren't all used in all tests, but
90 // 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 +000091 // in setUp().
92
93 Artifact in;
94
cpeyserac09f0a2018-02-05 09:33:15 -080095 SpecialArtifact outOne;
Rumou Duana77f32c2016-04-13 21:59:21 +000096 TreeFileArtifact outOneFileOne;
97 TreeFileArtifact outOneFileTwo;
Googlerece75722016-02-11 17:55:41 +000098 Button buttonOne = new Button();
99
cpeyserac09f0a2018-02-05 09:33:15 -0800100 SpecialArtifact outTwo;
Rumou Duana77f32c2016-04-13 21:59:21 +0000101 TreeFileArtifact outTwoFileOne;
102 TreeFileArtifact outTwoFileTwo;
Googlerece75722016-02-11 17:55:41 +0000103 Button buttonTwo = new Button();
Googlerece75722016-02-11 17:55:41 +0000104 @Before
105 public void setUp() throws Exception {
106 in = createSourceArtifact("input");
107 writeFile(in, "input_content");
108
109 outOne = createTreeArtifact("outputOne");
Rumou Duana77f32c2016-04-13 21:59:21 +0000110 outOneFileOne = treeFileArtifact(outOne, "out_one_file_one");
111 outOneFileTwo = treeFileArtifact(outOne, "out_one_file_two");
Googlerece75722016-02-11 17:55:41 +0000112
113 outTwo = createTreeArtifact("outputTwo");
Rumou Duana77f32c2016-04-13 21:59:21 +0000114 outTwoFileOne = treeFileArtifact(outTwo, "out_one_file_one");
115 outTwoFileTwo = treeFileArtifact(outTwo, "out_one_file_two");
Googlerece75722016-02-11 17:55:41 +0000116 }
117
cpeyser875068a2018-02-01 08:40:58 -0800118 @Test
119 public void testCodec() throws Exception {
shahan20f35b42018-02-28 15:57:33 -0800120 new SerializationTester(outOne, outOneFileOne)
shahanfae34b92018-02-13 10:08:47 -0800121 .addDependency(FileSystem.class, scratch.getFileSystem())
shahan2cbfb892018-03-28 09:00:11 -0700122 .addDependency(
123 OutputBaseSupplier.class, () -> scratch.getFileSystem().getPath(TestUtils.tmpDir()))
shahan20f35b42018-02-28 15:57:33 -0800124 .runTests();
cpeyser875068a2018-02-01 08:40:58 -0800125 }
126
Googlerece75722016-02-11 17:55:41 +0000127 /** Simple smoke test. If this isn't passing, something is very wrong... */
128 @Test
129 public void testTreeArtifactSimpleCase() throws Exception {
130 TouchingTestAction action = new TouchingTestAction(outOneFileOne, outOneFileTwo);
131 registerAction(action);
132 buildArtifact(action.getSoleOutput());
133
lberkiaea56b32017-05-30 12:35:33 +0200134 assertThat(outOneFileOne.getPath().exists()).isTrue();
135 assertThat(outOneFileTwo.getPath().exists()).isTrue();
Googlerece75722016-02-11 17:55:41 +0000136 }
137
138 /** Simple test for the case with dependencies. */
139 @Test
140 public void testDependentTreeArtifacts() throws Exception {
141 TouchingTestAction actionOne = new TouchingTestAction(outOneFileOne, outOneFileTwo);
142 registerAction(actionOne);
143
144 CopyTreeAction actionTwo = new CopyTreeAction(
145 ImmutableList.of(outOneFileOne, outOneFileTwo),
146 ImmutableList.of(outTwoFileOne, outTwoFileTwo));
147 registerAction(actionTwo);
148
149 buildArtifact(outTwo);
150
lberkiaea56b32017-05-30 12:35:33 +0200151 assertThat(outOneFileOne.getPath().exists()).isTrue();
152 assertThat(outOneFileTwo.getPath().exists()).isTrue();
153 assertThat(outTwoFileOne.getPath().exists()).isTrue();
154 assertThat(outTwoFileTwo.getPath().exists()).isTrue();
Googlerece75722016-02-11 17:55:41 +0000155 }
156
Rumou Duanbaeed332016-10-18 15:30:25 +0000157 @Test
158 public void testInputTreeArtifactPerActionFileCache() throws Exception {
159 TouchingTestAction actionOne = new TouchingTestAction(outOneFileOne, outOneFileTwo);
160 registerAction(actionOne);
161
Philipp Wollermann3d93b8f2016-10-19 13:35:27 +0000162 final Artifact normalOutput = createDerivedArtifact("normal/out");
ruperts841880d2017-10-18 00:58:29 +0200163 Action testAction =
164 new TestAction(
165 TestAction.NO_EFFECT, ImmutableList.of(outOne), ImmutableList.of(normalOutput)) {
166 @Override
167 public ActionResult execute(ActionExecutionContext actionExecutionContext) {
168 try {
169 // Check the file cache for input TreeFileArtifacts.
shahan499503b2018-06-07 18:57:07 -0700170 MetadataProvider fileCache = actionExecutionContext.getMetadataProvider();
ulfjack4abd6c32017-12-21 10:52:16 -0800171 assertThat(fileCache.getMetadata(outOneFileOne).getType().isFile()).isTrue();
172 assertThat(fileCache.getMetadata(outOneFileTwo).getType().isFile()).isTrue();
Rumou Duanbaeed332016-10-18 15:30:25 +0000173
ruperts841880d2017-10-18 00:58:29 +0200174 // Touch the action output.
175 touchFile(normalOutput);
176 } catch (Exception e) {
177 throw new RuntimeException(e);
178 }
179 return ActionResult.EMPTY;
180 }
181 };
Rumou Duanbaeed332016-10-18 15:30:25 +0000182
183 registerAction(testAction);
184 buildArtifact(normalOutput);
185 }
186
Googlerece75722016-02-11 17:55:41 +0000187 /** Unchanged TreeArtifact outputs should not cause reexecution. */
188 @Test
189 public void testCacheCheckingForTreeArtifactsDoesNotCauseReexecution() throws Exception {
cpeyserac09f0a2018-02-05 09:33:15 -0800190 SpecialArtifact outOne = createTreeArtifact("outputOne");
Googlerece75722016-02-11 17:55:41 +0000191 Button buttonOne = new Button();
192
cpeyserac09f0a2018-02-05 09:33:15 -0800193 SpecialArtifact outTwo = createTreeArtifact("outputTwo");
Googlerece75722016-02-11 17:55:41 +0000194 Button buttonTwo = new Button();
195
196 TouchingTestAction actionOne = new TouchingTestAction(
197 buttonOne, outOne, "file_one", "file_two");
198 registerAction(actionOne);
199
200 CopyTreeAction actionTwo = new CopyTreeAction(
201 buttonTwo, outOne, outTwo, "file_one", "file_two");
202 registerAction(actionTwo);
203
204 buttonOne.pressed = buttonTwo.pressed = false;
205 buildArtifact(outTwo);
lberkiaea56b32017-05-30 12:35:33 +0200206 assertThat(buttonOne.pressed).isTrue(); // built
207 assertThat(buttonTwo.pressed).isTrue(); // built
Googlerece75722016-02-11 17:55:41 +0000208
209 buttonOne.pressed = buttonTwo.pressed = false;
210 buildArtifact(outTwo);
lberkiaea56b32017-05-30 12:35:33 +0200211 assertThat(buttonOne.pressed).isFalse(); // not built
212 assertThat(buttonTwo.pressed).isFalse(); // not built
Googlerece75722016-02-11 17:55:41 +0000213 }
214
215 /**
216 * Test rebuilding TreeArtifacts for inputs, outputs, and dependents.
217 * Also a test for caching.
218 */
219 @Test
220 public void testTransitiveReexecutionForTreeArtifacts() throws Exception {
221 WriteInputToFilesAction actionOne = new WriteInputToFilesAction(
222 buttonOne,
223 in,
224 outOneFileOne, outOneFileTwo);
225 registerAction(actionOne);
226
227 CopyTreeAction actionTwo = new CopyTreeAction(
228 buttonTwo,
229 ImmutableList.of(outOneFileOne, outOneFileTwo),
230 ImmutableList.of(outTwoFileOne, outTwoFileTwo));
231 registerAction(actionTwo);
232
233 buttonOne.pressed = buttonTwo.pressed = false;
234 buildArtifact(outTwo);
lberkiaea56b32017-05-30 12:35:33 +0200235 assertThat(buttonOne.pressed).isTrue(); // built
236 assertThat(buttonTwo.pressed).isTrue(); // built
Googlerece75722016-02-11 17:55:41 +0000237
238 buttonOne.pressed = buttonTwo.pressed = false;
239 writeFile(in, "modified_input");
240 buildArtifact(outTwo);
lberkiaea56b32017-05-30 12:35:33 +0200241 assertThat(buttonOne.pressed).isTrue(); // built
242 assertThat(buttonTwo.pressed).isTrue(); // not built
Googlerece75722016-02-11 17:55:41 +0000243
244 buttonOne.pressed = buttonTwo.pressed = false;
245 writeFile(outOneFileOne, "modified_output");
246 buildArtifact(outTwo);
lberkiaea56b32017-05-30 12:35:33 +0200247 assertThat(buttonOne.pressed).isTrue(); // built
248 assertThat(buttonTwo.pressed).isFalse(); // should have been cached
Googlerece75722016-02-11 17:55:41 +0000249
250 buttonOne.pressed = buttonTwo.pressed = false;
251 writeFile(outTwoFileOne, "more_modified_output");
252 buildArtifact(outTwo);
lberkiaea56b32017-05-30 12:35:33 +0200253 assertThat(buttonOne.pressed).isFalse(); // not built
254 assertThat(buttonTwo.pressed).isTrue(); // built
Googlerece75722016-02-11 17:55:41 +0000255 }
256
257 /** Tests that changing a TreeArtifact directory should cause reexeuction. */
258 @Test
259 public void testDirectoryContentsCachingForTreeArtifacts() throws Exception {
260 WriteInputToFilesAction actionOne = new WriteInputToFilesAction(
261 buttonOne,
262 in,
263 outOneFileOne, outOneFileTwo);
264 registerAction(actionOne);
265
266 CopyTreeAction actionTwo = new CopyTreeAction(
267 buttonTwo,
268 ImmutableList.of(outOneFileOne, outOneFileTwo),
269 ImmutableList.of(outTwoFileOne, outTwoFileTwo));
270 registerAction(actionTwo);
271
272 buttonOne.pressed = buttonTwo.pressed = false;
273 buildArtifact(outTwo);
274 // just a smoke test--if these aren't built we have bigger problems!
lberkiaea56b32017-05-30 12:35:33 +0200275 assertThat(buttonOne.pressed).isTrue();
276 assertThat(buttonTwo.pressed).isTrue();
Googlerece75722016-02-11 17:55:41 +0000277
278 // Adding a file to a directory should cause reexecution.
279 buttonOne.pressed = buttonTwo.pressed = false;
280 Path spuriousOutputOne = outOne.getPath().getRelative("spuriousOutput");
281 touchFile(spuriousOutputOne);
282 buildArtifact(outTwo);
283 // Should re-execute, and delete spurious output
lberkiaea56b32017-05-30 12:35:33 +0200284 assertThat(spuriousOutputOne.exists()).isFalse();
285 assertThat(buttonOne.pressed).isTrue();
286 assertThat(buttonTwo.pressed).isFalse(); // should have been cached
Googlerece75722016-02-11 17:55:41 +0000287
288 buttonOne.pressed = buttonTwo.pressed = false;
289 Path spuriousOutputTwo = outTwo.getPath().getRelative("anotherSpuriousOutput");
290 touchFile(spuriousOutputTwo);
291 buildArtifact(outTwo);
lberkiaea56b32017-05-30 12:35:33 +0200292 assertThat(spuriousOutputTwo.exists()).isFalse();
293 assertThat(buttonOne.pressed).isFalse();
294 assertThat(buttonTwo.pressed).isTrue();
Googlerece75722016-02-11 17:55:41 +0000295
296 // Deleting should cause reexecution.
297 buttonOne.pressed = buttonTwo.pressed = false;
298 deleteFile(outOneFileOne);
299 buildArtifact(outTwo);
lberkiaea56b32017-05-30 12:35:33 +0200300 assertThat(outOneFileOne.getPath().exists()).isTrue();
301 assertThat(buttonOne.pressed).isTrue();
302 assertThat(buttonTwo.pressed).isFalse(); // should have been cached
Googlerece75722016-02-11 17:55:41 +0000303
304 buttonOne.pressed = buttonTwo.pressed = false;
305 deleteFile(outTwoFileOne);
306 buildArtifact(outTwo);
lberkiaea56b32017-05-30 12:35:33 +0200307 assertThat(outTwoFileOne.getPath().exists()).isTrue();
308 assertThat(buttonOne.pressed).isFalse();
309 assertThat(buttonTwo.pressed).isTrue();
Googlerece75722016-02-11 17:55:41 +0000310 }
311
Janak Ramakrishnan08b0f7f2016-07-13 17:00:59 +0000312 /** TreeArtifacts don't care about mtime, even when the file is empty. */
Googlerece75722016-02-11 17:55:41 +0000313 @Test
314 public void testMTimeForTreeArtifactsDoesNotMatter() throws Exception {
315 // For this test, we only touch the input file.
316 Artifact in = createSourceArtifact("touchable_input");
317 touchFile(in);
318
319 WriteInputToFilesAction actionOne = new WriteInputToFilesAction(
320 buttonOne,
321 in,
322 outOneFileOne, outOneFileTwo);
323 registerAction(actionOne);
324
325 CopyTreeAction actionTwo = new CopyTreeAction(
326 buttonTwo,
327 ImmutableList.of(outOneFileOne, outOneFileTwo),
328 ImmutableList.of(outTwoFileOne, outTwoFileTwo));
329 registerAction(actionTwo);
330
331 buttonOne.pressed = buttonTwo.pressed = false;
332 buildArtifact(outTwo);
lberkiaea56b32017-05-30 12:35:33 +0200333 assertThat(buttonOne.pressed).isTrue(); // built
334 assertThat(buttonTwo.pressed).isTrue(); // built
Googlerece75722016-02-11 17:55:41 +0000335
336 buttonOne.pressed = buttonTwo.pressed = false;
337 touchFile(in);
338 buildArtifact(outTwo);
Janak Ramakrishnan08b0f7f2016-07-13 17:00:59 +0000339 // mtime does not matter.
lberkiaea56b32017-05-30 12:35:33 +0200340 assertThat(buttonOne.pressed).isFalse();
341 assertThat(buttonTwo.pressed).isFalse();
Googlerece75722016-02-11 17:55:41 +0000342
343 // None of the below following should result in anything being built.
344 buttonOne.pressed = buttonTwo.pressed = false;
345 touchFile(outOneFileOne);
346 buildArtifact(outTwo);
347 // Nothing should be built.
lberkiaea56b32017-05-30 12:35:33 +0200348 assertThat(buttonOne.pressed).isFalse();
349 assertThat(buttonTwo.pressed).isFalse();
Googlerece75722016-02-11 17:55:41 +0000350
351 buttonOne.pressed = buttonTwo.pressed = false;
352 touchFile(outOneFileTwo);
353 buildArtifact(outTwo);
354 // Nothing should be built.
lberkiaea56b32017-05-30 12:35:33 +0200355 assertThat(buttonOne.pressed).isFalse();
356 assertThat(buttonTwo.pressed).isFalse();
Googlerece75722016-02-11 17:55:41 +0000357 }
358
359 /** Tests that the declared order of TreeArtifact contents does not matter. */
360 @Test
361 public void testOrderIndependenceOfTreeArtifactContents() throws Exception {
362 WriteInputToFilesAction actionOne = new WriteInputToFilesAction(
363 in,
364 // The design of WritingTestAction is s.t.
365 // these files will be registered in the given order.
366 outOneFileTwo, outOneFileOne);
367 registerAction(actionOne);
368
369 CopyTreeAction actionTwo = new CopyTreeAction(
370 ImmutableList.of(outOneFileOne, outOneFileTwo),
371 ImmutableList.of(outTwoFileOne, outTwoFileTwo));
372 registerAction(actionTwo);
373
374 buildArtifact(outTwo);
375 }
376
377 @Test
378 public void testActionExpansion() throws Exception {
379 WriteInputToFilesAction action = new WriteInputToFilesAction(in, outOneFileOne, outOneFileTwo);
380
381 CopyTreeAction actionTwo = new CopyTreeAction(
382 ImmutableList.of(outOneFileOne, outOneFileTwo),
383 ImmutableList.of(outTwoFileOne, outTwoFileTwo)) {
384 @Override
385 public void executeTestBehavior(ActionExecutionContext actionExecutionContext)
386 throws ActionExecutionException {
387 super.executeTestBehavior(actionExecutionContext);
388
389 Collection<ActionInput> expanded =
390 ActionInputHelper.expandArtifacts(ImmutableList.of(outOne),
391 actionExecutionContext.getArtifactExpander());
392 // Only files registered should show up here.
393 assertThat(expanded).containsExactly(outOneFileOne, outOneFileTwo);
394 }
395 };
396
397 registerAction(action);
398 registerAction(actionTwo);
399
400 buildArtifact(outTwo); // should not fail
401 }
402
403 @Test
404 public void testInvalidOutputRegistrations() throws Exception {
Rumou Duan9ad28cd2016-10-19 19:28:06 +0000405 // Failure expected
406 StoredEventHandler storingEventHandler = new StoredEventHandler();
407 reporter.removeHandler(failFastHandler);
408 reporter.addHandler(storingEventHandler);
409
Googlerece75722016-02-11 17:55:41 +0000410 TreeArtifactTestAction failureOne = new TreeArtifactTestAction(
411 Runnables.doNothing(), outOneFileOne, outOneFileTwo) {
412 @Override
Rumou Duan11730a02016-10-24 20:27:58 +0000413 public void executeTestBehavior(ActionExecutionContext actionExecutionContext) {
Googlerece75722016-02-11 17:55:41 +0000414 try {
415 writeFile(outOneFileOne, "one");
416 writeFile(outOneFileTwo, "two");
417 // In this test case, we only register one output. This will fail.
418 registerOutput(actionExecutionContext, "one");
419 } catch (IOException e) {
420 throw new RuntimeException(e);
421 }
422 }
423 };
424
425 registerAction(failureOne);
426 try {
427 buildArtifact(outOne);
428 fail(); // Should have thrown
Rumou Duan9ad28cd2016-10-19 19:28:06 +0000429 } catch (BuildFailedException e) {
430 //not all outputs were created
431 List<Event> errors = ImmutableList.copyOf(
Rumou Duan11730a02016-10-24 20:27:58 +0000432 Iterables.filter(storingEventHandler.getEvents(), IS_ERROR_EVENT));
Rumou Duan9ad28cd2016-10-19 19:28:06 +0000433 assertThat(errors).hasSize(2);
434 assertThat(errors.get(0).getMessage()).contains("not present on disk");
435 assertThat(errors.get(1).getMessage()).contains("not all outputs were created or valid");
Googlerece75722016-02-11 17:55:41 +0000436 }
437
438 TreeArtifactTestAction failureTwo = new TreeArtifactTestAction(
439 Runnables.doNothing(), outTwoFileOne, outTwoFileTwo) {
440 @Override
Rumou Duan11730a02016-10-24 20:27:58 +0000441 public void executeTestBehavior(ActionExecutionContext actionExecutionContext) {
Googlerece75722016-02-11 17:55:41 +0000442 try {
443 writeFile(outTwoFileOne, "one");
444 writeFile(outTwoFileTwo, "two");
445 // In this test case, register too many outputs. This will fail.
446 registerOutput(actionExecutionContext, "one");
447 registerOutput(actionExecutionContext, "two");
448 registerOutput(actionExecutionContext, "three");
449 } catch (IOException e) {
450 throw new RuntimeException(e);
451 }
452 }
453 };
454
455 registerAction(failureTwo);
Rumou Duan9ad28cd2016-10-19 19:28:06 +0000456 storingEventHandler.clear();
Googlerece75722016-02-11 17:55:41 +0000457 try {
458 buildArtifact(outTwo);
459 fail(); // Should have thrown
Rumou Duan9ad28cd2016-10-19 19:28:06 +0000460 } catch (BuildFailedException e) {
461 List<Event> errors = ImmutableList.copyOf(
Rumou Duan11730a02016-10-24 20:27:58 +0000462 Iterables.filter(storingEventHandler.getEvents(), IS_ERROR_EVENT));
Rumou Duan9ad28cd2016-10-19 19:28:06 +0000463 assertThat(errors).hasSize(2);
464 assertThat(errors.get(0).getMessage()).contains("not present on disk");
465 assertThat(errors.get(1).getMessage()).contains("not all outputs were created or valid");
Googlerece75722016-02-11 17:55:41 +0000466 }
467 }
468
469 private static void checkDirectoryPermissions(Path path) throws IOException {
lberkiaea56b32017-05-30 12:35:33 +0200470 assertThat(path.isDirectory()).isTrue();
471 assertThat(path.isExecutable()).isTrue();
472 assertThat(path.isReadable()).isTrue();
473 assertThat(path.isWritable()).isFalse();
Googlerece75722016-02-11 17:55:41 +0000474 }
475
476 private static void checkFilePermissions(Path path) throws IOException {
lberkiaea56b32017-05-30 12:35:33 +0200477 assertThat(path.isDirectory()).isFalse();
478 assertThat(path.isExecutable()).isTrue();
479 assertThat(path.isReadable()).isTrue();
480 assertThat(path.isWritable()).isFalse();
Googlerece75722016-02-11 17:55:41 +0000481 }
482
483 @Test
484 public void testOutputsAreReadOnlyAndExecutable() throws Exception {
cpeyserac09f0a2018-02-05 09:33:15 -0800485 final SpecialArtifact out = createTreeArtifact("output");
Googlerece75722016-02-11 17:55:41 +0000486
ruperts841880d2017-10-18 00:58:29 +0200487 TreeArtifactTestAction action =
488 new TreeArtifactTestAction(out) {
489 @Override
490 public ActionResult execute(ActionExecutionContext actionExecutionContext) {
491 try {
492 writeFile(out.getPath().getChild("one"), "one");
493 writeFile(out.getPath().getChild("two"), "two");
494 writeFile(out.getPath().getChild("three").getChild("four"), "three/four");
495 registerOutput(actionExecutionContext, "one");
496 registerOutput(actionExecutionContext, "two");
497 registerOutput(actionExecutionContext, "three/four");
498 } catch (Exception e) {
499 throw new RuntimeException(e);
500 }
501 return ActionResult.EMPTY;
502 }
503 };
Googlerece75722016-02-11 17:55:41 +0000504
505 registerAction(action);
506
507 buildArtifact(action.getSoleOutput());
508
509 checkDirectoryPermissions(out.getPath());
510 checkFilePermissions(out.getPath().getChild("one"));
511 checkFilePermissions(out.getPath().getChild("two"));
512 checkDirectoryPermissions(out.getPath().getChild("three"));
513 checkFilePermissions(out.getPath().getChild("three").getChild("four"));
514 }
515
Rumou Duan11730a02016-10-24 20:27:58 +0000516 @Test
517 public void testValidRelativeSymlinkAccepted() throws Exception {
cpeyserac09f0a2018-02-05 09:33:15 -0800518 final SpecialArtifact out = createTreeArtifact("output");
Rumou Duan11730a02016-10-24 20:27:58 +0000519
ruperts841880d2017-10-18 00:58:29 +0200520 TreeArtifactTestAction action =
521 new TreeArtifactTestAction(out) {
522 @Override
523 public ActionResult execute(ActionExecutionContext actionExecutionContext) {
524 try {
525 writeFile(out.getPath().getChild("one"), "one");
526 writeFile(out.getPath().getChild("two"), "two");
527 FileSystemUtils.ensureSymbolicLink(
528 out.getPath().getChild("links").getChild("link"), "../one");
529 } catch (Exception e) {
530 throw new RuntimeException(e);
531 }
532 return ActionResult.EMPTY;
533 }
534 };
Rumou Duan11730a02016-10-24 20:27:58 +0000535
536 registerAction(action);
537
538 buildArtifact(action.getSoleOutput());
539 }
540
541 @Test
542 public void testInvalidSymlinkRejected() throws Exception {
543 // Failure expected
544 StoredEventHandler storingEventHandler = new StoredEventHandler();
545 reporter.removeHandler(failFastHandler);
546 reporter.addHandler(storingEventHandler);
547
cpeyserac09f0a2018-02-05 09:33:15 -0800548 final SpecialArtifact out = createTreeArtifact("output");
Rumou Duan11730a02016-10-24 20:27:58 +0000549
ruperts841880d2017-10-18 00:58:29 +0200550 TreeArtifactTestAction action =
551 new TreeArtifactTestAction(out) {
552 @Override
553 public ActionResult execute(ActionExecutionContext actionExecutionContext) {
554 try {
555 writeFile(out.getPath().getChild("one"), "one");
556 writeFile(out.getPath().getChild("two"), "two");
557 FileSystemUtils.ensureSymbolicLink(
558 out.getPath().getChild("links").getChild("link"), "../invalid");
559 } catch (Exception e) {
560 throw new RuntimeException(e);
561 }
562 return ActionResult.EMPTY;
563 }
564 };
Rumou Duan11730a02016-10-24 20:27:58 +0000565
566 registerAction(action);
567
568 try {
569 buildArtifact(action.getSoleOutput());
570 fail(); // Should have thrown
571 } catch (BuildFailedException e) {
572 List<Event> errors = ImmutableList.copyOf(
573 Iterables.filter(storingEventHandler.getEvents(), IS_ERROR_EVENT));
574 assertThat(errors).hasSize(2);
575 assertThat(errors.get(0).getMessage()).contains(
576 "Failed to resolve relative path links/link");
577 assertThat(errors.get(1).getMessage()).contains("not all outputs were created or valid");
578 }
579 }
580
581 @Test
Janak Ramakrishnan6b4a8b32017-02-22 19:51:28 +0000582 public void testAbsoluteSymlinkBadTargetRejected() throws Exception {
Rumou Duan11730a02016-10-24 20:27:58 +0000583 // Failure expected
584 StoredEventHandler storingEventHandler = new StoredEventHandler();
585 reporter.removeHandler(failFastHandler);
586 reporter.addHandler(storingEventHandler);
587
cpeyserac09f0a2018-02-05 09:33:15 -0800588 final SpecialArtifact out = createTreeArtifact("output");
Rumou Duan11730a02016-10-24 20:27:58 +0000589
ruperts841880d2017-10-18 00:58:29 +0200590 TreeArtifactTestAction action =
591 new TreeArtifactTestAction(out) {
592 @Override
593 public ActionResult execute(ActionExecutionContext actionExecutionContext) {
594 try {
595 writeFile(out.getPath().getChild("one"), "one");
596 writeFile(out.getPath().getChild("two"), "two");
597 FileSystemUtils.ensureSymbolicLink(
598 out.getPath().getChild("links").getChild("link"), "/random/pointer");
599 } catch (Exception e) {
600 throw new RuntimeException(e);
601 }
602 return ActionResult.EMPTY;
603 }
604 };
Rumou Duan11730a02016-10-24 20:27:58 +0000605
606 registerAction(action);
607
608 try {
609 buildArtifact(action.getSoleOutput());
610 fail(); // Should have thrown
611 } catch (BuildFailedException e) {
612 List<Event> errors = ImmutableList.copyOf(
613 Iterables.filter(storingEventHandler.getEvents(), IS_ERROR_EVENT));
614 assertThat(errors).hasSize(2);
Janak Ramakrishnan6b4a8b32017-02-22 19:51:28 +0000615 assertThat(errors.get(0).getMessage()).contains("Failed to resolve relative path links/link");
Rumou Duan11730a02016-10-24 20:27:58 +0000616 assertThat(errors.get(1).getMessage()).contains("not all outputs were created or valid");
617 }
618 }
619
620 @Test
Janak Ramakrishnan6b4a8b32017-02-22 19:51:28 +0000621 public void testAbsoluteSymlinkAccepted() throws Exception {
622 scratch.overwriteFile("/random/pointer");
623
cpeyserac09f0a2018-02-05 09:33:15 -0800624 final SpecialArtifact out = createTreeArtifact("output");
Janak Ramakrishnan6b4a8b32017-02-22 19:51:28 +0000625
626 TreeArtifactTestAction action =
627 new TreeArtifactTestAction(out) {
628 @Override
ruperts841880d2017-10-18 00:58:29 +0200629 public ActionResult execute(ActionExecutionContext actionExecutionContext) {
Janak Ramakrishnan6b4a8b32017-02-22 19:51:28 +0000630 try {
631 writeFile(out.getPath().getChild("one"), "one");
632 writeFile(out.getPath().getChild("two"), "two");
633 FileSystemUtils.ensureSymbolicLink(
634 out.getPath().getChild("links").getChild("link"), "/random/pointer");
635 } catch (Exception e) {
636 throw new RuntimeException(e);
637 }
ruperts841880d2017-10-18 00:58:29 +0200638 return ActionResult.EMPTY;
Janak Ramakrishnan6b4a8b32017-02-22 19:51:28 +0000639 }
640 };
641
642 registerAction(action);
643
644 buildArtifact(action.getSoleOutput());
645 }
646
647 @Test
Rumou Duan11730a02016-10-24 20:27:58 +0000648 public void testRelativeSymlinkTraversingOutsideOfTreeArtifactRejected() throws Exception {
649 // Failure expected
650 StoredEventHandler storingEventHandler = new StoredEventHandler();
651 reporter.removeHandler(failFastHandler);
652 reporter.addHandler(storingEventHandler);
653
cpeyserac09f0a2018-02-05 09:33:15 -0800654 final SpecialArtifact out = createTreeArtifact("output");
Rumou Duan11730a02016-10-24 20:27:58 +0000655
ruperts841880d2017-10-18 00:58:29 +0200656 TreeArtifactTestAction action =
657 new TreeArtifactTestAction(out) {
658 @Override
659 public ActionResult execute(ActionExecutionContext actionExecutionContext) {
660 try {
661 writeFile(out.getPath().getChild("one"), "one");
662 writeFile(out.getPath().getChild("two"), "two");
663 FileSystemUtils.ensureSymbolicLink(
664 out.getPath().getChild("links").getChild("link"), "../../output/random/pointer");
665 } catch (Exception e) {
666 throw new RuntimeException(e);
667 }
668 return ActionResult.EMPTY;
669 }
670 };
Rumou Duan11730a02016-10-24 20:27:58 +0000671
672 registerAction(action);
673
674 try {
675 buildArtifact(action.getSoleOutput());
676 fail(); // Should have thrown
677 } catch (BuildFailedException e) {
678 List<Event> errors = ImmutableList.copyOf(
679 Iterables.filter(storingEventHandler.getEvents(), IS_ERROR_EVENT));
680 assertThat(errors).hasSize(2);
681 assertThat(errors.get(0).getMessage()).contains(
682 "A TreeArtifact may not contain relative symlinks whose target paths traverse "
683 + "outside of the TreeArtifact");
684 assertThat(errors.get(1).getMessage()).contains("not all outputs were created or valid");
685 }
686 }
687
Googlerece75722016-02-11 17:55:41 +0000688 // This is more a smoke test than anything, because it turns out that:
689 // 1) there is no easy way to turn fast digests on/off for these test cases, and
690 // 2) injectDigest() doesn't really complain if you inject bad digests or digests
691 // for nonexistent files. Instead some weird error shows up down the line.
692 // In fact, there are no tests for injectDigest anywhere in the codebase.
693 // So all we're really testing here is that injectDigest() doesn't throw a weird exception.
694 // TODO(bazel-team): write real tests for injectDigest, here and elsewhere.
695 @Test
696 public void testDigestInjection() throws Exception {
ruperts841880d2017-10-18 00:58:29 +0200697 TreeArtifactTestAction action =
698 new TreeArtifactTestAction(outOne) {
699 @Override
700 public ActionResult execute(ActionExecutionContext actionExecutionContext)
701 throws ActionExecutionException {
702 try {
703 writeFile(outOneFileOne, "one");
704 writeFile(outOneFileTwo, "two");
Googlerece75722016-02-11 17:55:41 +0000705
ruperts841880d2017-10-18 00:58:29 +0200706 MetadataHandler md = actionExecutionContext.getMetadataHandler();
707 FileStatus stat = outOneFileOne.getPath().stat(Symlinks.NOFOLLOW);
708 md.injectDigest(
709 outOneFileOne,
710 stat,
711 Hashing.md5().hashString("one", Charset.forName("UTF-8")).asBytes());
Googlerece75722016-02-11 17:55:41 +0000712
ruperts841880d2017-10-18 00:58:29 +0200713 stat = outOneFileTwo.getPath().stat(Symlinks.NOFOLLOW);
714 md.injectDigest(
715 outOneFileTwo,
716 stat,
717 Hashing.md5().hashString("two", Charset.forName("UTF-8")).asBytes());
718 } catch (Exception e) {
719 throw new RuntimeException(e);
720 }
721 return ActionResult.EMPTY;
722 }
723 };
Googlerece75722016-02-11 17:55:41 +0000724
725 registerAction(action);
726 buildArtifact(action.getSoleOutput());
727 }
728
Rumou Duan73876202016-06-06 18:52:08 +0000729 @Test
730 public void testExpandedActionsBuildInActionTemplate() throws Throwable {
731 // artifact1 is a tree artifact generated by a TouchingTestAction.
cpeyserac09f0a2018-02-05 09:33:15 -0800732 SpecialArtifact artifact1 = createTreeArtifact("treeArtifact1");
Rumou Duan73876202016-06-06 18:52:08 +0000733 TreeFileArtifact treeFileArtifactA = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000734 artifact1, PathFragment.create("child1"));
Rumou Duan73876202016-06-06 18:52:08 +0000735 TreeFileArtifact treeFileArtifactB = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000736 artifact1, PathFragment.create("child2"));
Rumou Duan73876202016-06-06 18:52:08 +0000737 registerAction(new TouchingTestAction(treeFileArtifactA, treeFileArtifactB));
738
739 // artifact2 is a tree artifact generated by an action template.
cpeyserac09f0a2018-02-05 09:33:15 -0800740 SpecialArtifact artifact2 = createTreeArtifact("treeArtifact2");
Rumou Duan73876202016-06-06 18:52:08 +0000741 SpawnActionTemplate actionTemplate = ActionsTestUtil.createDummySpawnActionTemplate(
742 artifact1, artifact2);
743 registerAction(actionTemplate);
744
745 // We mock out the action template function to expand into two actions that just touch the
746 // output files.
747 TreeFileArtifact expectedOutputTreeFileArtifact1 = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000748 artifact2, PathFragment.create("child1"));
Rumou Duan73876202016-06-06 18:52:08 +0000749 TreeFileArtifact expectedOutputTreeFileArtifact2 = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000750 artifact2, PathFragment.create("child2"));
Rumou Duan73876202016-06-06 18:52:08 +0000751 Action generateOutputAction = new DummyAction(
752 ImmutableList.<Artifact>of(treeFileArtifactA), expectedOutputTreeFileArtifact1);
753 Action noGenerateOutputAction = new DummyAction(
754 ImmutableList.<Artifact>of(treeFileArtifactB), expectedOutputTreeFileArtifact2);
755
tomlu3d1a1942017-11-29 14:01:21 -0800756 actionTemplateExpansionFunction =
757 new DummyActionTemplateExpansionFunction(
janakrcb314a22018-02-15 10:33:53 -0800758 actionKeyContext, ImmutableList.of(generateOutputAction, noGenerateOutputAction));
Rumou Duan73876202016-06-06 18:52:08 +0000759
760 buildArtifact(artifact2);
761 }
762
763 @Test
764 public void testExpandedActionDoesNotGenerateOutputInActionTemplate() throws Throwable {
765 // expect errors
766 reporter.removeHandler(failFastHandler);
767
768 // artifact1 is a tree artifact generated by a TouchingTestAction.
cpeyserac09f0a2018-02-05 09:33:15 -0800769 SpecialArtifact artifact1 = createTreeArtifact("treeArtifact1");
Rumou Duan73876202016-06-06 18:52:08 +0000770 TreeFileArtifact treeFileArtifactA = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000771 artifact1, PathFragment.create("child1"));
Rumou Duan73876202016-06-06 18:52:08 +0000772 TreeFileArtifact treeFileArtifactB = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000773 artifact1, PathFragment.create("child2"));
Rumou Duan73876202016-06-06 18:52:08 +0000774 registerAction(new TouchingTestAction(treeFileArtifactA, treeFileArtifactB));
775
776 // artifact2 is a tree artifact generated by an action template.
cpeyserac09f0a2018-02-05 09:33:15 -0800777 SpecialArtifact artifact2 = createTreeArtifact("treeArtifact2");
Rumou Duan73876202016-06-06 18:52:08 +0000778 SpawnActionTemplate actionTemplate = ActionsTestUtil.createDummySpawnActionTemplate(
779 artifact1, artifact2);
780 registerAction(actionTemplate);
781
782 // We mock out the action template function to expand into two actions:
783 // One Action that touches the output file.
784 // The other action that does not generate the output file.
785 TreeFileArtifact expectedOutputTreeFileArtifact1 = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000786 artifact2, PathFragment.create("child1"));
Rumou Duan73876202016-06-06 18:52:08 +0000787 TreeFileArtifact expectedOutputTreeFileArtifact2 = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000788 artifact2, PathFragment.create("child2"));
Rumou Duan73876202016-06-06 18:52:08 +0000789 Action generateOutputAction = new DummyAction(
790 ImmutableList.<Artifact>of(treeFileArtifactA), expectedOutputTreeFileArtifact1);
791 Action noGenerateOutputAction = new NoOpDummyAction(
792 ImmutableList.<Artifact>of(treeFileArtifactB),
793 ImmutableList.<Artifact>of(expectedOutputTreeFileArtifact2));
794
tomlu3d1a1942017-11-29 14:01:21 -0800795 actionTemplateExpansionFunction =
796 new DummyActionTemplateExpansionFunction(
janakrcb314a22018-02-15 10:33:53 -0800797 actionKeyContext, ImmutableList.of(generateOutputAction, noGenerateOutputAction));
Rumou Duan73876202016-06-06 18:52:08 +0000798
799 try {
800 buildArtifact(artifact2);
801 fail("Expected BuildFailedException");
802 } catch (BuildFailedException e) {
lberkiaea56b32017-05-30 12:35:33 +0200803 assertThat(e).hasMessageThat().contains("not all outputs were created or valid");
Rumou Duan73876202016-06-06 18:52:08 +0000804 }
805 }
806
807 @Test
808 public void testOneExpandedActionThrowsInActionTemplate() throws Throwable {
809 // expect errors
810 reporter.removeHandler(failFastHandler);
811
812 // artifact1 is a tree artifact generated by a TouchingTestAction.
cpeyserac09f0a2018-02-05 09:33:15 -0800813 SpecialArtifact artifact1 = createTreeArtifact("treeArtifact1");
Rumou Duan73876202016-06-06 18:52:08 +0000814 TreeFileArtifact treeFileArtifactA = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000815 artifact1, PathFragment.create("child1"));
Rumou Duan73876202016-06-06 18:52:08 +0000816 TreeFileArtifact treeFileArtifactB = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000817 artifact1, PathFragment.create("child2"));
Rumou Duan73876202016-06-06 18:52:08 +0000818 registerAction(new TouchingTestAction(treeFileArtifactA, treeFileArtifactB));
819
820 // artifact2 is a tree artifact generated by an action template.
cpeyserac09f0a2018-02-05 09:33:15 -0800821 SpecialArtifact artifact2 = createTreeArtifact("treeArtifact2");
Rumou Duan73876202016-06-06 18:52:08 +0000822 SpawnActionTemplate actionTemplate = ActionsTestUtil.createDummySpawnActionTemplate(
823 artifact1, artifact2);
824 registerAction(actionTemplate);
825
826 // We mock out the action template function to expand into two actions:
827 // One Action that touches the output file.
828 // The other action that just throws when executed.
829 TreeFileArtifact expectedOutputTreeFileArtifact1 = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000830 artifact2, PathFragment.create("child1"));
Rumou Duan73876202016-06-06 18:52:08 +0000831 TreeFileArtifact expectedOutputTreeFileArtifact2 = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000832 artifact2, PathFragment.create("child2"));
Rumou Duan73876202016-06-06 18:52:08 +0000833 Action generateOutputAction = new DummyAction(
834 ImmutableList.<Artifact>of(treeFileArtifactA), expectedOutputTreeFileArtifact1);
835 Action throwingAction = new ThrowingDummyAction(
836 ImmutableList.<Artifact>of(treeFileArtifactB),
837 ImmutableList.<Artifact>of(expectedOutputTreeFileArtifact2));
tomlu3d1a1942017-11-29 14:01:21 -0800838
839 actionTemplateExpansionFunction =
840 new DummyActionTemplateExpansionFunction(
janakrcb314a22018-02-15 10:33:53 -0800841 actionKeyContext, ImmutableList.of(generateOutputAction, throwingAction));
Rumou Duan73876202016-06-06 18:52:08 +0000842
843 try {
844 buildArtifact(artifact2);
845 fail("Expected BuildFailedException");
846 } catch (BuildFailedException e) {
lberkiaea56b32017-05-30 12:35:33 +0200847 assertThat(e).hasMessageThat().contains("Throwing dummy action");
Rumou Duan73876202016-06-06 18:52:08 +0000848 }
849 }
850
851 @Test
852 public void testAllExpandedActionsThrowInActionTemplate() throws Throwable {
853 // expect errors
854 reporter.removeHandler(failFastHandler);
855
856 // artifact1 is a tree artifact generated by a TouchingTestAction.
cpeyserac09f0a2018-02-05 09:33:15 -0800857 SpecialArtifact artifact1 = createTreeArtifact("treeArtifact1");
Rumou Duan73876202016-06-06 18:52:08 +0000858 TreeFileArtifact treeFileArtifactA = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000859 artifact1, PathFragment.create("child1"));
Rumou Duan73876202016-06-06 18:52:08 +0000860 TreeFileArtifact treeFileArtifactB = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000861 artifact1, PathFragment.create("child2"));
Rumou Duan73876202016-06-06 18:52:08 +0000862 registerAction(new TouchingTestAction(treeFileArtifactA, treeFileArtifactB));
863
864 // artifact2 is a tree artifact generated by an action template.
cpeyserac09f0a2018-02-05 09:33:15 -0800865 SpecialArtifact artifact2 = createTreeArtifact("treeArtifact2");
Rumou Duan73876202016-06-06 18:52:08 +0000866 SpawnActionTemplate actionTemplate = ActionsTestUtil.createDummySpawnActionTemplate(
867 artifact1, artifact2);
868 registerAction(actionTemplate);
869
870 // We mock out the action template function to expand into two actions that throw when executed.
871 TreeFileArtifact expectedOutputTreeFileArtifact1 = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000872 artifact2, PathFragment.create("child1"));
Rumou Duan73876202016-06-06 18:52:08 +0000873 TreeFileArtifact expectedOutputTreeFileArtifact2 = ActionInputHelper.treeFileArtifact(
nharmatab4060b62017-04-04 17:11:39 +0000874 artifact2, PathFragment.create("child2"));
Rumou Duan73876202016-06-06 18:52:08 +0000875 Action throwingAction = new ThrowingDummyAction(
876 ImmutableList.<Artifact>of(treeFileArtifactA),
877 ImmutableList.<Artifact>of(expectedOutputTreeFileArtifact1));
878 Action anotherThrowingAction = new ThrowingDummyAction(
879 ImmutableList.<Artifact>of(treeFileArtifactB),
880 ImmutableList.<Artifact>of(expectedOutputTreeFileArtifact2));
tomlu3d1a1942017-11-29 14:01:21 -0800881
882 actionTemplateExpansionFunction =
883 new DummyActionTemplateExpansionFunction(
janakrcb314a22018-02-15 10:33:53 -0800884 actionKeyContext, ImmutableList.of(throwingAction, anotherThrowingAction));
Rumou Duan73876202016-06-06 18:52:08 +0000885
886 try {
887 buildArtifact(artifact2);
888 fail("Expected BuildFailedException");
889 } catch (BuildFailedException e) {
lberkiaea56b32017-05-30 12:35:33 +0200890 assertThat(e).hasMessageThat().contains("Throwing dummy action");
Rumou Duan73876202016-06-06 18:52:08 +0000891 }
892 }
893
894 @Test
895 public void testInputTreeArtifactCreationFailedInActionTemplate() throws Throwable {
896 // expect errors
897 reporter.removeHandler(failFastHandler);
898
899 // artifact1 is created by a action that throws.
cpeyserac09f0a2018-02-05 09:33:15 -0800900 SpecialArtifact artifact1 = createTreeArtifact("treeArtifact1");
Rumou Duan73876202016-06-06 18:52:08 +0000901 registerAction(
902 new ThrowingDummyAction(ImmutableList.<Artifact>of(), ImmutableList.of(artifact1)));
903
904 // artifact2 is a tree artifact generated by an action template.
cpeyserac09f0a2018-02-05 09:33:15 -0800905 SpecialArtifact artifact2 = createTreeArtifact("treeArtifact2");
Rumou Duan73876202016-06-06 18:52:08 +0000906 SpawnActionTemplate actionTemplate = ActionsTestUtil.createDummySpawnActionTemplate(
907 artifact1, artifact2);
908 registerAction(actionTemplate);
909
910 try {
911 buildArtifact(artifact2);
912 fail("Expected BuildFailedException");
913 } catch (BuildFailedException e) {
lberkiaea56b32017-05-30 12:35:33 +0200914 assertThat(e).hasMessageThat().contains("Throwing dummy action");
Rumou Duan73876202016-06-06 18:52:08 +0000915 }
916 }
917
Rumou Duan3ddb4c92016-06-13 20:10:48 +0000918 @Test
919 public void testEmptyInputAndOutputTreeArtifactInActionTemplate() throws Throwable {
920 // artifact1 is an empty tree artifact which is generated by a single no-op dummy action.
cpeyserac09f0a2018-02-05 09:33:15 -0800921 SpecialArtifact artifact1 = createTreeArtifact("treeArtifact1");
Rumou Duan3ddb4c92016-06-13 20:10:48 +0000922 registerAction(new NoOpDummyAction(ImmutableList.<Artifact>of(), ImmutableList.of(artifact1)));
923
924 // artifact2 is a tree artifact generated by an action template that takes artifact1 as input.
cpeyserac09f0a2018-02-05 09:33:15 -0800925 SpecialArtifact artifact2 = createTreeArtifact("treeArtifact2");
Rumou Duan3ddb4c92016-06-13 20:10:48 +0000926 SpawnActionTemplate actionTemplate = ActionsTestUtil.createDummySpawnActionTemplate(
927 artifact1, artifact2);
928 registerAction(actionTemplate);
929
930 buildArtifact(artifact2);
931
932 assertThat(artifact1.getPath().exists()).isTrue();
933 assertThat(artifact1.getPath().getDirectoryEntries()).isEmpty();
934 assertThat(artifact2.getPath().exists()).isTrue();
935 assertThat(artifact2.getPath().getDirectoryEntries()).isEmpty();
936 }
937
Googlerece75722016-02-11 17:55:41 +0000938 /**
939 * A generic test action that takes at most one input TreeArtifact,
940 * exactly one output TreeArtifact, and some path fragment inputs/outputs.
941 */
942 private abstract static class TreeArtifactTestAction extends TestAction {
Rumou Duana77f32c2016-04-13 21:59:21 +0000943 final Iterable<TreeFileArtifact> inputFiles;
944 final Iterable<TreeFileArtifact> outputFiles;
Googlerece75722016-02-11 17:55:41 +0000945
cpeyserac09f0a2018-02-05 09:33:15 -0800946 TreeArtifactTestAction(final SpecialArtifact output, final String... subOutputs) {
Googlerece75722016-02-11 17:55:41 +0000947 this(Runnables.doNothing(),
948 null,
Rumou Duana77f32c2016-04-13 21:59:21 +0000949 ImmutableList.<TreeFileArtifact>of(),
Googlerece75722016-02-11 17:55:41 +0000950 output,
951 Collections2.transform(
952 Arrays.asList(subOutputs),
Rumou Duana77f32c2016-04-13 21:59:21 +0000953 new Function<String, TreeFileArtifact>() {
Googlerece75722016-02-11 17:55:41 +0000954 @Nullable
955 @Override
Rumou Duana77f32c2016-04-13 21:59:21 +0000956 public TreeFileArtifact apply(String s) {
957 return ActionInputHelper.treeFileArtifact(output, s);
Googlerece75722016-02-11 17:55:41 +0000958 }
959 }));
960 }
961
Rumou Duana77f32c2016-04-13 21:59:21 +0000962 TreeArtifactTestAction(Runnable effect, TreeFileArtifact... outputFiles) {
Googlerece75722016-02-11 17:55:41 +0000963 this(effect, Arrays.asList(outputFiles));
964 }
965
Rumou Duana77f32c2016-04-13 21:59:21 +0000966 TreeArtifactTestAction(Runnable effect, Collection<TreeFileArtifact> outputFiles) {
967 this(effect, null, ImmutableList.<TreeFileArtifact>of(),
Googlerece75722016-02-11 17:55:41 +0000968 outputFiles.iterator().next().getParent(), outputFiles);
969 }
970
971 TreeArtifactTestAction(Runnable effect, Artifact inputFile,
Rumou Duana77f32c2016-04-13 21:59:21 +0000972 Collection<TreeFileArtifact> outputFiles) {
973 this(effect, inputFile, ImmutableList.<TreeFileArtifact>of(),
Googlerece75722016-02-11 17:55:41 +0000974 outputFiles.iterator().next().getParent(), outputFiles);
975 }
976
Rumou Duana77f32c2016-04-13 21:59:21 +0000977 TreeArtifactTestAction(Runnable effect, Collection<TreeFileArtifact> inputFiles,
978 Collection<TreeFileArtifact> outputFiles) {
Googlerece75722016-02-11 17:55:41 +0000979 this(effect, inputFiles.iterator().next().getParent(), inputFiles,
980 outputFiles.iterator().next().getParent(), outputFiles);
981 }
982
983 TreeArtifactTestAction(
984 Runnable effect,
985 @Nullable Artifact input,
Rumou Duana77f32c2016-04-13 21:59:21 +0000986 Collection<TreeFileArtifact> inputFiles,
Googlerece75722016-02-11 17:55:41 +0000987 Artifact output,
Rumou Duana77f32c2016-04-13 21:59:21 +0000988 Collection<TreeFileArtifact> outputFiles) {
Googlerece75722016-02-11 17:55:41 +0000989 super(effect,
990 input == null ? ImmutableList.<Artifact>of() : ImmutableList.of(input),
991 ImmutableList.of(output));
992 Preconditions.checkArgument(
993 inputFiles.isEmpty() || (input != null && input.isTreeArtifact()));
994 Preconditions.checkArgument(output == null || output.isTreeArtifact());
995 this.inputFiles = ImmutableList.copyOf(inputFiles);
996 this.outputFiles = ImmutableList.copyOf(outputFiles);
Rumou Duana77f32c2016-04-13 21:59:21 +0000997 for (TreeFileArtifact inputFile : inputFiles) {
Googlerece75722016-02-11 17:55:41 +0000998 Preconditions.checkState(inputFile.getParent().equals(input));
999 }
Rumou Duana77f32c2016-04-13 21:59:21 +00001000 for (TreeFileArtifact outputFile : outputFiles) {
Googlerece75722016-02-11 17:55:41 +00001001 Preconditions.checkState(outputFile.getParent().equals(output));
1002 }
1003 }
1004
1005 @Override
ruperts841880d2017-10-18 00:58:29 +02001006 public ActionResult execute(ActionExecutionContext actionExecutionContext)
Googlerece75722016-02-11 17:55:41 +00001007 throws ActionExecutionException {
1008 if (getInputs().iterator().hasNext()) {
1009 // Sanity check--verify all inputs exist.
1010 Artifact input = getSoleInput();
1011 if (!input.getPath().exists()) {
1012 throw new IllegalStateException("action's input Artifact does not exist: "
1013 + input.getPath());
1014 }
Rumou Duana77f32c2016-04-13 21:59:21 +00001015 for (Artifact inputFile : inputFiles) {
Googlerece75722016-02-11 17:55:41 +00001016 if (!inputFile.getPath().exists()) {
1017 throw new IllegalStateException("action's input does not exist: " + inputFile);
1018 }
1019 }
1020 }
1021
1022 Artifact output = getSoleOutput();
lberkiaea56b32017-05-30 12:35:33 +02001023 assertThat(output.getPath().exists()).isTrue();
Googlerece75722016-02-11 17:55:41 +00001024 try {
1025 effect.call();
1026 executeTestBehavior(actionExecutionContext);
Rumou Duana77f32c2016-04-13 21:59:21 +00001027 for (TreeFileArtifact outputFile : outputFiles) {
Googlerece75722016-02-11 17:55:41 +00001028 actionExecutionContext.getMetadataHandler().addExpandedTreeOutput(outputFile);
1029 }
1030 } catch (RuntimeException e) {
1031 throw new RuntimeException(e);
1032 } catch (Exception e) {
1033 throw new ActionExecutionException("TestAction failed due to exception",
1034 e, this, false);
1035 }
ruperts841880d2017-10-18 00:58:29 +02001036 return ActionResult.EMPTY;
Googlerece75722016-02-11 17:55:41 +00001037 }
1038
1039 void executeTestBehavior(ActionExecutionContext c) throws ActionExecutionException {
1040 // Default: do nothing
1041 }
1042
1043 /** Checks there's exactly one input, and returns it. */
1044 // This prevents us from making testing mistakes, like
1045 // assuming there's only one input when this isn't actually true.
1046 Artifact getSoleInput() {
1047 Iterator<Artifact> it = getInputs().iterator();
1048 Artifact r = it.next();
1049 Preconditions.checkNotNull(r);
1050 Preconditions.checkState(!it.hasNext());
1051 return r;
1052 }
1053
1054 /** Checks there's exactly one output, and returns it. */
cpeyserac09f0a2018-02-05 09:33:15 -08001055 SpecialArtifact getSoleOutput() {
Googlerece75722016-02-11 17:55:41 +00001056 Iterator<Artifact> it = getOutputs().iterator();
cpeyserac09f0a2018-02-05 09:33:15 -08001057 SpecialArtifact r = (SpecialArtifact) it.next();
Googlerece75722016-02-11 17:55:41 +00001058 Preconditions.checkNotNull(r);
1059 Preconditions.checkState(!it.hasNext());
1060 Preconditions.checkState(r.equals(getPrimaryOutput()));
1061 return r;
1062 }
1063
1064 void registerOutput(ActionExecutionContext context, String outputName) throws IOException {
1065 context.getMetadataHandler().addExpandedTreeOutput(
nharmatab4060b62017-04-04 17:11:39 +00001066 treeFileArtifact(getSoleOutput(), PathFragment.create(outputName)));
Googlerece75722016-02-11 17:55:41 +00001067 }
1068
cpeyserac09f0a2018-02-05 09:33:15 -08001069 static List<TreeFileArtifact> asTreeFileArtifacts(
1070 final SpecialArtifact parent, String... files) {
Googlerece75722016-02-11 17:55:41 +00001071 return Lists.transform(
1072 Arrays.asList(files),
Rumou Duana77f32c2016-04-13 21:59:21 +00001073 new Function<String, TreeFileArtifact>() {
Googlerece75722016-02-11 17:55:41 +00001074 @Nullable
1075 @Override
Rumou Duana77f32c2016-04-13 21:59:21 +00001076 public TreeFileArtifact apply(String s) {
1077 return ActionInputHelper.treeFileArtifact(parent, s);
Googlerece75722016-02-11 17:55:41 +00001078 }
1079 });
1080 }
1081 }
1082
Rumou Duana77f32c2016-04-13 21:59:21 +00001083 /** An action that touches some output TreeFileArtifacts. Takes no inputs. */
Googlerece75722016-02-11 17:55:41 +00001084 private static class TouchingTestAction extends TreeArtifactTestAction {
Rumou Duana77f32c2016-04-13 21:59:21 +00001085 TouchingTestAction(TreeFileArtifact... outputPaths) {
Googlerece75722016-02-11 17:55:41 +00001086 super(Runnables.doNothing(), outputPaths);
1087 }
1088
cpeyserac09f0a2018-02-05 09:33:15 -08001089 TouchingTestAction(Runnable effect, SpecialArtifact output, String... outputPaths) {
Rumou Duana77f32c2016-04-13 21:59:21 +00001090 super(effect, asTreeFileArtifacts(output, outputPaths));
Googlerece75722016-02-11 17:55:41 +00001091 }
1092
1093 @Override
1094 public void executeTestBehavior(ActionExecutionContext actionExecutionContext)
1095 throws ActionExecutionException {
1096 try {
Rumou Duana77f32c2016-04-13 21:59:21 +00001097 for (Artifact file : outputFiles) {
Googlerece75722016-02-11 17:55:41 +00001098 touchFile(file);
1099 }
1100 } catch (IOException e) {
1101 throw new RuntimeException(e);
1102 }
1103 }
1104 }
1105
1106 /** Takes an input file and populates several copies inside a TreeArtifact. */
1107 private static class WriteInputToFilesAction extends TreeArtifactTestAction {
Rumou Duana77f32c2016-04-13 21:59:21 +00001108 WriteInputToFilesAction(Artifact input, TreeFileArtifact... outputs) {
Googlerece75722016-02-11 17:55:41 +00001109 this(Runnables.doNothing(), input, outputs);
1110 }
1111
1112 WriteInputToFilesAction(
1113 Runnable effect,
1114 Artifact input,
Rumou Duana77f32c2016-04-13 21:59:21 +00001115 TreeFileArtifact... outputs) {
Googlerece75722016-02-11 17:55:41 +00001116 super(effect, input, Arrays.asList(outputs));
1117 Preconditions.checkArgument(!input.isTreeArtifact());
1118 }
1119
1120 @Override
1121 public void executeTestBehavior(ActionExecutionContext actionExecutionContext)
1122 throws ActionExecutionException {
1123 try {
Rumou Duana77f32c2016-04-13 21:59:21 +00001124 for (Artifact file : outputFiles) {
Googlerece75722016-02-11 17:55:41 +00001125 FileSystemUtils.createDirectoryAndParents(file.getPath().getParentDirectory());
1126 FileSystemUtils.copyFile(getSoleInput().getPath(), file.getPath());
1127 }
1128 } catch (IOException e) {
1129 throw new RuntimeException(e);
1130 }
1131 }
1132 }
1133
Rumou Duana77f32c2016-04-13 21:59:21 +00001134 /** Copies the given TreeFileArtifact inputs to the given outputs, in respective order. */
Googlerece75722016-02-11 17:55:41 +00001135 private static class CopyTreeAction extends TreeArtifactTestAction {
1136
cpeyserac09f0a2018-02-05 09:33:15 -08001137 CopyTreeAction(
1138 Runnable effect, SpecialArtifact input, SpecialArtifact output, String... sourcesAndDests) {
Rumou Duana77f32c2016-04-13 21:59:21 +00001139 super(effect, input, asTreeFileArtifacts(input, sourcesAndDests), output,
1140 asTreeFileArtifacts(output, sourcesAndDests));
Googlerece75722016-02-11 17:55:41 +00001141 }
1142
1143 CopyTreeAction(
Rumou Duana77f32c2016-04-13 21:59:21 +00001144 Collection<TreeFileArtifact> inputPaths,
1145 Collection<TreeFileArtifact> outputPaths) {
Googlerece75722016-02-11 17:55:41 +00001146 super(Runnables.doNothing(), inputPaths, outputPaths);
1147 }
1148
1149 CopyTreeAction(
1150 Runnable effect,
Rumou Duana77f32c2016-04-13 21:59:21 +00001151 Collection<TreeFileArtifact> inputPaths,
1152 Collection<TreeFileArtifact> outputPaths) {
Googlerece75722016-02-11 17:55:41 +00001153 super(effect, inputPaths, outputPaths);
1154 }
1155
1156 @Override
1157 public void executeTestBehavior(ActionExecutionContext actionExecutionContext)
1158 throws ActionExecutionException {
Rumou Duana77f32c2016-04-13 21:59:21 +00001159 Iterator<TreeFileArtifact> inputIterator = inputFiles.iterator();
1160 Iterator<TreeFileArtifact> outputIterator = outputFiles.iterator();
Googlerece75722016-02-11 17:55:41 +00001161
1162 try {
1163 while (inputIterator.hasNext() || outputIterator.hasNext()) {
Rumou Duana77f32c2016-04-13 21:59:21 +00001164 Artifact input = inputIterator.next();
1165 Artifact output = outputIterator.next();
Googlerece75722016-02-11 17:55:41 +00001166 FileSystemUtils.createDirectoryAndParents(output.getPath().getParentDirectory());
1167 FileSystemUtils.copyFile(input.getPath(), output.getPath());
1168 }
1169 } catch (IOException e) {
1170 throw new RuntimeException(e);
1171 }
1172
1173 // both iterators must be of the same size
lberkiaea56b32017-05-30 12:35:33 +02001174 assertThat(inputIterator.hasNext()).isFalse();
1175 assertThat(inputIterator.hasNext()).isFalse();
Googlerece75722016-02-11 17:55:41 +00001176 }
1177 }
1178
cpeyserac09f0a2018-02-05 09:33:15 -08001179 private SpecialArtifact createTreeArtifact(String name) {
Googlerece75722016-02-11 17:55:41 +00001180 FileSystem fs = scratch.getFileSystem();
1181 Path execRoot = fs.getPath(TestUtils.tmpDir());
nharmatab4060b62017-04-04 17:11:39 +00001182 PathFragment execPath = PathFragment.create("out").getRelative(name);
Googlerece75722016-02-11 17:55:41 +00001183 return new SpecialArtifact(
tomlu1cdcdf92018-01-16 11:07:51 -08001184 ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("out")),
janakr573807d2018-01-11 14:02:35 -08001185 execPath,
1186 ACTION_LOOKUP_KEY,
Googlerece75722016-02-11 17:55:41 +00001187 SpecialArtifactType.TREE);
1188 }
1189
Ulf Adams94f63302016-04-29 15:04:13 +00001190 private void buildArtifact(Artifact artifact) throws Exception {
Googlerece75722016-02-11 17:55:41 +00001191 buildArtifacts(cachingBuilder(), artifact);
1192 }
1193
1194 private static void writeFile(Path path, String contents) throws IOException {
1195 FileSystemUtils.createDirectoryAndParents(path.getParentDirectory());
1196 // sometimes we write read-only files
1197 if (path.exists()) {
1198 path.setWritable(true);
1199 }
1200 FileSystemUtils.writeContentAsLatin1(path, contents);
1201 }
1202
Rumou Duana77f32c2016-04-13 21:59:21 +00001203 private static void writeFile(Artifact file, String contents) throws IOException {
Googlerece75722016-02-11 17:55:41 +00001204 writeFile(file.getPath(), contents);
1205 }
1206
1207 private static void touchFile(Path path) throws IOException {
1208 FileSystemUtils.createDirectoryAndParents(path.getParentDirectory());
1209 path.getParentDirectory().setWritable(true);
1210 FileSystemUtils.touchFile(path);
1211 }
1212
Rumou Duana77f32c2016-04-13 21:59:21 +00001213 private static void touchFile(Artifact file) throws IOException {
Googlerece75722016-02-11 17:55:41 +00001214 touchFile(file.getPath());
1215 }
1216
Rumou Duana77f32c2016-04-13 21:59:21 +00001217 private static void deleteFile(Artifact file) throws IOException {
Googlerece75722016-02-11 17:55:41 +00001218 Path path = file.getPath();
1219 // sometimes we write read-only files
1220 if (path.exists()) {
1221 path.setWritable(true);
1222 // work around the sticky bit (this might depend on the behavior of the OS?)
1223 path.getParentDirectory().setWritable(true);
1224 path.delete();
1225 }
1226 }
Rumou Duan73876202016-06-06 18:52:08 +00001227
1228 /** A dummy action template expansion function that just returns the injected actions */
1229 private static class DummyActionTemplateExpansionFunction implements SkyFunction {
tomlu3d1a1942017-11-29 14:01:21 -08001230 private final ActionKeyContext actionKeyContext;
tomlu9e91f202018-06-18 16:16:36 -07001231 private final ImmutableList<ActionAnalysisMetadata> actions;
Rumou Duan73876202016-06-06 18:52:08 +00001232
tomlu9e91f202018-06-18 16:16:36 -07001233 DummyActionTemplateExpansionFunction(
1234 ActionKeyContext actionKeyContext, ImmutableList<ActionAnalysisMetadata> actions) {
tomlu3d1a1942017-11-29 14:01:21 -08001235 this.actionKeyContext = actionKeyContext;
janakrcb314a22018-02-15 10:33:53 -08001236 this.actions = actions;
Rumou Duan73876202016-06-06 18:52:08 +00001237 }
1238
1239 @Override
1240 public SkyValue compute(SkyKey skyKey, Environment env) {
janakr0175ce32018-02-26 15:54:57 -08001241 try {
1242 return new ActionTemplateExpansionValue(
tomlu9e91f202018-06-18 16:16:36 -07001243 Actions.filterSharedActionsAndThrowActionConflict(actionKeyContext, actions));
janakr0175ce32018-02-26 15:54:57 -08001244 } catch (ActionConflictException e) {
1245 throw new IllegalStateException(e);
1246 }
Rumou Duan73876202016-06-06 18:52:08 +00001247 }
1248
1249 @Override
1250 public String extractTag(SkyKey skyKey) {
1251 return null;
1252 }
1253 }
1254
1255 /** No-op action that does not generate the action outputs. */
1256 private static class NoOpDummyAction extends TestAction {
1257 public NoOpDummyAction(Collection<Artifact> inputs, Collection<Artifact> outputs) {
1258 super(NO_EFFECT, inputs, outputs);
1259 }
1260
1261 /** Do nothing */
1262 @Override
ruperts841880d2017-10-18 00:58:29 +02001263 public ActionResult execute(ActionExecutionContext actionExecutionContext)
1264 throws ActionExecutionException {
1265 return ActionResult.EMPTY;
1266 }
Rumou Duan73876202016-06-06 18:52:08 +00001267 }
1268
1269 /** No-op action that throws when executed */
1270 private static class ThrowingDummyAction extends TestAction {
1271 public ThrowingDummyAction(Collection<Artifact> inputs, Collection<Artifact> outputs) {
1272 super(NO_EFFECT, inputs, outputs);
1273 }
1274
1275 /** Throws */
1276 @Override
ruperts841880d2017-10-18 00:58:29 +02001277 public ActionResult execute(ActionExecutionContext actionExecutionContext)
Rumou Duan73876202016-06-06 18:52:08 +00001278 throws ActionExecutionException {
1279 throw new ActionExecutionException("Throwing dummy action", this, true);
1280 }
1281 }
Googlerece75722016-02-11 17:55:41 +00001282}