RclexTesting.DSL.Actions (RclexTesting (Experimental) v0.12.11)

Copy Markdown View Source

Mock ROS action servers, send goals, and assert action activity.

mock_action/3 records incoming goals, feedback, and results. Use send_goal/3 to invoke an action server, and use the expect_* and refute_goal assertions to verify the recorded action lifecycle.

Options

Every function in this module accepts an optional trailing keyword list:

  • :namespace the namespace of the scenario node. Defaults to the namespace given to scenario/3, or "/".
  • :goal_callback decides whether an incoming goal is accepted. Arity 1 (the goal struct), must return :accept or :reject. Defaults to accepting every goal.
    • :cancel_callback decides whether a cancel request is honoured. Arity 1 (the goal handle), must return :accept or :reject. Defaults to rejecting every cancellation.
    • :handle_accepted_callback runs once a goal has been accepted. Arity 5 (goal info, action type, action name, node name, namespace). Defaults to Rclex.execute_goal/5, which runs the handler passed to mock_action/4.
    • :options a Rclex.ActionServerOptions struct (per-service QoS, :clock_type, :result_timeout). Defaults to Rclex.ActionServerOptions.default/0
  • :options a Rclex.ActionClientOptions struct. Defaults to the Rclex default.
  • :feedback_callback, :accepted_callback and :goal_uuid are forwarded to Rclex.send_goal_async/4.
  • :timeout how long to wait, in milliseconds. Defaults to 5000.

Any option other than :timeout is forwarded verbatim to the underlying Rclex call, so options not listed here are supported too.

Summary

Functions

Assert that the mocked action produced a new feedback message.

Assert that the mocked action server received a new goal.

Assert that the mocked action server rejected a goal.

Assert that the mocked action produced a result.

Register a test action server implementation on the scenario node.

Assert that no new goal is submitted to the mocked action server.

refute_goal/2 with either a predicate or options.

Assert that the mocked action server rejected no goal.

Send a goal to an action server and return the goal UUID.

Functions

expect_feedback(action_name, action_type)

@spec expect_feedback(String.t(), module()) :: struct()

Assert that the mocked action produced a new feedback message.

expect_feedback "/navigate", Navigate

expect_feedback(action_name, action_type, predicate)

@spec expect_feedback(String.t(), module(), (struct() -> boolean()) | keyword()) ::
  struct()

expect_feedback/2 with either a predicate or options.

expect_feedback "/navigate", Navigate, fn fb -> fb.progress == 100 end
expect_feedback "/navigate", Navigate, timeout: 10_000

opts

  • :timeout how long to wait, in milliseconds. Defaults to 5000.
  • :namespace the namespace of the scenario node. Defaults to the namespace given to scenario/3, or "/".

expect_feedback(action_name, action_type, predicate, opts)

@spec expect_feedback(String.t(), module(), (struct() -> boolean()), keyword()) ::
  struct()

expect_feedback/3 with both a predicate and options.

expect_feedback "/navigate", Navigate, fn fb -> fb.progress == 100 end, timeout: 10_000

expect_goal(action_name, action_type)

@spec expect_goal(String.t(), module()) :: struct()

Assert that the mocked action server received a new goal.

expect_goal "/navigate", Navigate

expect_goal(action_name, action_type, predicate)

@spec expect_goal(String.t(), module(), (struct() -> boolean()) | keyword()) ::
  struct()

expect_goal/2 with either a predicate or options.

expect_goal "/navigate", Navigate, fn goal -> goal.x == 10 end
expect_goal "/navigate", Navigate, timeout: 10_000

opts

  • :timeout how long to wait, in milliseconds. Defaults to 5000.
  • :namespace the namespace of the scenario node. Defaults to the namespace given to scenario/3, or "/".

expect_goal(action_name, action_type, predicate, opts)

@spec expect_goal(String.t(), module(), (struct() -> boolean()), keyword()) ::
  struct()

