diff --git a/BUILD b/BUILD index 1d800cd..a3158f3 100644 --- a/BUILD +++ b/BUILD @@ -41,6 +41,11 @@ alias( actual = "//score_coverage:merger", ) +alias( + name = "gcov_reporter", + actual = "//score_coverage:gcov_reporter", +) + alias( name = "generate_coverage_html", actual = "//score_coverage:generate_coverage_html", diff --git a/README.md b/README.md index b7484c1..18e8423 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ # coverage_tool — Bazel module `score_coverage` -LLVM source-based code coverage pipeline for Eclipse S-CORE: one +Code coverage pipeline for Eclipse S-CORE, LLVM source-based on Linux and +gcov-based for QNX on-target tests: one `bazel coverage` run gives one line and branch coverage report for C++ and Rust, untested in-scope files at exact 0 %, reviewed justifications with an effective-coverage metric, and a CI threshold gate. diff --git a/defs.bzl b/defs.bzl index ce5b882..bcff1c9 100644 --- a/defs.bzl +++ b/defs.bzl @@ -39,6 +39,14 @@ and point Bazel at them from their coverage bazelrc config: coverage:llvm_cov --coverage_output_generator=@score_coverage//:merger coverage:llvm_cov --coverage_report_generator=//tools/coverage:reporter_wrapper +For gcov-based toolchains (GCC on Linux, QCC on QNX with the tests executed +on target through score_qnx_unit_tests) a second reporter with +backend = "gcov" and the toolchain's gcov binary is declared; the gcov +coverage config keeps Bazel's own per-test merger: + + coverage:qnx --coverage_output_generator=@bazel_tools//tools/test:lcov_merger + coverage:qnx --coverage_report_generator=//tools/coverage:gcov_reporter_wrapper + See README.md for the complete adoption guide (toolchains, bazelrc, justifications, CI) and COVERAGE_GUIDE.md for how the pipeline works. """ @@ -51,17 +59,19 @@ score_coverage_scope = _coverage_scope def score_coverage_reporter( name, coverage_scope, - llvm_cov, - llvm_profdata, + llvm_cov = None, + llvm_profdata = None, llvm_cxxfilt = None, module_bazel = "//:MODULE.bazel", + backend = "llvm", + gcov = None, **kwargs): """Declare the consumer-side coverage report generator. The generated executable is passed to Bazel as --coverage_report_generator=//:. It wires the consumer's - coverage scope, workspace root and LLVM tools into score_coverage's - reporter. + coverage scope, workspace root and coverage tools into score_coverage's + reporter for the chosen backend. Args: name: Target name, referenced by --coverage_report_generator. @@ -70,21 +80,36 @@ def score_coverage_reporter( llvm_cov: Label of the llvm-cov binary (the consumer's LLVM toolchain, e.g. "@llvm_toolchain//:llvm-cov"). Must come from the same LLVM major version that produced the coverage instrumentation. - llvm_profdata: Label of the llvm-profdata binary. + Required for backend = "llvm". + llvm_profdata: Label of the llvm-profdata binary. Required for + backend = "llvm". llvm_cxxfilt: Optional label of llvm-cxxfilt for symbol demangling (C++ Itanium and Rust v0/legacy). toolchains_llvm exposes it as "@llvm_toolchain_llvm//:bin/llvm-cxxfilt". module_bazel: The consumer's root MODULE.bazel, used at runtime to locate the real workspace root. Requires exports_files(["MODULE.bazel"]) in the consumer's root BUILD file. + backend: "llvm" (Clang / rustc coverage mapping, Linux host tests) or + "gcov" (GCC / QCC .gcda counters, e.g. QNX on-target tests run + through score_qnx_unit_tests). One reporter target per backend. + gcov: Label of the gcov binary matching the compiler that produced the + .gcno/.gcda files, e.g. "@score_qcc_x86_64_toolchain_pkg//:gcov" or + "@score_gcc_x86_64_toolchain_pkg//:gcov". Required for + backend = "gcov". **kwargs: Common rule attributes (testonly, visibility, tags, ...). """ + if backend == "llvm" and (not llvm_cov or not llvm_profdata): + fail("score_coverage_reporter(%s): backend \"llvm\" needs llvm_cov and llvm_profdata" % name) + if backend == "gcov" and not gcov: + fail("score_coverage_reporter(%s): backend \"gcov\" needs gcov" % name) _reporter_wrapper( name = name, + backend = backend, coverage_scope = coverage_scope, module_bazel = module_bazel, llvm_cov = llvm_cov, llvm_profdata = llvm_profdata, llvm_cxxfilt = llvm_cxxfilt, + gcov = gcov, **kwargs ) diff --git a/docs/architecture/index.rst b/docs/architecture/index.rst index 12927af..ba9fdbf 100644 --- a/docs/architecture/index.rst +++ b/docs/architecture/index.rst @@ -23,9 +23,17 @@ Architecture :security: NO :realizes: wp__sw_implementation -The pipeline replaces Bazel's two coverage hooks, ``--coverage_output_generator`` -and ``--coverage_report_generator``, with its own tools and adds a -justification and gating layer on top. It has two phases. +The pipeline hooks into Bazel's two coverage extension points, +``--coverage_output_generator`` and ``--coverage_report_generator``, and adds a +justification and gating layer on top. It has two phases and, in phase 1, two +backends that produce the same report zip: + +- **llvm**: Clang and rustc coverage mapping, read with ``llvm-cov``; Linux + host tests, C++ and Rust. +- **gcov**: GCC and QNX QCC ``.gcda`` counters, read with the toolchain's + ``gcov``; C++ only. On QNX the tests run inside QEMU through + ``score_qnx_unit_tests``, which carries the counters back to the host, and + Bazel's own per-test collector turns them into LCOV. .. uml:: @@ -41,6 +49,15 @@ justification and gating layer on top. It has two phases. (allowlist.txt\npath_map.txt\nobjects.txt\nsource files) --> [reporter.py\n--coverage_report_generator] [reporter.py\n--coverage_report_generator] --> (_coverage_report.dat zip\nhtml_report, lcov_report, text_report) } + package "Phase 1, gcov backend: bazel coverage --config=qnx" { + [GCC / QCC\n-fprofile-arcs -ftest-coverage] --> [test binaries (QEMU on QNX)] + [test binaries (QEMU on QNX)] --> (.gcda per test) + (.gcda per test) --> [Bazel collector\ngcov + lcov_merger] + [Bazel collector\ngcov + lcov_merger] --> (coverage.dat LCOV) + (coverage.dat LCOV) --> [gcov_reporter.py\n--coverage_report_generator] + (allowlist.txt\npath_map.txt\ngcno.txt\nsource files) --> [gcov_reporter.py\n--coverage_report_generator] + [gcov_reporter.py\n--coverage_report_generator] --> (_coverage_report.dat zip\nhtml_report, lcov_report, text_report) + } package "Phase 2: bazel run //:generate_coverage_html" { (_coverage_report.dat zip\nhtml_report, lcov_report, text_report) --> [generate_coverage_html.py] [generate_coverage_html.py] --> [justify.py] @@ -101,6 +118,52 @@ with exact 0 % entries whose denominators come from the compiler's coverage map. Rust rlibs are expanded into their object members first because their leading ``lib.rmeta`` member makes ``llvm-cov`` reject the archive. +Phase 1, gcov backend +--------------------- + +The gcov backend exists for toolchains that cannot emit LLVM coverage +mapping: GCC on Linux and, the reason it was built, QCC on QNX. It changes +the collection, not the report. + +1. **Compilers.** The ``coverage`` feature of the S-CORE GCC and QCC + toolchains adds ``-fprofile-arcs -ftest-coverage``; the compiler writes a + ``.gcno`` notes file per translation unit and the instrumented binary + writes ``.gcda`` counters when it exits. rustc cannot produce either, so + Rust sources in scope are reported as *not instrumentable* on this backend. +2. **Execution and transport.** On Linux the test writes its ``.gcda`` under + Bazel's ``COVERAGE_DIR``. On QNX the test runs inside a QEMU micro-VM + (``score_qnx_unit_tests``, ``--run_under``); ``GCOV_PREFIX`` points the + counters at a guest directory, the guest tars them at exit, and the host + runner extracts the archive into ``COVERAGE_DIR``. From there both are the + same. +3. **Per-test collection stays Bazel's.** Bazel's ``collect_coverage.sh`` + runs the toolchain's ``gcov`` over the counters and its ``lcov_merger`` + writes one LCOV file per test. Two properties of that collector shape the + backend: it maps generated ``_virtual_includes/`` paths back to the + declared header itself, and it keeps only files of its instrumented-files + manifest, which never lists sources of external repositories, so a header + vendored from an external repository has no data on this backend even when + a test executes it. Tests are instrumented as well + (``--instrument_test_targets``) so header-only code that only a test + translation unit instantiates is measured; the scope allowlist still drops + the test sources. +4. **Final report.** ``gcov_reporter.py`` sums the per-test LCOV records per + file (the same semantics as merging profiles on the LLVM side), applies + the scope through the shared selection logic, and adds the zero-coverage + baseline: ``gcov --json-format`` over the ``.gcno`` of every in-scope + translation unit without its ``.gcda`` yields every line and branch at + zero. The ``.gcno`` files come from ``InstrumentedFilesInfo`` through the + scope aspect, listed in ``_gcno.txt`` and carried in the reporter's + runfiles. gcovr renders the HTML from the merged data (one page per file, + under the canonical path) and the text summary; LCOV and + ``unmapped_files.txt`` are written as on the LLVM side. + +Line semantics differ between the backends and are recorded in the +integration ground truth: gcov counts only lines the compiler emitted code +for (no closing braces, no unused inline functions), while LLVM's mapping +keeps unused functions at 0 %. The justification and gating layer is +backend-agnostic; ``effective_coverage.py`` recognises gcovr's HTML layout. + Phase 2: report generation and gate ----------------------------------- @@ -130,7 +193,10 @@ Module and consumer split manifest discovery * - ``score_coverage/reporter.py`` - final merge, llvm-cov show/export/report, allowlist filtering, - ``--empty-profile`` baselines, rlib expansion, path normalisation + ``--empty-profile`` baselines, rlib expansion, path normalisation; the + selection, staging and path helpers shared with the gcov backend + * - ``score_coverage/gcov_reporter.py`` + - gcov backend: per-test LCOV merge, ``.gcno`` baselines, gcovr HTML * - ``score_coverage/coverage_scope.bzl`` - the scope aspect and rule (CcInfo and CrateInfo) * - ``score_coverage/reporter_wrapper.bzl``, ``defs.bzl`` @@ -151,12 +217,14 @@ Module and consumer split * - ``score_coverage_scope(deps = [...])`` - names the repository's production targets * - ``score_coverage_reporter(...)`` - - carries the repository's LLVM tool labels and workspace root + - carries the repository's LLVM tool labels (or, with + ``backend = "gcov"``, the toolchain's gcov) and workspace root; one + target per backend * - ``coverage_justifications.yaml`` - reviewed, repository-specific engineering arguments * - MODULE.bazel toolchain blocks - LLVM and Ferrocene pins are per-repository decisions - * - the ``coverage:llvm_cov`` bazelrc block + * - the ``coverage:llvm_cov`` and ``coverage:qnx`` bazelrc blocks - bazelrc cannot be imported across modules Two wiring details make the external hosting work: every path in the generated @@ -178,3 +246,9 @@ Design decisions - **In-process tool calls.** ``generate_coverage_html`` imports the justification tools instead of nesting ``bazel run``; this keeps one process, one exit code and testable seams. +- **One report format for both backends.** The gcov backend replaces only the + final report step and produces the same zip layout, so phase 2, the + archive, the job summary and the qualification evidence are shared. Bazel's + per-test gcov collector is kept as it is: it is where the QNX transport + hands over, and re-implementing it would move the ``.gcda`` handling into + the qualified tool for no gain. diff --git a/docs/manual/constraints.rst b/docs/manual/constraints.rst index 714024a..8f54354 100644 --- a/docs/manual/constraints.rst +++ b/docs/manual/constraints.rst @@ -30,8 +30,11 @@ CSTR-01 Qualified environment Use the tool only in the environment it was validated in: Linux x86_64 host, Bazel 8.6, ``toolchains_llvm`` 1.8.0 with LLVM 22.1.7 for C++, the standard Ferrocene toolchain of ``score_toolchains_rust`` 0.10.0 or newer (built by -``ferrocene_toolchain_builder`` 1.3.1 or newer) for Rust. QNX on-target coverage -is outside this environment. Mitigates ERR-08. +``ferrocene_toolchain_builder`` 1.3.1 or newer) for Rust; for the gcov +backend the S-CORE GCC 12.2.0 toolchain (``score_bazel_cpp_toolchains``) on +Linux and QCC of QNX SDP 8.0 with the tests executed through +``score_qnx_unit_tests`` (see :ref:`CSTR-11 `). +Mitigates ERR-08. .. _cstr_coverage_scope_list: @@ -116,3 +119,19 @@ CSTR-10 Treat exit code 2 as a failed run Exit code 2 means the tool could not produce a verdict. CI must fail on it exactly like on exit code 1. Never map it to a pass. Mitigates ERR-03. + +.. _cstr_coverage_qnx_transport: + +CSTR-11 QNX: run the tests through score_qnx_unit_tests and keep the collector +------------------------------------------------------------------------------ + +On QNX the counters are written inside the QEMU guest. Only the +``run_under_qnx`` runner of ``score_qnx_unit_tests`` (0.2.0 or newer) brings +them back: it sets ``GCOV_PREFIX`` in the guest, archives the counters at exit +and extracts them into Bazel's ``COVERAGE_DIR``. The QNX coverage config must +therefore use that runner, Bazel's own per-test collector +(``--coverage_output_generator=@bazel_tools//tools/test:lcov_merger``) and +``--instrument_test_targets``, exactly as in the ``coverage:gcov`` block of the +integration workspace. Check that the QNX report lists the same files as the +Linux report of the same tree (Rust files excepted): a missing file is a lost +transport, not a coverage result. Mitigates ERR-11. diff --git a/docs/manual/known_problems.rst b/docs/manual/known_problems.rst index aa5de8a..0f8a1c8 100644 --- a/docs/manual/known_problems.rst +++ b/docs/manual/known_problems.rst @@ -50,13 +50,25 @@ stay listed with their upstream references. - Exit 127 in the test log; no profraw. - Exclude containerised or system tests from the coverage run; they keep running in the regular test jobs. - * - **QNX on-target coverage is not supported by this tool.** The - orchestrator accepts only the LLVM zip report. The gcovr-based HTML - post-processing exists but is reachable only through the consumer-side - flow of the ``communication`` repository (tooling issue #427). - - Exit 2 with ``is not the LLVM pipeline zip report`` on a gcov run. - - Use the Linux host pipeline; QNX centralisation is tracked in tooling - issue #427. + * - **QNX on-target coverage covers C++ only.** rustc emits no gcov + counters, and the LLVM profile transport from the QEMU guest is not + established yet (tooling issue #427, track 2). + - Rust sources listed as ``not-instrumented`` in ``unmapped_files.txt`` + of a QNX report. + - Measure Rust with the Linux (LLVM) run of the same tree. + * - **Vendored external headers have no data on the gcov backend.** Bazel's + per-test collector keeps only files of its instrumented-files manifest, + which never lists sources from external repositories, although gcov + itself recorded them. + - Such a header is ``no-data`` in a QNX report and measured in the Linux + report of the same tree. + - Known limitation of Bazel's collector; use the Linux report for those + headers. + * - **gcov and LLVM count different lines.** gcov reports only lines the + compiler emitted code for: unused inline functions and closing braces + have no line, while LLVM's mapping keeps unused functions at 0 %. + - Different totals for the same file on QNX and Linux. + - Expected; compare the two reports per file, do not merge them. * - **An archive is rejected by llvm-cov** because one member has no coverage mapping: the ``lib.rmeta`` of a Rust rlib, or the object of an empty translation unit. diff --git a/docs/manual/user_manual.rst b/docs/manual/user_manual.rst index 8e0b6ec..a77bb91 100644 --- a/docs/manual/user_manual.rst +++ b/docs/manual/user_manual.rst @@ -176,6 +176,75 @@ Do **not** combine ``--config=llvm_cov`` with configs that append other ``--extra_toolchains`` (for example a GCC host config): the last toolchain wins resolution and a GCC toolchain produces no covmap data. +Step 4b (optional): QNX on-target coverage, the gcov backend +------------------------------------------------------------ + +QCC is GCC-based and cannot emit LLVM coverage mapping, so QNX coverage uses +gcov counters. The tests run inside QEMU through ``score_qnx_unit_tests``, +which brings the counters back; Bazel's own collector turns them into LCOV; +score_coverage's gcov reporter produces the same report zip as on Linux. C++ +only: Rust sources in scope are listed as *not instrumentable* on this +backend and are measured by the Linux run. + +Declare a second reporter next to the LLVM one, with the gcov binary of the +QCC package (add ``score_qcc_x86_64_toolchain_pkg`` to the ``use_repo`` of +the toolchain extension): + +.. code-block:: starlark + + score_coverage_reporter( + name = "gcov_reporter_wrapper", + testonly = True, + backend = "gcov", + coverage_scope = ":coverage_scope", + gcov = "@score_qcc_x86_64_toolchain_pkg//:gcov", + tags = ["manual"], # keeps `bazel build //...` on a Linux host from fetching the QNX SDP + ) + +The ``manual`` tag matters: the target depends on the QNX SDP package, and a +wildcard build on a host without QNX credentials would otherwise fail on the +download. ``--coverage_report_generator`` names the target explicitly and is +not affected. + +and a coverage config that resets the LLVM settings, keeps Bazel's per-test +collector and points the final step at that reporter (copy and adapt the +``coverage:gcov`` block of ``integration_tests/.bazelrc``): + +.. code-block:: text + + coverage:qnx --config= # QCC, IFS toolchain, platforms + coverage:qnx --run_under=@score_qnx_unit_tests//src:run_under_qnx + coverage:qnx --test_lang_filters=cc + coverage:qnx --instrument_test_targets + coverage:qnx --noexperimental_use_llvm_covmap + coverage:qnx --noexperimental_generate_llvm_lcov + coverage:qnx --test_env=GENERATE_LLVM_LCOV --test_env=COVERAGE_GCOV_PATH --test_env=LLVM_PROFILE_CONTINUOUS_MODE + coverage:qnx --coverage_output_generator=@bazel_tools//tools/test:lcov_merger + coverage:qnx --coverage_report_generator=//tools/coverage:gcov_reporter_wrapper + +The LLVM-only rustc flags (``-Zcoverage-options=branch`` and friends) stay +out of the way as long as they live in their own ``coverage:llvm_cov`` config, +as in Step 4. If your workspace puts them on the bare ``coverage`` command +instead, declare them with the list-typed +``--@rules_rust//rust/settings:extra_rustc_flags`` and add +``coverage:qnx --@rules_rust//rust/settings:extra_rustc_flags=`` to clear +them: an empty value resets that list, whereas the repeatable singular +``extra_rustc_flag`` accumulates and cannot be reset from a config. + +Run and report as on Linux, with the platform filter for justifications: + +.. code-block:: shell + + bazel coverage --config=qnx //score/... --build_tests_only + bazel run @score_coverage//:generate_coverage_html -- --platform qnx \ + --yaml tools/coverage/coverage_justifications.yaml --archive-dir coverage_qnx_artifacts + +Known differences to the LLVM backend: gcov has no lines for unused inline +functions and for closing braces; headers a workspace target vendors from an +external repository are not measured (Bazel's collector filters them out) and +appear as ``no-data``; the report lists the toolchain's line semantics, not +LLVM's, so the two reports are compared per file, not merged. + Step 5 (optional): justifications --------------------------------- @@ -259,7 +328,8 @@ Command reference - Subtree of ``bazel-testlogs`` whose ``test.xml`` files are archived. * - ``--platform linux|qnx`` - Platform filter for justifications and default output directory - ``coverage_``. + ``coverage_``. Use ``qnx`` for reports of the gcov backend + produced from QNX on-target runs. * - ``--summary-md `` - Write the markdown summary to ```` instead of the step summary. * - ``output-dir`` diff --git a/docs/release/release_notes.rst b/docs/release/release_notes.rst index db598ee..62229fc 100644 --- a/docs/release/release_notes.rst +++ b/docs/release/release_notes.rst @@ -23,7 +23,56 @@ Release notes :security: NO :realizes: wp__module_sw_release_note -0.2.0 (unreleased) +0.3.0 (unreleased) +------------------ + +In plain words +~~~~~~~~~~~~~~ + +**Coverage of tests that run on QNX.** Until now the tool measured only what +runs on the Linux host. QNX's compiler (QCC) is GCC-based and produces gcov +counters instead of LLVM's coverage mapping, and the tests run inside a QEMU +virtual machine. This release adds a second *backend*: the QNX runner of +``score_qnx_unit_tests`` brings the counters back, Bazel's own collector turns +them into per-test data, and score_coverage's new gcov reporter produces the +same report as on Linux: the same HTML archive, the same LCOV file, the same +job summary, the same justifications and gate. A module declares one more +reporter target (``backend = "gcov"``) and one bazelrc block; see the user +manual, step 4b. + +What the QNX report does not contain: Rust sources (rustc cannot produce gcov +counters; they are listed as *not instrumentable* and measured by the Linux +run) and headers a module vendors from an external repository (Bazel's +collector drops them; also measured by the Linux run). gcov counts lines +differently from LLVM (no unused inline functions, no closing braces), so the +two reports are compared per file, not merged. + +Validated with the S-CORE GCC toolchain on Linux, which takes exactly the same +collection path as QCC on QNX, against a hand-derived ground truth; the QNX +transport itself is the one ``communication`` has used since 2026. + +Details for integrators +~~~~~~~~~~~~~~~~~~~~~~~ + +- New ``score_coverage/gcov_reporter.py`` (``//:gcov_reporter``): sums per-test + LCOV records, applies the scope, adds a zero-coverage baseline from the + ``.gcno`` notes of in-scope translation units, renders HTML and summary with + gcovr 8.6 (new pip dependency), writes ``unmapped_files.txt`` with the new + ``not-instrumented`` category. +- ``score_coverage_reporter`` gained ``backend`` and ``gcov``; ``llvm_cov`` / + ``llvm_profdata`` are required only for the LLVM backend. Existing consumer + BUILD files need no change. +- ``score_coverage_scope`` writes ``_gcno.txt`` and exports the ``gcno`` / + ``gcno_files`` output groups. +- Integration workspace: ``coverage:gcov`` config with the S-CORE GCC + toolchain, ``expected_lcov_gcov.dat`` ground truth, gcov checks in the + end-to-end script. +- New requirements ``tool_req__coverage_scope_gcno``, + ``tool_req__coverage_backend_select``, ``tool_req__coverage_gcov_merge``, + ``tool_req__coverage_gcov_baseline``, ``tool_req__coverage_gcov_html``; + potential errors ERR-11 and ERR-12; constraint CSTR-11. + +0.2.0 (2026-09-16) ------------------ In plain words diff --git a/docs/requirements/potential_errors.rst b/docs/requirements/potential_errors.rst index 1dc06bd..d12e7d8 100644 --- a/docs/requirements/potential_errors.rst +++ b/docs/requirements/potential_errors.rst @@ -116,6 +116,25 @@ requirements and constraints of use that mitigate the error. - none - good - Informational; costs review effort only. + * - ERR-11 + - gcov backend: a test's counters are lost or attributed to the wrong + file (transport from the target fails silently, per-test records + merged with the wrong semantics, the baseline overwrites test data). + - high + - weak + - :need:`tool_req__coverage_gcov_merge`, + :need:`tool_req__coverage_gcov_baseline`, + :need:`tool_req__coverage_validation_ground_truth`, + :ref:`CSTR-11 ` + * - ERR-12 + - gcov backend: an in-scope translation unit has no counters and no + notes file is found, so it disappears instead of showing 0 %. + - high + - weak + - :need:`tool_req__coverage_scope_gcno`, + :need:`tool_req__coverage_gcov_baseline`, + :need:`tool_req__coverage_report_unmapped`, + :ref:`CSTR-08 ` Classification -------------- @@ -123,6 +142,7 @@ Classification Tool impact: **yes**. An error in the *more coverage than real* direction lets a violation of the structural-coverage verification requirement go undetected. Tool error detection before qualification: **no** for ERR-02, ERR-03 and -ERR-07. The expected tool confidence level is therefore **TCL LOW**, and the +ERR-07; **weak** for ERR-11 and ERR-12 (a lost per-test record on QNX is +only noticed by comparing against the Linux report). The expected tool confidence level is therefore **TCL LOW**, and the qualification method of the S-CORE process, validation of the software tool, applies. The evaluation itself is recorded in the Tool Verification Report. diff --git a/docs/requirements/tool_requirements.rst b/docs/requirements/tool_requirements.rst index e2fe2d4..fbd4141 100644 --- a/docs/requirements/tool_requirements.rst +++ b/docs/requirements/tool_requirements.rst @@ -69,6 +69,22 @@ Scope the scope, and a ``_virtual_includes/`` path shall be mapped only when the target itself generated it. +.. tool_req:: gcno notes files accompany the scope + :id: tool_req__coverage_scope_gcno + :version: 1 + :implemented: YES + :tags: scope, gcov, ERR-01 + :safety: ASIL_B + :satisfies: stkh_req__coverage__uc_scope_completeness + + ``score_coverage_scope`` shall list, in ``_gcno.txt`` and the + ``gcno`` / ``gcno_files`` output groups, the ``.gcno`` notes files the + compiler wrote for the translation units of in-workspace targets (taken + from ``InstrumentedFilesInfo``, restricted to files the target owns), so + that the gcov backend can produce the zero-coverage baseline of every + in-scope translation unit. Under a toolchain that emits no ``.gcno`` the + manifest shall be present and empty. + .. tool_req:: Baseline objects accompany the scope :id: tool_req__coverage_scope_baseline_objects :version: 1 @@ -260,7 +276,76 @@ Report HTML with branch counts), ``lcov_report/lcov.dat`` (LCOV with line and branch records) and ``text_report/summary.txt`` (llvm-cov text summary), with ``llvm-cov`` warnings kept out of the LCOV data. When no valid per-test - output exists, the output shall be an empty zip. + output exists, the output shall be an empty zip. The gcov backend shall + produce the same layout (see :need:`tool_req__coverage_gcov_html`). + +.. tool_req:: Backend selection + :id: tool_req__coverage_backend_select + :version: 1 + :implemented: YES + :tags: report, gcov, ERR-08 + :safety: ASIL_B + :satisfies: stkh_req__coverage__uc_unified_report + + ``score_coverage_reporter`` shall accept ``backend = "llvm"`` (default, + requires ``llvm_cov`` and ``llvm_profdata``) or ``backend = "gcov"`` + (requires ``gcov``, the binary of the compiler that produced the counters) + and shall fail at analysis time when the tools of the selected backend are + missing, so that a coverage configuration can never silently run without a + matching reporter. + +.. tool_req:: gcov backend merges per-test LCOV records by summation + :id: tool_req__coverage_gcov_merge + :version: 1 + :implemented: YES + :tags: report, gcov, ERR-02 + :safety: ASIL_B + :satisfies: stkh_req__coverage__uc_unified_report + + The gcov reporter shall read every per-test LCOV file Bazel's collector + lists (ignoring ``baseline_coverage.dat``), shall normalise ``SF:`` paths + as the LLVM reporter does (workspace root, ``/proc/self/cwd/``, + configuration prefix, path map, virtual-include trees of targets outside + the scope), and shall merge records of the same file by adding line, + branch and function execution counts across tests; a branch recorded as + never reached (``-``) in every test shall stay so. The scope allowlist + shall be applied through the same selection as the LLVM backend. + +.. tool_req:: gcov backend baseline from gcno notes + :id: tool_req__coverage_gcov_baseline + :version: 1 + :implemented: YES + :tags: report, gcov, ERR-01 + :safety: ASIL_B + :satisfies: stkh_req__coverage__uc_scope_completeness + + For every allowlisted file that has no per-test data, the gcov reporter + shall run ``gcov --json-format --stdout --branch-probabilities`` over the + ``.gcno`` files of the scope and include the file with every reported line, + branch and function at zero, so that the LCOV record shows ``LH:0``. + Baseline data shall never be added to a file that has test data. A + ``.gcno`` listed in the manifest but missing shall be an error; a ``gcov`` + failure on one notes file shall be reported and shall not abort the run. + A source whose ``.gcno`` exists but that yields no lines shall be + categorised as compiled without code, and allowlisted Rust sources shall + be categorised as not instrumentable + (:need:`tool_req__coverage_report_unmapped`). + +.. tool_req:: gcov backend HTML and summary through gcovr + :id: tool_req__coverage_gcov_html + :version: 1 + :implemented: YES + :tags: report, gcov + :safety: QM + :satisfies: stkh_req__coverage__uc_unified_report + + The gcov reporter shall render ``html_report/`` with gcovr from the merged + data (``index.html`` plus one page per file with branch information, the + sources staged under their canonical paths) and ``text_report/summary.txt`` + from the same data, and shall write ``lcov_report/lcov.dat`` and + ``text_report/unmapped_files.txt`` in the same format as the LLVM backend, + so that ``generate_coverage_html``, the justification layer and the archive + need no backend-specific handling. Justifications -------------- diff --git a/docs/verification/verification_report.rst b/docs/verification/verification_report.rst index 0820bdc..27de616 100644 --- a/docs/verification/verification_report.rst +++ b/docs/verification/verification_report.rst @@ -33,6 +33,11 @@ Scope and environment Validated environment: Linux x86_64, Bazel 8.6.0, ``toolchains_llvm`` 1.8.0 with LLVM 22.1.7, ``score_toolchains_rust`` 0.10.0 (Ferrocene built by ``ferrocene_toolchain_builder`` 1.3.1), Python 3.12 (``rules_python`` 1.8.5), ``rules_rust`` 0.68.2-score. +gcov backend: ``score_bazel_cpp_toolchains`` 1.0.3 with GCC 12.2.0 on Linux, +gcovr 8.6. The QNX transport (QCC of QNX SDP 8.0, ``score_qnx_unit_tests`` +0.2.0 under QEMU) is the collection path of the ``communication`` repository +and is not exercised by this repository's CI; it is validated on a consumer +(see the release notes of the validating release). Test inventory -------------- @@ -52,12 +57,16 @@ Test inventory - report_merged_profile, report_allowlist, report_baseline_zero, report_rlib_expansion, report_missing_baseline, report_relative_paths, report_outputs, report_unmapped, scope_transitive + * - ``//score_coverage/tests:gcov_reporter_test`` + - 21 + - gcov_merge, gcov_baseline, gcov_html, report_relative_paths, + report_baseline_zero, report_allowlist, report_unmapped, report_outputs * - ``//score_coverage/tests:justify_test`` - 55 - just_yaml, just_markers, just_unknown_id, just_platform, just_missing_file * - ``//score_coverage/tests:effective_coverage_test`` - - 52 + - 53 - eff_metric, eff_stale, eff_branch_only, eff_path_match, eff_html, eff_gcovr * - ``//score_coverage/tests:generate_coverage_html_test`` @@ -67,14 +76,15 @@ Test inventory * - ``//score_coverage/tests:coverage_summary_test`` - 19 - summary_first - * - ``//score_coverage/tests/starlark:coverage_scope_tests`` (13 analysis tests) - - 13 - - scope_transitive, scope_excludes, scope_baseline_objects - * - ``integration_tests/run_integration_test.sh`` (19 end-to-end checks) - - 19 + * - ``//score_coverage/tests/starlark:coverage_scope_tests`` (14 analysis tests) + - 14 + - scope_transitive, scope_excludes, scope_baseline_objects, scope_gcno + * - ``integration_tests/run_integration_test.sh`` (22 end-to-end checks) + - 22 - validation_ground_truth, report_baseline_zero, report_relative_paths, - report_allowlist, report_unmapped, gate_exit_codes, gate_no_verdict, - just_unknown_id, artifacts, summary_first + report_allowlist, report_unmapped, gcov_merge, gcov_baseline, gcov_html, + gate_exit_codes, gate_no_verdict, just_unknown_id, artifacts, + summary_first Requirement coverage -------------------- diff --git a/integration_tests/.bazelrc b/integration_tests/.bazelrc index 22291f9..53ac122 100644 --- a/integration_tests/.bazelrc +++ b/integration_tests/.bazelrc @@ -76,3 +76,24 @@ coverage:llvm_cov --@rules_rust//rust/settings:extra_rustc_flag=-Clink-dead-code # use is a rolling (nightly-based) build. If you pin a stable-channel # Ferrocene release, drop this flag (losing the Rust branch column). coverage:llvm_cov --@rules_rust//rust/settings:extra_rustc_flag=-Zcoverage-options=branch + +# ============================================================================ +# gcov backend (GCC on Linux; the same collection path QNX/QCC uses on target) +# ============================================================================ +# Use with: bazel coverage --config=gcov //src/... --build_tests_only +coverage:gcov --nocache_test_results +coverage:gcov --cxxopt=-O0 +coverage:gcov --combined_report=lcov +coverage:gcov --dynamic_mode=off +coverage:gcov --host_platform=@score_bazel_platforms//:x86_64-linux-gcc_12.2.0-posix +coverage:gcov --platforms=@score_bazel_platforms//:x86_64-linux-gcc_12.2.0-posix +coverage:gcov --extra_toolchains=@score_gcc_x86_64_toolchain//:x86_64-linux-gcc_12.2.0 +# Bazel's own per-test gcov collector and LCOV merger; score_coverage takes +# over at the final report step. +coverage:gcov --coverage_output_generator=@bazel_tools//tools/test:lcov_merger +coverage:gcov --coverage_report_generator=//tools/coverage:gcov_reporter_wrapper +coverage:gcov --test_lang_filters=cc +# Bazel's gcov collector only keeps data of instrumented targets. Instrument +# the tests too so header-only code that only a test translation unit +# instantiates is measured (the scope allowlist still drops the test sources). +coverage:gcov --instrument_test_targets diff --git a/integration_tests/MODULE.bazel b/integration_tests/MODULE.bazel index f7e0483..8201f2d 100644 --- a/integration_tests/MODULE.bazel +++ b/integration_tests/MODULE.bazel @@ -44,6 +44,22 @@ python.toolchain( # Coverage toolchains (only active during `bazel coverage --config=llvm_cov`) # ******************************************************************************* +# gcov backend: the S-CORE GCC toolchain compiles with -fprofile-arcs +# -ftest-coverage and Bazel's own collector runs its gcov, exactly the path a +# QNX (QCC) on-target run takes; only the QEMU transport differs. +bazel_dep(name = "score_bazel_platforms", version = "1.1.0", dev_dependency = True) +bazel_dep(name = "score_bazel_cpp_toolchains", version = "1.0.3", dev_dependency = True) + +gcc = use_extension("@score_bazel_cpp_toolchains//extensions:gcc.bzl", "gcc", dev_dependency = True) +gcc.toolchain( + name = "score_gcc_x86_64_toolchain", + target_cpu = "x86_64", + target_os = "linux", + use_default_package = True, + version = "12.2.0", +) +use_repo(gcc, "score_gcc_x86_64_toolchain", "score_gcc_x86_64_toolchain_pkg") + bazel_dep(name = "score_toolchains_rust", version = "0.10.0", dev_dependency = True) bazel_dep(name = "toolchains_llvm", version = "1.8.0", dev_dependency = True) diff --git a/integration_tests/MODULE.bazel.lock b/integration_tests/MODULE.bazel.lock index 2d25a5a..36921ae 100644 --- a/integration_tests/MODULE.bazel.lock +++ b/integration_tests/MODULE.bazel.lock @@ -10,24 +10,54 @@ "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", + "https://bcr.bazel.build/modules/ape/1.0.1/MODULE.bazel": "37411cfd13bfc28cd264674d660a3ecb3b5b35b9dbe4c0b2be098683641b3fee", + "https://bcr.bazel.build/modules/ape/1.0.1/source.json": "96bc5909d1e3ccc4203272815ef874dbfd99651e240c05049f12193d16c1110b", "https://bcr.bazel.build/modules/apple_support/1.17.1/MODULE.bazel": "655c922ab1209978a94ef6ca7d9d43e940cd97d9c172fb55f94d91ac53f8610b", "https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442", "https://bcr.bazel.build/modules/apple_support/1.24.1/MODULE.bazel": "f46e8ddad60aef170ee92b2f3d00ef66c147ceafea68b6877cb45bd91737f5f8", "https://bcr.bazel.build/modules/apple_support/1.24.1/source.json": "cf725267cbacc5f028ef13bb77e7f2c2e0066923a4dab1025e4a0511b1ed258a", + "https://bcr.bazel.build/modules/aspect_bazel_lib/1.31.2/MODULE.bazel": "7bee702b4862612f29333590f4b658a5832d433d6f8e4395f090e8f4e85d442f", + "https://bcr.bazel.build/modules/aspect_bazel_lib/1.38.0/MODULE.bazel": "6307fec451ba9962c1c969eb516ebfe1e46528f7fa92e1c9ac8646bef4cdaa3f", + "https://bcr.bazel.build/modules/aspect_bazel_lib/1.42.2/MODULE.bazel": "2e0d8ab25c57a14f56ace1c8e881b69050417ff91b2fb7718dc00d201f3c3478", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.0.0/MODULE.bazel": "e118477db5c49419a88d78ebc7a2c2cea9d49600fe0f490c1903324a2c16ecd9", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.16.0/MODULE.bazel": "852f9ebbda017572a7c113a2434592dd3b2f55cd9a0faea3d4be5a09a59e4900", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.3/MODULE.bazel": "253d739ba126f62a5767d832765b12b59e9f8d2bc88cc1572f4a73e46eb298ca", - "https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.3/source.json": "ffab9254c65ba945f8369297ad97ca0dec213d3adc6e07877e23a48624a8b456", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.20.0/MODULE.bazel": "c5565bac49e1973227225b441fad1c938d498d83df62dc5da95b2fab0f0626a2", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.0/MODULE.bazel": "7fe0191f047d4fe4a4a46c1107e2350cbb58a8fc2e10913aa4322d3190dec0bf", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.0/source.json": "369df5b7f2eae82f200fff95cf1425f90dee90a0d0948122060b48150ff0e224", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "491f8681205e31bb57892d67442ce448cda4f472a8e6b3dc062865e29a64f89c", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.9.4/MODULE.bazel": "ccc41028429f894b02fde7ef67d416cba3ba5084ed9ddb9bb6107aa82d118776", + "https://bcr.bazel.build/modules/aspect_rules_js/1.33.1/MODULE.bazel": "db3e7f16e471cf6827059d03af7c21859e7a0d2bc65429a3a11f005d46fc501b", + "https://bcr.bazel.build/modules/aspect_rules_js/1.40.0/MODULE.bazel": "01a1014e95e6816b68ecee2584ae929c7d6a1b72e4333ab1ff2d2c6c30babdf1", + "https://bcr.bazel.build/modules/aspect_rules_js/1.40.0/source.json": "b6fd491369e9ef888fdef64b839023a2360caaea8eb370d2cfbfdd2a96721311", + "https://bcr.bazel.build/modules/aspect_rules_lint/0.12.0/MODULE.bazel": "e767c5dbfeb254ec03275a7701b5cfde2c4d2873676804bc7cb27ddff3728fed", + "https://bcr.bazel.build/modules/aspect_rules_lint/1.0.3/MODULE.bazel": "ed0fe929647ba21d2041e14ea3d757133ca306b72d4998e8a3d0d2f515196765", + "https://bcr.bazel.build/modules/aspect_rules_lint/1.3.1/MODULE.bazel": "06ce330900a7d6403bc8d88e5dfad6aeeb8ae40179f66bb89e69c8bf6f6b1a0b", + "https://bcr.bazel.build/modules/aspect_rules_lint/1.4.2/MODULE.bazel": "78d025facf6fa675fd6f0b62fd6a9a2bec7ef5ae1e288e5b53f4383b98017105", + "https://bcr.bazel.build/modules/aspect_rules_lint/1.4.4/MODULE.bazel": "24459eeeeb084bc3e7628c338e494746718bc17b3a3cbd94415c8df5c7c6dc37", + "https://bcr.bazel.build/modules/aspect_rules_lint/1.5.3/MODULE.bazel": "7fee71b11be63f1cf0458cd8c731712a0e672d0bb6df8879ed70249bf8dfdfdc", + "https://bcr.bazel.build/modules/aspect_rules_lint/2.3.0/MODULE.bazel": "4d36bce3c1347bb4fed5906877dce0e683cb207f4032711aff32334003bcf835", + "https://bcr.bazel.build/modules/aspect_rules_lint/2.3.0/source.json": "07c6e7333ac089e2428a02d68deb5f90e3947aa9b9ac22c26b9e8b0083122926", + "https://bcr.bazel.build/modules/aspect_rules_py/1.0.0/MODULE.bazel": "8eb29876512d3242af50a424300bec5c5f8957b455963df5f618cb7fd4e8ae19", "https://bcr.bazel.build/modules/aspect_rules_py/1.4.0/MODULE.bazel": "6fd29b93207a31445d5d3ab9d9882fd5511e43c95e8e82e7492872663720fd44", "https://bcr.bazel.build/modules/aspect_rules_py/1.4.0/source.json": "fb1ba946478fb6dbb26d49307d756b0fd2ff88be339af23c39c0397d59143d2c", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.5/MODULE.bazel": "5a3c8013c3ba9ebc0a65efda40e4376b869e1260873c98020504feed55244ce8", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.8/MODULE.bazel": "aa975a83e72bcaac62ee61ab12b788ea324a1d05c4aab28aadb202f647881679", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.8/source.json": "786cbc49377fb6bf4859aec5b1c61f8fc26b08e9fdb929e2dde2e1e2a406bd24", + "https://bcr.bazel.build/modules/bazel_features/0.1.0/MODULE.bazel": "47011d645b0f949f42ee67f2e8775188a9cf4a0a1528aa2fa4952f2fd00906fd", + "https://bcr.bazel.build/modules/bazel_features/1.0.0/MODULE.bazel": "d7f022dc887efb96e1ee51cec7b2e48d41e36ff59a6e4f216c40e4029e1585bf", + "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.13.0/MODULE.bazel": "c14c33c7c3c730612bdbe14ebbb5e61936b6f11322ea95a6e91cd1ba962f94df", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.2.0/MODULE.bazel": "122b2b606622afbaa498913d54f52d9bcd2d19a5edd1bd6d6c5aa17441c4d5f9", "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", @@ -39,8 +69,10 @@ "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0-beta.1/MODULE.bazel": "407729e232f611c3270005b016b437005daa7b1505826798ea584169a476e878", "https://bcr.bazel.build/modules/bazel_lib/3.0.0-rc.0/MODULE.bazel": "d6e00979a98ac14ada5e31c8794708b41434d461e7e7ca39b59b765e6d233b18", - "https://bcr.bazel.build/modules/bazel_lib/3.0.0-rc.0/source.json": "7051768079aa19302df2b9446ad0889839fd08b7d59851eba2c99234d665c9ba", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0/MODULE.bazel": "22b70b80ac89ad3f3772526cd9feee2fa412c2b01933fea7ed13238a448d370d", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0/source.json": "895f21909c6fba01d7c17914bb6c8e135982275a1b18cdaa4e62272217ef1751", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", @@ -55,13 +87,24 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", + "https://bcr.bazel.build/modules/buildifier_prebuilt/6.1.2/MODULE.bazel": "2ef4962c8b0b6d8d21928a89190755619254459bc67f870dc0ccb9ba9952d444", + "https://bcr.bazel.build/modules/buildifier_prebuilt/6.4.0/MODULE.bazel": "37389c6b5a40c59410b4226d3bb54b08637f393d66e2fa57925c6fcf68e64bf4", "https://bcr.bazel.build/modules/buildifier_prebuilt/7.3.1/MODULE.bazel": "537faf0ad9f5892910074b8e43b4c91c96f1d5d86b6ed04bdbe40cf68aa48b68", "https://bcr.bazel.build/modules/buildifier_prebuilt/8.2.0.2/MODULE.bazel": "a9b689711d5b69f9db741649b218c119b9fdf82924ba390415037e09798edd03", - "https://bcr.bazel.build/modules/buildifier_prebuilt/8.2.0.2/source.json": "51eb0a4b38aaaeab7fa64361576d616c4d8bfd0f17a0a10184aeab7084d79f8e", + "https://bcr.bazel.build/modules/buildifier_prebuilt/8.5.1/MODULE.bazel": "77f2a1958d1d07376dd3ce3ae16540f2c1b01921c1fd21930827271260e75a66", + "https://bcr.bazel.build/modules/buildifier_prebuilt/8.5.1/source.json": "ae9f3d9dc7bec033976cf47165a78788bebad2b5c272241063ae31bad8664fd4", "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/download_utils/1.0.1/MODULE.bazel": "f1d0afade59e37de978506d6bbf08d7fe5f94964e86944aaf58efcead827b41b", + "https://bcr.bazel.build/modules/download_utils/1.0.1/source.json": "05ddc5a3b1f7d8f3e5e0fd1617479e1cf72d63d59ab2b1f0463557a14fc6be0a", "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8", "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/source.json": "fa7b512dfcb5eafd90ce3959cf42a2a6fe96144ebbb4b3b3928054895f2afac2", + "https://bcr.bazel.build/modules/gazelle/0.27.0/MODULE.bazel": "3446abd608295de6d90b4a8a118ed64a9ce11dcb3dda2dc3290a22056bd20996", + "https://bcr.bazel.build/modules/gazelle/0.30.0/MODULE.bazel": "f888a1effe338491f35f0e0e85003b47bb9d8295ccba73c37e07702d8d31c65b", + "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", + "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", + "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", + "https://bcr.bazel.build/modules/gazelle/0.34.0/source.json": "cdf0182297e3adabbdea2da88d5b930b2ee5e56511c3e7d6512069db6315a1f7", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", @@ -70,12 +113,14 @@ "https://bcr.bazel.build/modules/helly25_bzl/0.4.3/MODULE.bazel": "9c20052fd3f1fb767c48b78c1bbc46f501a4ca69d14558429d1234e68e449f68", "https://bcr.bazel.build/modules/helly25_bzl/0.4.3/source.json": "e8c54d81e72633fb6f1d23c7e2d44e0b39b1caef2a256141136a2f63e6204b78", "https://bcr.bazel.build/modules/jq.bzl/0.1.0/MODULE.bazel": "2ce69b1af49952cd4121a9c3055faa679e748ce774c7f1fda9657f936cae902f", - "https://bcr.bazel.build/modules/jq.bzl/0.1.0/source.json": "746bf13cac0860f091df5e4911d0c593971cd8796b5ad4e809b2f8e133eee3d5", + "https://bcr.bazel.build/modules/jq.bzl/0.6.0/MODULE.bazel": "26ec5118e66a55fef36f8ea39d6415d55fc966671fe4d61a6b9ef0cd6bc7b6a1", + "https://bcr.bazel.build/modules/jq.bzl/0.6.0/source.json": "2ed4e35b9fa9505495784114f7637fbde5846ec14af917841b6c045d877f20eb", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", - "https://bcr.bazel.build/modules/package_metadata/0.0.2/source.json": "e53a759a72488d2c0576f57491ef2da0cf4aab05ac0997314012495935531b73", + "https://bcr.bazel.build/modules/package_metadata/0.0.3/MODULE.bazel": "77890552ecea9e284b5424c9de827a58099348763a4359e975c359a83d4faa83", + "https://bcr.bazel.build/modules/package_metadata/0.0.3/source.json": "742075a428ad12a3fa18a69014c2f57f01af910c6d9d18646c990200853e641a", "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", @@ -85,21 +130,28 @@ "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", - "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", + "https://bcr.bazel.build/modules/platforms/1.1.0/MODULE.bazel": "1c0c09f5bdcf4b3f924720d2478a3711cb39f4977019ca5988685e5b7e18b3d2", + "https://bcr.bazel.build/modules/platforms/1.1.0/source.json": "fcf351c47596c939140ab0d333dfdd08ed1ea6ce33c2fe70c12493a301cf1344", "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", - "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", + "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", + "https://bcr.bazel.build/modules/protobuf/29.1/source.json": "04cca85dce26b895ed037d98336d860367fe09919208f2ad383f0df1aff63199", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", "https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4", "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", + "https://bcr.bazel.build/modules/rules_buf/0.1.1/MODULE.bazel": "6189aec18a4f7caff599ad41b851ab7645d4f1e114aa6431acf9b0666eb92162", + "https://bcr.bazel.build/modules/rules_buf/0.5.2/MODULE.bazel": "5f2492d284ab9bedf2668178303abf5f3cd7d8cdf85d768951008e88456e9c6a", + "https://bcr.bazel.build/modules/rules_buf/0.5.2/source.json": "41876d4834c0832de4b393de6e55dfd1cb3b25d3109e4ba90eb7fb57c560e0d9", "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", @@ -118,25 +170,37 @@ "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", + "https://bcr.bazel.build/modules/rules_diff/1.0.0/MODULE.bazel": "1739509d8db9a6cd7d3584822340d3dfe1f9f27e62462fbca60aa061d88741b2", + "https://bcr.bazel.build/modules/rules_diff/1.0.0/source.json": "fc3824aed007b4db160ffb994036c6e558550857b6634a8e9ccee3e74c659312", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", + "https://bcr.bazel.build/modules/rules_go/0.33.0/MODULE.bazel": "a2b11b64cd24bf94f57454f53288a5dacfe6cb86453eee7761b7637728c1910c", + "https://bcr.bazel.build/modules/rules_go/0.38.1/MODULE.bazel": "fb8e73dd3b6fc4ff9d260ceacd830114891d49904f5bda1c16bc147bcc254f71", + "https://bcr.bazel.build/modules/rules_go/0.39.1/MODULE.bazel": "d34fb2a249403a5f4339c754f1e63dc9e5ad70b47c5e97faee1441fc6636cd61", + "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", + "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", + "https://bcr.bazel.build/modules/rules_go/0.42.0/source.json": "33cd3d725806ad432753c4263ffd0459692010fdc940cce60b2c0e32282b45c5", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", "https://bcr.bazel.build/modules/rules_java/6.3.0/MODULE.bazel": "a97c7678c19f236a956ad260d59c86e10a463badb7eb2eda787490f4c969b963", "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", + "https://bcr.bazel.build/modules/rules_java/7.0.6/MODULE.bazel": "6ddb07d9857a1a3accc9f6d005f20c969c4659c7710e6269a51db3527e0ea969", "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/8.11.0/MODULE.bazel": "c3d280bc5ff1038dcb3bacb95d3f6b83da8dd27bba57820ec89ea4085da767ad", + "https://bcr.bazel.build/modules/rules_java/8.13.0/MODULE.bazel": "0444ebf737d144cf2bb2ccb368e7f1cce735264285f2a3711785827c1686625e", "https://bcr.bazel.build/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", "https://bcr.bazel.build/modules/rules_java/8.15.1/MODULE.bazel": "5071eebf0fd602ab0617f846e0e0d8f388d66c961513c736e0ac4a1dcde3ff2c", "https://bcr.bazel.build/modules/rules_java/8.15.1/source.json": "e48286d5819767bc5b3d457539ae7f94e28a9b3e55d092d5c47176cb6a2a289b", "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", + "https://bcr.bazel.build/modules/rules_java/8.6.3/MODULE.bazel": "e90505b7a931d194245ffcfb6ff4ca8ef9d46b4e830d12e64817752e0198e2ed", "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", @@ -151,38 +215,59 @@ "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", + "https://bcr.bazel.build/modules/rules_multirun/0.9.0/MODULE.bazel": "32d628ef586b5b23f67e55886b7bc38913ea4160420d66ae90521dda2ff37df0", + "https://bcr.bazel.build/modules/rules_multirun/0.9.0/source.json": "e882ba77962fa6c5fe68619e5c7d0374ec9a219fb8d03c42eadaf6d0243771bd", + "https://bcr.bazel.build/modules/rules_multitool/0.11.0/MODULE.bazel": "8d9dda78d2398e136300d3ef4fbcc89ede7c32c158d8c016fa7d032df41c4aaf", + "https://bcr.bazel.build/modules/rules_multitool/0.4.0/MODULE.bazel": "15517987d5c00c9e7faab41fbe22ee67a350b6eabcc1e08baded5c6d9025897f", "https://bcr.bazel.build/modules/rules_multitool/1.11.1/MODULE.bazel": "f826d2d394e8d964e44ebb4a75ebcfe4e9cd4eb150e2ddcd60398ffeb939696a", "https://bcr.bazel.build/modules/rules_multitool/1.11.1/source.json": "201f43de1d35bd17f25a4fed3ba5a2ec500ef5e08b7d4b341bb9fd39cef0cbc6", + "https://bcr.bazel.build/modules/rules_multitool/1.2.0/MODULE.bazel": "8d818d6104f4030930291bbbbc5684702c237dbcdee7229097543e6a6035adaa", + "https://bcr.bazel.build/modules/rules_multitool/1.9.0/MODULE.bazel": "8a042b0dbf35e4aaa94c28ad69efa75c9e673e9ea4bd5c0fb70bab75ef9c636b", + "https://bcr.bazel.build/modules/rules_nodejs/5.8.2/MODULE.bazel": "6bc03c8f37f69401b888023bf511cb6ee4781433b0cb56236b2e55a21e3a026a", + "https://bcr.bazel.build/modules/rules_nodejs/6.5.2/MODULE.bazel": "7f9ea68a0ce6d82905ce9f74e76ab8a8b4531ed4c747018c9d76424ad0b3370d", + "https://bcr.bazel.build/modules/rules_nodejs/6.5.2/source.json": "6a6ca0940914d55c550d1417cad13a56c9900e23f651a762d8ccc5a64adcf661", "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", "https://bcr.bazel.build/modules/rules_pkg/1.1.0/MODULE.bazel": "9db8031e71b6ef32d1846106e10dd0ee2deac042bd9a2de22b4761b0c3036453", "https://bcr.bazel.build/modules/rules_pkg/1.1.0/source.json": "fef768df13a92ce6067e1cd0cdc47560dace01354f1d921cfb1d632511f7d608", "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc2/MODULE.bazel": "e17f94f8a347e2c808517b65d74988839d2d62daceb50073e44060193b785eb1", + "https://bcr.bazel.build/modules/rules_proto/6.0.0/MODULE.bazel": "b531d7f09f58dce456cd61b4579ce8c86b38544da75184eadaf0a7cb7966453f", "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", - "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", + "https://bcr.bazel.build/modules/rules_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96", + "https://bcr.bazel.build/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e", "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", + "https://bcr.bazel.build/modules/rules_python/0.26.0/MODULE.bazel": "42cb98cd15954e83b96b540dcc6d5a618eb061f056147ac4ea46e687a066a7c7", + "https://bcr.bazel.build/modules/rules_python/0.27.1/MODULE.bazel": "65dc875cc1a06c30d5bbdba7ab021fd9e551a6579e408a3943a61303e2228a53", "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", "https://bcr.bazel.build/modules/rules_python/0.29.0/MODULE.bazel": "2ac8cd70524b4b9ec49a0b8284c79e4cd86199296f82f6e0d5da3f783d660c82", "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", "https://bcr.bazel.build/modules/rules_python/1.0.0/MODULE.bazel": "898a3d999c22caa585eb062b600f88654bf92efb204fa346fb55f6f8edffca43", + "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", "https://bcr.bazel.build/modules/rules_python/1.8.5/MODULE.bazel": "28b2d79ed8368d7d45b34bacc220e3c0b99cbcd9392641961b849e4c3f55dd30", "https://bcr.bazel.build/modules/rules_python/1.8.5/source.json": "e261b03c8804f2582c9536013f987e1ea105a2b38c238aa2ac8f98fc34c8b18a", "https://bcr.bazel.build/modules/rules_rust/0.56.0/MODULE.bazel": "3295b00757db397122092322fe1e920be7f5c9fbfb8619138977e820f2cbbbae", + "https://bcr.bazel.build/modules/rules_rust/0.61.0/MODULE.bazel": "0318a95777b9114c8740f34b60d6d68f9cfef61e2f4b52424ca626213d33787b", + "https://bcr.bazel.build/modules/rules_rust/0.67.0/MODULE.bazel": "87c3816c4321352dcfd9e9e26b58e84efc5b21351ae3ef8fb5d0d57bde7237f5", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", + "https://bcr.bazel.build/modules/rules_shell/0.4.0/MODULE.bazel": "0f8f11bb3cd11755f0b48c1de0bbcf62b4b34421023aa41a2fc74ef68d9584f0", "https://bcr.bazel.build/modules/rules_shell/0.4.1/MODULE.bazel": "00e501db01bbf4e3e1dd1595959092c2fadf2087b2852d3f553b5370f5633592", + "https://bcr.bazel.build/modules/rules_shell/0.5.0/MODULE.bazel": "8c8447370594d45539f66858b602b0bb2cb2d3401a4ebb9ad25830c59c0f366d", "https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b", "https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c", "https://bcr.bazel.build/modules/sphinxdocs/2.2.0/MODULE.bazel": "e046c573919d72605d62c352a08d9223a10aafef3a7cb70d0fe253ebdd97019e", "https://bcr.bazel.build/modules/sphinxdocs/2.2.0/source.json": "b1da19a3d14a1dd8aa6a9ccaedc42bbe0313c8160a77ba5cca336cca1315298d", + "https://bcr.bazel.build/modules/stardoc/0.5.0/MODULE.bazel": "f9f1f46ba8d9c3362648eea571c6f9100680efc44913618811b58cc9c02cd678", "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.5.4/MODULE.bazel": "6569966df04610b8520957cb8e97cf2e9faac2c0309657c537ab51c16c18a2a4", "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", "https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd", "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", @@ -190,14 +275,19 @@ "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", "https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468", + "https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351", "https://bcr.bazel.build/modules/tar.bzl/0.6.0/MODULE.bazel": "a3584b4edcfafcabd9b0ef9819808f05b372957bbdff41601429d5fd0aac2e7c", "https://bcr.bazel.build/modules/tar.bzl/0.6.0/source.json": "4a620381df075a16cb3a7ed57bd1d05f7480222394c64a20fa51bdb636fda658", + "https://bcr.bazel.build/modules/toolchain_utils/1.0.2/MODULE.bazel": "9b8be503a4fcfd3b8b952525bff0869177a5234d5c35dc3e566b9f5ca2f755a1", + "https://bcr.bazel.build/modules/toolchain_utils/1.0.2/source.json": "88769ec576dddacafd8cca4631812cf8eead89f10a29d9405d9f7a553de6bf87", "https://bcr.bazel.build/modules/toolchains_llvm/1.8.0/MODULE.bazel": "68259b66e5fb84f94fa37125ad476c3eb8edf602a34bf58377cde9a16dd8fa98", "https://bcr.bazel.build/modules/toolchains_llvm/1.8.0/source.json": "70d80fe5b626a3fadf812af41dda4a028640a1184f5a3c7e6cb20130d93ed783", + "https://bcr.bazel.build/modules/toolchains_protoc/0.2.1/MODULE.bazel": "2f08433ff5e659069b3a1abfee2377d68f510f2de1da50678ed992c455b4ff91", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072", "https://bcr.bazel.build/modules/yq.bzl/0.1.1/source.json": "2d2bad780a9f2b9195a4a370314d2c17ae95eaa745cefc2e12fbc49759b15aa3", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198", @@ -209,21 +299,47 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/abseil-cpp/20230802.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/abseil-cpp/20230802.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/abseil-cpp/20240116.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/ape/1.0.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/apple_support/1.17.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/apple_support/1.23.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/apple_support/1.24.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/1.31.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/1.38.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/1.42.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.0.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.16.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.19.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.20.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.22.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.9.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_js/1.33.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_js/1.40.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_lint/0.12.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_lint/1.0.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_lint/1.3.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_lint/1.4.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_lint/1.4.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_lint/1.5.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_lint/2.3.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_py/1.0.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_py/1.4.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_tools_telemetry/0.2.5/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_tools_telemetry/0.2.8/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/0.1.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.1.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.1.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.10.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.11.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.13.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.15.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.17.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.18.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.19.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.2.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.21.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.27.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.28.0/MODULE.bazel": "not found", @@ -234,7 +350,9 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.4.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.9.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.9.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_lib/3.0.0-beta.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_lib/3.0.0-rc.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_lib/3.0.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.0.3/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.1.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.2.0/MODULE.bazel": "not found", @@ -248,19 +366,30 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.7.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.8.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.8.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/buildifier_prebuilt/6.1.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/buildifier_prebuilt/6.4.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/buildifier_prebuilt/7.3.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/buildifier_prebuilt/8.2.0.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/buildifier_prebuilt/8.5.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/buildozer/7.1.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/download_utils/1.0.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/gazelle/0.27.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/gazelle/0.30.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/gazelle/0.32.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/gazelle/0.33.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/gazelle/0.34.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/google_benchmark/1.8.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.11.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.14.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/helly25_bzl/0.4.3/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/jq.bzl/0.1.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/jq.bzl/0.6.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/jsoncpp/1.9.5/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/libpfm/4.11.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/package_metadata/0.0.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/package_metadata/0.0.3/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/0.0.10/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/0.0.11/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/0.0.4/MODULE.bazel": "not found", @@ -270,16 +399,22 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/0.0.8/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/0.0.9/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/1.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/1.1.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/protobuf/21.7/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/protobuf/27.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/protobuf/27.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/protobuf/29.0-rc2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/protobuf/29.0-rc3/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/protobuf/29.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/protobuf/29.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/protobuf/3.19.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/protobuf/3.19.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/protobuf/3.19.6/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/pybind11_bazel/2.11.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/re2/2023-09-01/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_android/0.1.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_buf/0.1.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_buf/0.5.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.0.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.0.10/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.0.13/MODULE.bazel": "not found", @@ -297,23 +432,33 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.2.17/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.2.4/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.2.8/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_diff/1.0.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_fuzzing/0.5.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_go/0.33.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_go/0.38.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_go/0.39.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_go/0.41.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_go/0.42.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/4.0.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/5.3.5/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/6.0.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/6.3.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/6.4.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/6.5.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/7.0.6/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/7.10.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/7.12.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/7.2.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/7.3.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/7.6.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/8.11.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/8.13.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/8.14.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/8.15.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/8.3.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/8.5.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/8.6.3/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_jvm_external/4.4.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_jvm_external/5.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_jvm_external/5.2/MODULE.bazel": "not found", @@ -325,56 +470,117 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_license/0.0.3/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_license/0.0.7/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_license/1.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_multirun/0.9.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_multitool/0.11.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_multitool/0.4.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_multitool/1.11.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_multitool/1.2.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_multitool/1.9.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_nodejs/5.8.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_nodejs/6.5.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_pkg/0.7.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_pkg/1.0.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_pkg/1.1.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/4.0.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/6.0.0-rc2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/6.0.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/6.0.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/7.0.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/7.1.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/0.10.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/0.23.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/0.25.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/0.26.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/0.27.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/0.28.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/0.29.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/0.31.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/0.4.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/0.40.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/1.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/1.4.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/1.8.5/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_rust/0.56.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_rust/0.61.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_rust/0.67.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_rust/0.68.2-score/MODULE.bazel": "37be8dee6df19d666c1d4266e1266d82012aa83bd82de38b3100fd7f641d064b", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_rust/0.68.2-score/source.json": "f88ad98dd08f296a546677e86ad42b20f61851e41a9fd3e0449971162fcaf784", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_shell/0.2.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_shell/0.3.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_shell/0.4.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_shell/0.4.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_shell/0.5.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_shell/0.6.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_cpp_toolchains/1.0.3/MODULE.bazel": "599f3f1123bc1400ade5bdd0c7c5470531885cf2fa1fa30024f23d5d21e6d58d", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_cpp_toolchains/1.0.3/source.json": "47eab63b3a98244787a9ab611f825d9c62d48bfdb5000173144d2f9e24b521b8", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_platforms/0.0.3/MODULE.bazel": "14c96e378c08705a46abe0799d6236fe3095c342c34f83f8d1b3f6046ce00651", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_platforms/0.0.3/source.json": "1a853ab23455388d550a9aecf3d8b53ec73de50e7fe2914d9269a3c698bf3624", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_platforms/1.1.0/MODULE.bazel": "e5c656abcad121a89cbabad3550bb20167a6865de219ef6e1c68ca30567e1c32", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_platforms/1.1.0/source.json": "9797d56e9dd68e9d3e8b674402df2d6d5e2174f84184ca4cbee78fc07dc117d9", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_cr_checker/0.2.2/MODULE.bazel": "dc36d9c35543db918c3fb5b93a8e684431f56c7c784cf2a1b90f35802a373c98", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_cr_checker/0.3.1/MODULE.bazel": "f49e037d7fbc0b2a8b2734fc6b47334e8cc8589ca7a5aa0f3ccca85cc5f79fac", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_dash_license_checker/0.1.1/MODULE.bazel": "76681dbd2d45b5c540869a2337174086c56c54953aab1d02cd878b59d31d13a5", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_devcontainer/1.11.0/MODULE.bazel": "fb6041c96e949a151307b4690ee81cf5d9254d6bf79540184b16567b04397171", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_devcontainer/1.11.0/source.json": "b379a2499cf06f16de65c2981665c3723afba9e858ed16b453f044882067f56f", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/0.2.4/MODULE.bazel": "ea4801e96c87e2b8650a0fa9e5fed9b8bdbef05c1bc3e30003ba527d5af60a43", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/0.2.6/MODULE.bazel": "1af2963e91c6472555e222f0aba3dc2f5492d04598298209a361978ee3e321e3", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/0.3.3/MODULE.bazel": "95d2b7d44d461c1cf9bd016605f740716fd4ea1303f5f2ed93de3566b90feb1b", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/1.0.0-RC1/MODULE.bazel": "e118b5cbdc453cde83b5ce481107d8e4a713d3458550b9d10445046d4bba2ff3", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/1.0.0/MODULE.bazel": "a3ad204b7412c02a899034d78de62b5549bafba5530a256d1007cb3f4ed20a11", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/1.0.2-CW1/MODULE.bazel": "08d30eba6a1c502096abd65ebb759afb35f988b31e4b72ab5866177703cfec77", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/1.0.2/MODULE.bazel": "259894df09b4aa6bb5152717b03c3c06a3fd4ad583a7119c52f3a2f0d277b857", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/1.2.0/MODULE.bazel": "a2b10950d585e14b09a6266025c0624b42101f72d3c4efe9591716b8713ede47", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/2.0.0/MODULE.bazel": "0c850a488fd50067b28726bfd7330a6970e36b63e67ea06efd5fbbc813e7be5c", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/2.0.3/MODULE.bazel": "9b945514727190d4c381d8965b972884ba04ce105260ffd2b3c9df51f206ebfe", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/8.0.1/MODULE.bazel": "446b0462f893021e3133b2f95672ae37dfe2ade434b6052c91530043e4d89993", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/8.1.1/MODULE.bazel": "4740f5e4229753b2ec023df5ce9c24c3472ad1e6d700a970e3e96f7347e65a1b", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/8.1.1/source.json": "189c748d57a732e0653af377bb7635253ed7fc135f33c2f107c4d8a4a17259c5", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_format_checker/0.1.1/MODULE.bazel": "1acc254faa90e9f97b79ac69af25b6c21c561f8d6079914f6352b9b20d26bd37", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_platform/0.1.0/MODULE.bazel": "cc9eae86e76f2a930510ed6e50ec991bb5661687e24881685b39c322087adf6f", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_platform/0.1.1/MODULE.bazel": "eb086ba99f9319371fbbd0a9252dfd27b0817039b88bd4d691602974b1ada005", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.0.4/MODULE.bazel": "f74302cb90a7c4878db302276afae82966878099861dcfca3ef43256131dab52", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.0.5/MODULE.bazel": "ed17c232ebd65e9d50fd5c1832f90f95ffe95b2a1113d63a176295a2af64d111", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.1.0/MODULE.bazel": "97dd927309f87ecb73629725683028a5dbb37a49b1159c771292e6993569055b", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.1.1-Beta/MODULE.bazel": "7ab8931f4b2754b728101ed5a2afda458b82b885f5cfdb9d8c4c62b5c1a2da7e", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.1.1/MODULE.bazel": "35c84c7cec7d15678f63145054bb74367afb65929724c2115095d5e1b96fca94", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.2.0/MODULE.bazel": "ec092bf01731a1866352d7b8aaa0e30cae319667abc8cf7a86aec0bdb8555a0e", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.3.1/MODULE.bazel": "29666e38fbc76eddd6676e594f225e474d130dce9c3a9d224e59ae7a499c4575", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.3.2/MODULE.bazel": "a32390ef217cef9a811408b0a1c5aeed1398c377aa846f5d5416d7b95b4e4366", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.3.2/source.json": "213a66278c43fb27f8ca295e721bc05daed7ad9438f1173c7cc57cabd969de11", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process_description/2.1.2/MODULE.bazel": "1076b36d2d05ab1a18df0746f6a545869eec6927019d651af99d99ef056e2023", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process_description/2.1.2/source.json": "f76099f076243e2641803971e2a95ed27f24756c9bdda5d10a808c664996f0fc", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_python_basics/0.3.0/MODULE.bazel": "785ddd5295213e36c31ab86bdc34f29c0f7d1b72e9abd931bb08f42c0e48e2e9", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_python_basics/0.3.1/MODULE.bazel": "99c491109937542e61df090222666a8613ef946fa7bb2b2d5ba648b2baba03ad", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_python_basics/0.3.2/MODULE.bazel": "f25490f64035a0e3a0d53ad9cb6164e8325ce6cf2a7ee68c6ae153840cb2497e", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_python_basics/0.3.4/MODULE.bazel": "53bd16dfbb1fb8ecf6822fb26f9f4e8333bac7b14d12bb02bf84078063820a31", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_rust_policies/0.0.2/MODULE.bazel": "ade2bad4a331b02d9b7e7d9842e8de8c6fded6186486e02c4f7db5cd4b71d34d", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_rust_policies/0.0.2/source.json": "fbcbc738e652b0c68d5d28dd1db09f2e643dc111f5739b2f6af7ec56c2e88043", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_starpls_lsp/0.1.0/MODULE.bazel": "b2f8c4c8d8e851706255ff9002b448bff6e040b8f0c6adedbde2a09375aa16cc", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_toolchains_rust/0.10.0/MODULE.bazel": "535296e6cdef55a506c580ca2ce541aa9ddefb354de1a24ab2bd4addc939282b", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_toolchains_rust/0.10.0/source.json": "8bda773be264da16d2a82a03ebb737421dd4a35855f1e9a5d03d9722d84c1df5", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_tooling/1.0.2/MODULE.bazel": "e70f396375b9d612b4f41ebceff7f18f68ab423b14625c138a354cc01bc62a10", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_tooling/1.1.2/MODULE.bazel": "56d08309931cfad67c2b6691207bb5f761a3946830d620c630d2436630e6b499", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_tooling/1.1.2/source.json": "f37e462ac9bb3bca49b944ed51e7b932b3822ff434a56c6cc5e1288ff5a9db01", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/sphinxdocs/2.2.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.5.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.5.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.5.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.5.4/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.5.6/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.6.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.7.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.7.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.7.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/tar.bzl/0.2.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/tar.bzl/0.5.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/tar.bzl/0.6.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/toolchain_utils/1.0.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/toolchains_llvm/1.8.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/toolchains_protoc/0.2.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/yq.bzl/0.1.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/zlib/1.2.11/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/zlib/1.2.12/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/zlib/1.3.1/MODULE.bazel": "not found" }, @@ -382,7 +588,7 @@ "moduleExtensions": { "@@aspect_rules_py+//py:extensions.bzl%py_tools": { "general": { - "bzlTransitiveDigest": "uh/OnBspuZDYzeAio/cE3jPFXYyt5/s0JxMJ1pkhoMo=", + "bzlTransitiveDigest": "VfJYy1B5v3oMq9SNoz4hGTKf+uJgqbxHJYD8rdhivHo=", "usagesDigest": "NC1b49l5tenTBVWEUGzzC0j5Kg1GH+l5lBw5JRCldIU=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -490,6 +696,38 @@ ] } }, + "@@aspect_tools_telemetry+//:extension.bzl%telemetry": { + "general": { + "bzlTransitiveDigest": "gA7tPEdJXhskzPIEUxjX9IdDrM6+WjfbgXJ8Ez47umk=", + "usagesDigest": "z8cWWnVpO8J/dzJXjYbJOFOppzxrQWI6B21RNkSjQI4=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "aspect_tools_telemetry_report": { + "repoRuleId": "@@aspect_tools_telemetry+//:extension.bzl%tel_repository", + "attributes": { + "deps": { + "aspect_rules_lint": "2.3.0", + "aspect_tools_telemetry": "0.2.8" + } + } + } + }, + "recordedRepoMappingEntries": [ + [ + "aspect_tools_telemetry+", + "aspect_bazel_lib", + "aspect_bazel_lib+" + ], + [ + "aspect_tools_telemetry+", + "bazel_skylib", + "bazel_skylib+" + ] + ] + } + }, "@@pybind11_bazel+//:python_configure.bzl%extension": { "general": { "bzlTransitiveDigest": "D2/qWHU6yQFwRG7Bb+caqrYMha5avsASao2vERrxK24=", @@ -524,6 +762,31 @@ ] } }, + "@@rules_buf+//buf:extensions.bzl%buf": { + "general": { + "bzlTransitiveDigest": "dSWqckK2ILN7aDIDHfv+Qrl1fb1hF7o7MDXY6T8C41s=", + "usagesDigest": "vxN6C2h72rUERbAmd1476FWpxdxo1NhYoY5JSFXJT3g=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "rules_buf_toolchains": { + "repoRuleId": "@@rules_buf+//buf/internal:toolchain.bzl%buf_download_releases", + "attributes": { + "version": "v1.47.2", + "sha256": "1b37b75dc0a777a0cba17fa2604bc9906e55bb4c578823d8b7a8fe3fc9fe4439" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_buf+", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, "@@rules_fuzzing+//fuzzing/private:extensions.bzl%non_module_dependencies": { "general": { "bzlTransitiveDigest": "4LouzhF/yT117s7peGnNs9ROomiJXC6Zl5R0oI21jho=", @@ -607,6 +870,84 @@ ] } }, + "@@rules_go+//go:extensions.bzl%go_sdk": { + "os:linux,arch:amd64": { + "bzlTransitiveDigest": "6+xDMUxUNCx/1D7ofV87ePScYp4E80x0ngSOjdU4Ly4=", + "usagesDigest": "igIBXyqNg9Be63Cuu6kZxOeoDRDMqxSv8BcoWiqSh3w=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "go_default_sdk": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_download_sdk_rule", + "attributes": { + "goos": "", + "goarch": "", + "sdks": {}, + "experiments": [], + "patches": [], + "patch_strip": 0, + "urls": [ + "https://dl.google.com/go/{}" + ], + "version": "1.21.1", + "strip_prefix": "go" + } + }, + "go_host_compatible_sdk_label": { + "repoRuleId": "@@rules_go+//go/private:extensions.bzl%host_compatible_toolchain", + "attributes": { + "toolchain": "@go_default_sdk//:ROOT" + } + }, + "go_toolchains": { + "repoRuleId": "@@rules_go+//go/private:sdk.bzl%go_multiple_toolchains", + "attributes": { + "prefixes": [ + "_0000_go_default_sdk_" + ], + "geese": [ + "" + ], + "goarchs": [ + "" + ], + "sdk_repos": [ + "go_default_sdk" + ], + "sdk_types": [ + "remote" + ], + "sdk_versions": [ + "1.21.1" + ] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "bazel_features+", + "bazel_features_globals", + "bazel_features++version_extension+bazel_features_globals" + ], + [ + "bazel_features+", + "bazel_features_version", + "bazel_features++version_extension+bazel_features_version" + ], + [ + "rules_go+", + "bazel_features", + "bazel_features+" + ], + [ + "rules_go+", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { "bzlTransitiveDigest": "nvW/NrBXlAmiQw99EMGKkLaD2KbNp2mQDlxdfpr+0Ls=", @@ -671,6 +1012,140 @@ ] } }, + "@@rules_nodejs+//nodejs:extensions.bzl%node": { + "general": { + "bzlTransitiveDigest": "FmfMiNXAxRoLWw3NloQbssosE1egrSvzirbQnso7j7E=", + "usagesDigest": "+/eD+bbRTXQvCxo1j0JSXtArQ5RZAZSjW14VyIMoWxY=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "nodejs_linux_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "include_headers": false, + "platform": "linux_amd64" + } + }, + "nodejs_linux_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "include_headers": false, + "platform": "linux_arm64" + } + }, + "nodejs_linux_s390x": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "include_headers": false, + "platform": "linux_s390x" + } + }, + "nodejs_linux_ppc64le": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "include_headers": false, + "platform": "linux_ppc64le" + } + }, + "nodejs_darwin_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "include_headers": false, + "platform": "darwin_amd64" + } + }, + "nodejs_darwin_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "include_headers": false, + "platform": "darwin_arm64" + } + }, + "nodejs_windows_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "include_headers": false, + "platform": "windows_amd64" + } + }, + "nodejs_windows_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "include_headers": false, + "platform": "windows_arm64" + } + }, + "nodejs": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "nodejs" + } + }, + "nodejs_host": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "nodejs" + } + }, + "nodejs_toolchains": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_toolchains_repo.bzl%nodejs_toolchains_repo", + "attributes": { + "user_node_repository_name": "nodejs" + } + } + }, + "recordedRepoMappingEntries": [] + } + }, "@@rules_python+//python/extensions:config.bzl%config": { "general": { "bzlTransitiveDigest": "TRGIl0CDmorwyNiblOYyhWuyKzi/kWFHT2uIofq7o9Y=", @@ -1104,6 +1579,73 @@ ] } }, + "@@score_bazel_cpp_toolchains+//extensions:gcc.bzl%gcc": { + "general": { + "bzlTransitiveDigest": "q6hXvCt3So3esFot1br87pd7Z5ujulkjUapUbRlUeIw=", + "usagesDigest": "xe0RtHWGop4THD+lVZwU1Y4SpIPhmJT5e00KimEwlT0=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "score_gcc_x86_64_toolchain_pkg": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/eclipse-score/toolchains_gcc_packages/releases/download/v0.0.4/x86_64-unknown-linux-gnu_gcc12.tar.gz" + ], + "build_file": "@@score_bazel_cpp_toolchains+//packages/linux/x86_64/gcc/12.2.0:gcc.BUILD", + "patch_cmds": [], + "sha256": "e9b9a7a63a5f8271b76d6e2057906b95c7a244e4931a8e10edeaa241e9f7c11e", + "strip_prefix": "x86_64-unknown-linux-gnu" + } + }, + "score_gcc_x86_64_toolchain": { + "repoRuleId": "@@score_bazel_cpp_toolchains+//rules:gcc.bzl%gcc_toolchain", + "attributes": { + "extra_compile_flags": [], + "extra_c_compile_flags": [], + "extra_cxx_compile_flags": [], + "extra_known_features": [], + "extra_enabled_features": [], + "extra_link_flags": [], + "license_info_variable": "", + "license_info_value": "", + "license_path": "/opt/score_qnx/license/licenses", + "sdk_version": "", + "sdp_version": "", + "tc_compiler_library_search_paths": [], + "tc_cpu": "x86_64", + "tc_identifier": "gcc_12.2.0", + "tc_os": "linux", + "tc_pkg_repo": "score_gcc_x86_64_toolchain_pkg", + "tc_system_toolchain": false, + "tc_runtime_ecosystem": "", + "gcc_version": "12.2.0", + "cc_toolchain_config": "@@score_bazel_cpp_toolchains+//templates/linux:cc_toolchain_config.bzl.template", + "cc_toolchain_flags": "@@score_bazel_cpp_toolchains+//templates/linux:cc_toolchain_flags.bzl.template", + "use_base_constraints_only": false + } + } + }, + "recordedRepoMappingEntries": [ + [ + "bazel_tools", + "rules_cc", + "rules_cc+" + ], + [ + "score_bazel_cpp_toolchains+", + "bazel_tools", + "bazel_tools" + ], + [ + "score_bazel_cpp_toolchains+", + "score_bazel_cpp_toolchains", + "score_bazel_cpp_toolchains+" + ] + ] + } + }, "@@score_toolchains_rust+//extensions:ferrocene_toolchain_ext.bzl%ferrocene_rules_rust_miri_toolchain_ext": { "general": { "bzlTransitiveDigest": "9k8SLwP4ec9xXjFivq0Kn0FradTQhcEA+j/WsLxfVPM=", diff --git a/integration_tests/expected_lcov_gcov.dat b/integration_tests/expected_lcov_gcov.dat new file mode 100644 index 0000000..60aa33d --- /dev/null +++ b/integration_tests/expected_lcov_gcov.dat @@ -0,0 +1,76 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +# Ground truth for the gcov backend (GCC 12.2.0, the collection path QNX/QCC +# takes on target), derived BY HAND from the fixture sources and +# coverable_test, which is the only test of `bazel coverage --config=gcov +# //src/...`. Compared like expected_lcov.dat: function records are ignored, +# one record per file. gcov semantics differ from LLVM's: a line is counted +# when the compiler emitted code for it (no closing braces, no lines of +# functions it never emitted), and BRDA lists both directions of every +# conditional jump. +# +# src/coverable.cpp classify() called with -5 and 0: entry (17) and the first +# condition (18) twice; `negative` (19) once; the second +# condition (21) and `zero` (22) once; `positive` (24) never. +# Branches: 18 true and false both taken, 21 true taken and +# false not => 3 of 4. +# src/uncovered.cpp never linked into a test: every line 0 from the gcno +# baseline (17, 18, 19, 21), both branch directions of 18 +# at 0 (gcov reports 0, not '-', for a notes-only run). +# src/vendored/include/vendored/inline_math.h +# twice() (18-19) called once from the test; never_inlined() +# is an unused inline function, GCC emits no code for it, so +# gcov has no lines for 22-24 (the LLVM backend reports them +# at 0). Reported under the declared path although the test +# compiles it through the vendored_math_internal twin target. +# (absent) external/itest_external+/include/vext/vext.h: gcov records +# the header, but Bazel's per-test merger keeps only files of +# its instrumented-files manifest, which never lists sources +# of external repositories; the file is listed as no-data. +# (absent) src/empty_unit.cpp (compiled without code), src/unused_api.h +# (no data), rust/*.rs (not instrumentable by gcov), +# extlib.cpp / extlib.h (out of scope), the test source. +SF:src/coverable.cpp +DA:17,2 +DA:18,2 +DA:19,1 +DA:21,1 +DA:22,1 +DA:24,0 +BRDA:18,0,0,1 +BRDA:18,0,1,1 +BRDA:21,0,0,1 +BRDA:21,0,1,0 +BRF:4 +BRH:3 +LF:6 +LH:5 +end_of_record +SF:src/uncovered.cpp +DA:17,0 +DA:18,0 +DA:19,0 +DA:21,0 +BRDA:18,0,0,0 +BRDA:18,0,1,0 +BRF:2 +BRH:0 +LF:4 +LH:0 +end_of_record +SF:src/vendored/include/vendored/inline_math.h +DA:18,1 +DA:19,1 +LF:2 +LH:2 +end_of_record diff --git a/integration_tests/run_integration_test.sh b/integration_tests/run_integration_test.sh index 44a9e3d..c5c23ec 100755 --- a/integration_tests/run_integration_test.sh +++ b/integration_tests/run_integration_test.sh @@ -289,5 +289,63 @@ fi rm -rf typo_dir typo.log echo "OK: unknown justification id is reported and does not count" +# --------------------------------------------------------------------------- +# gcov backend: GCC toolchain, Bazel's own per-test collector, score_coverage's +# gcov reporter. The same path a QNX (QCC) on-target run takes; only the QEMU +# transport of score_qnx_unit_tests differs. +# --------------------------------------------------------------------------- +echo "=== gcov backend: coverage build with the GCC toolchain ===" +bazel coverage --config=gcov //src/... --build_tests_only + +echo "=== gcov backend: gate, HTML, archive ===" +rm -rf gcov_artifacts_dir +if COVERAGE_THRESHOLD=100 bazel run @score_coverage//:generate_coverage_html -- \ + --yaml "${YAML}" --archive-dir gcov_artifacts_dir --summary-md gcov_summary.md coverage_gcov > gcov_run.log 2>&1; then + cat gcov_run.log + echo "ERROR: gcov gate passed at threshold 100 despite uncovered files" >&2 + exit 1 +fi +grep -q "Effective coverage" gcov_run.log || { cat gcov_run.log; echo "ERROR: gcov run did not reach the gate" >&2; exit 1; } +COVERAGE_THRESHOLD=10 bazel run @score_coverage//:generate_coverage_html -- \ + --yaml "${YAML}" --archive-dir gcov_artifacts_dir --summary-md gcov_summary.md coverage_gcov +echo "OK: gcov gate fails at 100 and passes at 10" + +echo "=== gcov backend: LCOV must match the hand-verified ground truth ===" +normalise_lcov gcov_artifacts_dir/coverage_report.dat > actual_gcov.dat +grep -v '^#' expected_lcov_gcov.dat | normalise_lcov /dev/stdin > expected_gcov.dat +if ! diff -u expected_gcov.dat actual_gcov.dat; then + echo "ERROR: gcov coverage data differs from expected_lcov_gcov.dat (see diff above)" >&2 + exit 1 +fi +rm -f actual_gcov.dat expected_gcov.dat +echo "OK: gcov LCOV matches the ground truth" + +echo "=== gcov backend: every index link opens; justification applied; categories ===" +GHTML="gcov_artifacts_dir/coverage_gcov" +[[ -f "${GHTML}/index.html" ]] || { echo "ERROR: gcovr index.html missing" >&2; exit 1; } +GLINKS="$(grep -oE 'href="index\.[^"]+\.html"' "${GHTML}/index.html" | sed -E 's/^href="//; s/"$//' | sort -u)" +[[ -n "${GLINKS}" ]] || { echo "ERROR: no per-file links in the gcovr index" >&2; exit 1; } +while IFS= read -r link; do + [[ -f "${GHTML}/${link}" ]] || { echo "ERROR: gcovr index links to ${link}, which does not exist" >&2; exit 1; } +done <<< "${GLINKS}" +for page in coverable.cpp uncovered.cpp inline_math.h; do + ls "${GHTML}"/index."${page}".*.html > /dev/null 2>&1 || { echo "ERROR: no gcovr page for ${page}" >&2; exit 1; } +done +G_RAW="$(grep -oP 'Raw line coverage:\s+\K[0-9.]+' gcov_artifacts_dir/justification_report/summary.txt)" +G_EFF="$(grep -oP 'Effective line coverage:\s+\K[0-9.]+' gcov_artifacts_dir/justification_report/summary.txt)" +if ! awk "BEGIN {exit (${G_EFF} > ${G_RAW}) ? 0 : 1}"; then + echo "ERROR: gcov effective coverage ${G_EFF}% not above raw ${G_RAW}% (justification not applied on gcovr HTML)" >&2 + exit 1 +fi +EXPECTED_GCOV_UNMAPPED=$'compiled-without-code\tsrc/empty_unit.cpp\ndeclaration-only\tsrc/coverable.h\ndeclaration-only\tsrc/uncovered.h\nno-data\texternal/itest_external+/include/vext/vext.h\nno-data\tsrc/unused_api.h\nnot-instrumented\trust/lib.rs\nnot-instrumented\trust/main.rs' +if [[ "$(cat gcov_artifacts_dir/unmapped_files.txt)" != "${EXPECTED_GCOV_UNMAPPED}" ]]; then + echo "ERROR: gcov unmapped_files.txt unexpected:" >&2 + cat gcov_artifacts_dir/unmapped_files.txt >&2 + exit 1 +fi +grep -qF "Not instrumentable by this backend (2)" gcov_summary.md || { echo "ERROR: not-instrumented section missing from the gcov summary" >&2; exit 1; } +rm -rf gcov_artifacts_dir gcov_summary.md gcov_run.log coverage_gcov +echo "OK: gcov HTML complete (${GLINKS//$'\n'/, }), effective ${G_EFF}% > raw ${G_RAW}%, categories as expected" + echo "" echo "=== All integration checks passed ===" diff --git a/integration_tests/tools/coverage/BUILD b/integration_tests/tools/coverage/BUILD index 8915e5a..a3238c4 100644 --- a/integration_tests/tools/coverage/BUILD +++ b/integration_tests/tools/coverage/BUILD @@ -43,3 +43,13 @@ score_coverage_reporter( llvm_cxxfilt = "@llvm_toolchain_llvm//:bin/llvm-cxxfilt", llvm_profdata = "@llvm_toolchain//:llvm-profdata", ) + +# gcov backend reporter: the same scope, the toolchain's gcov instead of the +# LLVM tools. Selected by the coverage:gcov config. +score_coverage_reporter( + name = "gcov_reporter_wrapper", + testonly = True, + backend = "gcov", + coverage_scope = ":coverage_scope", + gcov = "@score_gcc_x86_64_toolchain_pkg//:gcov", +) diff --git a/score_coverage/BUILD b/score_coverage/BUILD index 4af59ae..900226e 100644 --- a/score_coverage/BUILD +++ b/score_coverage/BUILD @@ -40,6 +40,16 @@ py_binary( deps = ["@rules_python//python/runfiles"], ) +py_binary( + name = "gcov_reporter", + srcs = ["gcov_reporter.py"], + deps = [ + ":reporter_lib", + "@rules_python//python/runfiles", + requirement("gcovr"), + ], +) + py_binary( name = "justify", srcs = ["justify.py"], @@ -85,6 +95,17 @@ py_library( deps = ["@rules_python//python/runfiles"], ) +py_library( + name = "gcov_reporter_lib", + srcs = ["gcov_reporter.py"], + imports = [".."], + deps = [ + ":reporter_lib", + "@rules_python//python/runfiles", + requirement("gcovr"), + ], +) + py_library( name = "justify_lib", srcs = ["justify.py"], diff --git a/score_coverage/coverage_scope.bzl b/score_coverage/coverage_scope.bzl index a20f968..7de5e75 100644 --- a/score_coverage/coverage_scope.bzl +++ b/score_coverage/coverage_scope.bzl @@ -34,7 +34,12 @@ The rule writes three text files the reporter consumes: workspace target exposes through ``strip_include_prefix`` / ``include_prefix``. The compiler records the generated ``/_virtual_includes//...`` path; the reporter maps it back to the declared header. -- ``_objects.txt``: archives / executables for the zero-coverage baseline. +- ``_objects.txt``: archives / executables for the zero-coverage baseline + of the LLVM backend. +- ``_gcno.txt``: the ``.gcno`` notes files the compiler wrote for the + in-scope translation units, the zero-coverage baseline of the gcov backend + (GCC and QCC): ``gcov`` on a ``.gcno`` without its ``.gcda`` yields every + line at zero. The source files themselves are exported in the ``source_files`` output group so the reporter can stage them for llvm-cov from its runfiles, independent of @@ -54,6 +59,7 @@ _CoverageScopeInfo = provider( "source_file_objects": "Depset of the File objects behind source_files (staged by the reporter).", "path_map": "Depset of '\\t' strings for headers behind include prefixes.", "object_files": "Depset of compiled archive/executable File objects for baseline coverage.", + "gcno_files": "Depset of .gcno File objects of in-scope translation units (gcov baseline).", }, ) @@ -111,10 +117,12 @@ def _coverage_scope_aspect_impl(target, ctx): direct_file_objects = [] direct_map = [] direct_archives = [] + direct_gcno = [] transitive = [] transitive_file_objects = [] transitive_map = [] transitive_archives = [] + transitive_gcno = [] # At cc_library / rust_library targets (rust_library provides CcInfo with # its rlib exposed as a .a symlink): collect srcs, hdrs, and static archive @@ -144,6 +152,15 @@ def _coverage_scope_aspect_impl(target, ctx): # Record the mapping so the reporter can report the declared file. direct_map.extend(_virtual_include_map(target, ctx, declared_hdrs)) + # gcov backend baseline: the .gcno notes files of this target's own + # translation units. InstrumentedFilesInfo.metadata_files is + # transitive, so keep only files this target owns; they exist only + # when the target is built with coverage instrumentation. + if InstrumentedFilesInfo in target: + for f in target[InstrumentedFilesInfo].metadata_files.to_list(): + if f.owner == target.label and f.extension == "gcno": + direct_gcno.append(f) + # Collect .a archive files for baseline coverage. for linker_input in target[CcInfo].linking_context.linker_inputs.to_list(): for lib in linker_input.libraries: @@ -171,12 +188,14 @@ def _coverage_scope_aspect_impl(target, ctx): transitive_file_objects.append(dep[_CoverageScopeInfo].source_file_objects) transitive_map.append(dep[_CoverageScopeInfo].path_map) transitive_archives.append(dep[_CoverageScopeInfo].object_files) + transitive_gcno.append(dep[_CoverageScopeInfo].gcno_files) return [_CoverageScopeInfo( source_files = depset(direct_files, transitive = transitive), source_file_objects = depset(direct_file_objects, transitive = transitive_file_objects), path_map = depset(direct_map, transitive = transitive_map), object_files = depset(direct_archives, transitive = transitive_archives), + gcno_files = depset(direct_gcno, transitive = transitive_gcno), )] _coverage_scope_aspect = aspect( @@ -195,6 +214,7 @@ def _coverage_scope_impl(ctx): all_map = {} all_objects = [] all_sources = [] + all_gcno = [] for dep in ctx.attr.deps: if _CoverageScopeInfo in dep: @@ -205,11 +225,13 @@ def _coverage_scope_impl(ctx): all_map[entry] = True all_objects.append(dep[_CoverageScopeInfo].object_files) all_sources.append(dep[_CoverageScopeInfo].source_file_objects) + all_gcno.append(dep[_CoverageScopeInfo].gcno_files) sorted_files = sorted(all_files.keys()) sorted_map = sorted(all_map.keys()) object_depset = depset(transitive = all_objects) source_depset = depset(transitive = all_sources) + gcno_depset = depset(transitive = all_gcno) # Write the allowlist file output = ctx.actions.declare_file(ctx.attr.name + "_allowlist.txt") @@ -233,14 +255,24 @@ def _coverage_scope_impl(ctx): content = "\n".join(archive_paths) + "\n" if archive_paths else "", ) + # Write the gcno manifest for the gcov backend (reporter runs gcov on each) + gcno_paths = sorted(set([f.short_path for f in gcno_depset.to_list()])) + gcno_output = ctx.actions.declare_file(ctx.attr.name + "_gcno.txt") + ctx.actions.write( + output = gcno_output, + content = "\n".join(gcno_paths) + "\n" if gcno_paths else "", + ) + return [ - DefaultInfo(files = depset([output, map_output, objects_output], transitive = [object_depset])), + DefaultInfo(files = depset([output, map_output, objects_output, gcno_output], transitive = [object_depset])), OutputGroupInfo( allowlist = depset([output]), path_map = depset([map_output]), objects = depset([objects_output]), object_files = object_depset, source_files = source_depset, + gcno = depset([gcno_output]), + gcno_files = gcno_depset, ), ] @@ -284,9 +316,9 @@ coverage_scope = rule( Uses an aspect to traverse the listed targets (cc_library, rust_library, rust_binary) and their transitive deps, collecting all source files (srcs + hdrs / CrateInfo.srcs). Outputs the allowlist (one canonical file - path per line), the virtual-includes path map and the baseline objects - manifest, and exports the source files themselves in the ``source_files`` - output group. + path per line), the virtual-includes path map, the baseline objects + manifest (LLVM backend) and the gcno manifest (gcov backend), and exports + the source files themselves in the ``source_files`` output group. The coverage reporter restricts reporting to exactly the allowlisted files, reports headers behind include prefixes under their declared path, diff --git a/score_coverage/coverage_summary.py b/score_coverage/coverage_summary.py index 786ad2c..c5dbc32 100644 --- a/score_coverage/coverage_summary.py +++ b/score_coverage/coverage_summary.py @@ -258,6 +258,12 @@ def rollup_by_directory(files: list[FileCoverage]) -> list[dict]: "placeholder .cpp of a header-only library that only includes headers. Nothing to cover " "in these files.", ), + ( + "not-instrumented", + "Not instrumentable by this backend", + "In-scope sources the coverage backend cannot instrument: Rust sources under the gcov " + "backend (QNX on-target runs). Measure them with the LLVM backend.", + ), ) diff --git a/score_coverage/effective_coverage.py b/score_coverage/effective_coverage.py index 0871704..b5e234f 100644 --- a/score_coverage/effective_coverage.py +++ b/score_coverage/effective_coverage.py @@ -1034,8 +1034,11 @@ def _process_gcovr_file( # Parse line coverage status from gcovr HTML. # Each source line: # followed by + # Both patterns stay inside the line's own table row: the row of a + # non-instrumented line carries a bare , and a + # match running on into the next row would shift every status by one line. line_pattern = re.compile( - r']*>.*?' + r']*>(?:(?!).)*?' r' on lines with branch issues branch_pattern = re.compile( - r']*>.*?(.*?)', + r']*>(?:(?!).)*?(.*?)', re.DOTALL, ) diff --git a/score_coverage/gcov_reporter.py b/score_coverage/gcov_reporter.py new file mode 100644 index 0000000..133dd35 --- /dev/null +++ b/score_coverage/gcov_reporter.py @@ -0,0 +1,512 @@ +#!/usr/bin/env python3 +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +"""Final coverage report generator for gcov-based toolchains (GCC, QNX QCC). + +Bazel's own per-test collector runs ``gcov`` over the ``.gcda`` counters a test +wrote (on QNX the counters travel back from the QEMU guest through +``score_qnx_unit_tests``) and stores one LCOV file per test. This script is +the ``--coverage_report_generator`` that follows: it merges the per-test LCOV +files, restricts them to the coverage scope, adds a zero-coverage baseline for +in-scope translation units no test executed (``gcov`` over their ``.gcno`` +notes without counters), and writes the same report zip as the LLVM reporter: +``html_report/`` (rendered by gcovr), ``lcov_report/lcov.dat`` and +``text_report/`` (summary and ``unmapped_files.txt``). + +Expected Bazel interface: + --reports_file= Text file listing paths to all per-test coverage outputs + --output_file= Where to write the final report (zip) +""" + +import argparse +import json +import os +import re +import subprocess +import sys +from collections import defaultdict +from dataclasses import dataclass, field +from pathlib import Path + +from python.runfiles import Runfiles + +from score_coverage.reporter import ( + FileSelection, + RunfilesLike, + canonical_path, + create_zip, + format_unmapped_files, + load_coverage_allowlist, + load_path_map, + read_reports_file, + resolve_foreign_virtual_includes, + resolve_tool, + select_files, + stage_sources, + write_empty_output, +) + +# Sources the gcov backend can never carry data for: rustc is an LLVM +# compiler and emits no .gcda counters. +UNMAPPED_NOT_INSTRUMENTED = "not-instrumented" +_RUST_SUFFIXES = (".rs",) + + +@dataclass +class FileRecord: + """Coverage of one source file: line hits, branch hits and functions.""" + + lines: dict[int, int] = field(default_factory=dict) + """line number -> execution count.""" + branches: dict[tuple[int, str, str], int | None] = field(default_factory=dict) + """(line, block, branch) -> taken count, or None when the block was never reached ('-').""" + functions: dict[str, tuple[int, int]] = field(default_factory=dict) + """function name -> (start line, execution count).""" + + def add(self, other: "FileRecord") -> None: + """Accumulate another record of the same file (another test's run): counts add up.""" + for line, count in other.lines.items(): + self.lines[line] = self.lines.get(line, 0) + count + for key, count in other.branches.items(): + current = self.branches.get(key) + if count is None: + self.branches.setdefault(key, None) + else: + self.branches[key] = (current or 0) + count + for name, (line, count) in other.functions.items(): + _, current = self.functions.get(name, (line, 0)) + self.functions[name] = (line, current + count) + + def zeroed(self) -> "FileRecord": + """The same structure with every count at zero (a baseline entry).""" + return FileRecord( + lines=dict.fromkeys(self.lines, 0), + branches={key: (None if count is None else 0) for key, count in self.branches.items()}, + functions={name: (line, 0) for name, (line, _) in self.functions.items()}, + ) + + +def main(argv: list[str] | None = None) -> None: + """Main entry point. ``argv`` defaults to ``sys.argv[1:]``.""" + args = parse_args(argv) + r = Runfiles.Create() + if r is None: + print("ERROR: runfiles are unavailable; the reporter must run as a Bazel coverage action.", file=sys.stderr) + sys.exit(1) + + reports = [path for path in read_reports_file(args.reports_file) if not path.endswith("baseline_coverage.dat")] + if not reports: + print("INFO: No coverage reports listed; writing empty output.", file=sys.stderr) + write_empty_output(args.output_file) + return + + gcov_path = resolve_tool(r, args.gcov, "gcov") + if not gcov_path: + print( + "ERROR: gcov not found in runfiles. Pass --gcov (the score_coverage_reporter macro does this).", + file=sys.stderr, + ) + sys.exit(1) + + workspace_root = args.workspace_root + path_map = load_path_map(r, args.path_map) + allowlist_files = load_coverage_allowlist(r, args.coverage_allowlist) if args.coverage_allowlist else [] + if args.coverage_allowlist and not allowlist_files: + print("ERROR: Coverage allowlist is empty.", file=sys.stderr) + sys.exit(-1) + allowlist: set[str] | None = set(allowlist_files) if allowlist_files else None + if allowlist is not None: + print(f"INFO: Using coverage allowlist with {len(allowlist)} source files.", file=sys.stderr) + + # Per-test LCOV files from Bazel's collector, summed per file. + tested: dict[str, FileRecord] = {} + for report in reports: + for raw, record in parse_lcov(Path(report).read_text(encoding="utf-8", errors="replace")).items(): + tested.setdefault(normalize_raw(raw, workspace_root), FileRecord()).add(record) + print(f"INFO: {len(reports)} per-test reports cover {len(tested)} files.", file=sys.stderr) + + # Zero-coverage baseline from the .gcno notes of every in-scope translation unit. + gcno_files = load_gcno_manifest(r, args.gcno_manifest) + baseline: dict[str, FileRecord] = {} + for gcno in sorted(gcno_files): + for raw, record in gcov_baseline(gcov_path, gcno).items(): + baseline.setdefault(normalize_raw(raw, workspace_root), FileRecord()).add(record) + print(f"INFO: {len(gcno_files)} gcno files describe {len(baseline)} files.", file=sys.stderr) + + # Merge by canonical name BEFORE the selection: a header may be recorded + # under its declared path by one test and under a virtual-include path by + # another, and both runs' counts belong to the same file. (The LLVM + # reporter keeps one variant instead because llvm-cov renders each raw + # path separately; here the merge is ours.) + canonical = canonical_names(set(tested) | set(baseline), path_map, allowlist) + tested = merge_by_name(tested, canonical) + baseline = merge_by_name(baseline, canonical) + test_covered = {name: name for name in tested} + baseline_covered = {name: name for name in baseline} + compiled_stems = compiled_stems_from_gcno(list(gcno_files.values())) + selection = select_files(test_covered, baseline_covered, allowlist, compiled_stems) + not_instrumented = mark_not_instrumented(selection) + report_selection(selection, not_instrumented) + + merged = merge_records(tested, baseline, selection) + + # Stage the in-scope sources so gcovr can render them (generated headers + # and external repositories are not reachable through the workspace). + source_root = Path.cwd() / "sources" + missing = stage_sources(source_root, {name: name for name in merged}, r, workspace_root) + if missing: + print(f"WARNING: {len(missing)} in-scope sources were not found (e.g., {missing[:5]})", file=sys.stderr) + + lcov_report_dir = Path.cwd() / "lcov_report" + lcov_report_dir.mkdir(exist_ok=True) + (lcov_report_dir / "lcov.dat").write_text(render_lcov(merged), encoding="utf-8") + + html_report_dir = Path.cwd() / "html_report" + text_report_dir = Path.cwd() / "text_report" + html_report_dir.mkdir(exist_ok=True) + text_report_dir.mkdir(exist_ok=True) + render_html(merged, source_root, html_report_dir, text_report_dir / "summary.txt") + print((text_report_dir / "summary.txt").read_text(encoding="utf-8"), file=sys.stderr) + (text_report_dir / "unmapped_files.txt").write_text( + format_unmapped_files(selection) + + "".join(f"{UNMAPPED_NOT_INSTRUMENTED}\t{n}\n" for n in sorted(not_instrumented)), + encoding="utf-8", + ) + + create_zip( + root=Path.cwd(), directories=[html_report_dir, lcov_report_dir, text_report_dir], output_file=args.output_file + ) + print(f"INFO: gcov coverage reporter completed. Output: {args.output_file}", file=sys.stderr) + + +# ----------------------------------------------------------------------------- +# LCOV parsing and rendering +# ----------------------------------------------------------------------------- + + +def parse_lcov(content: str) -> dict[str, FileRecord]: + """Parse LCOV text into ``{SF path: FileRecord}``; records of the same file are summed.""" + records: dict[str, FileRecord] = {} + current: FileRecord | None = None + name = "" + for line in content.splitlines(): + line = line.strip() + if line.startswith("SF:"): + name, current = line[3:], FileRecord() + elif current is None: + continue + elif line.startswith("DA:"): + parts = line[3:].split(",") + current.lines[int(parts[0])] = current.lines.get(int(parts[0]), 0) + max(0, int(parts[1])) + elif line.startswith("BRDA:"): + ln, block, branch, taken = line[5:].split(",", 3) + key = (int(ln), block, branch) + count = None if taken == "-" else max(0, int(taken)) + if count is None: + current.branches.setdefault(key, None) + else: + current.branches[key] = (current.branches.get(key) or 0) + count + elif line.startswith("FN:"): + start, fname = line[3:].split(",", 1) + _, count = current.functions.get(fname, (0, 0)) + current.functions[fname] = (int(start), count) + elif line.startswith("FNDA:"): + hits, fname = line[5:].split(",", 1) + start, count = current.functions.get(fname, (0, 0)) + current.functions[fname] = (start, count + int(hits)) + elif line == "end_of_record": + records.setdefault(name, FileRecord()).add(current) + current = None + return records + + +def render_lcov(records: dict[str, FileRecord]) -> str: + """LCOV text for the records, one ``SF:`` block per file in sorted order.""" + out: list[str] = [] + for name in sorted(records): + rec = records[name] + out.append(f"SF:{name}") + for fname, (start, _) in sorted(rec.functions.items(), key=lambda item: (item[1][0], item[0])): + out.append(f"FN:{start},{fname}") + for fname, (_, count) in sorted(rec.functions.items(), key=lambda item: (item[1][0], item[0])): + out.append(f"FNDA:{count},{fname}") + if rec.functions: + out.append(f"FNF:{len(rec.functions)}") + out.append(f"FNH:{sum(1 for _, count in rec.functions.values() if count > 0)}") + for line in sorted(rec.lines): + out.append(f"DA:{line},{rec.lines[line]}") + for (line, block, branch), count in sorted( + rec.branches.items(), key=lambda item: (item[0][0], item[0][1], item[0][2]) + ): + out.append(f"BRDA:{line},{block},{branch},{'-' if count is None else count}") + if rec.branches: + out.append(f"BRF:{len(rec.branches)}") + out.append(f"BRH:{sum(1 for count in rec.branches.values() if count)}") + out.append(f"LF:{len(rec.lines)}") + out.append(f"LH:{sum(1 for count in rec.lines.values() if count > 0)}") + out.append("end_of_record") + return "".join(line + "\n" for line in out) + + +# ----------------------------------------------------------------------------- +# Paths and selection +# ----------------------------------------------------------------------------- + + +def normalize_raw(path: str, workspace_root: str) -> str: + """Strip the workspace root, ``/proc/self/cwd/`` and ``./`` so paths are exec-root relative.""" + for prefix in (workspace_root.rstrip("/") + "/", "/proc/self/cwd/", "./"): + if path.startswith(prefix): + return path[len(prefix) :] + return path + + +def canonical_names(raws: set[str], path_map: dict[str, str], allowlist: set[str] | None) -> dict[str, str]: + """raw exec-root-relative path -> canonical name (config prefix dropped, virtual includes resolved).""" + names = {raw: canonical_path(raw, path_map) for raw in raws} + if allowlist is not None: + foreign, ambiguous = resolve_foreign_virtual_includes(set(names.values()), allowlist) + for raw, name in names.items(): + if name in foreign: + names[raw] = foreign[name] + for name, candidates in sorted(ambiguous.items()): + print( + f"WARNING: {name} matches several in-scope files ({candidates}); it stays out of the report.", + file=sys.stderr, + ) + return names + + +def merge_by_name(records: dict[str, FileRecord], canonical: dict[str, str]) -> dict[str, FileRecord]: + """Sum records of the same canonical name (variants of one file recorded under different raw paths).""" + merged: dict[str, FileRecord] = {} + for raw, record in records.items(): + merged.setdefault(canonical[raw], FileRecord()).add(record) + return merged + + +def mark_not_instrumented(selection: FileSelection) -> set[str]: + """Move allowlisted Rust sources out of the findings: gcov cannot instrument them.""" + rust = {name for name in selection.unmapped if name.endswith(_RUST_SUFFIXES)} + selection.unmapped -= rust + return rust + + +def report_selection(selection: FileSelection, not_instrumented: set[str]) -> None: + """Log what the scope did to the collected files.""" + if selection.baseline_only: + print(f"INFO: {len(selection.baseline_only)} allowlisted files only in baseline.", file=sys.stderr) + if selection.excluded: + print(f"INFO: Excluding {len(selection.excluded)} compiled files outside the scope.", file=sys.stderr) + if selection.unmapped: + print( + f"WARNING: {len(selection.unmapped)} in-scope files have no coverage data at all; " + f"listed in text_report/unmapped_files.txt (e.g., {sorted(selection.unmapped)[:5]})", + file=sys.stderr, + ) + if not_instrumented: + print( + f"INFO: {len(not_instrumented)} Rust sources are in scope but the gcov backend cannot instrument them.", + file=sys.stderr, + ) + + +def merge_records( + tested: dict[str, FileRecord], baseline: dict[str, FileRecord], selection: FileSelection +) -> dict[str, FileRecord]: + """Canonical name -> record: tested data where it exists, zeroed baseline data otherwise.""" + merged: dict[str, FileRecord] = {} + for raw, name in selection.staged.items(): + if raw in tested: + merged.setdefault(name, FileRecord()).add(tested[raw]) + for raw, name in selection.staged.items(): + if raw in baseline and name not in merged: + merged.setdefault(name, FileRecord()).add(baseline[raw].zeroed()) + return merged + + +# ----------------------------------------------------------------------------- +# gcov baseline +# ----------------------------------------------------------------------------- + + +def load_gcno_manifest(runfiles: RunfilesLike, rlocation_path: str | None) -> dict[str, str]: + """``{absolute path: short path}`` of the .gcno files listed by the scope; a missing file is an error.""" + if not rlocation_path: + return {} + manifest = runfiles.Rlocation(rlocation_path) + if not manifest or not Path(manifest).exists(): + print(f"WARNING: gcno manifest not found: {rlocation_path}", file=sys.stderr) + return {} + resolved: dict[str, str] = {} + for line in Path(manifest).read_text(encoding="utf-8").splitlines(): + short_path = line.strip() + if not short_path or short_path.startswith("#"): + continue + location = runfiles.Rlocation(os.path.join("_main", short_path)) + if not location or not os.path.exists(location): + print(f"ERROR: gcno file not found: {short_path}", file=sys.stderr) + sys.exit(-1) + resolved[location] = short_path + return resolved + + +_GCNO_RE = re.compile(r"(?:^|/)(?P.*?)/?_objs/[^/]+/(?P.+?)(?:\.pic)?\.gcno$") + + +def compiled_stems_from_gcno(gcno_files: list[str]) -> set[str]: + """``/`` of every translation unit that has a .gcno: the sources that were compiled. + + The short path ``src/_objs/uncovered/empty_unit.pic.gcno`` names + ``src/empty_unit``. A placeholder source that contains no code has a + .gcno like any other and is thereby classified as compiled without code + instead of as a finding. + """ + stems: set[str] = set() + for gcno in gcno_files: + match = _GCNO_RE.search(gcno) + if match: + stems.add(f"{match.group('pkg')}/{match.group('src')}" if match.group("pkg") else match.group("src")) + return stems + + +def gcov_baseline(gcov_path: Path, gcno: str) -> dict[str, FileRecord]: + """Run gcov over one .gcno without counters; every line and branch comes back at zero. + + Returns ``{gcov file path: FileRecord}``: the translation unit and every + header it instantiated code from, paths as gcov records them (relative + to the compilation directory, i.e. exec-root relative under Bazel). + """ + result = subprocess.run( + [str(gcov_path), "--json-format", "--stdout", "--branch-probabilities", gcno], + capture_output=True, + text=True, + check=False, + ) + if result.returncode != 0 or not result.stdout.strip(): + print(f"WARNING: gcov failed on {gcno}: {result.stderr.strip()[:200]}", file=sys.stderr) + return {} + try: + data = json.loads(result.stdout) + except json.JSONDecodeError as e: + print(f"WARNING: gcov produced no JSON for {gcno}: {e}", file=sys.stderr) + return {} + return records_from_gcov_json(data) + + +def records_from_gcov_json(data: dict) -> dict[str, FileRecord]: + """Convert gcov's JSON intermediate format into records (counts as reported).""" + records: dict[str, FileRecord] = {} + for entry in data.get("files", []): + rec = FileRecord() + blocks: dict[int, int] = defaultdict(int) + for line in entry.get("lines", []): + number = int(line["line_number"]) + rec.lines[number] = rec.lines.get(number, 0) + int(line.get("count", 0)) + for branch in line.get("branches", []): + index = blocks[number] + blocks[number] += 1 + rec.branches[(number, "0", str(index))] = int(branch.get("count", 0)) + for function in entry.get("functions", []): + rec.functions[function["name"]] = ( + int(function.get("start_line", 0)), + int(function.get("execution_count", 0)), + ) + records[entry["file"]] = rec + return records + + +# ----------------------------------------------------------------------------- +# HTML through gcovr +# ----------------------------------------------------------------------------- + + +def gcovr_tracefile(records: dict[str, FileRecord]) -> dict: + """The records as a gcovr JSON tracefile (format 0.14).""" + files = [] + for name in sorted(records): + rec = records[name] + by_line: dict[int, list[dict]] = defaultdict(list) + for (line, block, branch), count in sorted(rec.branches.items()): + # gcovr's source page prints "block -> block" for every branch and + # needs both ids; LCOV's block and branch numbers stand in for them. + by_line[line].append( + { + "count": count or 0, + "fallthrough": False, + "throw": False, + "source_block_id": int(block) if block.isdigit() else 0, + "destination_block_id": int(branch) if branch.isdigit() else 0, + } + ) + files.append( + { + "file": name, + "lines": [ + {"line_number": line, "count": rec.lines[line], "branches": by_line.get(line, [])} + for line in sorted(rec.lines) + ], + "functions": [ + {"name": fname, "lineno": start, "execution_count": count} + for fname, (start, count) in sorted(rec.functions.items(), key=lambda item: (item[1][0], item[0])) + if start > 0 + ], + } + ) + return {"gcovr/format_version": "0.14", "files": files} + + +def render_html(records: dict[str, FileRecord], source_root: Path, html_dir: Path, summary: Path) -> None: + """Render the HTML report (index.html plus one page per file) and the text summary with gcovr.""" + tracefile = Path.cwd() / "gcovr_tracefile.json" + tracefile.write_text(json.dumps(gcovr_tracefile(records)), encoding="utf-8") + argv = [ + "gcovr", + "--json-add-tracefile", + str(tracefile), + "--root", + str(source_root), + "--html-details", + str(html_dir / "index.html"), + "--txt", + str(summary), + ] + from gcovr.__main__ import main as gcovr_main # noqa: PLC0415 # pylint: disable=import-outside-toplevel + + saved_argv = sys.argv + sys.argv = argv + try: + gcovr_main() + except SystemExit as e: + if e.code not in (0, None): + print(f"ERROR: gcovr exited with code {e.code}", file=sys.stderr) + sys.exit(1) + finally: + sys.argv = saved_argv + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + """Parse command-line arguments matching the Bazel coverage_report_generator interface.""" + parser = argparse.ArgumentParser(description="gcov coverage reporter for Bazel") + parser.add_argument("--output_file", type=Path, required=True) + parser.add_argument("--reports_file", type=Path, required=True) + parser.add_argument("--coverage_allowlist", type=str, default=None, help="Rlocation path to the allowlist") + parser.add_argument("--path_map", type=str, default=None, help="Rlocation path to the virtual-includes map") + parser.add_argument("--gcno_manifest", type=str, default=None, help="Rlocation path to the gcno manifest") + parser.add_argument("--workspace_root", type=str, required=True, help="Real workspace root path") + parser.add_argument("--gcov", type=str, default=None, help="Rlocation path to the toolchain's gcov binary") + return parser.parse_args(argv) + + +if __name__ == "__main__": + main() diff --git a/score_coverage/generate_coverage_html.py b/score_coverage/generate_coverage_html.py index dda41bf..f11dd67 100644 --- a/score_coverage/generate_coverage_html.py +++ b/score_coverage/generate_coverage_html.py @@ -223,8 +223,9 @@ def extract_report(report_zip: Path, extract_dir: Path) -> None: ) if not zipfile.is_zipfile(report_zip): raise GenerateError( - f"{report_zip} is not the LLVM pipeline zip report.\n" - " Run 'bazel coverage --config=llvm_cov //... --build_tests_only' first." + f"{report_zip} is not a score_coverage report zip.\n" + " Run 'bazel coverage --config=llvm_cov //... --build_tests_only' (or the gcov config with the\n" + " score_coverage gcov reporter as --coverage_report_generator) first." ) with zipfile.ZipFile(report_zip) as zf: zf.extractall(extract_dir) diff --git a/score_coverage/reporter_wrapper.bzl b/score_coverage/reporter_wrapper.bzl index 727f2e6..46b46b3 100644 --- a/score_coverage/reporter_wrapper.bzl +++ b/score_coverage/reporter_wrapper.bzl @@ -14,10 +14,17 @@ """Executable wrapper rule for the coverage reporter. Instantiated in the CONSUMER repository (via the score_coverage_reporter -macro) so that the consumer's coverage_scope, MODULE.bazel and LLVM tool +macro) so that the consumer's coverage_scope, MODULE.bazel and coverage tool labels can be wired into the reporter, which itself lives in score_coverage. + +Two backends share the wrapper: ``llvm`` (Clang / rustc covmap, llvm-cov and +llvm-profdata; Linux) and ``gcov`` (GCC / QCC ``.gcda`` counters, the +toolchain's ``gcov``; Linux with GCC, QNX on target). Both reporters consume +the same scope outputs and produce the same report zip. """ +BACKENDS = ["llvm", "gcov"] + def _rlocation_path(ctx, file): """Return the Runfiles.Rlocation()-compatible path for a Bazel File. @@ -33,13 +40,26 @@ def _rlocation_path(ctx, file): def _reporter_wrapper_impl(ctx): launcher = ctx.actions.declare_file(ctx.label.name + ".sh") - reporter = ctx.executable.reporter + backend = ctx.attr.backend + if backend == "llvm": + if not ctx.file.llvm_cov or not ctx.file.llvm_profdata: + fail("backend = \"llvm\" needs llvm_cov and llvm_profdata") + reporter_attr = ctx.attr.reporter or ctx.attr._llvm_reporter + elif backend == "gcov": + if not ctx.file.gcov: + fail("backend = \"gcov\" needs gcov (the toolchain's gcov binary)") + reporter_attr = ctx.attr.reporter or ctx.attr._gcov_reporter + else: + fail("backend must be one of %s, got %s" % (BACKENDS, backend)) + reporter = reporter_attr[DefaultInfo].files_to_run.executable module_bazel = ctx.file.module_bazel coverage_scope = ctx.attr.coverage_scope allowlist_group = coverage_scope[OutputGroupInfo].allowlist.to_list() path_map_group = coverage_scope[OutputGroupInfo].path_map.to_list() objects_group = coverage_scope[OutputGroupInfo].objects.to_list() + gcno_group = coverage_scope[OutputGroupInfo].gcno.to_list() object_files = coverage_scope[OutputGroupInfo].object_files + gcno_files = coverage_scope[OutputGroupInfo].gcno_files source_files = coverage_scope[OutputGroupInfo].source_files if len(allowlist_group) != 1: @@ -48,15 +68,27 @@ def _reporter_wrapper_impl(ctx): fail("coverage_scope must provide exactly one path map file") if len(objects_group) != 1: fail("coverage_scope must provide exactly one objects manifest file") + if len(gcno_group) != 1: + fail("coverage_scope must provide exactly one gcno manifest file") allowlist = allowlist_group[0] path_map = path_map_group[0] baseline_objects = objects_group[0] + gcno_manifest = gcno_group[0] - cxxfilt_line = "" - if ctx.file.llvm_cxxfilt: - cxxfilt_line = ' --llvm_cxxfilt="{}" \\\n'.format( - _rlocation_path(ctx, ctx.file.llvm_cxxfilt), + # Backend-specific tool arguments, every path in rlocation form. + if backend == "llvm": + tool_lines = ' --llvm_cov="{}" \\\n --llvm_profdata="{}" \\\n --baseline_objects="{}" \\\n'.format( + _rlocation_path(ctx, ctx.file.llvm_cov), + _rlocation_path(ctx, ctx.file.llvm_profdata), + _rlocation_path(ctx, baseline_objects), + ) + if ctx.file.llvm_cxxfilt: + tool_lines += ' --llvm_cxxfilt="{}" \\\n'.format(_rlocation_path(ctx, ctx.file.llvm_cxxfilt)) + else: + tool_lines = ' --gcov="{}" \\\n --gcno_manifest="{}" \\\n'.format( + _rlocation_path(ctx, ctx.file.gcov), + _rlocation_path(ctx, gcno_manifest), ) # Bazel invokes the coverage report generator from within its coverage @@ -77,20 +109,14 @@ WORKSPACE_ROOT="$(cd "$(dirname "$(readlink -f "${{RUNFILES_DIR}}/{module_bazel} exec "${{RUNFILES_DIR}}/{reporter}" \\ --coverage_allowlist="{allowlist}" \\ --path_map="{path_map}" \\ - --baseline_objects="{baseline_objects}" \\ --workspace_root="${{WORKSPACE_ROOT}}" \\ - --llvm_cov="{llvm_cov}" \\ - --llvm_profdata="{llvm_profdata}" \\ -{cxxfilt_line} "$@" +{tool_lines} "$@" """.format( module_bazel = _rlocation_path(ctx, module_bazel), reporter = _rlocation_path(ctx, reporter), allowlist = _rlocation_path(ctx, allowlist), path_map = _rlocation_path(ctx, path_map), - baseline_objects = _rlocation_path(ctx, baseline_objects), - llvm_cov = _rlocation_path(ctx, ctx.file.llvm_cov), - llvm_profdata = _rlocation_path(ctx, ctx.file.llvm_profdata), - cxxfilt_line = cxxfilt_line, + tool_lines = tool_lines, ) ctx.actions.write( @@ -99,26 +125,26 @@ exec "${{RUNFILES_DIR}}/{reporter}" \\ is_executable = True, ) - direct_files = [ - reporter, - allowlist, - path_map, - baseline_objects, - module_bazel, - ctx.file.llvm_cov, - ctx.file.llvm_profdata, - ] - if ctx.file.llvm_cxxfilt: - direct_files.append(ctx.file.llvm_cxxfilt) - - # The in-scope source files travel with the reporter: llvm-cov must read - # them at report time, and neither generated headers nor external - # repositories are reachable through the workspace directory then. + direct_files = [reporter, allowlist, path_map, module_bazel] + if backend == "llvm": + direct_files += [baseline_objects, ctx.file.llvm_cov, ctx.file.llvm_profdata] + tools = [ctx.attr.llvm_cov, ctx.attr.llvm_profdata, ctx.attr.llvm_cxxfilt] + if ctx.file.llvm_cxxfilt: + direct_files.append(ctx.file.llvm_cxxfilt) + baseline_inputs = object_files + else: + direct_files += [gcno_manifest, ctx.file.gcov] + tools = [ctx.attr.gcov] + baseline_inputs = gcno_files + + # The in-scope source files travel with the reporter: the coverage tool + # must read them at report time, and neither generated headers nor + # external repositories are reachable through the workspace directory then. runfiles = ctx.runfiles( files = direct_files, - transitive_files = depset(transitive = [object_files, source_files]), - ).merge(ctx.attr.reporter[DefaultInfo].default_runfiles) - for tool in (ctx.attr.llvm_cov, ctx.attr.llvm_profdata, ctx.attr.llvm_cxxfilt): + transitive_files = depset(transitive = [baseline_inputs, source_files]), + ).merge(reporter_attr[DefaultInfo].default_runfiles) + for tool in tools: if tool: runfiles = runfiles.merge(tool[DefaultInfo].default_runfiles) @@ -131,11 +157,25 @@ reporter_wrapper = rule( implementation = _reporter_wrapper_impl, executable = True, attrs = { + "backend": attr.string( + default = "llvm", + values = BACKENDS, + doc = "Coverage data format: llvm (Clang/rustc covmap) or gcov (GCC/QCC .gcda counters).", + ), "reporter": attr.label( + executable = True, + cfg = "exec", + doc = "Override of the reporter binary; defaults to score_coverage's reporter for the backend.", + ), + "_llvm_reporter": attr.label( executable = True, cfg = "exec", default = Label("//score_coverage:reporter"), - doc = "The coverage reporter binary (defaults to score_coverage's).", + ), + "_gcov_reporter": attr.label( + executable = True, + cfg = "exec", + default = Label("//score_coverage:gcov_reporter"), ), "coverage_scope": attr.label( cfg = "target", @@ -149,13 +189,16 @@ reporter_wrapper = rule( ), "llvm_cov": attr.label( allow_single_file = True, - mandatory = True, - doc = "llvm-cov binary, e.g. @llvm_toolchain//:llvm-cov.", + doc = "llvm-cov binary, e.g. @llvm_toolchain//:llvm-cov (llvm backend).", ), "llvm_profdata": attr.label( allow_single_file = True, - mandatory = True, - doc = "llvm-profdata binary, e.g. @llvm_toolchain//:llvm-profdata.", + doc = "llvm-profdata binary, e.g. @llvm_toolchain//:llvm-profdata (llvm backend).", + ), + "gcov": attr.label( + allow_single_file = True, + doc = "gcov binary of the compiler that produced the .gcno/.gcda files, e.g. " + + "@score_qcc_x86_64_toolchain_pkg//:gcov (gcov backend).", ), "llvm_cxxfilt": attr.label( allow_single_file = True, diff --git a/score_coverage/requirements.in b/score_coverage/requirements.in index c3726e8..e7858f2 100644 --- a/score_coverage/requirements.in +++ b/score_coverage/requirements.in @@ -1 +1,2 @@ pyyaml +gcovr==8.6 diff --git a/score_coverage/requirements_3_12.txt b/score_coverage/requirements_3_12.txt index 1e80040..1e8f918 100644 --- a/score_coverage/requirements_3_12.txt +++ b/score_coverage/requirements_3_12.txt @@ -2,8 +2,293 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# bazel run //coverage:requirements_3_12.update +# bazel run //score_coverage:requirements_3_12.update # +colorlog==6.12.0 \ + --hash=sha256:2a7924c1dadf18b22a0eb8b06d1c7b01d5341707ec1641eb6fcc4fde0c3e8e5f \ + --hash=sha256:30d392604e9110045a2c2aeefc27d7a017abbab63f3a8aee594eac0801df784e + # via gcovr +gcovr==8.6 \ + --hash=sha256:b2e7042abca9321cadbab8a06eb34d19f801b831557b28cdc30a029313de8b9e \ + --hash=sha256:dbf9d87c38042752ad6f530aa8210427e22b526611bb7b7bfed0e81977d1f1ef + # via -r score_coverage/requirements.in +jinja2==3.1.6 \ + --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ + --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 + # via gcovr +lxml==6.1.3 \ + --hash=sha256:032a0a97eed428bd143c75a11118238546424ceb2fa311cca5f073aa44658dc4 \ + --hash=sha256:05f5bce9af14fd1506997594bd81cee6d9c6b58ea80a39c058327aa6371ed9e9 \ + --hash=sha256:0794e04ba343852c6d78e996c58ef4b8e579b4ecc72f8df0d4058bf843b4c96e \ + --hash=sha256:0ab2467e405e748d93495fb5568e74044802b8d3ff2b2a1607c3f78c6e982de5 \ + --hash=sha256:0bf5a3e397df2ec4258eb5eea4c1ac6cf013ca1abd04a176903bff20a70021fe \ + --hash=sha256:0c0710ac085a157b593c38fbcacd950f15c4afa8e2057527185875ab302752bc \ + --hash=sha256:0dee106e9aa97fb00541b1ed7827070564d0549c3d3fba8920e6b20fd980f748 \ + --hash=sha256:0f17d83c48ee9dfd96abae3ac3e2108c76d2fc86ce96355e37b8da9f7f4ecc08 \ + --hash=sha256:0feebef8d0521188d0157f758356072e840173aa61ca45b8b3f87959ac283dd5 \ + --hash=sha256:13a620a3fcc20023f9e6ed5c383e00e826f1c2d5db554df2f67240760f9118e8 \ + --hash=sha256:13d22c0d57355366b393936acf6b98a5e0edeadddd3fccbc6a846c50a76b8741 \ + --hash=sha256:160fcf381f76c3aeac28a756bec44f48942a8f7245a87aa28e3a523b4d90cd87 \ + --hash=sha256:16148acd77ed1d8836a56db883af2f5eed720f9723088110b16a0d08582130a6 \ + --hash=sha256:170773d8a3cdc76259065523ddd978c44f9806e28605f08812e8f86783e44ac6 \ + --hash=sha256:18293f8a8d8b6a8e71ef37706b659e3846a4261232158167b1ddf35f6994f633 \ + --hash=sha256:18a4db52b5a7b53a3540b0b0f4123319334621ee8083d496de314d0bf06ff59a \ + --hash=sha256:1a635e837b50a1819bebfedaac5916498ea024120969da8790500148fb0a894d \ + --hash=sha256:1aeca87830c4fe649dcf93fe2b059525b71c72587f21be4ae4af7103082a79fa \ + --hash=sha256:1b7c37339d7e75cab9a123a04248e243cefefb302ad6db566ea0c77cbcde421e \ + --hash=sha256:1beb0f9909b26cee938df9ba56b15252a84429b1fc30ce6fca161390b9789a70 \ + --hash=sha256:20384c2bbcbf87180c8c61eb60869699c1ec0cd09b62cfd13804022d860b0867 \ + --hash=sha256:20428910dae17a1a93152a3ff2c0441d2f4932992c0797d65651dd0561f1792f \ + --hash=sha256:207dfc3d47cf0e575e643bbc140dacc8863b39abaa1e5307cd64c7f2365b8a12 \ + --hash=sha256:209c3ccbfe35a04ac6d24f0611f9d1cbf8025d49991b14acd935236234d6c156 \ + --hash=sha256:2123e5aa075ac20d23c7af489255efd129cbfe190dbe88fd42598cc9df3199b6 \ + --hash=sha256:21402998e4b78e7cce237d2788841aaa21ac9a4d1574d04dc2d12ee41ae807b5 \ + --hash=sha256:2221e88679d1351e9a40aaee54bc65679b9795bbd0160bc3d5e36b163344eb75 \ + --hash=sha256:22eec57e26c418cde02c051ce9914a365e52a7f135a565c6f0480242aeebab48 \ + --hash=sha256:23c366231259cd75ad06495174701afb3fcb36a92917fa47de2d1f1bd9d95739 \ + --hash=sha256:25f4118c438f96bb466e83108506d03d5c31b1bd2387e83e5b070bda6ded9c37 \ + --hash=sha256:28a23fefdb345b2d4d0ff2860571b5ff9a89a28b6a120f720e8fb0324d346626 \ + --hash=sha256:290f66b97ede0e552e1cb44a0fd8a74f9753ee635b50830a0b122fb72788d015 \ + --hash=sha256:2b9b1325ca1c2a9a2dbb6eb913ae563313f2082ae60b03210f7e83ee80712274 \ + --hash=sha256:2bec13085dc8ef48a3fe62f7dfcacfeda2c785cdf19cc8eeda2bb9ed081da165 \ + --hash=sha256:2cae5d5c90a62d9139c512a0cb1aad1d182b022b5740daea2617eb5bf7fc658e \ + --hash=sha256:2e01125896585139453cab8cb235893644d8815d7509520da95ae3ee8d1c1f79 \ + --hash=sha256:2e62c569ec7531b679b184cbfe335c501c1d13c4b363560013019962eb630e6d \ + --hash=sha256:2f5b2a2b9811b853b39bfa41367c6d78747b8e3e80e07fc5a24aae295c1a4d7d \ + --hash=sha256:302f72413251c03f671e063c9414bed5dc8c927069e5abb69245521e51a4e81b \ + --hash=sha256:32a409be3190b088f960ac92bfedfbef2f86c49ff940765e1548177592d20026 \ + --hash=sha256:33cadd956b667997e4de1635fce9541f2e8ede2038fcde8cf55aa14d571d1bad \ + --hash=sha256:379f8a75cf6eb7eef0af074b55f49ab73b868388a98de14646abcdfa4564bb11 \ + --hash=sha256:3847e71a78cbbc1aff955dbbbaf2fff12153f611d3162c5beaa3395636cbc2f9 \ + --hash=sha256:38fc4e4e4e084e0bd491949482527d406788045c546d4f8789e93fc527b91385 \ + --hash=sha256:3a27ac6c780c8b8a1cd231b58407634cafc1c4cc28cd6c7141362df0f36351e7 \ + --hash=sha256:3a48093cdb058a93af842ede9703520e810b05dcd0fc6d7190a06376c3bfb6bd \ + --hash=sha256:3e42265103fb385d8642a78672edf376c6f7e1d3598a7a4f9cb1278f2f6b5f6f \ + --hash=sha256:3e9a00d1c2c30936f7add097c41afc5da6556c580909104aafd382cac92a855c \ + --hash=sha256:40983eabefd13da003e68170928c7acc011f0d095eefce5871a3c71c9385fb9a \ + --hash=sha256:40bcbd9f94166ffe925811e730607385cec959f42fb1bb7dad83748680465221 \ + --hash=sha256:41096ec0740a58dad03d3ae0c7486d306d20becefb13ceb1649835ab3eb64167 \ + --hash=sha256:415e3a115c0d510e329020012834d1c0aa1c581ee53a218603e38abbc1dea70a \ + --hash=sha256:41e2d428110b408e963b6fb18f9bbf1f5c027b56bd4b498d54556476c0aeb1c3 \ + --hash=sha256:424aa5657141d306ba9ad1baab4b2c0a0719040075ee6c66aee9bb2dea2b5054 \ + --hash=sha256:42632b4024ab24a6b488f559ac851312509888b6b80ae2aa11cf29a646a0d245 \ + --hash=sha256:45222d94ddd511536f3b2f7d9deae3b2339b4ce0f075f1ca25703b07cad9dd21 \ + --hash=sha256:4736e6c87e603146d8949d8501da621ad20c31015060d3fcf95ace2859f3e3e6 \ + --hash=sha256:48542c9acba9ff9450bd18d871d2c2c8787fdb283572b623d206f1b927cd7d9e \ + --hash=sha256:49fbc2682a9306135b7ec49e93f97f9c26689b9b7f96ed2742d8d6497e994d13 \ + --hash=sha256:4a579dfb9c835f8ab47f4b8ed33440cbc75b806b73297208e6ec2a33e903740b \ + --hash=sha256:4b061064b4a2fe8598a466d723d43dbcd5a610a5d5cfe02fb6226f5c17349f75 \ + --hash=sha256:4e11e885e0704be185867fcf71b904d8f65d7d6877bc121f69870b0d0479ba7b \ + --hash=sha256:4f4db7c7e954d289d71878938348b3d91b904a3e8210a11939359fb758a58e7d \ + --hash=sha256:527195c188d7d0af748cd48d220ab8cdc5cb99be3d49ac4d9be7324d8abf9bc0 \ + --hash=sha256:53258656846f5c48996b882fb4b135885e088a3ad3d96b4bc0530f95124d1f69 \ + --hash=sha256:545ccc14fb05485f48b4439ec35beb16d5b5280eb6c81c658bd4707a2a119414 \ + --hash=sha256:5609efdb0d3c95499c00046bc53648b3482ec2175b5503d6e611b3f0555dc71d \ + --hash=sha256:5929d9df5e7e3379183be0e21f7d559618a5b61cb63280df6164019242e337ed \ + --hash=sha256:5a143e6207579de8baeded4eaac9134413200359f1969d636f0bfb98ee8c3c8f \ + --hash=sha256:5a721a98c649855963811b59b55755b30566e7f7fc40bdc9803d66dee9f811cf \ + --hash=sha256:5cffe18571ccc51d742cd08cbb3f8b756de9311d18c7ea98f5d92f37b8fb60c2 \ + --hash=sha256:5d12669a2c419b0e8dc423d23dea24bb82f6f9cb829f32e04674b0ba40322a7c \ + --hash=sha256:5d582042c69857c364e8153de6e18e0da9b7b515a6a8113caf69a6ec8e0520f2 \ + --hash=sha256:61116cec57ed69aebc70f37a545eec095339bb829efbdabcfb97c51e9536e158 \ + --hash=sha256:611a51e61c92f62345a50b0035df6fc0d678f9299f33728826d831598862f59d \ + --hash=sha256:623c8799c17128753c65699f1c3aa32402657393a9ad6db09ed8b98ddf76611d \ + --hash=sha256:6374e9e382e5a98c9c5e66d41b357b470da1c54bce30f17f9dc4bcc58436cc1c \ + --hash=sha256:66299564c046bc7e0cc5de5106601eae907e9fa5904cd68a323380a8502f7861 \ + --hash=sha256:69cafd61aea04ebb3502c93c2aaa568b12931ca0802231e0b5de76bf8b6e74bd \ + --hash=sha256:6a406d0b3cb207b0fa460ed4dc93e866f44f105da0169361cb18ff998a44c7f0 \ + --hash=sha256:6ba4fe5bfbef6811a8e49b3719cde373ad399006c0c1ac184b7297116ecbba5d \ + --hash=sha256:6cd11e7550d89e551a87dcec30f04b1fca32e86b68708aa01a4daa455d8605e5 \ + --hash=sha256:6e1eb8a4cbffd5553680ad96be6680e364710656eced73d1dc90ec489df599a3 \ + --hash=sha256:6ea2f13dce778ca072ccee598bca46a092ce192e8fd907b6c1f0e52c800529a0 \ + --hash=sha256:71532ebf30be0048a45559b4fab15333fbaaf9042f658e878d918ecd0cf09805 \ + --hash=sha256:73fc05988ed20809450474ba760a87c8ad4e455fc09783c02195e56ec634b41a \ + --hash=sha256:75cc6569e86be5785b6188ef1642670c6adbc984e81ec35e224842ecd9eefcc8 \ + --hash=sha256:773062aec2f2e56b2b22d37054123f0de8a22a4688a0c3376c3fe42685f975cf \ + --hash=sha256:7ae4949f212a53b007dbc355884fda122545c5764a54256c9217e419a62a6559 \ + --hash=sha256:7b2bb7d703bed7ac893bf7f40d97b5d9279d35d2ce460624ca28929eab0d5a3d \ + --hash=sha256:7d0f5976aa2701996f759b30172925829867547bb073af0ae67d1307a0f0262c \ + --hash=sha256:7d5a748d12dd9b535e0a130f60dae9ddf0adafbabe61e7864f55c7436c84547a \ + --hash=sha256:7dd624c1eaa629ad44b59a1a0145fdf2d67895592dce94c9358b938b3d075e65 \ + --hash=sha256:7f75b9b9fec2a9c6b18095c81865580e795b1441c429e42d22fcc82a77f40039 \ + --hash=sha256:83e3a51e7933db700a0da0db31849db3a24022d9970da9bb73001e1d0326fd92 \ + --hash=sha256:8499d464de86fab0f102313cce32a9bed9ab1f06ec813cf025cb790964fbb765 \ + --hash=sha256:869dfcd4d381cb0ea87085cc4f011b9171b494ef21e76ad8665f6d5e2d1dc8a1 \ + --hash=sha256:8753b8d51dbc86fd335ee31fcf7f3658e9f5c016d4edfb23f76ad295f4b8c9d0 \ + --hash=sha256:887c021d9a977cff89cb273047c1352997b772a8908a25c21836861f69b92be1 \ + --hash=sha256:88e719b9437f148f7e1465df845c758dd1598618cbea3a2fd1e61a715542f2b2 \ + --hash=sha256:8a330c0ee5fa318c7b5cbbaad882baeca3f570357e7eb25ab34bf31008150758 \ + --hash=sha256:8db38ff3fb7aee7d6a82ae4da2eef1178656fe1216841fbd24870062a9d60473 \ + --hash=sha256:8e49a646acfab83c68974f4aa1d0a2acca9e88d7d627ae0fc13201b14b76d310 \ + --hash=sha256:909f4e927bb051f7740d6367285fc60cdcfdaf0258c2dba4ff5ba7eadadc250c \ + --hash=sha256:90f709b9accab6b2e4d14f5c8718203877a0486bcb3afd74d8b539ecd1e961d4 \ + --hash=sha256:92d96586376fb79a33474797186bf993250152ee5c32650b67db78d54b92e6f3 \ + --hash=sha256:93476b6514b373fc6ca67d26c442784f7807c86f00635bfe79f935c3eab2af17 \ + --hash=sha256:97acecb11cbc411473f15b8d780df06d7a9f3a2aad9aca78364f56640c8fb70e \ + --hash=sha256:97ce49699d87ebf8aad631b55d65b33219a4f1bfefbbf5bff19dc9af160aeaf9 \ + --hash=sha256:9bde9ae026a55b9a192078dfa6e27dd0ca4a050171ab6272e92f97b757dfdf48 \ + --hash=sha256:9e67324961ac9bbe616cce5100514d2e34d88665aeb07071e8b16eac55d06d94 \ + --hash=sha256:9efe56a68179f3adc4de41861c9358931db03837c48dd5e1c78077b84dd07f3a \ + --hash=sha256:a1932d7ce78a561367512c594fe66eac2b2ec9b9264cfd9b5f950622f4a116e2 \ + --hash=sha256:a1cec0f99b9b914d39176347a93b7610dc09324491aee1cbc57cd291a41a1d55 \ + --hash=sha256:a2e3f70673a1d5b82f38255f777d26cd855bf2092b1436c4867464a7892f9238 \ + --hash=sha256:a43b3bdf11e477dc7770609d3477316f974354dfc8425d596f64f471cc8daf6e \ + --hash=sha256:a5c18810318303ce9afb3f95e2ddb54834f96fa699a8600433fd5a93dcf44c56 \ + --hash=sha256:a7eb78ba28b187e1e9203a55c60fcf70df2d22cb205fe6d51b9383d6097419f0 \ + --hash=sha256:aa633613ff907ea91b9b0489a1f0da1b8725d8c6ccec6b77e8a1c9c235044bb0 \ + --hash=sha256:aa9fd1ee2a5dacfc41039ed49ffeeacfa75bafbd255b69f3b578e11897a0e623 \ + --hash=sha256:ace1d2c83b2bd24db5940600541140e87a325e119cb32d5fa9ad720d7e76648e \ + --hash=sha256:b1cc980905221a5d8b3c476330730b3adb40ff80add71ffbdb6215ba055656f1 \ + --hash=sha256:b37772102d44bb6628186accca3a121b1fa3a6b3d97518a8c29a5229ca4c0d0a \ + --hash=sha256:b3ff39654f0ce6ebd4db154211136dbe7e8157bcc3bed2344c87f32c7c6ecb6c \ + --hash=sha256:b477912f42c5c33405a10c759d22f80cf5af043ae02d95b9d8e5e5bc555739ed \ + --hash=sha256:b49638355ea3bebba70da783ccbc630fd72afa16bc46c54474bfa1f9a915bbc6 \ + --hash=sha256:b4fc6b03b9d9d90557274f571ab30e7fbbfc527955536935d96f98b6817a86e4 \ + --hash=sha256:b50343241eb69fd85f7791cf8bcc7b1c4729826b7d59ba2f6b27db29638fa745 \ + --hash=sha256:bc8dd3d9c93e70c3df974a201ac2958b6d77b465d813c51d1f15fa8e645763ae \ + --hash=sha256:be5346653c0b0e34be96869ff9dbeba23860156f89a2896a64c64fb419260cb6 \ + --hash=sha256:c00e26288784460885fe76e4d4b293573e0f791f52e6d60e27b42edf005922eb \ + --hash=sha256:c1b50797ac246bb2942a04b6c0f69af0667aba7cf7535f39bbb1b3208fd5d128 \ + --hash=sha256:c34ca1dc41bd86d9ff830d5bdf4e4a752bba6c54f7d2707027ce0eabd36084c9 \ + --hash=sha256:c55e71a9b1db1f107efb60da49c093689b74c5c31a708e5379e2fd9439d4fbb5 \ + --hash=sha256:c581b1d68b3845fb86c6b2983e755b29bf001461c59fa411d2c26a911b6559a9 \ + --hash=sha256:c59e4265608da6a041f54646ecc0c9ecdbb19aaf14c4c684bb6c2114998cc415 \ + --hash=sha256:c5e7ce578aa8a80910a72a8ca0bbea3baae10100827249001999726a788456d8 \ + --hash=sha256:c66f858b82497173f73366795fc6ee8171620e75a338506d6b2e7bc16f5fca11 \ + --hash=sha256:c6c0c13128a32eb04a51357e56a094e13aa8e6d3d1884de2e9ae923f6915e1a8 \ + --hash=sha256:c9389b3784b56c58d933b5e0aecdf28f901b073ff385358d8a7d40907f6e14b2 \ + --hash=sha256:ca0ec532ad2f5ba1e5ec120ac157769c57f01855b3d8bf37213f5d88abd9ba0a \ + --hash=sha256:cad7617727a96d189bd6f979d0fadf765198c7934e85f4edaba9bf3ad919a300 \ + --hash=sha256:cae82b5ca24b0c2beedb269f6e2a96f466acd926879ab00ae19f1a65cbf9ffb0 \ + --hash=sha256:cc669256d28736f7f3a149df5c380c50ace2692ba3e62203d10656fade4a2145 \ + --hash=sha256:ce1f220114959941170e22b8ad44279f6dee2dcef7591814d01ae805dc058889 \ + --hash=sha256:cfb398886a7eb4c719161c3efcff2a1248febc53a4d8e5072d2d8a87fed84ac9 \ + --hash=sha256:d077f21f4b16f0471353883748f126f62038760397c107bb9fad2ca94dc0dfb7 \ + --hash=sha256:d0c5c362bc94f1929dc7e96e715bbe7bd17037f802e6d8f0d1545df9133c0559 \ + --hash=sha256:d2765c18ce303149ee804b1f3dad11232726dd0a702d73a15cf19179ac8cc962 \ + --hash=sha256:d44442effeb8781f392340c5dc8c6716fba41dbeacb82fd4c0f09026fb5ff682 \ + --hash=sha256:d85dfab42dd672f87a7f76e9de7172962aee69fa12044f0d6e1a23cbd53fb80e \ + --hash=sha256:d97c5227621af74b111882a290b10f371780a38eef9d9e730408fba2259b52fb \ + --hash=sha256:d9a0d12846d6ce434fb3857918eef4315ec9b4769deb020c75828798614bfcfd \ + --hash=sha256:d9b3e7d71bf6acff341233417abbdface29c647e3113892d9aaedc02eb4aa2bc \ + --hash=sha256:da707f14ea3c35ee463d50acd596d6488e4b2b4ae7cf77a5bf93f55c023d63e8 \ + --hash=sha256:da85db328e507da922d586c3c7416ec360ec22e9cd9e0700691afacde0c81f53 \ + --hash=sha256:dc205732d593118cf701d986f40e9de7801bb2e371cb189ddbda9b7348f4d97e \ + --hash=sha256:dc3a44689eea43eab836e5c98a8ab015dc2419987d1ea6eafc7c590cdff86bed \ + --hash=sha256:dd5e90f34cffcfed97f36cf066325773d2b6021c60c29942e53a18b028501b1d \ + --hash=sha256:ddcf547bea2aee967d6a77779376a45e77e610e8465147a1f3d7e20d539d6e32 \ + --hash=sha256:e477aca0bc0d19f3b4ae9e4f2a1cfd687c31bf772d78734910658186b40b2477 \ + --hash=sha256:e8b17e23df3e827a69d25af70990ca2420e92668aaffaeeb3cd2351d7916a023 \ + --hash=sha256:e99e09ab7741f1281e2677f4c0058c7f5267d182530b09c87e4f6aa26adf3887 \ + --hash=sha256:ea2c01cdb16dc12156e455007c406dfaaece0c89aa4ba0e3b47586779f951d41 \ + --hash=sha256:ea6b1e9105b4b24a34c722432d9fb578f9ed83af21fa1abda639011e0f22bbb6 \ + --hash=sha256:ebd054ad1737a68fb7c5c073d405cef2b88bb824e294de3b4a4e995b47f0e376 \ + --hash=sha256:ec295280f4b37769256da025acf5890370355ac589c27e89caae0b5e9eedc702 \ + --hash=sha256:f6449672f9c93316deb5e2839e18931f468670e44d5bd9b1301a5a9655d45c07 \ + --hash=sha256:f683dc6300317700025e41d89a43e0276692ded16113a3c43eab704d605c58e5 \ + --hash=sha256:f6b9d2aad499c769ee8287609ab0e6de99d8bcea99c6e6c2e64945259fd52fb2 \ + --hash=sha256:f8b9c8ceebae6387d0dc77f7f4dbbfbfc962dba2efbfe6877486075a480726b4 \ + --hash=sha256:fad67b12ffe0f71e02b4932b04883cbc76a9072bbd30731409d3523cf058b011 \ + --hash=sha256:fbfb70ba01355251faf6b293171df49f73a88a1b6494db109ffea85442574458 \ + --hash=sha256:fe91993149523aa59941b9e3c90e2eb45f57ad014697aef6c8b13339a59c019e \ + --hash=sha256:febd35ef45f603c2d74b74655efdbf45e14f55fc0aef4ac82b663ca829b283e0 \ + --hash=sha256:ff88a92cafde90888511242d1c54afcc1a8adbb6dc0a88fa7f87e29e92400d4a + # via gcovr +markupsafe==3.0.3 \ + --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \ + --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \ + --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \ + --hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \ + --hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \ + --hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \ + --hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \ + --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \ + --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \ + --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \ + --hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \ + --hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \ + --hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \ + --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \ + --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \ + --hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \ + --hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \ + --hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \ + --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \ + --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \ + --hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \ + --hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \ + --hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \ + --hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \ + --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \ + --hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \ + --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \ + --hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \ + --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \ + --hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \ + --hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \ + --hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \ + --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \ + --hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \ + --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \ + --hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \ + --hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \ + --hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \ + --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \ + --hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \ + --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \ + --hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \ + --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \ + --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \ + --hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \ + --hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \ + --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \ + --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \ + --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \ + --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \ + --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \ + --hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \ + --hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \ + --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \ + --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \ + --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \ + --hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \ + --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \ + --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \ + --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \ + --hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \ + --hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \ + --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \ + --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \ + --hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \ + --hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \ + --hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \ + --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \ + --hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \ + --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \ + --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \ + --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \ + --hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \ + --hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \ + --hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \ + --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \ + --hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \ + --hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \ + --hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \ + --hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \ + --hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \ + --hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \ + --hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \ + --hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \ + --hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \ + --hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \ + --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \ + --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \ + --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50 + # via jinja2 +pygments==2.21.0 \ + --hash=sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9 \ + --hash=sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c + # via gcovr pyyaml==6.0.3 \ --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ --hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \ @@ -78,4 +363,4 @@ pyyaml==6.0.3 \ --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \ --hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \ --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 - # via -r coverage/requirements.in + # via -r score_coverage/requirements.in diff --git a/score_coverage/tests/BUILD b/score_coverage/tests/BUILD index 8fe2220..f2ca9b7 100644 --- a/score_coverage/tests/BUILD +++ b/score_coverage/tests/BUILD @@ -43,6 +43,15 @@ score_py_pytest( ], ) +score_py_pytest( + name = "gcov_reporter_test", + srcs = ["gcov_reporter_test.py"], + deps = [ + ":traceability", + "//score_coverage:gcov_reporter_lib", + ], +) + score_py_pytest( name = "coverage_summary_test", srcs = ["coverage_summary_test.py"], diff --git a/score_coverage/tests/effective_coverage_test.py b/score_coverage/tests/effective_coverage_test.py index 0486971..2960ffb 100644 --- a/score_coverage/tests/effective_coverage_test.py +++ b/score_coverage/tests/effective_coverage_test.py @@ -661,6 +661,31 @@ def test_partial_covered_line_class(self): stats = ec._process_gcovr_file(page, {5: self._j("p")}, self.applied, self.stale) self.assertEqual(stats["justified_branches"], 1) + def test_bare_rows_of_non_instrumented_lines_do_not_shift_statuses(self): + # gcovr 8.x emits a bare for lines the + # compiler emitted no code for (comments, blank lines, braces). The + # status of the next instrumented row must not be attributed to them. + bare = "".join( + ' \n' + f' {n}\n' + ' \n \n' + ' \n' + f' // line {n}\n' + " \n" + for n in range(1, 17) + ) + self.page.write_text(_gcovr_source_page("src/coverable.cpp", bare + COVERABLE_ROWS), encoding="utf-8") + stats = ec._process_gcovr_file(self.page, {24: self._j("pos"), 19: self._j("cov")}, self.applied, self.stale) + self.assertEqual(stats, {"justified": 1, "stale": 1, "justified_branches": 0}) + self.assertEqual(self.applied[0]["line"], 24) + self.assertEqual(self.stale[0]["line"], 19) + after = self.page.read_text(encoding="utf-8") + self.assertIn( + '24\n \n \n' + ' ', + after, + ) + def test_no_justifications_leaves_page_untouched(self): before = self.page.read_text(encoding="utf-8") stats = ec._process_gcovr_file(self.page, {}, self.applied, self.stale) diff --git a/score_coverage/tests/gcov_reporter_test.py b/score_coverage/tests/gcov_reporter_test.py new file mode 100644 index 0000000..76a6bf4 --- /dev/null +++ b/score_coverage/tests/gcov_reporter_test.py @@ -0,0 +1,464 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +"""Unit tests for the gcov backend reporter (GCC on Linux, QCC on QNX).""" + +# pylint: disable=missing-function-docstring,missing-class-docstring,protected-access,consider-using-with +# pylint: disable=too-many-instance-attributes + +import io +import os +import stat +import tempfile +import unittest +import zipfile +from contextlib import redirect_stderr +from pathlib import Path +from unittest import mock + +from score_coverage import gcov_reporter +from score_coverage.gcov_reporter import FileRecord, parse_lcov, render_lcov +from score_coverage.reporter import FileSelection +from score_coverage.tests.traceability import verifies + +LCOV_A = """SF:src/a.cpp +FN:3,_Z1ai +FNDA:2,_Z1ai +FNF:1 +FNH:1 +DA:3,2 +DA:4,1 +DA:5,0 +BRDA:4,0,0,1 +BRDA:4,0,1,0 +BRDA:6,0,0,- +BRF:3 +BRH:1 +LF:3 +LH:2 +end_of_record +""" + +LCOV_A_AGAIN = """SF:src/a.cpp +FN:3,_Z1ai +FNDA:1,_Z1ai +DA:3,1 +DA:4,0 +DA:5,1 +BRDA:4,0,0,0 +BRDA:4,0,1,1 +BRDA:6,0,0,- +LF:3 +LH:2 +end_of_record +""" + + +class _FakeRunfiles: + def __init__(self, mapping): + self.mapping = mapping + + def Rlocation(self, path): # noqa: N802 # pylint: disable=invalid-name + if os.path.isabs(path): + return path + return self.mapping.get(path) + + +GCOV_JSON = { + "format_version": "1", + "gcc_version": "12.2.0", + "current_working_directory": "/proc/self/cwd", + "data_file": "uncovered.pic.gcno", + "files": [ + { + "file": "src/uncovered.cpp", + "functions": [{"name": "_Z1fv", "start_line": 16, "end_line": 20, "execution_count": 0}], + "lines": [ + {"line_number": 16, "count": 0, "unexecuted_block": True, "branches": []}, + { + "line_number": 17, + "count": 0, + "unexecuted_block": True, + "branches": [ + {"count": 0, "throw": False, "fallthrough": False}, + {"count": 0, "throw": False, "fallthrough": True}, + ], + }, + {"line_number": 19, "count": 0, "unexecuted_block": True, "branches": []}, + ], + }, + { + "file": "bazel-out/k8-fastbuild/bin/src/_virtual_includes/v/api.h", + "functions": [], + "lines": [{"line_number": 5, "count": 0, "unexecuted_block": True, "branches": []}], + }, + ], +} + + +def _fake_gcov(path: Path, payload: dict) -> Path: + path.write_text( + "#!/usr/bin/env python3\nimport json, sys\n" + "sys.stderr.write('x.gcda:cannot open data file, assuming not executed\\n')\n" + f"print(json.dumps({payload!r}))\n", + encoding="utf-8", + ) + path.chmod(path.stat().st_mode | stat.S_IXUSR) + return path + + +@verifies("tool_req__coverage_gcov_merge") +class ParseAndRenderLcovTest(unittest.TestCase): + def test_parse_records_lines_branches_functions(self): + rec = parse_lcov(LCOV_A)["src/a.cpp"] + self.assertEqual(rec.lines, {3: 2, 4: 1, 5: 0}) + self.assertEqual(rec.branches, {(4, "0", "0"): 1, (4, "0", "1"): 0, (6, "0", "0"): None}) + self.assertEqual(rec.functions, {"_Z1ai": (3, 2)}) + + def test_records_of_two_tests_are_summed(self): + merged = FileRecord() + merged.add(parse_lcov(LCOV_A)["src/a.cpp"]) + merged.add(parse_lcov(LCOV_A_AGAIN)["src/a.cpp"]) + self.assertEqual(merged.lines, {3: 3, 4: 1, 5: 1}) + # a branch taken in either run is taken; a block never reached in both stays '-' + self.assertEqual(merged.branches, {(4, "0", "0"): 1, (4, "0", "1"): 1, (6, "0", "0"): None}) + self.assertEqual(merged.functions, {"_Z1ai": (3, 3)}) + + def test_never_reached_plus_count_is_count(self): + rec = FileRecord(branches={(1, "0", "0"): None}) + rec.add(FileRecord(branches={(1, "0", "0"): 2})) + self.assertEqual(rec.branches, {(1, "0", "0"): 2}) + + def test_render_round_trip_and_totals(self): + rec = parse_lcov(LCOV_A)["src/a.cpp"] + text = render_lcov({"src/a.cpp": rec}) + self.assertEqual(parse_lcov(text)["src/a.cpp"], rec) + self.assertIn("LF:3\nLH:2\n", text) + self.assertIn("BRF:3\nBRH:1\n", text) + self.assertIn("BRDA:6,0,0,-\n", text) + self.assertIn("FNF:1\nFNH:1\n", text) + + def test_zeroed_keeps_structure(self): + zero = parse_lcov(LCOV_A)["src/a.cpp"].zeroed() + self.assertEqual(zero.lines, {3: 0, 4: 0, 5: 0}) + self.assertEqual(zero.branches, {(4, "0", "0"): 0, (4, "0", "1"): 0, (6, "0", "0"): None}) + self.assertEqual(zero.functions, {"_Z1ai": (3, 0)}) + + def test_negative_counts_are_clamped(self): + rec = parse_lcov("SF:x\nDA:1,-1\nBRDA:1,0,0,-3\nend_of_record\n")["x"] + self.assertEqual(rec.lines, {1: 0}) + self.assertEqual(rec.branches, {(1, "0", "0"): 0}) + + +@verifies("tool_req__coverage_report_relative_paths", "tool_req__coverage_gcov_merge") +class PathsTest(unittest.TestCase): + def test_normalize_raw(self): + for raw in ("/ws/src/a.cpp", "/proc/self/cwd/src/a.cpp", "./src/a.cpp", "src/a.cpp"): + self.assertEqual(gcov_reporter.normalize_raw(raw, "/ws/"), "src/a.cpp") + self.assertEqual(gcov_reporter.normalize_raw("/usr/include/x.h", "/ws"), "/usr/include/x.h") + + def test_canonical_names_use_path_map_and_foreign_trees(self): + raws = { + "src/a.cpp", + "bazel-out/k8-fastbuild/bin/src/_virtual_includes/v/api.h", + "bazel-out/k8-fastbuild/bin/src/_virtual_includes/twin/w/w.h", + } + names = gcov_reporter.canonical_names( + raws, {"src/_virtual_includes/v/api.h": "src/v/api.h"}, {"src/a.cpp", "src/v/api.h", "src/w/include/w/w.h"} + ) + self.assertEqual(names["src/a.cpp"], "src/a.cpp") + self.assertEqual(names["bazel-out/k8-fastbuild/bin/src/_virtual_includes/v/api.h"], "src/v/api.h") + self.assertEqual(names["bazel-out/k8-fastbuild/bin/src/_virtual_includes/twin/w/w.h"], "src/w/include/w/w.h") + + def test_merge_by_name_sums_variants_of_one_file(self): + # One test records the header by its declared path, another through a + # virtual-include path: both runs count for the same file. + records = { + "src/v/api.h": parse_lcov("SF:x\nDA:5,1\nBRDA:5,0,0,1\nend_of_record\n")["x"], + "bazel-out/k8-fastbuild/bin/src/_virtual_includes/v/api.h": parse_lcov( + "SF:x\nDA:5,2\nDA:6,1\nBRDA:5,0,0,0\nend_of_record\n" + )["x"], + "src/a.cpp": parse_lcov("SF:x\nDA:1,1\nend_of_record\n")["x"], + } + canonical = { + "src/v/api.h": "src/v/api.h", + "bazel-out/k8-fastbuild/bin/src/_virtual_includes/v/api.h": "src/v/api.h", + "src/a.cpp": "src/a.cpp", + } + merged = gcov_reporter.merge_by_name(records, canonical) + self.assertEqual(set(merged), {"src/v/api.h", "src/a.cpp"}) + self.assertEqual(merged["src/v/api.h"].lines, {5: 3, 6: 1}) + self.assertEqual(merged["src/v/api.h"].branches, {(5, "0", "0"): 1}) + + def test_rust_sources_are_not_findings_on_gcov(self): + sel = FileSelection(unmapped={"src/a.h", "rust/lib.rs"}) + self.assertEqual(gcov_reporter.mark_not_instrumented(sel), {"rust/lib.rs"}) + self.assertEqual(sel.unmapped, {"src/a.h"}) + + +@verifies("tool_req__coverage_gcov_baseline", "tool_req__coverage_report_baseline_zero") +class BaselineTest(unittest.TestCase): + def test_records_from_gcov_json(self): + records = gcov_reporter.records_from_gcov_json(GCOV_JSON) + unc = records["src/uncovered.cpp"] + self.assertEqual(unc.lines, {16: 0, 17: 0, 19: 0}) + self.assertEqual(unc.branches, {(17, "0", "0"): 0, (17, "0", "1"): 0}) + self.assertEqual(unc.functions, {"_Z1fv": (16, 0)}) + self.assertIn("bazel-out/k8-fastbuild/bin/src/_virtual_includes/v/api.h", records) + + def test_gcov_baseline_runs_the_tool_and_tolerates_failures(self): + with tempfile.TemporaryDirectory() as tmp: + gcov = _fake_gcov(Path(tmp) / "gcov", GCOV_JSON) + records = gcov_reporter.gcov_baseline(gcov, "x.gcno") + self.assertEqual( + set(records), {"src/uncovered.cpp", "bazel-out/k8-fastbuild/bin/src/_virtual_includes/v/api.h"} + ) + broken = Path(tmp) / "broken" + broken.write_text("#!/bin/sh\necho not json\n", encoding="utf-8") + broken.chmod(0o755) + with redirect_stderr(io.StringIO()): + self.assertEqual(gcov_reporter.gcov_baseline(broken, "x.gcno"), {}) + failing = Path(tmp) / "failing" + failing.write_text("#!/bin/sh\nexit 3\n", encoding="utf-8") + failing.chmod(0o755) + with redirect_stderr(io.StringIO()): + self.assertEqual(gcov_reporter.gcov_baseline(failing, "x.gcno"), {}) + + def test_gcno_manifest_resolution_and_missing_file_error(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + (root / "a.gcno").write_bytes(b"") + manifest = root / "gcno.txt" + manifest.write_text("# comment\npkg/a.gcno\n\n", encoding="utf-8") + runfiles = _FakeRunfiles({"m/gcno.txt": str(manifest), "_main/pkg/a.gcno": str(root / "a.gcno")}) + self.assertEqual( + gcov_reporter.load_gcno_manifest(runfiles, "m/gcno.txt"), {str(root / "a.gcno"): "pkg/a.gcno"} + ) + self.assertEqual(gcov_reporter.load_gcno_manifest(runfiles, None), {}) + with redirect_stderr(io.StringIO()): + self.assertEqual(gcov_reporter.load_gcno_manifest(runfiles, "m/nope.txt"), {}) + manifest.write_text("pkg/missing.gcno\n", encoding="utf-8") + with redirect_stderr(io.StringIO()), self.assertRaises(SystemExit): + gcov_reporter.load_gcno_manifest(runfiles, "m/gcno.txt") + + def test_compiled_stems_from_gcno_paths(self): + stems = gcov_reporter.compiled_stems_from_gcno( + [ + "src/_objs/uncovered/empty_unit.pic.gcno", + "src/_objs/uncovered/uncovered.gcno", + "_objs/tool/main.pic.gcno", + "src/not_a_gcno.o", + ] + ) + self.assertEqual(stems, {"src/empty_unit", "src/uncovered", "main"}) + + def test_merge_prefers_test_data_and_zeroes_baseline_only_files(self): + tested = {"src/a.cpp": parse_lcov(LCOV_A)["src/a.cpp"]} + baseline = { + "src/a.cpp": FileRecord(lines={3: 5}), # must not be added on top of test data + "src/uncovered.cpp": FileRecord(lines={16: 0, 17: 0}, functions={"_Z1fv": (16, 0)}), + } + sel = FileSelection(staged={"src/a.cpp": "src/a.cpp", "src/uncovered.cpp": "src/uncovered.cpp"}) + merged = gcov_reporter.merge_records(tested, baseline, sel) + self.assertEqual(merged["src/a.cpp"].lines, {3: 2, 4: 1, 5: 0}) + self.assertEqual(merged["src/uncovered.cpp"].lines, {16: 0, 17: 0}) + self.assertEqual(merged["src/uncovered.cpp"].functions, {"_Z1fv": (16, 0)}) + + +@verifies("tool_req__coverage_gcov_html", "tool_req__coverage_report_outputs") +class GcovrRenderingTest(unittest.TestCase): + def test_tracefile_shape(self): + rec = parse_lcov(LCOV_A)["src/a.cpp"] + data = gcov_reporter.gcovr_tracefile({"src/a.cpp": rec}) + self.assertEqual(data["gcovr/format_version"], "0.14") + entry = data["files"][0] + self.assertEqual(entry["file"], "src/a.cpp") + self.assertEqual([line["line_number"] for line in entry["lines"]], [3, 4, 5]) + self.assertEqual([b["count"] for b in entry["lines"][1]["branches"]], [1, 0]) + self.assertEqual( + [(b["source_block_id"], b["destination_block_id"]) for b in entry["lines"][1]["branches"]], [(0, 0), (0, 1)] + ) + self.assertEqual(entry["functions"], [{"name": "_Z1ai", "lineno": 3, "execution_count": 2}]) + + def test_render_html_with_real_gcovr(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + (root / "sources" / "src").mkdir(parents=True) + (root / "sources" / "src" / "a.cpp").write_text( + "int a;\nint b;\nint c(int x) {\n if (x) {\n return 1;\n }\n return 0;\n}\n", encoding="utf-8" + ) + html_dir = root / "html_report" + html_dir.mkdir() + cwd = os.getcwd() + os.chdir(root) + try: + with redirect_stderr(io.StringIO()): + gcov_reporter.render_html( + {"src/a.cpp": parse_lcov(LCOV_A)["src/a.cpp"]}, root / "sources", html_dir, root / "summary.txt" + ) + finally: + os.chdir(cwd) + names = sorted(p.name for p in html_dir.iterdir()) + self.assertIn("index.html", names) + self.assertTrue(any(n.startswith("index.a.cpp.") and n.endswith(".html") for n in names), names) + summary = (root / "summary.txt").read_text(encoding="utf-8") + self.assertIn("src/a.cpp", summary) + self.assertIn("TOTAL", summary) + + +@verifies( + "tool_req__coverage_gcov_merge", + "tool_req__coverage_gcov_baseline", + "tool_req__coverage_report_allowlist", + "tool_req__coverage_report_unmapped", +) +class GcovReporterMainTest(unittest.TestCase): + """main() end to end with two per-test LCOV files, a fake gcov and a scope.""" + + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.root = Path(self.tmp.name) + self.ws = self.root / "ws" + for rel, text in { + "src/a.cpp": "int a;\n" * 8, + "src/uncovered.cpp": "int u;\n" * 20, + "src/v/api.h": "int api();\n" * 6, + "src/uncovered.h": "int u();\n", + "rust/lib.rs": "fn x() {}\n", + }.items(): + (self.ws / rel).parent.mkdir(parents=True, exist_ok=True) + (self.ws / rel).write_text(text, encoding="utf-8") + (self.root / "t0.dat").write_text( + LCOV_A + "SF:external/gtest+/x.cc\nDA:1,1\nLF:1\nLH:1\nend_of_record\n", encoding="utf-8" + ) + (self.root / "t1.dat").write_text( + LCOV_A_AGAIN + + "SF:bazel-out/k8-fastbuild/bin/src/_virtual_includes/v/api.h\nDA:5,3\nLF:1\nLH:1\nend_of_record\n", + encoding="utf-8", + ) + (self.root / "t2.dat").write_text("SF:src/v/api.h\nDA:5,4\nLF:1\nLH:1\nend_of_record\n", encoding="utf-8") + (self.root / "reports.txt").write_text( + f"{self.root}/t0.dat\n{self.root}/t1.dat\n{self.root}/t2.dat\n{self.root}/baseline_coverage.dat\n", + encoding="utf-8", + ) + (self.root / "baseline_coverage.dat").write_text("SF:src/a.cpp\nend_of_record\n", encoding="utf-8") + self.gcov = _fake_gcov(self.root / "gcov", GCOV_JSON) + (self.root / "uncovered.pic.gcno").write_bytes(b"gcno") + (self.root / "empty_unit.pic.gcno").write_bytes(b"gcno") + (self.root / "gcno.txt").write_text( + "src/_objs/uncovered/uncovered.pic.gcno\nsrc/_objs/uncovered/empty_unit.pic.gcno\n", encoding="utf-8" + ) + (self.ws / "src" / "empty_unit.cpp").write_text('#include "u.h"\n', encoding="utf-8") + (self.root / "allow.txt").write_text( + "src/a.cpp\nsrc/uncovered.cpp\nsrc/uncovered.h\nsrc/v/api.h\nrust/lib.rs\nsrc/empty_unit.cpp\n", + encoding="utf-8", + ) + (self.root / "map.txt").write_text("src/_virtual_includes/v/api.h\tsrc/v/api.h\n", encoding="utf-8") + self.runfiles = _FakeRunfiles( + { + "m/gcov": str(self.gcov), + "m/gcno.txt": str(self.root / "gcno.txt"), + "_main/src/_objs/uncovered/uncovered.pic.gcno": str(self.root / "uncovered.pic.gcno"), + "_main/src/_objs/uncovered/empty_unit.pic.gcno": str(self.root / "empty_unit.pic.gcno"), + "m/allow.txt": str(self.root / "allow.txt"), + "m/map.txt": str(self.root / "map.txt"), + } + ) + self.output = self.root / "out.zip" + self.workdir = self.root / "work" + self.workdir.mkdir() + self.cwd = os.getcwd() + os.chdir(self.workdir) + self.patch = mock.patch.object(gcov_reporter.Runfiles, "Create", return_value=self.runfiles) + self.patch.start() + + def tearDown(self): + self.patch.stop() + os.chdir(self.cwd) + self.tmp.cleanup() + + def _argv(self, **extra): + argv = [ + "--output_file", + str(self.output), + "--reports_file", + str(self.root / "reports.txt"), + "--workspace_root", + str(self.ws), + "--gcov", + "m/gcov", + "--gcno_manifest", + "m/gcno.txt", + "--coverage_allowlist", + "m/allow.txt", + "--path_map", + "m/map.txt", + ] + for k, v in extra.items(): + argv += [f"--{k}", str(v)] + return argv + + def test_full_report(self): + err = io.StringIO() + with redirect_stderr(err): + gcov_reporter.main(self._argv()) + with zipfile.ZipFile(self.output) as zf: + names = set(zf.namelist()) + lcov = zf.read("lcov_report/lcov.dat").decode() + unmapped = zf.read("text_report/unmapped_files.txt").decode() + summary = zf.read("text_report/summary.txt").decode() + self.assertIn("html_report/index.html", names) + self.assertTrue(any(n.startswith("html_report/index.a.cpp.") for n in names), names) + # two tests summed for a.cpp; the virtual-include header under its declared path; + # the untested TU from the gcno baseline at 0; gtest excluded + self.assertIn("SF:src/a.cpp\n", lcov) + self.assertIn("DA:3,3\n", lcov) + # api.h recorded under its virtual path by one test (3) and under its + # declared path by another (4): one record, counts summed + self.assertIn("SF:src/v/api.h\nDA:5,7\n", lcov) + self.assertEqual(lcov.count("SF:src/v/api.h\n"), 1) + self.assertIn("SF:src/uncovered.cpp\n", lcov) + self.assertIn("DA:16,0\n", lcov) + self.assertIn("FNDA:0,_Z1fv\n", lcov) + self.assertNotIn("gtest", lcov) + self.assertNotIn("_virtual_includes", lcov) + # uncovered.h: declaration-only (its .cpp has data); lib.rs: not instrumentable + self.assertIn("declaration-only\tsrc/uncovered.h\n", unmapped) + self.assertIn("not-instrumented\trust/lib.rs\n", unmapped) + self.assertIn("compiled-without-code\tsrc/empty_unit.cpp\n", unmapped) + self.assertNotIn("no-data", unmapped) + self.assertIn("TOTAL", summary) + self.assertIn("3 per-test reports cover", err.getvalue()) + self.assertIn("1 allowlisted files only in baseline", err.getvalue()) + # the staged sources sit under the raw layout so gcovr found them + self.assertTrue((self.workdir / "sources" / "src" / "a.cpp").exists()) + + def test_no_reports_writes_empty_zip(self): + (self.root / "reports.txt").write_text(f"{self.root}/baseline_coverage.dat\n", encoding="utf-8") + with redirect_stderr(io.StringIO()): + gcov_reporter.main(self._argv()) + with zipfile.ZipFile(self.output) as zf: + self.assertEqual(zf.namelist(), []) + + def test_missing_gcov_is_an_error(self): + with redirect_stderr(io.StringIO()), self.assertRaises(SystemExit) as ctx: + gcov_reporter.main(self._argv(gcov="m/nope")) + self.assertEqual(ctx.exception.code, 1) + + def test_empty_allowlist_is_an_error(self): + (self.root / "allow.txt").write_text("# nothing\n", encoding="utf-8") + with redirect_stderr(io.StringIO()), self.assertRaises(SystemExit): + gcov_reporter.main(self._argv()) + + +if __name__ == "__main__": + unittest.main() diff --git a/score_coverage/tests/starlark/coverage_scope_tests.bzl b/score_coverage/tests/starlark/coverage_scope_tests.bzl index 96970f7..f2e7ee2 100644 --- a/score_coverage/tests/starlark/coverage_scope_tests.bzl +++ b/score_coverage/tests/starlark/coverage_scope_tests.bzl @@ -241,6 +241,24 @@ def _test_output_groups_impl(env, target): _PKG + "/" + target.label.name + "_objects.txt", ]) +# --- gcov backend outputs ---------------------------------------------------- + +def _test_gcno_manifest_and_output_groups(name): + coverage_scope(name = name + "_subject", testonly = True, deps = [_FIX + ":mid"]) + analysis_test(name = name, impl = _test_gcno_manifest_and_output_groups_impl, target = name + "_subject") + +def _test_gcno_manifest_and_output_groups_impl(env, target): + # The manifest is always written; its content lists the .gcno notes of the + # in-scope translation units when the resolved C++ toolchain emits them + # (GCC / QCC) and is empty under an LLVM covmap toolchain. + subject = env.expect.that_target(target) + subject.output_group("gcno").contains_exactly([_PKG + "/" + target.label.name + "_gcno.txt"]) + manifest = env.expect.that_target(target).action_generating( + _PKG + "/" + target.label.name + "_gcno.txt", + ).content() + manifest.not_equals("external/") + subject.default_outputs().contains_at_least([_PKG + "/" + target.label.name + "_gcno.txt"]) + def coverage_scope_test_suite(name): test_suite( name = name, @@ -258,5 +276,6 @@ def coverage_scope_test_suite(name): _test_rust_library_sources_and_archive, _test_rust_binary_collects_crate_sources_and_executable, _test_output_groups, + _test_gcno_manifest_and_output_groups, ], ) diff --git a/tools/self_coverage_gate.py b/tools/self_coverage_gate.py index 7c5a192..8970603 100644 --- a/tools/self_coverage_gate.py +++ b/tools/self_coverage_gate.py @@ -95,30 +95,73 @@ def in_scope(path: str) -> bool: return path.startswith(SCOPE_PREFIX) and not path.startswith(EXCLUDED_PREFIX) +def _record_line( + current: FileCoverage, + line: str, + lines: dict[str, dict[int, int]], + branches: dict[str, dict[tuple[int, str], bool]], +) -> bool: + """Fold one LCOV line into the per-file detail maps; True when it was a DA or BRDA line.""" + key, _, value = line.partition(":") + if key == "DA": + number, _, hits = value.partition(",") + file_lines = lines.setdefault(current.path, {}) + file_lines[int(number)] = max(file_lines.get(int(number), 0), int(hits.split(",")[0])) + return True + if key == "BRDA": + number, block, _, taken = value.split(",", 3) + file_branches = branches.setdefault(current.path, {}) + identity = (int(number), block) + file_branches[identity] = file_branches.get(identity, False) or (taken not in ("-", "0")) + return True + if key in ("LF", "LH", "BRF", "BRH"): + number = int(value) + if number < 0: + raise ValueError(f"negative {key} in record for {current.path}") + attr = {"LF": "lines_found", "LH": "lines_hit", "BRF": "branches_found", "BRH": "branches_hit"}[key] + setattr(current, attr, getattr(current, attr) + number) + return False + + def parse_lcov(path: Path) -> Totals: - """Aggregate LF/LH/BRF/BRH per in-scope source file (records may repeat per test).""" + """Aggregate line and branch coverage per in-scope source file. + + Lines are keyed by line number and branches by ``(line, block)``, each + counted once and hit when any record hits it. coverage.py numbers the + third BRDA field differently from run to run (it encodes whether the arc + was taken), so when two test targets import the same module Bazel's LCOV + merger lists the same arc twice and the record's own ``BRF`` overstates + the branch count. Records without DA/BRDA lines fall back to their + LF/LH/BRF/BRH summary, summed per file. + """ if not path.is_file(): raise FileNotFoundError(f"LCOV file not found: {path}") per_file: dict[str, FileCoverage] = {} + lines: dict[str, dict[int, int]] = {} + branches: dict[str, dict[tuple[int, str], bool]] = {} current: FileCoverage | None = None + saw_detail = False with open(path, encoding="utf-8") as f: for raw in f: line = raw.rstrip("\n") if line.startswith("SF:"): current = FileCoverage(path=line[3:]) + saw_detail = False elif line == "end_of_record": - if current is not None and in_scope(current.path): + if current is not None and in_scope(current.path) and not saw_detail: per_file.setdefault(current.path, FileCoverage(path=current.path)).add(current) + elif current is not None and in_scope(current.path): + per_file.setdefault(current.path, FileCoverage(path=current.path)) current = None elif current is not None: - key, _, value = line.partition(":") - if key in ("LF", "LH", "BRF", "BRH"): - number = int(value) - if number < 0: - raise ValueError(f"negative {key} in record for {current.path}") - attr = {"LF": "lines_found", "LH": "lines_hit", "BRF": "branches_found", "BRH": "branches_hit"}[key] - setattr(current, attr, getattr(current, attr) + number) - for fc in per_file.values(): + saw_detail = _record_line(current, line, lines, branches) or saw_detail + for name, fc in per_file.items(): + if name in lines: + fc.lines_found += len(lines[name]) + fc.lines_hit += sum(1 for hits in lines[name].values() if hits > 0) + if name in branches: + fc.branches_found += len(branches[name]) + fc.branches_hit += sum(1 for hit in branches[name].values() if hit) if fc.lines_hit > fc.lines_found or fc.branches_hit > fc.branches_found: raise ValueError(f"corrupt LCOV record for {fc.path}: hit count exceeds found count") return Totals(files=sorted(per_file.values(), key=lambda fc: fc.path)) diff --git a/tools/self_coverage_gate_test.py b/tools/self_coverage_gate_test.py index 5e5d4f8..b41fb2f 100644 --- a/tools/self_coverage_gate_test.py +++ b/tools/self_coverage_gate_test.py @@ -33,6 +33,16 @@ "SF:score_coverage/b.py\nLF:10\nLH:0\nBRF:2\nBRH:0\nend_of_record\n" ) +# coverage.py detail records of the same file from two test targets: the +# third BRDA field differs between runs for the same arc (line 7, block 131), +# and Bazel's merger keeps both; the gate must count that arc once, as hit. +LCOV_DETAIL = ( + "SF:score_coverage/c.py\nDA:1,3\nDA:2,0\nDA:3,1\n" + "BRDA:7,131,1,1\nBRDA:7,132,1,1\nBRDA:9,140,0,-\nLF:3\nLH:2\nBRF:3\nBRH:2\nend_of_record\n" + "SF:score_coverage/c.py\nDA:1,0\nDA:2,2\nDA:3,0\n" + "BRDA:7,131,0,-\nBRDA:7,132,1,1\nBRDA:9,140,0,-\nLF:3\nLH:1\nBRF:3\nBRH:1\nend_of_record\n" +) + class ParseLcovTest(unittest.TestCase): def setUp(self): @@ -51,6 +61,13 @@ def test_scope_and_aggregation(self): self.assertEqual((totals.lines_found, totals.lines_hit), (30, 17)) self.assertEqual((totals.branches_found, totals.branches_hit), (10, 5)) + def test_detail_records_dedupe_lines_and_branches(self): + self.lcov.write_text(LCOV_DETAIL, encoding="utf-8") + totals = gate.parse_lcov(self.lcov) + c = totals.files[0] + # 3 lines, all hit in some run; 3 distinct (line, block) arcs, 2 taken in some run + self.assertEqual((c.lines_found, c.lines_hit, c.branches_found, c.branches_hit), (3, 3, 3, 2)) + def test_missing_file(self): with self.assertRaises(FileNotFoundError): gate.parse_lcov(self.lcov.parent / "missing")