1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages
DocForge2/tests/test_python_logic.py

126 lines
3.9 KiB
Python

from __future__ import annotations
import unittest
from docforge.python_logic import PythonLogicOwner, analyze_python_source
class PythonLogicTests(unittest.TestCase):
def projection(self, source: str, qualified_name: str, line: int = 1):
return analyze_python_source(
source,
source_id="source.example",
owners=(PythonLogicOwner("py.symbol.example", qualified_name, line),),
filename="example.py",
)[0]
def test_boolean_short_circuit_branches_and_terminals(self) -> None:
projection = self.projection(
"""\
def decide(enabled, cached, stale):
if enabled and (cached is None or stale):
return "fetch"
raise RuntimeError("disabled")
""",
"decide",
)
kinds = [node.kind for node in projection.nodes]
labels = [node.label for node in projection.nodes]
edge_labels = [edge.label for edge in projection.edges]
self.assertEqual(1, kinds.count("entry"))
self.assertEqual(1, kinds.count("exit"))
self.assertEqual(3, kinds.count("condition"))
self.assertIn("enabled", labels)
self.assertIn("cached is None", labels)
self.assertIn("stale", labels)
self.assertIn("TRUE", edge_labels)
self.assertIn("FALSE", edge_labels)
self.assertIn("RETURN", edge_labels)
self.assertIn("RAISE", edge_labels)
def test_loops_match_try_and_control_transfers_are_explicit(self) -> None:
projection = self.projection(
"""\
def process(items, mode):
for item in items:
if item.skip:
continue
if item.stop:
break
consume(item)
else:
finish()
match mode:
case "safe":
value = safe()
case _:
value = fallback()
try:
return value
except ValueError:
raise
finally:
cleanup()
""",
"process",
)
kinds = {node.kind for node in projection.nodes}
relations = {edge.relation for edge in projection.edges}
labels = {edge.label for edge in projection.edges}
self.assertTrue(
{"loop", "continue", "break", "case", "try", "except", "finally"}.issubset(kinds)
)
self.assertTrue(
{"loop", "continue", "break", "case", "exception", "return", "raise"}.issubset(
relations
)
)
self.assertIn("EXHAUSTED", labels)
self.assertIn("NEXT ITEM", labels)
self.assertIn("NEXT CASE", labels)
def test_class_methods_nested_functions_and_async_functions_use_explicit_owners(self) -> None:
source = """\
class Worker:
async def run(self):
async with self.session():
await self.step()
if self.enabled:
def nested():
return True
return nested()
"""
projections = analyze_python_source(
source,
source_id="source.worker",
owners=(
PythonLogicOwner("py.symbol.worker.run", "Worker.run", 2),
PythonLogicOwner("py.symbol.worker.nested", "Worker.run.nested", 7),
),
filename="worker.py",
)
self.assertEqual(
("py.symbol.worker.nested", "py.symbol.worker.run"),
tuple(projection.owner_node_id for projection in projections),
)
run = next(item for item in projections if item.owner_node_id.endswith(".run"))
self.assertIn("action", {node.kind for node in run.nodes})
self.assertIn("call", {node.kind for node in run.nodes})
def test_projection_is_deterministic(self) -> None:
source = """\
def choose(first, second):
return first if first else second
"""
first = self.projection(source, "choose")
second = self.projection(source, "choose")
self.assertEqual(first, second)
if __name__ == "__main__":
unittest.main()