expect_goal/3 with both a predicate and options.

expect_goal "/navigate", Navigate, fn goal -> goal.x == 10 end, timeout: 10_000

expect_rejected_goal(action_name, action_type)

@spec expect_rejected_goal(String.t(), module()) :: struct()

Assert that the mocked action server rejected a goal.

A goal is rejected when the :goal_callback given to mock_action/4 returns :reject. Such a goal never reaches the handler, so expect_goal/2 will not match it — use this assertion instead.

mock_action "/navigate", Navigate, handler,
  goal_callback: fn goal -> if goal.x >= 0, do: :accept, else: :reject end

send_goal "/navigate", Navigate, %Navigate.Goal{x: -1}
expect_rejected_goal "/navigate", Navigate

expect_rejected_goal(action_name, action_type, predicate)

@spec expect_rejected_goal(String.t(), module(), (struct() -> boolean()) | keyword()) ::
  struct()

expect_rejected_goal/2 with either a predicate or options.

expect_rejected_goal "/navigate", Navigate, fn goal -> goal.x < 0 end
expect_rejected_goal "/navigate", Navigate, timeout: 10_000

opts

  • :timeout how long to wait, in milliseconds. Defaults to 5000.
  • :namespace the namespace of the scenario node. Defaults to the namespace given to scenario/3, or "/".

expect_rejected_goal(action_name, action_type, predicate, opts)

@spec expect_rejected_goal(String.t(), module(), (struct() -> boolean()), keyword()) ::
  struct()

expect_rejected_goal/3 with both a predicate and options.

expect_rejected_goal "/navigate", Navigate, fn goal -> goal.x < 0 end, timeout: 10_000

expect_result(action_name, action_type)

@spec expect_result(String.t(), module()) :: struct()

Assert that the mocked action produced a result.

expect_result "/navigate", Navigate

expect_result(action_name, action_type, predicate)

@spec expect_result(String.t(), module(), (struct() -> boolean()) | keyword()) ::
  struct()

expect_result/2 with either a predicate or options.

expect_result "/navigate", Navigate, fn result -> result.success end
expect_result "/navigate", Navigate, timeout: 10_000

opts

  • :timeout how long to wait, in milliseconds. Defaults to 5000.
  • :namespace the namespace of the scenario node. Defaults to the namespace given to scenario/3, or "/".

expect_result(action_name, action_type, predicate, opts)

@spec expect_result(String.t(), module(), (struct() -> boolean()), keyword()) ::
  struct()

expect_result/3 with both a predicate and options.

expect_result "/navigate", Navigate, fn result -> result.success end, timeout: 10_000

mock_action(action_name, action_type, handler, opts \\ [])

@spec mock_action(String.t(), module(), (struct(), function() -> struct()), keyword()) ::
  :ok

Register a test action server implementation on the scenario node.

Goals, feedback, and results are all recorded in the collector. The handler receives (goal, publish_feedback) and must return a result struct. Cleanup is automatic when the scenario ends.

mock_action "/navigate", Navigate, fn goal, publish_feedback ->
  publish_feedback.(%Navigate.Feedback{progress: 50})
  publish_feedback.(%Navigate.Feedback{progress: 100})
  %Navigate.Result{success: true}
end

handler is the execute callback. The three remaining action-server callbacks are available as options:

mock_action "/navigate", Navigate, handler,
  goal_callback: fn goal -> if goal.x >= 0, do: :accept, else: :reject end,
  cancel_callback: fn _goal_handle -> :accept end,
  options: %Rclex.ActionServerOptions{result_timeout: 30.0}

A goal rejected by :goal_callback never reaches handler, so it is not recorded as a goal and expect_goal/2 will not match it. Assert it with expect_rejected_goal/2 instead.

