mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 19:56:53 +00:00
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>
222 lines
8.5 KiB
Python
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()
|