Files
cadSet/SimpleCADAPI/test/test_tracking_data_models.py
T

332 lines
12 KiB
Python

"""Tests for tracking data models: TopoRef, TopoEvent, TopoDelta, OperationNode, OperationGraph."""
import unittest
from simplecadapi.topology import (
TopoKind,
TopoEvent,
TopoRef,
TopoEntry,
TopoRoleEntry,
TopoDelta,
OperationNode,
OperationGraph,
topo_delta_from_dict,
topo_delta_to_dict,
)
class TestTopoRef(unittest.TestCase):
def test_fields(self):
ref = TopoRef("g1", "n1", 0, TopoKind.FACE, "f42")
self.assertEqual(ref.graph_id, "g1")
self.assertEqual(ref.node_id, "n1")
self.assertEqual(ref.output_slot, 0)
self.assertEqual(ref.kind, TopoKind.FACE)
self.assertEqual(ref.topo_id, "f42")
def test_equality(self):
a = TopoRef("g1", "n1", 0, TopoKind.FACE, "f1")
b = TopoRef("g1", "n1", 0, TopoKind.FACE, "f1")
self.assertEqual(a, b)
def test_inequality_different_topo_id(self):
a = TopoRef("g1", "n1", 0, TopoKind.FACE, "f1")
b = TopoRef("g1", "n1", 0, TopoKind.FACE, "f2")
self.assertNotEqual(a, b)
def test_inequality_different_node(self):
a = TopoRef("g1", "n1", 0, TopoKind.FACE, "f1")
b = TopoRef("g1", "n2", 0, TopoKind.FACE, "f1")
self.assertNotEqual(a, b)
def test_hashable(self):
a = TopoRef("g1", "n1", 0, TopoKind.FACE, "f1")
b = TopoRef("g1", "n1", 0, TopoKind.FACE, "f1")
s = {a, b}
self.assertEqual(len(s), 1)
def test_hash_unequal(self):
a = TopoRef("g1", "n1", 0, TopoKind.FACE, "f1")
b = TopoRef("g1", "n1", 0, TopoKind.FACE, "f2")
s = {a, b}
self.assertEqual(len(s), 2)
class TestTopoEntry(unittest.TestCase):
def test_preserved(self):
ref = TopoRef("g1", "n1", 0, TopoKind.FACE, "f1")
entry = TopoEntry(ref, TopoEvent.PRESERVED, origin_role="body")
self.assertEqual(entry.event, TopoEvent.PRESERVED)
self.assertEqual(entry.origin_role, "body")
self.assertEqual(entry.parent_refs, ())
def test_generated_with_parents(self):
ref = TopoRef("g1", "n2", 0, TopoKind.FACE, "f_new")
parent = TopoRef("g1", "n1", 0, TopoKind.FACE, "f1")
entry = TopoEntry(
ref, TopoEvent.GENERATED, origin_role="body", parent_refs=(parent,)
)
self.assertEqual(entry.event, TopoEvent.GENERATED)
self.assertEqual(len(entry.parent_refs), 1)
self.assertEqual(entry.parent_refs[0], parent)
def test_deleted(self):
ref = TopoRef("g1", "n1", 0, TopoKind.FACE, "f_gone")
entry = TopoEntry(ref, TopoEvent.DELETED)
self.assertEqual(entry.event, TopoEvent.DELETED)
self.assertIsNone(entry.origin_role)
class TestTopoDelta(unittest.TestCase):
def test_preserved_and_generated(self):
preserved_ref = TopoRef("g1", "n2", 0, TopoKind.FACE, "f_keep")
generated_ref = TopoRef("g1", "n2", 0, TopoKind.FACE, "f_new")
delta = TopoDelta(
preserved=[preserved_ref],
generated=[generated_ref],
)
self.assertEqual(len(delta.preserved), 1)
self.assertEqual(len(delta.modified), 0)
self.assertEqual(len(delta.generated), 1)
self.assertEqual(len(delta.deleted), 0)
def test_modified_and_deleted(self):
mod_ref = TopoRef("g1", "n2", 0, TopoKind.FACE, "f_mod")
del_ref = TopoRef("g1", "n1", 0, TopoKind.FACE, "f_del")
delta = TopoDelta(
modified=[mod_ref],
deleted=[del_ref],
)
self.assertEqual(len(delta.modified), 1)
self.assertEqual(len(delta.deleted), 1)
def test_section_edges(self):
edge_ref = TopoRef("g1", "n2", 0, TopoKind.EDGE, "e_section")
delta = TopoDelta(section_edges=[edge_ref])
self.assertEqual(len(delta.section_edges), 1)
self.assertEqual(delta.section_edges[0].kind, TopoKind.EDGE)
def test_empty(self):
delta = TopoDelta()
self.assertEqual(delta.preserved, ())
self.assertEqual(delta.modified, ())
self.assertEqual(delta.generated, ())
self.assertEqual(delta.deleted, ())
self.assertEqual(delta.section_edges, ())
def test_entries_are_canonical_and_preserve_output_and_source_kind(self):
source_ref = TopoRef("g1", "n1", 0, TopoKind.EDGE, "e_source")
output_ref = TopoRef("g1", "n2", 0, TopoKind.FACE, "f_output")
entry = TopoEntry(
ref=output_ref,
event=TopoEvent.GENERATED,
origin_role="profile",
parent_refs=(source_ref,),
metadata={
"derivation": "boundary",
"coverage": "complete",
"status": "proven",
"evidence_kind": "kernel_history",
"source_kind": "EDGE",
},
)
delta = TopoDelta(generated=(output_ref,), entries=(entry,))
self.assertEqual(delta.entries, (entry,))
self.assertEqual(entry.ref.kind, TopoKind.FACE)
self.assertEqual(entry.parent_refs[0].kind, TopoKind.EDGE)
self.assertEqual(entry.metadata["source_kind"], "EDGE")
self.assertEqual(
topo_delta_from_dict(topo_delta_to_dict(delta)).entries,
delta.entries,
)
def test_output_roles_roundtrip_independently_from_change_events(self):
source_ref = TopoRef("g1", "n1", 0, TopoKind.EDGE, "e_source")
output_ref = TopoRef("g1", "n2", 0, TopoKind.FACE, "f_output")
role = TopoRoleEntry(
ref=output_ref,
role="Extrusion.Side",
origin_role="profile",
parent_refs=(source_ref,),
metadata={
"coverage": "complete",
"status": "proven",
"evidence_method": "Generated",
},
)
delta = TopoDelta(roles=(role,))
restored = topo_delta_from_dict(topo_delta_to_dict(delta))
self.assertEqual(role.role, "extrusion.side")
self.assertEqual(restored.roles, (role,))
self.assertEqual(restored.entries, ())
class TestOperationNode(unittest.TestCase):
def test_creation(self):
node = OperationNode(
node_id="n1",
op="make_line_redge",
params={"start": (0, 0, 0), "end": (10, 0, 0)},
)
self.assertEqual(node.node_id, "n1")
self.assertEqual(node.op, "make_line_redge")
self.assertEqual(node.params["end"], (10, 0, 0))
self.assertEqual(node.inputs, ())
self.assertIsNone(node.topo_delta)
def test_with_inputs(self):
node1 = OperationNode("n1", "make_line_redge", {})
node2 = OperationNode("n2", "make_wire_from_edges_rwire", {}, inputs=(node1,))
self.assertEqual(len(node2.inputs), 1)
self.assertEqual(node2.inputs[0].node_id, "n1")
def test_with_topo_delta(self):
delta = TopoDelta(generated=[TopoRef("g1", "n1", 0, TopoKind.FACE, "f_new")])
node = OperationNode("n1", "make_extrude_rsolid", {}, topo_delta=delta)
self.assertIsNotNone(node.topo_delta)
self.assertEqual(len(node.topo_delta.generated), 1)
def test_output_count(self):
node = OperationNode("n1", "make_translate_rshape", {}, output_count=2)
self.assertEqual(node.output_count, 2)
class TestOperationGraph(unittest.TestCase):
def test_empty_graph(self):
graph = OperationGraph()
self.assertEqual(graph.node_count, 0)
self.assertEqual(graph.edge_count, 0)
self.assertEqual(graph.nodes, [])
def test_add_primitive_node(self):
graph = OperationGraph()
node = graph.add_node(
"make_line_redge", {"start": (0, 0, 0), "end": (10, 0, 0)}
)
self.assertEqual(graph.node_count, 1)
self.assertEqual(node.op, "make_line_redge")
self.assertEqual(node.params["end"], (10, 0, 0))
def test_add_node_with_inputs(self):
graph = OperationGraph()
box_node = graph.add_node(
"make_line_redge", {"start": (0, 0, 0), "end": (10, 0, 0)}
)
cyl_node = graph.add_node(
"make_line_redge", {"start": (10, 0, 0), "end": (10, 10, 0)}
)
cut_node = graph.add_node(
"make_wire_from_edges_rwire", {"edge_count": 2}, inputs=[box_node, cyl_node]
)
self.assertEqual(graph.node_count, 3)
self.assertEqual(graph.edge_count, 2)
self.assertEqual(len(cut_node.inputs), 2)
def test_get_node(self):
graph = OperationGraph()
node = graph.add_node("make_line_redge", {})
found = graph.get_node(node.node_id)
self.assertEqual(found, node)
def test_get_node_missing(self):
graph = OperationGraph()
self.assertIsNone(graph.get_node("nonexistent"))
def test_unique_ids(self):
graph = OperationGraph()
n1 = graph.add_node("make_line_redge", {})
n2 = graph.add_node("make_line_redge", {})
self.assertNotEqual(n1.node_id, n2.node_id)
def test_edges_are_set(self):
graph = OperationGraph()
box_node = graph.add_node("make_line_redge", {})
cyl_node = graph.add_node("make_line_redge", {})
graph.add_node("make_wire_from_edges_rwire", {}, inputs=[box_node, cyl_node])
edges = graph.edges
# Should not have duplicate edges
self.assertEqual(len(edges), len(set(edges)))
def test_upstream_nodes(self):
graph = OperationGraph()
n1 = graph.add_node("make_line_redge", {})
n2 = graph.add_node("make_line_redge", {})
n3 = graph.add_node("make_wire_from_edges_rwire", {}, inputs=[n1, n2])
upstream = graph.upstream_nodes(n3.node_id)
self.assertEqual(len(upstream), 2)
self.assertIn(n1.node_id, upstream)
self.assertIn(n2.node_id, upstream)
def test_upstream_empty(self):
graph = OperationGraph()
n1 = graph.add_node("make_line_redge", {})
self.assertEqual(graph.upstream_nodes(n1.node_id), [])
def test_downstream_nodes(self):
graph = OperationGraph()
n1 = graph.add_node("make_line_redge", {})
n2 = graph.add_node("make_line_redge", {})
n3 = graph.add_node("make_wire_from_edges_rwire", {}, inputs=[n1, n2])
downstream = graph.downstream_nodes(n1.node_id)
self.assertEqual(len(downstream), 1)
self.assertEqual(downstream[0], n3.node_id)
def test_is_dag_valid(self):
graph = OperationGraph()
n1 = graph.add_node("make_line_redge", {})
n2 = graph.add_node("make_line_redge", {})
n3 = graph.add_node("make_wire_from_edges_rwire", {}, inputs=[n1, n2])
self.assertTrue(graph.is_dag())
def test_is_dag_empty(self):
graph = OperationGraph()
self.assertTrue(graph.is_dag())
def test_root_nodes(self):
graph = OperationGraph()
n1 = graph.add_node("make_line_redge", {})
n2 = graph.add_node("make_line_redge", {})
n3 = graph.add_node("make_wire_from_edges_rwire", {}, inputs=[n1, n2])
roots = graph.root_nodes()
self.assertEqual(len(roots), 2)
root_ids = [r.node_id for r in roots]
self.assertIn(n1.node_id, root_ids)
self.assertIn(n2.node_id, root_ids)
def test_leaf_nodes(self):
graph = OperationGraph()
n1 = graph.add_node("make_line_redge", {})
n2 = graph.add_node("make_line_redge", {})
n3 = graph.add_node("make_wire_from_edges_rwire", {}, inputs=[n1, n2])
leaves = graph.leaf_nodes()
self.assertEqual(len(leaves), 1)
self.assertEqual(leaves[0].node_id, n3.node_id)
def test_topological_order(self):
graph = OperationGraph()
n1 = graph.add_node("make_line_redge", {})
n2 = graph.add_node("make_line_redge", {})
n3 = graph.add_node("make_wire_from_edges_rwire", {}, inputs=[n1, n2])
n4 = graph.add_node("make_face_from_wire_rface", {}, inputs=[n3])
topo = graph.topological_order()
self.assertEqual(len(topo), 4)
# n1 and n2 before n3, n3 before n4
idx = {node.node_id: i for i, node in enumerate(topo)}
self.assertLess(idx[n1.node_id], idx[n3.node_id])
self.assertLess(idx[n2.node_id], idx[n3.node_id])
self.assertLess(idx[n3.node_id], idx[n4.node_id])
def test_topological_order_single(self):
graph = OperationGraph()
n1 = graph.add_node("make_line_redge", {})
topo = graph.topological_order()
self.assertEqual(len(topo), 1)
self.assertEqual(topo[0].node_id, n1.node_id)
if __name__ == "__main__":
unittest.main()