Skip to content

Commit c066ec5

Browse files
committed
Require and normalize valid_from in validity consistency check
1 parent 70d5387 commit c066ec5

3 files changed

Lines changed: 65 additions & 5 deletions

File tree

src/extensions/score_metamodel/checks/check_options.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,16 @@ def parse_milestone(value: str) -> tuple[int, int, int]:
278278
return (major, minor, patch)
279279

280280

281+
def _normalize_validity_attr(value: object | None) -> str | None:
282+
"""Normalize validity attributes and treat whitespace-only values as missing."""
283+
if value is None:
284+
return None
285+
if isinstance(value, str):
286+
normalized = value.strip()
287+
return normalized if normalized else None
288+
return str(value)
289+
290+
281291
# req-Id: tool_req__docs_req_attr_validity_consistency
282292
@local_check
283293
def check_validity_consistency(
@@ -286,17 +296,24 @@ def check_validity_consistency(
286296
log: CheckLogger,
287297
):
288298
"""
289-
Check if the attributes valid_from < valid_until.
299+
Check if the attributes valid_from and valid_until are set and valid_from < valid_until.
290300
"""
291301
if need["type"] not in ("stkh_req", "feat_req"):
292302
return
293303

294-
valid_from = need.get("valid_from", None)
295-
valid_until = need.get("valid_until", None)
304+
valid_from = _normalize_validity_attr(need.get("valid_from", None))
305+
valid_until = _normalize_validity_attr(need.get("valid_until", None))
306+
307+
# Temporary grace period: only enforce presence of valid_from as non-fatal info.
308+
if not valid_from:
309+
msg = "missing validity attribute: valid_from must be set."
310+
log.warning_for_need(need, msg, is_new_check=True)
311+
return
296312

297-
if not valid_from or not valid_until:
313+
if not valid_until:
298314
return
299315

316+
# Both attributes are present, check consistency
300317
valid_from_version = parse_milestone(valid_from)
301318
valid_until_version = parse_milestone(valid_until)
302319
if valid_from_version >= valid_until_version:

src/extensions/score_metamodel/metamodel.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,14 +263,16 @@ needs_types:
263263
rationale: ^.+$
264264
# req-Id: tool_req__docs_common_attr_security
265265
security: ^(YES|NO)$
266+
# req-Id: tool_req__docs_req_attr_validity_correctness
267+
valid_from: ^v(0|[1-9]\d*)\.(0|[1-9]\d*)(\.(0|[1-9]\d*))?$
266268
optional_options:
267269
# req-Id: tool_req__docs_req_attr_reqcov
268270
reqcovered: ^(YES|NO)$
269271
# req-Id: tool_req__docs_req_attr_testcov
270272
testcovered: ^(YES|NO)$
271273
hash: ^.*$
272274
# req-Id: tool_req__docs_req_attr_validity_correctness
273-
valid_from: ^v(0|[1-9]\d*)\.(0|[1-9]\d*)(\.(0|[1-9]\d*))?$
275+
# valid_from: ^v(0|[1-9]\d*)\.(0|[1-9]\d*)(\.(0|[1-9]\d*))?$
274276
valid_until: ^v(0|[1-9]\d*)\.(0|[1-9]\d*)(\.(0|[1-9]\d*))?$
275277
tags:
276278
- requirement

src/extensions/score_metamodel/tests/test_check_options.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from score_metamodel.checks.check_options import (
2121
check_extra_options,
2222
check_options,
23+
check_validity_consistency,
2324
parse_milestone,
2425
)
2526
from score_metamodel.tests import fake_check_logger, need
@@ -128,3 +129,43 @@ def test_milestone_parsing():
128129
assert parse_milestone("v0.5") == (0, 5, 0)
129130
assert parse_milestone("v1.0") == (1, 0, 0)
130131
assert parse_milestone("v1.0.1") == (1, 0, 1)
132+
133+
134+
def test_validity_consistency_infos_for_empty_valid_from() -> None:
135+
test_need = need(
136+
id="feat_req__id_1",
137+
type="feat_req",
138+
valid_from="",
139+
valid_until="v1.0",
140+
)
141+
logger = fake_check_logger()
142+
app = Mock(spec=Sphinx)
143+
144+
check_validity_consistency(app, test_need, cast(CheckLogger, logger))
145+
146+
assert logger.warnings == 0
147+
logger.flush_new_checks()
148+
logger.assert_info(
149+
"missing validity attribute: valid_from must be set.",
150+
expect_location=False,
151+
)
152+
153+
154+
def test_validity_consistency_infos_for_whitespace_valid_from() -> None:
155+
test_need = need(
156+
id="feat_req__id_2",
157+
type="feat_req",
158+
valid_from=" ",
159+
valid_until="v1.0",
160+
)
161+
logger = fake_check_logger()
162+
app = Mock(spec=Sphinx)
163+
164+
check_validity_consistency(app, test_need, cast(CheckLogger, logger))
165+
166+
assert logger.warnings == 0
167+
logger.flush_new_checks()
168+
logger.assert_info(
169+
"missing validity attribute: valid_from must be set.",
170+
expect_location=False,
171+
)

0 commit comments

Comments
 (0)