| // Copyright 2019 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.exec; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static com.google.common.truth.Truth.assertWithMessage; | |
| import static org.mockito.ArgumentMatchers.any; | |
| import static org.mockito.Mockito.doAnswer; | |
| import static org.mockito.Mockito.mock; | |
| import static org.mockito.Mockito.never; | |
| import static org.mockito.Mockito.times; | |
| import static org.mockito.Mockito.verify; | |
| import static org.mockito.Mockito.when; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.devtools.build.lib.actions.ActionEnvironment; | |
| import com.google.devtools.build.lib.actions.ActionExecutionContext; | |
| import com.google.devtools.build.lib.actions.Artifact; | |
| import com.google.devtools.build.lib.actions.ArtifactPathResolver; | |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil; | |
| import com.google.devtools.build.lib.analysis.Runfiles; | |
| import com.google.devtools.build.lib.analysis.actions.SymlinkTreeAction; | |
| import com.google.devtools.build.lib.analysis.actions.SymlinkTreeActionContext; | |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; | |
| import com.google.devtools.build.lib.events.StoredEventHandler; | |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; | |
| import com.google.devtools.build.lib.vfs.OutputService; | |
| import com.google.devtools.build.lib.vfs.Path; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import com.google.devtools.build.lib.vfs.Symlinks; | |
| import java.util.Map; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| import org.mockito.ArgumentCaptor; | |
| /** Unit tests for {@link SymlinkTreeStrategy}. */ | |
| @RunWith(JUnit4.class) | |
| public final class SymlinkTreeStrategyTest extends BuildViewTestCase { | |
| @Test | |
| public void testArtifactToPathConversion() { | |
| Artifact artifact = getBinArtifactWithNoOwner("dir/foo"); | |
| assertThat(SymlinkTreeStrategy.TO_PATH.apply(artifact)) | |
| .isEqualTo(artifact.getPath().asFragment()); | |
| assertThat(SymlinkTreeStrategy.TO_PATH.apply(null)).isEqualTo(null); | |
| } | |
| @Test | |
| public void outputServiceInteraction() throws Exception { | |
| ActionExecutionContext context = mock(ActionExecutionContext.class); | |
| OutputService outputService = mock(OutputService.class); | |
| StoredEventHandler eventHandler = new StoredEventHandler(); | |
| when(context.getContext(SymlinkTreeActionContext.class)) | |
| .thenReturn(new SymlinkTreeStrategy(outputService, null)); | |
| when(context.getInputPath(any())).thenAnswer((i) -> ((Artifact) i.getArgument(0)).getPath()); | |
| when(context.getPathResolver()).thenReturn(ArtifactPathResolver.IDENTITY); | |
| when(context.getEventHandler()).thenReturn(eventHandler); | |
| when(outputService.canCreateSymlinkTree()).thenReturn(true); | |
| Artifact inputManifest = getBinArtifactWithNoOwner("dir/manifest.in"); | |
| Artifact outputManifest = getBinArtifactWithNoOwner("dir.runfiles/MANIFEST"); | |
| Artifact runfile = getBinArtifactWithNoOwner("dir/runfile"); | |
| doAnswer( | |
| (i) -> { | |
| outputManifest.getPath().getParentDirectory().createDirectoryAndParents(); | |
| return null; | |
| }) | |
| .when(outputService) | |
| .createSymlinkTree(any(), any()); | |
| Runfiles runfiles = | |
| new Runfiles.Builder("TESTING", false) | |
| .setEmptyFilesSupplier((paths) -> ImmutableList.of(PathFragment.create("dir/empty"))) | |
| .addArtifact(runfile) | |
| .build(); | |
| SymlinkTreeAction action = | |
| new SymlinkTreeAction( | |
| ActionsTestUtil.NULL_ACTION_OWNER, | |
| inputManifest, | |
| runfiles, | |
| outputManifest, | |
| /*filesetRoot=*/ null, | |
| ActionEnvironment.EMPTY, | |
| /*enableRunfiles=*/ true, | |
| /*inprocessSymlinkCreation=*/ false, | |
| /*skipRunfilesManifests=*/ false); | |
| action.execute(context); | |
| @SuppressWarnings("unchecked") | |
| ArgumentCaptor<Map<PathFragment, PathFragment>> capture = ArgumentCaptor.forClass(Map.class); | |
| verify(outputService, times(1)).createSymlinkTree(capture.capture(), any()); | |
| assertThat(capture.getValue()) | |
| .containsExactly( | |
| PathFragment.create("TESTING/dir/runfile"), | |
| runfile.getPath().asFragment(), | |
| PathFragment.create("TESTING/dir/empty"), | |
| null); | |
| } | |
| @Test | |
| public void inprocessSymlinkCreation() throws Exception { | |
| ActionExecutionContext context = mock(ActionExecutionContext.class); | |
| OutputService outputService = mock(OutputService.class); | |
| StoredEventHandler eventHandler = new StoredEventHandler(); | |
| when(context.getContext(SymlinkTreeActionContext.class)) | |
| .thenReturn(new SymlinkTreeStrategy(outputService, null)); | |
| when(context.getInputPath(any())).thenAnswer((i) -> ((Artifact) i.getArgument(0)).getPath()); | |
| when(context.getEventHandler()).thenReturn(eventHandler); | |
| when(outputService.canCreateSymlinkTree()).thenReturn(false); | |
| Artifact inputManifest = getBinArtifactWithNoOwner("dir/manifest.in"); | |
| Artifact outputManifest = getBinArtifactWithNoOwner("dir.runfiles/MANIFEST"); | |
| Artifact runfile = getBinArtifactWithNoOwner("dir/runfile"); | |
| Runfiles runfiles = | |
| new Runfiles.Builder("TESTING", false) | |
| .setEmptyFilesSupplier((paths) -> ImmutableList.of(PathFragment.create("dir/empty"))) | |
| .addArtifact(runfile) | |
| .build(); | |
| SymlinkTreeAction action = | |
| new SymlinkTreeAction( | |
| ActionsTestUtil.NULL_ACTION_OWNER, | |
| inputManifest, | |
| runfiles, | |
| outputManifest, | |
| /*filesetRoot=*/ null, | |
| ActionEnvironment.EMPTY, | |
| /*enableRunfiles=*/ true, | |
| /*inprocessSymlinkCreation=*/ true, | |
| /*skipRunfilesManifests*/ false); | |
| action.execute(context); | |
| // Check that the OutputService is not used. | |
| verify(outputService, never()).createSymlinkTree(any(), any()); | |
| Path p = outputManifest.getPath().getParentDirectory().getRelative("TESTING/dir/runfile"); | |
| assertWithMessage("Path %s expected to exist", p).that(p.exists(Symlinks.NOFOLLOW)).isTrue(); | |
| assertWithMessage("Path %s expected to be a symlink", p).that(p.isSymbolicLink()).isTrue(); | |
| assertThat(p.readSymbolicLink()).isEqualTo(runfile.getPath().asFragment()); | |
| Path q = outputManifest.getPath().getParentDirectory().getRelative("TESTING/dir/empty"); | |
| assertWithMessage("Path %s expected to be a file", q).that(q.isFile()).isTrue(); | |
| assertThat(FileSystemUtils.readContent(q)).isEmpty(); | |
| } | |
| } |