117 lines
4.3 KiB
Python
117 lines
4.3 KiB
Python
"""Regression tests for operation source provenance."""
|
|
|
|
import json
|
|
|
|
import simplecadapi as scad
|
|
from simplecadapi.serializer import export_graph_json
|
|
from simplecadapi.source_mapping import canonical_source_payload
|
|
from simplecadapi.topology import OperationGraph
|
|
|
|
|
|
def test_source_mapping_records_call_span_and_assignment_target():
|
|
with scad.GraphSession(graph_id="source_mapping") as session:
|
|
width = 10.0
|
|
body = scad.make_box_rsolid(
|
|
width=width,
|
|
height=2.0,
|
|
depth=3.0,
|
|
)
|
|
|
|
node = session.graph.nodes[0]
|
|
assert node.source is not None
|
|
assert node.source["path"].endswith("test_source_mapping.py")
|
|
assert node.source["path_kind"] == "project_relative"
|
|
assert node.source["line"] <= node.source["end_line"]
|
|
assert node.source["call_text"].startswith("scad.make_box_rsolid")
|
|
assert node.source["assignment_targets"] == ["body"]
|
|
assert "input_bindings" not in node.source
|
|
|
|
|
|
def test_source_mapping_disambiguates_same_line_calls_and_round_trips():
|
|
with scad.GraphSession(graph_id="source_mapping_same_line") as session:
|
|
first = scad.make_box_rsolid(1.0, 1.0, 1.0); second = scad.make_box_rsolid(2.0, 2.0, 2.0)
|
|
|
|
nodes = session.graph.topological_order()
|
|
assert len(nodes) == 2
|
|
assert nodes[0].source is not None
|
|
assert nodes[1].source is not None
|
|
assert nodes[0].source["assignment_targets"] == ["first"]
|
|
assert nodes[1].source["assignment_targets"] == ["second"]
|
|
assert nodes[0].source["column"] < nodes[1].source["column"]
|
|
|
|
payload = json.loads(export_graph_json(session.graph))
|
|
assert payload["capabilities"]["source_mapping"] is True
|
|
assert "local_path" not in payload["nodes"][0]["source"]
|
|
restored = OperationGraph.from_dict(payload)
|
|
assert restored.nodes[0].source == canonical_source_payload(nodes[0].source)
|
|
assert restored.nodes[1].source == canonical_source_payload(nodes[1].source)
|
|
|
|
|
|
def test_source_mapping_does_not_claim_nested_call_as_assignment_output():
|
|
with scad.GraphSession(graph_id="source_mapping_nested") as session:
|
|
body = scad.make_box_rsolid(1.0, 1.0, 1.0)
|
|
moved = scad.translate_shape(
|
|
shape=body,
|
|
vector=(1.0, 0.0, 0.0),
|
|
)
|
|
|
|
translate_node = session.graph.topological_order()[1]
|
|
assert translate_node.source is not None
|
|
assert translate_node.source["assignment_targets"] == ["moved"]
|
|
|
|
|
|
def test_source_mapping_records_complex_targets_and_return():
|
|
class Holder:
|
|
pass
|
|
|
|
holder = Holder()
|
|
with scad.GraphSession(graph_id="source_mapping_targets") as session:
|
|
holder.body = scad.make_box_rsolid(1.0, 1.0, 1.0)
|
|
items = [None]
|
|
index = 0
|
|
items[index] = scad.make_box_rsolid(2.0, 2.0, 2.0)
|
|
|
|
first, second = session.graph.topological_order()
|
|
assert first.source is not None
|
|
assert first.source["assignment_targets"] == ["holder.body"]
|
|
assert second.source is not None
|
|
assert second.source["assignment_targets"] == ["items[index]"]
|
|
|
|
|
|
def test_source_mapping_records_return_and_requires_session_builder():
|
|
@scad.requires_session
|
|
def build_box():
|
|
return scad.make_box_rsolid(1.0, 1.0, 1.0)
|
|
|
|
with scad.GraphSession(graph_id="source_mapping_return") as session:
|
|
result = build_box()
|
|
|
|
node = session.graph.nodes[0]
|
|
assert result.get_metadata("graph")["node_id"] == node.node_id
|
|
assert node.source is not None
|
|
assert node.source["call_text"] == "scad.make_box_rsolid(1.0, 1.0, 1.0)"
|
|
assert node.source["assignment_targets"] == []
|
|
|
|
|
|
def test_macro_nodes_share_the_user_callsite_and_assignment_target():
|
|
with scad.GraphSession(graph_id="source_mapping_macro") as session:
|
|
profile = scad.make_rectangle_rface(width=2.0, height=1.0)
|
|
|
|
nodes = session.graph.topological_order()
|
|
assert len(nodes) > 1
|
|
assert profile.get_metadata("graph")["node_id"] == nodes[-1].node_id
|
|
assert all(node.source is not None for node in nodes)
|
|
|
|
callsite_ids = {node.source["callsite_id"] for node in nodes if node.source}
|
|
call_texts = {node.source["call_text"] for node in nodes if node.source}
|
|
targets = {
|
|
tuple(node.source["assignment_targets"])
|
|
for node in nodes
|
|
if node.source
|
|
}
|
|
assert len(callsite_ids) == 1
|
|
assert call_texts == {
|
|
"scad.make_rectangle_rface(width=2.0, height=1.0)"
|
|
}
|
|
assert targets == {("profile",)}
|