Files
computer-playbook/tests/unit/python/utils/annotations/test_suppress.py
T
kevinveenbirkenbachandClaude Opus 5 0bb437d9ce feat(images): check declared architectures against the registry, and make a pin say why
Nothing held a role's architectures to reality: test_image_reachable proves a tag exists, test_arm64_images proves four named roles publish arm64, and the closure lint only proves the declarations do not intersect to nothing. An amd64-only bump became a scheduling failure on an arm64 row rather than a red test.

A new external test sweeps every pinned image and judges a role by the intersection over its services, because a role runs only where all of its images run; judging per image would demand arm64 from an amd64-only role's nginx and call a narrowing stale because nginx publishes arm64 while the application does not. A role declaring nothing must cover every architecture the matrix rotates; a role that narrows must narrow to exactly its capability, which is what retires its TODO by itself. An unread image downgrades the stale verdict to a warning, since the image a probe could not read may be the one the narrowing exists for. The probe reads the mirror first and caches on disk, because the sweep is ~160 manifests and Docker Hub counts its allowance per IP, shared.

A new lint requires every architectures declaration to carry a nocheck marker and a Reason. bigbluebutton and opentalk keep their pins with measured reasons; decidim, pretix and taiga lose theirs, because each builds its own image and their upstream Dockerfiles are arch-neutral. Their source builds are not converted yet, so until that lands the matrix may hand them an arm64 row that fails at FROM.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-05 22:11:48 +02:00

222 lines
8.5 KiB
Python

