From 33ee9b7f31e45c3d612f8331d3ee20c3e88b6d86 Mon Sep 17 00:00:00 2001 From: duongtd Date: Mon, 29 Jun 2026 13:45:50 +0700 Subject: [PATCH] add file test --- CMakeLists.txt | 41 +- include/mission_adapters/mission_adapters.h | 25 +- src/mission_adapters.cpp | 65 ++- src/robot_control_test.cpp | 38 +- src/test_mission_adapters | Bin 132984 -> 0 bytes src/test_mission_adapters.cpp | 578 -------------------- test/mission_adapters_test.cpp | 360 ++++++++++++ 7 files changed, 469 insertions(+), 638 deletions(-) delete mode 100755 src/test_mission_adapters delete mode 100755 src/test_mission_adapters.cpp create mode 100644 test/mission_adapters_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fc8dce2..95e1ed6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,7 +92,7 @@ else() catkin_package( INCLUDE_DIRS include - LIBRARIES ${PROJECT_NAME} ${PROJECT_NAME}_utils + LIBRARIES ${PROJECT_NAME} CATKIN_DEPENDS robot_costmap_2d robot_nav_core @@ -105,7 +105,7 @@ else() robot_visualization_msgs robot_nav_2d_utils data_convert - DEPENDS PCL Boost Eigen + DEPENDS PCL Boost ) include_directories( @@ -218,11 +218,11 @@ endif() # ======================================================== if(BUILDING_WITH_CATKIN) - # install(TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_utils - # ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} - # LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} - # RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} - # ) + install(TARGETS ${PROJECT_NAME} + ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} + LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} + RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} + ) install(DIRECTORY include/${PROJECT_NAME}/ DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} @@ -237,12 +237,12 @@ if(BUILDING_WITH_CATKIN) else() - # install(TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_utils - # EXPORT ${PROJECT_NAME}-targets - # ARCHIVE DESTINATION lib - # LIBRARY DESTINATION lib - # RUNTIME DESTINATION bin - # ) + install(TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME}-targets + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin + ) install(EXPORT ${PROJECT_NAME}-targets FILE ${PROJECT_NAME}-targets.cmake @@ -290,24 +290,15 @@ if(CATKIN_ENABLE_TESTING) message(FATAL_ERROR "catkin_add_gtest NOT FOUND") endif() - find_package(GTest REQUIRED) - - add_executable(test_mission_adapters - src/test_mission_adapters.cpp - ) - - target_link_libraries(test_mission_adapters - mission_adapters - GTest::GTest - GTest::Main - pthread - ${catkin_LIBRARIES} + catkin_add_gtest(test_mission_adapters + test/mission_adapters_test.cpp ) if(TARGET test_mission_adapters) target_link_libraries(test_mission_adapters mission_adapters ${catkin_LIBRARIES} + pthread ) target_include_directories(test_mission_adapters PRIVATE diff --git a/include/mission_adapters/mission_adapters.h b/include/mission_adapters/mission_adapters.h index 4b0f057..6986ef8 100644 --- a/include/mission_adapters/mission_adapters.h +++ b/include/mission_adapters/mission_adapters.h @@ -7,8 +7,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -68,17 +70,17 @@ namespace mission_adapters class Action { public: - int sequenceId; - ActionType type; + int sequenceId = 0; + ActionType type = ActionType::NODE_ACTION; robot_protocol_msgs::Action action; }; class Mission { public: - int sequenceId; - MissionType type; - int priority; + int sequenceId = 0; + MissionType type = MissionType::SIMPLE_GOAL; + int priority = 0; robot_geometry_msgs::PoseStamped start; robot_geometry_msgs::PoseStamped goal; std::vector nodes; @@ -88,8 +90,9 @@ namespace mission_adapters struct Event { - EventType type; - int priority; + EventType type = EventType::SUBMIT_MISSIONS; + int priority = PRIORITY_ORDER; + uint64_t sequence = 0; std::vector> missions; }; @@ -97,7 +100,10 @@ namespace mission_adapters { bool operator()(const Event& lhs, const Event& rhs) const { - return lhs.priority > rhs.priority; + if (lhs.priority != rhs.priority) + return lhs.priority > rhs.priority; + + return lhs.sequence > rhs.sequence; } }; @@ -108,6 +114,7 @@ namespace mission_adapters std::priority_queue, EventCompare> queue_; std::condition_variable cv_; bool stop_ = false; + uint64_t next_sequence_ = 0; public: void push(const Event& event); @@ -235,4 +242,4 @@ namespace mission_adapters } // namespace mission_adapters -#endif // MISSION_ADAPTERS_MISSION_ADAPTERS_H \ No newline at end of file +#endif // MISSION_ADAPTERS_MISSION_ADAPTERS_H diff --git a/src/mission_adapters.cpp b/src/mission_adapters.cpp index 3a20c46..29b7532 100644 --- a/src/mission_adapters.cpp +++ b/src/mission_adapters.cpp @@ -11,13 +11,16 @@ namespace mission_adapters void EventBus::push(const Event& event) { std::lock_guard lock(mutex_); - queue_.push(event); + Event queued_event = event; + queued_event.sequence = next_sequence_++; + queue_.push(std::move(queued_event)); cv_.notify_one(); } void EventBus::push(Event&& event) { std::lock_guard lock(mutex_); + event.sequence = next_sequence_++; queue_.push(std::move(event)); cv_.notify_one(); } @@ -47,6 +50,8 @@ namespace mission_adapters { std::lock_guard lock(mutex_); stop_ = false; + next_sequence_ = 0; + while (!queue_.empty()) queue_.pop(); } // ───────────────────────────────────────────────────────────────────────── @@ -74,7 +79,13 @@ namespace mission_adapters std::vector> missions; std::vector action_node_indices; - if(order.nodes.empty()) return missions; + if (order.nodes.empty()) return missions; + + if (order.nodes.size() > 1 && order.edges.size() < order.nodes.size() - 1) + { + robot::log_error("Invalid VDA5050 order: edge count is smaller than node_count - 1"); + return missions; + } for (size_t i = 0; i < order.nodes.size(); ++i) { @@ -169,6 +180,10 @@ namespace mission_adapters void MissionManager::submit(const std::vector>& missions) { std::lock_guard lock(mutex_); + + if (state_ == MissionState::EMERGENCY) + return; + if (!mission_queue_.empty()) { mission_queue_ = {}; @@ -188,7 +203,6 @@ namespace mission_adapters case MissionState::COMPLETED: case MissionState::FAILED: case MissionState::CANCELLED: - case MissionState::EMERGENCY: case MissionState::CLEAR_EMERGENCY: state_ = MissionState::QUEUED; break; @@ -230,6 +244,10 @@ namespace mission_adapters void MissionManager::onNavigationDone() { std::lock_guard lock(mutex_); + + if (state_ != MissionState::RUNNING) + return; + current_mission_.reset(); state_ = mission_queue_.empty() ? MissionState::IDLE : MissionState::QUEUED; } @@ -237,6 +255,10 @@ namespace mission_adapters void MissionManager::onNavigationFailed() { std::lock_guard lock(mutex_); + + if (state_ != MissionState::RUNNING) + return; + current_mission_.reset(); while (!mission_queue_.empty()) mission_queue_.pop(); state_ = MissionState::FAILED; @@ -374,38 +396,59 @@ namespace mission_adapters void EventProcessor::pauseEvent() { - event_bus_.push({EventType::PAUSE, PRIORITY_PAUSE, {}}); + Event event; + event.type = EventType::PAUSE; + event.priority = PRIORITY_PAUSE; + event_bus_.push(std::move(event)); } void EventProcessor::resumeEvent() { - event_bus_.push({EventType::RESUME, PRIORITY_RESUME, {}}); + Event event; + event.type = EventType::RESUME; + event.priority = PRIORITY_RESUME; + event_bus_.push(std::move(event)); } void EventProcessor::cancelEvent() { - event_bus_.push({EventType::CANCEL, PRIORITY_CANCEL, {}}); + Event event; + event.type = EventType::CANCEL; + event.priority = PRIORITY_CANCEL; + event_bus_.push(std::move(event)); } void EventProcessor::navDoneEvent() { - event_bus_.push({EventType::NAV_DONE, PRIORITY_NAV_DONE, {}}); + Event event; + event.type = EventType::NAV_DONE; + event.priority = PRIORITY_NAV_DONE; + event_bus_.push(std::move(event)); } // FIX #4: Use PRIORITY_NAV_FAILED (correct constant name). void EventProcessor::navFailedEvent() { - event_bus_.push({EventType::NAV_FAILED, PRIORITY_NAV_FAILED, {}}); + Event event; + event.type = EventType::NAV_FAILED; + event.priority = PRIORITY_NAV_FAILED; + event_bus_.push(std::move(event)); } void EventProcessor::emergencyEvent() { - event_bus_.push({EventType::EMERGENCY, PRIORITY_EMERGENCY, {}}); + Event event; + event.type = EventType::EMERGENCY; + event.priority = PRIORITY_EMERGENCY; + event_bus_.push(std::move(event)); } void EventProcessor::clearEmergencyEvent() { - event_bus_.push({EventType::CLEAR_EMERGENCY, PRIORITY_EMERGENCY, {}}); + Event event; + event.type = EventType::CLEAR_EMERGENCY; + event.priority = PRIORITY_EMERGENCY; + event_bus_.push(std::move(event)); } // ───────────────────────────────────────────────────────────────────────── @@ -483,4 +526,4 @@ namespace mission_adapters } } -} // namespace mission_adapters \ No newline at end of file +} // namespace mission_adapters diff --git a/src/robot_control_test.cpp b/src/robot_control_test.cpp index bd80e67..be607ee 100644 --- a/src/robot_control_test.cpp +++ b/src/robot_control_test.cpp @@ -1,6 +1,8 @@ #include #include +#include + using namespace mission_adapters; class RobotControlTest @@ -23,7 +25,7 @@ class RobotControlTest } public: - RobotControlTest(); + explicit RobotControlTest(robot::move_base_core::BaseNavigation::Ptr move_base); ~RobotControlTest(); void run(); @@ -32,7 +34,8 @@ private: void executeMission(const Mission& mission); }; -RobotControlTest::RobotControlTest() +RobotControlTest::RobotControlTest(robot::move_base_core::BaseNavigation::Ptr move_base) + : move_base_ptr_(std::move(move_base)) {} RobotControlTest::~RobotControlTest() @@ -43,31 +46,43 @@ RobotControlTest::~RobotControlTest() void RobotControlTest::run() { + if (!move_base_ptr_) + { + robot::log_error("RobotControlTest requires a valid BaseNavigation pointer"); + return; + } + robot::Rate rate(50); - // FIX #1: Set the callback ONCE before starting the executor, not inside the loop. mission_executor_.setMissionCallback( [this](const std::shared_ptr& mission) { executeMission(*mission); }); - + event_processor_.start(); + mission_executor_.start(); - while (true) + while (robot::ok()) { auto feedback = move_base_ptr_->getFeedback(); + if (!feedback) + { + rate.sleep(); + continue; + } + auto nav_state = feedback->navigation_state; - if (nav_state != prev_nav_done_state_ && nav_state == robot::move_base_core::State::SUCCEEDED && areActionsDone()) + if (nav_state != prev_nav_state_ && nav_state == robot::move_base_core::State::SUCCEEDED && areActionsDone()) { event_processor_.navDoneEvent(); } - else if (nav_state == robot::move_base_core::State::ABORTED) + else if (nav_state != prev_nav_state_ && nav_state == robot::move_base_core::State::ABORTED) { event_processor_.navFailedEvent(); } - prev_nav_state_ = nav_state; + prev_nav_state_ = nav_state; // Example: receive an order (replace condition with your real source) if (/* new order available */ false) @@ -86,10 +101,3 @@ void RobotControlTest::executeMission(const Mission& mission) // TODO: send mission goal to move_base_ptr_ (void)mission; } - -int main() -{ - RobotControlTest test; - test.run(); - return 0; -} \ No newline at end of file diff --git a/src/test_mission_adapters b/src/test_mission_adapters deleted file mode 100755 index 80f4a89dc3c7e0a3a7e89db134c45ec3a0e637cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 132984 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%0AKYY^Mehg}fhC;ccpI5&?Ru|65-DKQsmS zWEX)ZI$i2NO7-XP=a()1Z;C45|E-+-d*RW4;l+9K;LF<)wvZ+YG3`+NUq4Fngw6N? zwk=lHzCsb`MTk2U;#zMgeG8?3!G|h(g_8bdoG(rjJ4P{q=6SS2dhPrE4uGLm>8j1J#Kos7*Xo4*ufY zhutDebugqDb=MPYmCphjfTBEgwr(U<8J$Y)tV3U zI~_pA02}eHYXcg{O;IDZO`^ z8%dlgU3)OPc1&S^pY&U<$fnB;aG6>RADs^$ z-DZXKQrhFsGcCFV`27|>nG9L@OyDj}5ovi(zoGDai4C`ZHG8zXs7N6Uv6 z6W3<_Wpm%3qJ2D7pB|c$CYZK^rvs@hC6q^knp7 zBuVTIAyhPh`gNXI_6shT(QpN?@H)QTq|?qoEdb8qO?84JkbA35w5KrrHTs(Mk@qee zqkVYM)Ok=8*EYP{#LKC>I)Y{p*e;l2 z6l-SCzG+gdEooov4i-f{`gy6{A`ezWUD^1u-sEww=8vE1Efqq}V@TaODWSKD&)~c} zr?kW0p{-R|0=K@d1xUKAvO3S*v5o$JsiB^ZNqbPp?-O0M~!hv z{An90y`VTS{_ii*1FNr1=46jB?INprYi((@>>1Res_%5G{I+MyVL0n_OH`2Hc-d)dE zk6w%alfudr8iimmy@~(@o7o%#V`%W2WOH1S@4&ngn`87zH4_SDIxdd@u${4^R(6%I zBf-`NhwYSnk;-hnRX6wMmAtOpk1y@oBz3Z`LMNL4+SEr~is;4syykA=tyvDxn-V9H z=kzwCXpV!@nX-*t0;GI>Y)a8dR_3q;@`)#ybz1WY(S5H6uh5RHXlig@)ZJwcb)&Y{ zO0LZeA$I*WjlFlfPhrQk$`Qdo3yC=H0r+*om1Ao2I7ibGM+Z6xH?=fcPPAaK=^Z-V z>Irl<=L&a?q>CsfP;8@cx|YopnVeP@Id)nJ{LvW#sOd7Fe-tL`@sJ6Z^*9LpV@G!5 z855I8q{M2FhgVnz5U+5+wfMwIOdDR!zB($T>;4rRo~w7%OmplS0iPN@R>3M_dyqUF zB&A~w%as2s&XBVX?+RAozio4KFv~9b>}B_kS!Uz_;}i;9b`B>}Q?{YGfm^!6Ac^p9 zclzU&>JZHm{TGDzsvged%8PU03*+q76%F&jp>uxvI`5|)8D8CFBI|2$ZyRCYrV>AC0 zoOx8K@2|NPWU}^ow5OPVK||PrALic8qhI2aULE>uSU2>9zd`SBei}^**!%VFR#Vk3_fR3^ zVLd`vukcemNcrNT?xTPWEr%VM$Ky~a!W53Zfs(LZ15|E_&05$%kg z&`Iq7u6?=QY*+fkH?bW_q zpu#j;CSm@+Y+vG!Qg;{o^5?hzCQJOYzeUr0il} z!j$Z8NOd$5(;XkI%)|&OhI#(5|IEc8l?+z+qmow{}HNmFKnMQS((_<{F z=q;wbFq~3- z)Q`){MB)`!QiO@ajWC0eJmU3ibKR%ct1A_PJ87C)|1SnZ*+5(qsGA#&%kijvb3Pj8 zMcqc$Z13x&?lm)3`Y_X{f^?I8b|GM1U@q~y^?ulHVkEPjeKzyb57$lD=-d>W%0xlU z0fd~^?=6E8jN<~WBM+o0f$^!T;T2<3L$NiJbTG-o?!Y(kQp?|7a-^kZYV{V9Dfe(C z8=E?CxI6BBk%$RXCkY)-xuB3^PZ|7cwnT(1bT7V+x&2!KnWGkdq<3aNpL+qb208&< zPI^&%iBeZsvR}9k^0B6H9kt~wjMSSsZuQjibz<9`sT|8JiQSQ7)iRB-8(jv~TY^_s zi4Vb;orpShH|a25q=Aa!Do@A0&zVHLzN(GlqRCZip57Hi%eUHNpPiJQOIGY%J^r~G z5B=ohCYYEg-x9mPj631JGr}`k#q}I#9(-BF`oT`T)QBh~oG))@7Kbfw1C&P1fY9N64p@z7ApM zItlh1mOmS8Y69G)5q4bFiC|T6i-)w|1`k)E-34JvvIw8`@TU+yuq(oUAw?G9A6p{d zx|{qESq3)xrmX@so;A$cnj=BPMUSb2ciu(Kjy=!^D}{75VC2 z6Q@d-wwQZ%dOLYIVhc94Gt2DeT}`vMa%bd}ou%&MG`s!TAmJpi%HSxvG42Edq`B0YHL5)qJzHV{BJ zCAx7x)`*>+Wza1ZkebEf4lj+BlBN%dv``-|B2+;sF&^eLz?j~}#2?yB&s#JALap4x z{W>a49k+g8V|&ia=gOB3{x%f^?Ae&R+*4^N5!}tMz#q)O@^OO&!-GwC^F+OcnjZ-H z@te_O6)e#jE>xkF`a~4p1mL;AdnPWN5Ho$_U=i7~yGZ#WAe$_|uku+1iavy>G973G zg!Qq5Fq)dVj_O1l4cHo+;CiP#@I{)m^wR#|B zk(<6s-3EhYO;7wJ=69F*=7zoH75dJ>Ets;E(g=n4>#)olZV*$)hKsT^#}@IuXi7}9 zx+imSpSs^l5#cCymPI%FWh)<@t$aR}!+q{u4{8m^|9)LGHRA9|FaZJEPkZ0esMG8D;F>jbTP#O@U_+tEZ=D^sTW51@W zuM2c~h?2jx(9j6AJ_$=oJ^%OjUkdz}0{^AJe<|=^3jCJ>|E0ixDe%Q8kjF8UlgE!6 zKjEzLfk;&}95`e0`0Zdv9MD3Y-uK)h(%87~59`ow$HEQ)?of3QUZst<%R$sEh_COmf0!1ja0yF~4%wjQUtz z18FC2z3+#i1@jg!p0{XWD7N+9Uo8qX%uwc%x~26X8yZr&Z=ht}!dR)7Xz}rj=UrT< z%0rVbs;^Vh&>V_9e(8vSy}4*aV8L;x8CdG-m}yt^ncly|gZ zRAn?evU1e8X<4im*If{qH6rls@WjCQ34z6n8e(;`02|NA6MHIY$Oy|W z6qC(qAwB{p^_1 zoEHl%j?IW!@-Lnd4=s+LyI@|dQnhGd-Qds#@w#~3h#{euulSL=yBoaB!jD=or{VayGZts- zwJAr>m^Z&}mY;RzjD<7n=7WA=I4IRcX63#ave!2!c6~E*A=<#NcPRA^4J>MyRo8G_ ztZs2EaP+Le$Z)tO5Uv^%MjoodfitTrhm{R0>y+sUN*!w0df!hM&hd!i32(O8GKvj_ zhJ+R_nnnMXg=Wl@gmz85SV=-5k2+HW)5KcJuy`;xb+hK!8=IBHULC%I2%R<}G{j4{ zZy*{OJtfp>al7fv;&;G_!6o+90HE zshiU*}xN6_P%QyZMn8#fw==Fq2l~Fc;5ufMjsnE;It_h7=v~Xs;p#jYms*f+8djguv zw{j>?wNrfV(Znd)lXF%1r^>7GR9?t*{=9{A0#IVo+^zTh7Oj5YpRyXss7RyXyS|UD zYlK;5ZN2w53qvIf<}EyKZrzM}DHD|ga)_6xxHx8RV+~vHy(zS0>wU`&eb2>?oll)! z!iCn5)v3Lz{r92RT&&%!(D`%M{%WQvQ}}z0tDdoN#vJ%nRGBE97OI{xbMCx_bxNb3 zG=NcdR`$JYCh5s18$4a)q0kt zKC_%`+Rv%a^L2z*C}bHNvX#tS`{$6yZ_^i8!{F)5uIUyo@{@U5|Fp7fy3fRKR5!=T z)IVwKeb>^?#qu%c8`knQtC%1Z8aHiNl9RgTs~BPg&8G8=tC!MP-q6gocP^B&-g@st@ldP*iz;DTTsLO{ z5@v-Id_S{)EdPhT``yrICd&~g@Xi|75cUrDuwla;TkpGJrfo}>QWo!-U97G}-x&G8 zzAs;UjrD>5U;O>4(Y@-=m#_U{DCB#U(`Q&0c(Kttqw#%0lQ6HLiSc=8pb$>`5B-cU zUwi%k3&b^gc=_6&?;AjCEL;=|%|wNQ+Tz%(lTV%{4lzvsH83a`e^Vy&(k+?Hxm<_boXK3m|A+bi-mfy5fxtwcb7w4` zH*@;pSOd&uMkq5EE#xWAAVB^WJd0S)m_Kip67%Z;FFU;18)7!wUjI&v4H+`MIv%TA zIvrH%BGK{X)2Gi_7@t0K>Cz!5PLE<^rq?f;KX2wm;SR%BN4PpT1zm`E}D5 z&z;dwH*0!q28M4hKhl3g>5YOR?H8&fr>yZ4W8bib3_OkbnLckp{rt%IiPMKqk1SlW z=={2d$i&#NSiF9IU1V(a_#xl$RBe{oh72`=k@a1|qn9iR*GwEbJ#2nCmGyA@pQwMB zXFYvi_uoO_n12(2?+h6#k9B&T{DA2UW|o8}&tDSWYg>=`&WS@z2A^tmoHN&5{O8Lr>q4uEu@g_qVaYcbtq(jUqOYr1PZ-tpkngE^A|3nsnamXMuOL#o82sy_nalv;J&QRe@ND7ga_!*y zvGA_TWD3EvjB7F1Wn81eo9jxhcW}L#Ya7=Vu7TN=2j%1q`qt{sF`1oo$$KViO(>qf4% zTw88r&W>x~X7D)>IM)iUg}=^Z=5k$mYbLXr>m9dcG6nR*#yg-F*JXc#-dqds%4CKg z%=NFdTi5$DnJrvf9>`=$4xwB^SC?|FAwv5Ru6I01e-I#1_&n|6ddF7iLBF>_BPCkBAYkqkbpApDgT9|kEfPMuli762L2>xFOPoJO^RsEnqAW+Qzq5LnqVNYfm zAy^uta*=A>fc(QN*+xnA}2JsFcxKz=GW#{Xr&>y_X8Da{K0 z7a=pla$u{tf3h}{5xeN8ba(N8BQR5u2YZC;j&86QfECQhWMBpJ6Yd@S&j&VEkkJDR zSM+iQ7B%)BGcdGpU{Te;!jS_DOdUngU@+vdNoepDrpo3&5=IPP}+*=;399VQk zZz$H7-|LKh2NqQtGL9p=cWY(tHu9CAQ~#ZOA*$F$zFP8KtNOwN3mbbufnLq~4lJx3 zSWwwN-~cYj4+LHy&9h3g*S_xc3%a^0hj7vU-|E`clKDxLRgbRMXG%Y(PpSsi^!jIy zT@vj-siz*Pp-Ay%2%CEnHXwf-+-&-N^4|M>Wv|{#@_X!H2Xhb@%zyoIc&+jqSza5s zvR7ljEBdbJlghukw@y4XI!ygI7#%2Gl;7VS< z|B~$eC=gJ-tH}2P`E-P5*L=OsBr%$aj86ZmY)1O6f}}dQw-|f+OUb|u7@2N;7wMGK zZ-_s0JFkkmRmObED8wiEI*KyC-j4hcT_Fg9SM7 zU%wps<)OnpStVa%pDXfL^iKJ-D8#NM>w7=gv(6gRzX!S} zPU@t4eJ`7+o1yHULSBv133+r0;U%A>3nG%kp}^({o`)MkQa9;NvOy#9`sr z+v4ZhbjtK>I&^L9TQzWHUhl^!P6jd8l1pWnwyvVAv+=9vz|Z-c@sc{*+G%?``)KUI zjd{HugLr_t+1RoDPo>Nb%KI3(7%m?Cp)D_$2R$8B+iPbxjsv3}_-%PvC%@Hp=eNs# zvL_?0lYgT1?o@w0{C59e*n{|g+Pn7nsEX@9cXuZVc@a{Ah$suFRfNrM2oP$m0kS{< zA(E)nDmNinU~4v;<|VX0{l$XRT9jC|KCm=aDfocKLM>Py@l~x>OsiNwiW>VS^wG{&aXgn+X$ukMAL5lgU+wzcYCt+@4_2#<{0^1=f>K*3w3kh5bQ13pSWfm zMcq1_&TT_&>n8Ztrf!=(vAT6Qgzf{!iZmQc?V|A;*9xx3*|KChJdz`Kt z^#5e~B38UR9kz87^24TXL*nX&b=GzUkl>P6aj8)k4E}=CMe7N2LxvlihIob7&gXuz&OYPI2A#`>( zrox?<;kYxhNZk<&19%#`I3pXw%Xn9==&AV%6=R)C%@?oZT+5mk`ScRMwp+YnL+;8HSE?l>**ZSsgB?k!@R+=#Pn@Ng+;e*gYX` zuDg=VrxUL~5_uc>T;G;;L(|#IIj{%7KGQ`6QLpu+hp!_&e2r@kc0Wtwc^xgKH6#=2 z*j2FQ(4JoE;0v-+O0IFPcR6#7bQnf}w*aqh1K+n_2b&A6qY-pYpZcY<(|*3r6YKnY zpx1Rh&Tr9N__O8#JKc97@jwZ5!n^IKf~5C#@NhdE76^JzeK~qG&g((%(D9MT|A3ub zzX3g=vo209H#;28=TkAxJr{d1SE;YNL318>YAW#*wRuK%hjpDHymlzs*C;O1$d?&@Weq!%T^vL$v zx&vz$={V!H%(2jn>4LQ1`ll^-Knk>i-eID5KWwa|-<+W7i8kBmzF4}Px%NX+n&Dh$ z-x+f=9Q$lrgt|~>z}`S} z|1j!+K>7vo<-w<5BPTw3U)Jqf$AUzgqgcO7vY{qlj;31oCt@9;b{33mDJ@w;@sG22*|`IrF^HHAWCW1f-zRVYa73=X@j1{3|f;{d+L;QJly#}591gWc)` z055W9P?-lE9QLbL=aK9Uxde5cDBou8F*v>^edypfy99icglAl7RJS_V7lur_au8Gb zryK6?;PH8s&y5f0JK85MdhFmF{;>CO0936|$Z;EuPaJo-*v|;>P8Y{xCq3d-Mw`KR zrLi4p*iInHXYqR-qWmoRo-g8^34jqho%|qYPf!V6oE{_eh`e4uHSjYBKa#{BP-p2>E&_|G%hAyhAuZQ=atOqdY1@%F>fKAFOA8NyykTME$rw8SfA2%3zOX;Kj2U9FGU+kzTdDlYUh;+nTu)W`P&8^6|Vg zTfTb6S=<3CYaCw~-6mV>;xC#Q#1AR4HRVm$dnUUlqw*gn`)wxhpU&iMmF$H~zP6I> z&wP#&Pv!#tr(C|XlD(G8J1f~C@qA=>B`6%ps|3of<9^`8>pv3Hy*`TXTFTxT#h+Np z4v+e!3yFuu0RGGvzHcepJBEL9$Jt=Wp<>m2B(TFFTRgUQ&rJ*zTv+Y@bc7**>Qd zjoCi85>?+`T8WBopVw`mUfUN`q88iF={8X4_C?)9cKf+kI*@-)Id$R36_o$_^C|ze z7gQq4jwPKg6!Q4R)P2ufOkMNGi!(v;jf*q!`u&Tkk)K@5LE?*xsbSw=O3nD;(ox8@ zc`3cUcPWj+j-^BaBh5$r3>p|7+Ku^t#nbJa@6BgxQ~Bn6wjq`OGoNiu{lbC7{pk-n z9?EB%v*=An7QZi_b!HXgd2{wHj$88C4~JzTao4a19ly_K?+pL36R9gk^7r%Ett08p z10(NuyqV9AjBYa!?LLXfw2t9ddfA>aZ3YOmjr~O`3V7+XHUkv4P9TKeO}NXY%0`Q+H#?=mfD8;$Y z!4ISuyHn`-FDZC=Z7RPq&ABI)A5LXIp-B{*BVRiIcZ}t04gTjmw$s6%%_9-NC6B$% zXKu`6|4Jc9cPd|##~w=KpA9!&PUr6qXCJ2X4SB3Bi=Gc<@kjF5)7gASp0Oc^@NXSP z@So;#P}nz|-o81UD8#in&&ZmMR(z8$Wk(G7u9vSO8~sMnPJ=$%9d?jtUPoQ^mW$(h zjH^gw-p%>zoIS|-`V{96s9(0F@L#2{N9i%zBjA*c&Bi5-fT8an$S^iI_#5fYHywQM z5Mv!bjsEhtTD>q_Hc-z5W$Yk4GrN7Q(*QbtrE|WcxHuB+2 zwmH2J@GCOfji)l%57ZL1$(T!v#|I96^~vl9&I88XDKAit$ z9J?j&>>rP3caK>9(Rg;paXTC@jbnR8y+IWBjDCa4+H+Ey0WxdcZ3Z&!7+Zgd){>Ulu66bgp3;=}Jjrp%YC~QtyXZ$FiJ&}5y zBjRE0>HK*Q+ns*3<6{r|Jd>~Svg@-%;>Dr-jePe0P?6Y_%RkF!Pv!D=^VyrZ8y&BD z*!6j*Aj`o#zR}CB81XC0|H?>yM?PC~JpXMz`|0ugm-*}u$MXXo_V)2tJCN8qns4y3 zM@I{a|2T=i0NgSBrhInKm>ep74Bvv%-8T@akH>E_5dHZS{!~8ObZR%jHl1FH8gH6V ziPATn(M|6+O{xUpP7lX3n&>(KT=t*vpC0&65B#SG?0cXrU&6f}8Rw|9#@_l{h*N~h z0}|4z;`;qEt^ao_yd3r3tKwn{-pdNFU6s?R;$qs6#s@l#gm6f`&+*Eat$rEn|05M% zv3g&r;*M&KA!Dz?^XmGj`gN$dUBNr_dzHQOGFcBz&3(B{x2kq%c%_2tw2r%zz$^bd zk*>z?w7}i1_IRyH;N1$|rs}72TjM6RGHCd9mh>wsovAD4mJej&qp)ka%!y-wBCLEs zrqwY(apsL);CP@2tJHh4S5Uww5E1@ONX2X{6Cp>%ZWVh~T&&_U6<4abUd62{ zZc}l)iaS)?sp2jbcdPi2irF|-zKY!{_Nus8#bqk4R5AG|rO>M4HWf?gvHPFaX(SMv zGi%n@+^3zl3_mMtc2D=xySxR(1C&E*s^F9%t#`Jthgb!hQ9woxlnVzqS@C>Hsbs`*6 z&K(bt&rX)b+_vy+)^0Q2PBxTv*ury|`xHC+!=iFD{;iV^ebd2SOK`HU;7qw4d^p=h zPVz0m$?|Yk+zvj1X?Y!=#R=)B<#Zx^Bx_ajI}v_7vy&fANPR7D6Y;S+SooRVJ7L8x zwuPS5z1^9;1iZRB)}oX!;KUPW1m_p+5@qhw5X+U!~w$|IzecQgE&JX!=vI zkPv;hYOk*U^?(z7EB;3cuJtBO|6ERVMze}XsmH0$Ww=PemssE{04MsI&vz>PA1U~Y zr$~UF9$OTg&5&?9zer)Pf`6*;$4D?s!+J~ft@uR>?wutO>C7R8M-@EpRGHR#!6Gc^ zq8~~moX&?)*rMPQ6h3C42rjJJgs=5ZI?qL6g@S+Gg8#gNFI4bSiOvow_)Q9aw}KCW z0zve(zDj3_D1;Pz=2vA}_uESfUZdc2hJwQ175tXdWcqCxu{e9)}Y{8FJ7qNj|(_*EBz%(2cn$lXubJDg@2iVk7Rnkpz&W- z_*$R-mcpM7Mb}QwlmJfs*{=3W3Z2bX@GcAdn+k5-SA_wm{wcHc&*cK%Qx0!dbjp-` z)8*bL@O#ShWcBY7_`}(QHtT|2dtV1WvsrikHG%q}=eqf!px<*{_*~E#(PMqd$R_$d z>wgm9hRr%s2>3Ykf16@VIx|CIhk_UXRi?=pOyM=a$3hIW95f|BC@9 z`d*JDKxbYkydvE(39*)B28tcgl#}sNmh`B?{t9FXC^YQDf0Ey$@U}-pX??M5q045+Ht;fye#8 zC%F542@fg!G%P3tFaB5p#2I=#E(Sb@oxnzRst#88-vpfcS?dW(46#nY?fUsOz=?i` z_)!PGRVg~D*pLyN9gzS!`%K~cfZNfzMd7=L%KC`2h`@hb;ODT;--_&v&L>bPgZy=) z{#HHYHASauk)%WCG$^dZ!ZD0WR{EG;2QLMj>Z$D_XpRVP0B%=Ljt>Qd-=)S~oN+{s zuPJ!jMY3Evn?ZqomzN9tHm^+6Z&N620i4EH+aU@in7yIsv>%oLasC{SXJQ{q<+h$8 z>31pop95}J&rX4_km90!fK$2J&QY!2(S5LXbS?p$%GLHD-9IlXcp1LlP@wbP6r3<5 zPw(vb|%5!qwvcLBz}iP zXBF5I*wKFgaH>z+SrY$t8L=P3AY+IB3&2G`ACQE_ZxZoX0r^0DSnrYWd{u596j3`m z7bby+1RUddvm`)gS}9xwIF-vjm+9-2e0y2JyWf>?pQ`^C3SK`#0=}W(OWgMLsRNw& zR@-AW-@YW^x$Ka#XKMIIN$6ycmGvw>Nft=wi7A|^;7s)!mRAwl0jK$8T^H^J+^*k- zK%u36X#GIuSIYo93vjWHSoUjVZ?U8EkfPK6p``OOSzR`Cyrl0|@XO_ERtosAxc#OQ z$E*@?8uzYg5|MtpM&VL{FQ4Q8->c|%4v_`Y?=dLs1Dxcfwi|2y8G5pWYkP2wL}!-* zPW_NY_P9oBIJqmXy{G180y;`qxWB)^Rm{Y>(w@ehy1>CNlaHA*i^>>1K z5}my(;3^RpWk63R`eh>}{r9+h83dgA-#TBK0kEAp{U+tqV5;6&fL?*AnTe)=SPI%5DA z<1$(Hn>dF}Re)T!D|a;Dgx{(*=yZ0N!VnvBpr;j2y2qixl_@x>XUmEy!dQM zN1SyArR|vDx~~*z5#5t6`#^Ts7IgUH1bn#@(`x{51*w5Gc%US!BZhk${VSw&)w1H2fWj4lyFa zeM#s%tMIMz^L+)^-yd~9ES@6Et=}){YF>H-a63L{2~LHy-!D&feLXWTgij-1p*g#` zPB_36KB0V*&6)5&1;0|lF=6SkbW7y-dExq3QT8t`Z<=n-s9SB;R5ew>!x7wBHTjB6 zg*9W9mOVqRCm{vnJyg%NodUfVhQs8f)I-Y-QJw%DoId+xwiAo;JDwcQ$MDi&8 zN)5PIkCMKB09$SD4G; zh7ue^kM&iJjd<*>j-KjpZ4GdI(}Xl8|Ge5OUW8Tl(pZuD2DWe2NK|c+;^e z1ceJhKhch@3V4mS{{N_m?#5#&LZ^}^{N}m=$sXbDST;XgP9}`n6j1 zw+P*of&wv3v;%unxR!;}&gewy(J?w_e@Cv*8Y;0csJ&n$TT%JyvrTESfoEfoZ}y zy;*8PK3aWdq8{Z<3yMrL7$&#>NLIk(Xumv>_9Pm|z|_!+@?%f2H<{N@@ktV}|BddG zceQiPfwaFr#QQy5eG*qQVqime^wg^xni`v$absOI!-WEY6~RW_8Dchtuu9@u2(0*K zP1syf8(xOByapF1G@4b-t5|gyPH_Xcqr+3=o$9kkGM9sw&8mims?{c(!#1pD%Nwdf z0kft#6k3feaVac|!KMUM)119{#yMCt7nYb{4Rg-I^GyF-1)N*5n3*NtTsY&L(ph+y zkQw?%JfN|u3iqy`n7vJ5C^BZKaYf_Q z8Dh)mSMF8)&=jnfx|GfXr^EHC-`^7N%Xpe;)`c5F=r#D7#ttvsP+A7G^2xv~sb5qf zitpQbu+~%Ib9gWv!PwcGlW`2Npg`7s5Iau|5qN2eaM_KaI(jF@#8|`<@LGKi9ADRCR)pb5 z8FMVEEhz>a5Y;5?N+lizyTTJO6xZI^b$U}+ITQE8L$^qOt>+JR4zcR1ch0E z!F@Oy?r(tz@S5d*v%+UCt|$;oPx&BEuy|QBkfGFEW-F$|?9&)=Jv9JEtSYC?32jIM zWU!)1)?bUyK%Alzt4znx;Z_n%;+BL-Of%R{QYvOsmb6*1O>tDD_wdcGnMr!fKBnTl zuBtBFSQ`k`_mG`^*If-=5+zegu-+Fci~OAoX@dQFa77)7l0K&$Enq?niMdfe1ERFR zyFA=*X;ni_bo~;3_s!BpnD8~hIuqA7(O$owN@f3fL-h^j43<>5<_}*A18$8e?o}HI zpZI)nH%&<)Yd1iN7cNbMfA|*M;D)(juP96rSMrdu)|={u*A);n{lB6hK~+(kmn6Cf zwl#f=_Mp&MqIyVq6Z)2JVDR}0;%|s3ttJ6pjcXT7x{;)*v9y}j_-d?I1>msg-4Uf_ z070sjPwRzqP(YQr3>s#g2_>U?WpVMWB}-<^EH!HWL4Ic8hz^f-%ZJ=&N(@J@h~=lEsb?BrqnepPb61z zfu|v`+=EL~=fmKz!~6xIZmSK)q3vj*MO6cBfl_{-=boWzMEwr9Y{ zCPCQg2D(Xz?$e|D`Gj3U+zw$%+zx?u=B1GO*dr7UY`vG4)?h<2mkj)J4V;fXg_nA- zrnW3`^&tL0#}D2GFM>nqqDoxJDpZ^DrrJi5mXz@Kuq0;IK(+|Dh zMpV>Y8cG;7G$Yw1KC*=|!O}wkj}Da9zzYJfB;j@&D0S7Bl+Kful*Ndm%8BicN9tR# zPmtiC61p!-UigI89rZOrdM_!|DKaGmg$e?GtoMi1D5Cu_G3y~A-fvUo0F(e}`a@f6 zZ@_|ZXhN5n)hjO%pXp)}B$*tt+)EV|Lxaqe@&meAan3Al&7);%DmLtr^RSSK71Ku7 zwBu8;clVc11*c+;*CueL7#d8mUKr4_15_MTAtp&bE+!K*xxY#f_STDRbst-uz&9s$ ziL>4QV?`hw3c#!?43$1#8Rk6fE}{BBjnHNL)-rrk!*vU*T7oNt$&YRURx+ay>y2KK zq#X?$Z}(A`Cc^m(<8>6$Asd5zsVO!pP*kriBx{j>QJi9{HckDxH;%FpAE?D^z*o6h zmu~A-YJv?0xfFc>r8hY!^~Gb+VvCSSY_O^dEgmMmfqlCfH@^+il5H{U4f?W82Ehen z5ES-6aac?0JK%BqEq@^?$ZDf@Y?gyw`(CUD$HWwO6ijU=JQDS7Avhtxt3y2vUf>U# z9Vi*-5~a1+HdUNIs45W+-=G#CbuQe2*brVVHjT#5!hViJki-|kzsq&(D zL0gXr*fv3HEH`79O_G*KH+aHOwG10@u@jO#Q4|a}3azuS6v|@{CYMP4)f5C%H3Woa z><@*Ylqag-(qd8@TvlyXH#Un8o+v2knGsouWbN!Z*%R9>K3T@B>w~+dT2GlMTYd-w z0Fz1*n|OIZs}n{g$%?uvgW!K&9Ld?VqDxV9e`O^>D~kQE*IU&MYZi@Tkb;he0&BM-{b~m z$1Qf-!j=-KtFf~!(+Z%O-+nERHb!krSM{;=a7d zEF&Ger3w=jN;de*A1a?|epg9yDT52YjpJhWl`9X|Hu@8-)XXfcI0ptfeaHtJaysHu zL0<=mtnL0nepwbhbza@2LCmkkfyU+#iI-WkaKeI&EXH3M{`~l91~@aisM7Hz**c%EX=M;rcVYOPEm_4)DV^?I=q`EhiZxVIV~E9=5VHO=w|9;MQ9L3+HN-DLL&hU-k4 z;(dOkwb?ZHF}wf$lUwzdjrLD+%|`pDemd&U^>VW%M@ZVD>6@3Ls?RE!D?-$xbnc5z zjtTMIPfL2ipjgtS22n`*1nm=OGWKsOiv4C(XbqwnIjV1bdBvvJEv-e@OVp%$Fbsa< z*tj~>RJ9CoQ-h3G>L;v}frfhK!AH)3XGLAJ2gmsEHLq!P9B5f{u(oDWuttIK>3I^Z z+X8)MRpUzLsaaizB4pguAm8A#ejVyf}cZdG-lo}@mBWgSkI7=HdOd`as3 z+K--&-BC>WkY-6wMLHFKI{y;=J609rXWygVMNmH{j{3P&U{-~P4E*W*U8>$XK281bk?Kf&r`xZ8PaXt}vgrKU52%i{ z-%p^3VD10!07^bJb$;zPP{(yDpRT{g(=qu8ARnDNU7RB`=vY)5AgVw0ovy#cV(c0P zkLDjH-xRA@`wi9Q>-@U?H>vzH6+!KTFKeo*mq zh^c&?U;BI1aiN+|I-9lpS1tLqUq~INX+}{5H2-OOZ(H(f|B*VrdWgo9v9 z8kuSC==|Eh<=lITDr0UnFf{)u=yG+t?g1>meC-Ew<#w6>W*LdR@%c3!a?=!_KkB!m zLuRxLU$lHp@C=|4EnR=@S97Gw@0MgVIxCJo$pvj=)>AFiy`G2GuxVP}T`1qj9)i9b~cKHwgOJYv9WUwUJ{{kO$ BQyc&Q diff --git a/src/test_mission_adapters.cpp b/src/test_mission_adapters.cpp deleted file mode 100755 index 0de5e50..0000000 --- a/src/test_mission_adapters.cpp +++ /dev/null @@ -1,578 +0,0 @@ -#include - -#include -#include -#include - -#include "mission_adapters/mission_adapters.h" - -using namespace mission_adapters; - -namespace -{ - -robot_geometry_msgs::PoseStamped MakeGoal(double x, double y) -{ - robot_geometry_msgs::PoseStamped g; - g.pose.position.x = x; - g.pose.position.y = y; - return g; -} - -robot_protocol_msgs::Action MakeAction( - const std::string &id) -{ - robot_protocol_msgs::Action a; - a.actionId = id; - a.actionType = "TEST"; - return a; -} - -robot_protocol_msgs::Node MakeNode( - int seq, - bool add_action = false) -{ - robot_protocol_msgs::Node n; - n.sequenceId = seq; - n.nodeId = "node_" + std::to_string(seq); - - n.nodePosition.x = seq; - n.nodePosition.y = seq; - - if (add_action) - n.actions.push_back( - MakeAction("node_action_" + std::to_string(seq))); - - return n; -} - -robot_protocol_msgs::Edge MakeEdge( - int seq, - bool add_action = false) -{ - robot_protocol_msgs::Edge e; - - e.sequenceId = seq; - e.edgeId = "edge_" + std::to_string(seq); - - if (add_action) - e.actions.push_back( - MakeAction("edge_action_" + std::to_string(seq))); - - return e; -} - -robot_protocol_msgs::Order MakeOrder( - int node_count) -{ - robot_protocol_msgs::Order order; - - for (int i = 0; i < node_count; ++i) - order.nodes.push_back( - MakeNode(i)); - - for (int i = 0; i < node_count - 1; ++i) - order.edges.push_back( - MakeEdge(i)); - - return order; -} - -} // namespace - -//-------------------------------------------------------------- -// EventBus -//-------------------------------------------------------------- - -TEST(EventBus, PriorityOrdering) -{ - EventBus bus; - - bus.push({EventType::PAUSE, - PRIORITY_PAUSE, - {}}); - - bus.push({EventType::CANCEL, - PRIORITY_CANCEL, - {}}); - - bus.push({EventType::EMERGENCY, - PRIORITY_EMERGENCY, - {}}); - - Event e; - - ASSERT_TRUE(bus.pop(e)); - EXPECT_EQ(e.priority, - PRIORITY_EMERGENCY); - - ASSERT_TRUE(bus.pop(e)); - EXPECT_EQ(e.priority, - PRIORITY_CANCEL); - - ASSERT_TRUE(bus.pop(e)); - EXPECT_EQ(e.priority, - PRIORITY_PAUSE); -} - -TEST(EventBus, StopReturnsFalse) -{ - EventBus bus; - - std::thread t([&]() - { bus.stop(); }); - - Event e; - - EXPECT_FALSE(bus.pop(e)); - - t.join(); -} - -TEST(EventBus, ConcurrentPush) -{ - EventBus bus; - - constexpr int N = 100; - - std::thread producer([&]() - { - for (int i = 0; i < N; i++) - { - bus.push( - {EventType::NAV_DONE, - PRIORITY_NAV_DONE, - {}}); - } - }); - - int count = 0; - - while (count < N) - { - Event e; - - if (bus.pop(e)) - ++count; - } - - producer.join(); - - EXPECT_EQ(count, N); -} - -//-------------------------------------------------------------- -// GoalAdapter -//-------------------------------------------------------------- - -TEST(GoalAdapter, ConvertSingleGoal) -{ - GoalAdapter adapter; - - auto missions = - adapter.convert( - MakeGoal(5.5, 9.1)); - - ASSERT_EQ(missions.size(), 1u); - - EXPECT_EQ( - missions[0]->type, - MissionType::SIMPLE_GOAL); - - EXPECT_DOUBLE_EQ( - missions[0]->goal.pose.position.x, - 5.5); - - EXPECT_DOUBLE_EQ( - missions[0]->goal.pose.position.y, - 9.1); -} - -//-------------------------------------------------------------- -// VDA5050Adapter -//-------------------------------------------------------------- - -TEST(VDA5050Adapter, EmptyOrder) -{ - VDA5050Adapter adapter; - - robot_protocol_msgs::Order order; - - auto missions = - adapter.convert(order); - - EXPECT_TRUE( - missions.empty()); -} - -TEST(VDA5050Adapter, OrderWithoutActionsCreatesTailMission) -{ - VDA5050Adapter adapter; - - auto order = - MakeOrder(4); - - auto missions = - adapter.convert(order); - - ASSERT_EQ( - missions.size(), - 1u); - - EXPECT_EQ( - missions[0]->nodes.size(), - 4u); - - EXPECT_EQ( - missions[0]->edges.size(), - 3u); -} - -TEST(VDA5050Adapter, SplitAtNodeAction) -{ - VDA5050Adapter adapter; - - auto order = - MakeOrder(5); - - order.nodes[2].actions.push_back( - MakeAction("dock")); - - auto missions = - adapter.convert(order); - - ASSERT_EQ( - missions.size(), - 2u); - - EXPECT_EQ( - missions[0]->nodes.size(), - 3u); - - EXPECT_EQ( - missions[1]->nodes.size(), - 3u); -} - -TEST(VDA5050Adapter, CollectEdgeAction) -{ - VDA5050Adapter adapter; - - auto order = - MakeOrder(2); - - order.nodes[1].actions.push_back( - MakeAction("node")); - - order.edges[0].actions.push_back( - MakeAction("edge")); - - auto missions = - adapter.convert(order); - - ASSERT_EQ( - missions.size(), - 1u); - - ASSERT_EQ( - missions[0]->actions.size(), - 2u); - - EXPECT_LT( - missions[0]->actions[0].sequenceId, - missions[0]->actions[1].sequenceId); -} - -//-------------------------------------------------------------- -// MissionManager -//-------------------------------------------------------------- - -TEST(MissionManager, SubmitChangesState) -{ - MissionManager mgr; - - GoalAdapter adapter; - - mgr.submit( - adapter.convert( - MakeGoal(1, 2))); - - EXPECT_EQ( - mgr.state(), - MissionState::QUEUED); - - EXPECT_TRUE( - mgr.hasMission()); -} - -TEST(MissionManager, NextMission) -{ - MissionManager mgr; - - GoalAdapter adapter; - - mgr.submit( - adapter.convert( - MakeGoal(1, 2))); - - auto m = - mgr.nextMission(); - - ASSERT_NE( - m, - nullptr); - - EXPECT_EQ( - mgr.state(), - MissionState::RUNNING); -} - -TEST(MissionManager, NavigationDone) -{ - MissionManager mgr; - - GoalAdapter adapter; - - mgr.submit( - adapter.convert( - MakeGoal(0, 0))); - - mgr.nextMission(); - - mgr.onNavigationDone(); - - EXPECT_EQ( - mgr.state(), - MissionState::IDLE); - - EXPECT_FALSE( - mgr.hasMission()); -} - -TEST(MissionManager, NavigationFailed) -{ - MissionManager mgr; - - GoalAdapter adapter; - - mgr.submit( - adapter.convert( - MakeGoal(0, 0))); - - mgr.nextMission(); - - mgr.onNavigationFailed(); - - EXPECT_EQ( - mgr.state(), - MissionState::FAILED); -} - -TEST(MissionManager, PauseResume) -{ - MissionManager mgr; - - GoalAdapter adapter; - - mgr.submit( - adapter.convert( - MakeGoal(1, 1))); - - mgr.pause(); - - EXPECT_EQ( - mgr.state(), - MissionState::PAUSED); - - mgr.resume(); - - EXPECT_EQ( - mgr.state(), - MissionState::QUEUED); -} - -TEST(MissionManager, Cancel) -{ - MissionManager mgr; - - GoalAdapter adapter; - - mgr.submit( - adapter.convert( - MakeGoal(1, 1))); - - mgr.cancel(); - - EXPECT_EQ( - mgr.state(), - MissionState::CANCELLED); - - EXPECT_FALSE( - mgr.hasMission()); -} - -TEST(MissionManager, Emergency) -{ - MissionManager mgr; - - GoalAdapter adapter; - - mgr.submit( - adapter.convert( - MakeGoal(2, 2))); - - mgr.emergency(); - - EXPECT_EQ( - mgr.state(), - MissionState::EMERGENCY); -} - -TEST(MissionManager, ClearEmergency) -{ - MissionManager mgr; - - mgr.emergency(); - - mgr.clearEmergency(); - - EXPECT_EQ( - mgr.state(), - MissionState::CLEAR_EMERGENCY); -} - -//-------------------------------------------------------------- -// EventProcessor -//-------------------------------------------------------------- - -TEST(EventProcessor, GoalEvent) -{ - MissionManager mgr; - - EventProcessor proc(mgr); - - proc.start(); - - proc.goalEvent( - MakeGoal(5, 6)); - - std::this_thread::sleep_for( - std::chrono::milliseconds(100)); - - EXPECT_EQ( - mgr.state(), - MissionState::QUEUED); - - proc.stop(); -} - -TEST(EventProcessor, EmergencyHasHighestPriority) -{ - MissionManager mgr; - - EventProcessor proc(mgr); - - proc.start(); - - proc.goalEvent( - MakeGoal(1, 1)); - - proc.emergencyEvent(); - - std::this_thread::sleep_for( - std::chrono::milliseconds(100)); - - EXPECT_EQ( - mgr.state(), - MissionState::EMERGENCY); - - proc.stop(); -} - -//-------------------------------------------------------------- -// MissionExecutor -//-------------------------------------------------------------- - -TEST(MissionExecutor, CallbackInvokedOnce) -{ - MissionManager mgr; - - GoalAdapter adapter; - - mgr.submit( - adapter.convert( - MakeGoal(10, 10))); - - MissionExecutor exec(mgr); - - std::atomic count{0}; - - exec.setMissionCallback( - [&](const std::shared_ptr &) - { - count++; - }); - - exec.start(); - - std::this_thread::sleep_for( - std::chrono::milliseconds(200)); - - exec.stop(); - - EXPECT_EQ( - count.load(), - 1); -} - -TEST(MissionExecutor, NewMissionAfterDone) -{ - MissionManager mgr; - - GoalAdapter adapter; - - mgr.submit( - adapter.convert( - MakeGoal(1, 1))); - - MissionExecutor exec(mgr); - - std::atomic count{0}; - - exec.setMissionCallback( - [&](const std::shared_ptr &) - { - count++; - }); - - exec.start(); - - std::this_thread::sleep_for( - std::chrono::milliseconds(100)); - - mgr.onNavigationDone(); - - mgr.submit( - adapter.convert( - MakeGoal(2, 2))); - - std::this_thread::sleep_for( - std::chrono::milliseconds(150)); - - exec.stop(); - - EXPECT_EQ( - count.load(), - 2); -} - -//-------------------------------------------------------------- - -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest( - &argc, - argv); - - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/test/mission_adapters_test.cpp b/test/mission_adapters_test.cpp new file mode 100644 index 0000000..f864927 --- /dev/null +++ b/test/mission_adapters_test.cpp @@ -0,0 +1,360 @@ +#include + +#include +#include +#include + +#include + +namespace +{ +using namespace mission_adapters; + +robot_geometry_msgs::PoseStamped makeGoal(double x, double y) +{ + robot_geometry_msgs::PoseStamped goal; + goal.pose.position.x = x; + goal.pose.position.y = y; + return goal; +} + +robot_protocol_msgs::Action makeAction(const std::string& id) +{ + robot_protocol_msgs::Action action; + action.actionId = id; + action.actionType = "TEST"; + return action; +} + +robot_protocol_msgs::Node makeNode(int sequence_id, bool add_action = false) +{ + robot_protocol_msgs::Node node; + node.sequenceId = sequence_id; + node.nodeId = "node_" + std::to_string(sequence_id); + node.nodePosition.x = sequence_id; + node.nodePosition.y = sequence_id; + + if (add_action) + node.actions.push_back(makeAction("node_action_" + std::to_string(sequence_id))); + + return node; +} + +robot_protocol_msgs::Edge makeEdge(int sequence_id, bool add_action = false) +{ + robot_protocol_msgs::Edge edge; + edge.sequenceId = sequence_id; + edge.edgeId = "edge_" + std::to_string(sequence_id); + + if (add_action) + edge.actions.push_back(makeAction("edge_action_" + std::to_string(sequence_id))); + + return edge; +} + +robot_protocol_msgs::Order makeOrder(int node_count) +{ + robot_protocol_msgs::Order order; + + for (int i = 0; i < node_count; ++i) + order.nodes.push_back(makeNode(i)); + + for (int i = 0; i < node_count - 1; ++i) + order.edges.push_back(makeEdge(i)); + + return order; +} + +bool waitForState(MissionManager& manager, MissionState expected, std::chrono::milliseconds timeout) +{ + const auto deadline = std::chrono::steady_clock::now() + timeout; + while (std::chrono::steady_clock::now() < deadline) + { + if (manager.state() == expected) + return true; + + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + return manager.state() == expected; +} + +class MissionAdaptersTest : public ::testing::Test +{ +protected: + GoalAdapter goal_adapter; + VDA5050Adapter order_adapter; +}; + +TEST(EventBusTest, PopsHighestPriorityFirst) +{ + EventBus bus; + + Event pause; + pause.type = EventType::PAUSE; + pause.priority = PRIORITY_PAUSE; + bus.push(pause); + + Event cancel; + cancel.type = EventType::CANCEL; + cancel.priority = PRIORITY_CANCEL; + bus.push(cancel); + + Event emergency; + emergency.type = EventType::EMERGENCY; + emergency.priority = PRIORITY_EMERGENCY; + bus.push(emergency); + + Event event; + ASSERT_TRUE(bus.pop(event)); + EXPECT_EQ(event.type, EventType::EMERGENCY); + + ASSERT_TRUE(bus.pop(event)); + EXPECT_EQ(event.type, EventType::CANCEL); + + ASSERT_TRUE(bus.pop(event)); + EXPECT_EQ(event.type, EventType::PAUSE); +} + +TEST(EventBusTest, PreservesFifoOrderForSamePriority) +{ + EventBus bus; + + Event emergency; + emergency.type = EventType::EMERGENCY; + emergency.priority = PRIORITY_EMERGENCY; + bus.push(emergency); + + Event clear_emergency; + clear_emergency.type = EventType::CLEAR_EMERGENCY; + clear_emergency.priority = PRIORITY_EMERGENCY; + bus.push(clear_emergency); + + Event event; + ASSERT_TRUE(bus.pop(event)); + EXPECT_EQ(event.type, EventType::EMERGENCY); + + ASSERT_TRUE(bus.pop(event)); + EXPECT_EQ(event.type, EventType::CLEAR_EMERGENCY); +} + +TEST(EventBusTest, StopUnblocksPop) +{ + EventBus bus; + Event event; + + std::thread stopper([&bus] { bus.stop(); }); + EXPECT_FALSE(bus.pop(event)); + stopper.join(); +} + +TEST(EventBusTest, ResetDropsPendingEvents) +{ + EventBus bus; + + Event pause; + pause.type = EventType::PAUSE; + pause.priority = PRIORITY_PAUSE; + bus.push(pause); + + bus.reset(); + EXPECT_TRUE(bus.empty()); +} + +TEST_F(MissionAdaptersTest, GoalAdapterCreatesSingleMission) +{ + const auto missions = goal_adapter.convert(makeGoal(5.5, 9.1)); + + ASSERT_EQ(missions.size(), 1u); + EXPECT_EQ(missions.front()->type, MissionType::SIMPLE_GOAL); + EXPECT_DOUBLE_EQ(missions.front()->goal.pose.position.x, 5.5); + EXPECT_DOUBLE_EQ(missions.front()->goal.pose.position.y, 9.1); +} + +TEST_F(MissionAdaptersTest, EmptyOrderCreatesNoMissions) +{ + robot_protocol_msgs::Order order; + EXPECT_TRUE(order_adapter.convert(order).empty()); +} + +TEST_F(MissionAdaptersTest, OrderWithoutActionsCreatesOneTailMission) +{ + const auto missions = order_adapter.convert(makeOrder(4)); + + ASSERT_EQ(missions.size(), 1u); + EXPECT_EQ(missions.front()->nodes.size(), 4u); + EXPECT_EQ(missions.front()->edges.size(), 3u); +} + +TEST_F(MissionAdaptersTest, InvalidOrderWithMissingEdgesCreatesNoMissions) +{ + auto order = makeOrder(4); + order.edges.pop_back(); + + EXPECT_TRUE(order_adapter.convert(order).empty()); +} + +TEST_F(MissionAdaptersTest, OrderSplitsAtNodeAction) +{ + auto order = makeOrder(5); + order.nodes[2].actions.push_back(makeAction("dock")); + + const auto missions = order_adapter.convert(order); + + ASSERT_EQ(missions.size(), 2u); + EXPECT_EQ(missions[0]->nodes.size(), 3u); + EXPECT_EQ(missions[1]->nodes.size(), 3u); +} + +TEST_F(MissionAdaptersTest, OrderCollectsAndSortsActions) +{ + auto order = makeOrder(2); + order.edges[0].actions.push_back(makeAction("edge")); + order.nodes[1].actions.push_back(makeAction("node")); + + const auto missions = order_adapter.convert(order); + + ASSERT_EQ(missions.size(), 1u); + ASSERT_EQ(missions.front()->actions.size(), 2u); + EXPECT_EQ(missions.front()->actions[0].type, ActionType::EDGE_ACTION); + EXPECT_EQ(missions.front()->actions[1].type, ActionType::NODE_ACTION); +} + +TEST_F(MissionAdaptersTest, ManagerRunsMissionLifecycle) +{ + MissionManager manager; + + manager.submit(goal_adapter.convert(makeGoal(1.0, 2.0))); + EXPECT_EQ(manager.state(), MissionState::QUEUED); + EXPECT_TRUE(manager.hasMission()); + + auto mission = manager.nextMission(); + ASSERT_NE(mission, nullptr); + EXPECT_EQ(manager.state(), MissionState::RUNNING); + + manager.onNavigationDone(); + EXPECT_EQ(manager.state(), MissionState::IDLE); + EXPECT_FALSE(manager.hasMission()); +} + +TEST_F(MissionAdaptersTest, ManagerHandlesPauseResumeCancelAndFailure) +{ + MissionManager manager; + + manager.submit(goal_adapter.convert(makeGoal(1.0, 1.0))); + manager.pause(); + EXPECT_EQ(manager.state(), MissionState::PAUSED); + + manager.resume(); + EXPECT_EQ(manager.state(), MissionState::QUEUED); + + manager.cancel(); + EXPECT_EQ(manager.state(), MissionState::CANCELLED); + EXPECT_FALSE(manager.hasMission()); + + manager.submit(goal_adapter.convert(makeGoal(2.0, 2.0))); + manager.nextMission(); + manager.onNavigationFailed(); + EXPECT_EQ(manager.state(), MissionState::FAILED); + EXPECT_FALSE(manager.hasMission()); +} + +TEST_F(MissionAdaptersTest, NavigationResultIsIgnoredOutsideRunningState) +{ + MissionManager manager; + + manager.submit(goal_adapter.convert(makeGoal(1.0, 1.0))); + manager.emergency(); + manager.onNavigationDone(); + EXPECT_EQ(manager.state(), MissionState::EMERGENCY); + + manager.onNavigationFailed(); + EXPECT_EQ(manager.state(), MissionState::EMERGENCY); + + manager.clearEmergency(); + manager.submit(goal_adapter.convert(makeGoal(2.0, 2.0))); + manager.cancel(); + manager.onNavigationDone(); + EXPECT_EQ(manager.state(), MissionState::CANCELLED); +} + +TEST_F(MissionAdaptersTest, EmergencyClearsAndBlocksNewMissionsUntilCleared) +{ + MissionManager manager; + + manager.submit(goal_adapter.convert(makeGoal(1.0, 1.0))); + manager.emergency(); + EXPECT_EQ(manager.state(), MissionState::EMERGENCY); + EXPECT_FALSE(manager.hasMission()); + + manager.submit(goal_adapter.convert(makeGoal(2.0, 2.0))); + EXPECT_EQ(manager.state(), MissionState::EMERGENCY); + EXPECT_FALSE(manager.hasMission()); + + manager.clearEmergency(); + manager.submit(goal_adapter.convert(makeGoal(3.0, 3.0))); + EXPECT_EQ(manager.state(), MissionState::QUEUED); + EXPECT_TRUE(manager.hasMission()); +} + +TEST_F(MissionAdaptersTest, EventProcessorProcessesGoalAndEmergency) +{ + MissionManager manager; + EventProcessor processor(manager); + + processor.start(); + processor.goalEvent(makeGoal(5.0, 6.0)); + EXPECT_TRUE(waitForState(manager, MissionState::QUEUED, std::chrono::milliseconds(250))); + + processor.emergencyEvent(); + EXPECT_TRUE(waitForState(manager, MissionState::EMERGENCY, std::chrono::milliseconds(250))); + + processor.goalEvent(makeGoal(7.0, 8.0)); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + EXPECT_EQ(manager.state(), MissionState::EMERGENCY); + + processor.clearEmergencyEvent(); + EXPECT_TRUE(waitForState(manager, MissionState::CLEAR_EMERGENCY, std::chrono::milliseconds(250))); + + processor.stop(); +} + +TEST_F(MissionAdaptersTest, MissionExecutorDispatchesEachMissionOnce) +{ + MissionManager manager; + MissionExecutor executor(manager); + std::atomic callback_count{0}; + + executor.setMissionCallback( + [&callback_count](const std::shared_ptr& mission) + { + ASSERT_NE(mission, nullptr); + ++callback_count; + }); + + manager.submit(goal_adapter.convert(makeGoal(10.0, 10.0))); + executor.start(); + + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(250); + while (callback_count.load() < 1 && std::chrono::steady_clock::now() < deadline) + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + manager.onNavigationDone(); + manager.submit(goal_adapter.convert(makeGoal(11.0, 11.0))); + + const auto second_deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(250); + while (callback_count.load() < 2 && std::chrono::steady_clock::now() < second_deadline) + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + executor.stop(); + + EXPECT_EQ(callback_count.load(), 2); +} + +} // namespace + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}