כיסוי קוד עם Bazel

Bazel כוללת פקודת משנה coverage ליצירת דוחות כיסוי קוד בנושא מאגרים שניתן לבדוק עם bazel coverage. הודות לאידולוגיות של המערכות האקולוגיות השונות של השפה, לא תמיד קל לעשות זאת בעבודה על פרויקט נתון.

הדף הזה מתעד את התהליך הכללי ליצירה ולהצגה של דוחות כיסוי, וכן כמה הערות ספציפיות לשפה בשפות שהתצורה שלהן ידועה. מומלץ לקרוא תחילה את הקטע הכללי, ולאחר מכן לקרוא את הדרישות עבור שפה מסוימת. שימו לב גם לקטע של ביצוע מרחוק, שכולל שיקולים נוספים.

אמנם יש הרבה אפשרויות להתאמה אישית, אבל המסמך הזה מתמקד בהפקת ובצריכת דוחות של lcov. כרגע זהו המסלול הנתמך ביותר.

יצירת דוח כיסוי

הכנה

כדי ליצור דוחות כיסוי, צריך לפעול לפי השלבים הבאים:

  • מאגר בסיסי עם יעדי בדיקה
  • ערכת כלים התקנת כלים לכיסוי קוד ספציפי לשפה
  • הגדרה נכונה של &מירכאות;

שתי השיטות הראשונות הן ספציפיות לשפה ועשויות להיות פשוטות יותר, ואם כי קשה יותר להגיע לפרויקטים מורכבים.

"שני בזאר מאפשר את ההפעלה הזו לקבוצת משנה ספציפית של קבצים באמצעות התכונה --instrumentation_filter, שמאפשרת לסנן מסנן ליעדים שנבדקים באמצעות אינסטרומנטציה. כדי להפעיל אינסטרומנטציה לבדיקות, חובה לסמן את התיבה --instrument_test_targets.

כברירת מחדל, Bazel מנסה להתאים לחבילות היעד, ומדפיסה את המסנן הרלוונטי כהודעה של INFO.

כיסוי פועל

כדי להפיק דוח כיסוי, משתמשים ב-bazel coverage --combined_report=lcov [target]. הפעולה הזו מפעילה את הבדיקות עבור היעד, ויוצרת דוחות כיסוי בפורמט lcov לכל קובץ.

בסיום הפעולה, מאחר ש-Bazel מפעילה פעולה שאוספת את כל קובצי הכריכה שנוצרו, וממזגת אותם לקובץ אחד שנוצר בסופו של דבר ב-$(bazel info output_path)/_coverage/_coverage_report.dat.

דוחות הכיסוי נוצרים גם אם הבדיקות נכשלות. עם זאת, הבדיקה לא חלה על הבדיקות שנכשלו – רק הדיווחים על הבדיקות עברו.

כיסוי הכיסוי

דוח הכיסוי יוצג רק בפורמט lcovלא קריא למשתמשים. מתוך כך, אנחנו יכולים להשתמש בכלי genhtml (חלק מפרויקט lcov) כדי להפיק דוח שאפשר להציג בדפדפן האינטרנט:

genhtml --output genhtml "$(bazel info output_path)/_coverage/_coverage_report.dat"

לתשומת ליבכם, genhtml קורא גם את קוד המקור, כדי להוסיף הערות לכיסוי חסר בקבצים האלה. כדי שזה יעבוד, אנחנו מצפים ש-genhtml יופעל בבסיס פרויקט ה-Bazal.

להצגת התוצאה, פשוט פתחו את הקובץ index.html שנוצר בספרייה genhtml בדפדפן כלשהו.

עזרה ומידע נוסף בנושא הכלי genhtml, או בפורמט הכיסוי של lcov, מופיעים בפרויקט lcov.

ביצוע מרחוק

