Files
computer-playbook/docs/contributing/actions/testing/suppression.md
T
kevinveenbirkenbachandClaude Opus 4.7 7232dba0dd test(lint): regroup role-scoped lint tests under tests/lint/ansible/roles/
Move five role-scoped lint tests out of the catch-all
tests/lint/repository/ tree into the ansible/roles/ tree where
they conceptually belong, dropping the redundant `role_` /
`meta_` filename prefixes:

  test_role_file_spot.py             → roles/test_file_spot.py
  test_role_readme.py                → roles/test_readme.py
  test_role_meta_info.py             → roles/meta/test_info.py
  test_role_meta_layout.py           → roles/meta/test_layout.py
  test_role_meta_main_galaxy_schema  → roles/meta/test_main_galaxy_schema.py

Plus regroup two web-app-only Playwright tests under
roles/web-app/playwright/ so the path itself states the scope:

  test_playwright_env_keys_used      → roles/web-app/playwright/test_env_keys_used.py
  test_web_app_roles_have_playwright_spec
                                     → roles/web-app/playwright/test_has_spec.py

New __init__.py files set PROJECT_ROOT for the new package depths.
External references (suppression catalog entry, design doc,
web-app-joomla README, two integration-test docstrings) updated
to the new paths. Stale path strings in legacy requirement docs
left untouched per the snapshot policy from req 019.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 04:33:59 +02:00

21 KiB

Suppression Markers 🚫

The unified # noqa / # nocheck syntax that suppresses individual checks across the infinito-nexus test suite.

You MUST use these markers only when the check genuinely does not apply. You MUST NOT use them to silence legitimate issues.

Grammar 📐

A suppression marker is a comment carrying one or more rule keys:

<comment-prefix> (noqa|nocheck): <rule>(, <rule>)*

Rules:

  • noqa and nocheck are accepted as synonyms by the parser, but by repo convention every project rule MUST use nocheck:. Reason: # noqa: <code> is also parsed by ruff as a flake8 directive, and ruff warns on non-flake8 rule keys (every key in this catalog is project-specific, so the warning fires on every marker). nocheck: is the project-specific keyword ruff ignores. Reserve noqa: for real flake8 / ruff codes (E402, F401, …). The convention is enforced by test_noqa_only_ruff_codes.py, which fails any # noqa: <code> whose codes don't match the ruff/flake8 shape [A-Z]+\d+.
  • The keyword is matched case-insensitively.
  • <rule> is a kebab-case identifier from the catalog below.
  • Multiple rules MAY be combined on one comment, comma-separated: # nocheck: shared, email.

Accepted comment prefixes (so the marker fits any file format that the test scans):