"""Unit tests for the unified suppression-marker grammar."""
from __future__ import annotations
import unittest
from utils.annotations.suppress import (
has_rule_with_reason,
is_suppressed_anywhere,
is_suppressed_at,
is_suppressed_in_head,
line_has_rule,
suppressed_line_numbers,
)
class TestLineHasRule(unittest.TestCase):
def test_noqa_and_nocheck_are_synonyms(self):
self.assertTrue(line_has_rule("# noqa: url", "url"))
self.assertTrue(line_has_rule("# nocheck: url", "url"))
def test_case_insensitive(self):
self.assertTrue(line_has_rule("# NOQA: URL", "url"))
self.assertTrue(line_has_rule("# NoCheck: Url", "url"))
def test_html_jinja_and_slash_prefixes(self):
self.assertTrue(line_has_rule("<!-- nocheck: url -->", "url"))
self.assertTrue(line_has_rule("{# nocheck: url #}", "url"))
self.assertTrue(line_has_rule("// noqa: url", "url"))
def test_inline_marker_in_code(self):
line = 'tokens.append("https://example.org") # nocheck: url'
self.assertTrue(line_has_rule(line, "url"))
def test_multi_rule_comma_separated(self):
line = "# noqa: shared, email"
self.assertTrue(line_has_rule(line, "shared"))
self.assertTrue(line_has_rule(line, "email"))
self.assertFalse(line_has_rule(line, "url"))
def test_unrelated_text_does_not_match(self):
self.assertFalse(line_has_rule("# this is just a comment", "url"))
self.assertFalse(line_has_rule("noqa url", "url")) # missing colon
def test_word_boundary_avoids_false_positives(self):
self.assertFalse(line_has_rule("# nocheck: url-other", "url"))
self.assertTrue(line_has_rule("# nocheck: url-other", "url-other"))
class TestIsSuppressedAt(unittest.TestCase):
def test_same_line_mode(self):
lines = ["x = 1", "y = 2 # nocheck: url"]
self.assertTrue(is_suppressed_at(lines, 2, "url", mode="same-line"))
self.assertFalse(is_suppressed_at(lines, 1, "url", mode="same-line"))
def test_line_above_mode(self):
lines = ["# nocheck: docker-version", "version: 1.2.3"]
self.assertTrue(is_suppressed_at(lines, 2, "docker-version", mode="line-above"))
self.assertFalse(is_suppressed_at(lines, 2, "docker-version", mode="same-line"))
def test_line_above_skips_blank_lines(self):
lines = ["# noqa: email", "", "", "email:"]
self.assertTrue(is_suppressed_at(lines, 4, "email", mode="line-above"))
def test_block_above_reaches_past_a_later_annotation(self):
"""A gettext entry stacks comments; the newest one lands closest.
``translate.py`` appends ``translated-by`` when it fills an entry,
which pushes an older marker one line further from the msgid. Under
``line-above`` the marker would silently stop working.
"""
lines = [
"# nocheck: url",
"# translated-by: libretranslate",
'msgid "see https://example.invalid/"',
]
self.assertTrue(is_suppressed_at(lines, 3, "url", mode="block-above"))
self.assertFalse(is_suppressed_at(lines, 3, "url", mode="line-above"))
def test_block_above_covers_the_translation_too(self):
"""A gettext entry repeats the URL in msgid and msgstr.
Only the msgid follows the comments; the msgstr follows the msgid.
A marker that stopped at the first non-comment line would leave the
translation unguarded.
"""
lines = [
"# nocheck: url",
"# translated-by: libretranslate",
'msgid "see https://example.invalid/"',
'msgstr "siehe https://example.invalid/"',
]
self.assertTrue(is_suppressed_at(lines, 3, "url", mode="block-above"))
self.assertTrue(is_suppressed_at(lines, 4, "url", mode="block-above"))
def test_block_above_stops_at_the_blank_line_between_entries(self):
lines = [
"# nocheck: url",
'msgid "guarded"',
'msgstr ""',
"",
'msgid "see https://example.invalid/"',
'msgstr ""',
]
self.assertFalse(is_suppressed_at(lines, 5, "url", mode="block-above"))
def test_block_above_accepts_the_marker_directly_above(self):
lines = ["# nocheck: url", 'msgid "see https://example.invalid/"']
self.assertTrue(is_suppressed_at(lines, 2, "url", mode="block-above"))
def test_block_above_accepts_the_marker_on_the_line_itself(self):
lines = ['msgid "see https://example.invalid/" # nocheck: url']
self.assertTrue(is_suppressed_at(lines, 1, "url", mode="block-above"))
def test_default_mode_accepts_either(self):
same = ["foo # nocheck: url"]
above = ["# nocheck: url", "foo"]
self.assertTrue(is_suppressed_at(same, 1, "url"))
self.assertTrue(is_suppressed_at(above, 2, "url"))
def test_out_of_range_returns_false(self):
self.assertFalse(is_suppressed_at([], 1, "url"))
self.assertFalse(is_suppressed_at(["x"], 0, "url"))
self.assertFalse(is_suppressed_at(["x"], 999, "url"))
class TestFileLevelHelpers(unittest.TestCase):
def test_head_marker_within_scan_window(self):
lines = ['"""docstring."""', "# nocheck: file-size", "rest = 1"]
self.assertTrue(is_suppressed_in_head(lines, "file-size", scan_lines=30))
def test_head_marker_outside_scan_window_misses(self):
lines = ["x = 1"] * 40 + ["# nocheck: file-size"]
self.assertFalse(is_suppressed_in_head(lines, "file-size", scan_lines=30))
def test_anywhere_marker(self):
lines = ["x = 1"] * 100 + ["# nocheck: run-once"]
self.assertTrue(is_suppressed_anywhere(lines, "run-once"))
def test_anywhere_no_match(self):
self.assertFalse(is_suppressed_anywhere(["x", "y"], "run-once"))
class TestSuppressedLineNumbers(unittest.TestCase):
def test_returns_one_based_line_numbers(self):
lines = [
"# unrelated",
"# noqa: url",
"x # nocheck: url",
"# noqa: shared",
]
self.assertEqual(suppressed_line_numbers(lines, "url"), {2, 3})
self.assertEqual(suppressed_line_numbers(lines, "shared"), {4})
class TestHasRuleWithReason(unittest.TestCase):
"""An exemption that has to say why, written above the value it exempts."""
def test_both_markers_on_the_value_line(self) -> None:
lines = [" min_storage: 20GB # nocheck: rule-x Reason: the volume"]
self.assertTrue(has_rule_with_reason(lines, 1, "rule-x"))
def test_both_markers_in_the_comment_block_above(self) -> None:
lines = [
" # nocheck: rule-x",
" # Reason: the data volume dominates",
" min_storage: 20GB",
]
self.assertTrue(has_rule_with_reason(lines, 3, "rule-x"))
def test_the_marker_order_does_not_matter(self) -> None:
lines = [
" # Reason: the data volume dominates",
" # nocheck: rule-x",
" min_storage: 20GB",
]
self.assertTrue(has_rule_with_reason(lines, 3, "rule-x"))
def test_a_marker_without_a_reason_is_refused(self) -> None:
lines = [" # nocheck: rule-x", " min_storage: 20GB"]
self.assertFalse(has_rule_with_reason(lines, 2, "rule-x"))
def test_a_reason_without_the_marker_is_refused(self) -> None:
lines = [" # Reason: the data volume dominates", " min_storage: 20GB"]
self.assertFalse(has_rule_with_reason(lines, 2, "rule-x"))
def test_an_empty_reason_is_refused(self) -> None:
lines = [" # nocheck: rule-x Reason:", " min_storage: 20GB"]
self.assertFalse(has_rule_with_reason(lines, 2, "rule-x"))
def test_another_rules_marker_does_not_count(self) -> None:
lines = [" # nocheck: rule-y Reason: something else", " min_storage: 20GB"]
self.assertFalse(has_rule_with_reason(lines, 2, "rule-x"))
def test_a_blank_line_ends_the_comment_block(self) -> None:
lines = [
" # nocheck: rule-x Reason: the data volume dominates",
"",
" min_storage: 20GB",
]
self.assertFalse(
has_rule_with_reason(lines, 3, "rule-x"),
"a marker separated by a blank line belongs to the construct above it",
)
def test_a_line_number_outside_the_file_is_refused(self) -> None:
for line_no in (0, 5):
with self.subTest(line_no=line_no):
self.assertFalse(has_rule_with_reason(["x"], line_no, "rule-x"))
if __name__ == "__main__":
unittest.main()