| // Copyright 2016 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| package com.google.devtools.build.lib.analysis.actions; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static org.junit.Assert.fail; |
| import static org.mockito.Mockito.mock; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.actions.Action; |
| import com.google.devtools.build.lib.actions.ActionExecutionContext; |
| import com.google.devtools.build.lib.actions.ActionExecutionException; |
| import com.google.devtools.build.lib.actions.ActionInput; |
| import com.google.devtools.build.lib.actions.ActionInputPrefetcher; |
| import com.google.devtools.build.lib.actions.ActionResult; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact; |
| import com.google.devtools.build.lib.actions.Artifact.SpecialArtifactType; |
| import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact; |
| import com.google.devtools.build.lib.actions.ArtifactOwner; |
| import com.google.devtools.build.lib.actions.BaseSpawn; |
| import com.google.devtools.build.lib.actions.Executor; |
| import com.google.devtools.build.lib.actions.Root; |
| import com.google.devtools.build.lib.actions.Spawn; |
| import com.google.devtools.build.lib.actions.SpawnActionContext; |
| import com.google.devtools.build.lib.actions.cache.Md5Digest; |
| import com.google.devtools.build.lib.actions.cache.Metadata; |
| import com.google.devtools.build.lib.actions.cache.MetadataHandler; |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil; |
| import com.google.devtools.build.lib.analysis.FilesToRunProvider; |
| import com.google.devtools.build.lib.analysis.util.ActionTester; |
| import com.google.devtools.build.lib.analysis.util.ActionTester.ActionCombinationFactory; |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; |
| import com.google.devtools.build.lib.exec.util.TestExecutorBuilder; |
| import com.google.devtools.build.lib.vfs.FileStatus; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import java.util.ArrayList; |
| import java.util.List; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Tests {@link PopulateTreeArtifactAction}. */ |
| @RunWith(JUnit4.class) |
| public class PopulateTreeArtifactActionTest extends BuildViewTestCase { |
| private static class TestMetadataHandler implements MetadataHandler { |
| private final List<Artifact> storingExpandedTreeFileArtifacts; |
| |
| TestMetadataHandler(List<Artifact> storingExpandedTreeFileArtifacts) { |
| this.storingExpandedTreeFileArtifacts = storingExpandedTreeFileArtifacts; |
| } |
| |
| @Override |
| public void addExpandedTreeOutput(TreeFileArtifact output) { |
| storingExpandedTreeFileArtifacts.add(output); |
| } |
| |
| @Override |
| public Iterable<TreeFileArtifact> getExpandedOutputs(Artifact artifact) { |
| throw new UnsupportedOperationException(artifact.prettyPrint()); |
| } |
| |
| @Override |
| public Metadata getMetadata(Artifact artifact) { |
| throw new UnsupportedOperationException(artifact.prettyPrint()); |
| } |
| |
| @Override |
| public void setDigestForVirtualArtifact(Artifact artifact, Md5Digest md5Digest) { |
| throw new UnsupportedOperationException(artifact.prettyPrint() + ": " + md5Digest); |
| } |
| |
| @Override |
| public void injectDigest(ActionInput output, FileStatus statNoFollow, byte[] digest) { |
| throw new UnsupportedOperationException(output.toString()); |
| } |
| |
| @Override |
| public void markOmitted(ActionInput output) { |
| throw new UnsupportedOperationException(output.toString()); |
| } |
| |
| @Override |
| public boolean artifactOmitted(Artifact artifact) { |
| throw new UnsupportedOperationException(artifact.prettyPrint()); |
| } |
| |
| @Override |
| public void discardOutputMetadata() { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| |
| private Root root; |
| |
| @Before |
| public void setRootDir() throws Exception { |
| root = Root.asDerivedRoot(scratch.dir("/exec/root")); |
| } |
| |
| @Test |
| public void testActionOutputs() throws Exception { |
| Action action = createPopulateTreeArtifactAction(); |
| assertThat(Artifact.toExecPaths(action.getOutputs())).containsExactly("test/archive_member"); |
| } |
| |
| @Test |
| public void testActionInputs() throws Exception { |
| Action action = createPopulateTreeArtifactAction(); |
| assertThat(Artifact.toExecPaths(action.getInputs())).containsExactly( |
| "myArchive.zip", |
| "archiveManifest.txt", |
| "unzipBinary"); |
| } |
| |
| @Test |
| public void testSpawnOutputs() throws Exception { |
| PopulateTreeArtifactAction action = createPopulateTreeArtifactAction(); |
| Spawn spawn = action.createSpawn(); |
| Iterable<Artifact> outputs = actionInputsToArtifacts(spawn.getOutputFiles()); |
| assertThat(Artifact.toExecPaths(outputs)).containsExactly( |
| "test/archive_member/archive_members/1.class", |
| "test/archive_member/archive_members/2.class", |
| "test/archive_member/archive_members/txt/text.txt"); |
| } |
| |
| @Test |
| public void testSpawnInputs() throws Exception { |
| PopulateTreeArtifactAction action = createPopulateTreeArtifactAction(); |
| Spawn spawn = action.createSpawn(); |
| Iterable<Artifact> inputs = actionInputsToArtifacts(spawn.getInputFiles()); |
| assertThat(Artifact.toExecPaths(inputs)).containsExactly( |
| "myArchive.zip", |
| "archiveManifest.txt", |
| "unzipBinary"); |
| } |
| |
| @Test |
| public void testSpawnArguments() throws Exception { |
| PopulateTreeArtifactAction action = createPopulateTreeArtifactAction(); |
| BaseSpawn spawn = (BaseSpawn) action.createSpawn(); |
| assertThat(spawn.getArguments()).containsExactly( |
| "unzipBinary", |
| "x", |
| "myArchive.zip", |
| "-d", |
| "test/archive_member", |
| "@archiveManifest.txt").inOrder(); |
| } |
| |
| @Test |
| public void testTreeArtifactPopulated() throws Exception { |
| ArrayList<Artifact> treefileArtifacts = new ArrayList<Artifact>(); |
| PopulateTreeArtifactAction action = createPopulateTreeArtifactAction(); |
| ActionExecutionContext executionContext = actionExecutionContext(treefileArtifacts); |
| ActionResult actionResult = action.execute(executionContext); |
| |
| assertThat(actionResult.spawnResults()).isEmpty(); |
| |
| assertThat(Artifact.toExecPaths(treefileArtifacts)).containsExactly( |
| "test/archive_member/archive_members/1.class", |
| "test/archive_member/archive_members/2.class", |
| "test/archive_member/archive_members/txt/text.txt"); |
| } |
| |
| @Test |
| public void testInvalidManifestEntryPaths() throws Exception { |
| Action action = createPopulateTreeArtifactAction(); |
| scratch.overwriteFile( |
| "archiveManifest.txt", |
| "archive_members/1.class", |
| "../invalid_relative_path/myfile.class"); |
| ActionExecutionContext executionContext = actionExecutionContext(new ArrayList<Artifact>()); |
| |
| try { |
| action.execute(executionContext); |
| fail("Invalid manifest entry paths, expected exception"); |
| } catch (ActionExecutionException e) { |
| // Expect ActionExecutionException |
| } |
| } |
| |
| @Test |
| public void testTreeFileArtifactPathPrefixConflicts() throws Exception { |
| Action action = createPopulateTreeArtifactAction(); |
| scratch.overwriteFile( |
| "archiveManifest.txt", |
| "archive_members/conflict", |
| "archive_members/conflict/1.class"); |
| ActionExecutionContext executionContext = actionExecutionContext(new ArrayList<Artifact>()); |
| |
| try { |
| action.execute(executionContext); |
| fail("Artifact path prefix conflicts, expected exception"); |
| } catch (ActionExecutionException e) { |
| // Expect ActionExecutionException |
| } |
| } |
| |
| @Test |
| public void testEmptyTreeArtifactInputAndOutput() throws Exception { |
| Action action = createPopulateTreeArtifactAction(); |
| scratch.overwriteFile("archiveManifest.txt", ""); |
| |
| ArrayList<Artifact> treeFileArtifacts = new ArrayList<Artifact>(); |
| ActionExecutionContext executionContext = actionExecutionContext(treeFileArtifacts); |
| |
| ActionResult actionResult = action.execute(executionContext); |
| |
| assertThat(actionResult.spawnResults()).isEmpty(); |
| assertThat(treeFileArtifacts).isEmpty(); |
| } |
| |
| @Test |
| public void testOutputTreeFileArtifactDirsCreated() throws Exception { |
| Action action = createPopulateTreeArtifactAction(); |
| scratch.overwriteFile( |
| "archiveManifest.txt", |
| "archive_members/dirA/memberA", |
| "archive_members/dirB/memberB"); |
| |
| ArrayList<Artifact> treeFileArtifacts = new ArrayList<Artifact>(); |
| ActionExecutionContext executionContext = actionExecutionContext(treeFileArtifacts); |
| ActionResult actionResult = action.execute(executionContext); |
| |
| assertThat(actionResult.spawnResults()).isEmpty(); |
| |
| // We check whether the parent directory structures of output TreeFileArtifacts exist even |
| // though the spawn is not executed (the SpawnActionContext is mocked out). |
| assertThat(treeFileArtifacts).hasSize(2); |
| for (Artifact treeFileArtifact : treeFileArtifacts) { |
| assertThat(treeFileArtifact.getPath().getParentDirectory().exists()).isTrue(); |
| assertThat(treeFileArtifact.getPath().exists()).isFalse(); |
| } |
| } |
| |
| private enum KeyAttributes { |
| ARCHIVE, |
| TREE_ARTIFACT, |
| ARCHIVE_MANIFEST, |
| ZIPPER |
| } |
| |
| @Test |
| public void testComputeKey() throws Exception { |
| final Artifact archiveA = getSourceArtifact("myArchiveA.zip"); |
| final Artifact archiveB = getSourceArtifact("myArchiveB.zip"); |
| final Artifact treeArtifactToPopulateA = createTreeArtifact("testA/archive_member"); |
| final Artifact treeArtifactToPopulateB = createTreeArtifact("testB/archive_member"); |
| final Artifact archiveManifestA = getSourceArtifact("archiveManifestA.txt"); |
| final Artifact archiveManifestB = getSourceArtifact("archiveManifestB.txt"); |
| final FilesToRunProvider zipperA = FilesToRunProvider.fromSingleExecutableArtifact( |
| getSourceArtifact("unzipBinaryA")); |
| final FilesToRunProvider zipperB = FilesToRunProvider.fromSingleExecutableArtifact( |
| getSourceArtifact("unzipBinaryB")); |
| |
| ActionTester.runTest( |
| KeyAttributes.class, |
| new ActionCombinationFactory<KeyAttributes>() { |
| @Override |
| public Action generate(ImmutableSet<KeyAttributes> attributesToFlip) { |
| Artifact archive = |
| attributesToFlip.contains(KeyAttributes.ARCHIVE) ? archiveA : archiveB; |
| Artifact treeArtifactToPopulate = |
| attributesToFlip.contains(KeyAttributes.TREE_ARTIFACT) |
| ? treeArtifactToPopulateA |
| : treeArtifactToPopulateB; |
| Artifact archiveManifest = |
| attributesToFlip.contains(KeyAttributes.ARCHIVE_MANIFEST) |
| ? archiveManifestA |
| : archiveManifestB; |
| FilesToRunProvider zipper = |
| attributesToFlip.contains(KeyAttributes.ZIPPER) ? zipperA : zipperB; |
| |
| return new PopulateTreeArtifactAction( |
| ActionsTestUtil.NULL_ACTION_OWNER, |
| archive, |
| archiveManifest, |
| treeArtifactToPopulate, |
| zipper); |
| } |
| }); |
| } |
| |
| private PopulateTreeArtifactAction createPopulateTreeArtifactAction() throws Exception { |
| Artifact archive = getSourceArtifact("myArchive.zip"); |
| Artifact treeArtifactToPopulate = createTreeArtifact("test/archive_member"); |
| Artifact archiveManifest = getSourceArtifact("archiveManifest.txt"); |
| FilesToRunProvider unzip = FilesToRunProvider.fromSingleExecutableArtifact( |
| getSourceArtifact("unzipBinary")); |
| |
| scratch.file( |
| "archiveManifest.txt", |
| "archive_members/1.class", |
| "archive_members/2.class", |
| "archive_members/txt/text.txt"); |
| |
| return new PopulateTreeArtifactAction( |
| ActionsTestUtil.NULL_ACTION_OWNER, |
| archive, |
| archiveManifest, |
| treeArtifactToPopulate, |
| unzip); |
| } |
| |
| private ActionExecutionContext actionExecutionContext( |
| List<Artifact> storingExpandedTreeFileArtifacts) throws Exception { |
| Executor executor = |
| new TestExecutorBuilder(fileSystem, directories, null) |
| .setExecution(PopulateTreeArtifactAction.MNEMONIC, mock(SpawnActionContext.class)) |
| .build(); |
| |
| return new ActionExecutionContext( |
| executor, |
| null, |
| ActionInputPrefetcher.NONE, |
| new TestMetadataHandler(storingExpandedTreeFileArtifacts), |
| null, |
| ImmutableMap.<String, String>of(), |
| null); |
| } |
| |
| private Artifact createTreeArtifact(String rootRelativePath) { |
| PathFragment relpath = PathFragment.create(rootRelativePath); |
| return new SpecialArtifact( |
| root.getPath().getRelative(relpath), |
| root, |
| root.getExecPath().getRelative(relpath), |
| ArtifactOwner.NULL_OWNER, |
| SpecialArtifactType.TREE); |
| } |
| |
| private Iterable<Artifact> actionInputsToArtifacts(Iterable<? extends ActionInput> files) { |
| ImmutableList.Builder<Artifact> builder = ImmutableList.<Artifact>builder(); |
| for (ActionInput file : files) { |
| builder.add((Artifact) file); |
| } |
| return builder.build(); |
| } |
| } |