opts

  • :namespace the namespace of the scenario node. Defaults to the namespace given to scenario/3, or "/".
  • :goal_callback decides whether an incoming goal is accepted. Arity 1 (the goal struct), must return :accept or :reject. Defaults to accepting every goal.
    • :cancel_callback decides whether a cancel request is honoured. Arity 1 (the goal handle), must return :accept or :reject. Defaults to rejecting every cancellation.
    • :handle_accepted_callback runs once a goal has been accepted. Arity 5 (goal info, action type, action name, node name, namespace). Defaults to Rclex.execute_goal/5, which runs the handler passed to mock_action/4.
    • :options a Rclex.ActionServerOptions struct (per-service QoS, :clock_type, :result_timeout). Defaults to Rclex.ActionServerOptions.default/0

refute_goal(action_name, action_type)

@spec refute_goal(String.t(), module()) :: :ok

Assert that no new goal is submitted to the mocked action server.

refute_goal "/navigate", Navigate

refute_goal(action_name, action_type, opts)

@spec refute_goal(String.t(), module(), (struct() -> boolean()) | keyword()) :: :ok

refute_goal/2 with either a predicate or options.

refute_goal "/navigate", Navigate, fn goal -> goal.x < 0 end
refute_goal "/navigate", Navigate, timeout: 500

opts

  • :timeout how long to wait, in milliseconds. Defaults to 200.
  • :namespace the namespace of the scenario node. Defaults to the namespace given to scenario/3, or "/".

refute_goal(action_name, action_type, predicate, opts)

@spec refute_goal(String.t(), module(), (struct() -> boolean()), keyword()) :: :ok

refute_goal/3 with both a predicate and options.

refute_goal "/navigate", Navigate, fn goal -> goal.x < 0 end, timeout: 500

refute_rejected_goal(action_name, action_type)

@spec refute_rejected_goal(String.t(), module()) :: :ok

Assert that the mocked action server rejected no goal.

refute_rejected_goal "/navigate", Navigate

refute_rejected_goal(action_name, action_type, opts)

@spec refute_rejected_goal(String.t(), module(), (struct() -> boolean()) | keyword()) ::
  :ok

refute_rejected_goal/2 with either a predicate or options.

refute_rejected_goal "/navigate", Navigate, fn goal -> goal.x > 0 end
refute_rejected_goal "/navigate", Navigate, timeout: 500

opts

  • :timeout how long to wait, in milliseconds. Defaults to 200.
  • :namespace the namespace of the scenario node. Defaults to the namespace given to scenario/3, or "/".

refute_rejected_goal(action_name, action_type, predicate, opts)

@spec refute_rejected_goal(String.t(), module(), (struct() -> boolean()), keyword()) ::
  :ok

refute_rejected_goal/3 with both a predicate and options.

refute_rejected_goal "/navigate", Navigate, fn goal -> goal.x > 0 end, timeout: 500

send_goal(action_name, action_type, goal, opts \\ [])

@spec send_goal(String.t(), module(), struct(), keyword()) :: binary()

Send a goal to an action server and return the goal UUID.

Starts an action client on the scenario node on first use.

goal_uuid = send_goal "/navigate", Navigate, %Navigate.Goal{x: 10, y: 20}
goal_uuid = send_goal "/navigate", Navigate, goal, feedback_callback: &IO.inspect/1

Use :accepted_callback to observe whether the server accepted the goal, which is the client-side counterpart to mock_action/4's :goal_callback:

test_pid = self()

send_goal "/navigate", Navigate, goal,
  accepted_callback: fn _uuid, accepted, _stamp -> send(test_pid, {:accepted, accepted}) end

assert_receive {:accepted, false}

opts

  • :namespace the namespace of the scenario node. Defaults to the namespace given to scenario/3, or "/".
  • :options a Rclex.ActionClientOptions struct. Defaults to the Rclex default.
  • :feedback_callback, :accepted_callback and :goal_uuid are forwarded to Rclex.send_goal_async/4.