Skip to content
Open
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
4 changes: 4 additions & 0 deletions bazel/rules/rules_score/lobster/config/lobster_de.conf.tpl
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{FEAT_REQ_BLOCK}

{TEST_SPEC_BLOCK}

{TEST_CASE_BLOCK}

{RECEIVED_AOU_BLOCK}

{FORWARDED_AOU_BLOCK}
Expand Down
59 changes: 57 additions & 2 deletions bazel/rules/rules_score/private/dependable_element.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ load(
"SphinxModuleInfo",
"SphinxNeedsInfo",
"SphinxSourcesInfo",
"TestSpecificationsInfo",
"UnitInfo",
)
load(
Expand Down Expand Up @@ -1405,6 +1406,26 @@ def _dependable_element_index_impl(ctx):

feat_req_lobster_depset = depset(transitive = feat_req_lobster_files)

# Collect test specification .lobster files from test_specifications
# targets (TestSpecificationsInfo). These trace up to Feature Requirements
# via the TestSpec.verifies field.
test_spec_lobster_files = []
for req_target in ctx.attr.test_specifications:
if TestSpecificationsInfo in req_target:
test_spec_lobster_files.append(req_target[TestSpecificationsInfo].srcs)

test_spec_lobster_depset = depset(transitive = test_spec_lobster_files)

# Collect python test-case .lobster files from `tests` targets exposing an
# OutputGroupInfo "lobster" output group (py_lobster_trace()). These trace
# up to Test Specifications via the pytest verifies-mark.
py_test_lobster_files = []
for test_target in ctx.attr.tests:
if OutputGroupInfo in test_target and hasattr(test_target[OutputGroupInfo], "lobster"):
py_test_lobster_files.append(test_target[OutputGroupInfo].lobster)

py_test_lobster_depset = depset(transitive = py_test_lobster_files)

comp_req_lobster_files = []
comp_test_lobster_files = []
comp_arch_lobster_files = []
Expand Down Expand Up @@ -1505,6 +1526,8 @@ def _dependable_element_index_impl(ctx):

# Build the DE-level lobster report if feature and component traces exist
feat_req_list = feat_req_lobster_depset.to_list()
test_spec_list = test_spec_lobster_depset.to_list()
py_test_list = py_test_lobster_depset.to_list()

comp_req_list = comp_req_lobster_depset.to_list()
comp_test_list = comp_test_lobster_depset.to_list()
Expand Down Expand Up @@ -1579,6 +1602,8 @@ def _dependable_element_index_impl(ctx):
# reference" issues for levels a given dependable element does not use.
has_any_lobster_input = any([
feat_req_list,
test_spec_list,
py_test_list,
received_aou_list,
comp_req_list,
comp_test_list,
Expand Down Expand Up @@ -1637,6 +1662,22 @@ def _dependable_element_index_impl(ctx):
output = lobster_config,
substitutions = {
"{FEAT_REQ_BLOCK}": format_lobster_block("requirements", "Feature Requirements", feat_req_list),
# Test Specifications intentionally does not `trace to:` Feature
# Requirements: doing so would require every Feature Requirement
# to be covered by a test specification as soon as a single one
# exists. Each TestSpec.verifies reference is instead resolved
# individually against Feature Requirements via the normal
# per-item up-reference check.
"{TEST_SPEC_BLOCK}": format_lobster_block(
"requirements",
"Test Specifications",
test_spec_list,
),
"{TEST_CASE_BLOCK}": format_lobster_block(
"activity",
"Test Cases",
py_test_list,
),
# Target level: every item here must be covered, either by a
# Component Requirement (`derived_from`) or by being
# further chain-forwarded (Forwarded AoUs, below). Without the
Expand Down Expand Up @@ -1736,7 +1777,7 @@ def _dependable_element_index_impl(ctx):
},
)

all_lobster_inputs = feat_req_list + comp_req_list + comp_arch_list + comp_test_list + interface_req_list + fm_list + rc_list + safetymeasures_list + received_aou_list + forwarded_aou_markers_list + coverage_lobster_files
all_lobster_inputs = feat_req_list + test_spec_list + py_test_list + comp_req_list + comp_arch_list + comp_test_list + interface_req_list + fm_list + rc_list + safetymeasures_list + received_aou_list + forwarded_aou_markers_list + coverage_lobster_files
lobster_report_file = subrule_lobster_report(all_lobster_inputs, lobster_config)
lobster_files = [lobster_config, lobster_report_file]

Expand All @@ -1758,7 +1799,11 @@ def _dependable_element_index_impl(ctx):
if not source_root:
package = ctx.label.package
package_depth = len(package.split("/")) if package else 0
source_root = "/".join([".." for _ in range(package_depth + 2)]) + "/"
# +3: escape "traceability_report/", "html/" and "<name>_doc/"
# (the multi-page report nests item pages one level deeper than
# "<name>_doc/html/", inside "<name>_doc/html/traceability_report/").
# +1: escape the "bazel-bin" convenience symlink itself.
source_root = "/".join([".." for _ in range(package_depth + 4)]) + "/"
rst_args = ctx.actions.args()
rst_args.add(lobster_report_file.path)
rst_args.add_all(["--out-dir", lobster_rst_dir.path])
Expand Down Expand Up @@ -1852,6 +1897,11 @@ def _dependable_element_index_attrs():
providers = [[FeatureRequirementsInfo], [AssumedSystemRequirementsInfo]],
doc = "Feature or assumed system requirements targets.",
),
"test_specifications": attr.label_list(
default = [],
providers = [[TestSpecificationsInfo]],
doc = "Test specification targets tracing to feature requirements via TestSpec.verifies, used for LOBSTER traceability.",
),
"architectural_design": attr.label_list(
mandatory = True,
doc = "Architectural design targets or files.",
Expand Down Expand Up @@ -2096,6 +2146,7 @@ def dependable_element(
checklists = [],
glossary = [],
deps = [],
test_specifications = [],
aou_forwarding = None,
maturity = "release",
generate_html_report = False,
Expand Down Expand Up @@ -2136,6 +2187,9 @@ def dependable_element(
terminology and definitions for the dependable element.
deps: Optional list of other module targets this element depends on.
Cross-references will work automatically.
test_specifications: Optional list of labels to test_specifications
targets tracing to feature requirements via TestSpec.verifies,
used for LOBSTER traceability.
aou_forwarding: Optional label to a YAML file listing received AoU IDs
to further-forward to this element's own dependees. Only needed for
chain-forwarding received AoUs that this element cannot handle.
Expand Down Expand Up @@ -2193,6 +2247,7 @@ def dependable_element(
glossary = glossary,
tests = tests,
deps = deps,
test_specifications = test_specifications,
processed_deps = processed_deps,
aou_forwarding = aou_forwarding,
integrity_level = integrity_level,
Expand Down
10 changes: 8 additions & 2 deletions bazel/rules/rules_score/private/requirements.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public-facing macros.

load("@lobster//:lobster.bzl", "subrule_lobster_trlc")
load("@trlc//:trlc.bzl", "TrlcProviderInfo", "subrule_trlc_image_stage")
load("//bazel/rules/rules_score:providers.bzl", "AssumedSystemRequirementsInfo", "AssumptionsOfUseInfo", "ComponentRequirementsInfo", "FeatureRequirementsInfo", "SafetyAnalysisProviderInfo", "SphinxSourcesInfo")
load("//bazel/rules/rules_score:providers.bzl", "AssumedSystemRequirementsInfo", "AssumptionsOfUseInfo", "ComponentRequirementsInfo", "FeatureRequirementsInfo", "SafetyAnalysisProviderInfo", "SphinxSourcesInfo", "TestSpecificationsInfo")
load("//bazel/rules/rules_score/private:rst_to_trlc.bzl", "rst_to_trlc")

_DEFAULT_SPEC = Label("//bazel/rules/rules_score/trlc/config:score_requirements_model")
Expand All @@ -34,6 +34,7 @@ _REQ_KIND_TITLES = {
"component": "Component Requirements",
"assumed_system": "Assumed System Requirements",
"aou": "Assumptions of Use",
"test_spec": "Test Specifications",
}

# ============================================================================
Expand Down Expand Up @@ -117,6 +118,11 @@ def _requirements_impl(ctx):
aou_lobster = depset([lobster_file]),
name = ctx.label.name,
)
elif ctx.attr.req_kind == "test_spec":
req_provider = TestSpecificationsInfo(
srcs = depset([lobster_file]),
name = ctx.label.name,
)
else: # assumed_system
req_provider = AssumedSystemRequirementsInfo(
srcs = depset([lobster_file]),
Expand Down Expand Up @@ -173,7 +179,7 @@ _score_requirements_rule = rule(
doc = "Other requirement targets whose TRLC records are needed for cross-reference parsing.",
),
"req_kind": attr.string(
values = ["feature", "component", "assumed_system", "aou"],
values = ["feature", "component", "assumed_system", "aou", "test_spec"],
mandatory = True,
doc = "Kind of requirements; determines which domain provider is emitted.",
),
Expand Down
107 changes: 107 additions & 0 deletions bazel/rules/rules_score/private/test_specifications.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# *******************************************************************************
# 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
# *******************************************************************************

"""
Test Specification build rules for S-CORE projects.

Test specifications describe verification test cases (preconditions, test
steps, expected results) that trace to feature requirements via the
TestSpec.verifies field, enabling LOBSTER traceability from requirements
down to test design.
"""

load("@trlc//:trlc.bzl", "trlc_requirements_test")
load("//bazel/rules/rules_score/private:requirements.bzl", "score_requirements_rule")

# ============================================================================
# Public Macro
# ============================================================================

def test_specifications(
name,
srcs,
lobster_config,
deps = [],
spec = Label("//bazel/rules/rules_score/trlc/config:score_requirements_model"),
ref_package = "",
image_srcs = [],
**kwargs):
"""Define test specifications following S-CORE process guidelines.

Creates a target providing TestSpecificationsInfo, TrlcProviderInfo, and
SphinxSourcesInfo, plus a validation test target ``<name>_test``.

Because this target emits TrlcProviderInfo, it can reference
feature_requirements targets directly in its ``deps`` without any
intermediate trlc_requirements wrapper.

Args:
name: The name of the target.
srcs: List of .trlc source files containing TestSpec records as
defined in the requirements model in use (the base S-CORE
requirements model does not define TestSpec; it is provided by
extension models such as SweAsCodeReq).
lobster_config: Lobster extraction config label. There is no default
because the TestSpec record type is not part of the base S-CORE
requirements model (e.g. use
``@swe_as_code//tools/trlc/config:lobster_test_specification``).
deps: Optional list of requirement targets (e.g. feature_requirements)
whose TRLC records are needed for cross-reference parsing since
TestSpec.verifies references FeatReqId. These targets must
provide TrlcProviderInfo.
spec: Optional TRLC specification target providing RSL type definitions.
Defaults to the S-CORE requirements model
(``@score_tooling//bazel/rules/rules_score/trlc/config:score_requirements_model``).
Override this when using a custom requirements model that defines TestSpec.
ref_package: TRLC package prefix used for verifies cross-references
when converting RST sources.
image_srcs: Image and diagram files (.svg, .png, or .puml) to stage
next to the rendered RST.
visibility: Bazel visibility specification for the generated targets.

Generated Targets:
<name>: Main target providing TestSpecificationsInfo, TrlcProviderInfo,
and SphinxSourcesInfo.
<name>_test: TRLC validation test (runs ``trlc --verify``).

Example:
```starlark
feature_requirements(
name = "feat_req",
srcs = ["feature_requirements.trlc"],
)

test_specifications(
name = "test_spec",
srcs = ["test_specifications.trlc"],
deps = [":feat_req"],
lobster_config = "@swe_as_code//tools/trlc/config:lobster_test_specification",
)
```
"""
score_requirements_rule(
name = name,
srcs = srcs,
deps = deps,
req_kind = "test_spec",
lobster_config = lobster_config,
spec = spec,
ref_package = ref_package,
image_srcs = image_srcs,
**kwargs
)
trlc_requirements_test(
name = name + "_test",
reqs = [":" + name],
**kwargs
)
8 changes: 8 additions & 0 deletions bazel/rules/rules_score/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ ComponentRequirementsInfo = provider(
},
)

TestSpecificationsInfo = provider(
doc = "Provider for test specification artifacts.",
fields = {
"srcs": "Depset of .lobster traceability files generated from TRLC test specification sources.",
"name": "Name of the test specifications target.",
},
)

AssumedSystemRequirementsInfo = provider(
doc = "Provider for assumed system requirements artifacts.",
fields = {
Expand Down
5 changes: 5 additions & 0 deletions bazel/rules/rules_score/rules_score.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ load(
"//bazel/rules/rules_score/private:sphinx_module.bzl",
_sphinx_module = "sphinx_module",
)
load(
"//bazel/rules/rules_score/private:test_specifications.bzl",
_test_specifications = "test_specifications",
)
load(
"//bazel/rules/rules_score/private:unit.bzl",
_unit = "unit",
Expand All @@ -84,6 +88,7 @@ glossary = _glossary
filter_execpath = _filter_execpath
safety_analysis = _safety_analysis
sphinx_module = _sphinx_module
test_specifications = _test_specifications
unit = _unit
unit_design = _unit_design
component = _component
Expand Down
Loading