288 lines
9.3 KiB
C++
288 lines
9.3 KiB
C++
/*********************************************************************
|
|
*
|
|
* Kiểm đường nạp plugin thật: YAML -> library_path -> Boost.DLL -> schema.
|
|
*
|
|
* Test này cần PNKX_NAV_CORE_CONFIG_DIR trỏ vào test/config của gói:
|
|
*
|
|
* PNKX_NAV_CORE_CONFIG_DIR=src/AMR_T800/Test/mission_adapters/test/config \
|
|
* ./devel/lib/mission_adapters/plugin_registry_test
|
|
*
|
|
*********************************************************************/
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <cstdlib>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <robot/robot.h>
|
|
|
|
#include <mission_adapters/mission_adapters.h>
|
|
#include <mission_adapters/plugin_registry.h>
|
|
|
|
#include "mission_test_utils.h"
|
|
|
|
namespace
|
|
{
|
|
using namespace mission_adapters;
|
|
|
|
/**
|
|
* @brief Nguồn mission thứ ba, viết hoàn toàn trong test.
|
|
*
|
|
* Sự tồn tại của nó là bài kiểm tra thật cho tính plugin: thêm một loại nguồn mới mà không sửa file
|
|
* nào trong `src/` của gói.
|
|
*/
|
|
class DummySourceAdapter : public MissionSourceAdapter
|
|
{
|
|
public:
|
|
static constexpr const char* kSchema = "test.dummy";
|
|
|
|
bool configure(const std::string& name, robot::NodeHandle& nh) override
|
|
{
|
|
(void)nh;
|
|
name_ = name;
|
|
return true;
|
|
}
|
|
|
|
std::string schema() const override { return kSchema; }
|
|
|
|
bool validate(const MissionRequest& request, std::string& reason) const override
|
|
{
|
|
if (request.raw_payload.empty())
|
|
{
|
|
reason = "raw_payload is empty";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ConversionResult convert(const MissionRequest& request) override
|
|
{
|
|
(void)request;
|
|
auto mission = std::make_shared<Mission>();
|
|
mission->type = MissionType::SIMPLE_GOAL;
|
|
|
|
ConversionResult result;
|
|
result.missions.push_back(mission);
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
std::string name_;
|
|
};
|
|
|
|
class PluginRegistryTest : public ::testing::Test
|
|
{
|
|
protected:
|
|
robot::NodeHandle nh;
|
|
};
|
|
|
|
// ── Đường nạp bình thường ────────────────────────────────────────────────────────────────────────
|
|
|
|
TEST_F(PluginRegistryTest, LoadsBothSourcesFromConfig)
|
|
{
|
|
PluginRegistry registry;
|
|
|
|
ASSERT_TRUE(registry.loadFromConfig(nh))
|
|
<< "could not load the plugin — check PNKX_NAV_CORE_CONFIG_DIR and devel/lib";
|
|
|
|
EXPECT_EQ(registry.size(), 2u);
|
|
EXPECT_NE(registry.find(schema::kPoseStamped), nullptr);
|
|
EXPECT_NE(registry.find(schema::kVda5050Order), nullptr);
|
|
EXPECT_EQ(registry.find("does.not.exist"), nullptr);
|
|
}
|
|
|
|
TEST_F(PluginRegistryTest, LoadedAdapterConvertsRealPayload)
|
|
{
|
|
PluginRegistry registry;
|
|
ASSERT_TRUE(registry.loadFromConfig(nh));
|
|
|
|
auto* adapter = registry.find(schema::kPoseStamped);
|
|
ASSERT_NE(adapter, nullptr);
|
|
|
|
const auto request = MissionRequest::fromPose(mission_test::makeGoal(3.0, 4.0));
|
|
|
|
std::string reason;
|
|
ASSERT_TRUE(adapter->validate(request, reason)) << reason;
|
|
|
|
const auto result = adapter->convert(request);
|
|
ASSERT_EQ(result.missions.size(), 1u);
|
|
EXPECT_DOUBLE_EQ(result.missions.front()->goal.pose.position.x, 3.0);
|
|
}
|
|
|
|
// ── Các cách hỏng, đều phải báo lỗi rõ ràng chứ không crash ──────────────────────────────────────
|
|
|
|
TEST_F(PluginRegistryTest, MissingLibraryPathKeyFailsCleanly)
|
|
{
|
|
PluginRegistry registry;
|
|
|
|
EXPECT_FALSE(registry.loadFromConfig(nh, "registry_test_missing_library_path"));
|
|
EXPECT_EQ(registry.size(), 0u);
|
|
}
|
|
|
|
TEST_F(PluginRegistryTest, MissingLibraryFileFailsCleanly)
|
|
{
|
|
PluginRegistry registry;
|
|
|
|
EXPECT_FALSE(registry.loadFromConfig(nh, "registry_test_missing_library_file"));
|
|
EXPECT_EQ(registry.size(), 0u);
|
|
}
|
|
|
|
TEST_F(PluginRegistryTest, WrongSymbolNameFailsCleanly)
|
|
{
|
|
PluginRegistry registry;
|
|
|
|
// Thư viện có thật, symbol thì không: import_alias ném system_error, registry phải nuốt và báo.
|
|
EXPECT_FALSE(registry.loadFromConfig(nh, "registry_test_wrong_symbol"));
|
|
EXPECT_EQ(registry.size(), 0u);
|
|
}
|
|
|
|
TEST_F(PluginRegistryTest, DuplicateSchemaIsRejected)
|
|
{
|
|
PluginRegistry registry;
|
|
|
|
// Hai instance cùng khai schema "geometry.pose_stamped": cái thứ hai bị từ chối thay vì ghi đè
|
|
// im lặng, vì định tuyến khi đó sẽ phụ thuộc thứ tự nạp.
|
|
EXPECT_FALSE(registry.loadFromConfig(nh, "registry_test_duplicate_schema"));
|
|
EXPECT_EQ(registry.size(), 1u);
|
|
}
|
|
|
|
TEST_F(PluginRegistryTest, EntryWithoutTypeFailsCleanly)
|
|
{
|
|
PluginRegistry registry;
|
|
|
|
EXPECT_FALSE(registry.loadFromConfig(nh, "registry_test_entry_without_type"));
|
|
EXPECT_EQ(registry.size(), 0u);
|
|
}
|
|
|
|
TEST_F(PluginRegistryTest, MissingSourceListFailsCleanly)
|
|
{
|
|
PluginRegistry registry;
|
|
|
|
EXPECT_FALSE(registry.loadFromConfig(nh, "registry_test_empty"));
|
|
EXPECT_EQ(registry.size(), 0u);
|
|
}
|
|
|
|
// ── Đăng ký trực tiếp (nguồn biên dịch thẳng vào host, hoặc test) ───────────────────────────────
|
|
|
|
TEST_F(PluginRegistryTest, RejectsNullAndDuplicateManualRegistration)
|
|
{
|
|
PluginRegistry registry;
|
|
|
|
EXPECT_FALSE(registry.registerAdapter(nullptr));
|
|
|
|
auto first = std::make_shared<DummySourceAdapter>();
|
|
EXPECT_TRUE(registry.registerAdapter(first));
|
|
|
|
auto second = std::make_shared<DummySourceAdapter>();
|
|
EXPECT_FALSE(registry.registerAdapter(second)) << "two adapters with the same schema were both "
|
|
"accepted";
|
|
|
|
EXPECT_EQ(registry.size(), 1u);
|
|
}
|
|
|
|
/**
|
|
* Nguồn thứ ba đi hết đường: đăng ký -> EventProcessor định tuyến theo schema -> mission vào hàng
|
|
* đợi. Không có dòng nào trong `src/` của gói biết tới DummySourceAdapter.
|
|
*/
|
|
TEST_F(PluginRegistryTest, ThirdPartyAdapterFlowsThroughCoreUnchanged)
|
|
{
|
|
PluginRegistry registry;
|
|
ASSERT_TRUE(registry.registerAdapter(std::make_shared<DummySourceAdapter>()));
|
|
|
|
MissionManager manager;
|
|
EventProcessor processor(manager, registry);
|
|
processor.start();
|
|
|
|
processor.submitRequest(
|
|
MissionRequest::fromRaw(DummySourceAdapter::kSchema, "{\"job\":\"pick\"}"));
|
|
|
|
EXPECT_TRUE(mission_test::waitForState(manager, MissionState::QUEUED,
|
|
std::chrono::milliseconds(500)));
|
|
|
|
// Payload không hợp lệ -> validate() chặn -> hàng đợi không đổi (A1).
|
|
processor.submitRequest(MissionRequest::fromRaw(DummySourceAdapter::kSchema, ""));
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(80));
|
|
|
|
EXPECT_EQ(manager.state(), MissionState::QUEUED);
|
|
EXPECT_TRUE(manager.hasMission());
|
|
|
|
processor.stop();
|
|
}
|
|
|
|
/**
|
|
* @brief Plugin hỏng: sinh mission không goal và cũng không action.
|
|
*
|
|
* Core phải chặn, vì chặng như vậy không có việc gì để làm và sẽ không bao giờ báo kết quả về —
|
|
* mission layer kẹt RUNNING vĩnh viễn.
|
|
*/
|
|
class EmptyMissionAdapter : public MissionSourceAdapter
|
|
{
|
|
public:
|
|
static constexpr const char* kSchema = "test.empty_mission";
|
|
|
|
bool configure(const std::string&, robot::NodeHandle&) override { return true; }
|
|
std::string schema() const override { return kSchema; }
|
|
bool validate(const MissionRequest&, std::string&) const override { return true; }
|
|
|
|
ConversionResult convert(const MissionRequest&) override
|
|
{
|
|
auto mission = std::make_shared<Mission>();
|
|
mission->has_goal = false; // và actions rỗng
|
|
|
|
ConversionResult result;
|
|
result.missions.push_back(mission);
|
|
return result;
|
|
}
|
|
};
|
|
|
|
TEST_F(PluginRegistryTest, MissionWithoutGoalAndWithoutActionIsRejected)
|
|
{
|
|
PluginRegistry registry;
|
|
ASSERT_TRUE(registry.registerAdapter(std::make_shared<EmptyMissionAdapter>()));
|
|
|
|
MissionManager manager;
|
|
EventProcessor processor(manager, registry);
|
|
|
|
processor.start();
|
|
processor.submitRequest(MissionRequest::fromRaw(EmptyMissionAdapter::kSchema, "payload"));
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(80));
|
|
processor.stop();
|
|
|
|
EXPECT_EQ(manager.state(), MissionState::IDLE);
|
|
EXPECT_FALSE(manager.hasMission());
|
|
}
|
|
|
|
TEST_F(PluginRegistryTest, UnknownSchemaIsIgnoredNotCrashing)
|
|
{
|
|
PluginRegistry registry;
|
|
MissionManager manager;
|
|
EventProcessor processor(manager, registry);
|
|
|
|
processor.start();
|
|
processor.submitRequest(MissionRequest::fromRaw("nobody.handles.this", "payload"));
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(80));
|
|
processor.stop();
|
|
|
|
EXPECT_EQ(manager.state(), MissionState::IDLE);
|
|
EXPECT_FALSE(manager.hasMission());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
// Test tự trỏ vào cây config và thư mục .so của chính nó, để chạy được cả qua ctest lẫn khi gọi
|
|
// thẳng binary (đúng cách test_costmap đang làm). overwrite = 0 nên biến môi trường do người
|
|
// chạy đặt vẫn thắng.
|
|
#ifdef MISSION_ADAPTERS_TEST_CONFIG_DIR
|
|
setenv("PNKX_NAV_CORE_CONFIG_DIR", MISSION_ADAPTERS_TEST_CONFIG_DIR, 0);
|
|
#endif
|
|
#ifdef MISSION_ADAPTERS_TEST_LIBRARY_DIR
|
|
setenv("PNKX_NAV_CORE_LIBRARY_PATH", MISSION_ADAPTERS_TEST_LIBRARY_DIR, 0);
|
|
#endif
|
|
|
|
testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|