| #!/usr/bin/env bash | |
| set -o errexit -o nounset -o pipefail | |
| # Coverage behavior for `bazel test` / `bazel coverage`. | |
| # | |
| # Every target executes the same covered()/uncovered() code and differs only in how | |
| # the source is provided or the test exits — see BUILD.bazel for what each exercises. | |
| # | |
| # //:first_party_npmpkg_test is an UNSUPPORTED scenario, kept as executable | |
| # documentation: first-party code repackaged with npm_package runs from a copy in | |
| # the .aspect_rules_js store with no link back to source, so coverage comes out | |
| # empty. Use js_library linking (//:first_party_jslib_test) for first-party | |
| # coverage. See docs/troubleshooting.md and | |
| # https://github.com/aspect-build/rules_js/issues/2933. | |
| # | |
| # Coverage is checked twice: inline (the _lcov_merger runs in the test action) and | |
| # split (--experimental_split_coverage_postprocessing, required for remote | |
| # execution), where the merger runs as its own action without the test's runfiles. | |
| # Both must report the same coverage: the report is generated in the test action, | |
| # and the merger only publishes it. | |
| readonly WORKING_TARGETS=(//:test //:expected_exit_test //:first_party_jslib_test) | |
| readonly UNSUPPORTED_TARGETS=(//:first_party_npmpkg_test) | |
| readonly ALL_TARGETS=("${WORKING_TARGETS[@]}" "${UNSUPPORTED_TARGETS[@]}") | |
| testlogs="$(bazel info bazel-testlogs)" | |
| # Prints nothing and returns 0 if the report is real; otherwise prints the reason and | |
| # returns 1. Keyed off the function names rather than the SF: path so it is agnostic | |
| # to how the source resolves. | |
| coverage_is_real() { | |
| local dat="$1" | |
| [[ -s "$dat" ]] || { | |
| echo "coverage.dat is missing or empty" | |
| return 1 | |
| } | |
| grep -q '^FNDA:1,covered$' "$dat" || { | |
| echo "covered() not marked as executed" | |
| return 1 | |
| } | |
| grep -q '^FNDA:0,uncovered$' "$dat" || { | |
| echo "uncovered() not marked as unexecuted" | |
| return 1 | |
| } | |
| grep -Eq '^DA:[0-9]+,[1-9]' "$dat" || { | |
| echo "all line execution counts are zero" | |
| return 1 | |
| } | |
| } | |
| assert_coverage() { | |
| local target="$1" mode="$2" dat="$testlogs/${1#//:}/coverage.dat" reason | |
| if reason="$(coverage_is_real "$dat")"; then | |
| echo "PASS($target, $mode)" | |
| else | |
| echo "FAIL($target, $mode): $reason" | |
| cat "$dat" 2>/dev/null || true | |
| exit 1 | |
| fi | |
| } | |
| # The unsupported case must report empty coverage, the documented behavior. If it | |
| # ever produces a real report the script fails, so the docs get revisited rather than | |
| # the stale expectation rotting unnoticed in a green log. | |
| check_unsupported() { | |
| local target="$1" mode="$2" dat="$testlogs/${1#//:}/coverage.dat" reason | |
| if reason="$(coverage_is_real "$dat")"; then | |
| echo "UNSUPPORTED($target, $mode) now produces coverage — update docs/troubleshooting.md and https://github.com/aspect-build/rules_js/issues/2933." | |
| exit 1 | |
| else | |
| echo "UNSUPPORTED($target, $mode): empty coverage, as documented ($reason)" | |
| fi | |
| } | |
| # The code under test executes in every scenario. If this passes but coverage below | |
| # is empty, the failure is in coverage reporting, not test execution. | |
| bazel test --nocache_test_results "${ALL_TARGETS[@]}" | |
| # --nocache_test_results on the coverage runs below: they differ only in | |
| # post-processing flags, which do not invalidate a cached test result — without it a | |
| # later run would just republish an earlier run's coverage.dat. | |
| # | |
| # No --instrument_test_targets: every covered source here lives in a js_library the | |
| # test depends on, so the default instrumentation already picks it up. | |
| bazel coverage --nocache_test_results "${ALL_TARGETS[@]}" | |
| for t in "${WORKING_TARGETS[@]}"; do assert_coverage "$t" inline; done | |
| for t in "${UNSUPPORTED_TARGETS[@]}"; do check_unsupported "$t" inline; done | |
| bazel coverage --nocache_test_results \ | |
| --experimental_split_coverage_postprocessing \ | |
| --experimental_fetch_all_coverage_outputs \ | |
| "${ALL_TARGETS[@]}" | |
| for t in "${WORKING_TARGETS[@]}"; do assert_coverage "$t" split; done | |
| for t in "${UNSUPPORTED_TARGETS[@]}"; do check_unsupported "$t" split; done |