Skip to content

Commit 5f078cb

Browse files
russozclaude
andauthored
refactor(nox): migrate regen-shellcheck-ignores script to nox session (#228)
Move the standalone tests/utils/regen-shellcheck-ignores script into a nox session (regen_shellcheck_ignores) in noxfile.py, and update the testing guide docs to reference the new nox invocation. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7a57f7c commit 5f078cb

3 files changed

Lines changed: 120 additions & 175 deletions

File tree

docs/docsite/rst/testing_guide.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -220,15 +220,15 @@ directives inside the module files themselves.
220220

221221
Some project-wide suppressions live in ``.shellcheckrc`` at the repository root.
222222

223-
To regenerate the ignore-file entries systematically, use the helper script
224-
``tests/utils/regen-shellcheck-ignores``. It strips all existing ``shellcheck`` lines
225-
from every ``ignore-X.Y.txt`` file, runs the check for each supported ansible-core
226-
version, and appends the new failures (with descriptions from the ``shellcheck`` wiki)
227-
back to the appropriate ignore files.
223+
To regenerate the ignore-file entries systematically, use the ``regen_shellcheck_ignores``
224+
nox session. It strips all existing ``shellcheck`` lines from every ``ignore-X.Y.txt``
225+
file, runs the check for each supported ansible-core version, and appends the new
226+
failures (with descriptions from the ``shellcheck`` wiki) back to the appropriate
227+
ignore files.
228228

229229
.. code-block:: console
230230
231-
$ python3 tests/utils/regen-shellcheck-ignores
231+
$ nox -e regen_shellcheck_ignores
232232
233233
Run this after adding or significantly modifying shell files, or whenever the set of
234234
supported ansible-core versions changes.

noxfile.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,5 +321,119 @@ def roles(session: nox.Session):
321321
session.run("molecule", "-vv", "test", "--scenario-name", scenario, external=True, env=env)
322322

323323

324+
@nox.session(reuse_venv=True, default=False)
325+
def regen_shellcheck_ignores(session: nox.Session):
326+
"""Regenerate shellcheck ignore entries in tests/sanity/ignore-*.txt."""
327+
import html
328+
import re
329+
import subprocess
330+
import urllib.request
331+
from html.parser import HTMLParser
332+
333+
sanity_dir = Path("tests") / "sanity"
334+
if not sanity_dir.is_dir():
335+
session.error("Run this from the top directory of the project")
336+
337+
class TitleParser(HTMLParser):
338+
def __init__(self, body: str) -> None:
339+
super().__init__()
340+
self.in_title = False
341+
self.title_parts = []
342+
self.feed(body)
343+
344+
def handle_starttag(self, tag, attrs):
345+
if tag.lower() == "title":
346+
self.in_title = True
347+
348+
def handle_endtag(self, tag):
349+
if tag.lower() == "title":
350+
self.in_title = False
351+
352+
def handle_data(self, data):
353+
if self.in_title:
354+
self.title_parts.append(data)
355+
356+
@property
357+
def title(self) -> str:
358+
return html.unescape("".join(self.title_parts).strip())
359+
360+
sc_desc_cache = {}
361+
362+
def retrieve_sc_description(sc_code: str) -> str:
363+
if sc_code in sc_desc_cache:
364+
return sc_desc_cache[sc_code]
365+
url = f"https://www.shellcheck.net/wiki/{sc_code}"
366+
try:
367+
with urllib.request.urlopen(url, timeout=10) as resp:
368+
charset = resp.headers.get_content_charset() or "utf-8"
369+
body = resp.read().decode(charset, errors="replace")
370+
except Exception as e:
371+
sc_desc_cache[sc_code] = f"(failed to fetch: {e})"
372+
return sc_desc_cache[sc_code]
373+
parser = TitleParser(body)
374+
title = parser.title.replace(f"ShellCheck: {sc_code} – ", "").rstrip(".")
375+
sc_desc_cache[sc_code] = f"{title} - {url}" if title else f"(no title found at {url})"
376+
return sc_desc_cache[sc_code]
377+
378+
ig_version_re = re.compile(r".*/ignore-(?P<version>\d\.\d+)\.txt")
379+
380+
def get_version(filename):
381+
if not (match := ig_version_re.search(str(filename))):
382+
raise ValueError(f"ignore filename not recognized: {filename}")
383+
version = match.group("version")
384+
return f"ac{version.replace('.', '')}", version
385+
386+
ignore_files = sorted(sanity_dir.glob("ignore-*.txt"))
387+
tox_targets = [get_version(f)[0] for f in ignore_files[:-1]] + ["dev"]
388+
ignore_versions = [get_version(f)[1] for f in ignore_files]
389+
390+
for tox_target, ignore_version in zip(tox_targets, ignore_versions, strict=True):
391+
ignore_file = sanity_dir / f"ignore-{ignore_version}.txt"
392+
session.log(f"Processing {ignore_file} ({tox_target}/{ignore_version})")
393+
394+
with ignore_file.open("r", encoding="utf-8") as f:
395+
kept = [ll for ll in f if "shellcheck" not in ll]
396+
with ignore_file.open("w", encoding="utf-8") as f:
397+
f.writelines(kept)
398+
399+
proc = subprocess.run(
400+
[
401+
"andebox",
402+
"tox-test",
403+
"-e",
404+
tox_target,
405+
"--",
406+
"sanity",
407+
"--python",
408+
"default",
409+
"--docker",
410+
"default",
411+
"--test",
412+
"shellcheck",
413+
],
414+
stdout=subprocess.PIPE,
415+
stderr=subprocess.STDOUT,
416+
text=True,
417+
check=False,
418+
)
419+
420+
found = set()
421+
for line in proc.stdout.splitlines():
422+
if not line.startswith("ERROR"):
423+
continue
424+
parts = line.split(":", 5)
425+
if "/" not in parts[1]:
426+
continue
427+
path, sc_code = parts[1].strip(), parts[4].strip()
428+
desc = retrieve_sc_description(sc_code)
429+
found.add(f"{path} shellcheck:{sc_code} # {desc}")
430+
431+
if found:
432+
with ignore_file.open("a", encoding="utf-8") as f:
433+
f.writelines(f"{ll}\n" for ll in sorted(found))
434+
435+
session.log(f"Added {len(found)} shellcheck ignore entries to {ignore_file}")
436+
437+
324438
if __name__ == "__main__":
325439
nox.main()

tests/utils/regen-shellcheck-ignores

Lines changed: 0 additions & 169 deletions
This file was deleted.

0 commit comments

Comments
 (0)