blob: 7a9c0356aa51ec61186f44d74d521b555ee813fd [file] [log] [blame]
// 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();
}
}