unittest_stop1_executor.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2019 Pilz GmbH & Co. KG
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU Lesser General Public License for more details.
13 
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <atomic>
19 #include <chrono>
20 #include <functional>
21 #include <memory>
22 #include <thread>
23 
24 #include <gtest/gtest.h>
25 #include <gmock/gmock.h>
26 
27 #include <ros/ros.h>
28 
29 #include <pilz_testutils/async_test.h>
30 
33 
34 #define EXPECT_RECOVER \
35  EXPECT_CALL(*this, recover_func()) \
36  .WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(RECOVER_SRV_CALLED_EVENT), Return(true)))
37 
38 #define EXPECT_UNHOLD \
39  EXPECT_CALL(*this, unhold_func()) \
40  .WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(UNHOLD_SRV_CALLED_EVENT), Return(true)))
41 
42 #define EXPECT_HOLD \
43  EXPECT_CALL(*this, hold_func()) \
44  .WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(HOLD_SRV_CALLED_EVENT), Return(true)))
45 
46 #define EXPECT_HALT \
47  EXPECT_CALL(*this, halt_func()) \
48  .WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(HALT_SRV_CALLED_EVENT), Return(true)))
49 
51 {
52 
53 using namespace prbt_hardware_support;
54 
55 using ::testing::_;
56 using ::testing::DoAll;
57 using ::testing::InSequence;
58 using ::testing::Invoke;
59 using ::testing::InvokeWithoutArgs;
60 using ::testing::Return;
61 using ::testing::AtLeast;
62 
63 const std::string RECOVER_SRV_CALLED_EVENT{"recover_srv_called"};
64 const std::string UNHOLD_SRV_CALLED_EVENT{"unhold_srv_called"};
65 const std::string HOLD_SRV_CALLED_EVENT{"hold_srv_called"};
66 const std::string HALT_SRV_CALLED_EVENT{"halt_srv_called"};
67 
72 {
73 public:
75  const TServiceCallFunc& unhold_func,
76  const TServiceCallFunc& recover_func,
77  const TServiceCallFunc& halt_func)
78  : Stop1Executor(hold_func, unhold_func, recover_func, halt_func)
79  {
80  }
81 
82  FRIEND_TEST(Stop1ExecutorTest, testExitInStateEnabling);
83  FRIEND_TEST(Stop1ExecutorTest, testExitInStateStopping);
84  FRIEND_TEST(Stop1ExecutorTest, testExitInStateEnableRequestDuringStop);
85  FRIEND_TEST(Stop1ExecutorTest, testExitInStateStopRequestedDuringEnable);
86 };
87 
88 class Stop1ExecutorTest : public ::testing::Test, public ::testing::AsyncTest
89 {
90 protected:
91  Stop1ExecutorForTests* createStop1Executor();
92 
93 public:
94  MOCK_METHOD0(hold_func, bool());
95  MOCK_METHOD0(unhold_func, bool());
96  MOCK_METHOD0(recover_func, bool());
97  MOCK_METHOD0(halt_func, bool());
98 
99 };
100 
102 {
103  return new Stop1ExecutorForTests( std::bind(&Stop1ExecutorTest::hold_func, this),
104  std::bind(&Stop1ExecutorTest::unhold_func, this),
105  std::bind(&Stop1ExecutorTest::recover_func, this),
106  std::bind(&Stop1ExecutorTest::halt_func, this) );
107 }
108 
114 TEST_F(Stop1ExecutorTest, testDestructor)
115 {
116  {
117  std::shared_ptr<Stop1Executor> adapter_run_permitted {new Stop1Executor( std::bind(&Stop1ExecutorTest::hold_func, this),
118  std::bind(&Stop1ExecutorTest::unhold_func, this),
119  std::bind(&Stop1ExecutorTest::recover_func, this),
120  std::bind(&Stop1ExecutorTest::halt_func, this) ) };
121  }
122 
123  {
124  Stop1Executor adapter_run_permitted( std::bind(&Stop1ExecutorTest::hold_func, this),
125  std::bind(&Stop1ExecutorTest::unhold_func, this),
126  std::bind(&Stop1ExecutorTest::recover_func, this),
127  std::bind(&Stop1ExecutorTest::halt_func, this) );
128  }
129 }
130 
145 {
146  {
147  InSequence dummy;
150  }
151 
152  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
153  adapter_run_permitted->updateRunPermitted(true);
154 
156 }
157 
180 TEST_F(Stop1ExecutorTest, testEnableStopEnable)
181 {
182  /**********
183  * Step 1 *
184  **********/
185  {
186  InSequence dummy;
189  }
190 
191  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
192  adapter_run_permitted->updateRunPermitted(true);
193 
195 
196  /**********
197  * Step 2 *
198  **********/
199  {
200  InSequence dummy;
201  EXPECT_HOLD;
202  EXPECT_HALT;
203  }
204 
205  adapter_run_permitted->updateRunPermitted(false);
206 
208 
209  /**********
210  * Step 3 *
211  **********/
212  {
213  InSequence dummy;
216  }
217 
218  std::atomic_bool keep_spamming{true};
219  std::thread spam_enable{[&adapter_run_permitted, &keep_spamming]() { while (keep_spamming) { adapter_run_permitted->updateRunPermitted(true); } }};
220 
222 
223  keep_spamming = false;
224  spam_enable.join();
225 }
226 
251 TEST_F(Stop1ExecutorTest, testSpamEnablePlusStop)
252 {
253  /**********
254  * Step 1 *
255  **********/
256  {
257  InSequence dummy;
260  }
261 
262  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
263 
264  std::atomic_bool keep_spamming{true};
265  std::thread spam_enable{[&adapter_run_permitted, &keep_spamming]() { while (keep_spamming) { adapter_run_permitted->updateRunPermitted(true); } }};
266 
268 
269  keep_spamming = false;
270  spam_enable.join();
271 
272  /**********
273  * Step 2 *
274  **********/
275 
276  adapter_run_permitted->updateRunPermitted(true);
277 
278  /**********
279  * Step 3 *
280  **********/
281  {
282  InSequence dummy;
283  EXPECT_HOLD;
284  EXPECT_HALT;
285  }
286 
287  adapter_run_permitted->updateRunPermitted(false);
288 
290 }
291 
316 TEST_F(Stop1ExecutorTest, testSpamRunPermittedActivePlusEnable)
317 {
318  /**********
319  * Step 1 *
320  **********/
321  {
322  InSequence dummy;
325  }
326 
327  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
328 
329  adapter_run_permitted->updateRunPermitted(true);
330 
332 
333  /**********
334  * Step 2 *
335  **********/
336  {
337  InSequence dummy;
338  EXPECT_HOLD;
339  EXPECT_HALT;
340  }
341 
342  std::atomic_bool keep_spamming{true};
343  std::thread spam_disable{[&adapter_run_permitted, &keep_spamming]() { while (keep_spamming) { adapter_run_permitted->updateRunPermitted(false); } }};
344 
346 
347  keep_spamming = false;
348  spam_disable.join();
349 
350  /**********
351  * Step 3 *
352  **********/
353  {
354  InSequence dummy;
357  }
358 
359  keep_spamming = true;
360  std::thread spam_enable{[&adapter_run_permitted, &keep_spamming]() { while (keep_spamming) { adapter_run_permitted->updateRunPermitted(true); } }};
361 
363 
364  keep_spamming = false;
365  spam_enable.join();
366 }
367 
392 TEST_F(Stop1ExecutorTest, testSkippingHoldPlusEnable)
393 {
394  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
395 
396  // define function for recover-invoke action
397  auto run_permitted_false_during_recover_action = [this, &adapter_run_permitted]() {
398  this->triggerClearEvent(RECOVER_SRV_CALLED_EVENT);
399  adapter_run_permitted->updateRunPermitted(false);
400  return true;
401  };
402 
403  auto halt_action = [this]() {
404  this->triggerClearEvent(HALT_SRV_CALLED_EVENT);
405  return true;
406  };
407 
408  /**********
409  * Step 1 *
410  **********/
411  {
412  InSequence dummy;
413 
414  EXPECT_CALL(*this, recover_func())
415  .WillOnce(InvokeWithoutArgs(run_permitted_false_during_recover_action));
416 
417  EXPECT_CALL(*this, halt_func())
418  .WillOnce(InvokeWithoutArgs(halt_action));
419  }
420 
421  adapter_run_permitted->updateRunPermitted(true);
422 
424 
425  /**********
426  * Step 2 *
427  **********/
428  {
429  InSequence dummy;
432  }
433 
434  std::atomic_bool keep_spamming{true};
435  std::thread spam_enable{[&adapter_run_permitted, &keep_spamming]() { while (keep_spamming) { adapter_run_permitted->updateRunPermitted(true); } }};
436 
438 
439  keep_spamming = false;
440  spam_enable.join();
441 }
442 
462 TEST_F(Stop1ExecutorTest, testEnableDuringHoldService)
463 {
464  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
465 
466  auto enable_during_hold_action = [this, &adapter_run_permitted]() {
467  adapter_run_permitted->updateRunPermitted(true);
468  this->triggerClearEvent(HOLD_SRV_CALLED_EVENT);
469  return true;
470  };
471 
472  /**********
473  * Step 1 *
474  **********/
475  {
476  InSequence dummy;
477 
480  }
481 
482  adapter_run_permitted->updateRunPermitted(true);
483 
485 
486  {
487  InSequence dummy;
488 
489  EXPECT_CALL(*this, hold_func())
490  .WillOnce(InvokeWithoutArgs(enable_during_hold_action));
491 
492  EXPECT_HALT;
495  }
496 
497  adapter_run_permitted->updateRunPermitted(false);
498 
500 }
501 
520 TEST_F(Stop1ExecutorTest, testEnableDuringHaltService)
521 {
522  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
523 
524  // define function for recover-invoke action
525  auto run_permitted_false_during_recover_action = [this, &adapter_run_permitted]() {
526  this->triggerClearEvent(RECOVER_SRV_CALLED_EVENT);
527  adapter_run_permitted->updateRunPermitted(false);
528  return true;
529  };
530 
531  auto enable_during_halt_action = [this, &adapter_run_permitted]() {
532  this->triggerClearEvent(HALT_SRV_CALLED_EVENT);
533  adapter_run_permitted->updateRunPermitted(true);
534  return true;
535  };
536 
537  /**********
538  * Step 1 *
539  **********/
540 
541  const std::string RECOVER_SRV_CALLED_EVENT2{"recover_srv_called2"};
542  const std::string UNHOLD_SRV_CALLED_EVENT2{"unhold_srv_called2"};
543  {
544  InSequence dummy;
545 
546  EXPECT_CALL(*this, recover_func())
547  .WillOnce(InvokeWithoutArgs(run_permitted_false_during_recover_action));
548 
549  EXPECT_CALL(*this, halt_func())
550  .WillOnce(InvokeWithoutArgs(enable_during_halt_action));
551 
552  EXPECT_CALL(*this, recover_func())
553  .WillOnce(InvokeWithoutArgs([this, RECOVER_SRV_CALLED_EVENT2]() {
554  this->triggerClearEvent(RECOVER_SRV_CALLED_EVENT2);
555  return true;
556  }));
557 
558  EXPECT_CALL(*this, unhold_func())
559  .WillOnce(InvokeWithoutArgs([this, UNHOLD_SRV_CALLED_EVENT2]() {
560  this->triggerClearEvent(UNHOLD_SRV_CALLED_EVENT2);
561  return true;
562  }));
563  }
564 
565  adapter_run_permitted->updateRunPermitted(true);
566 
567  BARRIER({RECOVER_SRV_CALLED_EVENT, HALT_SRV_CALLED_EVENT, RECOVER_SRV_CALLED_EVENT2, UNHOLD_SRV_CALLED_EVENT2});
568 }
569 
589 TEST_F(Stop1ExecutorTest, testEnableDisableDuringHaltService)
590 {
591  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
592 
593  // define function for recover-invoke action
594  auto run_permitted_false_during_recover_action = [this, &adapter_run_permitted]() {
595  this->triggerClearEvent(RECOVER_SRV_CALLED_EVENT);
596  adapter_run_permitted->updateRunPermitted(false);
597  return true;
598  };
599 
600  auto enable_during_halt_action = [this, &adapter_run_permitted]() {
601  this->triggerClearEvent(HALT_SRV_CALLED_EVENT);
602  adapter_run_permitted->updateRunPermitted(true);
603  adapter_run_permitted->updateRunPermitted(false); // Important flip!
604  return true;
605  };
606 
607  /**********
608  * Step 1 *
609  **********/
610 
611  {
612  InSequence dummy;
613 
614  EXPECT_CALL(*this, recover_func())
615  .Times(1)
616  .WillOnce(InvokeWithoutArgs(run_permitted_false_during_recover_action));
617 
618  EXPECT_CALL(*this, halt_func())
619  .Times(1)
620  .WillOnce(InvokeWithoutArgs(enable_during_halt_action));
621  }
622 
623  adapter_run_permitted->updateRunPermitted(true);
624 
626 }
627 
652 TEST_F(Stop1ExecutorTest, testRecoverFailPlusRetry)
653 {
654  /**********
655  * Step 1 *
656  **********/
657  EXPECT_CALL(*this, recover_func())
658  .WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(RECOVER_SRV_CALLED_EVENT), Return(false)))
659  .WillRepeatedly(Return(false));
660 
661  // unhold is optional here
662  EXPECT_CALL(*this, unhold_func())
663  .WillRepeatedly(Return(true));
664 
665  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
666 
667  adapter_run_permitted->updateRunPermitted(true);
668 
669  BARRIER({RECOVER_SRV_CALLED_EVENT});
670 
671  /**********
672  * Step 2 *
673  **********/
674  {
675  InSequence dummy;
676 
677  // hold and is_executing and is optional here
678  EXPECT_CALL(*this, hold_func())
679  .WillRepeatedly(Return(true));
680 
681  EXPECT_HALT;
682  }
683 
684  adapter_run_permitted->updateRunPermitted(false);
685 
686  BARRIER({HALT_SRV_CALLED_EVENT});
687 
688  /**********
689  * Step 3 *
690  **********/
691  {
692  InSequence dummy;
693 
696  }
697 
698  std::atomic_bool keep_spamming{true};
699  std::thread spam_enable{[&adapter_run_permitted, &keep_spamming]() { while (keep_spamming) { adapter_run_permitted->updateRunPermitted(true); } }};
700 
702 
703  keep_spamming = false;
704  spam_enable.join();
705 }
706 
722 TEST_F(Stop1ExecutorTest, testUnholdFail)
723 {
724  /**********
725  * Step 1 *
726  **********/
727  {
728  InSequence dummy;
729 
731 
732  EXPECT_CALL(*this, unhold_func())
733  .WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(UNHOLD_SRV_CALLED_EVENT), Return(false)))
734  .WillRepeatedly(Return(false));
735  }
736 
737  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
738  adapter_run_permitted->updateRunPermitted(true);
739 
741 
742  /**********
743  * Step 2 *
744  **********/
745  {
746  InSequence dummy;
747 
748  EXPECT_HOLD;
749  EXPECT_HALT;
750  }
751 
752  adapter_run_permitted->updateRunPermitted(false);
753 
755 }
756 
773 TEST_F(Stop1ExecutorTest, testHoldFail)
774 {
775  /**********
776  * Step 1 *
777  **********/
778  {
779  InSequence dummy;
782  }
783 
784  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
785 
786  adapter_run_permitted->updateRunPermitted(true);
787 
789 
790  /**********
791  * Step 2 *
792  **********/
793  {
794  InSequence dummy;
795 
796  EXPECT_CALL(*this, hold_func())
797  .WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(HOLD_SRV_CALLED_EVENT), Return(false)))
798  .WillRepeatedly(Return(false));
799 
800  EXPECT_CALL(*this, halt_func())
801  .WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(HALT_SRV_CALLED_EVENT), Return(false)))
802  .WillRepeatedly(Return(false));
803  }
804 
805  adapter_run_permitted->updateRunPermitted(false);
806 
808 }
809 
823 TEST_F(Stop1ExecutorTest, testHoldImmediatelyAfterUnhold)
824 {
825  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
826 
827  // define function for unhold-invoke action
828  auto unhold_action = [this, &adapter_run_permitted]() {
829  this->triggerClearEvent(UNHOLD_SRV_CALLED_EVENT);
830  adapter_run_permitted->updateRunPermitted(false);
831  return true;
832  };
833 
834  /**********
835  * Step 1 *
836  **********/
837  {
838  InSequence dummy;
839 
841 
842  EXPECT_CALL(*this, unhold_func())
843  .WillOnce(InvokeWithoutArgs(unhold_action));
844 
845  EXPECT_HOLD;
846  EXPECT_HALT;
847  }
848 
849  adapter_run_permitted->updateRunPermitted(true);
850 
852 }
853 
867 TEST_F(Stop1ExecutorTest, testExitInStateEnabling)
868 {
869  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
870 
871  // define function for recover-invoke action
872  auto recover_action = [this, &adapter_run_permitted]() {
873  adapter_run_permitted->stopStateMachine();
874  this->triggerClearEvent(RECOVER_SRV_CALLED_EVENT);
875  return true;
876  };
877 
878  {
879  InSequence dummy;
880 
881  EXPECT_CALL(*this, recover_func())
882  .WillOnce(InvokeWithoutArgs(recover_action));
883 
884  // do not exclude other service calls as stopping the state machine does not prevent further actions
885  EXPECT_CALL(*this, unhold_func())
886  .WillRepeatedly(Return(true));
887  }
888 
889  adapter_run_permitted->updateRunPermitted(true);
890 
891  BARRIER({RECOVER_SRV_CALLED_EVENT});
892 }
893 
909 TEST_F(Stop1ExecutorTest, testExitInStateStopping)
910 {
911  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
912 
913  /**********
914  * Step 1 *
915  **********/
916  {
917  InSequence dummy;
918 
921  }
922 
923  adapter_run_permitted->updateRunPermitted(true);
924 
926 
927  /**********
928  * Step 2 *
929  **********/
930  // define function for hold-invoke action
931  auto hold_action = [this, &adapter_run_permitted]() {
932  adapter_run_permitted->stopStateMachine();
933  this->triggerClearEvent(HOLD_SRV_CALLED_EVENT);
934  return true;
935  };
936 
937  {
938  InSequence dummy;
939 
940  EXPECT_CALL(*this, hold_func())
941  .WillOnce(InvokeWithoutArgs(hold_action));
942 
943  // do not exclude other service calls as stopping the state machine does not prevent further actions
944  EXPECT_CALL(*this, halt_func())
945  .WillRepeatedly(Return(true));
946  }
947 
948  adapter_run_permitted->updateRunPermitted(false);
949 
950  BARRIER({HOLD_SRV_CALLED_EVENT});
951 }
952 
966 TEST_F(Stop1ExecutorTest, testExitInStateStopRequestedDuringEnable)
967 {
968  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
969 
970  // define function for recover-invoke action
971  auto recover_action = [this, &adapter_run_permitted]() {
972  adapter_run_permitted->updateRunPermitted(false);
973  adapter_run_permitted->stopStateMachine();
974  this->triggerClearEvent(RECOVER_SRV_CALLED_EVENT);
975  return true;
976  };
977 
978  /**********
979  * Step 1 *
980  **********/
981  {
982  InSequence dummy;
983 
984  EXPECT_CALL(*this, recover_func())
985  .WillOnce(InvokeWithoutArgs(recover_action));
986 
987  // do not exclude other service calls as stopping the state machine does not prevent further actions
988  EXPECT_CALL(*this, halt_func())
989  .WillRepeatedly(Return(true));
990  }
991 
992  adapter_run_permitted->updateRunPermitted(true);
993 
994  BARRIER(RECOVER_SRV_CALLED_EVENT);
995 }
996 
1013 TEST_F(Stop1ExecutorTest, testExitInStateEnableRequestDuringStop)
1014 {
1015  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted {createStop1Executor()};
1016 
1017  /**********
1018  * Step 1 *
1019  **********/
1020  {
1021  InSequence dummy;
1022 
1024  EXPECT_UNHOLD;
1025  }
1026 
1027  adapter_run_permitted->updateRunPermitted(true);
1028 
1030 
1031  /**********
1032  * Step 2 *
1033  **********/
1034 
1035  auto enable_during_hold_action = [this, &adapter_run_permitted]() {
1036  adapter_run_permitted->updateRunPermitted(true);
1037  adapter_run_permitted->stopStateMachine();
1038  this->triggerClearEvent(HOLD_SRV_CALLED_EVENT);
1039  return true;
1040  };
1041 
1042  {
1043  InSequence dummy;
1044 
1045  EXPECT_CALL(*this, hold_func())
1046  .WillOnce(InvokeWithoutArgs(enable_during_hold_action));
1047 
1048  // do not exclude other service calls as stopping the state machine does not prevent further actions
1049  EXPECT_CALL(*this, halt_func())
1050  .WillRepeatedly(Return(true));
1051  EXPECT_CALL(*this, recover_func())
1052  .WillRepeatedly(Return(true));
1053  EXPECT_CALL(*this, unhold_func())
1054  .WillRepeatedly(Return(true));
1055  }
1056 
1057  adapter_run_permitted->updateRunPermitted(false);
1058 
1059  BARRIER(HOLD_SRV_CALLED_EVENT);
1060 }
1061 
1062 
1063 } // namespace prbt_hardware_support_tests
1064 
1065 int main(int argc, char **argv)
1066 {
1067  // for (limited) ros::Time functionality, no ROS communication
1068  ros::Time::init();
1069 
1070  testing::InitGoogleMock(&argc, argv);
1071  return RUN_ALL_TESTS();
1072 }
TEST_F(Stop1ExecutorTest, testDestructor)
Test increases function coverage by ensuring that all Dtor variants are called.
Stop1ExecutorForTests(const TServiceCallFunc &hold_func, const TServiceCallFunc &unhold_func, const TServiceCallFunc &recover_func, const TServiceCallFunc &halt_func)
int main(int argc, char **argv)
#define EXPECT_HOLD
#define EXPECT_UNHOLD
static void init()
#define EXPECT_HALT
Performs service calls for Stop1 and the respective reversal, that is enabling the manipulator...
std::function< bool()> TServiceCallFunc
#define EXPECT_RECOVER


prbt_hardware_support
Author(s):
autogenerated on Tue Feb 2 2021 03:50:17