כשמפעילים את האפשרות של ביצוע בדיקה מרחוק, יש כמה נקודות שחשוב לשים לב אליהן:

  • עדיין לא ניתן לבצע את פעולת השילוב של הדוח מרחוק. הסיבה לכך היא ש-Bazel לא מזהה את קובצי הפלט של הכיסוי כחלק מהתרשים שלה (ראו הבעיה הזו), ולכן היא לא יכולה להתייחס אליהם כהלכה כאל קלט לפעולה המשולבת. כדי לעקוף את הבעיה, צריך להשתמש בגרסה --strategy=CoverageReport=local.
    • הערה: ייתכן שיהיה צורך לציין משהו כמו --strategy=CoverageReport=local,remote, אם
  • גם לא ניתן להשתמש ב--remote_download_minimal ובסימונים דומים כתוצאה מההתראה הראשונה.
  • Bazel לא תוכל ליצור בשלב זה פרטי כיסוי אם הבדיקות נשמרו במטמון בעבר. כדי לעקוף את הבעיה, ניתן להגדיר את --nocache_test_results ספציפית למסלולי כיסוי, אבל זה כרוך גם בעלויות מורכבות מבחינת זמני הבדיקה.
  • --experimental_split_coverage_postprocessing וגם --experimental_fetch_all_coverage_outputs
    • בדרך כלל הכיסוי פועל כחלק מפעולת הבדיקה, ולכן כברירת מחדל אנחנו לא מקבלים חזרה את כל הכיסוי כפלט של ההפעלה מרחוק כברירת מחדל. הסימונים האלה מבטלים את ברירת המחדל ומשיגים את נתוני הכיסוי. לפרטים נוספים, עיינו בבעיה הזו.

תצורה ספציפית לשפה

Java

Java צריך להיות מחוץ לאריזה עם תצורת ברירת המחדל. כלי הברגים של Bazel מכילים את כל מה שצריך לביצוע מרחוק, כולל JUnit.

Python

דרישות מוקדמות

יש דרישות מוקדמות להרצת פונט עם הכיסוי:

שימוש ב-cover.py שהשתנה

דרך לעשות זאת היא באמצעות rules_python, כך אפשר להשתמש בקובץ requirements.txt, הדרישות המפורטות בקובץ נוצרות כיעדים רגילים באמצעות כלל המאגר pip_Install.

הרשומה requirements.txt צריכה לכלול את הערך הבא:

git+https://github.com/ulfjack/coveragepy.git@lcov-support

לאחר מכן יש להשתמש בקובץ rules_python, pip_install ובקובץ requirements.txt בקובץ WORKSPACE בתור:

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "rules_python",
    url = "https://github.com/bazelbuild/rules_python/releases/download/0.5.0/rules_python-0.5.0.tar.gz",
    sha256 = "cd6730ed53a002c56ce4e2f396ba3b3be262fd7cb68339f0377a45e8227fe332",
)

load("@rules_python//python:pip.bzl", "pip_install")

pip_install(
   name = "python_deps",
   requirements = "//:requirements.txt",
)

לאחר מכן, ניתן יהיה לצרוך את הדרישה שלcover.py על ידי הגדרת היעדים לבדיקה באמצעות ההגדרה הבאה ב-BUILD קבצים:

load("@python_deps//:requirements.bzl", "entry_point")

alias(
    name = "python_coverage_tools",
    actual = entry_point("coverage"),
)

py_test(
    name = "test",
    srcs = ["test.py"],
    env = {
        "PYTHON_COVERAGE": "$(location :python_coverage_tools)",
    },
    deps = [
        ":main",
        ":python_coverage_tools",
    ],
)

אם אתם משתמשים בארגז כלים הירמטי של Python, במקום להוסיף את התלות בכיסוי לכל יעד py_test תוכלו להוסיף את כלי הכיסוי לתצורה של Toolchain.

מכיוון שהכלל pip_Install תלוי בשרשרת הכלים של Python, לא ניתן להשתמש בו לאחזור המודול coverage. במקום זאת, אפשר להוסיף את WORKSPACE, למשל

http_archive(
    name = "coverage_linux_x86_64"",
    build_file_content = """
py_library(
    name = "coverage",
    srcs = ["coverage/__main__.py"],
    data = glob(["coverage/*", "coverage/**/*.py"]),
    visibility = ["//visibility:public"],
)
""",
    sha256 = "84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3",
    type = "zip",
    urls = [
        "https://files.pythonhosted.org/packages/74/0d/0f3c522312fd27c32e1abe2fb5c323b583a5c108daf2c26d6e8dfdd5a105/coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
    ],
)

לאחר מכן, מגדירים את ארגז הכלים של Fithon למשל.

py_runtime(
    name = "py3_runtime_linux_x86_64",
    coverage_tool = "@coverage_linux_x86_64//:coverage",
    files = ["@python3_9_x86_64-unknown-linux-gnu//:files"],
    interpreter = "@python3_9_x86_64-unknown-linux-gnu//:bin/python3",
    python_version = "PY3",
)

py_runtime_pair(
    name = "python_runtimes_linux_x86_64",
    py2_runtime = None,
    py3_runtime = ":py3_runtime_linux_x86_64",
)

toolchain(
    name = "python_toolchain_linux_x86_64",
    exec_compatible_with = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
    toolchain = ":python_runtimes_linux_x86_64",
    toolchain_type = "@bazel_tools//tools/python:toolchain_type",
)