"""P5-M5: unit tests for simulation tool adapters (maxwell / jmag mock). Covers: registry (register/get/listed), maxwell mock full pipeline, jmag mock full pipeline, tool differentiation (same params -> different metrics), set_parameter read-back verification, boundary (empty params / unknown tool / run before connect), anomaly (real-mode RuntimeError), and executor dynamic adapter import by tool name. All source is ASCII only. Run: python scripts/test_adapters.py exit 0 = PASS. """ import os import sys import unittest _ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) _SRC = os.path.join(_ROOT, "src") if _SRC not in sys.path: sys.path.insert(0, _SRC) from afmcore.adapters import ( # noqa: E402 SimulationAdapter, get_adapter, register_adapter, registered_tools, ) import afmcore.adapters.maxwell # noqa: E402,F401 (registers "maxwell") import afmcore.adapters.jmag # noqa: E402,F401 (registers "jmag") class TestAdapterRegistry(unittest.TestCase): def test_maxwell_registered(self): self.assertIn("maxwell", registered_tools()) def test_jmag_registered(self): self.assertIn("jmag", registered_tools()) def test_get_adapter_returns_instance(self): a = get_adapter("maxwell", model_path="/tmp/x.mot", mock=True) self.assertIsInstance(a, SimulationAdapter) self.assertEqual(a.tool_name, "maxwell") def test_unknown_tool_raises_keyerror(self): with self.assertRaises(KeyError): get_adapter("nonexistent_tool_xyz") def test_register_adapter_rejects_non_string(self): with self.assertRaises(ValueError): register_adapter("", SimulationAdapter) class TestMaxwellMockPipeline(unittest.TestCase): def setUp(self): self.adapter = get_adapter( "maxwell", model_path="/tmp/test.mot", output_dir="output/p5m5_test", mock=True, ) def test_connect_disconnect(self): self.adapter.connect() self.assertTrue(self.adapter._connected) self.adapter.disconnect() self.assertFalse(self.adapter._connected) def test_load_model_clears_params(self): self.adapter._params["old"] = 1.0 self.adapter.load_model("/tmp/new.mot") self.assertEqual(self.adapter._loaded_model, "/tmp/new.mot") self.assertEqual(self.adapter._params, {}) def test_set_parameter_readback(self): self.adapter.connect() self.adapter.set_parameter("airgap_mm", 1.5) self.assertEqual(self.adapter._params["airgap_mm"], 1.5) def test_run_simulation_before_connect_raises(self): with self.assertRaises(RuntimeError): self.adapter.run_simulation("electromagnetic") def test_run_simulation_unsupported_mode_raises(self): self.adapter.connect() with self.assertRaises(ValueError): self.adapter.run_simulation("structural") def test_extract_metrics_deterministic(self): self.adapter.connect() self.adapter.set_parameter("airgap_mm", 1.0) self.adapter.set_parameter("magnet_thickness_mm", 5.0) ext = self.adapter.extract_metrics("output/p5m5_test", "t1") self.assertEqual(ext["status"], "OK") # tavg = 0.50*1.0 + 0.30*5.0 + 0.10 = 2.10 self.assertAlmostEqual(ext["metrics"]["tavg_nm"], 2.10, places=4) self.assertAlmostEqual(ext["metrics"]["efficiency_pct"], 85.10, places=2) self.assertIn("winding_temp_c", ext["metrics"]) # thermal domain def test_run_point_full_pipeline(self): result = self.adapter.run_point( model_path="/tmp/test.mot", params={"airgap_mm": 1.0, "magnet_thickness_mm": 5.0}, output_dir="output/p5m5_test", tag="full", ) self.assertEqual(result["status"], "OK") self.assertIn("tavg_nm", result["metrics"]) self.assertEqual(result["params"]["airgap_mm"], 1.0) def test_run_point_empty_params(self): result = self.adapter.run_point( model_path="/tmp/test.mot", params={}, output_dir="output/p5m5_test", tag="empty", ) self.assertEqual(result["status"], "OK") # all params default to 0 -> tavg = 0.10 self.assertAlmostEqual(result["metrics"]["tavg_nm"], 0.10, places=4) def test_real_mode_raises(self): a = get_adapter("maxwell", model_path="/tmp/x.mot", mock=False) with self.assertRaises(RuntimeError): a.connect() class TestJMAGMockPipeline(unittest.TestCase): def setUp(self): self.adapter = get_adapter( "jmag", model_path="/tmp/test.jmag", output_dir="output/p5m5_test", mock=True, ) def test_tool_label_and_domains(self): self.assertEqual(self.adapter.tool_label, "JMAG Designer (mock)") self.assertEqual(self.adapter.capability_domains, ("electromagnetic",)) def test_extract_metrics_deterministic(self): self.adapter.connect() self.adapter.set_parameter("airgap_mm", 1.0) self.adapter.set_parameter("magnet_thickness_mm", 5.0) ext = self.adapter.extract_metrics("output/p5m5_test", "t1") # tavg = 0.45*1.0 + 0.32*5.0 + 0.12 = 2.17 self.assertAlmostEqual(ext["metrics"]["tavg_nm"], 2.17, places=4) self.assertNotIn("winding_temp_c", ext["metrics"]) # jmag has no thermal domain def test_run_point_full_pipeline(self): result = self.adapter.run_point( model_path="/tmp/test.jmag", params={"airgap_mm": 2.0, "magnet_thickness_mm": 4.0}, output_dir="output/p5m5_test", tag="full", ) self.assertEqual(result["status"], "OK") # tavg = 0.45*2.0 + 0.32*4.0 + 0.12 = 0.90+1.28+0.12 = 2.30 self.assertAlmostEqual(result["metrics"]["tavg_nm"], 2.30, places=4) def test_unsupported_mode_raises(self): self.adapter.connect() with self.assertRaises(ValueError): self.adapter.run_simulation("thermal") class TestToolDifferentiation(unittest.TestCase): """Same input params must produce different metrics for maxwell vs jmag.""" def test_same_params_different_tavg(self): params = {"airgap_mm": 1.0, "magnet_thickness_mm": 5.0} mx = get_adapter("maxwell", mock=True) jm = get_adapter("jmag", mock=True) r_mx = mx.run_point("/tmp/x.mot", params, "output/p5m5_test", "diff") r_jm = jm.run_point("/tmp/x.jmag", params, "output/p5m5_test", "diff") self.assertNotEqual(r_mx["metrics"]["tavg_nm"], r_jm["metrics"]["tavg_nm"]) self.assertNotEqual(r_mx["metrics"]["efficiency_pct"], r_jm["metrics"]["efficiency_pct"]) class TestExecutorDynamicImport(unittest.TestCase): """MotorCADTaskExecutor must dynamically import the adapter matching tool.""" def test_executor_tool_maxwell_creates_maxwell_adapter(self): sys.path.insert(0, _ROOT) from scripts.task_executor import MotorCADTaskExecutor ex = MotorCADTaskExecutor( web_base_url="http://127.0.0.1:9", model_path="/tmp/test.mot", tool="maxwell", enable_mock=False, ) adapter = ex._ensure_adapter() self.assertEqual(adapter.tool_name, "maxwell") self.assertIn("maxwell", registered_tools()) def test_executor_tool_jmag_creates_jmag_adapter(self): sys.path.insert(0, _ROOT) from scripts.task_executor import MotorCADTaskExecutor ex = MotorCADTaskExecutor( web_base_url="http://127.0.0.1:9", model_path="/tmp/test.jmag", tool="jmag", enable_mock=False, ) adapter = ex._ensure_adapter() self.assertEqual(adapter.tool_name, "jmag") self.assertIn("jmag", registered_tools()) def test_executor_tool_unknown_raises_keyerror(self): sys.path.insert(0, _ROOT) from scripts.task_executor import MotorCADTaskExecutor ex = MotorCADTaskExecutor( web_base_url="http://127.0.0.1:9", model_path="/tmp/test.mot", tool="nonexistent_tool_xyz", enable_mock=False, ) with self.assertRaises(KeyError): ex._ensure_adapter() if __name__ == "__main__": unittest.main(verbosity=2)