|
4 | 4 | with a ``check`` and a ``falsify`` (old, new) whose ``old`` occurs in the |
5 | 5 | snippet, so tests/smoke/run_content.py can never silently skip a snippet. |
6 | 6 | - Every ```python block in skills/*/SKILL.md compiles. Blocks tagged with a |
7 | | - ``<!-- smoke: run -->`` line are also executed by run_content.py in Blender; |
8 | | - this reports how many are tagged. |
| 7 | + ``<!-- smoke: run -->`` line are also executed by run_content.py in Blender. |
| 8 | +- Ratchet (#525, tests/smoke/skill_blocks.json): at least ``floor`` blocks are |
| 9 | + tagged, and every skill has a tagged block or a ``compile_only`` entry saying |
| 10 | + why its blocks cannot run standalone and which example proves it instead. |
9 | 11 |
|
10 | 12 | Exit 1 with one ::error:: line per problem. |
11 | 13 | """ |
12 | 14 | import glob |
| 15 | +import json |
13 | 16 | import os |
14 | 17 | import re |
15 | 18 | import sys |
@@ -48,29 +51,56 @@ def check(root=ROOT, cases=None): |
48 | 51 | errors.append(f"{name}: falsify string {fals[0]!r} is not in the snippet") |
49 | 52 |
|
50 | 53 | blocks = tagged = 0 |
| 54 | + per_skill = {} |
51 | 55 | for path in sorted(glob.glob(os.path.join(root, "skills", "*", "SKILL.md"))): |
52 | 56 | rel = os.path.relpath(path, root).replace(os.sep, "/") |
53 | 57 | with open(path, encoding="utf-8") as fh: |
54 | 58 | text = fh.read() |
55 | | - tagged += len(TAG.findall(text)) |
| 59 | + n_tagged = len(TAG.findall(text)) |
| 60 | + tagged += n_tagged |
| 61 | + per_skill[os.path.basename(os.path.dirname(path))] = n_tagged |
56 | 62 | for m in FENCE.finditer(text): |
57 | 63 | blocks += 1 |
58 | 64 | start = text.count("\n", 0, m.start(1)) + 1 |
59 | 65 | try: |
60 | 66 | compile(m.group(1), rel, "exec") |
61 | 67 | except SyntaxError as e: |
62 | 68 | errors.append(f"{rel}:{start + (e.lineno or 1) - 1}: python block does not compile: {e.msg}") |
| 69 | + errors += check_ratchet(root, per_skill, tagged) |
63 | 70 | return errors, blocks, tagged |
64 | 71 |
|
65 | 72 |
|
| 73 | +def check_ratchet(root, per_skill, tagged): |
| 74 | + path = os.path.join(root, "tests", "smoke", "skill_blocks.json") |
| 75 | + if not os.path.isfile(path): |
| 76 | + return [] |
| 77 | + with open(path, encoding="utf-8") as fh: |
| 78 | + cfg = json.load(fh) |
| 79 | + floor, allow = cfg["floor"], cfg.get("compile_only", {}) |
| 80 | + errors = [] |
| 81 | + if tagged < floor: |
| 82 | + errors.append(f"only {tagged} SKILL.md blocks are tagged <!-- smoke: run -->, below the " |
| 83 | + f"floor of {floor} in tests/smoke/skill_blocks.json") |
| 84 | + for skill, n in sorted(per_skill.items()): |
| 85 | + if n == 0 and not allow.get(skill, "").strip(): |
| 86 | + errors.append(f"skills/{skill}: no block is tagged <!-- smoke: run --> and " |
| 87 | + "tests/smoke/skill_blocks.json gives no compile_only reason") |
| 88 | + if n and skill in allow: |
| 89 | + errors.append(f"skills/{skill} has a tagged block; drop its compile_only entry " |
| 90 | + "in tests/smoke/skill_blocks.json") |
| 91 | + for skill in sorted(set(allow) - set(per_skill)): |
| 92 | + errors.append(f"tests/smoke/skill_blocks.json: compile_only names unknown skill {skill!r}") |
| 93 | + return errors |
| 94 | + |
| 95 | + |
66 | 96 | def main(): |
67 | 97 | errors, blocks, tagged = check() |
68 | 98 | for e in errors: |
69 | 99 | print(f"::error::{e}") |
70 | 100 | if errors: |
71 | 101 | return 1 |
72 | 102 | print(f"{len(CASES)} snippets catalogued with falsifiers; {blocks} skill python blocks compile, " |
73 | | - f"{tagged} tagged for execution") |
| 103 | + f"{tagged} tagged for execution (ratchet in tests/smoke/skill_blocks.json)") |
74 | 104 | return 0 |
75 | 105 |
|
76 | 106 |
|
|
0 commit comments