blob: 3458210a9280153eeb75cbf358c975e9aa15000b [file] [log] [blame] [edit]
#!/bin/bash
#
# Copyright 2017 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.
#
# execution_phase_tests.sh: miscellaneous integration tests of Bazel for
# behaviors that affect the execution phase.
#
# --- begin runfiles.bash initialization ---
set -euo pipefail
if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
if [[ -f "$TEST_SRCDIR/MANIFEST" ]]; then
export RUNFILES_MANIFEST_FILE="$TEST_SRCDIR/MANIFEST"
elif [[ -f "$0.runfiles/MANIFEST" ]]; then
export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
elif [[ -f "$TEST_SRCDIR/io_bazel/tools/bash/runfiles/runfiles.bash" ]]; then
export RUNFILES_DIR="$TEST_SRCDIR"
fi
fi
if [[ -f "${RUNFILES_DIR:-/dev/null}/io_bazel/tools/bash/runfiles/runfiles.bash" ]]; then
source "${RUNFILES_DIR}/io_bazel/tools/bash/runfiles/runfiles.bash"
elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
source "$(grep -m1 "^io_bazel/tools/bash/runfiles/runfiles.bash " \
"$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
else
echo >&2 "ERROR: cannot find //third_party/bazel/tools/bash/runfiles:runfiles.bash"
exit 1
fi
# --- end runfiles.bash initialization ---
source "$(rlocation "io_bazel/src/test/shell/integration_test_setup.sh")" \
|| { echo "integration_test_setup.sh not found!" >&2; exit 1; }
case "$(uname -s | tr [:upper:] [:lower:])" in
msys*|mingw*|cygwin*)
declare -r is_windows=true
;;
*)
declare -r is_windows=false
;;
esac
if "$is_windows"; then
export MSYS_NO_PATHCONV=1
export MSYS2_ARG_CONV_EXCL="*"
fi
#### HELPER FUNCTIONS ##################################################
if ! type try_with_timeout >&/dev/null; then
# Bazel's testenv.sh defines try_with_timeout but the Google-internal version
# uses a different testenv.sh.
function try_with_timeout() { $* ; }
fi
function set_up() {
cd ${WORKSPACE_DIR}
}
function tear_down() {
try_with_timeout bazel shutdown
}
# Looks for the last occurrence of a log message in a log file.
#
# This assumes the use of java.util.logging.SimpleFormatter, which splits
# the context of a log entry and the log message itself in two lines.
#
# TODO(jmmv): We should have functionality in unittest.bash to check the
# contents of the Bazel's client log in a way that allows us to test for
# only the messages printed by the last-run command.
function assert_last_log() {
local context="${1}"; shift
local message="${1}"; shift
local log="${1}"; shift
local fail_message="${1}"; shift
if ! grep "${context}" "${log}" | grep -q "${message}" ; then
cat "${log}" >>"${TEST_log}" # Help debugging when we fail.
fail "${fail_message}"
fi
}
# Asserts that the last dump of cache stats in the log matches the given
# metric and value.
function assert_cache_stats() {
local metric="${1}"; shift
local exp_value="${1}"; shift
local java_log
java_log="$(bazel info server_log 2>/dev/null)" || fail "bazel info failed"
grep "CacheFileDigestsModule" "${java_log}" >"${TEST_log}"
[ -s "${TEST_log}" ] || fail "Could not find cache stats in log"
expect_log "${metric}=${exp_value}"
}
#### TESTS #############################################################
function test_cache_computed_file_digests_behavior() {
local -r pkg="${FUNCNAME}"
mkdir -p "$pkg" || fail "could not create \"$pkg\""
mkdir -p $pkg/package || fail "mkdir failed"
cat >$pkg/package/BUILD <<EOF
genrule(
name = "foo",
srcs = ["foo.in"],
outs = ["foo.out"],
cmd = "cat \$(location foo.in) >\$@",
)
genrule(
name = "bar",
srcs = ["bar.in", ":foo"],
outs = ["bar.out"],
cmd = "cat \$(location bar.in) \$(location :foo) >\$@",
)
EOF
touch $pkg/package/foo.in $pkg/package/bar.in
bazel build $pkg/package:bar >>"${TEST_log}" 2>&1 || fail "Should build"
# We cannot make any robust assertions on the first run because of implicit
# dependencies we have no control about.
# Rebuilding without changes should yield hits for everything. Run this
# multiple times to ensure the reported statistics are not accumulated.
for run in 1 2 3; do
bazel build $pkg/package:bar >>"${TEST_log}" 2>&1 || fail "Should build"
assert_cache_stats "hit count" 1 # stable-status.txt
assert_cache_stats "miss count" 1 # volatile-status.txt
done
# Throw away the in-memory Skyframe state by flipping a flag. We expect hits
# for the previous outputs, which are used to query the action cache.
bazel build --nocheck_visibility $pkg/package:bar >>"${TEST_log}" 2>&1 \
|| fail "Should build"
assert_cache_stats "hit count" 3 # stable-status.txt foo.out bar.out
assert_cache_stats "miss count" 1 # volatile-status.txt
# Change the size of the cache and retry the same build. We expect no hits
# because resizing the cache invalidates all of its contents.
bazel build --cache_computed_file_digests=100 $pkg/package:bar \
>>"${TEST_log}" 2>&1 || fail "Should build"
assert_cache_stats "hit count" 0
assert_cache_stats "miss count" 4 # {stable,volatile}-status* {foo,bar}.out
# Run a non-build command, which should not interfere with the cache.
bazel info >>"${TEST_log}" 2>&1 || fail "Should run"
assert_cache_stats "hit count" 0 # Same as previous command; unmodified.
assert_cache_stats "miss count" 4 # Same as previous command; unmodified.
# Rebuild without changes one more time with the new size of the cache to
# ensure the cache is not reset across runs with the flag override.
bazel build --nocheck_visibility --cache_computed_file_digests=100 \
$pkg/package:bar >>"${TEST_log}" 2>&1 || fail "Should build"
assert_cache_stats "hit count" 3 # stable-status.txt foo.out bar.out
assert_cache_stats "miss count" 1 # volatile-status.txt
}
function DISABLED_test_cache_computed_file_digests_uncaught_changes() {
# Does not work on Windows, https://github.com/bazelbuild/bazel/issues/6098
local timestamp=201703151112.13 # Fixed timestamp to mark our file with.
mkdir -p package || fail "mkdir failed"
cat >package/BUILD <<EOF
genrule(
name = "foo",
srcs = ["foo.in"],
outs = ["foo.out"],
cmd = "echo foo >\$@ && touch -t ${timestamp} \$@",
)
EOF
touch package/foo.in
# Build the target once to populate the action cache, then update a file to a
# known timestamp, and rebuild the target to recompute our internal digests
# cache.
bazel build package:foo >>"${TEST_log}" 2>&1 || fail "Should build"
local output_file="$(find bazel-out/ -name foo.out)"
touch -t "${timestamp}" "${output_file}"
bazel build package:foo >>"${TEST_log}" 2>&1 || fail "Should build"
# Modify the content of a file in the action cache in a way that bypasses the
# logic to cache file digests: replace the file's content with new contents of
# the same length; avoid modifying the inode number; and respect the previous
# timestamp.
function log_metadata_for_test_debugging() {
echo "${1} ${2} modifying it in place:"
stat "${output_file}"
if which md5sum >/dev/null; then # macOS and possibly others.
md5sum "${output_file}"
elif which md5 >/dev/null; then # Linux and possibly others.
md5 "${output_file}"
fi
}
log_metadata_for_test_debugging "${output_file}" before >>"${TEST_log}"
chmod +w "${output_file}"
echo bar >"${output_file}" # Contents must match length in genrule.
chmod -w "${output_file}"
touch -t "${timestamp}" "${output_file}"
log_metadata_for_test_debugging "${output_file}" after >>"${TEST_log}"
# Assert all hits after discarding the in-memory Skyframe state while
# modifying the on-disk state in a way that bypasses the digests cache
# functionality.
bazel build --nocheck_visibility package:foo >>"${TEST_log}" 2>&1 \
|| fail "Should build"
[[ "$(cat "${output_file}")" == bar ]] \
|| fail "External change to action cache misdetected"
# For completeness, make the changes to the same output file visibile and
# ensure Blaze notices them. This is to check that we actually modified the
# right output file above.
touch "${output_file}"
bazel build package:foo >>"${TEST_log}" 2>&1 || fail "Should build"
[[ "$(cat "${output_file}")" == foo ]] \
|| fail "External change to action cache not detected"
}
function test_cache_computed_file_digests_ui() {
local -r pkg="${FUNCNAME}"
mkdir -p "$pkg" || fail "could not create \"$pkg\""
mkdir -p $pkg/package || fail "mkdir failed"
echo "cc_library(name = 'foo', srcs = ['foo.cc'])" >$pkg/package/BUILD
echo "int foo(void) { return 0; }" >$pkg/package/foo.cc
local java_log
java_log="$(bazel info server_log 2>/dev/null)" || fail "bazel info failed"
bazel build $pkg/package:foo >>"${TEST_log}" 2>&1 || fail "Should build"
assert_last_log "CacheFileDigestsModule" "Cache stats" "${java_log}" \
"Digests cache not enabled by default"
bazel build --cache_computed_file_digests=0 $pkg/package:foo >>"${TEST_log}" 2>&1 \
|| fail "Should build"
assert_last_log "CacheFileDigestsModule" "Disabled cache" "${java_log}" \
"Digests cache not disabled as requested"
bazel build $pkg/package:foo >>"${TEST_log}" 2>&1 || fail "Should build"
assert_last_log "CacheFileDigestsModule" "Cache stats" "${java_log}" \
"Digests cache not reenabled"
}
function test_analysis_warning_cached() {
mkdir -p "foo" "bar" || fail "Could not create directories"
cat > foo/BUILD <<'EOF' || fail "foo/BUILD"
cc_library(
name = 'foo',
deprecation = 'foo warning',
srcs = ['foo.cc'],
visibility = ['//visibility:public']
)
EOF
cat > bar/BUILD <<'EOF' || fail "bar/BUILD"
cc_library(name = 'bar', srcs = ['bar.cc'], deps = ['//foo:foo'])
EOF
touch foo/foo.cc bar/bar.cc || fail "Couldn't touch"
bazel build --nobuild //bar:bar >& "$TEST_log" || fail "Expected success"
expect_log "WARNING: .*: foo warning"
bazel build //bar:bar >& "$TEST_log" || fail "Expected success"
expect_log "WARNING: .*: foo warning"
echo "// comment" >> bar/bar.cc || fail "Couldn't change contents"
bazel build //bar:bar >& "$TEST_log" || fail "Expected success"
expect_log "WARNING: .*: foo warning"
}
function test_max_open_file_descriptors() {
echo "nfiles: hard $(ulimit -H -n), soft $(ulimit -S -n)"
local exp_nfiles="$(ulimit -H -n)"
if [[ "$(uname -s)" == Darwin ]]; then
local maxfiles="$(/usr/sbin/sysctl -n kern.maxfilesperproc)"
if [[ "${exp_nfiles}" == "unlimited" || "${exp_nfiles}" -gt "${maxfiles}" ]]
then
exp_nfiles="${maxfiles}"
fi
elif "${is_windows}"; then
# We do not implement the resources unlimiting feature on Windows at
# the moment... so just expect the soft limit to remain unchanged.
exp_nfiles="$(ulimit -S -n)"
fi
echo "Will expect soft nfiles to be ${exp_nfiles}"
mkdir -p "pkg" || fail "Could not create directory"
cat > pkg/BUILD <<'EOF' || fail "Could not create test file"
genrule(
name = "nfiles",
outs = ["nfiles-soft"],
cmd = "mkdir -p pkg && ulimit -S -n >$(location nfiles-soft)",
)
EOF
bazel build //pkg:nfiles >& "${TEST_log}" || fail "Expected success"
local soft="$(cat bazel-genfiles/pkg/nfiles-soft)"
# Make sure that the soft limit was raised to the expected hard value.
# Our code doesn't touch the hard limit (even in the case "unlimited" case
# handled above) and that's OK: if we were able to set the soft limit to a
# high value, the hard limit must already be the same or higher.
assert_equals "${exp_nfiles}" "${soft}"
}
function test_action_symlink_output_change_detected() {
mkdir -p a
WORKSPACE="$PWD"
echo "same" > same1
echo "same" > same2
echo "different" > different
cat > a/BUILD <<EOF
genrule(
name = "a",
srcs = [],
outs = ["ao"],
local = 1,
cmd = "touch $WORKSPACE/arun && ln -s $WORKSPACE/same1 \$@",
)
genrule(
name = "b",
srcs = ["ao"],
outs = ["bo"],
local = 1,
cmd = "touch $WORKSPACE/brun && touch \$@",
)
EOF
bazel build //a:b || fail "build failed"
[[ -r brun ]] || fail "b was not run"
rm -f bazel-genfiles/a/ao arun brun
bazel build //a:b || fail "build failed"
[[ -r arun ]] || fail "a was not run"
[[ -r brun ]] && fail "b was run"
rm -fr bazel-genfiles/a/ao arun brun
ln -s "$WORKSPACE/same2" bazel-genfiles/a/ao
bazel build //a:b || fail "build failed"
# Only the contents of target of the symlink should matter, where the symlink
# points to should not
[[ -r arun ]] && fail "a was run"
[[ -r brun ]] && fail "b was run"
rm -fr bazel-genfiles/a/ao arun brun
ln -s "$WORKSPACE/different" bazel-genfiles/a/ao
bazel build //a:b || fail "build failed"
# If the symlink points to a file with different contents, the action should
# be re-run
[[ -r arun ]] || fail "a was not run"
[[ -r brun ]] && fail "b was run"
: # So the exit code of the test is not inferred from that of "-r" above
}
# Trivial test to verify that the various flags that specify resource limits
# accept the same syntax.
function test_resource_flags_syntax() {
local threads=HOST_CPUS*0.8
local ram=HOST_RAM*0.8
# TODO(jmmv): The IncludeScanningModule is present in Bazel but is not
# part of the build, so this flag, which we should test here, isn't
# available: --experimental_include_scanning_parallelism="${threads}"
bazel build --nobuild \
--experimental_fsvc_threads="${threads}" \
--experimental_sandbox_async_tree_delete_idle_threads="${threads}" \
--jobs="${threads}" \
--legacy_globbing_threads="${threads}" \
--loading_phase_threads="${threads}" \
--local_cpu_resources="${threads}" \
--local_ram_resources="${ram}" \
--local_test_jobs="${threads}" \
|| fail "Empty build failed"
}
function test_track_directory_crossing_package() {
mkdir -p foo/dir/subdir
touch foo/dir/subdir/BUILD
echo "filegroup(name = 'foo', srcs = ['dir'])" > foo/BUILD
bazel --host_jvm_args=-DBAZEL_TRACK_SOURCE_DIRECTORIES=1 build //foo \
>& "$TEST_log" || fail "Expected success"
expect_log "WARNING: Directory artifact foo/dir crosses package boundary into"
}
run_suite "Integration tests of ${PRODUCT_NAME} using the execution phase."