ulfjack | 8f075d2 | 2019-11-26 02:48:42 -0800 | [diff] [blame] | 1 | // Copyright 2019 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. |
| 14 | |
| 15 | package com.google.devtools.build.lib.exec; |
| 16 | |
| 17 | import static com.google.common.truth.Truth.assertThat; |
ulfjack | 112d41f | 2019-11-28 00:33:14 -0800 | [diff] [blame] | 18 | import static com.google.common.truth.Truth.assertWithMessage; |
ulfjack | 8f075d2 | 2019-11-26 02:48:42 -0800 | [diff] [blame] | 19 | import static org.mockito.ArgumentMatchers.any; |
ulfjack | 1a80bb8 | 2019-11-26 07:52:35 -0800 | [diff] [blame] | 20 | import static org.mockito.Mockito.doAnswer; |
ulfjack | 8f075d2 | 2019-11-26 02:48:42 -0800 | [diff] [blame] | 21 | import static org.mockito.Mockito.mock; |
ulfjack | 112d41f | 2019-11-28 00:33:14 -0800 | [diff] [blame] | 22 | import static org.mockito.Mockito.never; |
ulfjack | 8f075d2 | 2019-11-26 02:48:42 -0800 | [diff] [blame] | 23 | import static org.mockito.Mockito.times; |
| 24 | import static org.mockito.Mockito.verify; |
| 25 | import static org.mockito.Mockito.when; |
| 26 | |
| 27 | import com.google.common.collect.ImmutableList; |
| 28 | import com.google.devtools.build.lib.actions.ActionEnvironment; |
| 29 | import com.google.devtools.build.lib.actions.ActionExecutionContext; |
| 30 | import com.google.devtools.build.lib.actions.Artifact; |
| 31 | import com.google.devtools.build.lib.actions.ArtifactPathResolver; |
| 32 | import com.google.devtools.build.lib.actions.util.ActionsTestUtil; |
| 33 | import com.google.devtools.build.lib.analysis.Runfiles; |
| 34 | import com.google.devtools.build.lib.analysis.actions.SymlinkTreeAction; |
| 35 | import com.google.devtools.build.lib.analysis.actions.SymlinkTreeActionContext; |
| 36 | import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; |
| 37 | import com.google.devtools.build.lib.events.StoredEventHandler; |
ulfjack | 112d41f | 2019-11-28 00:33:14 -0800 | [diff] [blame] | 38 | import com.google.devtools.build.lib.vfs.FileSystemUtils; |
ulfjack | 8f075d2 | 2019-11-26 02:48:42 -0800 | [diff] [blame] | 39 | import com.google.devtools.build.lib.vfs.OutputService; |
ulfjack | 112d41f | 2019-11-28 00:33:14 -0800 | [diff] [blame] | 40 | import com.google.devtools.build.lib.vfs.Path; |
ulfjack | 8f075d2 | 2019-11-26 02:48:42 -0800 | [diff] [blame] | 41 | import com.google.devtools.build.lib.vfs.PathFragment; |
ulfjack | 112d41f | 2019-11-28 00:33:14 -0800 | [diff] [blame] | 42 | import com.google.devtools.build.lib.vfs.Symlinks; |
ulfjack | 8f075d2 | 2019-11-26 02:48:42 -0800 | [diff] [blame] | 43 | import java.util.Map; |
| 44 | import org.junit.Test; |
| 45 | import org.junit.runner.RunWith; |
| 46 | import org.junit.runners.JUnit4; |
| 47 | import org.mockito.ArgumentCaptor; |
| 48 | |
| 49 | /** Unit tests for {@link SymlinkTreeStrategy}. */ |
| 50 | @RunWith(JUnit4.class) |
| 51 | public final class SymlinkTreeStrategyTest extends BuildViewTestCase { |
| 52 | @Test |
| 53 | public void testArtifactToPathConversion() { |
| 54 | Artifact artifact = getBinArtifactWithNoOwner("dir/foo"); |
ulfjack | 1a80bb8 | 2019-11-26 07:52:35 -0800 | [diff] [blame] | 55 | assertThat(SymlinkTreeStrategy.TO_PATH.apply(artifact)) |
| 56 | .isEqualTo(artifact.getPath().asFragment()); |
ulfjack | 8f075d2 | 2019-11-26 02:48:42 -0800 | [diff] [blame] | 57 | assertThat(SymlinkTreeStrategy.TO_PATH.apply(null)).isEqualTo(null); |
| 58 | } |
| 59 | |
| 60 | @Test |
| 61 | public void outputServiceInteraction() throws Exception { |
| 62 | ActionExecutionContext context = mock(ActionExecutionContext.class); |
| 63 | OutputService outputService = mock(OutputService.class); |
| 64 | StoredEventHandler eventHandler = new StoredEventHandler(); |
| 65 | |
| 66 | when(context.getContext(SymlinkTreeActionContext.class)) |
| 67 | .thenReturn(new SymlinkTreeStrategy(outputService, null)); |
| 68 | when(context.getInputPath(any())).thenAnswer((i) -> ((Artifact) i.getArgument(0)).getPath()); |
| 69 | when(context.getPathResolver()).thenReturn(ArtifactPathResolver.IDENTITY); |
| 70 | when(context.getEventHandler()).thenReturn(eventHandler); |
| 71 | when(outputService.canCreateSymlinkTree()).thenReturn(true); |
| 72 | |
| 73 | Artifact inputManifest = getBinArtifactWithNoOwner("dir/manifest.in"); |
ulfjack | 112d41f | 2019-11-28 00:33:14 -0800 | [diff] [blame] | 74 | Artifact outputManifest = getBinArtifactWithNoOwner("dir.runfiles/MANIFEST"); |
ulfjack | 8f075d2 | 2019-11-26 02:48:42 -0800 | [diff] [blame] | 75 | Artifact runfile = getBinArtifactWithNoOwner("dir/runfile"); |
ulfjack | 1a80bb8 | 2019-11-26 07:52:35 -0800 | [diff] [blame] | 76 | doAnswer( |
| 77 | (i) -> { |
| 78 | outputManifest.getPath().getParentDirectory().createDirectoryAndParents(); |
| 79 | return null; |
| 80 | }) |
| 81 | .when(outputService) |
| 82 | .createSymlinkTree(any(), any()); |
ulfjack | 8f075d2 | 2019-11-26 02:48:42 -0800 | [diff] [blame] | 83 | |
| 84 | Runfiles runfiles = |
| 85 | new Runfiles.Builder("TESTING", false) |
| 86 | .setEmptyFilesSupplier((paths) -> ImmutableList.of(PathFragment.create("dir/empty"))) |
| 87 | .addArtifact(runfile) |
| 88 | .build(); |
| 89 | SymlinkTreeAction action = |
| 90 | new SymlinkTreeAction( |
| 91 | ActionsTestUtil.NULL_ACTION_OWNER, |
| 92 | inputManifest, |
| 93 | runfiles, |
| 94 | outputManifest, |
felly | a0e7b67 | 2020-01-29 11:31:07 -0800 | [diff] [blame] | 95 | /*filesetRoot=*/ null, |
ulfjack | 8f075d2 | 2019-11-26 02:48:42 -0800 | [diff] [blame] | 96 | ActionEnvironment.EMPTY, |
ulfjack | 112d41f | 2019-11-28 00:33:14 -0800 | [diff] [blame] | 97 | /*enableRunfiles=*/ true, |
ulfjack | ef1a9da | 2019-11-29 12:00:03 -0800 | [diff] [blame] | 98 | /*inprocessSymlinkCreation=*/ false, |
| 99 | /*skipRunfilesManifests=*/ false); |
ulfjack | 8f075d2 | 2019-11-26 02:48:42 -0800 | [diff] [blame] | 100 | |
| 101 | action.execute(context); |
| 102 | |
| 103 | @SuppressWarnings("unchecked") |
ulfjack | 1a80bb8 | 2019-11-26 07:52:35 -0800 | [diff] [blame] | 104 | ArgumentCaptor<Map<PathFragment, PathFragment>> capture = ArgumentCaptor.forClass(Map.class); |
| 105 | verify(outputService, times(1)).createSymlinkTree(capture.capture(), any()); |
ulfjack | 8f075d2 | 2019-11-26 02:48:42 -0800 | [diff] [blame] | 106 | assertThat(capture.getValue()) |
| 107 | .containsExactly( |
| 108 | PathFragment.create("TESTING/dir/runfile"), |
ulfjack | 1a80bb8 | 2019-11-26 07:52:35 -0800 | [diff] [blame] | 109 | runfile.getPath().asFragment(), |
ulfjack | 8f075d2 | 2019-11-26 02:48:42 -0800 | [diff] [blame] | 110 | PathFragment.create("TESTING/dir/empty"), |
| 111 | null); |
| 112 | } |
ulfjack | 112d41f | 2019-11-28 00:33:14 -0800 | [diff] [blame] | 113 | |
| 114 | @Test |
| 115 | public void inprocessSymlinkCreation() throws Exception { |
| 116 | ActionExecutionContext context = mock(ActionExecutionContext.class); |
| 117 | OutputService outputService = mock(OutputService.class); |
| 118 | StoredEventHandler eventHandler = new StoredEventHandler(); |
| 119 | |
| 120 | when(context.getContext(SymlinkTreeActionContext.class)) |
| 121 | .thenReturn(new SymlinkTreeStrategy(outputService, null)); |
| 122 | when(context.getInputPath(any())).thenAnswer((i) -> ((Artifact) i.getArgument(0)).getPath()); |
| 123 | when(context.getEventHandler()).thenReturn(eventHandler); |
| 124 | when(outputService.canCreateSymlinkTree()).thenReturn(false); |
| 125 | |
| 126 | Artifact inputManifest = getBinArtifactWithNoOwner("dir/manifest.in"); |
| 127 | Artifact outputManifest = getBinArtifactWithNoOwner("dir.runfiles/MANIFEST"); |
| 128 | Artifact runfile = getBinArtifactWithNoOwner("dir/runfile"); |
| 129 | |
| 130 | Runfiles runfiles = |
| 131 | new Runfiles.Builder("TESTING", false) |
| 132 | .setEmptyFilesSupplier((paths) -> ImmutableList.of(PathFragment.create("dir/empty"))) |
| 133 | .addArtifact(runfile) |
| 134 | .build(); |
| 135 | SymlinkTreeAction action = |
| 136 | new SymlinkTreeAction( |
| 137 | ActionsTestUtil.NULL_ACTION_OWNER, |
| 138 | inputManifest, |
| 139 | runfiles, |
| 140 | outputManifest, |
felly | a0e7b67 | 2020-01-29 11:31:07 -0800 | [diff] [blame] | 141 | /*filesetRoot=*/ null, |
ulfjack | 112d41f | 2019-11-28 00:33:14 -0800 | [diff] [blame] | 142 | ActionEnvironment.EMPTY, |
| 143 | /*enableRunfiles=*/ true, |
ulfjack | ef1a9da | 2019-11-29 12:00:03 -0800 | [diff] [blame] | 144 | /*inprocessSymlinkCreation=*/ true, |
| 145 | /*skipRunfilesManifests*/ false); |
ulfjack | 112d41f | 2019-11-28 00:33:14 -0800 | [diff] [blame] | 146 | |
| 147 | action.execute(context); |
| 148 | // Check that the OutputService is not used. |
| 149 | verify(outputService, never()).createSymlinkTree(any(), any()); |
| 150 | |
| 151 | Path p = outputManifest.getPath().getParentDirectory().getRelative("TESTING/dir/runfile"); |
| 152 | assertWithMessage("Path %s expected to exist", p).that(p.exists(Symlinks.NOFOLLOW)).isTrue(); |
| 153 | assertWithMessage("Path %s expected to be a symlink", p).that(p.isSymbolicLink()).isTrue(); |
| 154 | assertThat(p.readSymbolicLink()).isEqualTo(runfile.getPath().asFragment()); |
| 155 | Path q = outputManifest.getPath().getParentDirectory().getRelative("TESTING/dir/empty"); |
| 156 | assertWithMessage("Path %s expected to be a file", q).that(q.isFile()).isTrue(); |
| 157 | assertThat(FileSystemUtils.readContent(q)).isEmpty(); |
| 158 | } |
ulfjack | 8f075d2 | 2019-11-26 02:48:42 -0800 | [diff] [blame] | 159 | } |