Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .bazelignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Nested consumer workspace with its own MODULE.bazel; built by
# integration_tests/run_integration_test.sh, not as part of //...
integration_tests
# Local module used as an external-repository fixture by the analysis tests.
score_coverage/tests/starlark/external_fixture
7 changes: 7 additions & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,10 @@ cc_feature(
args = [":runtime_relocation_args"],
feature_name = "enable_llvm_coverage_for_death_tests",
)

# Entry point expected by the cicd-workflows license check. An alias only: the
# implementation (dev dependency) lives in //tools.
alias(
name = "license-check",
actual = "//tools:license-check",
)
9 changes: 9 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ bazel_dep(name = "buildifier_prebuilt", version = "8.5.1", dev_dependency = True
# Starlark analysis tests for the coverage_scope rule/aspect, incl. Rust fixtures
# built with the standard Ferrocene toolchain (registered in .bazelrc).
bazel_dep(name = "rules_testing", version = "0.9.0", dev_dependency = True)

# External-repository header fixture for the analysis tests (a workspace target
# vendoring a header from another module, eclipse-score/baselibs#558).
bazel_dep(name = "coverage_external_fixture", version = "0.0.0", dev_dependency = True)
local_path_override(
module_name = "coverage_external_fixture",
path = "score_coverage/tests/starlark/external_fixture",
)

bazel_dep(name = "score_toolchains_rust", version = "0.10.0", dev_dependency = True)

###############################################################################
Expand Down
6 changes: 6 additions & 0 deletions docs/manual/known_problems.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ stay listed with their upstream references.
- ``no coverage data found`` on a Rust archive.
- Handled since the pipeline expands rlibs into their object members; if
seen, the installed version predates the fix.
* - **Vendored headers appear under their virtual-includes path.** A header
compiled through ``strip_include_prefix`` is reported as
``<pkg>/_virtual_includes/<target>/<path>``, not under the label it was
declared with, because that is the identity the compiler records.
- Report rows named ``_virtual_includes``.
- Expected; justifications for such lines must use the reported path.
* - **Instrumentation filter appears ignored.**
- ``--instrumentation_filter`` has no visible effect.
- Expected: ``--experimental_use_llvm_covmap`` instruments everything;
Expand Down
6 changes: 6 additions & 0 deletions docs/release/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,11 @@ Changes relative to the pipeline in score_tooling 2.2.x:
``--lcov``; ``summary.txt`` is written for gcovr reports too.
- The repository-bound ``combined_report`` and ``llvm_profile_wrapper`` helpers
are not part of the module.
- Headers reached through ``strip_include_prefix`` / ``include_prefix`` (Bazel's
``_virtual_includes/`` tree) and headers a workspace target vendors from an
external repository are now part of the coverage scope; the reporter
normalises the configuration-specific ``bazel-out/<config>/bin/`` prefix and
suppresses the duplicate baseline entry of such headers
(eclipse-score/baselibs#558).

Known problems: see :doc:`../manual/known_problems`.
19 changes: 15 additions & 4 deletions docs/requirements/tool_requirements.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ Scope
and ``rust_library`` targets (``srcs``, ``hdrs``) and the ``CrateInfo``
sources of ``rust_binary`` targets, and shall write them sorted and
deduplicated, one workspace-relative path per line, to the allowlist file.
For workspace targets it shall additionally list the post-processing
identity of the public headers: the generated ``_virtual_includes/`` path a
header gets through ``strip_include_prefix`` or ``include_prefix`` (the path
the coverage mapping records), and headers a workspace target vendors from
an external repository.
Comment on lines +45 to +49

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, reworded: tool_req__coverage_scope_excludes now says external targets are not traversed and generated files are not listed, with the two exceptions named in tool_req__coverage_scope_transitive (headers a workspace target declares from an external repository, and the _virtual_includes/ identities of its public headers).


.. tool_req:: External and generated sources are excluded from the scope
:id: tool_req__coverage_scope_excludes
Expand All @@ -51,8 +56,11 @@ Scope
:safety: QM
:satisfies: stkh_req__coverage__uc_scope_completeness

``score_coverage_scope`` shall not list files from external repositories or
generated files in the allowlist.
``score_coverage_scope`` shall not traverse external targets and shall not
list their files, and shall not list generated files, with one exception
each: headers a workspace target declares from an external repository, and
the ``_virtual_includes/`` identities of a workspace target's public headers
(see :need:`tool_req__coverage_scope_transitive`).

.. tool_req:: Baseline objects accompany the scope
:id: tool_req__coverage_scope_baseline_objects
Expand Down Expand Up @@ -186,8 +194,11 @@ Report

The reporter shall rewrite the absolute workspace root and the compiler's
``/proc/self/cwd/`` prefix in LCOV ``SF:`` records and in HTML page titles to
workspace-relative paths, so that the archived report is portable and file
identity does not depend on the machine.
workspace-relative paths, and shall drop the configuration-specific
``bazel-out/<config>/bin/`` prefix of generated headers, so that the archived
report is portable and file identity depends neither on the machine nor on
the build configuration. A generated header covered by a test binary shall
appear once, not additionally as a 0 % entry from the baseline archive.

.. tool_req:: Report contents
:id: tool_req__coverage_report_outputs
Expand Down
18 changes: 10 additions & 8 deletions docs/verification/verification_report.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ Test inventory
- 20
- merge_profraw, merge_no_data, merge_tool_error
* - ``//score_coverage/tests:reporter_test``
- 34
- report_merged_profile, report_allowlist, report_rlib_expansion,
report_missing_baseline, report_relative_paths, report_outputs
- 39
- report_merged_profile, report_allowlist, report_baseline_zero,
report_rlib_expansion, report_missing_baseline, report_relative_paths,
report_outputs, scope_transitive
* - ``//score_coverage/tests:justify_test``
- 41
- just_yaml, just_markers, just_unknown_id, just_platform,
Expand All @@ -66,8 +67,8 @@ Test inventory
* - ``//score_coverage/tests:coverage_summary_test``
- 17
- summary_first
* - ``//score_coverage/tests/starlark:coverage_scope_tests`` (8 analysis tests)
- 8
* - ``//score_coverage/tests/starlark:coverage_scope_tests`` (11 analysis tests)
- 11
- scope_transitive, scope_excludes, scope_baseline_objects
* - ``integration_tests/run_integration_test.sh`` (15 end-to-end checks)
- 15
Expand All @@ -94,7 +95,7 @@ generated link:

- :need:`tool_req__coverage_scope_transitive`,
:need:`tool_req__coverage_scope_excludes` and
:need:`tool_req__coverage_scope_baseline_objects` are verified by the eight
:need:`tool_req__coverage_scope_baseline_objects` are verified by the eleven
Starlark analysis tests in ``score_coverage/tests/starlark`` (rules_testing
produces no test properties).
- :need:`tool_req__coverage_validation_ground_truth` is verified by the
Expand Down Expand Up @@ -157,8 +158,9 @@ End-to-end validation
---------------------

``integration_tests/run_integration_test.sh`` builds a consumer workspace with a
tested and an untested C++ library, a tested Rust library and an untested Rust
binary, one justified line, and asserts:
tested and an untested C++ library, a header-only library reached through
``strip_include_prefix``, a tested Rust library and an untested Rust binary, one
justified line, and asserts:

1. the gate fails at 100 % and passes at 10 % (effective and raw mode);
2. the HTML, the summary and the archive tree are produced, the summary also
Expand Down
20 changes: 20 additions & 0 deletions integration_tests/expected_lcov.dat
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
# baseline; no branches.
# src/uncovered.cpp no test links against it => all lines 0, both directions
# of the branch on line 18 never executed ('-').
# src/_virtual_includes/vendored_math/vendored/inline_math.h
# header-only library behind strip_include_prefix, reported
# under the generated virtual-includes path the compiler
# records (baselibs#558). twice() (lines 18-20) is called once
# by coverable_test; never_inlined() (22-24) is never called
# and clang still emits its mapping => 3 of 6 lines, no
# branches. Exactly one record: the baseline variant of the
# same header is suppressed.
SF:rust/lib.rs
DA:16,2
DA:17,2
Expand Down Expand Up @@ -95,3 +103,15 @@ BRH:0
LF:6
LH:0
end_of_record
SF:src/_virtual_includes/vendored_math/vendored/inline_math.h
DA:18,1
DA:19,1
DA:20,1
DA:22,0
DA:23,0
DA:24,0
BRF:0
BRH:0
LF:6
LH:3
end_of_record
15 changes: 14 additions & 1 deletion integration_tests/src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,21 @@ cc_library(
hdrs = ["uncovered.h"],
)

# Header-only library whose header is reached through strip_include_prefix:
# the compiler sees it via Bazel's generated _virtual_includes/ tree, the same
# mechanism vendored third-party headers use (eclipse-score/baselibs#558).
# One inline function is called by the test, the other never.
cc_library(
name = "vendored_math",
hdrs = ["vendored/include/vendored/inline_math.h"],
strip_include_prefix = "vendored/include",
)

cc_test(
name = "coverable_test",
srcs = ["coverable_test.cpp"],
deps = [":coverable"],
deps = [
":coverable",
":vendored_math",
],
)
4 changes: 4 additions & 0 deletions integration_tests/src/coverable_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <cstring>

#include "src/coverable.h"
#include "vendored/inline_math.h"

// Deliberately exercises only the negative and zero branches; the positive
// branch stays uncovered (and justified via the COV_JUSTIFIED marker).
Expand All @@ -24,5 +25,8 @@ int main() {
if (std::strcmp(classify(0), "zero") != 0) {
return 1;
}
if (coverage_integration::twice(21) != 42) {
return 1;
}
return 0;
}
28 changes: 28 additions & 0 deletions integration_tests/src/vendored/include/vendored/inline_math.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/********************************************************************************
* 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
********************************************************************************/
#ifndef COVERAGE_INTEGRATION_TESTS_SRC_VENDORED_INLINE_MATH_H
#define COVERAGE_INTEGRATION_TESTS_SRC_VENDORED_INLINE_MATH_H

namespace coverage_integration {

inline int twice(int value) {
return value * 2;
}

inline int never_inlined(int value) {
return value - 1;
}

} // namespace coverage_integration

#endif // COVERAGE_INTEGRATION_TESTS_SRC_VENDORED_INLINE_MATH_H
1 change: 1 addition & 0 deletions integration_tests/tools/coverage/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ score_coverage_scope(
"//rust:untested_tool",
"//src:coverable",
"//src:uncovered",
"//src:vendored_math",
],
)

Expand Down
30 changes: 29 additions & 1 deletion score_coverage/coverage_scope.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ _CoverageScopeInfo = provider(
# Aspect: traverses library/binary deps to collect files
# =============================================================================

def _is_workspace_target(target):
"""True for targets of the main repository (not of an external module)."""
label = str(target.label)
return not label.startswith("@@") or label.startswith("@@//")

def _workspace_relative(f):
"""short_path in the form the reporter compares against covmap paths.

Main-repository files: "<pkg>/<file>". External files have a short_path
of "../<repo>/<file>"; the compiler records them as "external/<repo>/<file>".
"""
if f.short_path.startswith("../"):
return "external/" + f.short_path[3:]
return f.short_path

def _coverage_scope_aspect_impl(target, ctx):
"""Collects source file paths and archive files from the build graph."""
direct_files = []
Expand All @@ -58,15 +73,28 @@ def _coverage_scope_aspect_impl(target, ctx):
# At cc_library / rust_library targets (rust_library provides CcInfo with
# its rlib exposed as a .a symlink): collect srcs, hdrs, and static archive
if CcInfo in target:
in_workspace = _is_workspace_target(target)
for attr_name in ["srcs", "hdrs"]:
if hasattr(ctx.rule.attr, attr_name):
for src in getattr(ctx.rule.attr, attr_name):
for f in src.files.to_list():
if not f.path.startswith("external/") and f.is_source:
direct_files.append(f.short_path)

if in_workspace:
# Post-processing identity of the public headers. With
# strip_include_prefix / include_prefix, Bazel compiles against a
# generated _virtual_includes/ symlink tree and the coverage mapping
# records THAT path, never the declared header label. Headers a
# workspace target vendors from an external repository are part of
# that target and therefore in scope as well
# (eclipse-score/baselibs#558). Other generated headers stay out.
for f in target[CcInfo].compilation_context.direct_public_headers:
if "/_virtual_includes/" in f.short_path or f.is_source:
direct_files.append(_workspace_relative(f))

# Only collect workspace-internal labels and archives
if not str(target.label).startswith("@@") or str(target.label).startswith("@@//"):
if in_workspace:
# 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:
Expand Down
Loading
Loading