blob: ec7655ac36d53b70cdc4a3de297c620eb5640df9 [file] [log] [blame]
#!/bin/bash
#
# Copyright 2015 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
#
# Load test environment
# 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; }
source ${CURRENT_DIR}/../sandboxing_test_utils.sh \
|| { echo "sandboxing_test_utils.sh not found!" >&2; exit 1; }
function set_up {
add_to_bazelrc "build --spawn_strategy=sandboxed"
add_to_bazelrc "build --genrule_strategy=sandboxed"
}
function test_sandboxed_tooldir() {
mkdir -p examples/genrule
cat << 'EOF' > examples/genrule/BUILD
genrule(
name = "tooldir",
srcs = [],
outs = ["tooldir.txt"],
cmd = "ls -l external/bazel_tools/tools/genrule | tee $@ >&2; " +
"cat external/bazel_tools/tools/genrule/genrule-setup.sh >&2",
)
EOF
bazel build examples/genrule:tooldir &> $TEST_log \
|| fail "Hermetic genrule failed: examples/genrule:tooldir"
[ -f "bazel-genfiles/examples/genrule/tooldir.txt" ] \
|| fail "Genrule did not produce output: examples/genrule:works"
cat "bazel-genfiles/examples/genrule/tooldir.txt" > $TEST_log
expect_log "genrule-setup.sh"
}
function test_sandbox_block_filesystem() {
# The point of this test is to attempt to read something from the filesystem
# that is blocked via --sandbox_block_path= and thus shouldn't be accessible.
#
# /var/log is an arbitrary choice of directory that should exist on all
# Unix-like systems.
local block_path
case "$(uname -s)" in
Darwin)
# TODO(jmmv): sandbox-exec does not resolve symlinks, so attempting
# to block /var/log does not work. Unsure if we should make this work
# by resolving symlinks or documenting the expected behavior.
block_path=/private/var/log
;;
*)
block_path=/var/log
;;
esac
mkdir pkg
cat >pkg/BUILD <<EOF
genrule(
name = "breaks",
srcs = [ "a.txt" ],
outs = [ "breaks.txt" ],
cmd = "ls ${block_path} &> \$@",
)
EOF
touch pkg/a.txt
local output_file="bazel-genfiles/pkg/breaks.txt"
bazel build --sandbox_block_path="${block_path}" pkg:breaks \
&> $TEST_log \
&& fail "Non-hermetic genrule succeeded: examples/genrule:breaks" || true
[ -f "$output_file" ] ||
fail "Action did not produce output: $output_file"
cat "${output_file}" >$TEST_log
if [ "$(wc -l $output_file | awk '{print $1}')" -gt 1 ]; then
fail "Output contained more than one line: $output_file"
fi
grep -E "(Operation not permitted|Permission denied)" $output_file ||
fail "Output did not contain expected error message: $output_file"
}
# TODO(philwo) - this doesn't work on Ubuntu 14.04 due to "unshare" being too
# old and not understanding the --user flag.
function DISABLED_test_sandbox_different_nobody_uid() {
cat /etc/passwd | sed 's/\(^nobody:[^:]*:\)[0-9]*:[0-9]*/\15000:16000/g' > \
"${TEST_TMPDIR}/passwd"
unshare --user --mount --map-root-user -- bash - \
<< EOF || fail "Hermetic genrule with different UID for nobody failed" \
set -e
set -u
mount --bind ${TEST_TMPDIR}/passwd /etc/passwd
bazel build examples/genrule:works &> ${TEST_log}
EOF
}
# Tests that a pseudoterminal can be opened in linux when --sandbox_explicit_pseudoterminal is active
function test_can_enable_pseudoterminals() {
if [[ "$(uname -s)" != Linux ]]; then
echo "Skipping test: flag intended for linux systems"
return 0
fi
cat > test.py <<'EOF'
import pty
pty.openpty()
EOF
cat > BUILD <<'EOF'
py_test(
name = "test",
srcs = ["test.py"],
)
EOF
bazel test --sandbox_explicit_pseudoterminal --verbose_failures --sandbox_debug :test || fail "test did not pass"
}
function test_sandbox_debug() {
if [[ "$PLATFORM" == "darwin" ]]; then
# The process wrapper sandbox used in MacOS doesn't emit debug output.
return 0
fi
cat > BUILD <<'EOF'
genrule(
name = "broken",
outs = ["bla.txt"],
cmd = "exit 1",
)
EOF
bazel build --verbose_failures :broken &> $TEST_log \
&& fail "build should have failed" || true
expect_log "Use --sandbox_debug to see verbose messages from the sandbox and retain the sandbox build root for debugging"
expect_log "Executing genrule //:broken failed"
bazel build --verbose_failures --sandbox_debug :broken &> $TEST_log \
&& fail "build should have failed" || true
expect_log "Executing genrule //:broken failed"
expect_not_log "Use --sandbox_debug to see verbose messages from the sandbox and retain the sandbox build root for debugging"
expect_log "child exited normally with code 1"
bazel build --verbose_failures --sandbox_debug --experimental_use_hermetic_linux_sandbox :broken &> $TEST_log \
&& fail "build should have failed with hermetic sandbox" || true
expect_log "child exited normally with code 1"
bazel build --verbose_failures --sandbox_debug --incompatible_sandbox_hermetic_tmp :broken &> $TEST_log \
&& fail "build should have failed with hermetic sandbox /tmp" || true
expect_log "child exited normally with code 1"
}
function test_sandbox_expands_tree_artifacts_in_runfiles_tree() {
create_workspace_with_default_repos WORKSPACE
cat > def.bzl <<'EOF'
def _mkdata_impl(ctx):
out = ctx.actions.declare_directory(ctx.label.name + ".d")
script = "mkdir -p {out}; touch {out}/file; ln -s file {out}/link".format(out = out.path)
ctx.actions.run_shell(
outputs = [out],
command = script,
)
runfiles = ctx.runfiles(files = [out])
return [DefaultInfo(
files = depset([out]),
runfiles = runfiles,
)]
mkdata = rule(
_mkdata_impl,
)
EOF
cat > mkdata_test.sh <<'EOF'
#!/bin/bash
set -euo pipefail
test_dir="$1"
cd "$test_dir"
ls -l | cut -f1,9 -d' ' >&2
if [ ! -f file -o -L file ]; then
echo "'file' is not a regular file" >&2
exit 1
fi
EOF
chmod +x mkdata_test.sh
cat > BUILD <<'EOF'
load("//:def.bzl", "mkdata")
mkdata(name = "mkdata")
sh_test(
name = "mkdata_test",
srcs = ["mkdata_test.sh"],
args = ["$(location :mkdata)"],
data = [":mkdata"],
)
EOF
bazel test --test_output=streamed //:mkdata_test &>$TEST_log && fail "expected test to fail" || true
expect_log "'file' is not a regular file"
}
# Regression test for https://github.com/bazelbuild/bazel/issues/6262.
function test_create_tree_artifact_outputs() {
create_workspace_with_default_repos WORKSPACE
cat > def.bzl <<'EOF'
def _r(ctx):
d = ctx.actions.declare_directory("%s_dir" % ctx.label.name)
ctx.actions.run_shell(
outputs = [d],
command = "cd %s && pwd" % d.path,
)
return [DefaultInfo(files = depset([d]))]
r = rule(implementation = _r)
EOF
cat > BUILD <<'EOF'
load(":def.bzl", "r")
r(name = "a")
EOF
bazel build --test_output=streamed :a &>$TEST_log || fail "expected build to succeed"
}
# Regression test for https://github.com/bazelbuild/bazel/issues/20032.
function test_read_only_tree_artifact() {
create_workspace_with_default_repos WORKSPACE
cat > def.bzl <<'EOF'
def _r(ctx):
d = ctx.actions.declare_directory(ctx.label.name)
ctx.actions.run_shell(
outputs = [d],
command = "touch $1/file.txt && chmod -w $1",
arguments = [d.path],
)
return DefaultInfo(files = depset([d]))
r = rule(_r)
EOF
cat > BUILD <<'EOF'
load(":def.bzl", "r")
r(name = "a")
EOF
bazel build --test_output=streamed :a &>$TEST_log || fail "expected build to succeed"
}
function test_empty_tree_artifact_as_inputs() {
# Test that when an empty tree artifact is the input, an empty directory is
# created in the sandbox for action to read.
create_workspace_with_default_repos WORKSPACE
mkdir -p pkg
cat > pkg/def.bzl <<'EOF'
def _r(ctx):
empty_d = ctx.actions.declare_directory("%s/empty_dir" % ctx.label.name)
ctx.actions.run_shell(
outputs = [empty_d],
command = "mkdir -p %s" % empty_d.path,
)
f = ctx.actions.declare_file("%s/file" % ctx.label.name)
ctx.actions.run_shell(
inputs = [empty_d],
outputs = [f],
command = "touch %s && cd %s && pwd" % (f.path, empty_d.path),
)
return [DefaultInfo(files = depset([f]))]
r = rule(implementation = _r)
EOF
cat > pkg/BUILD <<'EOF'
load(":def.bzl", "r")
r(name = "a")
EOF
bazel build //pkg:a &>$TEST_log || fail "expected build to succeed"
}
function test_add_mount_pair_tmp_source() {
if [[ "$PLATFORM" == "darwin" ]]; then
# Tests Linux-specific functionality
return 0
fi
create_workspace_with_default_repos WORKSPACE
sed -i.bak '/sandbox_tmpfs_path/d' $TEST_TMPDIR/bazelrc
local mounted=$(mktemp -d "/tmp/bazel_mounted.XXXXXXXX")
trap "rm -fr $mounted" EXIT
echo GOOD > "$mounted/data.txt"
mkdir -p pkg
cat > pkg/BUILD <<EOF
genrule(
name = "gen",
outs = ["gen.txt"],
# Verify that /tmp is still hermetic.
cmd = """[ ! -e "${mounted}/data.txt" ] && cp /etc/data.txt \$@""",
)
EOF
# This assumes the existence of /etc on the host system
bazel build --sandbox_add_mount_pair="$mounted:/etc" //pkg:gen || fail "build failed"
assert_contains GOOD bazel-bin/pkg/gen.txt
}
function test_add_mount_pair_tmp_target() {
if [[ "$PLATFORM" == "darwin" ]]; then
# Tests Linux-specific functionality
return 0
fi
create_workspace_with_default_repos WORKSPACE
sed -i.bak '/sandbox_tmpfs_path/d' $TEST_TMPDIR/bazelrc
local source_dir=$(mktemp -d "/tmp/bazel_mounted.XXXXXXXX")
trap "rm -fr $source_dir" EXIT
echo BAD > "$source_dir/data.txt"
mkdir -p pkg
cat > pkg/BUILD <<EOF
genrule(
name = "gen",
outs = ["gen.txt"],
# Verify that /tmp is still hermetic.
cmd = """[ ! -e "${source_dir}/data.txt" ] && ls "$source_dir" > \$@""",
)
EOF
# This assumes the existence of /etc on the host system
bazel build --sandbox_add_mount_pair="/etc:$source_dir" //pkg:gen || fail "build failed"
assert_contains passwd bazel-bin/pkg/gen.txt
}
function test_add_mount_pair_tmp_target_and_source() {
if [[ "$PLATFORM" == "darwin" ]]; then
# Tests Linux-specific functionality
return 0
fi
create_workspace_with_default_repos WORKSPACE
sed -i.bak '/sandbox_tmpfs_path/d' $TEST_TMPDIR/bazelrc
local mounted=$(mktemp -d "/tmp/bazel_mounted.XXXXXXXX")
trap "rm -fr $mounted" EXIT
echo GOOD > "$mounted/data.txt"
local tmp_file=$(mktemp "/tmp/bazel_tmp.XXXXXXXX")
trap "rm $tmp_file" EXIT
echo BAD > "$tmp_file"
mkdir -p pkg
cat > pkg/BUILD <<EOF
genrule(
name = "gen",
outs = ["gen.txt"],
# Verify that /tmp is still hermetic.
cmd = """[ ! -e "${tmp_file}" ] && cp "$mounted/data.txt" \$@""",
)
EOF
bazel build --sandbox_add_mount_pair="$mounted" //pkg:gen || fail "build failed"
assert_contains GOOD bazel-bin/pkg/gen.txt
}
function test_symlink_with_output_base_under_tmp() {
if [[ "$PLATFORM" == "darwin" ]]; then
# Tests Linux-specific functionality
return 0
fi
create_workspace_with_default_repos WORKSPACE
sed -i.bak '/sandbox_tmpfs_path/d' $TEST_TMPDIR/bazelrc
mkdir -p pkg
cat > pkg/BUILD <<'EOF'
load(":r.bzl", "symlink_rule")
genrule(name="r1", srcs=[], outs=[":r1"], cmd="echo CONTENT > $@")
symlink_rule(name="r2", input=":r1")
genrule(name="r3", srcs=[":r2"], outs=[":r3"], cmd="cp $< $@")
EOF
cat > pkg/r.bzl <<'EOF'
def _symlink_impl(ctx):
output = ctx.actions.declare_file(ctx.label.name)
ctx.actions.symlink(output = output, target_file = ctx.file.input)
return [DefaultInfo(files = depset([output]))]
symlink_rule = rule(
implementation = _symlink_impl,
attrs = {"input": attr.label(allow_single_file=True)})
EOF
local tmp_output_base=$(mktemp -d "/tmp/bazel_output_base.XXXXXXXX")
trap "chmod -R u+w $tmp_output_base && rm -fr $tmp_output_base" EXIT
bazel --output_base="$tmp_output_base" build //pkg:r3
assert_contains CONTENT bazel-bin/pkg/r3
bazel --output_base="$tmp_output_base" shutdown
}
function test_symlink_to_directory_with_output_base_under_tmp() {
if [[ "$PLATFORM" == "darwin" ]]; then
# Tests Linux-specific functionality
return 0
fi
create_workspace_with_default_repos WORKSPACE
sed -i.bak '/sandbox_tmpfs_path/d' $TEST_TMPDIR/bazelrc
mkdir -p pkg
cat > pkg/BUILD <<'EOF'
load(":r.bzl", "symlink_rule", "tree_rule")
tree_rule(name="t1")
symlink_rule(name="t2", input=":t1")
genrule(name="t3", srcs=[":t2"], outs=[":t3"], cmd=";\n".join(
["cat $(location :t2)/{a/a,b/b} > $@"]))
EOF
cat > pkg/r.bzl <<'EOF'
def _symlink_impl(ctx):
output = ctx.actions.declare_directory(ctx.label.name)
ctx.actions.symlink(output = output, target_file = ctx.file.input)
return [DefaultInfo(files = depset([output]))]
symlink_rule = rule(
implementation = _symlink_impl,
attrs = {"input": attr.label(allow_single_file=True)})
def _tree_impl(ctx):
output = ctx.actions.declare_directory(ctx.label.name)
ctx.actions.run_shell(
outputs = [output],
command = "export TREE=%s && mkdir $TREE/a $TREE/b && echo -n A > $TREE/a/a && echo -n B > $TREE/b/b" % output.path)
return [DefaultInfo(files = depset([output]))]
tree_rule = rule(
implementation = _tree_impl,
attrs = {})
EOF
local tmp_output_base=$(mktemp -d "/tmp/bazel_output_base.XXXXXXXX")
trap "chmod -R u+w $tmp_output_base && rm -fr $tmp_output_base" EXIT
bazel --output_base="$tmp_output_base" build //pkg:t3
assert_contains AB bazel-bin/pkg/t3
bazel --output_base="$tmp_output_base" shutdown
}
# The test shouldn't fail if the environment doesn't support running it.
check_sandbox_allowed || exit 0
run_suite "sandbox"