mirror of
https://github.com/Gathin23/cleanyourtv.git
synced 2026-10-27 19:54:45 +00:00
The three scripts that draw README's card read sys.argv[1], sys.argv[2] at the top of the module, so a run with no arguments died on IndexError, and press.md's Fredoka.ttf - a Google Fonts file no folder of this repo holds - died inside Pillow on 'OSError: cannot open resource'. One font_and_out() guard per script, before the Pillow import; gen_tables.py --font refuses a missing path itself instead of wrapping their message in CalledProcessError; press.md says where the font comes from. The one test that ran gen_tables.py --check on this tree and demanded exit 0 is gone: the tables are stale on any branch that adds a list folder, which docs/workflows/tv-list.md tells a list PR to do and leaves to a maintainer after merging, so that assertion would have been red on every list PR. In its place, --check must give the same verdict with and without --font.
136 lines
7.1 KiB
Python
136 lines
7.1 KiB
Python
"""The three scripts that draw the README card need Pillow, and nothing a contributor reads said so.
|
|
|
|
`tools/requirements.txt` is what a reader installs for the tools and it has no Pillow; the package lives
|
|
in `tools/requirements-perf.txt`, a file whose header used to call itself "only for the performance
|
|
tools". `docs/workflows/press.md` is the page that asks a contributor to redraw `docs/featured.png` and
|
|
`docs/card.jpg`, so following it ended in `ModuleNotFoundError: No module named 'PIL'` with no hint of
|
|
what to install. The three card scripts now name that file the way tools/flamechart.py and
|
|
tools/screendiff.py already did, and this test keeps the three of them, the requirements file and the
|
|
workflow page in step.
|
|
|
|
readme_card.py had the second half of the same problem: its own usage line said `docs/card.png`, a file
|
|
the repo does not ship and no page links, while the front page displays `docs/card.jpg` - so a
|
|
contributor who ran the command the script documents wrote a stray file and left the README card stale.
|
|
The picture every card script names is checked against the files in the repo, and readme_card.py's
|
|
against the one README.md shows.
|
|
|
|
Run from the repository root: python3 -m unittest discover -s tests -v
|
|
"""
|
|
import ast
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
TOOLS = ROOT / "tools"
|
|
PRESS = ROOT / "docs" / "workflows" / "press.md"
|
|
PERF = TOOLS / "requirements-perf.txt"
|
|
|
|
# The scripts press.md sends a reader to, with the picture each one draws. The font they take is not in
|
|
# the repo; the tests below pass a file that exists so the Pillow check is the one they reach.
|
|
CARD_SCRIPTS = {
|
|
"readme_stats.py": "stats.png",
|
|
"readme_featured.py": "featured.png",
|
|
"readme_card.py": "card.jpg",
|
|
}
|
|
|
|
|
|
def shadow_dir(tmp):
|
|
"""A PIL package that cannot be imported, so a script behaves as on a venv without Pillow."""
|
|
pkg = Path(tmp) / "PIL"
|
|
pkg.mkdir(exist_ok=True)
|
|
(pkg / "__init__.py").write_text('raise ImportError("No module named \'PIL\'")\n', encoding="utf-8")
|
|
return str(tmp)
|
|
|
|
|
|
def run_without_pillow(argv, tmp):
|
|
env = dict(os.environ)
|
|
env["PYTHONPATH"] = shadow_dir(tmp)
|
|
return subprocess.run([sys.executable, *argv], cwd=ROOT, env=env, capture_output=True, text=True)
|
|
|
|
|
|
class CardScriptsPillowTest(unittest.TestCase):
|
|
def test_the_shadow_really_hides_pillow(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
done = run_without_pillow(["-c", "import PIL"], tmp)
|
|
self.assertNotEqual(done.returncode, 0, "the PIL shadow did not take effect: the rest of this file would prove nothing")
|
|
|
|
def test_each_card_script_is_importable_without_pillow_and_names_the_file(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
font = Path(tmp) / "Fredoka.ttf"
|
|
font.write_bytes(b"") # any font path that exists: the scripts check the font before Pillow
|
|
for name, picture in CARD_SCRIPTS.items():
|
|
with self.subTest(script=name):
|
|
self.assertTrue((TOOLS / name).is_file(), f"press.md names {name}, which is not in tools/")
|
|
done = run_without_pillow([str(TOOLS / name), str(font), picture], tmp)
|
|
self.assertNotEqual(done.returncode, 0, f"{name} ran without Pillow, so the shadow missed it")
|
|
self.assertIn("requirements-perf.txt", done.stderr, f"{name} does not say where Pillow comes from")
|
|
self.assertNotIn("Traceback", done.stderr, f"{name} still dies on a bare traceback")
|
|
|
|
def test_the_requirements_file_installs_pillow(self):
|
|
lines = [l.strip().lower() for l in PERF.read_text(encoding="utf-8").split("\n")]
|
|
self.assertTrue(any(l.startswith("pillow") for l in lines), "tools/requirements-perf.txt no longer installs Pillow")
|
|
header = "\n".join(l for l in PERF.read_text(encoding="utf-8").split("\n") if l.startswith("#"))
|
|
for name in CARD_SCRIPTS:
|
|
with self.subTest(script=name):
|
|
self.assertIn(name, header, f"the header of requirements-perf.txt does not say it is needed by {name}")
|
|
|
|
def test_press_md_sends_the_reader_to_that_file(self):
|
|
text = PRESS.read_text(encoding="utf-8")
|
|
self.assertIn("tools/requirements-perf.txt", text, "press.md asks for a redraw without saying what to install")
|
|
self.assertIn("docs/card.jpg", text, "press.md no longer names the card the README links")
|
|
self.assertIn("docs/featured.png", text)
|
|
# readme_stats.py draws the stat strip, which a press entry does not change, so press.md is not
|
|
# required to name it; the two scripts it does send a reader to are.
|
|
for name in ("readme_featured.py", "readme_card.py"):
|
|
with self.subTest(script=name):
|
|
self.assertIn(name, text, f"press.md does not name {name}")
|
|
|
|
|
|
DOCS_PICTURE = re.compile(r"docs/[\w.\-]+\.(?:png|jpg|jpeg|webp)")
|
|
|
|
|
|
def docstring_of(path):
|
|
"""The module docstring of a tools/ script, which is where each one documents its two arguments."""
|
|
return ast.get_docstring(ast.parse(path.read_text(encoding="utf-8"))) or ""
|
|
|
|
|
|
class CardScriptOutputTest(unittest.TestCase):
|
|
def test_every_picture_a_card_script_names_is_one_the_repo_ships(self):
|
|
for name in CARD_SCRIPTS:
|
|
named = set(DOCS_PICTURE.findall(docstring_of(TOOLS / name)))
|
|
self.assertTrue(named, f"{name}'s docstring names no picture, so this check would prove nothing")
|
|
for rel in sorted(named):
|
|
with self.subTest(script=name, picture=rel):
|
|
self.assertTrue((ROOT / rel).is_file(),
|
|
f"{name}'s usage line names {rel}, which the repo does not ship: "
|
|
"a reader who follows it writes a new file that nothing links")
|
|
|
|
def test_the_card_script_writes_the_picture_the_front_page_shows(self):
|
|
readme = (ROOT / "README.md").read_text(encoding="utf-8")
|
|
shown = set(re.findall(r'<img src="(docs/[^"]*card[^"]*)"', readme))
|
|
self.assertTrue(shown, "README.md no longer displays a card image, so this check would prove nothing")
|
|
named = set(DOCS_PICTURE.findall(docstring_of(TOOLS / "readme_card.py")))
|
|
for rel in sorted(shown):
|
|
with self.subTest(picture=rel):
|
|
self.assertIn(rel, named,
|
|
f"README.md displays {rel} but readme_card.py's usage line writes {sorted(named)}")
|
|
|
|
def test_the_docstring_reader_reads_a_script_and_not_its_comments(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
made = Path(tmp) / "example.py"
|
|
made.write_text('"""Writes docs/one.png.\n\npython3 example.py docs/one.png\n"""\n'
|
|
'# docs/two.png is only a comment\n', encoding="utf-8")
|
|
self.assertIn("docs/one.png", docstring_of(made))
|
|
self.assertNotIn("docs/two.png", docstring_of(made))
|
|
made.write_text("import sys # no docstring at all\n", encoding="utf-8")
|
|
self.assertEqual(docstring_of(made), "")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|