| // Copyright 2022 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.sandbox; |
| |
| import static com.google.common.util.concurrent.Futures.immediateVoidFuture; |
| import static org.mockito.Mockito.mock; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.io.Files; |
| import com.google.common.util.concurrent.ListenableFuture; |
| import com.google.devtools.build.lib.actions.ActionContext; |
| import com.google.devtools.build.lib.actions.ActionInput; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander; |
| import com.google.devtools.build.lib.actions.MetadataProvider; |
| import com.google.devtools.build.lib.actions.Spawn; |
| import com.google.devtools.build.lib.actions.cache.MetadataInjector; |
| import com.google.devtools.build.lib.exec.SpawnInputExpander; |
| import com.google.devtools.build.lib.exec.SpawnRunner.ProgressStatus; |
| import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionContext; |
| import com.google.devtools.build.lib.testutil.BlazeTestUtils; |
| import com.google.devtools.build.lib.testutil.TestConstants; |
| import com.google.devtools.build.lib.util.io.FileOutErr; |
| import com.google.devtools.build.lib.vfs.FileSystem; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import java.io.File; |
| import java.io.IOException; |
| import java.time.Duration; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.SortedMap; |
| import java.util.TreeMap; |
| import javax.annotation.Nullable; |
| |
| // TODO(b/62588075): Use this class for the LocalSpawnRunnerTest as well. |
| /** |
| * Utilities to help test SpawnRunners. |
| * |
| * <p>For example, to make embedded tools available for tests, or to use a rigged {@link |
| * SpawnExecutionContext} for testing purposes. |
| */ |
| public final class SpawnRunnerTestUtil { |
| private SpawnRunnerTestUtil() {} |
| |
| /** A rigged spawn execution policy that can be used for testing purposes. */ |
| public static final class SpawnExecutionContextForTesting implements SpawnExecutionContext { |
| private final List<ProgressStatus> reportedStatus = new ArrayList<>(); |
| |
| private final Spawn spawn; |
| private final Duration timeout; |
| private final FileOutErr fileOutErr; |
| |
| /** An object that can expand middleman artifacts. */ |
| private final ArtifactExpander artifactExpander = |
| new ArtifactExpander() { |
| @Override |
| public void expand(Artifact artifact, Collection<? super Artifact> output) { |
| // Do nothing. |
| } |
| }; |
| |
| /** |
| * Creates a new spawn execution policy for testing purposes. |
| * |
| * @param fileOutErr the {@link FileOutErr} object to use. After a {@link Spawn} is executed, |
| * its stdout and stderr can be available here, if the spawn runner uses the fileOutErr |
| * returned by {@link #getFileOutErr()} on the spawn execution policy |
| * @param timeout the timeout to use. Spawn runners may request this via {@link #getTimeout()} |
| */ |
| public SpawnExecutionContextForTesting(Spawn spawn, FileOutErr fileOutErr, Duration timeout) { |
| this.spawn = spawn; |
| this.fileOutErr = fileOutErr; |
| this.timeout = timeout; |
| } |
| |
| @Override |
| public int getId() { |
| return 0; |
| } |
| |
| @Override |
| public ListenableFuture<Void> prefetchInputs() { |
| return immediateVoidFuture(); |
| } |
| |
| @Override |
| public void lockOutputFiles(int exitCode, String errorMessage, FileOutErr outErr) |
| throws InterruptedException {} |
| |
| @Override |
| public boolean speculating() { |
| return false; |
| } |
| |
| @Override |
| public MetadataProvider getMetadataProvider() { |
| return mock(MetadataProvider.class); |
| } |
| |
| @Override |
| public ArtifactExpander getArtifactExpander() { |
| return artifactExpander; |
| } |
| |
| @Override |
| public SpawnInputExpander getSpawnInputExpander() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public Duration getTimeout() { |
| return timeout; |
| } |
| |
| @Override |
| public FileOutErr getFileOutErr() { |
| return fileOutErr; |
| } |
| |
| @Override |
| public SortedMap<PathFragment, ActionInput> getInputMapping(PathFragment baseDirectory) { |
| TreeMap<PathFragment, ActionInput> inputMapping = new TreeMap<>(); |
| for (ActionInput actionInput : spawn.getInputFiles().toList()) { |
| inputMapping.put(baseDirectory.getRelative(actionInput.getExecPath()), actionInput); |
| } |
| return inputMapping; |
| } |
| |
| @Override |
| public void report(ProgressStatus progress) { |
| reportedStatus.add(progress); |
| } |
| |
| @Override |
| public MetadataInjector getMetadataInjector() { |
| return mock(MetadataInjector.class); |
| } |
| |
| @Override |
| public <T extends ActionContext> T getContext(Class<T> identifyingType) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public boolean isRewindingEnabled() { |
| return false; |
| } |
| |
| @Override |
| public void checkForLostInputs() {} |
| |
| @Nullable |
| @Override |
| public FileSystem getActionFileSystem() { |
| return null; |
| } |
| } |
| |
| /** |
| * Copies a file into a specific path. |
| * |
| * @param sourceFile the file to copy |
| * @param destinationDirectoryPath the directory to copy the sourceFile into |
| */ |
| public static Path copyFileToPath(File sourceFile, Path destinationDirectoryPath) |
| throws IOException { |
| Preconditions.checkArgument(sourceFile.exists(), "source file to copy does not exist"); |
| Preconditions.checkArgument( |
| destinationDirectoryPath.exists(), "destination directory to copy to does not exist"); |
| |
| Path destinationFilePath = destinationDirectoryPath.getRelative(sourceFile.getName()); |
| File destinationFile = destinationFilePath.getPathFile(); |
| |
| Preconditions.checkState(!destinationFilePath.exists(), "destination file already exists"); |
| Files.copy(sourceFile, destinationFile); |
| |
| return destinationFilePath; |
| } |
| |
| private static Path copyToolIntoPath(String sourceToolRelativePath, Path destinationDirectoryPath) |
| throws IOException { |
| File sourceToolFile = |
| new File( |
| PathFragment.create(BlazeTestUtils.runfilesDir()) |
| .getRelative(sourceToolRelativePath) |
| .getPathString()); |
| Preconditions.checkState(sourceToolFile.exists(), "tool not found"); |
| |
| Path binDirectoryPath = destinationDirectoryPath.getRelative("_bin"); |
| binDirectoryPath.createDirectory(); |
| |
| Path destinationToolPath = copyFileToPath(sourceToolFile, binDirectoryPath); |
| |
| destinationToolPath.setExecutable(true); |
| |
| return destinationToolPath; |
| } |
| |
| /** Copies the {@code process-wrapper} tool a path where a runner expects to find it. */ |
| public static Path copyProcessWrapperIntoPath(Path destinationDirectoryPath) throws IOException { |
| return copyToolIntoPath(TestConstants.PROCESS_WRAPPER_PATH, destinationDirectoryPath); |
| } |
| |
| /** Copies the {@code linux-sandbox} tool into a path where a runner expects to find it. */ |
| public static Path copyLinuxSandboxIntoPath(Path destinationDirectoryPath) throws IOException { |
| return copyToolIntoPath(TestConstants.LINUX_SANDBOX_PATH, destinationDirectoryPath); |
| } |
| |
| /** Copies the {@code spend_cpu_time} test util into a path where a runner expects to find it. */ |
| public static Path copyCpuTimeSpenderIntoPath(Path destinationDirectoryPath) throws IOException { |
| File realCpuTimeSpenderFile = |
| new File( |
| PathFragment.create(BlazeTestUtils.runfilesDir()) |
| .getRelative(TestConstants.CPU_TIME_SPENDER_PATH) |
| .getPathString()); |
| Preconditions.checkState(realCpuTimeSpenderFile.exists(), "spend_cpu_time not found"); |
| |
| Path destinationCpuTimeSpenderPath = |
| copyFileToPath(realCpuTimeSpenderFile, destinationDirectoryPath); |
| |
| destinationCpuTimeSpenderPath.setExecutable(true); |
| |
| return destinationCpuTimeSpenderPath; |
| } |
| } |