Skip to content

Commit 0ef4d3b

Browse files
committed
more unit tests for ros2
1 parent af44849 commit 0ef4d3b

1 file changed

Lines changed: 96 additions & 0 deletions

File tree

open-codegen/test/test_ros2.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,12 +243,64 @@ def setUpRos2PackageGeneration(cls):
243243
solver_configuration=cls.solverConfig()) \
244244
.build()
245245

246+
@classmethod
247+
def _inject_deterministic_solver_error(cls):
248+
"""Patch the generated solver so negative `p[0]` triggers a known error."""
249+
solver_root = os.path.join(cls.TEST_DIR, cls.OPTIMIZER_NAME)
250+
target_lib = os.path.join(solver_root, "src", "lib.rs")
251+
with open(target_lib, "r", encoding="utf-8") as fh:
252+
solver_lib = fh.read()
253+
254+
if "forced solver error for ROS2 test" in solver_lib:
255+
return
256+
257+
anchor = (
258+
' assert_eq!(u.len(), ROSENBROCK_ROS2_NUM_DECISION_VARIABLES, '
259+
'"Wrong number of decision variables (u)");\n'
260+
)
261+
injected_guard = (
262+
anchor +
263+
'\n'
264+
' if p[0] < 0.0 {\n'
265+
' return Err(SolverError::Cost("forced solver error for ROS2 test"));\n'
266+
' }\n'
267+
)
268+
if anchor not in solver_lib:
269+
raise RuntimeError("Could not inject deterministic solver error into ROS2 solver")
270+
271+
with open(target_lib, "w", encoding="utf-8") as fh:
272+
fh.write(solver_lib.replace(anchor, injected_guard, 1))
273+
274+
@classmethod
275+
def _rebuild_generated_solver_library(cls):
276+
"""Rebuild the generated Rust solver and refresh the ROS2 static library."""
277+
solver_root = os.path.join(cls.TEST_DIR, cls.OPTIMIZER_NAME)
278+
process = subprocess.Popen(
279+
["cargo", "build"],
280+
cwd=solver_root,
281+
stdout=subprocess.PIPE,
282+
stderr=subprocess.PIPE,
283+
)
284+
_stdout, stderr = process.communicate()
285+
if process.returncode != 0:
286+
raise RuntimeError(
287+
"Could not rebuild generated ROS2 solver:\n{}".format(stderr.decode())
288+
)
289+
290+
generated_static_lib = os.path.join(
291+
solver_root, "target", "debug", f"lib{cls.OPTIMIZER_NAME}.a")
292+
ros2_static_lib = os.path.join(
293+
cls.ros2_package_dir(), "extern_lib", f"lib{cls.OPTIMIZER_NAME}.a")
294+
shutil.copyfile(generated_static_lib, ros2_static_lib)
295+
246296
@classmethod
247297
def setUpClass(cls):
248298
"""Generate the ROS2 package once before all tests run."""
249299
if shutil.which("ros2") is None or shutil.which("colcon") is None:
250300
raise unittest.SkipTest("ROS2 CLI tools are not available in PATH")
251301
cls.setUpRos2PackageGeneration()
302+
cls._inject_deterministic_solver_error()
303+
cls._rebuild_generated_solver_library()
252304

253305
@classmethod
254306
def ros2_package_dir(cls):
@@ -443,6 +495,12 @@ def _assert_invalid_request_message(self, echo_stdout, error_code, error_message
443495
self.assertIn(f"error_code: {error_code}", echo_stdout)
444496
self.assertIn(error_message_fragment, echo_stdout)
445497

498+
def _assert_solver_error_message(self, echo_stdout, error_message_fragment):
499+
"""Assert that the echoed result message reports a solver-side failure."""
500+
self.assertIn("status: 3", echo_stdout)
501+
self.assertIn("error_code: 2000", echo_stdout)
502+
self.assertIn(error_message_fragment, echo_stdout)
503+
446504
def _publish_request_and_collect_result(self, ros2_dir, env, request_payload):
447505
"""Publish one request and return one echoed result message."""
448506
_, setup_script = self.ros2_shell()
@@ -484,6 +542,38 @@ def _exercise_invalid_request(self, ros2_dir, env):
484542
3003,
485543
"wrong number of parameters")
486544

545+
def _exercise_invalid_initial_guess(self, ros2_dir, env):
546+
"""Verify that invalid warm-start dimensions are reported clearly."""
547+
echo_stdout = self._publish_request_and_collect_result(
548+
ros2_dir,
549+
env,
550+
"{parameter: [1.0, 2.0], initial_guess: [0.0], initial_y: [], initial_penalty: 15.0}")
551+
self._assert_invalid_request_message(
552+
echo_stdout,
553+
1600,
554+
"initial guess has incompatible dimensions")
555+
556+
def _exercise_invalid_initial_y(self, ros2_dir, env):
557+
"""Verify that invalid multiplier dimensions are reported clearly."""
558+
echo_stdout = self._publish_request_and_collect_result(
559+
ros2_dir,
560+
env,
561+
"{parameter: [1.0, 2.0], initial_guess: [0.0, 0.0, 0.0, 0.0, 0.0], initial_y: [0.0], initial_penalty: 15.0}")
562+
self._assert_invalid_request_message(
563+
echo_stdout,
564+
1700,
565+
"wrong dimension of Lagrange multipliers")
566+
567+
def _exercise_solver_error(self, ros2_dir, env):
568+
"""Verify that solver-side failures propagate to the ROS2 result message."""
569+
echo_stdout = self._publish_request_and_collect_result(
570+
ros2_dir,
571+
env,
572+
"{parameter: [-1.0, 2.0], initial_guess: [0.0, 0.0, 0.0, 0.0, 0.0], initial_y: [], initial_penalty: 15.0}")
573+
self._assert_solver_error_message(
574+
echo_stdout,
575+
"forced solver error for ROS2 test")
576+
487577
def test_ros2_package_generation(self):
488578
"""Verify the ROS2 package files are generated."""
489579
ros2_dir = self.ros2_package_dir()
@@ -512,6 +602,9 @@ def test_generated_ros2_package_works(self):
512602
self._wait_for_node_and_topics(ros2_dir, env, node_process)
513603
self._exercise_running_optimizer(ros2_dir, env)
514604
self._exercise_invalid_request(ros2_dir, env)
605+
self._exercise_invalid_initial_guess(ros2_dir, env)
606+
self._exercise_invalid_initial_y(ros2_dir, env)
607+
self._exercise_solver_error(ros2_dir, env)
515608
self._exercise_running_optimizer(ros2_dir, env)
516609
finally:
517610
if node_process.poll() is None:
@@ -535,6 +628,9 @@ def test_generated_ros2_launch_file_works(self):
535628
self._wait_for_node_and_topics(ros2_dir, env, launch_process)
536629
self._exercise_running_optimizer(ros2_dir, env)
537630
self._exercise_invalid_request(ros2_dir, env)
631+
self._exercise_invalid_initial_guess(ros2_dir, env)
632+
self._exercise_invalid_initial_y(ros2_dir, env)
633+
self._exercise_solver_error(ros2_dir, env)
538634
self._exercise_running_optimizer(ros2_dir, env)
539635
finally:
540636
if launch_process.poll() is None:

0 commit comments

Comments
 (0)