feat: integrate SimpleCADAPI 2.0.2 CAD workflows
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
"""Structural checks for the example model/session contract."""
|
||||
|
||||
import ast
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
EXAMPLES = ROOT / "examples"
|
||||
|
||||
|
||||
def _model_files() -> tuple[Path, ...]:
|
||||
return (
|
||||
EXAMPLES / "04_dimension_tolerance_chain.py",
|
||||
EXAMPLES / "08_constrained_sketch.py",
|
||||
EXAMPLES / "09_naca0016_blade_freecad.py",
|
||||
EXAMPLES / "10_part_assembly.py",
|
||||
EXAMPLES / "16_compact_two_stage_planetary_reducer" / "main.py",
|
||||
EXAMPLES / "20_integrated_bldc_joint_actuator" / "main.py",
|
||||
)
|
||||
|
||||
|
||||
def _source_files() -> tuple[Path, ...]:
|
||||
return tuple(
|
||||
path
|
||||
for path in EXAMPLES.rglob("*.py")
|
||||
if "out" not in path.relative_to(EXAMPLES).parts
|
||||
)
|
||||
|
||||
|
||||
def _is_decorator(node: ast.expr, name: str) -> bool:
|
||||
if isinstance(node, ast.Call):
|
||||
node = node.func
|
||||
return (
|
||||
isinstance(node, ast.Attribute)
|
||||
and isinstance(node.value, ast.Name)
|
||||
and node.value.id == "scad"
|
||||
and node.attr == name
|
||||
) or (isinstance(node, ast.Name) and node.id == name)
|
||||
|
||||
|
||||
class TestExampleModelContract(unittest.TestCase):
|
||||
def test_example_10_output_dir_is_anchored_to_the_example_file(self):
|
||||
path = EXAMPLES / "10_part_assembly.py"
|
||||
spec = importlib.util.spec_from_file_location("example_10_path_contract", path)
|
||||
self.assertIsNotNone(spec)
|
||||
assert spec is not None and spec.loader is not None
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
self.assertTrue(module.OUT_DIR.is_absolute())
|
||||
self.assertEqual(
|
||||
module.OUT_DIR,
|
||||
EXAMPLES / "out" / "hydraulic_rod_assembly",
|
||||
)
|
||||
|
||||
def test_each_current_model_entry_has_one_explicit_result(self):
|
||||
for path in _model_files():
|
||||
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
||||
model_functions = [
|
||||
node
|
||||
for node in tree.body
|
||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
||||
and any(_is_decorator(decorator, "model") for decorator in node.decorator_list)
|
||||
]
|
||||
self.assertEqual(len(model_functions), 1, path)
|
||||
self.assertFalse(
|
||||
isinstance(model_functions[0], ast.AsyncFunctionDef),
|
||||
f"async model entry is unsupported: {path}",
|
||||
)
|
||||
capture_calls = [
|
||||
node
|
||||
for node in ast.walk(tree)
|
||||
if isinstance(node, ast.Call)
|
||||
and isinstance(node.func, ast.Attribute)
|
||||
and isinstance(node.func.value, ast.Name)
|
||||
and node.func.value.id == "scad"
|
||||
and node.func.attr == "capture_result"
|
||||
]
|
||||
self.assertEqual(len(capture_calls), 1, path)
|
||||
|
||||
def test_examples_do_not_own_manual_sessions_or_serialization(self):
|
||||
for path in _source_files():
|
||||
source = path.read_text(encoding="utf-8")
|
||||
self.assertNotIn("GraphSession(", source, path)
|
||||
self.assertNotIn("export_model_json(", source, path)
|
||||
self.assertNotIn("export_session_json(", source, path)
|
||||
|
||||
def test_make_builders_require_the_active_session(self):
|
||||
for path in _source_files():
|
||||
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
continue
|
||||
if not (node.name.startswith("make_") or node.name.startswith("_make_")):
|
||||
continue
|
||||
decorators = node.decorator_list
|
||||
has_session_contract = any(
|
||||
_is_decorator(decorator, "requires_session")
|
||||
or _is_decorator(decorator, "model")
|
||||
for decorator in decorators
|
||||
)
|
||||
self.assertTrue(has_session_contract, f"{path}:{node.lineno}:{node.name}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user