blob: c5d62cafbeee616bd001bc8b15004ed61f636979 [file] [log] [blame]
#!/usr/bin/env bash
#
# Copyright 2021 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.
# --- begin runfiles.bash initialization v3 ---
# Copy-pasted from the Bazel Bash runfiles library v3.
set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash
source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
source "$0.runfiles/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
{ echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
# --- end runfiles.bash initialization v3 ---
source "$(rlocation "io_bazel/src/test/shell/integration_test_setup.sh")" \
|| { echo "integration_test_setup.sh not found!" >&2; exit 1; }
source "$(rlocation "io_bazel/src/test/shell/bazel/coverage_helpers.sh")" \
|| { echo "coverage_helpers.sh not found!" >&2; exit 1; }
COVERAGE_GENERATOR_WORKSPACE_FILE="$1"; shift
if [[ "${COVERAGE_GENERATOR_WORKSPACE_FILE}" != "released" ]]; then
COVERAGE_GENERATOR_DIR="$(dirname "$(rlocation $COVERAGE_GENERATOR_WORKSPACE_FILE)")"
add_to_bazelrc "build --override_repository=bazel_tools+remote_coverage_tools_extension+remote_coverage_tools=${COVERAGE_GENERATOR_DIR}"
fi
if is_windows; then
starlark_is_windows=True
else
starlark_is_windows=False
fi
# Returns the path of the code coverage report that was generated by Bazel by
# looking at the current $TEST_log. The method fails if TEST_log does not
# contain any coverage report for a passed test.
function get_coverage_file_path_from_test_log() {
local ending_part="$(sed -n -e '/PASSED/,$p' "$TEST_log")"
local coverage_file_path=$(grep -Eo "([A-Z]:)?/[/a-zA-Z0-9+\.\_\-]+\.dat$" <<< "$ending_part")
[[ -e "$coverage_file_path" ]] || fail "Coverage output file does not exist!"
echo "$coverage_file_path"
}
function set_up() {
touch WORKSPACE
add_to_bazelrc "common --test_output=errors"
}
function test_starlark_rule_without_lcov_merger() {
cat <<EOF > rules.bzl
UNIX_TEST = """
#!/usr/bin/env bash
if [[ ! -r extra ]]; then
echo "extra file not found" >&2
exit 1
fi
if [[ -z \$COVERAGE ]]; then
echo "COVERAGE environment variable not set, coverage not run."
exit 1
fi
"""
WINDOWS_TEST = """
@echo off
findstr extra %RUNFILES_MANIFEST_FILE:/=\\\\% >nul
if errorlevel 1 (
echo extra file not found >&2
exit /b 1
)
if "%COVERAGE%"=="" (
echo COVERAGE environment variable not set, coverage not run.
exit /b 1
)
"""
def _impl(ctx):
output = ctx.actions.declare_file(ctx.attr.name + ".bat")
ctx.actions.write(output, WINDOWS_TEST if ${starlark_is_windows} else UNIX_TEST, is_executable = True)
extra_file = ctx.actions.declare_file("extra")
ctx.actions.write(extra_file, "extra")
return [DefaultInfo(executable=output, runfiles=ctx.runfiles(files=[extra_file]))]
custom_test = rule(
implementation = _impl,
test = True,
)
EOF
cat <<EOF > BUILD
load(":rules.bzl", "custom_test")
custom_test(name = "foo_test")
EOF
bazel coverage //:foo_test --combined_report=lcov > $TEST_log \
|| fail "Coverage run failed but should have succeeded."
}
function test_starlark_rule_without_lcov_merger_failing_test() {
cat <<EOF > rules.bzl
UNIX_FAILING_TEST = "exit 1"
WINDOWS_FAILING_TEST = "@echo off\nexit /b 1"
def _impl(ctx):
output = ctx.actions.declare_file(ctx.attr.name + ".bat")
ctx.actions.write(output, WINDOWS_FAILING_TEST if ${starlark_is_windows} else UNIX_FAILING_TEST, is_executable = True)
return [
DefaultInfo(
executable = output,
)
]
custom_test = rule(
implementation = _impl,
test = True,
)
EOF
cat <<EOF > BUILD
load(":rules.bzl", "custom_test")
custom_test(name = "foo_test")
EOF
if bazel coverage //:foo_test > $TEST_log; then
fail "Coverage run succeeded but should have failed."
fi
}
function test_starlark_rule_with_custom_lcov_merger() {
add_rules_shell "MODULE.bazel"
cat <<EOF > lcov_merger.sh
for var in "\$@"
do
if [[ "\$var" == "--output_file="* ]]; then
path="\${var##--output_file=}"
mkdir -p "\$(dirname \$path)"
echo lcov_merger_called >> \$path
exit 0
fi
done
EOF
chmod +x lcov_merger.sh
cat <<EOF > rules.bzl
UNIX_EMPTY_TEST = ""
WINDOWS_EMPTY_TEST = "@echo off"
def _impl(ctx):
output = ctx.actions.declare_file(ctx.attr.name + ".bat")
ctx.actions.write(output, WINDOWS_EMPTY_TEST if ${starlark_is_windows} else UNIX_EMPTY_TEST, is_executable = True)
return [DefaultInfo(executable=output)]
custom_test = rule(
implementation = _impl,
test = True,
attrs = {
"_lcov_merger": attr.label(default = ":lcov_merger", cfg = config.exec(exec_group = "test")),
},
)
EOF
cat <<EOF > BUILD
load(":rules.bzl", "custom_test")
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
sh_binary(
name = "lcov_merger",
srcs = ["lcov_merger.sh"],
)
custom_test(name = "foo_test")
EOF
bazel coverage --test_output=all //:foo_test --combined_report=lcov > $TEST_log \
|| fail "Coverage run failed but should have succeeded."
local coverage_file_path="$( get_coverage_file_path_from_test_log )"
cat $coverage_file_path
grep "lcov_merger_called" "$coverage_file_path" \
|| fail "Coverage report did not contain evidence of custom lcov_merger."
}
function test_starlark_rule_with_configuration_field_lcov_merger_coverage_enabled() {
add_rules_shell "MODULE.bazel"
cat <<EOF > lcov_merger.sh
for var in "\$@"
do
if [[ "\$var" == "--output_file="* ]]; then
path="\${var##--output_file=}"
mkdir -p "\$(dirname \$path)"
echo lcov_merger_called >> \$path
exit 0
fi
done
EOF
chmod +x lcov_merger.sh
cat <<EOF > rules.bzl
UNIX_EMPTY_TEST = ""
WINDOWS_EMPTY_TEST = "@echo off"
def _impl(ctx):
output = ctx.actions.declare_file(ctx.attr.name + ".bat")
ctx.actions.write(output, WINDOWS_EMPTY_TEST if ${starlark_is_windows} else UNIX_EMPTY_TEST, is_executable = True)
return [DefaultInfo(executable=output)]
custom_test = rule(
implementation = _impl,
test = True,
attrs = {
"_lcov_merger": attr.label(
default = configuration_field(fragment = "coverage", name = "output_generator"),
cfg = config.exec(exec_group = "test")
),
},
fragments = ["coverage"],
)
EOF
cat <<EOF > BUILD
load(":rules.bzl", "custom_test")
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
sh_binary(
name = "lcov_merger",
srcs = ["lcov_merger.sh"],
)
custom_test(name = "foo_test")
EOF
bazel coverage --test_output=all //:foo_test --combined_report=lcov --coverage_output_generator=//:lcov_merger > $TEST_log \
|| fail "Coverage run failed but should have succeeded."
local coverage_file_path="$( get_coverage_file_path_from_test_log )"
cat $coverage_file_path
grep "lcov_merger_called" "$coverage_file_path" \
|| fail "Coverage report did not contain evidence of custom lcov_merger."
}
function test_starlark_rule_with_configuration_field_lcov_merger_coverage_disabled() {
cat <<EOF > rules.bzl
UNIX_EMPTY_TEST = ""
WINDOWS_EMPTY_TEST = "@echo off"
def _impl(ctx):
if ctx.attr._lcov_merger:
fail("Expected _lcov_merger to be None if coverage is not collected")
output = ctx.actions.declare_file(ctx.attr.name + ".bat")
ctx.actions.write(output, WINDOWS_EMPTY_TEST if ${starlark_is_windows} else UNIX_EMPTY_TEST, is_executable = True)
return [DefaultInfo(executable=output)]
custom_test = rule(
implementation = _impl,
test = True,
attrs = {
"_lcov_merger": attr.label(
default = configuration_field(fragment = "coverage", name = "output_generator"),
cfg = config.exec(exec_group = "test")
),
},
fragments = ["coverage"],
)
EOF
cat <<EOF > BUILD
load(":rules.bzl", "custom_test")
custom_test(name = "foo_test")
EOF
bazel test --test_output=all //:foo_test > $TEST_log \
|| fail "Test run failed but should have succeeded."
}
function test_starlark_rule_default_baseline_coverage() {
mkdir -p test
cat <<EOF > test/rules.bzl
def _my_library_impl(ctx):
providers = []
transitive_files = [dep[DefaultInfo].files for dep in (ctx.attr.deps + ctx.attr.srcs)]
providers.append(
DefaultInfo(
files = depset(transitive = transitive_files),
)
)
providers.append(
coverage_common.instrumented_files_info(
ctx = ctx,
source_attributes = ["srcs"],
dependency_attributes = ["deps"],
)
)
return providers
my_library = rule(
implementation = _my_library_impl,
attrs = {
"srcs": attr.label_list(
allow_files = True,
),
"deps": attr.label_list(),
},
)
def _my_test_impl(ctx):
providers = []
out = ctx.actions.declare_file(ctx.label.name + ".bat")
all_files = depset(transitive = [dep[DefaultInfo].files for dep in (ctx.attr.deps + ctx.attr.srcs)])
if ${starlark_is_windows}:
script_content = "@echo off\n" + "\n".join([
"""(
echo SF:{}
echo DA:1,1
echo DA:2,0
echo LF:2
echo LH:1
echo end_of_record
) >> "%COVERAGE_DIR%\\\\my_test.dat"
""".format(f.path)
for f in all_files.to_list()
])
else:
script_content = "#!/bin/bash\n" + "\n".join([
"""cat <<E_O_F >> "\$COVERAGE_DIR/my_test.dat"
SF:{}
DA:1,1
DA:2,0
LF:2
LH:1
end_of_record
E_O_F
""".format(f.path)
for f in all_files.to_list()
])
ctx.actions.write(out, script_content, is_executable = True)
providers.append(DefaultInfo(executable = out))
providers.append(
coverage_common.instrumented_files_info(
ctx = ctx,
source_attributes = ["srcs"],
dependency_attributes = ["deps"],
)
)
return providers
my_test = rule(
implementation = _my_test_impl,
test = True,
attrs = {
"srcs": attr.label_list(
allow_files = True,
),
"deps": attr.label_list(),
"_lcov_merger": attr.label(
default = configuration_field(fragment = "coverage", name = "output_generator"),
cfg = config.exec(exec_group = "test"),
),
},
)
EOF
cat <<'EOF' > test/BUILD
load(":rules.bzl", "my_library", "my_test")
my_library(
name = "untested_lib",
srcs = [
"untested_1.txt",
"untested_2.txt",
],
)
my_library(
name = "covered_lib",
srcs = [
"covered_1.txt",
"covered_2.txt",
],
)
my_library(
name = "all_libs",
deps = [
":untested_lib",
":covered_lib",
],
)
my_library(
name = "tested_libs",
deps = [
":covered_lib",
],
)
my_test(
name = "my_test",
srcs = [
"test_1.txt",
"test_2.txt",
],
deps = [":tested_libs"],
)
EOF
touch test/{untested_1.txt,untested_2.txt,covered_1.txt,covered_2.txt,test_1.txt,test_2.txt}
bazel coverage //test:my_test //test:all_libs --combined_report=lcov &> $TEST_log \
|| fail "Coverage run failed but should have succeeded."
local expected_coverage="SF:test/covered_1.txt
FNF:0
FNH:0
DA:1,1
DA:2,0
LH:1
LF:2
end_of_record
SF:test/covered_2.txt
FNF:0
FNH:0
DA:1,1
DA:2,0
LH:1
LF:2
end_of_record
SF:test/untested_1.txt
FNF:0
FNH:0
LH:0
LF:0
end_of_record
SF:test/untested_2.txt
FNF:0
FNH:0
LH:0
LF:0
end_of_record"
local expected_baseline_coverage="SF:test/covered_1.txt
FNF:0
FNH:0
LH:0
LF:0
end_of_record
SF:test/covered_2.txt
FNF:0
FNH:0
LH:0
LF:0
end_of_record
SF:test/untested_1.txt
FNF:0
FNH:0
LH:0
LF:0
end_of_record
SF:test/untested_2.txt
FNF:0
FNH:0
LH:0
LF:0
end_of_record"
assert_coverage_result "$expected_coverage" bazel-out/_coverage/_coverage_report.dat
assert_coverage_result "$expected_baseline_coverage" bazel-out/_coverage/_baseline_report.dat
}
function do_test_starlark_rule_custom_baseline_coverage() {
mkdir -p test
cat <<EOF > test/rules.bzl
def _my_library_impl(ctx):
providers = []
transitive_files = [dep[DefaultInfo].files for dep in (ctx.attr.deps + ctx.attr.srcs)]
providers.append(
DefaultInfo(
files = depset(transitive = transitive_files),
)
)
baseline_coverage_files = []
for src in ctx.files.srcs:
lcov_file = ctx.actions.declare_file(ctx.label.name + "_" + str(hash(src.path)) + ".dat")
ctx.actions.write(
lcov_file,
"""
SF:{}
DA:1,0
DA:2,0
LF:2
LH:0
end_of_record
""".format(src.path),
)
baseline_coverage_files.append(lcov_file)
providers.append(
coverage_common.instrumented_files_info(
ctx = ctx,
source_attributes = ["srcs"],
dependency_attributes = ["deps"],
baseline_coverage_files = baseline_coverage_files,
)
)
return providers
my_library = rule(
implementation = _my_library_impl,
attrs = {
"srcs": attr.label_list(
allow_files = True,
),
"deps": attr.label_list(),
},
)
def _my_test_impl(ctx):
providers = []
out = ctx.actions.declare_file(ctx.label.name + ".bat")
all_files = depset(transitive = [dep[DefaultInfo].files for dep in (ctx.attr.deps + ctx.attr.srcs)])
if ${starlark_is_windows}:
script_content = "@echo off\n" + "\n".join([
"""(
echo SF:{}
echo DA:1,1
echo DA:2,0
echo LF:2
echo LH:1
echo end_of_record
) >> "%COVERAGE_DIR%\\\\my_test.dat"
""".format(f.path)
for f in all_files.to_list()
])
else:
script_content = "#!/bin/bash\n" + "\n".join([
"""cat <<E_O_F >> "\$COVERAGE_DIR/my_test.dat"
SF:{}
DA:1,1
DA:2,0
LF:2
LH:1
end_of_record
E_O_F
""".format(f.path)
for f in all_files.to_list()
])
ctx.actions.write(out, script_content, is_executable = True)
providers.append(DefaultInfo(executable = out))
providers.append(
coverage_common.instrumented_files_info(
ctx = ctx,
source_attributes = ["srcs"],
dependency_attributes = ["deps"],
)
)
return providers
my_test = rule(
implementation = _my_test_impl,
test = True,
attrs = {
"srcs": attr.label_list(
allow_files = True,
),
"deps": attr.label_list(),
"_lcov_merger": attr.label(
default = configuration_field(fragment = "coverage", name = "output_generator"),
cfg = config.exec(exec_group = "test"),
),
},
)
EOF
cat <<'EOF' > test/BUILD
load(":rules.bzl", "my_library", "my_test")
my_library(
name = "untested_lib",
srcs = [
"untested_1.txt",
"untested_2.txt",
],
)
my_library(
name = "covered_lib",
srcs = [
"covered_1.txt",
"covered_2.txt",
],
)
my_library(
name = "all_libs",
deps = [
":untested_lib",
":covered_lib",
],
)
my_library(
name = "tested_libs",
deps = [
":covered_lib",
],
)
my_test(
name = "my_test",
srcs = [
"test_1.txt",
"test_2.txt",
],
deps = [":tested_libs"],
)
EOF
touch test/{untested_1.txt,untested_2.txt,covered_1.txt,covered_2.txt,test_1.txt,test_2.txt}
bazel coverage //test:my_test //test:all_libs --combined_report=lcov &> $TEST_log \
|| fail "Coverage run failed but should have succeeded."
local expected_coverage="SF:test/covered_1.txt
FNF:0
FNH:0
DA:1,1
DA:2,0
LH:1
LF:2
end_of_record
SF:test/covered_2.txt
FNF:0
FNH:0
DA:1,1
DA:2,0
LH:1
LF:2
end_of_record
SF:test/untested_1.txt
FNF:0
FNH:0
DA:1,0
DA:2,0
LH:0
LF:2
end_of_record
SF:test/untested_2.txt
FNF:0
FNH:0
DA:1,0
DA:2,0
LH:0
LF:2
end_of_record"
local expected_baseline_coverage="SF:test/covered_1.txt
FNF:0
FNH:0
DA:1,0
DA:2,0
LH:0
LF:2
end_of_record
SF:test/covered_2.txt
FNF:0
FNH:0
DA:1,0
DA:2,0
LH:0
LF:2
end_of_record
SF:test/untested_1.txt
FNF:0
FNH:0
DA:1,0
DA:2,0
LH:0
LF:2
end_of_record
SF:test/untested_2.txt
FNF:0
FNH:0
DA:1,0
DA:2,0
LH:0
LF:2
end_of_record"
assert_coverage_result "$expected_coverage" bazel-out/_coverage/_coverage_report.dat
assert_coverage_result "$expected_baseline_coverage" bazel-out/_coverage/_baseline_report.dat
}
function test_starlark_rule_custom_baseline_coverage() {
do_test_starlark_rule_custom_baseline_coverage
}
function test_starlark_rule_custom_baseline_coverage_with_split_postprocessing() {
add_to_bazelrc "common --experimental_fetch_all_coverage_outputs"
add_to_bazelrc "common --experimental_split_coverage_postprocessing"
do_test_starlark_rule_custom_baseline_coverage
}
run_suite "test tests"