moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
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
63static constexpr int WAIT_FOR_ACTION_SERVER_TIME_OUT{ 10 }; // seconds
64
65const std::string SEQUENCE_ACTION_NAME("/sequence_move_group");
66
67// Parameters from the node
68const std::string JOINT_POSITION_TOLERANCE("joint_position_tolerance");
69
70// events for callback tests
71const std::string GOAL_SUCCEEDED_EVENT = "GOAL_SUCCEEDED";
72const std::string SERVER_IDLE_EVENT = "SERVER_IDLE";
73
74const std::string TEST_DATA_FILE_NAME("testdata_file_name");
75const std::string GROUP_NAME("group_name");
76
78
79class IntegrationTestSequenceAction : public testing::Test, public testing::AsyncTest
80{
81protected:
82 void SetUp() override;
83
84public:
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
90protected:
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
100 std::string group_name_;
102
105};
106
108{
109 // get necessary parameters
111 ASSERT_TRUE(ph_.getParam(TEST_DATA_FILE_NAME, test_data_file_name_));
112 ph_.param<std::string>(GROUP_NAME, group_name_, "manipulator");
113
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
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
147TEST_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
170TEST_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
198TEST_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
227TEST_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
256TEST_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//*******************************************************
333MATCHER_P(FeedbackStateEq, state, "")
334{
335 return arg->state == state;
336}
337MATCHER(IsResultSuccess, "")
338{
339 return arg->response.error_code.val == moveit_msgs::msg::MoveItErrorCodes::SUCCESS;
340}
341MATCHER(IsResultNotEmpty, "")
342{
343 return !arg->response.planned_trajectories.empty();
344}
345
358TEST_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
447TEST_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
484TEST_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
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
583TEST_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
610TEST_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
623int 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_
actionlib::SimpleActionClient< moveit_msgs::msg::MoveGroupSequenceAction > ac_
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
const moveit::core::RobotModelPtr & getModel() const
Get the constructed planning_models::RobotModel.
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")
const std::string JOINT_POSITION_TOLERANCE("joint_position_tolerance")
const std::string GROUP_NAME("group_name")
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