blob: 12e8fc47f21ef05f053597eb6a18801eab7a3531 [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"
# Enabled in testenv.sh.tmpl, but not in Bazel by default.
sed -i.bak '/sandbox_tmpfs_path/d' "$bazelrc"
}
function tear_down() {
bazel clean --expunge
bazel shutdown
rm -rf pkg
}
function test_sandbox_base_keeps_dirs_with_sandbox_debug {
mkdir pkg
cat >pkg/BUILD <<EOF
genrule(name = "pkg", outs = ["pkg.out"], cmd = "echo >\$@")
EOF
local output_base="$(bazel info output_base)"
bazel build --sandbox_debug //pkg >"${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?"
bazel shutdown
}
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)"
bazel build //pkg >"${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}/sandbox_stash" ]] \
|| fail "${sandbox_base}/sandbox_stash directory not present"
[[ -d "${sandbox_base}/_moved_trash_dir" ]] \
|| fail "${sandbox_base}/_moved_trash_dir directory not present"
[[ $(ls -1 ${sandbox_base} | wc -l | tr -d ' ') == 2 ]] \
|| fail "${sandbox_base} contains stale dirs"
bazel shutdown
[[ -d "${sandbox_base}/sandbox_stash" ]] \
|| fail "${sandbox_base}/_moved_trash_dir directory not present"
bazel build
[[ ! -d "${sandbox_base}/sandbox_stash" ]] \
|| fail "${sandbox_base}/_moved_trash_dir directory not present"
[[ $(ls -1 ${sandbox_base} | wc -l | tr -d ' ') == 1 ]] \
|| fail "${sandbox_base} contains stale dirs"
}
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 {
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 \
--sandbox_add_mount_pair=/tmp \
--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
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 {
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 \
--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_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
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/sandbox_stash"
[[ -d "${sandbox_stash}" ]] \
|| fail "${sandbox_stash} not present"
[[ -d "${sandbox_stash}/Genrule/3" ]] \
|| fail "${sandbox_stash} did not stash anything"
[[ -L "${sandbox_stash}/Genrule/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}/Genrule/"
[[ ! -L "${sandbox_stash}/Genrule/6/$execroot_reldir/pkg/a.txt" ]] \
|| fail "${sandbox_stash} should no longer have a link to a.txt"
[[ -L "${sandbox_stash}/Genrule/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_and_clean() {
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 //pkg:a >"${TEST_log}" 2>&1 \
|| fail "Expected build to succeed"
local sandbox_stash="${output_base}/sandbox/sandbox_stash"
[[ -d "${sandbox_stash}" ]] \
|| fail "${sandbox_stash} not present"
[[ -d "${sandbox_stash}/Genrule/3" ]] \
|| fail "${sandbox_stash} did not stash anything"
[[ -L "${sandbox_stash}/Genrule/3/$execroot_reldir/pkg/a.txt" ]] \
|| fail "${sandbox_stash} did not have a link to a.txt"
bazel build //pkg:b >"${TEST_log}" 2>&1 \
|| fail "Expected build to succeed"
ls -R "${sandbox_stash}/Genrule/"
[[ ! -L "${sandbox_stash}/Genrule/6/$execroot_reldir/pkg/a.txt" ]] \
|| fail "${sandbox_stash} should no longer have a link to a.txt"
[[ -L "${sandbox_stash}/Genrule/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 //pkg:a >"${TEST_log}" 2>&1 \
|| fail "Expected build to succeed"
bazel shutdown
}
function test_hermetic_tmp_with_tmp_sandbox_base() {
mkdir pkg
cat >pkg/BUILD <<EOF
genrule(name = "pkg", outs = ["pkg.out"], cmd = "echo >\$@")
EOF
mkdir /tmp/sandbox_base
bazel build --incompatible_sandbox_hermetic_tmp \
--sandbox_base=/tmp/sandbox_base //pkg >"${TEST_log}" 2>&1 \
|| fail "Expected build to succeed"
}
function test_runfiles_from_tests_get_reused_and_tmp_clean() {
do_test_runfiles_from_tests_get_reused_and_tmp_clean \
"--noexperimental_inmemory_sandbox_stashes"
}
function test_runfiles_from_tests_get_reused_and_tmp_clean_in_mem_stashes() {
do_test_runfiles_from_tests_get_reused_and_tmp_clean \
"--experimental_inmemory_sandbox_stashes"
}
function do_test_runfiles_from_tests_get_reused_and_tmp_clean() {
local -r in_memory_stashes="$1"
mkdir pkg
mkdir pkg/b
touch pkg/file1.txt
touch pkg/file2.txt
touch pkg/file3.txt
cat >pkg/reusing_test.bzl <<'EOF'
def _reused_runfiles_test_impl(ctx):
output = ctx.actions.declare_file(ctx.label.name + ".sh")
runfiles = ctx.runfiles(files = ctx.files.files)
runfiles = runfiles.merge(runfiles)
test_code = """
#!/bin/bash
dir_inode_number=$(ls -di $TEST_SRCDIR | cut -f1 -d" ")
echo "The directory inode is $dir_inode_number"
file_inode_number=$(ls -i $TEST_SRCDIR/_main/pkg/file1.txt | cut -f1 -d" ")
echo "The file inode is $file_inode_number"
find -L $TEST_SRCDIR -type f
"""
ctx.actions.run_shell(
outputs = [output],
mnemonic = "myexample",
command = """
output_path={}
echo '{}' > $output_path
chmod 777 $output_path
""".format(output.path, test_code)
)
return [DefaultInfo(executable = output, runfiles = runfiles)]
reused_runfiles_test = rule(
implementation = _reused_runfiles_test_impl,
test = True,
attrs = {
"files" : attr.label_list(allow_files=True),
}
)
EOF
cat >pkg/BUILD <<'EOF'
load(":reusing_test.bzl", "reused_runfiles_test")
exports_files([
"file1.txt",
"file2.txt",
"file3.txt",
])
reused_runfiles_test(
name = "a",
files = [
"file1.txt",
"file2.txt",
],
)
EOF
cat >pkg/b/BUILD <<'EOF'
load("//pkg:reusing_test.bzl", "reused_runfiles_test")
reused_runfiles_test(
name = "b",
files = [
"//pkg:file1.txt",
"//pkg:file3.txt",
],
)
EOF
test_output="reuse_test_output.txt"
local out_directory
if is_bazel; then
bazel coverage --test_output=streamed \
"$in_memory_stashes" --experimental_split_coverage_postprocessing=1 \
--experimental_fetch_all_coverage_outputs //pkg:a > ${test_output} \
|| fail "Expected build to succeed"
out_directory="bazel-out"
else
bazel test "$in_memory_stashes" --test_output=streamed \
//pkg:a > ${test_output} || fail "Expected build to succeed"
out_directory="blaze-out"
fi
grep -q "file1.txt" ${test_output} || fail "Missing file1.txt"
grep -q "file2.txt" ${test_output} || fail "Missing file2.txt"
dir_inode_a=$(awk '/The directory inode is/ {print $5}' ${test_output})
file_inode_a=$(awk '/The file inode is/ {print $5}' ${test_output})
local output_base="$(bazel info output_base)"
local stashed_test_dir="${output_base}/sandbox/sandbox_stash/TestRunner/6/execroot/$WORKSPACE_NAME"
touch $(find "$stashed_test_dir/$out_directory/" -name a.sh.runfiles -type d)"/$WORKSPACE_NAME/pkg/file4.txt"
[[ -d "${stashed_test_dir}/$out_directory" ]] \
|| fail "${stashed_test_dir}/$out_directory directory not present"
[[ -d "${stashed_test_dir}/_tmp" ]] \
&& fail "${stashed_test_dir}/_tmp directory is present"
if is_bazel; then
bazel coverage --test_output=streamed //pkg/b:b \
"$in_memory_stashes" --experimental_split_coverage_postprocessing=1 \
--experimental_fetch_all_coverage_outputs > ${test_output} \
|| fail "Expected build to succeed"
else
bazel test "$in_memory_stashes" --test_output=streamed \
//pkg/b:b > ${test_output} || fail "Expected build to succeed"
fi
grep -q "file1.txt" ${test_output} || fail "Missing file1.txt"
grep -q "file3.txt" ${test_output} || fail "Missing file3.txt"
grep -q "file2.txt" ${test_output} && fail "Present file2.txt"
if [[ "$in_memory_stashes" =~ ^"--no" ]]; then
grep -q "file4.txt" ${test_output} \
&& fail "Present file4.txt which was added artificially and should" \
" have been cleaned up with disk clean-up stashes"
else
grep -q "file4.txt" ${test_output} \
|| fail "Missing file4.txt which was added artificially and shouldn't" \
" have been cleaned up with in-memory stashes"
fi
dir_inode_b=$(awk '/The directory inode is/ {print $5}' ${test_output})
file_inode_b=$(awk '/The file inode is/ {print $5}' ${test_output})
[[ ${dir_inode_a} == ${dir_inode_b} ]] \
|| fail "Test //pkg:b didn't reuse runfiles directory"
[[ ${file_inode_a} == ${file_inode_b} ]] \
|| fail "Test //pkg:b didn't reuse runfiles file"
}
function test_changed_async_deleter_filesystem() {
if [ ! -d /dev/shm ]; then
return
fi
mkdir pkg
cat >pkg/BUILD <<'EOF'
cc_library(
name = "a",
srcs = [ "a.cc" ],
)
EOF
touch pkg/a.cc
bazel build //pkg:a \
|| fail "Expected build to succeed"
bazel clean
bazel build --sandbox_base=/dev/shm //pkg:a \
|| fail "Expected build to succeed"
}
function test_bad_state_linux_sandboxing() {
mkdir pkg
# This test is meant to catch a bad state being left over by an unfinished
# linux-sandboxing initialization. Since it's difficult to replicate the same
# conditions that end up in that state, this instead runs a null build
# where linux-sandboxing is unsupported by passing -1 grace seconds.
# Then we create inaccessibleHelperFile/Dir (the bad state) artificially and
# run a null build again making sure there is no crash.
bazel build --local_termination_grace_seconds=-1 \
|| fail "Expected build to succeed"
file_path="$(bazel info output_base)/sandbox/inaccessibleHelperFile"
dir_path="$(bazel info output_base)/sandbox/inaccessibleHelperDir"
touch $file_path
mkdir $dir_path
chmod 000 $file_path
chmod 000 $dir_path
bazel build --local_termination_grace_seconds=-1 \
|| fail "Expected build to succeed"
}
function is_bazel() {
[ $TEST_WORKSPACE == "_main" ]
}
run_suite "sandboxing"