test_spool_swap_writers_3325.py 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
  1. """Every spool assignment writer logs a mid-print swap (#3325).
  2. A spool assigned to a tray while a print runs splits the print between it and
  3. the spool the tray started with, but only if the writer told
  4. ``spool_swaps.record_assignment`` about it. A writer added later without that
  5. call would put the whole print on the new spool again, so each function that
  6. writes an assignment row must make the call, or be listed below with why not.
  7. """
  8. import ast
  9. import re
  10. from pathlib import Path
  11. BACKEND_DIR = Path(__file__).parent.parent.parent / "app"
  12. WRITES_ASSIGNMENT = re.compile(
  13. r"INSERT INTO spoolman_slot_assignments|INSERT INTO spool_assignment|SpoolAssignment\(|"
  14. r"update\(SpoolmanSlotAssignment\)|update\(SpoolAssignment\)"
  15. )
  16. EXEMPT = {
  17. ("core/database.py", "_migrate_widen_spoolman_slot_ams_id_range"): "startup migration copying existing rows",
  18. }
  19. def _writers():
  20. for path in BACKEND_DIR.rglob("*.py"):
  21. source = path.read_text()
  22. if not WRITES_ASSIGNMENT.search(source):
  23. continue
  24. for node in ast.walk(ast.parse(source)):
  25. if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
  26. continue
  27. body = ast.get_source_segment(source, node) or ""
  28. nested = [
  29. n for n in ast.walk(node) if n is not node and isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))
  30. ]
  31. if WRITES_ASSIGNMENT.search(body) and not any(
  32. WRITES_ASSIGNMENT.search(ast.get_source_segment(source, n) or "") for n in nested
  33. ):
  34. yield str(path.relative_to(BACKEND_DIR)), node.name, "record_assignment" in body
  35. def test_every_assignment_writer_logs_a_swap():
  36. writers = list(_writers())
  37. assert len(writers) >= 7 # the scan finds the writers at all
  38. missing = sorted(f"{path}:{name}" for path, name, records in writers if not records and (path, name) not in EXEMPT)
  39. assert missing == [], "call spool_swaps.record_assignment before committing:\n" + "\n".join(missing)
  40. def test_exemptions_still_exist():
  41. found = {(path, name) for path, name, _ in _writers()}
  42. assert set(EXEMPT) <= found