| #!/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; } | 
 |  | 
 | 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 | 
 |   local WORKSPACE_NAME=$TEST_WORKSPACE | 
 |   # 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 copy outputs from sandbox:.*readonlydir/output.txt (Permission denied)" | 
 |  | 
 |   # 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 copy outputs from sandbox:.*readonlydir/output.txt (Permission denied)" | 
 |  | 
 |   # This is the UserExecException telling us that the build failed. | 
 |   expect_log "ERROR:.*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_hermetic_tmp_with_tmpdir_under_tmp() { | 
 |   mkdir pkg | 
 |   cat >pkg/BUILD <<EOF | 
 | genrule(name = "pkg", outs = ["pkg.out"], cmd = "echo >\$@") | 
 | EOF | 
 |   mkdir /tmp/my_tmpdir | 
 |   TMPDIR=/tmp/my_tmpdir \ | 
 |     bazel build --incompatible_sandbox_hermetic_tmp \ | 
 |      //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 WORKSPACE_NAME=$TEST_WORKSPACE | 
 |   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" |