blob: 7825fa80b96d61f53b666fdfb69bd9746a1fb9e5 [file] [log] [blame]
#!/bin/bash
#
# 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.
#
# Test sandboxing spawn strategy
#
set -euo pipefail
# Load the test setup defined in the parent directory
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; }
disable_bzlmod
function set_up() {
add_to_bazelrc "build --spawn_strategy=sandboxed"
add_to_bazelrc "build --genrule_strategy=sandboxed"
}
function tear_down() {
bazel clean --expunge
bazel shutdown
rm -rf pkg
}
function do_sandbox_base_wiped_only_on_startup_test {
local extra_args=( "${@}" )
mkdir pkg
cat >pkg/BUILD <<EOF
genrule(name = "pkg", outs = ["pkg.out"], cmd = "echo >\$@")
EOF
local output_base="$(bazel info output_base)"
do_build() {
bazel build --sandbox_debug "${extra_args[@]}" //pkg
}
do_build >"${TEST_log}" 2>&1 || fail "Expected build to succeed"
find "${output_base}" >>"${TEST_log}" 2>&1 || true
local sandbox_dir="$(echo "${output_base}/sandbox"/*-sandbox)"
[[ -d "${sandbox_dir}" ]] \
|| fail "${sandbox_dir} is missing; prematurely deleted?"
local garbage="${output_base}/sandbox/garbage"
mkdir -p "${garbage}/some/nested/contents"
do_build >"${TEST_log}" 2>&1 || fail "Expected build to succeed"
expect_not_log "Deleting stale sandbox"
[[ -d "${garbage}" ]] \
|| fail "Spurious contents deleted from sandbox base too early"
bazel shutdown
do_build >"${TEST_log}" 2>&1 || fail "Expected build to succeed"
expect_log "Deleting stale sandbox"
[[ ! -d "${garbage}" ]] \
|| fail "sandbox base was not cleaned on restart"
}
function test_sandbox_base_wiped_only_on_startup_with_sync_deletions() {
do_sandbox_base_wiped_only_on_startup_test \
--experimental_sandbox_async_tree_delete_idle_threads=0
}
function test_sandbox_base_wiped_only_on_startup_with_async_deletions() {
do_sandbox_base_wiped_only_on_startup_test \
--experimental_sandbox_async_tree_delete_idle_threads=HOST_CPUS
}
function do_succeed_when_executor_not_initialized_test() {
local extra_args=( "${@}" )
mkdir pkg
mkfifo pkg/BUILD
bazel build --nobuild "${@}" //pkg:all \
>"${TEST_log}" 2>&1 &
local pid="${!}"
echo "Waiting for Blaze to finish initializing all modules"
while ! grep "currently loading: pkg" "${TEST_log}"; do
sleep 1
done
echo "Interrupting Blaze before it gets to init the executor"
kill "${pid}"
echo "And now giving Blaze a chance to finalize all modules"
echo "unblock fifo" >pkg/BUILD
wait "${pid}" || true
expect_log "Build did NOT complete successfully"
# Disallow some common messages we might see during a crash.
expect_not_log "Internal error"
expect_not_log "stack trace"
expect_not_log "NullPointerException"
}
function test_succeed_when_executor_not_initialized_with_defaults() {
# Pass a no-op flag to the test to workaround a bug in macOS's default
# and ancient bash version which causes it to error out on an empty
# argument list when $@ is consumed and set -u is enabled.
local noop=( --nobuild )
do_succeed_when_executor_not_initialized_test "${noop[@]}"
}
function test_succeed_when_executor_not_initialized_with_async_deletions() {
do_succeed_when_executor_not_initialized_test \
--experimental_sandbox_async_tree_delete_idle_threads=auto
}
function test_sandbox_base_can_be_rm_rfed() {
mkdir pkg
cat >pkg/BUILD <<EOF
genrule(name = "pkg", outs = ["pkg.out"], cmd = "echo >\$@")
EOF
local output_base="$(bazel info output_base)"
do_build() {
bazel build --sandbox_debug //pkg
}
do_build >"${TEST_log}" 2>&1 || fail "Expected build to succeed"
find "${output_base}" >>"${TEST_log}" 2>&1 || true
local sandbox_base="${output_base}/sandbox"
[[ -d "${sandbox_base}" ]] \
|| fail "${sandbox_base} is missing; build did not use sandboxing?"
# Ensure the sandbox base does not contain protected files that would prevent
# a simple "rm -rf" from working under an unprivileged user.
rm -rf "${sandbox_base}" || fail "Cannot clean sandbox base"
# And now ensure Bazel reconstructs the sandbox base on a second build.
do_build >"${TEST_log}" 2>&1 || fail "Expected build to succeed"
}
function test_sandbox_not_used_with_legacy_fallback() {
mkdir pkg
cat >pkg/BUILD <<EOF
genrule(name = "pkg", outs = ["pkg.out"], cmd = "pwd; echo >\$@",
tags = ["no-sandbox"])
EOF
local output_base="$(bazel info output_base)"
local sandbox_base="${output_base}/sandbox"
rm -rf ${sandbox_base}
bazel build \
--incompatible_legacy_local_fallback //pkg \
>"${TEST_log}" 2>&1 || fail "Expected build to succeed"
expect_not_log "${output_base}.*/sandbox/"
expect_log "implicit fallback from sandbox to local"
}
function test_sandbox_local_not_used_without_legacy_fallback() {
mkdir pkg
cat >pkg/BUILD <<EOF
genrule(name = "pkg", outs = ["pkg.out"], cmd = "pwd; echo >\$@",
tags = ["no-sandbox"])
EOF
local output_base="$(bazel info output_base)"
local sandbox_base="${output_base}/sandbox"
rm -rf ${sandbox_base}
bazel build \
--noincompatible_legacy_local_fallback //pkg \
>"${TEST_log}" 2>&1 && fail "Expected build to fail" || true
# Still warning in this case even when the flag is flipped
expect_log "implicit fallback from sandbox to local"
}
function test_sandbox_local_used_with_proper_strategy() {
mkdir pkg
cat >pkg/BUILD <<EOF
genrule(name = "pkg", outs = ["pkg.out"], cmd = "pwd; echo >\$@",
tags = ["no-sandbox"])
EOF
local output_base="$(bazel info output_base)"
local sandbox_base="${output_base}/sandbox"
rm -rf ${sandbox_base}
bazel build --genrule_strategy=sandboxed,standalone \
--noincompatible_legacy_local_fallback //pkg \
>"${TEST_log}" 2>&1 || fail "Expected build to succeed"
expect_not_log "${output_base}.*/sandbox/"
expect_not_log "implicit fallback from sandbox to local"
}
function test_sandbox_base_top_is_removed() {
mkdir pkg
cat >pkg/BUILD <<EOF
genrule(name = "pkg", outs = ["pkg.out"], cmd = "echo >\$@")
EOF
local output_base="$(bazel info output_base)"
do_build() {
bazel build //pkg
}
do_build >"${TEST_log}" 2>&1 || fail "Expected build to succeed"
find "${output_base}" >>"${TEST_log}" 2>&1 || true
local sandbox_base="${output_base}/sandbox"
[[ ! -d "${sandbox_base}" ]] \
|| fail "${sandbox_base} left behind unnecessarily"
# Restart Bazel and check we don't print spurious "Deleting stale sandbox"
# warnings.
bazel shutdown
do_build >"${TEST_log}" 2>&1 || fail "Expected build to succeed"
expect_not_log "Deleting stale sandbox"
}
function test_sandbox_old_contents_not_reused_in_consecutive_builds() {
mkdir pkg
cat >pkg/BUILD <<EOF
genrule(
name = "pkg",
srcs = ["pkg.in"],
outs = ["pkg.out"],
cmd = "cp \$(location :pkg.in) \$@",
)
EOF
touch pkg/pkg.in
for i in $(seq 5); do
# Ensure that, even if we don't clean up the sandbox at all (with
# --sandbox_debug), consecutive builds don't step on each other by trying to
# reuse previous spawn identifiers.
bazel build --sandbox_debug //pkg \
>"${TEST_log}" 2>&1 || fail "Expected build to succeed"
echo foo >>pkg/pkg.in
done
}
function test_sandbox_hardening_with_cgroups_v1() {
if ! grep -E '^cgroup +[^ ]+ +cgroup +.*memory.*' /proc/mounts; then
echo "No cgroup v1 memory controller mounted, skipping test"
return 0
fi
memmount=$(grep -E '^cgroup +[^ ]+ +cgroup +.*memory.*' /proc/mounts | cut -d' ' -f2)
if ! grep -E '^[0-9]*:[^:]*memory[^:]*:' /proc/self/cgroup &>/dev/null; then
echo "Does not use cgroups v1, skipping test"
return 0
fi
memsubdir=$(grep -E '^[0-9]*:[^:]*memory[^:]*:' /proc/self/cgroup | cut -d: -f3)
memdir="$memmount$memsubdir"
if [[ ! -w "$memdir" ]]; then
echo "Cgroups v1 directory not writable, skipping test"
return 0
fi
mkdir pkg
cat >pkg/BUILD <<EOF
genrule(
name = "pkg",
outs = ["pkg.out"],
cmd = "pwd; hexdump -C -n 250000 < /dev/random | sort > /dev/null 2>\$@"
)
EOF
local genfiles_base="$(bazel info ${PRODUCT_NAME}-genfiles)"
bazel build --genrule_strategy=linux-sandbox \
--experimental_sandbox_memory_limit_mb=1000 //pkg \
>"${TEST_log}" 2>&1 || fail "Expected build to succeed"
rm -f ${genfiles_base}/pkg/pkg.out
bazel build --genrule_strategy=linux-sandbox \
--experimental_sandbox_memory_limit_mb=1 //pkg \
>"${TEST_log}" 2>&1 && fail "Expected build to fail" || true
}
function test_sandbox_hardening_with_cgroups_v2() {
if ! grep -E '^cgroup2 +[^ ]+ +cgroup2 ' /proc/mounts; then
echo "No cgroup2 mounted, skipping test"
return 0
fi
if ! grep -E '^0::' /proc/self/cgroup &>/dev/null; then
echo "Does not use cgroups v2, skipping test"
return 0
fi
if ! XDG_RUNTIME_DIR=/run/user/$( id -u ) systemd-run --user --scope true; then
echo "Not able to use systemd, skipping test"
return 0
fi
mkdir pkg
cat >pkg/BUILD <<EOF
genrule(
name = "pkg",
outs = ["pkg.out"],
cmd = "pwd; hexdump -C -n 250000 < /dev/random | sort > /dev/null 2>\$@"
)
EOF
local genfiles_base="$(bazel info ${PRODUCT_NAME}-genfiles)"
# Need to make sure the bazel server runs under systemd, too.
bazel shutdown
XDG_RUNTIME_DIR=/run/user/$( id -u ) systemd-run --user --scope \
bazel build --genrule_strategy=linux-sandbox \
--experimental_sandbox_memory_limit_mb=1000 //pkg \
>"${TEST_log}" 2>&1 || fail "Expected build to succeed"
rm -f ${genfiles_base}/pkg/pkg.out
bazel shutdown
XDG_RUNTIME_DIR=/run/user/$( id -u ) systemd-run --user --scope \
bazel build --genrule_strategy=linux-sandbox \
--experimental_sandbox_memory_limit_mb=1 //pkg \
>"${TEST_log}" 2>&1 && fail "Expected build to fail" || true
}
function test_sandboxed_genrule() {
mkdir -p examples/genrule
cat << 'EOF' > examples/genrule/a.txt
foo bar bz
EOF
cat << 'EOF' > examples/genrule/BUILD
genrule(
name = "works",
srcs = [ "a.txt" ],
outs = [ "works.txt" ],
cmd = "wc $(location :a.txt) > $@",
)
EOF
bazel build examples/genrule:works &> $TEST_log \
|| fail "Hermetic genrule failed: examples/genrule:works"
[ -f "bazel-genfiles/examples/genrule/works.txt" ] \
|| fail "Genrule did not produce output: examples/genrule:works"
}
function test_sandboxed_genrule_with_tools() {
mkdir -p examples/genrule
cat << 'EOF' > examples/genrule/BUILD
sh_binary(
name = "tool",
srcs = ["tool.sh"],
data = ["datafile"],
)
genrule(
name = "tools_work",
srcs = [],
outs = ["tools.txt"],
cmd = "$(location :tool) $@",
tools = [":tool"],
)
EOF
cat << 'EOF' >> examples/genrule/datafile
this is a datafile
EOF
# The workspace name is initialized in testenv.sh; use that var rather than
# hardcoding it here. The extra sed pass is so we can selectively expand that
# one var while keeping the rest of the heredoc literal.
cat | sed "s/{{WORKSPACE_NAME}}/$WORKSPACE_NAME/" >> examples/genrule/tool.sh << 'EOF'
#!/bin/sh
set -e
cp $(dirname $0)/tool.runfiles/{{WORKSPACE_NAME}}/examples/genrule/datafile $1
echo "Tools work!"
EOF
chmod +x examples/genrule/tool.sh
bazel build examples/genrule:tools_work &> $TEST_log \
|| fail "Hermetic genrule failed: examples/genrule:tools_work"
[ -f "bazel-genfiles/examples/genrule/tools.txt" ] \
|| fail "Genrule did not produce output: examples/genrule:tools_work"
}
# Test for #400: Linux sandboxing and relative symbolic links.
#
# let A = examples/genrule/symlinks/a/b/x.txt -> ../x.txt
# where examples/genrule/symlinks/a/b -> examples/genrule/symlinks/ok/sub
# thus the realpath of A is example/genrule/symlinks/ok/x.txt
# but if the code doesn't correctly resolve intermediate symlinks and instead
# uses string operations to handle ".." parts, it will arrive at:
# examples/genrule/symlinks/a/x.txt, which is wrong.
#
function test_sandbox_relative_symlink_in_inputs() {
mkdir -p examples/genrule
cat << 'EOF' > examples/genrule/BUILD
genrule(
name = "relative_symlinks",
srcs = [ "symlinks/a/b/x.txt" ],
outs = [ "relative_symlinks.txt" ],
cmd = "cat $(location :symlinks/a/b/x.txt) > $@",
)
EOF
mkdir -p examples/genrule/symlinks/{a,ok/sub}
echo OK > examples/genrule/symlinks/ok/x.txt
ln -s $PWD/examples/genrule/symlinks/ok/sub examples/genrule/symlinks/a/b
ln -s ../x.txt examples/genrule/symlinks/a/b/x.txt
bazel build examples/genrule:relative_symlinks &> $TEST_log \
|| fail "Hermetic genrule failed: examples/genrule:relative_symlinks"
[ -f "bazel-genfiles/examples/genrule/relative_symlinks.txt" ] \
|| fail "Genrule did not produce output: examples/genrule:relative_symlinks"
}
function test_sandbox_undeclared_deps() {
output_file="bazel-genfiles/examples/genrule/breaks1.txt"
mkdir -p examples/genrule
cat << 'EOF' > examples/genrule/a.txt
foo bar bz
EOF
cat << 'EOF' > examples/genrule/BUILD
genrule(
name = "breaks1",
srcs = [ "a.txt" ],
outs = [ "breaks1.txt" ],
cmd = "wc $(location :a.txt) `dirname $(location :a.txt)`/b.txt &> $@",
)
EOF
bazel build examples/genrule:breaks1 &> $TEST_log \
&& fail "Non-hermetic genrule succeeded: examples/genrule:breaks1" || true
[ -f "$output_file" ] ||
fail "Action did not produce output: $output_file"
if [ $(wc -l $output_file) -gt 1 ]; then
fail "Output contained more than one line: $output_file"
fi
fgrep "No such file or directory" $output_file ||
fail "Output did not contain expected error message: $output_file"
}
function test_sandbox_undeclared_deps_with_local() {
mkdir -p examples/genrule
echo "foo bar bz" > examples/genrule/a.txt
echo "apples oranges bananas" > examples/genrule/b.txt
cat << 'EOF' > examples/genrule/BUILD
genrule(
name = "breaks1_works_with_local",
srcs = [ "a.txt" ],
outs = [ "breaks1_works_with_local.txt" ],
cmd = "wc $(location :a.txt) `dirname $(location :a.txt)`/b.txt > $@",
local = 1,
)
EOF
bazel build --incompatible_legacy_local_fallback \
examples/genrule:breaks1_works_with_local &> $TEST_log \
|| fail "Non-hermetic genrule failed even though local=1: examples/genrule:breaks1_works_with_local"
[ -f "bazel-genfiles/examples/genrule/breaks1_works_with_local.txt" ] \
|| fail "Genrule did not produce output: examples/genrule:breaks1_works_with_local"
}
function test_sandbox_undeclared_deps_with_local_tag() {
mkdir -p examples/genrule
echo "foo bar bz" > examples/genrule/a.txt
echo "apples oranges bananas" > examples/genrule/b.txt
cat << 'EOF' > examples/genrule/BUILD
genrule(
name = "breaks1_works_with_local_tag",
srcs = [ "a.txt" ],
outs = [ "breaks1_works_with_local_tag.txt" ],
cmd = "wc $(location :a.txt) `dirname $(location :a.txt)`/b.txt > $@",
tags = [ "local" ],
)
EOF
bazel build --incompatible_legacy_local_fallback \
examples/genrule:breaks1_works_with_local_tag &> $TEST_log \
|| fail "Non-hermetic genrule failed even though tags=['local']: examples/genrule:breaks1_works_with_local_tag"
[ -f "bazel-genfiles/examples/genrule/breaks1_works_with_local_tag.txt" ] \
|| fail "Genrule did not produce output: examples/genrule:breaks1_works_with_local_tag"
}
function test_sandbox_undeclared_deps_with_local_tag_no_fallback() {
mkdir -p examples/genrule
echo "foo bar bz" > examples/genrule/a.txt
cat << 'EOF' > examples/genrule/BUILD
genrule(
name = "breaks1_works_with_local_tag",
srcs = [ "a.txt" ],
outs = [ "breaks1_works_with_local_tag.txt" ],
cmd = "wc $(location :a.txt) `dirname $(location :a.txt)`/b.txt > $@",
tags = [ "local" ],
)
EOF
bazel build examples/genrule:breaks1_works_with_local_tag &> $TEST_log \
&& fail "Non-hermetic genrule suucceeded even though tags=['local']: examples/genrule:breaks1_works_with_local_tag" \
|| true
}
function write_starlark_breaks1() {
cat << 'EOF' >> examples/genrule/starlark.bzl
def _starlark_breaks1_impl(ctx):
print(ctx.outputs.output.path)
ctx.actions.run_shell(
inputs = [ ctx.file.input ],
outputs = [ ctx.outputs.output ],
command = "wc %s `dirname %s`/b.txt &> %s" % (ctx.file.input.path,
ctx.file.input.path,
ctx.outputs.output.path),
execution_requirements = { tag: '' for tag in ctx.attr.action_tags },
)
starlark_breaks1 = rule(
_starlark_breaks1_impl,
attrs = {
"input": attr.label(mandatory=True, allow_single_file=True),
"output": attr.output(mandatory=True),
"action_tags": attr.string_list(),
},
)
EOF
}
function test_sandbox_undeclared_deps_starlark() {
mkdir -p examples/genrule
echo "foo bar bz" > examples/genrule/a.txt
echo "apples oranges bananas" > examples/genrule/b.txt
cat << 'EOF' > examples/genrule/BUILD
load('//examples/genrule:starlark.bzl', 'starlark_breaks1')
starlark_breaks1(
name = "starlark_breaks1",
input = "a.txt",
output = "starlark_breaks1.txt",
)
EOF
write_starlark_breaks1
output_file="bazel-bin/examples/genrule/starlark_breaks1.txt"
bazel build examples/genrule:starlark_breaks1 &> $TEST_log \
&& fail "Non-hermetic genrule succeeded: examples/genrule:starlark_breaks1" || true
[ -f "$output_file" ] ||
fail "Action did not produce output: $output_file"
if [ $(wc -l $output_file) -gt 1 ]; then
fail "Output contained more than one line: $output_file"
fi
fgrep "No such file or directory" $output_file ||
fail "Output did not contain expected error message: $output_file"
}
function test_sandbox_undeclared_deps_starlark_with_local_tag() {
mkdir -p examples/genrule
echo "foo bar bz" > examples/genrule/a.txt
echo "apples oranges bananas" > examples/genrule/b.txt
cat << 'EOF' > examples/genrule/BUILD
load('//examples/genrule:starlark.bzl', 'starlark_breaks1')
starlark_breaks1(
name = "starlark_breaks1",
input = "a.txt",
output = "starlark_breaks1.txt",
)
starlark_breaks1(
name = "starlark_breaks1_works_with_local_tag",
input = "a.txt",
output = "starlark_breaks1_works_with_local_tag.txt",
action_tags = [ "local" ],
)
EOF
write_starlark_breaks1
bazel build --incompatible_legacy_local_fallback \
examples/genrule:starlark_breaks1_works_with_local_tag &> $TEST_log \
|| fail "Non-hermetic genrule failed even though tags=['local']: examples/genrule:starlark_breaks1_works_with_local_tag"
[ -f "bazel-bin/examples/genrule/starlark_breaks1_works_with_local_tag.txt" ] \
|| fail "Action did not produce output: examples/genrule:starlark_breaks1_works_with_local_tag"
}
function test_sandbox_cyclic_symlink_in_inputs() {
mkdir -p examples/genrule
# Create cyclic symbolic links to check whether the strategy catches that.
ln -sf cyclic2 examples/genrule/cyclic1
ln -sf cyclic1 examples/genrule/cyclic2
cat << 'EOF' > examples/genrule/BUILD
genrule(
name = "breaks3",
srcs = [ "cyclic1", "cyclic2" ],
outs = [ "breaks3.txt" ],
cmd = "wc $(location :cyclic1) > $@",
)
EOF
bazel build examples/genrule:breaks3 &> $TEST_log \
&& fail "Genrule with cyclic symlinks succeeded: examples/genrule:breaks3" || true
[ ! -f "bazel-genfiles/examples/genrule/breaks3.txt" ] || {
output=$(cat "bazel-genfiles/examples/genrule/breaks3.txt")
fail "Genrule with cyclic symlinks breaks3 succeeded with following output: $output"
}
}
function test_requires_root() {
if [[ "$(uname -s)" != Linux ]]; then
echo "Skipping test: fake usernames not supported in this system" 1>&2
return 0
fi
cat > test.sh <<'EOF'
#!/bin/sh
([ $(id -u) = "0" ] && [ $(id -g) = "0" ]) || exit 1
EOF
chmod +x test.sh
cat > BUILD <<'EOF'
sh_test(
name = "test",
srcs = ["test.sh"],
tags = ["requires-fakeroot"],
)
EOF
bazel test --test_output=errors :test || fail "test did not pass"
bazel test --nocache_test_results --sandbox_fake_username --test_output=errors :test || fail "test did not pass"
}
# Tests that /proc/self == /proc/$$. This should always be true unless the PID namespace is active without /proc being remounted correctly.
function test_sandbox_proc_self() {
if [[ ! -d /proc/self ]]; then
echo "Skipping tests: requires /proc" 1>&2
return 0
fi
mkdir -p examples/genrule
cat << 'EOF' > examples/genrule/BUILD
genrule(
name = "check_proc_works",
outs = [ "check_proc_works.txt" ],
cmd = "sh -c 'cd /proc/self && echo $$$$ && exec cat stat | sed \"s/\\([^ ]*\\) .*/\\1/g\"' > $@",
)
EOF
bazel build examples/genrule:check_proc_works >& $TEST_log || fail "build should have succeeded"
(
# Catch the head and tail commands failing.
set -e
if [[ "$(head -n1 "bazel-genfiles/examples/genrule/check_proc_works.txt")" \
!= "$(tail -n1 "bazel-genfiles/examples/genrule/check_proc_works.txt")" ]] ; then
fail "Reading PID from /proc/self/stat should have worked, instead have these: $(cat "bazel-genfiles/examples/genrule/check_proc_works.txt")"
fi
)
}
function test_succeeding_action_with_ioexception_while_copying_outputs_throws_correct_exception() {
cat > BUILD <<'EOF'
genrule(
name = "test",
outs = ["readonlydir/output.txt"],
cmd = "touch $(location readonlydir/output.txt); chmod 0 $(location readonlydir/output.txt); chmod 0500 `dirname $(location readonlydir/output.txt)`",
)
EOF
bazel build :test &> $TEST_log \
&& fail "build should have failed" || true
# This is the generic "we caught an IOException" log message used by the
# SandboxedStrategy. We don't want to see this in this case, because we have
# special handling that prints a better error message and then lets the
# sandbox code throw the actual ExecException.
expect_not_log "I/O error during sandboxed execution"
# There was no ExecException during sandboxed execution, because the action
# returned an exit code of 0.
expect_not_log "Executing genrule //:test failed: linux-sandbox failed: error executing command"
# This is the error message telling us that some output artifacts couldn't be copied.
expect_log "Could not move output artifacts from sandboxed execution"
# The build fails, because the action didn't generate its output artifact.
expect_log "ERROR:.*Executing genrule //:test failed"
}
function test_failing_action_with_ioexception_while_copying_outputs_throws_correct_exception() {
cat > BUILD <<'EOF'
genrule(
name = "test",
outs = ["readonlydir/output.txt"],
cmd = "touch $(location readonlydir/output.txt); chmod 0 $(location readonlydir/output.txt); chmod 0500 `dirname $(location readonlydir/output.txt)`; exit 1",
)
EOF
bazel build :test &> $TEST_log \
&& fail "build should have failed" || true
# This is the generic "we caught an IOException" log message used by the
# SandboxedStrategy. We don't want to see this in this case, because we have
# special handling that prints a better error message and then lets the
# sandbox code throw the actual ExecException.
expect_not_log "I/O error during sandboxed execution"
# This is the error message printed by the EventHandler telling us that some
# output artifacts couldn't be copied.
expect_log "Could not move output artifacts from sandboxed execution"
# This is the UserExecException telling us that the build failed.
expect_log "Executing genrule //:test failed:"
}
function test_read_non_hermetic_tmp {
sed -i.bak '/sandbox_tmpfs_path/d' "$bazelrc"
temp_dir=$(mktemp -d /tmp/test.XXXXXX)
trap 'rm -rf ${temp_dir}' EXIT
mkdir -p pkg
cat > pkg/BUILD <<'EOF'
sh_test(
name = "tmp_test",
srcs = ["tmp_test.sh"],
)
EOF
cat > pkg/tmp_test.sh <<EOF
[[ -f "${temp_dir}/file" ]]
EOF
chmod +x pkg/tmp_test.sh
touch "${temp_dir}/file"
bazel test //pkg:tmp_test \
--test_output=errors &>$TEST_log || fail "Expected test to pass"
}
function test_read_hermetic_tmp {
if [[ "$(uname -s)" != Linux ]]; then
echo "Skipping test: --incompatible_sandbox_hermetic_tmp is only supported in Linux" 1>&2
return 0
fi
temp_dir=$(mktemp -d /tmp/test.XXXXXX)
trap 'rm -rf ${temp_dir}' EXIT
mkdir -p pkg
cat > pkg/BUILD <<'EOF'
sh_test(
name = "tmp_test",
srcs = ["tmp_test.sh"],
)
EOF
cat > pkg/tmp_test.sh <<EOF
[[ ! -f "${temp_dir}/file" ]]
EOF
chmod +x pkg/tmp_test.sh
touch "${temp_dir}/file"
bazel test //pkg:tmp_test --incompatible_sandbox_hermetic_tmp \
--test_output=errors &>$TEST_log || fail "Expected test to pass"
}
function test_read_hermetic_tmp_user_override {
if [[ "$(uname -s)" != Linux ]]; then
echo "Skipping test: --incompatible_sandbox_hermetic_tmp is only supported in Linux" 1>&2
return 0
fi
sed -i.bak '/sandbox_tmpfs_path/d' "$bazelrc"
temp_dir=$(mktemp -d /tmp/test.XXXXXX)
trap 'rm -rf ${temp_dir}' EXIT
mkdir -p pkg
cat > pkg/BUILD <<'EOF'
sh_test(
name = "tmp_test",
srcs = ["tmp_test.sh"],
)
EOF
cat > pkg/tmp_test.sh <<EOF
[[ -f "${temp_dir}/file" ]]
EOF
chmod +x pkg/tmp_test.sh
touch "${temp_dir}/file"
bazel test //pkg:tmp_test --incompatible_sandbox_hermetic_tmp --sandbox_add_mount_pair=/tmp \
--test_output=errors &>$TEST_log || fail "Expected test to pass"
}
function test_write_non_hermetic_tmp {
sed -i.bak '/sandbox_tmpfs_path/d' "$bazelrc"
temp_dir=$(mktemp -d /tmp/test.XXXXXX)
trap 'rm -rf ${temp_dir}' EXIT
mkdir -p pkg
cat > pkg/BUILD <<'EOF'
sh_test(
name = "tmp_test",
srcs = ["tmp_test.sh"],
)
EOF
cat > pkg/tmp_test.sh <<EOF
touch "${temp_dir}/file"
EOF
chmod +x pkg/tmp_test.sh
bazel test //pkg:tmp_test \
--test_output=errors &>$TEST_log || fail "Expected test to pass"
[[ -f "${temp_dir}/file" ]] || fail "Expected ${temp_dir}/file to exist"
}
function test_write_hermetic_tmp {
if [[ "$(uname -s)" != Linux ]]; then
echo "Skipping test: --incompatible_sandbox_hermetic_tmp is only supported in Linux" 1>&2
return 0
fi
temp_dir=$(mktemp -d /tmp/test.XXXXXX)
trap 'rm -rf ${temp_dir}' EXIT
mkdir -p pkg
cat > pkg/BUILD <<'EOF'
sh_test(
name = "tmp_test",
srcs = ["tmp_test.sh"],
)
EOF
cat > pkg/tmp_test.sh <<EOF
mkdir -p "${temp_dir}"
touch "${temp_dir}/file"
EOF
chmod +x pkg/tmp_test.sh
bazel test //pkg:tmp_test --incompatible_sandbox_hermetic_tmp \
--test_output=errors &>$TEST_log || fail "Expected test to pass"
[[ ! -f "${temp_dir}" ]] || fail "Expected ${temp_dir} to not exit"
}
function test_write_hermetic_tmp_user_override {
if [[ "$(uname -s)" != Linux ]]; then
echo "Skipping test: --incompatible_sandbox_hermetic_tmp is only supported in Linux" 1>&2
return 0
fi
sed -i.bak '/sandbox_tmpfs_path/d' "$bazelrc"
temp_dir=$(mktemp -d /tmp/test.XXXXXX)
trap 'rm -rf ${temp_dir}' EXIT
mkdir -p pkg
cat > pkg/BUILD <<'EOF'
sh_test(
name = "tmp_test",
srcs = ["tmp_test.sh"],
)
EOF
cat > pkg/tmp_test.sh <<EOF
touch "${temp_dir}/file"
EOF
chmod +x pkg/tmp_test.sh
bazel test //pkg:tmp_test --incompatible_sandbox_hermetic_tmp --sandbox_add_mount_pair=/tmp \
--test_output=errors &>$TEST_log || fail "Expected test to pass"
[[ -f "${temp_dir}/file" ]] || fail "Expected ${temp_dir}/file to exist"
}
function test_sandbox_reuse_stashes_sandbox() {
mkdir pkg
cat >pkg/BUILD <<'EOF'
genrule(
name = "a",
srcs = [ "a.txt" ],
outs = [ "aout.txt" ],
cmd = "wc $(location :a.txt) > $@",
)
genrule(
name = "b",
srcs = [ "b.txt" ],
outs = [ "bout.txt" ],
cmd = "wc $(location :b.txt) > $@",
)
EOF
echo A > pkg/a.txt
echo BB > pkg/b.txt
local output_base="$(bazel info output_base)"
local execroot="$(bazel info execution_root)"
local execroot_reldir="${execroot#$output_base}"
bazel build --reuse_sandbox_directories //pkg:a >"${TEST_log}" 2>&1 \
|| fail "Expected build to succeed"
local sandbox_stash="${output_base}/sandbox_stash"
[[ -d "${sandbox_stash}" ]] \
|| fail "${sandbox_stash} not present"
[[ -d "${sandbox_stash}/_NoMnemonic_/3" ]] \
|| fail "${sandbox_stash} did not stash anything"
[[ -L "${sandbox_stash}/_NoMnemonic_/3/$execroot_reldir/pkg/a.txt" ]] \
|| fail "${sandbox_stash} did not have a link to a.txt"
bazel build --reuse_sandbox_directories //pkg:b >"${TEST_log}" 2>&1 \
|| fail "Expected build to succeed"
ls -R "${sandbox_stash}/_NoMnemonic_/"
[[ ! -L "${sandbox_stash}/_NoMnemonic_/6/$execroot_reldir/pkg/a.txt" ]] \
|| fail "${sandbox_stash} should no longer have a link to a.txt"
[[ -L "${sandbox_stash}/_NoMnemonic_/6/$execroot_reldir/pkg/b.txt" ]] \
|| fail "${sandbox_stash} should now have a link to b.txt"
bazel clean
[[ ! -d "${sandbox_stash}" ]] \
|| fail "${sandbox_stash} present after clean"
bazel build --reuse_sandbox_directories //pkg:a >"${TEST_log}" 2>&1 \
|| fail "Expected build to succeed"
}
function test_sandbox_reuse_stashes_sandbox_with_changing_hermetic_tmp() {
mkdir pkg
cat >pkg/BUILD <<'EOF'
genrule(
name = "a",
srcs = [ "a.txt" ],
outs = [ "aout.txt" ],
cmd = "wc $(location :a.txt) > $@",
)
genrule(
name = "b",
srcs = [ "b.txt" ],
outs = [ "bout.txt" ],
cmd = "wc $(location :b.txt) > $@",
)
EOF
echo A > pkg/a.txt
echo BB > pkg/b.txt
local output_base="$(bazel info output_base)"
local execroot="$(bazel info execution_root)"
local execroot_reldir="${execroot#$output_base}"
bazel build --reuse_sandbox_directories \
//pkg:a >"${TEST_log}" 2>&1 \
|| fail "Expected build to succeed"
local sandbox_stash="${output_base}/sandbox_stash"
[[ -d "${sandbox_stash}" ]] \
|| fail "${sandbox_stash} not present"
[[ -d "${sandbox_stash}/_NoMnemonic_/3" ]] \
|| fail "${sandbox_stash} did not stash anything"
[[ -L "${sandbox_stash}/_NoMnemonic_/3/$execroot_reldir/pkg/a.txt" ]] \
|| fail "${sandbox_stash} did not have a link to a.txt"
bazel build --reuse_sandbox_directories --incompatible_sandbox_hermetic_tmp \
//pkg:b >"${TEST_log}" 2>&1 \
|| fail "Expected build to succeed"
ls -R "${sandbox_stash}/_NoMnemonic_/"
[[ ! -L "${sandbox_stash}/_NoMnemonic_/6/$execroot_reldir/pkg/a.txt" ]] \
|| fail "${sandbox_stash} should no longer have a link to a.txt"
[[ -L "${sandbox_stash}/_NoMnemonic_/6/$execroot_reldir/pkg/b.txt" ]] \
|| fail "${sandbox_stash} should now have a link to b.txt"
bazel clean
[[ ! -d "${sandbox_stash}" ]] \
|| fail "${sandbox_stash} present after clean"
bazel build --reuse_sandbox_directories --incompatible_sandbox_hermetic_tmp \
//pkg:a >"${TEST_log}" 2>&1 \
|| fail "Expected build to succeed"
}
function test_sandbox_reuse_clean() {
mkdir pkg
cat >pkg/BUILD <<'EOF'
genrule(
name = "a",
srcs = [ "a.txt" ],
outs = [ "aout.txt" ],
cmd = "wc $(location :a.txt) > $@",
)
EOF
echo A > pkg/a.txt
local output_base="$(bazel info output_base)"
bazel build --reuse_sandbox_directories //pkg:a >"${TEST_log}" 2>&1 \
|| fail "Expected build to succeed"
local sandbox_stash="${output_base}/sandbox_stash"
[[ -d "${sandbox_stash}" ]] \
|| fail "${sandbox_stash} not present"
[[ -d "${sandbox_stash}/_NoMnemonic_/3" ]] \
|| fail "${sandbox_stash} did not stash anything"
bazel clean --reuse_sandbox_directories
[[ ! -d "${sandbox_stash}" ]] \
|| fail "${sandbox_stash} present after clean"
bazel build --experimental_sandbox_async_tree_delete_idle_threads=2 \
--reuse_sandbox_directories //pkg:a >"${TEST_log}" 2>&1 \
|| fail "Expected build to succeed"
[[ -d "${sandbox_stash}/_NoMnemonic_/6" ]] \
|| fail "${sandbox_stash} did not stash anything"
bazel clean
[[ ! -d "${sandbox_stash}" ]] \
|| fail "${sandbox_stash} present after non-reusing clean"
}
run_suite "sandboxing"