@@ -707,6 +707,9 @@ TEST_F(LogSystemTest, RecordAndPlayback)
707
707
// Create temp directory to store log
708
708
this ->CreateLogsDir ();
709
709
710
+ // Used to count the expected number of poses recorded. Counting is necessary
711
+ // as the number varied depending on the CPU load.
712
+ int expectedPoseCount = 0 ;
710
713
// Record
711
714
{
712
715
// World with moving entities
@@ -725,6 +728,23 @@ TEST_F(LogSystemTest, RecordAndPlayback)
725
728
recordServerConfig.SetSdfString (recordSdfRoot.Element ()->ToString (" " ));
726
729
Server recordServer (recordServerConfig);
727
730
731
+ std::chrono::steady_clock::time_point lastPoseTime;
732
+ const int poseHz = 60 ;
733
+ const std::chrono::duration<double > msgPeriod{1.0 / poseHz};
734
+ // This system counts the expected number of poses recorded by reproducing
735
+ // the throttle mechanism used by ign-transport.
736
+ test::Relay recordedPoseCounter;
737
+ recordedPoseCounter.OnPostUpdate (
738
+ [&](const UpdateInfo &, const EntityComponentManager &)
739
+ {
740
+ auto tNow = std::chrono::steady_clock::now ();
741
+ if ((tNow - lastPoseTime) > msgPeriod)
742
+ {
743
+ lastPoseTime = tNow;
744
+ ++expectedPoseCount;
745
+ }
746
+ });
747
+ recordServer.AddSystem (recordedPoseCounter.systemPtr );
728
748
// Run for a few seconds to record different poses
729
749
recordServer.Run (true , 1000 , false );
730
750
}
@@ -814,98 +834,84 @@ TEST_F(LogSystemTest, RecordAndPlayback)
814
834
// Callback function for entities played back
815
835
// Compare current pose being played back with the pose with the closest
816
836
// timestamp in the recorded file.
817
- std::function<void (const msgs::Pose_V &)> msgCb =
818
- [&](const msgs::Pose_V &_playedMsg) -> void
819
- {
820
- // Playback continues even after the log file is over
821
- if (batch.end () == recordedIter)
822
- return ;
823
-
824
- ASSERT_TRUE (_playedMsg.has_header ());
825
- ASSERT_TRUE (_playedMsg.header ().has_stamp ());
826
- EXPECT_EQ (0 , _playedMsg.header ().stamp ().sec ());
827
-
828
- // Get next recorded message
829
- EXPECT_EQ (" ignition.msgs.Pose_V" , recordedIter->Type ());
830
- recordedMsg.ParseFromString (recordedIter->Data ());
831
-
832
- ASSERT_TRUE (recordedMsg.has_header ());
833
- ASSERT_TRUE (recordedMsg.header ().has_stamp ());
834
- EXPECT_EQ (0 , recordedMsg.header ().stamp ().sec ());
835
-
836
- // Log clock timestamp matches message timestamp
837
- EXPECT_EQ (recordedMsg.header ().stamp ().nsec (),
838
- recordedIter->TimeReceived ().count ());
839
-
840
- // Dynamic poses are throttled according to real time during playback,
841
- // so we can't guarantee the exact timestamp as recorded.
842
- EXPECT_NEAR (_playedMsg.header ().stamp ().nsec (),
843
- recordedMsg.header ().stamp ().nsec (), 100000000 );
844
-
845
- // Loop through all recorded poses, update map
846
- std::map<std::string, msgs::Pose> entityRecordedPose;
847
- for (int i = 0 ; i < recordedMsg.pose_size (); ++i)
848
- {
849
- entityRecordedPose[recordedMsg.pose (i).name ()] = recordedMsg.pose (i);
850
- }
837
+ test::Relay playbackPoseTester;
838
+ playbackPoseTester.OnPostUpdate (
839
+ [&](const UpdateInfo &_info, const EntityComponentManager &_ecm)
840
+ {
841
+ // Playback continues even after the log file is over
842
+ if (batch.end () == recordedIter)
843
+ return ;
844
+
845
+ // Get next recorded message
846
+ EXPECT_EQ (" ignition.msgs.Pose_V" , recordedIter->Type ());
847
+ recordedMsg.ParseFromString (recordedIter->Data ());
848
+
849
+ ASSERT_TRUE (recordedMsg.has_header ());
850
+ ASSERT_TRUE (recordedMsg.header ().has_stamp ());
851
+ EXPECT_EQ (0 , recordedMsg.header ().stamp ().sec ());
852
+
853
+ // Log clock timestamp matches message timestamp
854
+ EXPECT_EQ (recordedMsg.header ().stamp ().nsec (),
855
+ recordedIter->TimeReceived ().count ());
856
+
857
+ // A recorded messages is matched when its timestamp is within 100us
858
+ // of the current iteration's sim time.
859
+ if (std::abs ((_info.simTime - recordedIter->TimeReceived ()).count ()) >
860
+ 100000 )
861
+ {
862
+ return ;
863
+ }
851
864
852
- // Has 6 dynamic entities: 4 in dbl pendulum and 2 in nested model
853
- EXPECT_EQ (6 , _playedMsg.pose ().size ());
854
- EXPECT_EQ (6u , entityRecordedPose.size ());
865
+ // Has 6 dynamic entities: 4 in dbl pendulum and 2 in nested model
866
+ EXPECT_EQ (6 , recordedMsg.pose_size ());
855
867
856
- // Loop through all entities and compare played poses to recorded ones
857
- for (int i = 0 ; i < _playedMsg.pose_size (); ++i)
858
- {
859
- auto posePlayed = msgs::Convert (_playedMsg.pose (i));
860
- auto poseRecorded = msgs::Convert (
861
- entityRecordedPose[_playedMsg.pose (i).name ()]);
862
-
863
- EXPECT_NEAR (posePlayed.Pos ().X (), poseRecorded.Pos ().X (), 0.1 )
864
- << _playedMsg.pose (i).name ();
865
- EXPECT_NEAR (posePlayed.Pos ().Y (), poseRecorded.Pos ().Y (), 0.1 )
866
- << _playedMsg.pose (i).name ();
867
- EXPECT_NEAR (posePlayed.Pos ().Z (), poseRecorded.Pos ().Z (), 0.1 )
868
- << _playedMsg.pose (i).name ();
869
-
870
- EXPECT_NEAR (posePlayed.Rot ().Roll (), poseRecorded.Rot ().Roll (), 0.1 )
871
- << _playedMsg.pose (i).name ();
872
- EXPECT_NEAR (posePlayed.Rot ().Pitch (), poseRecorded.Rot ().Pitch (), 0.1 )
873
- << _playedMsg.pose (i).name ();
874
- EXPECT_NEAR (posePlayed.Rot ().Yaw (), poseRecorded.Rot ().Yaw (), 0.1 )
875
- << _playedMsg.pose (i).name ();
876
- }
868
+ // Loop through all recorded poses, and check them against the
869
+ // playedback poses.
870
+ for (int i = 0 ; i < recordedMsg.pose_size (); ++i)
871
+ {
872
+ const math::Pose3d &poseRecorded = msgs::Convert (recordedMsg.pose (i));
873
+ const std::string &name = recordedMsg.pose (i).name ();
874
+
875
+ auto entity = _ecm.EntityByComponents (
876
+ components::Name (recordedMsg.pose (i).name ()));
877
+ ASSERT_NE (kNullEntity , entity);
878
+ auto poseComp = _ecm.Component <components::Pose>(entity);
879
+ ASSERT_NE (nullptr , poseComp);
880
+ const auto &posePlayed = poseComp->Data ();
881
+
882
+ EXPECT_NEAR (posePlayed.Pos ().X (), poseRecorded.Pos ().X (), 0.1 )
883
+ << name;
884
+ EXPECT_NEAR (posePlayed.Pos ().Y (), poseRecorded.Pos ().Y (), 0.1 )
885
+ << name;
886
+ EXPECT_NEAR (posePlayed.Pos ().Z (), poseRecorded.Pos ().Z (), 0.1 )
887
+ << name;
888
+
889
+ EXPECT_NEAR (posePlayed.Rot ().Roll (), poseRecorded.Rot ().Roll (), 0.1 )
890
+ << name;
891
+ EXPECT_NEAR (posePlayed.Rot ().Pitch (), poseRecorded.Rot ().Pitch (), 0.1 )
892
+ << name;
893
+ EXPECT_NEAR (posePlayed.Rot ().Yaw (), poseRecorded.Rot ().Yaw (), 0.1 )
894
+ << name;
895
+ }
877
896
878
- ++recordedIter;
879
- nTotal++;
880
- };
897
+ ++recordedIter;
898
+ });
881
899
882
- // Subscribe to ignition topic and compare to logged poses
883
- transport::Node node;
884
- // TODO(louise) The world name should match the recorded world
885
- node.Subscribe (" /world/default/dynamic_pose/info" , msgCb);
886
-
887
- int playbackSteps = 500 ;
888
- int poseHz = 60 ;
889
- int expectedPoseCount = playbackSteps * 1e-3 / (1.0 /poseHz);
890
- // Run for a few seconds to play back different poses
891
- playServer.Run (true , playbackSteps, false );
892
-
893
- int sleep = 0 ;
894
- int maxSleep = 30 ;
895
- for (; nTotal < expectedPoseCount && sleep < maxSleep; ++sleep )
896
- {
897
- std::this_thread::sleep_for (std::chrono::milliseconds (100 ));
898
- }
899
- EXPECT_NE (maxSleep, sleep );
900
+ playServer.AddSystem (playbackPoseTester.systemPtr );
901
+
902
+ // Playback a subset of the log file and check for poses. Note: the poses are
903
+ // checked in the playbackPoseTester
904
+ playServer.Run (true , 500 , false );
905
+
906
+ // Count the total number of pose messages in the log file
907
+ for (auto it = batch.begin (); it != batch.end (); ++it, ++nTotal) { }
900
908
901
- #if !defined (__APPLE__)
902
- // / \todo(anyone) there seems to be a race condition that sometimes cause an
903
- // / additional messages to be published by the scene broadcaster
904
- // 60Hz
905
- EXPECT_TRUE (nTotal == expectedPoseCount || nTotal == expectedPoseCount + 1 )
909
+ // The expectedPoseCount might be off by ±2 because the ign transport's
910
+ // throttle mechanism (which is used by the SceneBroadcaster when publishing
911
+ // poses) uses real-time
912
+ EXPECT_LE (std::abs (nTotal - expectedPoseCount), 2 )
906
913
<< " nTotal [" << nTotal << " ] expectedPoseCount [" << expectedPoseCount
907
914
<< " ]" ;
908
- #endif
909
915
910
916
this ->RemoveLogsDir ();
911
917
}
0 commit comments