test_executor_p5m2.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. """P5-M2 regression: MotorCADTaskExecutor mock-vs-real branch.
  2. Run: python scripts/test_executor_p5m2.py
  3. Verifies that enable_mock=True routes to the base-class mock solver (no
  4. Motor-CAD, no model_path required) and that enable_mock=False keeps the
  5. real-adapter contract (model_path required). exit 0 == PASS.
  6. NOTE: All strings in this file are ASCII only.
  7. """
  8. import json
  9. import os
  10. import sys
  11. import tempfile
  12. import unittest
  13. _SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
  14. if _SCRIPTS_DIR not in sys.path:
  15. sys.path.insert(0, _SCRIPTS_DIR)
  16. import task_executor as te # noqa: E402
  17. # local-file mode: no real web calls during the test
  18. te.requests = None
  19. def _make_local_task(tmp, tid, params):
  20. path = os.path.join(tmp, tid + "_task.json")
  21. with open(path, "w", encoding="utf-8") as fh:
  22. json.dump({"task_id": tid, "task_name": "p5m2",
  23. "parameters": params}, fh)
  24. return {"task_id": tid, "_local_file": path, "parameters": params}
  25. class MotorCADExecutorMockBranchTest(unittest.TestCase):
  26. """Exercise the enable_mock branch added in P5-M2."""
  27. def setUp(self):
  28. self.tmp = tempfile.mkdtemp(prefix="p5m2_exec_")
  29. def tearDown(self):
  30. import shutil
  31. shutil.rmtree(self.tmp, ignore_errors=True)
  32. def _run(self, executor, tid, params):
  33. executor.dispatch_task = lambda t: True
  34. captured = {}
  35. executor.on_complete = lambda t, res, met: captured.setdefault(t, res)
  36. executor.execute_task(_make_local_task(self.tmp, tid, params))
  37. return captured.get(tid)
  38. # ---------- happy path ----------
  39. def test_mock_point_source_and_no_model_required(self):
  40. ex = te.MotorCADTaskExecutor(task_dir=self.tmp, enable_mock=True,
  41. model_path=None)
  42. res = self._run(ex, "m1", [{"airgap_mm": 1.0, "point_id": 1}])
  43. self.assertEqual(len(res), 1)
  44. self.assertEqual(res[0]["status"], "OK")
  45. self.assertEqual(res[0]["source"], "mock")
  46. self.assertIn("tavg_nm", res[0])
  47. def test_mock_multi_point(self):
  48. ex = te.MotorCADTaskExecutor(task_dir=self.tmp, enable_mock=True,
  49. model_path=None)
  50. res = self._run(ex, "m2", [
  51. {"airgap_mm": 0.8, "point_id": 1},
  52. {"airgap_mm": 1.0, "point_id": 2},
  53. {"airgap_mm": 1.2, "point_id": 3},
  54. ])
  55. self.assertEqual(len(res), 3)
  56. for r in res:
  57. self.assertEqual(r["status"], "OK")
  58. self.assertEqual(r["source"], "mock")
  59. # ---------- boundary ----------
  60. def test_mock_with_illegal_model_path_still_mocks(self):
  61. # mock must win even when model_path points at a nonexistent file,
  62. # proving no Motor-CAD instance / adapter is launched.
  63. ex = te.MotorCADTaskExecutor(task_dir=self.tmp, enable_mock=True,
  64. model_path="Z:/nonexistent/model.mot")
  65. res = self._run(ex, "m3", [{"airgap_mm": 1.0, "point_id": 1}])
  66. self.assertEqual(res[0]["source"], "mock")
  67. def test_default_mock_off(self):
  68. # P4-M3 default: real solver adapter, so enable_mock defaults False
  69. ex = te.MotorCADTaskExecutor(task_dir=self.tmp, model_path=None)
  70. self.assertIs(ex.enable_mock, False)
  71. # ---------- abnormal ----------
  72. def test_real_mode_requires_model_path(self):
  73. ex = te.MotorCADTaskExecutor(task_dir=self.tmp, enable_mock=False,
  74. model_path=None)
  75. with self.assertRaises(RuntimeError):
  76. ex._run_simulation_point({"airgap_mm": 1.0}, 0)
  77. # ---------- metadata key exclusion ----------
  78. def test_point_id_excluded_from_motorcad_params(self):
  79. # Regression (P5-M2 real E2E): point_id is a passthrough
  80. # metadata key and must never be sent to Motor-CAD set_variable.
  81. src_path = os.path.join(_SCRIPTS_DIR, "robust_motorcad.py")
  82. with open(src_path, "r", encoding="utf-8") as fh:
  83. src = fh.read()
  84. self.assertIn(
  85. 'if var in ("point_index", "point_label", "point_id"):',
  86. src)
  87. # ---------- business alias ----------
  88. def test_business_alias_airgap_resolves(self):
  89. from robust_motorcad import resolve_variable_name
  90. self.assertEqual(resolve_variable_name("airgap_mm"), "Airgap")
  91. # unknown canonical names pass through untouched
  92. self.assertEqual(resolve_variable_name("SomeUnknown"), "SomeUnknown")
  93. # ---------- empty value ----------
  94. def test_mock_empty_model_path_string(self):
  95. ex = te.MotorCADTaskExecutor(task_dir=self.tmp, enable_mock=True,
  96. model_path="")
  97. res = self._run(ex, "m4", [{"airgap_mm": 1.0, "point_id": 1}])
  98. self.assertEqual(res[0]["source"], "mock")
  99. if __name__ == "__main__":
  100. unittest.main(verbosity=2)