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()).WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(RECOVER_SRV_CALLED_EVENT), Return(true)))
36 
37 #define EXPECT_UNHOLD \
38  EXPECT_CALL(*this, unhold_func()).WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(UNHOLD_SRV_CALLED_EVENT), Return(true)))
39 
40 #define EXPECT_HOLD \
41  EXPECT_CALL(*this, hold_func()).WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(HOLD_SRV_CALLED_EVENT), Return(true)))
42 
43 #define EXPECT_HALT \
44  EXPECT_CALL(*this, halt_func()).WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(HALT_SRV_CALLED_EVENT), Return(true)))
45 
47 {
48 using namespace prbt_hardware_support;
49 
50 using ::testing::_;
51 using ::testing::AtLeast;
52 using ::testing::DoAll;
53 using ::testing::InSequence;
54 using ::testing::Invoke;
55 using ::testing::InvokeWithoutArgs;
56 using ::testing::Return;
57 
58 const std::string RECOVER_SRV_CALLED_EVENT{ "recover_srv_called" };
59 const std::string UNHOLD_SRV_CALLED_EVENT{ "unhold_srv_called" };
60 const std::string HOLD_SRV_CALLED_EVENT{ "hold_srv_called" };
61 const std::string HALT_SRV_CALLED_EVENT{ "halt_srv_called" };
62 
67 {
68 public:
69  Stop1ExecutorForTests(const TServiceCallFunc& hold_func, const TServiceCallFunc& unhold_func,
70  const TServiceCallFunc& recover_func, const TServiceCallFunc& halt_func)
71  : Stop1Executor(hold_func, unhold_func, recover_func, halt_func)
72  {
73  }
74 
75  FRIEND_TEST(Stop1ExecutorTest, testExitInStateEnabling);
76  FRIEND_TEST(Stop1ExecutorTest, testExitInStateStopping);
77  FRIEND_TEST(Stop1ExecutorTest, testExitInStateEnableRequestDuringStop);
78  FRIEND_TEST(Stop1ExecutorTest, testExitInStateStopRequestedDuringEnable);
79 };
80 
81 class Stop1ExecutorTest : public ::testing::Test, public ::testing::AsyncTest
82 {
83 protected:
84  Stop1ExecutorForTests* createStop1Executor();
85 
86 public:
87  MOCK_METHOD0(hold_func, bool());
88  MOCK_METHOD0(unhold_func, bool());
89  MOCK_METHOD0(recover_func, bool());
90  MOCK_METHOD0(halt_func, bool());
91 };
92 
94 {
95  return new Stop1ExecutorForTests(
96  std::bind(&Stop1ExecutorTest::hold_func, this), std::bind(&Stop1ExecutorTest::unhold_func, this),
97  std::bind(&Stop1ExecutorTest::recover_func, this), std::bind(&Stop1ExecutorTest::halt_func, this));
98 }
99 
105 TEST_F(Stop1ExecutorTest, testDestructor)
106 {
107  {
108  std::shared_ptr<Stop1Executor> adapter_run_permitted{ new Stop1Executor(
109  std::bind(&Stop1ExecutorTest::hold_func, this), std::bind(&Stop1ExecutorTest::unhold_func, this),
110  std::bind(&Stop1ExecutorTest::recover_func, this), std::bind(&Stop1ExecutorTest::halt_func, this)) };
111  }
112 
113  {
114  Stop1Executor adapter_run_permitted(
115  std::bind(&Stop1ExecutorTest::hold_func, this), std::bind(&Stop1ExecutorTest::unhold_func, this),
116  std::bind(&Stop1ExecutorTest::recover_func, this), std::bind(&Stop1ExecutorTest::halt_func, this));
117  }
118 }
119 
134 {
135  {
136  InSequence dummy;
139  }
140 
141  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
142  adapter_run_permitted->updateRunPermitted(true);
143 
145 }
146 
169 TEST_F(Stop1ExecutorTest, testEnableStopEnable)
170 {
171  /**********
172  * Step 1 *
173  **********/
174  {
175  InSequence dummy;
178  }
179 
180  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
181  adapter_run_permitted->updateRunPermitted(true);
182 
184 
185  /**********
186  * Step 2 *
187  **********/
188  {
189  InSequence dummy;
190  EXPECT_HOLD;
191  EXPECT_HALT;
192  }
193 
194  adapter_run_permitted->updateRunPermitted(false);
195 
197 
198  /**********
199  * Step 3 *
200  **********/
201  {
202  InSequence dummy;
205  }
206 
207  std::atomic_bool keep_spamming{ true };
208  std::thread spam_enable{ [&adapter_run_permitted, &keep_spamming]() {
209  while (keep_spamming)
210  {
211  adapter_run_permitted->updateRunPermitted(true);
212  }
213  } };
214 
216 
217  keep_spamming = false;
218  spam_enable.join();
219 }
220 
245 TEST_F(Stop1ExecutorTest, testSpamEnablePlusStop)
246 {
247  /**********
248  * Step 1 *
249  **********/
250  {
251  InSequence dummy;
254  }
255 
256  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
257 
258  std::atomic_bool keep_spamming{ true };
259  std::thread spam_enable{ [&adapter_run_permitted, &keep_spamming]() {
260  while (keep_spamming)
261  {
262  adapter_run_permitted->updateRunPermitted(true);
263  }
264  } };
265 
267 
268  keep_spamming = false;
269  spam_enable.join();
270 
271  /**********
272  * Step 2 *
273  **********/
274 
275  adapter_run_permitted->updateRunPermitted(true);
276 
277  /**********
278  * Step 3 *
279  **********/
280  {
281  InSequence dummy;
282  EXPECT_HOLD;
283  EXPECT_HALT;
284  }
285 
286  adapter_run_permitted->updateRunPermitted(false);
287 
289 }
290 
315 TEST_F(Stop1ExecutorTest, testSpamRunPermittedActivePlusEnable)
316 {
317  /**********
318  * Step 1 *
319  **********/
320  {
321  InSequence dummy;
324  }
325 
326  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
327 
328  adapter_run_permitted->updateRunPermitted(true);
329 
331 
332  /**********
333  * Step 2 *
334  **********/
335  {
336  InSequence dummy;
337  EXPECT_HOLD;
338  EXPECT_HALT;
339  }
340 
341  std::atomic_bool keep_spamming{ true };
342  std::thread spam_disable{ [&adapter_run_permitted, &keep_spamming]() {
343  while (keep_spamming)
344  {
345  adapter_run_permitted->updateRunPermitted(false);
346  }
347  } };
348 
350 
351  keep_spamming = false;
352  spam_disable.join();
353 
354  /**********
355  * Step 3 *
356  **********/
357  {
358  InSequence dummy;
361  }
362 
363  keep_spamming = true;
364  std::thread spam_enable{ [&adapter_run_permitted, &keep_spamming]() {
365  while (keep_spamming)
366  {
367  adapter_run_permitted->updateRunPermitted(true);
368  }
369  } };
370 
372 
373  keep_spamming = false;
374  spam_enable.join();
375 }
376 
401 TEST_F(Stop1ExecutorTest, testSkippingHoldPlusEnable)
402 {
403  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
404 
405  // define function for recover-invoke action
406  auto run_permitted_false_during_recover_action = [this, &adapter_run_permitted]() {
407  this->triggerClearEvent(RECOVER_SRV_CALLED_EVENT);
408  adapter_run_permitted->updateRunPermitted(false);
409  return true;
410  };
411 
412  auto halt_action = [this]() {
413  this->triggerClearEvent(HALT_SRV_CALLED_EVENT);
414  return true;
415  };
416 
417  /**********
418  * Step 1 *
419  **********/
420  {
421  InSequence dummy;
422 
423  EXPECT_CALL(*this, recover_func()).WillOnce(InvokeWithoutArgs(run_permitted_false_during_recover_action));
424 
425  EXPECT_CALL(*this, halt_func()).WillOnce(InvokeWithoutArgs(halt_action));
426  }
427 
428  adapter_run_permitted->updateRunPermitted(true);
429 
431 
432  /**********
433  * Step 2 *
434  **********/
435  {
436  InSequence dummy;
439  }
440 
441  std::atomic_bool keep_spamming{ true };
442  std::thread spam_enable{ [&adapter_run_permitted, &keep_spamming]() {
443  while (keep_spamming)
444  {
445  adapter_run_permitted->updateRunPermitted(true);
446  }
447  } };
448 
450 
451  keep_spamming = false;
452  spam_enable.join();
453 }
454 
474 TEST_F(Stop1ExecutorTest, testEnableDuringHoldService)
475 {
476  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
477 
478  auto enable_during_hold_action = [this, &adapter_run_permitted]() {
479  adapter_run_permitted->updateRunPermitted(true);
480  this->triggerClearEvent(HOLD_SRV_CALLED_EVENT);
481  return true;
482  };
483 
484  /**********
485  * Step 1 *
486  **********/
487  {
488  InSequence dummy;
489 
492  }
493 
494  adapter_run_permitted->updateRunPermitted(true);
495 
497 
498  {
499  InSequence dummy;
500 
501  EXPECT_CALL(*this, hold_func()).WillOnce(InvokeWithoutArgs(enable_during_hold_action));
502 
503  EXPECT_HALT;
506  }
507 
508  adapter_run_permitted->updateRunPermitted(false);
509 
511 }
512 
531 TEST_F(Stop1ExecutorTest, testEnableDuringHaltService)
532 {
533  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
534 
535  // define function for recover-invoke action
536  auto run_permitted_false_during_recover_action = [this, &adapter_run_permitted]() {
537  this->triggerClearEvent(RECOVER_SRV_CALLED_EVENT);
538  adapter_run_permitted->updateRunPermitted(false);
539  return true;
540  };
541 
542  auto enable_during_halt_action = [this, &adapter_run_permitted]() {
543  this->triggerClearEvent(HALT_SRV_CALLED_EVENT);
544  adapter_run_permitted->updateRunPermitted(true);
545  return true;
546  };
547 
548  /**********
549  * Step 1 *
550  **********/
551 
552  const std::string recover_srv_called_event2{ "recover_srv_called2" };
553  const std::string unhold_srv_called_event2{ "unhold_srv_called2" };
554  {
555  InSequence dummy;
556 
557  EXPECT_CALL(*this, recover_func()).WillOnce(InvokeWithoutArgs(run_permitted_false_during_recover_action));
558 
559  EXPECT_CALL(*this, halt_func()).WillOnce(InvokeWithoutArgs(enable_during_halt_action));
560 
561  EXPECT_CALL(*this, recover_func()).WillOnce(InvokeWithoutArgs([this, recover_srv_called_event2]() {
562  this->triggerClearEvent(recover_srv_called_event2);
563  return true;
564  }));
565 
566  EXPECT_CALL(*this, unhold_func()).WillOnce(InvokeWithoutArgs([this, unhold_srv_called_event2]() {
567  this->triggerClearEvent(unhold_srv_called_event2);
568  return true;
569  }));
570  }
571 
572  adapter_run_permitted->updateRunPermitted(true);
573 
574  BARRIER({ RECOVER_SRV_CALLED_EVENT, HALT_SRV_CALLED_EVENT, recover_srv_called_event2, unhold_srv_called_event2 });
575 }
576 
596 TEST_F(Stop1ExecutorTest, testEnableDisableDuringHaltService)
597 {
598  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
599 
600  // define function for recover-invoke action
601  auto run_permitted_false_during_recover_action = [this, &adapter_run_permitted]() {
602  this->triggerClearEvent(RECOVER_SRV_CALLED_EVENT);
603  adapter_run_permitted->updateRunPermitted(false);
604  return true;
605  };
606 
607  auto enable_during_halt_action = [this, &adapter_run_permitted]() {
608  this->triggerClearEvent(HALT_SRV_CALLED_EVENT);
609  adapter_run_permitted->updateRunPermitted(true);
610  adapter_run_permitted->updateRunPermitted(false); // Important flip!
611  return true;
612  };
613 
614  /**********
615  * Step 1 *
616  **********/
617 
618  {
619  InSequence dummy;
620 
621  EXPECT_CALL(*this, recover_func()).Times(1).WillOnce(InvokeWithoutArgs(run_permitted_false_during_recover_action));
622 
623  EXPECT_CALL(*this, halt_func()).Times(1).WillOnce(InvokeWithoutArgs(enable_during_halt_action));
624  }
625 
626  adapter_run_permitted->updateRunPermitted(true);
627 
629 }
630 
655 TEST_F(Stop1ExecutorTest, testRecoverFailPlusRetry)
656 {
657  /**********
658  * Step 1 *
659  **********/
660  EXPECT_CALL(*this, recover_func())
661  .WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(RECOVER_SRV_CALLED_EVENT), Return(false)))
662  .WillRepeatedly(Return(false));
663 
664  // unhold is optional here
665  EXPECT_CALL(*this, unhold_func()).WillRepeatedly(Return(true));
666 
667  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
668 
669  adapter_run_permitted->updateRunPermitted(true);
670 
671  BARRIER({ RECOVER_SRV_CALLED_EVENT });
672 
673  /**********
674  * Step 2 *
675  **********/
676  {
677  InSequence dummy;
678 
679  // hold and is_executing and is optional here
680  EXPECT_CALL(*this, hold_func()).WillRepeatedly(Return(true));
681 
682  EXPECT_HALT;
683  }
684 
685  adapter_run_permitted->updateRunPermitted(false);
686 
687  BARRIER({ HALT_SRV_CALLED_EVENT });
688 
689  /**********
690  * Step 3 *
691  **********/
692  {
693  InSequence dummy;
694 
697  }
698 
699  std::atomic_bool keep_spamming{ true };
700  std::thread spam_enable{ [&adapter_run_permitted, &keep_spamming]() {
701  while (keep_spamming)
702  {
703  adapter_run_permitted->updateRunPermitted(true);
704  }
705  } };
706 
708 
709  keep_spamming = false;
710  spam_enable.join();
711 }
712 
728 TEST_F(Stop1ExecutorTest, testUnholdFail)
729 {
730  /**********
731  * Step 1 *
732  **********/
733  {
734  InSequence dummy;
735 
737 
738  EXPECT_CALL(*this, unhold_func())
739  .WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(UNHOLD_SRV_CALLED_EVENT), Return(false)))
740  .WillRepeatedly(Return(false));
741  }
742 
743  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
744  adapter_run_permitted->updateRunPermitted(true);
745 
747 
748  /**********
749  * Step 2 *
750  **********/
751  {
752  InSequence dummy;
753 
754  EXPECT_HOLD;
755  EXPECT_HALT;
756  }
757 
758  adapter_run_permitted->updateRunPermitted(false);
759 
761 }
762 
779 TEST_F(Stop1ExecutorTest, testHoldFail)
780 {
781  /**********
782  * Step 1 *
783  **********/
784  {
785  InSequence dummy;
788  }
789 
790  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
791 
792  adapter_run_permitted->updateRunPermitted(true);
793 
795 
796  /**********
797  * Step 2 *
798  **********/
799  {
800  InSequence dummy;
801 
802  EXPECT_CALL(*this, hold_func())
803  .WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(HOLD_SRV_CALLED_EVENT), Return(false)))
804  .WillRepeatedly(Return(false));
805 
806  EXPECT_CALL(*this, halt_func())
807  .WillOnce(DoAll(ACTION_OPEN_BARRIER_VOID(HALT_SRV_CALLED_EVENT), Return(false)))
808  .WillRepeatedly(Return(false));
809  }
810 
811  adapter_run_permitted->updateRunPermitted(false);
812 
814 }
815 
829 TEST_F(Stop1ExecutorTest, testHoldImmediatelyAfterUnhold)
830 {
831  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
832 
833  // define function for unhold-invoke action
834  auto unhold_action = [this, &adapter_run_permitted]() {
835  this->triggerClearEvent(UNHOLD_SRV_CALLED_EVENT);
836  adapter_run_permitted->updateRunPermitted(false);
837  return true;
838  };
839 
840  /**********
841  * Step 1 *
842  **********/
843  {
844  InSequence dummy;
845 
847 
848  EXPECT_CALL(*this, unhold_func()).WillOnce(InvokeWithoutArgs(unhold_action));
849 
850  EXPECT_HOLD;
851  EXPECT_HALT;
852  }
853 
854  adapter_run_permitted->updateRunPermitted(true);
855 
857 }
858 
872 TEST_F(Stop1ExecutorTest, testExitInStateEnabling)
873 {
874  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
875 
876  // define function for recover-invoke action
877  auto recover_action = [this, &adapter_run_permitted]() {
878  adapter_run_permitted->stopStateMachine();
879  this->triggerClearEvent(RECOVER_SRV_CALLED_EVENT);
880  return true;
881  };
882 
883  {
884  InSequence dummy;
885 
886  EXPECT_CALL(*this, recover_func()).WillOnce(InvokeWithoutArgs(recover_action));
887 
888  // do not exclude other service calls as stopping the state machine does not prevent further actions
889  EXPECT_CALL(*this, unhold_func()).WillRepeatedly(Return(true));
890  }
891 
892  adapter_run_permitted->updateRunPermitted(true);
893 
894  BARRIER({ RECOVER_SRV_CALLED_EVENT });
895 }
896 
912 TEST_F(Stop1ExecutorTest, testExitInStateStopping)
913 {
914  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
915 
916  /**********
917  * Step 1 *
918  **********/
919  {
920  InSequence dummy;
921 
924  }
925 
926  adapter_run_permitted->updateRunPermitted(true);
927 
929 
930  /**********
931  * Step 2 *
932  **********/
933  // define function for hold-invoke action
934  auto hold_action = [this, &adapter_run_permitted]() {
935  adapter_run_permitted->stopStateMachine();
936  this->triggerClearEvent(HOLD_SRV_CALLED_EVENT);
937  return true;
938  };
939 
940  {
941  InSequence dummy;
942 
943  EXPECT_CALL(*this, hold_func()).WillOnce(InvokeWithoutArgs(hold_action));
944 
945  // do not exclude other service calls as stopping the state machine does not prevent further actions
946  EXPECT_CALL(*this, halt_func()).WillRepeatedly(Return(true));
947  }
948 
949  adapter_run_permitted->updateRunPermitted(false);
950 
951  BARRIER({ HOLD_SRV_CALLED_EVENT });
952 }
953 
967 TEST_F(Stop1ExecutorTest, testExitInStateStopRequestedDuringEnable)
968 {
969  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
970 
971  // define function for recover-invoke action
972  auto recover_action = [this, &adapter_run_permitted]() {
973  adapter_run_permitted->updateRunPermitted(false);
974  adapter_run_permitted->stopStateMachine();
975  this->triggerClearEvent(RECOVER_SRV_CALLED_EVENT);
976  return true;
977  };
978 
979  /**********
980  * Step 1 *
981  **********/
982  {
983  InSequence dummy;
984 
985  EXPECT_CALL(*this, recover_func()).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()).WillRepeatedly(Return(true));
989  }
990 
991  adapter_run_permitted->updateRunPermitted(true);
992 
993  BARRIER(RECOVER_SRV_CALLED_EVENT);
994 }
995 
1012 TEST_F(Stop1ExecutorTest, testExitInStateEnableRequestDuringStop)
1013 {
1014  std::unique_ptr<Stop1ExecutorForTests> adapter_run_permitted{ createStop1Executor() };
1015 
1016  /**********
1017  * Step 1 *
1018  **********/
1019  {
1020  InSequence dummy;
1021 
1023  EXPECT_UNHOLD;
1024  }
1025 
1026  adapter_run_permitted->updateRunPermitted(true);
1027 
1029 
1030  /**********
1031  * Step 2 *
1032  **********/
1033 
1034  auto enable_during_hold_action = [this, &adapter_run_permitted]() {
1035  adapter_run_permitted->updateRunPermitted(true);
1036  adapter_run_permitted->stopStateMachine();
1037  this->triggerClearEvent(HOLD_SRV_CALLED_EVENT);
1038  return true;
1039  };
1040 
1041  {
1042  InSequence dummy;
1043 
1044  EXPECT_CALL(*this, hold_func()).WillOnce(InvokeWithoutArgs(enable_during_hold_action));
1045 
1046  // do not exclude other service calls as stopping the state machine does not prevent further actions
1047  EXPECT_CALL(*this, halt_func()).WillRepeatedly(Return(true));
1048  EXPECT_CALL(*this, recover_func()).WillRepeatedly(Return(true));
1049  EXPECT_CALL(*this, unhold_func()).WillRepeatedly(Return(true));
1050  }
1051 
1052  adapter_run_permitted->updateRunPermitted(false);
1053 
1054  BARRIER(HOLD_SRV_CALLED_EVENT);
1055 }
1056 
1057 } // namespace prbt_hardware_support_tests
1058 
1059 int main(int argc, char** argv)
1060 {
1061  // for (limited) ros::Time functionality, no ROS communication
1062  ros::Time::init();
1063 
1064  testing::InitGoogleMock(&argc, argv);
1065  return RUN_ALL_TESTS();
1066 }
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 Mon Feb 28 2022 23:14:34