moveit2
The MoveIt Motion Planning Framework for ROS 2.
integrationtest_sequence_action.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2018 Pilz GmbH & Co. KG
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above
14  * copyright notice, this list of conditions and the following
15  * disclaimer in the documentation and/or other materials provided
16  * with the distribution.
17  * * Neither the name of Pilz GmbH & Co. KG nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *********************************************************************/
34 
35 #include <chrono>
36 #include <condition_variable>
37 #include <gmock/gmock.h>
38 #include <gtest/gtest.h>
39 #include <iostream>
40 #include <memory>
41 #include <mutex>
42 #include <stdexcept>
43 #include <thread>
44 
45 #include <actionlib/client/simple_action_client.h>
50 #include <moveit_msgs/Constraints.h>
51 #include <moveit_msgs/GetMotionPlan.h>
52 #include <moveit_msgs/JointConstraint.h>
53 #include <ros/ros.h>
54 
56 
60 
61 #include <moveit_msgs/MoveGroupSequenceAction.h>
62 
63 static constexpr int WAIT_FOR_ACTION_SERVER_TIME_OUT{ 10 }; // seconds
64 
65 const std::string SEQUENCE_ACTION_NAME("/sequence_move_group");
66 
67 // Parameters from the node
68 const std::string JOINT_POSITION_TOLERANCE("joint_position_tolerance");
69 
70 // events for callback tests
71 const std::string GOAL_SUCCEEDED_EVENT = "GOAL_SUCCEEDED";
72 const std::string SERVER_IDLE_EVENT = "SERVER_IDLE";
73 
74 const std::string TEST_DATA_FILE_NAME("testdata_file_name");
75 const std::string GROUP_NAME("group_name");
76 
78 
79 class IntegrationTestSequenceAction : public testing::Test, public testing::AsyncTest
80 {
81 protected:
82  void SetUp() override;
83 
84 public:
85  MOCK_METHOD0(active_callback, void());
86  MOCK_METHOD1(feedback_callback, void(const moveit_msgs::msg::MoveGroupSequenceFeedbackConstPtr& feedback));
87  MOCK_METHOD2(done_callback, void(const actionlib::SimpleClientGoalState& state,
88  const moveit_msgs::msg::MoveGroupSequenceResultConstPtr& result));
89 
90 protected:
91  ros::NodeHandle ph_{ "~" };
92  actionlib::SimpleActionClient<moveit_msgs::msg::MoveGroupSequenceAction> ac_{ ph_, SEQUENCE_ACTION_NAME, true };
93  std::shared_ptr<moveit::planning_interface::MoveGroupInterface> move_group_;
94 
96  robot_model::RobotModelPtr robot_model_;
98 
99  std::string test_data_file_name_;
100  std::string group_name_;
102 
105 };
106 
108 {
109  // get necessary parameters
110  ASSERT_TRUE(ph_.getParam(JOINT_POSITION_TOLERANCE, joint_position_tolerance_));
111  ASSERT_TRUE(ph_.getParam(TEST_DATA_FILE_NAME, test_data_file_name_));
112  ph_.param<std::string>(GROUP_NAME, group_name_, "manipulator");
113 
114  robot_model_ = model_loader_.getModel();
115 
116  data_loader_ = std::make_unique<XmlTestdataLoader>(test_data_file_name_, robot_model_);
117  ASSERT_NE(nullptr, data_loader_) << "Failed to load test data by provider.";
118 
119  // wait for action server
120  ASSERT_TRUE(ac_.waitForServer(ros::Duration(WAIT_FOR_ACTION_SERVER_TIME_OUT))) << "Action server is not active.";
121 
122  // move to default position
123  start_config = data_loader_->getJoints("ZeroPose", group_name_);
124  robot_state::RobotState robot_state{ start_config.toRobotState() };
125 
126  move_group_ = std::make_shared<moveit::planning_interface::MoveGroupInterface>(start_config.getGroupName());
127  move_group_->setPlannerId("PTP");
128  move_group_->setGoalTolerance(joint_position_tolerance_);
129  move_group_->setJointValueTarget(robot_state);
130  move_group_->move();
131 
132  ASSERT_TRUE(
133  isAtExpectedPosition(robot_state, *(move_group_->getCurrentState()), joint_position_tolerance_, group_name_));
134 }
135 
147 TEST_F(IntegrationTestSequenceAction, TestSendingOfEmptySequence)
148 {
149  moveit_msgs::msg::MoveGroupSequenceGoal seq_goal;
150 
151  ac_.sendGoalAndWait(seq_goal);
152  moveit_msgs::msg::MoveGroupSequenceResultConstPtr res = ac_.getResult();
153  EXPECT_EQ(res->response.error_code.val, moveit_msgs::msg::MoveItErrorCodes::SUCCESS)
154  << "Execution of sequence failed.";
155  EXPECT_TRUE(res->response.planned_trajectories.empty());
156 }
157 
170 TEST_F(IntegrationTestSequenceAction, TestDifferingGroupNames)
171 {
172  Sequence seq{ data_loader_->getSequence("ComplexSequence") };
173  MotionCmd& cmd{ seq.getCmd(1) };
174  cmd.setPlanningGroup("WrongGroupName");
175 
176  moveit_msgs::msg::MoveGroupSequenceGoal seq_goal;
177  seq_goal.request = seq.toRequest();
178 
179  ac_.sendGoalAndWait(seq_goal);
180  moveit_msgs::msg::MoveGroupSequenceResultConstPtr res = ac_.getResult();
181  EXPECT_EQ(res->response.error_code.val, moveit_msgs::msg::MoveItErrorCodes::INVALID_GROUP_NAME)
182  << "Incorrect error code.";
183  EXPECT_TRUE(res->response.planned_trajectories.empty());
184 }
185 
198 TEST_F(IntegrationTestSequenceAction, TestNegativeBlendRadius)
199 {
200  Sequence seq{ data_loader_->getSequence("ComplexSequence") };
201  seq.setBlendRadius(0, -1.0);
202 
203  moveit_msgs::msg::MoveGroupSequenceGoal seq_goal;
204  seq_goal.request = seq.toRequest();
205 
206  ac_.sendGoalAndWait(seq_goal);
207 
208  moveit_msgs::msg::MoveGroupSequenceResultConstPtr res = ac_.getResult();
209  EXPECT_EQ(res->response.error_code.val, moveit_msgs::msg::MoveItErrorCodes::INVALID_MOTION_PLAN)
210  << "Incorrect error code.";
211  EXPECT_TRUE(res->response.planned_trajectories.empty());
212 }
213 
227 TEST_F(IntegrationTestSequenceAction, TestOverlappingBlendRadii)
228 {
229  Sequence seq{ data_loader_->getSequence("ComplexSequence") };
230  seq.setBlendRadius(0, 10 * seq.getBlendRadius(0));
231 
232  moveit_msgs::msg::MoveGroupSequenceGoal seq_goal;
233  seq_goal.request = seq.toRequest();
234 
235  ac_.sendGoalAndWait(seq_goal);
236 
237  moveit_msgs::msg::MoveGroupSequenceResultConstPtr res = ac_.getResult();
238  EXPECT_EQ(res->response.error_code.val, moveit_msgs::msg::MoveItErrorCodes::INVALID_MOTION_PLAN)
239  << "Incorrect error code";
240  EXPECT_TRUE(res->response.planned_trajectories.empty());
241 }
242 
256 TEST_F(IntegrationTestSequenceAction, TestTooLargeBlendRadii)
257 {
258  Sequence seq{ data_loader_->getSequence("ComplexSequence") };
259  seq.erase(2, seq.size());
260  seq.setBlendRadius(0, 10 * seq.getBlendRadius(seq.size() - 2));
261 
262  moveit_msgs::msg::MoveGroupSequenceGoal seq_goal;
263  seq_goal.request = seq.toRequest();
264 
265  ac_.sendGoalAndWait(seq_goal);
266 
267  moveit_msgs::msg::MoveGroupSequenceResultConstPtr res = ac_.getResult();
268  EXPECT_EQ(res->response.error_code.val, moveit_msgs::msg::MoveItErrorCodes::FAILURE) << "Incorrect error code";
269  EXPECT_TRUE(res->response.planned_trajectories.empty());
270 }
271 
287 {
288  Sequence seq{ data_loader_->getSequence("ComplexSequence") };
289  // Erase certain command to invalid command following the command in sequence.
290  seq.erase(3, 4);
291 
292  moveit_msgs::msg::MoveGroupSequenceGoal seq_goal;
293  seq_goal.request = seq.toRequest();
294 
295  ac_.sendGoalAndWait(seq_goal);
296  moveit_msgs::msg::MoveGroupSequenceResultConstPtr res = ac_.getResult();
297  EXPECT_NE(res->response.error_code.val, moveit_msgs::msg::MoveItErrorCodes::SUCCESS) << "Incorrect error code.";
298  EXPECT_TRUE(res->response.planned_trajectories.empty());
299 }
300 
313 {
314  Sequence seq{ data_loader_->getSequence("ComplexSequence") };
316 
317  // Invalidate link name
318  CircInterimCart& circ{ seq.getCmd<CircInterimCart>(1) };
319  circ.getGoalConfiguration().setLinkName("InvalidLinkName");
320 
321  moveit_msgs::msg::MoveGroupSequenceGoal seq_goal;
322  seq_goal.request = seq.toRequest();
323 
324  ac_.sendGoalAndWait(seq_goal);
325  moveit_msgs::msg::MoveGroupSequenceResultConstPtr res = ac_.getResult();
326  EXPECT_NE(res->response.error_code.val, moveit_msgs::msg::MoveItErrorCodes::SUCCESS) << "Incorrect error code.";
327  EXPECT_TRUE(res->response.planned_trajectories.empty());
328 }
329 
330 //*******************************************************
331 //*** matcher for callback functions of action server ***
332 //*******************************************************
333 MATCHER_P(FeedbackStateEq, state, "")
334 {
335  return arg->state == state;
336 }
337 MATCHER(IsResultSuccess, "")
338 {
339  return arg->response.error_code.val == moveit_msgs::msg::MoveItErrorCodes::SUCCESS;
340 }
341 MATCHER(IsResultNotEmpty, "")
342 {
343  return !arg->response.planned_trajectories.empty();
344 }
345 
358 TEST_F(IntegrationTestSequenceAction, TestActionServerCallbacks)
359 {
360  using ::testing::_;
361  using ::testing::AllOf;
362  using ::testing::AtLeast;
363  using ::testing::InSequence;
364 
365  namespace ph = std::placeholders;
366 
367  Sequence seq{ data_loader_->getSequence("ComplexSequence") };
368  // We do not need the complete sequence, just two commands.
369  seq.erase(2, seq.size());
370 
371  moveit_msgs::msg::MoveGroupSequenceGoal seq_goal;
372  seq_goal.request = seq.toRequest();
373 
374  // set expectations (no guarantee, that done callback is called before idle
375  // feedback)
376  EXPECT_CALL(*this, active_callback()).Times(1).RetiresOnSaturation();
377 
378  EXPECT_CALL(*this, done_callback(_, AllOf(IsResultSuccess(), IsResultNotEmpty())))
379  .Times(1)
381  .RetiresOnSaturation();
382 
383  // the feedbacks are expected in order
384  {
385  InSequence dummy;
386 
387  EXPECT_CALL(*this, feedback_callback(FeedbackStateEq("PLANNING"))).Times(AtLeast(1));
388  EXPECT_CALL(*this, feedback_callback(FeedbackStateEq("MONITOR"))).Times(AtLeast(1));
389  EXPECT_CALL(*this, feedback_callback(FeedbackStateEq("IDLE")))
390  .Times(AtLeast(1))
392  .RetiresOnSaturation();
393  }
394 
395  // send goal using mocked callback methods
396  ac_.sendGoal(
397  seq_goal, [this](const auto& state, const auto& result) { done_callback(state, result); },
398  [this] { active_callback(); }, [this](const auto& feedback) { feedback_callback(feedback); });
399 
400  // wait for the ecpected events
402 }
403 
416 {
417  Sequence seq{ data_loader_->getSequence("ComplexSequence") };
418  // We do not need the complete sequence, just two commands.
419  seq.erase(2, seq.size());
420 
421  moveit_msgs::msg::MoveGroupSequenceGoal seq_goal;
422  seq_goal.planning_options.plan_only = true;
423  seq_goal.request = seq.toRequest();
424 
425  ac_.sendGoalAndWait(seq_goal);
426  moveit_msgs::msg::MoveGroupSequenceResultConstPtr res = ac_.getResult();
427  EXPECT_EQ(res->response.error_code.val, moveit_msgs::msg::MoveItErrorCodes::SUCCESS) << "Sequence execution failed.";
428  EXPECT_FALSE(res->response.planned_trajectories.empty()) << "Planned trajectory is empty";
429 
430  ASSERT_TRUE(isAtExpectedPosition(*(move_group_->getCurrentState()), start_config.toRobotState(),
431  joint_position_tolerance_, group_name_))
432  << "Robot did move although \"PlanOnly\" flag set.";
433 }
434 
447 TEST_F(IntegrationTestSequenceAction, TestIgnoreRobotStateForPlanOnly)
448 {
449  Sequence seq{ data_loader_->getSequence("ComplexSequence") };
450  // We do not need the complete sequence, just two commands.
451  seq.erase(2, seq.size());
452 
453  // create request
454  moveit_msgs::msg::MoveGroupSequenceGoal seq_goal;
455  seq_goal.planning_options.plan_only = true;
456  seq_goal.request = seq.toRequest();
457 
458  seq_goal.planning_options.planning_scene_diff.robot_state.is_diff = true;
459 
460  ac_.sendGoalAndWait(seq_goal);
461  moveit_msgs::msg::MoveGroupSequenceResultConstPtr res = ac_.getResult();
462  EXPECT_EQ(res->response.error_code.val, moveit_msgs::msg::MoveItErrorCodes::SUCCESS)
463  << "Execution of sequence failed.";
464  EXPECT_FALSE(res->response.planned_trajectories.empty()) << "Planned trajectory is empty";
465 
466  ASSERT_TRUE(isAtExpectedPosition(*(move_group_->getCurrentState()), start_config.toRobotState(),
467  joint_position_tolerance_, group_name_))
468  << "Robot did move although \"PlanOnly\" flag set.";
469 }
470 
484 TEST_F(IntegrationTestSequenceAction, TestNegativeBlendRadiusForPlanOnly)
485 {
486  Sequence seq{ data_loader_->getSequence("ComplexSequence") };
487  seq.setBlendRadius(0, -1.0);
488 
489  moveit_msgs::msg::MoveGroupSequenceGoal seq_goal;
490  seq_goal.request = seq.toRequest();
491  seq_goal.planning_options.plan_only = true;
492 
493  ac_.sendGoalAndWait(seq_goal);
494 
495  moveit_msgs::msg::MoveGroupSequenceResultConstPtr res = ac_.getResult();
496  EXPECT_EQ(res->response.error_code.val, moveit_msgs::msg::MoveItErrorCodes::INVALID_MOTION_PLAN)
497  << "Incorrect error code.";
498  EXPECT_TRUE(res->response.planned_trajectories.empty());
499 }
500 
513 TEST_F(IntegrationTestSequenceAction, TestIgnoreRobotState)
514 {
515  Sequence seq{ data_loader_->getSequence("ComplexSequence") };
516  // We do not need the complete sequence, just two commands.
517  seq.erase(2, seq.size());
518 
519  // create request
520  moveit_msgs::msg::MoveGroupSequenceGoal seq_goal;
521  seq_goal.request = seq.toRequest();
522 
523  seq_goal.planning_options.planning_scene_diff.robot_state.is_diff = true;
524 
525  ac_.sendGoalAndWait(seq_goal);
526  moveit_msgs::msg::MoveGroupSequenceResultConstPtr res = ac_.getResult();
527  EXPECT_EQ(res->response.error_code.val, moveit_msgs::msg::MoveItErrorCodes::SUCCESS)
528  << "Execution of sequence failed.";
529  EXPECT_FALSE(res->response.planned_trajectories.empty()) << "Planned trajectory is empty";
530 }
531 
544 {
545  Sequence seq{ data_loader_->getSequence("ComplexSequence") };
546  moveit_msgs::msg::MotionSequenceRequest req{ seq.toRequest() };
547  // Create large request by making copies of the original sequence commands
548  // and adding them to the end of the original sequence.
549  size_t n{ req.items.size() };
550  for (size_t i = 0; i < n; ++i)
551  {
552  moveit_msgs::msg::MotionSequenceItem item{ req.items.at(i) };
553  if (i == 0)
554  {
555  // Remove start state because only the first request
556  // is allowed to have a start state in a sequence.
557  item.req.start_state = moveit_msgs::msg::RobotState();
558  }
559  req.items.push_back(item);
560  }
561 
562  moveit_msgs::msg::MoveGroupSequenceGoal seq_goal;
563  seq_goal.request = req;
564 
565  ac_.sendGoalAndWait(seq_goal);
566  moveit_msgs::msg::MoveGroupSequenceResultConstPtr res = ac_.getResult();
567  EXPECT_EQ(res->response.error_code.val, moveit_msgs::msg::MoveItErrorCodes::SUCCESS) << "Incorrect error code.";
568  EXPECT_FALSE(res->response.planned_trajectories.empty()) << "Planned trajectory is empty";
569 }
570 
583 TEST_F(IntegrationTestSequenceAction, TestComplexSequenceWithoutBlending)
584 {
585  Sequence seq{ data_loader_->getSequence("ComplexSequence") };
586 
588 
589  moveit_msgs::msg::MoveGroupSequenceGoal seq_goal;
590  seq_goal.request = seq.toRequest();
591 
592  ac_.sendGoalAndWait(seq_goal);
593  moveit_msgs::msg::MoveGroupSequenceResultConstPtr res = ac_.getResult();
594  EXPECT_EQ(res->response.error_code.val, moveit_msgs::msg::MoveItErrorCodes::SUCCESS);
595  EXPECT_FALSE(res->response.planned_trajectories.empty()) << "Planned trajectory is empty";
596 }
597 
610 TEST_F(IntegrationTestSequenceAction, TestComplexSequenceWithBlending)
611 {
612  Sequence seq{ data_loader_->getSequence("ComplexSequence") };
613 
614  moveit_msgs::msg::MoveGroupSequenceGoal seq_goal;
615  seq_goal.request = seq.toRequest();
616 
617  ac_.sendGoalAndWait(seq_goal);
618  moveit_msgs::msg::MoveGroupSequenceResultConstPtr res = ac_.getResult();
619  EXPECT_EQ(res->response.error_code.val, moveit_msgs::msg::MoveItErrorCodes::SUCCESS);
620  EXPECT_FALSE(res->response.planned_trajectories.empty()) << "Planned trajectory is empty";
621 }
622 
623 int main(int argc, char** argv)
624 {
625  ros::init(argc, argv, "integrationtest_sequence_action_capability");
626  ros::NodeHandle nh;
627 
628  ros::AsyncSpinner spinner{ 1 };
629  spinner.start();
630 
631  testing::InitGoogleTest(&argc, argv);
632  return RUN_ALL_TESTS();
633 }
#define ACTION_OPEN_BARRIER_VOID(str)
Definition: async_test.h:116
#define BARRIER(...)
Definition: async_test.h:108
robot_model_loader::RobotModelLoader model_loader_
MOCK_METHOD1(feedback_callback, void(const moveit_msgs::msg::MoveGroupSequenceFeedbackConstPtr &feedback))
JointConfiguration start_config
The configuration at which the robot stays at the beginning of each test.
MOCK_METHOD0(active_callback, void())
std::shared_ptr< moveit::planning_interface::MoveGroupInterface > move_group_
MOCK_METHOD2(done_callback, void(const actionlib::SimpleClientGoalState &state, const moveit_msgs::msg::MoveGroupSequenceResultConstPtr &result))
Data class storing all information regarding a Circ command.
Definition: circ.h:49
Class to define a robot configuration in space with the help of joint values.
Base class for commands storing all general information of a command.
Definition: motioncmd.h:48
void setPlanningGroup(const std::string &planning_group)
Definition: motioncmd.h:69
Data class storing all information regarding a Sequence command.
Definition: sequence.h:54
void setBlendRadius(const size_t index_cmd, const double blend_radius)
Definition: sequence.h:130
void erase(const size_t start, const size_t end)
Deletes all commands from index 'start' to index 'end'.
Definition: sequence.cpp:94
moveit_msgs::msg::MotionSequenceRequest toRequest() const
Definition: sequence.cpp:67
Test class that allows the handling of asynchronous test objects.
Definition: async_test.h:70
MATCHER_P(FeedbackStateEq, state, "")
const std::string JOINT_POSITION_TOLERANCE("joint_position_tolerance")
int main(int argc, char **argv)
TEST_F(IntegrationTestSequenceAction, TestSendingOfEmptySequence)
Test behavior of sequence action server when empty sequence is sent.
const std::string GROUP_NAME("group_name")
MATCHER(IsResultSuccess, "")
const std::string SERVER_IDLE_EVENT
const std::string GOAL_SUCCEEDED_EVENT
const std::string TEST_DATA_FILE_NAME("testdata_file_name")
const std::string SEQUENCE_ACTION_NAME("/sequence_move_group")
::testing::AssertionResult isAtExpectedPosition(const moveit::core::RobotState &expected, const moveit::core::RobotState &actual, const double epsilon, const std::string &group_name="")
Definition: checks.h:42
std::unique_ptr< TestdataLoader > TestdataLoaderUPtr