blob: 49b0b8bf34ef2289ad76ae7a9b3e6572a74a770e [file] [log] [blame]
// 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.sandbox;
import static com.google.common.truth.Truth.assertThat;
import com.google.devtools.build.lib.vfs.DigestHashFunction;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.JavaIoFileSystem;
import com.google.devtools.build.lib.vfs.Path;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.junit.After;
import org.junit.Before;
/**
* Common code to unit test {@link RealSandboxfsProcess}.
*
* <p>These tests validate the communication protocol between Bazel and a sandboxfs but do so using
* golden data. They are meant to sanity-check changes to the Bazel codebase against all supported
* sandboxfs versions but cannot guarantee that the integration with a real sandboxfs binary work.
*/
public abstract class BaseRealSandboxfsProcessTest {
/** Path to the mount point passed to sandboxfs. Not accessed, so it's not even created. */
static final String FAKE_MOUNT_POINT = "/non-existent/mount/point";
/** Sandboxfs version to return to Bazel when queried. */
private final String version;
/**
* Expected mount arguments for each {@link #createAndStartFakeSandboxfs} call. Supplied as a
* single string with all arguments concatenated as seen by sandboxfs.
*/
private final String expectedArgs;
private FileSystem fileSystem;
private Path tmpDir;
// Initialized via createAndStartFakeSandboxfs and checked by verifyFakeSandboxfsExecution.
private Path capturedArgs;
private Path capturedRequests;
BaseRealSandboxfsProcessTest(String version, String expectedArgs) {
this.version = version;
this.expectedArgs = expectedArgs;
}
@Before
public void setUp() throws Exception {
fileSystem = new JavaIoFileSystem(DigestHashFunction.getDefaultUnchecked());
tmpDir = fileSystem.getPath(System.getenv("TEST_TMPDIR")).getRelative("test");
tmpDir.createDirectory();
}
@After
public void tearDown() throws Exception {
tmpDir.deleteTree();
}
/**
* Starts a sandboxfs instance using a fake binary that captures all received requests and yields
* mock responses.
*
* @param responses the mock responses to return to Bazel when issuing requests, broken down by
* line printed to stdout
* @return a sandboxfs process handler
* @throws IOException if the fake sandboxfs cannot be prepared or started
*/
SandboxfsProcess createAndStartFakeSandboxfs(List<String> responses) throws IOException {
capturedArgs = tmpDir.getRelative("captured-args");
capturedRequests = tmpDir.getRelative("captured-requests");
Path fakeSandboxfs = tmpDir.getRelative("fake-sandboxfs");
try (PrintWriter writer =
new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(fakeSandboxfs.getOutputStream(), StandardCharsets.UTF_8)))) {
writer.println("#! /bin/sh");
// Ignore requests for termination. The real sandboxfs process must be sent a SIGTERM to stop
// serving, but in our case we want to terminate cleanly after waiting for all input to be
// recorded.
writer.println("trap '' TERM;");
// Handle a --version invocation and exit quickly, which is a prerequisite for the mount call.
writer.println("if [ \"${*}\" = \"--version\" ]; then");
writer.println(" echo sandboxfs " + version + ";");
writer.println(" exit 0;");
writer.println("fi;");
// Capture all arguments for later inspection.
writer.println("for arg in \"${@}\"; do");
writer.println(" echo \"${arg}\" >>" + capturedArgs + ";");
writer.println("done;");
// Emit all responses first to avoid blocking any reads from the Java side.
for (String response : responses) {
writer.println("echo '" + response + "';");
}
// And finally capture all requests for later inspection.
writer.println("cat >" + capturedRequests + ";");
}
fakeSandboxfs.setExecutable(true);
return RealSandboxfsProcess.mount(
fakeSandboxfs.asFragment(),
fileSystem.getPath(FAKE_MOUNT_POINT),
tmpDir.getRelative("log"));
}
/**
* Checks that the given sandboxfs process behaved as expected.
*
* @param process the sandboxfs instance to stop and verify, which must have been previously
* started by {@link #createAndStartFakeSandboxfs}
* @param expectedRequests a flat string containing all requests given to sandboxfs (i.e. the raw
* contents of its stdin)
* @throws IOException if the fake sandboxfs instance cannot be stopped or if there is a problem
* reading the captured data
*/
void verifyFakeSandboxfsExecution(SandboxfsProcess process, String expectedRequests)
throws IOException {
process.destroy();
String args = FileSystemUtils.readContent(capturedArgs, StandardCharsets.UTF_8);
assertThat(args).isEqualTo(expectedArgs);
String requests = FileSystemUtils.readContent(capturedRequests, StandardCharsets.UTF_8);
assertThat(requests).isEqualTo(expectedRequests);
}
}