zcbot/tests/test_ansys_adapter.py

138 lines
4.9 KiB
Python

from __future__ import annotations
import importlib.util
import json
import subprocess
import sys
import unittest
from pathlib import Path
from uuid import uuid4
from core.software_contracts import SoftwareContractError, get_contract
ROOT = Path(__file__).resolve().parents[1]
ADAPTER = (
ROOT / "windows-node" / "adapters" / "ansys.mechanical.static_structural@v1"
)
WORKER_PATH = ADAPTER / "worker.py"
def _load_worker():
spec = importlib.util.spec_from_file_location("zcbot_ansys_worker", WORKER_PATH)
if spec is None or spec.loader is None:
raise RuntimeError("could not load ANSYS worker")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def _request() -> dict:
return {
"schema_version": 1,
"inputs": [{"key": "geometry", "artifact_id": str(uuid4())}],
"operation": {
"analysis": {
"type": "static_structural",
"title": "coupon static analysis",
"unit_system": "mm_kg_s",
"geometry": {"input": "geometry"},
"material": "structural_steel",
"mesh": {"global_size": 2.0},
"boundary_conditions": [
{"type": "fixed_support", "named_selection": "fixed_face"}
],
"loads": [
{
"type": "force",
"named_selection": "load_face",
"components": [0, 0, -1000],
}
],
"results": ["total_deformation", "equivalent_stress"],
}
},
"outputs": [
{"key": "project", "format": "mechdat"},
{"key": "stress_image", "format": "png"},
],
}
class AnsysContractTests(unittest.TestCase):
def test_contract_is_not_enrolled_by_default_and_normalizes_static_job(self) -> None:
contract = get_contract("ansys.mechanical.static_structural@v1")
normalized, digest = contract.normalize_request(_request())
self.assertFalse(contract.default_enrollment)
self.assertEqual(contract.feature(normalized), "static_structural")
self.assertEqual(contract.required_adapter_version(normalized), "0.1.0")
self.assertEqual(len(digest), 64)
self.assertEqual(
set(contract.expected_outputs(normalized)),
{
"project",
"stress_image",
"summary",
"result_table",
"analysis_spec",
"provenance",
},
)
def test_contract_rejects_code_paths_and_unsupported_analysis(self) -> None:
contract = get_contract("ansys.mechanical.static_structural@v1")
for key, value in (
("script", "print('unsafe')"),
("apdl", "/SOLU"),
("path", r"C:\\private\\model.step"),
):
request = _request()
request[key] = value
with self.assertRaises(SoftwareContractError):
contract.normalize_request(request)
request = _request()
request["operation"]["analysis"]["type"] = "modal"
with self.assertRaises(SoftwareContractError):
contract.normalize_request(request)
class AnsysWorkerTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.worker = _load_worker()
def test_worker_semantics_reject_zero_force_and_reused_target(self) -> None:
request = _request()
request["operation"]["analysis"]["loads"][0]["components"] = [0, 0, 0]
with self.assertRaisesRegex(ValueError, "FORCE_VECTOR_MUST_BE_NONZERO"):
self.worker._validate_semantics(request)
request = _request()
request["operation"]["analysis"]["loads"][0]["named_selection"] = "FIXED_FACE"
with self.assertRaisesRegex(ValueError, "NAMED_SELECTION_TARGETS_MUST_BE_UNIQUE"):
self.worker._validate_semantics(request)
def test_probe_is_json_and_does_not_claim_ready_off_windows(self) -> None:
completed = subprocess.run(
[sys.executable, str(WORKER_PATH), "--probe"],
check=True,
capture_output=True,
text=True,
)
value = json.loads(completed.stdout)
self.assertEqual(value["adapter_version"], "0.1.0")
self.assertEqual(value["software"], "ANSYS Mechanical")
if sys.platform != "win32":
self.assertEqual(value["health"], "unavailable")
def test_execution_is_guarded_until_real_license_acceptance(self) -> None:
source = WORKER_PATH.read_text(encoding="utf-8")
self.assertIn('EXECUTION_GATE = "ZCBOT_ANSYS_242_VALIDATED"', source)
self.assertIn("ANSYS_242_REAL_LICENSE_ACCEPTANCE_REQUIRED", source)
for forbidden in ("subprocess", "eval(", "exec(", "os.system"):
self.assertNotIn(forbidden, source)
if __name__ == "__main__":
unittest.main()