Prefix Use in
# Python, YAML, shell, conf, INI, …
// JS, JSONC, …
{# … #} Jinja2 templates
<!-- … --> HTML, Markdown

Implementation: utils/annotations/suppress.py.

Position semantics 📍

The placement rule is per check. The catalog column "Position" uses these labels:

  • same line: the marker MUST be on the same line as the construct it suppresses.
  • same line / span: the marker MUST be on the construct's own line or, for multi-line call expressions, any physical line spanned by that call.
  • line above: the marker MUST be on the immediately preceding non-empty line. Blank lines between the marker and the construct break the association.
  • same or above: either of the above is accepted.
  • comment block above: the marker may sit on any line in a contiguous comment block above the construct (no blank line between marker and construct).
  • head (first 30 lines): the marker MAY appear anywhere in the first 30 lines of the file. Used for file-level opt-outs that should stay visible at the top of the file.
  • anywhere: the marker may appear on any line of the file. Used for whole-file opt-outs whose semantics legitimately apply to the entire file.

Catalog 📚

Rule Position Affected test Effect
url same or above test_urls_reachable.py Skips probing the literal HTTP(S) URL. Use for CDN roots and API base URLs that return 4xx without a path.
docker-version line above test_image_versions.py Skips the live version-update warning for that image's version: key.
direct-yaml same line / span test_no_direct_yaml_calls.py Allows the marked yaml.safe_load / safe_dump call to bypass utils.cache.yaml. This rule's checker hard-rejects noqa: and accepts only nocheck: (ruff parses noqa: as flake8 and warns on non-flake8 codes).
redundant-bool-ternary same line test_no_redundant_bool_patterns.py Allows the redundant | bool | ternary('true','false') pattern on the marked line, only when a consumer really requires the lowercase true/false tokens.
redundant-bool-comparison same line test_no_redundant_bool_patterns.py Allows ... | bool == True/False/true/false on the marked line, only when a real tri-state guard is intended (e.g. distinguishing None from False).
shared comment block above test_service_shared_consistency.py Marks a service whose enabled: true legitimately does not require shared: true.
email line above test_email.py Suppresses the missing-email-integration warning when paired with enabled: false and shared: false.
logout line above test_logout_dashboard.py Opts a web-app-* role out of the universal-logout integration. Marker MUST be on the line directly above logout:, paired with enabled: false and shared: false.
dashboard line above test_logout_dashboard.py Opts a web-app-* role out of the dashboard tile integration. Same shape as logout.
oidc line above test_sso.py Opts the role's native OIDC path out (so that oauth2 must take over, OR, if oauth2 is also opted out, the role legitimately has no login flow). Same shape as email.
oauth2 line above test_sso.py Opts the role's oauth2-proxy path out. In combination with # nocheck: oidc it declares "this role has no login flow at all". Same shape as email.
file-size head (first 30 lines) test_python_file_size.py Opts the entire .py file out of the 500-line cap.
project-walk same line / span test_no_raw_project_walk.py Allows a raw Path.rglob, os.walk, or glob.glob call when the walk is intentionally outside the cached project-tree helper contract.
cache-read same line / span test_no_uncached_file_reads.py Allows a raw Path.read_text / Path.read_bytes call in a tests/lint/ test when the read is intentionally outside the LRU-cached utils.cache.files.read_text contract.
run-once anywhere test_run_once_tags.py, test_schema.py Marks a role's tasks/main.yml as intentionally re-runnable; skips both the run-once tag check and the suffix check.
run-once-suffix same line test_schema.py Allows a single when: item to reference a run_once_<other> flag whose suffix differs from the current role.
raw-docker same or above; head (first 30 lines) test_no_raw_docker.py Marks a single line or a whole file under roles/ as legitimately calling docker / docker compose / docker-compose directly. The check is scoped to roles/; bootstrap scripts and CI workflows outside roles/ are not scanned and need no marker.
hardcoded-dns-resolver same or above test_no_hardcoded_dns_resolvers.py Marks a line that legitimately needs a literal IP from NETWORK_PUBLIC_DNS_RESOLVERS at the substitution point (CoreDNS forward directives that don't run through Jinja, host-bootstrap shell scripts, documentation examples). The variable in group_vars/all/08_networks.yml is the SPOT for these IPs everywhere else.
dynamic-flag same line (per-flag) OR comment block above key (whole block) test_dynamic_flags.py Marks a roles/*/meta/services.yml flag whose value legitimately stays literal. Per-flag (same line) is the typical shape for databases (enabled: true literal, shared still dynamic). Block-level (comment block above the service key) is for entries where both flags stay literal (e.g. css).
lookup-config-path same or above tests/integration/lookups/config/ (literal / variable / wildcard / role-local) Skips a single lookup('config', …) call from every path-validation pass. Use when the call legitimately resolves at runtime against state that is NOT visible to the static scan, typically a self-referential role like web-app-oauth2-proxy reading services.oauth2.* keys that other roles publish but its own meta/services.yml does not. Note: the role-local pass already ignores any call whose app argument is NOT the literal application_id (e.g. _BBB_COTURN_ROLE, oauth2_proxy_application_id), so cross-role lookups need no marker for that pass. Mark a call only when the literal / variable / wildcard pass complains.
unused-var same or above tests/lint/ansible/variables/test_role_and_group_vars_used.py Exempts a top-level key in roles/<role>/{vars,defaults}/main.yml or group_vars/**/*.yml from the "must be referenced in some .yml / .yaml / .j2" check. Use when the var is consumed somewhere the lint cannot see (Python plugin code, an external tool reading the rendered Ansible inventory, set_fact chains driven by computed names, etc.). Do NOT use to silence a legitimately dead var; prune the declaration instead.
project-root-import same or above tests/lint/repository/test_project_root_import.py Permits a local PROJECT_ROOT / parents[N] / os.pardir chain on the marked line. Reserved for two cases: a __main__.py bootstrap shim that prepends the repo root to sys.path before any package import resolves, and a standalone script under roles/<role>/files/ that has no package container to import from. The marker MUST carry an inline comment that explains why the local computation is unavoidable.
domain-spot same or above tests/lint/repository/test_domain_primary_spot.py Marks a line that legitimately uses a <word>.{{ DOMAIN_PRIMARY }} host literal outside the SPOT (roles/<role>/meta/server.yml / meta/variants.yml). Use only for legacy domain cleanups (e.g. removing stale nginx config for a renamed host) where the canonical SPOT cannot represent the historical name.
literal-protocol-lookup same or above tests/lint/repository/test_literal_protocol_with_lookup.py Allows a hardcoded http:// / https:// literal next to a lookup(...) call. Reserved for legitimate internal cases where the protocol is genuinely fixed: Docker-network upstreams that always speak plaintext (http://<container>:<port>), loopback URLs in CI fixtures, and similar. Production-facing URLs MUST go through lookup('tls', …, 'protocols.web').
self-path-reference same line tests/lint/repository/test_no_self_path_reference.py Allows a file to contain its own relative path from the repo root as a string literal on the marked line. Reserved for cases where the self-reference is load-bearing (e.g. a static site generator that injects its own source path into the rendered output). Files at the repo root (no / in their relative path) are exempt without a marker.
role-file-spot same or above tests/lint/ansible/roles/test_file_spot.py Allows a .py file to carry a literal role-file path (e.g. "meta/services.yml") on the marked line instead of the matching ROLE_FILE_* constant from utils.roles.mapping. Reserved for tests that assert the constant's literal value, migration scripts that operate on raw paths before the SPOT applies, or doc strings that embed the path.
playwright-service-flag comment block above tests/integration/roles/test_playwright_env_services_match.py Exempts a top-level service in roles/<role>/meta/services.yml from the "must be registered as <NAME>_SERVICE_ENABLED= in templates/playwright.env.j2" check. Use only when the service legitimately cannot or must not be gated from Playwright (e.g. requirement-006 self-gate exceptions where the role IS the provider, like web-app-keycloak for oidc).
playwright-service-gate same or above tests/integration/roles/test_playwright_spec_env_gates.py Exempts a <NAME>_SERVICE_ENABLED= line in roles/<role>/templates/playwright.env.j2 from the "must be gated by requireService / skipUnlessServiceEnabled / isServiceEnabled / isServiceDisabledReason in files/playwright.spec.js" check. Use only when the flag legitimately exists for non-spec consumers but the spec has no scenario to gate.

Examples 💡

url, on the line carrying the literal URL or on the line above it:

ensure_proxy_repo raw raw-packagist "$(... https://repo.packagist.org/ ...)"  # nocheck: url
# nocheck: url
api_base_url: "https://example.org/v1"

docker-version, directly above a version: key:

service_x:
  # nocheck: docker-version
  version: "4.5"

direct-yaml, anywhere on the call's physical line or on any line spanned by the multi-line call. This rule MUST use nocheck: (not noqa:): ruff parses # noqa: <code> as a flake8 directive and warns about non-flake8 codes like direct-yaml.

data = yaml.safe_load(text)  # nocheck: direct-yaml

shared, somewhere in the comment block immediately above a service key:

# This service is inherently role-local.
# nocheck: shared
my_local_service:
  enabled: true

email, directly above the email: block, paired with both flags false:

# nocheck: email
email:
  enabled: false
  shared: false

file-size, at the top of a long Python file:

"""Module docstring."""
# nocheck: file-size  (single-host orchestration script ships as one file)

project-walk, on the raw walk call or on any line spanned by the call:

for path in temp_dir.rglob("*"):  # noqa: project-walk
    ...

Use project-walk only for scans that genuinely do not fit utils.cache.files.iter_project_files, such as temporary directories, .git metadata, or a deliberately tiny fixed path probe. Broad scans of the repository tree SHOULD use the cached helpers instead.

cache-read, on the raw read call or on any line spanned by the call:

text = tmp_path.read_text()  # noqa: cache-read  (synthetic fixture written above)

Use cache-read only when the file legitimately does not belong to the project-tree LRU cache, e.g. a tempdir written and read inside the same test, a file outside the repository root, or an in-memory io.StringIO-backed Path. Reads of project files (under roles/, tasks/, docs/, tests/, etc.) MUST go through utils.cache.files.read_text so the LRU cache is shared across the pytest session.

run-once, at the top of a role's tasks/main.yml:

# nocheck: run-once

- name: ...

run-once-suffix, on the exact when: list-item line:

when:
  - run_once_svc_db_openldap is not defined   # nocheck: run-once-suffix

raw-docker, per-line on a single role-task line that legitimately calls docker directly (e.g. a bootstrap step that runs before the container wrapper is on the path):

- name: "Bootstrap engine before the wrapper exists"
  ansible.builtin.command: docker info  # nocheck: raw-docker

raw-docker, file-level at the top of a role's bundled shell script that legitimately drives the engine directly:

#!/usr/bin/env bash
# nocheck: raw-docker. Runs from sys-svc-container's installer before the wrapper symlink lands.
docker buildx build ...

dynamic-flag, two placements depending on scope:

Per-flag (same line), for databases where enabled: true reflects a static fact about the role's needs but shared still resolves dynamically:

mariadb:
  enabled: true  # nocheck: dynamic-flag
  shared: "{{ 'svc-db-mariadb' in group_names }}"

Block-level (comment block above the service key), for service entries whose both flags legitimately stay literal (e.g. css, javascript):

# nocheck: dynamic-flag
css:
  enabled: true
  shared: true

project-root-import, on the line that locally derives the repo root inside a __main__.py sys.path bootstrap (the marker line MUST explain why the local computation runs before any package import resolves), or inside a standalone script that has no package container to import from:

PROJECT_ROOT = Path(__file__).resolve().parents[1]  # nocheck: project-root-import, sys.path bootstrap before package imports resolve
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

unused-var, on the var declaration line (or directly above it), when the var is consumed by Python plugin code or another non-YAML/Jinja surface that the static scan cannot reach:

# nocheck: unused-var  # consumed by plugins/filter/<role>_render.py
ROLE_TEMPLATE_PATH: "templates/<role>/render.j2"
ROLE_TEMPLATE_PATH: "templates/<role>/render.j2"  # nocheck: unused-var  # consumed by plugins/filter/<role>_render.py

lookup-config-path, on the line carrying a lookup('config', …) call (or directly above it) when the path legitimately resolves against runtime-only state that the static scan cannot see, for example a self-referential role that consumes the services.<self>.* keys other roles publish about it:

# nocheck: lookup-config-path
upstreams = {{ lookup('config', application_id, 'services.oauth2.origin.host') }}
upstreams = {{ lookup('config', application_id, 'services.oauth2.origin.host') }}  {# nocheck: lookup-config-path #}

hardcoded-dns-resolver, on the line that emits the literal IP, when the substitution point cannot consume NETWORK_PUBLIC_DNS_RESOLVERS (e.g. a CoreDNS forward directive in a non-Jinja template):

forward . 1.1.1.1 8.8.8.8  # nocheck: hardcoded-dns-resolver

Tool-Native noqa Codes 🧹

Some Python files also use Ruff-compatible uppercase # noqa codes. These codes are handled by Ruff and related Python linters, not by utils/annotations/suppress.py. They MAY appear in Python files when the local exception is intentional and clear from context.

Code Tool meaning Typical allowed use in this repository
BLE001 Blind exception caught Narrow command-wrapper boundaries that must convert any underlying failure into a domain-specific error.
E402 Module import not at top Tests that MUST adjust sys.path or load fixtures before importing the module under test.
E731 Lambda assigned to variable Small test-local callbacks or path formatters where a named function would add noise.
F401 Imported but unused Re-export modules or import-probe tests where import success is the assertion.
SLF001 Private member access Test-only introspection of parser or object internals when the public surface does not expose the needed state.

Adding a new rule 🆕

  1. Pick a kebab-case <rule> identifier.
  2. Decide which position semantic from the list above fits the check.
  3. Use the is_suppressed_at / is_suppressed_in_head / is_suppressed_anywhere helpers from utils/annotations/suppress.py in your test. Do NOT roll a new regex.
  4. Add the rule to the catalog table on this page.