| #!/bin/bash | |
| # Copyright 2018 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. | |
| CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| source "${CURRENT_DIR}/../integration_test_setup.sh" \ | |
| || { echo "integration_test_setup.sh not found!" >&2; exit 1; } | |
| # Bazel build arguments to disable the use of the sandbox. We have tests below | |
| # that configure a fake sandboxfs and they would fail if they were to use it. | |
| DISABLE_SANDBOX_ARGS=( | |
| --genrule_strategy=local | |
| --spawn_strategy=local | |
| ) | |
| # Creates a fake sandboxfs process in "path" that logs interactions with it in | |
| # the given "log" file and reports the given "version". | |
| function create_fake_sandboxfs() { | |
| local path="${1}"; shift | |
| local log="${1}"; shift | |
| local version="${1}"; shift | |
| cat >"${path}" <<EOF | |
| #!/bin/sh | |
| rm -f "${log}" | |
| trap 'echo "Terminated" >>"${log}"' EXIT TERM | |
| echo "PID: \${$}" >>"${log}" | |
| echo "ARGS: \${*}" >>"${log}" | |
| if [ "\${1}" = --version ]; then | |
| echo "${version}" | |
| exit 0 | |
| fi | |
| while read line; do | |
| echo "Received: \${line}" >>"${log}" | |
| if [ -z "\${line}" ]; then | |
| echo "Done" | |
| fi | |
| done | |
| EOF | |
| chmod +x "${path}" | |
| } | |
| function create_hello_package() { | |
| mkdir -p hello | |
| cat >hello/BUILD <<EOF | |
| cc_binary(name = "hello", srcs = ["hello.cc"]) | |
| EOF | |
| cat >hello/hello.cc <<EOF | |
| #include <stdio.h> | |
| int main(void) { printf("Hello, world!\n"); return 0; } | |
| EOF | |
| } | |
| function test_default_sandboxfs_from_path() { | |
| mkdir -p fake-tools | |
| create_fake_sandboxfs fake-tools/sandboxfs "$(pwd)/log" "sandboxfs 0.0.0" | |
| PATH="$(pwd)/fake-tools:${PATH}"; export PATH | |
| create_hello_package | |
| # This test relies on a PATH change that is only recognized when the server | |
| # first starts up, so ensure there are no Bazel servers left behind. | |
| # | |
| # TODO(philwo): This is awful. The testing infrastructure should ensure | |
| # that tests cannot pollute each other's state, but at the moment that's not | |
| # the case. | |
| bazel shutdown | |
| bazel build \ | |
| "${DISABLE_SANDBOX_ARGS[@]}" \ | |
| --experimental_use_sandboxfs \ | |
| //hello >"${TEST_log}" 2>&1 || fail "Build should have succeeded" | |
| # Dump fake sandboxfs' log for debugging. | |
| sed -e 's,^,SANDBOXFS: ,' log >>"${TEST_log}" | |
| grep -q "Terminated" log \ | |
| || fail "sandboxfs process was not terminated (not executed?)" | |
| } | |
| function test_explicit_sandboxfs_not_found() { | |
| create_hello_package | |
| bazel build \ | |
| --experimental_use_sandboxfs \ | |
| --experimental_sandboxfs_path="/non-existent/sandboxfs" \ | |
| //hello >"${TEST_log}" 2>&1 && fail "Build succeeded but should have failed" | |
| expect_log "Failed to get sandboxfs version.*/non-existent/sandboxfs" | |
| } | |
| function test_explicit_sandboxfs_is_invalid() { | |
| mkdir -p fake-tools | |
| create_hello_package | |
| do_build() { | |
| bazel build \ | |
| "${DISABLE_SANDBOX_ARGS[@]}" \ | |
| --experimental_use_sandboxfs=yes \ | |
| --experimental_sandboxfs_path="$(pwd)/fake-tools/sandboxfs" \ | |
| //hello | |
| } | |
| # Try with a binary that prints an invalid version string. | |
| create_fake_sandboxfs fake-tools/sandboxfs "$(pwd)/log" "not-sandboxfs 0.0.0" | |
| do_build >"${TEST_log}" 2>&1 && fail "Build should have failed" | |
| # Now try with a valid binary just to ensure our test scenario works. | |
| create_fake_sandboxfs fake-tools/sandboxfs "$(pwd)/log" "sandboxfs 0.0.0" | |
| do_build >"${TEST_log}" 2>&1 || fail "Build should have succeeded" | |
| sed -e 's,^,SANDBOXFS: ,' log >>"${TEST_log}" | |
| grep -q "Terminated" log \ | |
| || fail "sandboxfs process was not terminated (not executed?)" | |
| } | |
| function test_use_sandboxfs_if_present() { | |
| # This test relies on a PATH change that is only recognized when the server | |
| # first starts up, so ensure there are no Bazel servers left behind. | |
| # | |
| # TODO(philwo): This is awful. The testing infrastructure should ensure | |
| # that tests cannot pollute each other's state, but at the moment that's not | |
| # the case. | |
| bazel shutdown | |
| mkdir -p fake-tools | |
| PATH="$(pwd)/fake-tools:${PATH}"; export PATH | |
| create_hello_package | |
| do_build() { | |
| bazel build \ | |
| "${DISABLE_SANDBOX_ARGS[@]}" \ | |
| --experimental_use_sandboxfs=auto \ | |
| //hello | |
| } | |
| # Try with sandboxfs not in the PATH. | |
| do_build >"${TEST_log}" 2>&1 || fail "Build should have succeeded" | |
| [[ ! -f log ]] || echo "sandboxfs was used but should not have" | |
| # Now try with sandboxfs in the PATH to ensure our test scenario works. | |
| create_fake_sandboxfs fake-tools/sandboxfs "$(pwd)/log" "sandboxfs 0.0.0" | |
| do_build >"${TEST_log}" 2>&1 || fail "Build should have succeeded" | |
| sed -e 's,^,SANDBOXFS: ,' log >>"${TEST_log}" | |
| grep -q "Terminated" log \ | |
| || fail "sandboxfs process was not terminated (not executed?)" | |
| } | |
| # Runs a build of the given target using a fake sandboxfs that captures its | |
| # activity and dumps it to the given log file. | |
| function build_with_fake_sandboxfs() { | |
| local log="${1}"; shift | |
| create_fake_sandboxfs fake-sandboxfs.sh "${log}" "sandboxfs 0.0.0" | |
| local ret=0 | |
| bazel build \ | |
| "${DISABLE_SANDBOX_ARGS[@]}" \ | |
| --experimental_use_sandboxfs \ | |
| --experimental_sandboxfs_path="$(pwd)/fake-sandboxfs.sh" \ | |
| "${@}" >"${TEST_log}" 2>&1 || ret="${?}" | |
| # Dump fake sandboxfs' log for debugging. | |
| sed -e 's,^,SANDBOXFS: ,' log >>"${TEST_log}" | |
| return "${ret}" | |
| } | |
| function test_mount_unmount() { | |
| create_hello_package | |
| local output_base="$(bazel info output_base)" | |
| local sandbox_base="${output_base}/sandbox" | |
| build_with_fake_sandboxfs "$(pwd)/log" //hello \ | |
| || fail "Build should have succeeded" | |
| grep -q "ARGS: .*${sandbox_base}/sandboxfs" log \ | |
| || fail "Cannot find expected mount point in sandboxfs mount call" | |
| grep -q "Terminated" log \ | |
| || fail "sandboxfs process was not terminated (not unmounted?)" | |
| } | |
| function test_debug_lifecycle() { | |
| create_hello_package | |
| function sandboxfs_pid() { | |
| case "$(uname)" in | |
| Darwin) | |
| # We cannot use ps to look for the sandbox process because this is | |
| # not allowed when running with macOS's App Sandboxing. | |
| grep -q "Terminated" log && return | |
| grep "^PID:" log | awk '{print $2}' | |
| ;; | |
| *) | |
| # We could use the same approach we follow on Darwin to look for the | |
| # PID of the subprocess, but it's better if we look at the real | |
| # process table if we are able to. | |
| ps ax | grep [f]ake-sandboxfs | awk '{print $1}' | |
| ;; | |
| esac | |
| } | |
| # Want sandboxfs to be left mounted after a build with debugging on. | |
| build_with_fake_sandboxfs "$(pwd)/log" --sandbox_debug //hello | |
| grep -q "ARGS:" log || fail "sandboxfs was not run" | |
| grep -q "Terminated" log \ | |
| && fail "sandboxfs process was terminated but should not have been" | |
| local pid1="$(sandboxfs_pid)" | |
| [[ -n "${pid1}" ]] || fail "sandboxfs process not found in process table" | |
| # Want sandboxfs to be restarted if the previous build had debugging on. | |
| build_with_fake_sandboxfs "$(pwd)/log" --sandbox_debug //hello | |
| local pid2="$(sandboxfs_pid)" | |
| [[ -n "${pid2}" ]] || fail "sandboxfs process not found in process table" | |
| [[ "${pid1}" -ne "${pid2}" ]] || fail "sandboxfs was not restarted" | |
| # Want build to finish successfully and to clear the mount point. | |
| build_with_fake_sandboxfs "$(pwd)/log" --nosandbox_debug //hello | |
| local pid3="$(sandboxfs_pid)" | |
| [[ -z "${pid3}" ]] || fail "sandboxfs was not terminated" | |
| } | |
| function test_always_unmounted_on_exit() { | |
| create_hello_package | |
| # Want sandboxfs to be left mounted after a build with debugging on. | |
| build_with_fake_sandboxfs "$(pwd)/log" --sandbox_debug //hello | |
| grep -q "ARGS:" log || fail "sandboxfs was not run" | |
| grep -q "Terminated" log \ | |
| && fail "sandboxfs process was terminated but should not have been" | |
| # Want Bazel to unmount the sandboxfs instance on exit no matter what. | |
| # | |
| # Note that we do not even tell Bazel where the sandboxfs binary lives | |
| # but we expect changes to the log of the currently-running sandboxfs | |
| # binary. This is intentional to verify that the already-mounted | |
| # instance is the one shut down. | |
| bazel shutdown | |
| grep -q "Terminated" log \ | |
| || fail "sandboxfs process was not terminated but should have been" | |
| } | |
| run_suite "sandboxfs-based sandboxing tests" |