From bcd4b3ec193858889470e33df66afa21c5d6f2b9 Mon Sep 17 00:00:00 2001 From: Dan Calavrezo <195309321+dcalavrezo-qorix@users.noreply.github.com> Date: Mon, 28 Sep 2026 09:48:35 +0300 Subject: [PATCH 1/5] Add the gcov backend for QNX on-target coverage QCC is GCC-based and cannot emit LLVM coverage mapping, and QNX tests run inside QEMU. This adds a second backend that changes the collection, not the report (tooling issue #427, track 1): - score_qnx_unit_tests' run_under_qnx runner brings the .gcda counters back from the guest; Bazel's own per-test collector (gcov + lcov_merger) turns them into LCOV; the new gcov_reporter.py is the --coverage_report_generator: it sums per-test records per file, applies the scope through the selection shared with the LLVM reporter, adds a zero-coverage baseline by running gcov over the .gcno notes of every in-scope translation unit, renders HTML and text summary with gcovr 8.6 (new pip dependency), and writes the same zip layout, so phase 2 (justifications, summary, gate, archive) is unchanged. - score_coverage_scope collects the .gcno files through InstrumentedFilesInfo (_gcno.txt, gcno/gcno_files output groups). score_coverage_reporter gained backend = "llvm"|"gcov" and gcov = ; the LLVM tools are required only for the LLVM backend. Existing consumers need no change. - Rust sources are reported as not-instrumented on this backend (new unmapped category); headers vendored from external repositories have no data because Bazel's collector filters them out (documented). Two defects found on the way, both fixed: - effective_coverage's gcovr parser matched across table rows; real gcovr pages have bare rows for non-instrumented lines, so every line status was attributed to the previous instrumented line and no justification ever applied to a gcovr report. - the self-coverage gate double counted branches when two test targets import the same module: coverage.py's BRDA branch field encodes whether the arc was taken, Bazel's merger keeps both variants. Arcs are now keyed by (line, block). Validation: the integration workspace gained the S-CORE GCC 12.2.0 toolchain, a coverage:gcov config, a hand-derived expected_lcov_gcov.dat and gcov checks (gate, ground truth, every gcovr link opens, justification applied, categories); 20 new unit tests; one new analysis test. GCC on Linux takes exactly the collection path QCC takes on QNX; the QEMU transport is validated on a consumer. Docs: architecture (backend section and diagram), tool requirements (scope_gcno, backend_select, gcov_merge, gcov_baseline, gcov_html), potential errors ERR-11/ERR-12, constraint CSTR-11, user manual step 4b, known problems, release notes 0.3.0, verification report. Signed-off-by: Dan Calavrezo <195309321+dcalavrezo-qorix@users.noreply.github.com> --- BUILD | 5 + README.md | 3 +- defs.bzl | 35 +- docs/architecture/index.rst | 86 ++- docs/manual/constraints.rst | 23 +- docs/manual/known_problems.rst | 26 +- docs/manual/user_manual.rst | 58 +- docs/release/release_notes.rst | 51 +- docs/requirements/potential_errors.rst | 22 +- docs/requirements/tool_requirements.rst | 87 ++- docs/verification/verification_report.rst | 26 +- integration_tests/.bazelrc | 21 + integration_tests/MODULE.bazel | 16 + integration_tests/MODULE.bazel.lock | 562 +++++++++++++++++- integration_tests/expected_lcov_gcov.dat | 76 +++ integration_tests/run_integration_test.sh | 58 ++ integration_tests/tools/coverage/BUILD | 10 + score_coverage/BUILD | 21 + score_coverage/coverage_scope.bzl | 42 +- score_coverage/coverage_summary.py | 6 + score_coverage/effective_coverage.py | 7 +- score_coverage/gcov_reporter.py | 497 ++++++++++++++++ score_coverage/generate_coverage_html.py | 5 +- score_coverage/reporter_wrapper.bzl | 117 ++-- score_coverage/requirements.in | 1 + score_coverage/requirements_3_12.txt | 289 ++++++++- score_coverage/tests/BUILD | 9 + .../tests/effective_coverage_test.py | 25 + score_coverage/tests/gcov_reporter_test.py | 439 ++++++++++++++ .../tests/starlark/coverage_scope_tests.bzl | 19 + tools/self_coverage_gate.py | 63 +- tools/self_coverage_gate_test.py | 17 + 32 files changed, 2621 insertions(+), 101 deletions(-) create mode 100644 integration_tests/expected_lcov_gcov.dat create mode 100644 score_coverage/gcov_reporter.py create mode 100644 score_coverage/tests/gcov_reporter_test.py 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..24032c8 100644 --- a/docs/manual/user_manual.rst +++ b/docs/manual/user_manual.rst @@ -176,6 +176,61 @@ 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", + ) + +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 --@rules_rust//rust/settings:extra_rustc_flags= + coverage:qnx --coverage_output_generator=@bazel_tools//tools/test:lcov_merger + coverage:qnx --coverage_report_generator=//tools/coverage:gcov_reporter_wrapper + +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 +314,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..5f277cb 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`` + - 20 + - 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..eebcff8 --- /dev/null +++ b/score_coverage/gcov_reporter.py @@ -0,0 +1,497 @@ +#!/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) + + canonical = canonical_names(set(tested) | set(baseline), path_map, allowlist) + test_covered = {raw: canonical[raw] for raw in tested} + baseline_covered = {raw: canonical[raw] for raw 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 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..a8c6a16 --- /dev/null +++ b/score_coverage/tests/gcov_reporter_test.py @@ -0,0 +1,439 @@ +# ******************************************************************************* +# 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_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 / "reports.txt").write_text( + f"{self.root}/t0.dat\n{self.root}/t1.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) + self.assertIn("SF:src/v/api.h\nDA:5,3\n", lcov) + 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("2 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") From 29351a55e9d6fc8439b7af91f87fae923a1bf87b Mon Sep 17 00:00:00 2001 From: Dan Calavrezo <195309321+dcalavrezo-qorix@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:12:34 +0300 Subject: [PATCH 2/5] Manual: tag the gcov reporter target manual Found on communication: the QNX gcov reporter depends on the QNX SDP package, and bazel build //... on a Linux host without credentials fails on that download. The manual tag keeps wildcards away from the target; --coverage_report_generator still names it explicitly. Signed-off-by: Dan Calavrezo <195309321+dcalavrezo-qorix@users.noreply.github.com> --- docs/manual/user_manual.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/manual/user_manual.rst b/docs/manual/user_manual.rst index 24032c8..2e6f4c0 100644 --- a/docs/manual/user_manual.rst +++ b/docs/manual/user_manual.rst @@ -198,8 +198,14 @@ the toolchain extension): 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``): From 55bf112500df786efe0f6ee724cb775f98d5cee4 Mon Sep 17 00:00:00 2001 From: Dan Calavrezo <195309321+dcalavrezo-qorix@users.noreply.github.com> Date: Mon, 28 Sep 2026 13:00:31 +0300 Subject: [PATCH 3/5] Sum gcov records of one file recorded under several paths The gcov reporter dropped every raw path variant of a canonical file except one before merging. When one test reports a header by its declared path and another test through a _virtual_includes path, the second test's counts were lost. Merge test and baseline records by canonical name before the selection so all variants are summed. Reported by the Copilot review on PR #10. Signed-off-by: Dan Calavrezo <195309321+dcalavrezo-qorix@users.noreply.github.com> --- score_coverage/gcov_reporter.py | 19 +++++++++++-- score_coverage/tests/gcov_reporter_test.py | 31 +++++++++++++++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/score_coverage/gcov_reporter.py b/score_coverage/gcov_reporter.py index eebcff8..133dd35 100644 --- a/score_coverage/gcov_reporter.py +++ b/score_coverage/gcov_reporter.py @@ -143,9 +143,16 @@ def main(argv: list[str] | None = None) -> None: 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) - test_covered = {raw: canonical[raw] for raw in tested} - baseline_covered = {raw: canonical[raw] for raw in baseline} + 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) @@ -280,6 +287,14 @@ def canonical_names(raws: set[str], path_map: dict[str, str], allowlist: set[str 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)} diff --git a/score_coverage/tests/gcov_reporter_test.py b/score_coverage/tests/gcov_reporter_test.py index a8c6a16..76a6bf4 100644 --- a/score_coverage/tests/gcov_reporter_test.py +++ b/score_coverage/tests/gcov_reporter_test.py @@ -179,6 +179,26 @@ def test_canonical_names_use_path_map_and_foreign_trees(self): 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"}) @@ -325,8 +345,10 @@ def setUp(self): + "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}/baseline_coverage.dat\n", encoding="utf-8" + 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) @@ -400,7 +422,10 @@ def test_full_report(self): # 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) - self.assertIn("SF:src/v/api.h\nDA:5,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) @@ -412,7 +437,7 @@ def test_full_report(self): self.assertIn("compiled-without-code\tsrc/empty_unit.cpp\n", unmapped) self.assertNotIn("no-data", unmapped) self.assertIn("TOTAL", summary) - self.assertIn("2 per-test reports cover", err.getvalue()) + 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()) From 95a5d8d8eeb2311111a52d25b980a4604050ccc1 Mon Sep 17 00:00:00 2001 From: Dan Calavrezo <195309321+dcalavrezo-qorix@users.noreply.github.com> Date: Mon, 28 Sep 2026 13:02:59 +0300 Subject: [PATCH 4/5] Explain when the QNX config must reset the rules_rust flags Only the list-typed extra_rustc_flags setting can be cleared with an empty value; the repeatable extra_rustc_flag accumulates. The QNX block no longer resets a flag that a workspace following Step 4 never sets, and the manual says what to do when the LLVM rustc flags sit on the bare coverage command. Count the new gcov reporter test. Signed-off-by: Dan Calavrezo <195309321+dcalavrezo-qorix@users.noreply.github.com> --- docs/manual/user_manual.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/manual/user_manual.rst b/docs/manual/user_manual.rst index 2e6f4c0..a77bb91 100644 --- a/docs/manual/user_manual.rst +++ b/docs/manual/user_manual.rst @@ -219,10 +219,18 @@ collector and points the final step at that reporter (copy and adapt the 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 --@rules_rust//rust/settings:extra_rustc_flags= 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 From 265da7c2dbbf35b4ea5bf68439722827fa13b55d Mon Sep 17 00:00:00 2001 From: Dan Calavrezo <195309321+dcalavrezo-qorix@users.noreply.github.com> Date: Mon, 28 Sep 2026 13:03:22 +0300 Subject: [PATCH 5/5] Count the new gcov reporter test in the verification report Signed-off-by: Dan Calavrezo <195309321+dcalavrezo-qorix@users.noreply.github.com> --- docs/verification/verification_report.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/verification/verification_report.rst b/docs/verification/verification_report.rst index 5f277cb..27de616 100644 --- a/docs/verification/verification_report.rst +++ b/docs/verification/verification_report.rst @@ -58,7 +58,7 @@ Test inventory report_rlib_expansion, report_missing_baseline, report_relative_paths, report_outputs, report_unmapped, scope_transitive * - ``//score_coverage/tests:gcov_reporter_test`` - - 20 + - 21 - gcov_merge, gcov_baseline, gcov_html, report_relative_paths, report_baseline_zero, report_allowlist, report_unmapped, report_outputs * - ``//score_coverage/tests:justify_test``