बेज़ेल के साथ कोड कवरेज

Bazel में coverage सब-कमांड होती है. इसकी मदद से, उन रिपॉज़िटरी के लिए कोड कवरेज रिपोर्ट जनरेट की जा सकती हैं जिन्हें bazel coverage की मदद से टेस्ट किया जा सकता है. अलग-अलग भाषाओं के इकोसिस्टम की खासियतों की वजह से, किसी प्रोजेक्ट के लिए इसे लागू करना हमेशा आसान नहीं होता.

इस पेज पर, कवरेज रिपोर्ट बनाने और देखने की सामान्य प्रोसेस के बारे में बताया गया है. साथ ही, इसमें उन भाषाओं के लिए कुछ खास नोट भी दिए गए हैं जिनके कॉन्फ़िगरेशन के बारे में ज़्यादा जानकारी उपलब्ध है. इसे सबसे पहले सामान्य सेक्शन को पढ़कर समझें. इसके बाद, किसी खास भाषा से जुड़ी ज़रूरी शर्तों के बारे में पढ़ें. रिमोट एक्ज़ीक्यूशन सेक्शन के बारे में भी ध्यान दें. इसके लिए, कुछ अतिरिक्त बातों का ध्यान रखना ज़रूरी है.

हालांकि, इसमें कई तरह के बदलाव किए जा सकते हैं. इस दस्तावेज़ में, lcov रिपोर्ट बनाने और इस्तेमाल करने पर फ़ोकस किया गया है. फ़िलहाल, यह सबसे ज़्यादा इस्तेमाल किया जाने वाला तरीका है.

कवरेज रिपोर्ट बनाना

वीडियो की रणनीति

कवरेज रिपोर्ट बनाने के लिए, यह बुनियादी वर्कफ़्लो ज़रूरी है:

  • टेस्ट टारगेट वाली सामान्य रिपॉज़िटरी
  • ऐसी टूलचेन जिसमें भाषा के हिसाब से कोड कवरेज टूल इंस्टॉल किए गए हों
  • "इंस्ट्रूमेंटेशन" का सही कॉन्फ़िगरेशन

पहले दो तरीके, भाषा के हिसाब से काम करते हैं और इन्हें समझना आसान होता है. हालांकि, जटिल प्रोजेक्ट के लिए बाद वाला तरीका ज़्यादा मुश्किल हो सकता है.

इस मामले में "इंस्ट्रूमेंटेशन" का मतलब, कवरेज टूल से है. इनका इस्तेमाल किसी खास टारगेट के लिए किया जाता है. Bazel, --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 को Bazel प्रोजेक्ट के रूट में एक्ज़ीक्यूट किया जाए.

नतीजा देखने के लिए, किसी भी वेब ब्राउज़र में index.html डायरेक्ट्री में मौजूद genhtml फ़ाइल खोलें.

genhtml टूल या lcov कवरेज फ़ॉर्मैट के बारे में ज़्यादा मदद और जानकारी पाने के लिए, lcov प्रोजेक्ट देखें.

रिमोट एक्ज़ीक्यूशन

फ़िलहाल, रिमोट टेस्ट एक्ज़ीक्यूशन के साथ काम करने में कुछ समस्याएं आ रही हैं:

  • रिपोर्ट कॉम्बिनेशन की कार्रवाई को अभी दूर से नहीं चलाया जा सकता. ऐसा इसलिए है, क्योंकि Bazel, कवरेज आउटपुट फ़ाइलों को अपने ग्राफ़ का हिस्सा नहीं मानता. इसके बारे में इस समस्या में बताया गया है. इसलिए, वह उन्हें कॉम्बिनेशन ऐक्शन के इनपुट के तौर पर सही तरीके से इस्तेमाल नहीं कर सकता. इस समस्या को हल करने के लिए, --strategy=CoverageReport=local का इस्तेमाल करें.
    • ध्यान दें: Bazel की रणनीतियों को हल करने के तरीके की वजह से, अगर Bazel को local,remote आज़माने के लिए सेट अप किया गया है, तो इसके बजाय --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

ज़रूरी शर्तें

Python का इस्तेमाल करके कवरेज चलाने के लिए, कुछ ज़रूरी शर्तें पूरी करनी होंगी:

बदलाव किए गए coverage.py का इस्तेमाल करना

इसके लिए, rules_python का इस्तेमाल किया जा सकता है. इससे requirements.txt फ़ाइल का इस्तेमाल किया जा सकता है. फ़ाइल में दी गई ज़रूरी शर्तों को, pip_install रिपॉज़िटरी के नियम का इस्तेमाल करके, Bazel टारगेट के तौर पर बनाया जाता है.

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",
)

इसके बाद, test targets, coverage.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 टारगेट में कवरेज डिपेंडेंसी जोड़ने के बजाय, टूलचेन कॉन्फ़िगरेशन में कवरेज टूल जोड़ा जा सकता है.

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",
    ],
)

इसके बाद, अपने Python टूलचेन को कॉन्फ़िगर करें. उदाहरण के लिए,

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",
)