feat(categorizer): implement auto_classify using AST scan (no regex)
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
|
import ast
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
class FixtureClass(str, Enum):
|
class FixtureClass(str, Enum):
|
||||||
UNIT = "unit"
|
UNIT = "unit"
|
||||||
@@ -26,3 +28,50 @@ class CategoryRecord:
|
|||||||
test_order: dict[str, int] = field(default_factory=dict)
|
test_order: dict[str, int] = field(default_factory=dict)
|
||||||
source: str = "auto"
|
source: str = "auto"
|
||||||
warnings: list[str] = field(default_factory=list)
|
warnings: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
_OPT_IN_PREFIXES: tuple[str, ...] = ("test_clean_install", "test_docker_build")
|
||||||
|
_PERF_KEYWORDS: tuple[str, ...] = ("perf", "stress", "phase_3_final", "phase_4_stress")
|
||||||
|
_FIXTURE_ARGS: dict[FixtureClass, frozenset[str]] = {
|
||||||
|
FixtureClass.LIVE_GUI: frozenset({"live_gui"}),
|
||||||
|
FixtureClass.MOCK_APP: frozenset({"mock_app", "app_instance"}),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _fixture_args_used(source: str) -> set[str]:
|
||||||
|
try:
|
||||||
|
tree = ast.parse(source)
|
||||||
|
except SyntaxError:
|
||||||
|
return set()
|
||||||
|
found: set[str] = set()
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||||
|
found.update(a.arg for a in node.args.args)
|
||||||
|
found.update(a.arg for a in node.args.kwonlyargs)
|
||||||
|
found.update(a.arg for a in node.args.posonlyargs)
|
||||||
|
return found
|
||||||
|
|
||||||
|
def _classify_fixture_class(path: Path, source: str) -> FixtureClass:
|
||||||
|
name = path.name
|
||||||
|
for prefix in _OPT_IN_PREFIXES:
|
||||||
|
if name.startswith(prefix):
|
||||||
|
return FixtureClass.OPT_IN
|
||||||
|
args = _fixture_args_used(source)
|
||||||
|
for fc, fixture_names in _FIXTURE_ARGS.items():
|
||||||
|
if args & fixture_names:
|
||||||
|
return fc
|
||||||
|
lowered = name.lower()
|
||||||
|
for kw in _PERF_KEYWORDS:
|
||||||
|
if kw in lowered:
|
||||||
|
return FixtureClass.PERFORMANCE
|
||||||
|
return FixtureClass.UNIT
|
||||||
|
|
||||||
|
def auto_classify(path: Path, durations: dict[str, float] | None = None) -> CategoryRecord:
|
||||||
|
source = path.read_text(encoding="utf-8", errors="replace")
|
||||||
|
fixture_class = _classify_fixture_class(path, source)
|
||||||
|
return CategoryRecord(
|
||||||
|
filename=path.name,
|
||||||
|
fixture_class=fixture_class,
|
||||||
|
subsystems=[],
|
||||||
|
speed=Speed.MEDIUM,
|
||||||
|
batch_group="",
|
||||||
|
source="auto",
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user