blob: 562d7eabb90aa071191fcb3292c4375e7e3157d1 [file] [log] [blame]
// 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;
}
}