mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-24 04:39:06 -05:00
fix: keep release checks importable on python 3.9
check_release_assets.py built a tuple[..., int | None] alias at import, a TypeError on the 3.9 floor tools.md states, and check_release.py imports it. A test reads every script for unions and match statements evaluated at runtime.
This commit is contained in:
1 parent
0242dc77ee
commit
9bb3dfbd1b
2 files changed
+53
-1
No files matched your search
@@ -22,6 +22,7 @@ import re
|
||||
import subprocess
|
||||
import sys
|
||||
from collections.abc import Iterable
|
||||
from typing import Optional, Tuple
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from common import composition_tier, load_database
|
||||
@@ -32,7 +33,8 @@ from largefiles import (
|
||||
registered_paths,
|
||||
)
|
||||
|
||||
Finding = tuple[str, str, int, int | None]
|
||||
# Evaluated at import, where `int | None` needs Python 3.10.
|
||||
Finding = Tuple[str, str, int, Optional[int]]
|
||||
|
||||
|
||||
def expected_assets(db: dict, gitignore_text: str) -> dict[str, int]:
|
||||
|
||||
@@ -416,5 +416,55 @@ class PythonFloorOnRetroPie(unittest.TestCase):
|
||||
self.assertIn("3.7.3", changing)
|
||||
|
||||
|
||||
class ToolsRunOnTheStatedFloor(unittest.TestCase):
|
||||
"""tools.md and testing-guide.md state Python 3.9 for every script, and
|
||||
check_release_assets.py built `int | None` at import: a TypeError on 3.9
|
||||
before any argument was read, in check_release.py too. CI runs 3.12 only,
|
||||
so this test reads the source for what 3.9 cannot evaluate."""
|
||||
|
||||
TYPES = {"int", "str", "float", "bytes", "bool", "dict", "list", "tuple", "set", "type"}
|
||||
|
||||
def _type_like(self, node: ast.AST) -> bool:
|
||||
if isinstance(node, ast.Constant) and node.value is None:
|
||||
return True
|
||||
if isinstance(node, ast.Subscript):
|
||||
node = node.value
|
||||
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
|
||||
return self._type_like(node.left) or self._type_like(node.right)
|
||||
return isinstance(node, ast.Name) and node.id in self.TYPES
|
||||
|
||||
def _runtime_unions(self, tree: ast.AST) -> list[int]:
|
||||
annotations = set()
|
||||
for node in ast.walk(tree):
|
||||
for field in ("annotation", "returns"):
|
||||
value = getattr(node, field, None)
|
||||
if value is not None:
|
||||
annotations.update(id(n) for n in ast.walk(value))
|
||||
return [
|
||||
node.lineno for node in ast.walk(tree)
|
||||
if id(node) not in annotations
|
||||
and (isinstance(node, ast.Match)
|
||||
or isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr)
|
||||
and (self._type_like(node.left) or self._type_like(node.right)))
|
||||
]
|
||||
|
||||
def test_pages_state_the_floor(self):
|
||||
self.assertIn("Python 3.9 or newer", _page("tools.md"))
|
||||
self.assertIn("Python 3.9 or newer", _page("testing-guide.md"))
|
||||
|
||||
def test_no_script_evaluates_310_syntax(self):
|
||||
offenders = [
|
||||
f"{path.relative_to(ROOT)}:{line}"
|
||||
for path in sorted((ROOT / "scripts").rglob("*.py"))
|
||||
for line in self._runtime_unions(ast.parse(path.read_text(encoding="utf-8")))
|
||||
]
|
||||
self.assertEqual(offenders, [], "\n".join(offenders))
|
||||
|
||||
def test_the_check_catches_the_alias(self):
|
||||
self.assertTrue(self._runtime_unions(ast.parse("F = tuple[str, int | None]")))
|
||||
self.assertFalse(self._runtime_unions(ast.parse("def f(x: int | None) -> str | None: ...")))
|
||||
self.assertFalse(self._runtime_unions(ast.parse("mask = flags | other")))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user