Files
mongo/evergreen/functions/code_coverage_data_process.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

195 lines
6.7 KiB
Python
Raw Permalink Normal View History

import glob
import os
import platform
import subprocess
import sys
import requests
import retry
from buildscripts.util.expansions import get_expansion
# This script is used to gather code coverage data from the build and post it to coveralls.
# It is run as part of the Evergreen build process.
# It is not intended to be run directly.
@retry.retry(tries=3, delay=5)
def retry_coveralls_report(args: list, env: dict[str, str]):
print("Running coveralls report...")
subprocess.run(args, env=env, check=True, encoding="utf-8")
@retry.retry(tries=3, delay=5)
def retry_coveralls_post(coveralls_report: str):
print("Posting to coveralls")
files = {"json_file": open(coveralls_report, "rb")}
response = requests.post("https://coveralls.io/api/v1/jobs", files=files)
print(response.text)
if not response.ok:
raise RuntimeError(f"Error while sending coveralls report: {response.status_code}")
def get_bazel_coverage_report_file() -> str:
BAZEL_BINARY = "bazel" # simplified since we already restrict which platforms can run coverage
proc = subprocess.run([BAZEL_BINARY, "info", "output_path"], check=True, capture_output=True)
bazel_output_location = proc.stdout.decode("utf-8").strip()
bazel_coverage_report_location = os.path.join(
bazel_output_location, "_coverage", "_coverage_report.dat"
)
return bazel_coverage_report_location
def main():
should_gather_code_coverage = get_expansion("gather_code_coverage_results", False)
if not should_gather_code_coverage:
print("Missing 'gather_code_coverage_results' expansion, skipping code coverage.")
return 0
gcov_binary = get_expansion("gcov_tool", None)
if not gcov_binary:
print(
"Missing 'gcov_tool' expansion, skipping code coverage because this is likely not a code coverage variant."
)
return 0
disallowed_arches = {"s390x", "s390", "ppc64le", "ppc64", "ppc", "ppcle"}
arch = platform.uname().machine.lower()
print(f"Detected arch: {arch}")
if arch in disallowed_arches:
raise RuntimeError(f"Code coverage not supported on architecture '{arch}'.")
if not os.path.exists(".git"):
raise RuntimeError(
"No git repo found in working directory. Code coverage needs git repo to function."
)
coveralls_token = get_expansion("coveralls_token")
assert coveralls_token is not None, "Coveralls token was not found"
github_pr_number = get_expansion("github_pr_number", "")
revision_order_id = get_expansion("revision_order_id")
# this keeps coverage reports consistent across evergreen tasks and merge queue maneuvers
github_commit = get_expansion("github_commit")
bazel_coverage_report_location = get_bazel_coverage_report_file()
if os.path.exists(bazel_coverage_report_location):
print("Found bazel coverage report.")
version_id = get_expansion("version_id")
task_id = get_expansion("task_id")
args = [
"coveralls",
"report",
bazel_coverage_report_location,
"--service-name=travis-ci",
f"--repo-token={coveralls_token}",
f"--job-id={revision_order_id}",
f"--build-url=https://spruce.mongodb.com/version/{version_id}/",
f"--job-url=https://spruce.mongodb.com/task/{task_id}/",
]
if github_pr_number:
args.append(
f"--pull-request={github_pr_number}",
)
my_env = os.environ.copy()
my_env["COVERALLS_GIT_COMMIT"] = github_commit
retry_coveralls_report(args, my_env)
# no gcda files are generated from bazel coverage so we can exit early here
return 0
print(f"No bazel coverage report found at {bazel_coverage_report_location}")
# because of bazel symlink shenanigans, the bazel gcda and gcno files are put in different
# directories when the GCOV_PREFIX and GCOV_PREFIX_STRIP env vars are used. We manually
# put the gcno files where the gcda files are generated to fix this.
has_bazel_gcno = False
workdir = get_expansion("workdir")
bazel_output_dir = os.path.join(workdir, "bazel-out")
for file in glob.iglob("./**/bazel-out/**/*.gcno", root_dir=workdir, recursive=True):
has_bazel_gcno = True
parts = file.split("bazel-out/")
assert len(parts) == 2, "Something went wrong, path was not split into 2 parts."
old_path = os.path.join(workdir, file)
new_path = os.path.join(bazel_output_dir, parts[1])
new_dir = os.path.dirname(new_path)
os.makedirs(new_dir, exist_ok=True)
os.rename(old_path, new_path)
if not has_bazel_gcno:
raise RuntimeError("Neither bazel coverage nor gcno files were found.")
my_env = os.environ.copy()
my_env["COVERALLS_REPO_TOKEN"] = coveralls_token
my_env["TRAVIS_PULL_REQUEST"] = github_pr_number
my_env["TRAVIS_JOB_ID"] = revision_order_id
my_env["TRAVIS_COMMIT"] = github_commit
coveralls_report = "gcovr-coveralls.json"
args = [
"python3",
"-m",
"gcovr",
"--output",
coveralls_report,
"--coveralls-pretty",
"--txt",
"gcovr-coveralls.txt",
"--print-summary",
"--exclude",
"build/debug/.*",
"--exclude",
".*bazel-out/.*",
"--exclude",
".*external/mongo_toolchain/.*",
"--exclude",
r".*src/.*_gen\.(h|hpp|cpp)",
"--exclude",
r".*src/mongo/db/cst/grammar\.yy",
"--exclude",
".*src/mongo/idl/.*",
"--exclude",
r".*src/mongo/.*_test\.(h|hpp|cpp)",
"--exclude",
".*src/mongo/dbtests/.*",
"--exclude",
".*src/mongo/unittest/.*",
"--exclude",
".*/third_party/.*",
"--gcov-ignore-errors",
"source_not_found",
"--gcov-ignore-parse-errors",
"negative_hits.warn",
"--gcov-exclude-directories",
".*src/mongo/dbtests/.*",
"--gcov-exclude-directories",
".*src/mongo/idl/.*",
"--gcov-exclude-directories",
".*src/mongo/unittest/.*",
"--gcov-exclude-directories",
".*/third_party/.*",
"--gcov-executable",
gcov_binary,
bazel_output_dir,
]
print("Running gcovr command")
process = subprocess.run(
args, env=my_env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding="utf-8"
)
print(process.stdout)
if process.returncode != 0:
raise RuntimeError(f"gcovr failed with code: {process.returncode}")
if not os.path.exists(coveralls_report):
raise RuntimeError(f"Could not find coveralls json report at {coveralls_report}")
retry_coveralls_post(coveralls_report)
return 0
if __name__ == "__main__":
sys.exit(main())