180 lines
5.3 KiB
C++
180 lines
5.3 KiB
C++
/*********************************************************************
|
|
*
|
|
* Kiểm WaitRecovery — behavior mới của bộ default.
|
|
*
|
|
* Đây là recovery an toàn nhất (robot không di chuyển) và hữu dụng nhất cho AMR trong kho, nơi phần
|
|
* lớn tình huống chặn đường là vật cản động. Nó cũng là chỗ rẻ nhất để chứng minh đường `elapsed`
|
|
* của base chạy đúng theo đồng hồ thật.
|
|
*
|
|
* Author: DuongTD
|
|
*********************************************************************/
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <robot/node_handle.h>
|
|
|
|
#include <recovery_core/recovery_registry.h>
|
|
|
|
#include "recovery_test_utils.h"
|
|
|
|
namespace
|
|
{
|
|
using recovery_core::RecoveryGoal;
|
|
using recovery_core::RecoveryOutputType;
|
|
using recovery_core::RecoveryStatus;
|
|
using recovery_test::VelocityRig;
|
|
|
|
constexpr double kConfiguredWait = 3.0; // [s] khớp `recovery/wait/wait_duration`
|
|
|
|
struct WaitFixture
|
|
{
|
|
WaitFixture()
|
|
{
|
|
loaded = registry.loadFromConfig(nh, "recovery", rig.ctx);
|
|
wait = recovery_test::findBehavior(registry, "wait");
|
|
}
|
|
|
|
VelocityRig rig;
|
|
robot::NodeHandle nh;
|
|
recovery_core::RecoveryRegistry registry;
|
|
bool loaded = false;
|
|
recovery_core::RecoveryBehavior* wait = nullptr;
|
|
};
|
|
|
|
TEST(WaitRecovery, DeclaresNoOutputFamily)
|
|
{
|
|
WaitFixture fixture;
|
|
ASSERT_TRUE(fixture.loaded);
|
|
ASSERT_NE(fixture.wait, nullptr);
|
|
|
|
EXPECT_EQ(fixture.wait->outputKind(), RecoveryOutputType::kNone);
|
|
}
|
|
|
|
TEST(WaitRecovery, NeverEmitsVelocity)
|
|
{
|
|
WaitFixture fixture;
|
|
ASSERT_TRUE(fixture.loaded);
|
|
ASSERT_NE(fixture.wait, nullptr);
|
|
|
|
ASSERT_TRUE(fixture.wait->start(RecoveryGoal(), robot::Time(1000.0)));
|
|
|
|
robot::Time now(1000.0);
|
|
for (int i = 0; i < 60; ++i)
|
|
{
|
|
now = robot::Time(now.toSec() + 0.1);
|
|
const auto result = fixture.wait->update(now);
|
|
// Behavior đứng yên tuyệt đối không được làm caller tưởng nó đang lái robot.
|
|
EXPECT_EQ(result.velocity(), nullptr);
|
|
EXPECT_EQ(result.output_type, RecoveryOutputType::kNone);
|
|
if (result.terminal())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(WaitRecovery, SucceedsAfterConfiguredDuration)
|
|
{
|
|
WaitFixture fixture;
|
|
ASSERT_TRUE(fixture.loaded);
|
|
ASSERT_NE(fixture.wait, nullptr);
|
|
|
|
ASSERT_TRUE(fixture.wait->start(RecoveryGoal(), robot::Time(1000.0)));
|
|
|
|
EXPECT_EQ(fixture.wait->update(robot::Time(1001.0)).status, RecoveryStatus::kRunning);
|
|
EXPECT_EQ(fixture.wait->update(robot::Time(1002.9)).status, RecoveryStatus::kRunning);
|
|
EXPECT_EQ(fixture.wait->update(robot::Time(1003.0)).status, RecoveryStatus::kSucceeded);
|
|
}
|
|
|
|
TEST(WaitRecovery, CountsByClockNotByTickCount)
|
|
{
|
|
WaitFixture fixture;
|
|
ASSERT_TRUE(fixture.loaded);
|
|
ASSERT_NE(fixture.wait, nullptr);
|
|
|
|
ASSERT_TRUE(fixture.wait->start(RecoveryGoal(), robot::Time(1000.0)));
|
|
|
|
// Một tick duy nhất nhưng nhảy qua trọn thời lượng: phải xong ngay, không cần đủ số nhịp.
|
|
const auto result = fixture.wait->update(robot::Time(1000.0 + kConfiguredWait));
|
|
|
|
EXPECT_EQ(result.status, RecoveryStatus::kSucceeded);
|
|
EXPECT_NEAR(result.elapsed, kConfiguredWait, 1e-6);
|
|
}
|
|
|
|
TEST(WaitRecovery, ProgressAdvancesMonotonically)
|
|
{
|
|
WaitFixture fixture;
|
|
ASSERT_TRUE(fixture.loaded);
|
|
ASSERT_NE(fixture.wait, nullptr);
|
|
|
|
ASSERT_TRUE(fixture.wait->start(RecoveryGoal(), robot::Time(1000.0)));
|
|
|
|
double last = -1.0;
|
|
for (int i = 1; i <= 5; ++i)
|
|
{
|
|
const auto result = fixture.wait->update(robot::Time(1000.0 + 0.5 * i));
|
|
EXPECT_GE(result.progress, last);
|
|
EXPECT_GE(result.remaining, 0.0);
|
|
last = result.progress;
|
|
}
|
|
}
|
|
|
|
TEST(WaitRecovery, PerRunDurationOverride)
|
|
{
|
|
WaitFixture fixture;
|
|
ASSERT_TRUE(fixture.loaded);
|
|
ASSERT_NE(fixture.wait, nullptr);
|
|
|
|
RecoveryGoal goal;
|
|
goal.params["wait_duration"] = 1.0;
|
|
|
|
ASSERT_TRUE(fixture.wait->start(goal, robot::Time(1000.0)));
|
|
|
|
EXPECT_EQ(fixture.wait->update(robot::Time(1000.5)).status, RecoveryStatus::kRunning);
|
|
EXPECT_EQ(fixture.wait->update(robot::Time(1001.0)).status, RecoveryStatus::kSucceeded);
|
|
}
|
|
|
|
TEST(WaitRecovery, InvalidOverrideFallsBackToConfiguredDuration)
|
|
{
|
|
WaitFixture fixture;
|
|
ASSERT_TRUE(fixture.loaded);
|
|
ASSERT_NE(fixture.wait, nullptr);
|
|
|
|
RecoveryGoal goal;
|
|
goal.params["wait_duration"] = -5.0; // vô lý -> phải cảnh báo và dùng default
|
|
|
|
ASSERT_TRUE(fixture.wait->start(goal, robot::Time(1000.0)));
|
|
|
|
EXPECT_EQ(fixture.wait->update(robot::Time(1002.0)).status, RecoveryStatus::kRunning);
|
|
EXPECT_EQ(fixture.wait->update(robot::Time(1003.0)).status, RecoveryStatus::kSucceeded);
|
|
}
|
|
|
|
TEST(WaitRecovery, NeedsNoPoseOrCollisionPorts)
|
|
{
|
|
// Điểm mạnh của WaitRecovery: chạy được cả khi TF hỏng, nên nó là đường phục hồi cuối cùng còn
|
|
// dùng được khi mọi thứ khác đã mất pose.
|
|
WaitFixture fixture;
|
|
ASSERT_TRUE(fixture.loaded);
|
|
ASSERT_NE(fixture.wait, nullptr);
|
|
|
|
fixture.rig.pose.setAvailable(false);
|
|
|
|
ASSERT_TRUE(fixture.wait->start(RecoveryGoal(), robot::Time(1000.0)));
|
|
EXPECT_EQ(fixture.wait->update(robot::Time(1003.0)).status, RecoveryStatus::kSucceeded);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
#ifdef RECOVERY_CORE_TEST_CONFIG_DIR
|
|
setenv("PNKX_NAV_CORE_CONFIG_DIR", RECOVERY_CORE_TEST_CONFIG_DIR, 0);
|
|
#endif
|
|
#ifdef RECOVERY_CORE_TEST_LIBRARY_DIR
|
|
setenv("PNKX_NAV_CORE_LIBRARY_PATH", RECOVERY_CORE_TEST_LIBRARY_DIR, 0);
|
|
#endif
|
|
testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|