Vendor MsQuic with build and install support, add DeviceManager status to configurable heartbeats, and report only enabled devices. Add the local QUIC gateway, protocol coverage, real MsQuic E2E tests, process smoke tests, and updated integration documentation.
380 lines
13 KiB
C++
380 lines
13 KiB
C++
#include "manager/device_manager/include/device_manager.h"
|
|
|
|
#include "devices/camera/abstract_camera.h"
|
|
#include "devices/dexhand/abstract_dexhand.h"
|
|
#include "devices/microphone/abstract_microphone.h"
|
|
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
namespace {
|
|
|
|
#define CHECK_TRUE(condition) \
|
|
do { \
|
|
if (!(condition)) { \
|
|
return false; \
|
|
} \
|
|
} while (false)
|
|
|
|
using cmvr::device::AbstractDevice;
|
|
using cmvr::device::DeviceHealthSnapshot;
|
|
using cmvr::device::DeviceHealthState;
|
|
using cmvr::device::DeviceKind;
|
|
using cmvr::device::DeviceManager;
|
|
using cmvr::device::DeviceManagerSnapshot;
|
|
using cmvr::device::ManagedDeviceSnapshot;
|
|
using cmvr::device::ManagedDeviceState;
|
|
|
|
class MemoryCamera final : public cmvr::device::AbstractCamera {
|
|
public:
|
|
std::string typeName() const override { return "MemoryCamera"; }
|
|
void getState(cmvr::device::CameraState& output) override
|
|
{
|
|
output = state;
|
|
}
|
|
|
|
cmvr::device::CameraState state{};
|
|
};
|
|
|
|
class MemoryMicrophone final : public cmvr::device::AbstractMicrophone {
|
|
public:
|
|
std::string typeName() const override { return "MemoryMicrophone"; }
|
|
void getState(cmvr::device::MicrophoneState& output) override
|
|
{
|
|
output = state;
|
|
}
|
|
|
|
cmvr::device::MicrophoneState state{};
|
|
};
|
|
|
|
class MemoryDexHand final : public cmvr::device::AbstractDexHand {
|
|
public:
|
|
std::string typeName() const override { return "MemoryDexHand"; }
|
|
Status state() const override { return lifecycle; }
|
|
std::string lastError() const override { return error; }
|
|
void setAngles(const std::vector<int>&) override {}
|
|
void setTactilePollingRegions(
|
|
const std::vector<TactileRegionKey>&) override {}
|
|
std::vector<TactileRegionData> getSensorData() override { return {}; }
|
|
TactileRegionData getSensorData(FingerType, TactileRegion) override
|
|
{
|
|
return {};
|
|
}
|
|
ResultantForce getResultantForce(FingerType, TactileRegion) override
|
|
{
|
|
return {};
|
|
}
|
|
|
|
Status lifecycle{Status::CREATED};
|
|
std::string error;
|
|
};
|
|
|
|
class FakeDevice final : public AbstractDevice {
|
|
public:
|
|
explicit FakeDevice(std::string id,
|
|
DeviceKind kind = DeviceKind::Camera)
|
|
: AbstractDevice(std::move(id)), kind_(kind)
|
|
{
|
|
}
|
|
|
|
DeviceKind kind() const noexcept override { return kind_; }
|
|
std::string typeName() const override { return "FakeDevice"; }
|
|
|
|
bool start() override
|
|
{
|
|
++start_calls;
|
|
if (throw_on_start) {
|
|
throw std::runtime_error(std::string(700, 's'));
|
|
}
|
|
return start_result;
|
|
}
|
|
|
|
bool stop() override
|
|
{
|
|
++stop_calls;
|
|
if (throw_on_stop) {
|
|
throw std::runtime_error(std::string(700, 't'));
|
|
}
|
|
return stop_result;
|
|
}
|
|
|
|
DeviceHealthSnapshot healthSnapshot() override
|
|
{
|
|
++health_calls;
|
|
if (throw_on_health) {
|
|
throw std::runtime_error(std::string(700, 'h'));
|
|
}
|
|
return health;
|
|
}
|
|
|
|
DeviceKind kind_;
|
|
bool start_result{true};
|
|
bool stop_result{true};
|
|
bool throw_on_start{false};
|
|
bool throw_on_stop{false};
|
|
bool throw_on_health{false};
|
|
DeviceHealthSnapshot health{DeviceHealthState::Healthy, {}};
|
|
std::atomic<int> start_calls{0};
|
|
std::atomic<int> stop_calls{0};
|
|
std::atomic<int> health_calls{0};
|
|
};
|
|
|
|
const ManagedDeviceSnapshot* findDevice(const DeviceManagerSnapshot& snapshot,
|
|
const std::string& id)
|
|
{
|
|
for (const auto& device : snapshot.devices) {
|
|
if (device.id == id) {
|
|
return &device;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool isSorted(const DeviceManagerSnapshot& snapshot)
|
|
{
|
|
for (std::size_t i = 1; i < snapshot.devices.size(); ++i) {
|
|
if (snapshot.devices[i].id < snapshot.devices[i - 1].id) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool testCategoryHealthAdapters()
|
|
{
|
|
MemoryCamera camera;
|
|
CHECK_TRUE(camera.healthSnapshot().state ==
|
|
DeviceHealthState::Unknown);
|
|
camera.state.is_initialized = true;
|
|
CHECK_TRUE(camera.healthSnapshot().state ==
|
|
DeviceHealthState::Healthy);
|
|
camera.state.error_message = "camera warning";
|
|
CHECK_TRUE(camera.healthSnapshot().state ==
|
|
DeviceHealthState::Degraded);
|
|
camera.state.is_error = true;
|
|
CHECK_TRUE(camera.healthSnapshot().state ==
|
|
DeviceHealthState::Fault);
|
|
|
|
MemoryMicrophone microphone;
|
|
microphone.state.is_initialized = true;
|
|
CHECK_TRUE(microphone.healthSnapshot().state ==
|
|
DeviceHealthState::Healthy);
|
|
microphone.state.is_error = true;
|
|
microphone.state.error_message = "microphone fault";
|
|
const auto microphone_health = microphone.healthSnapshot();
|
|
CHECK_TRUE(microphone_health.state == DeviceHealthState::Fault);
|
|
CHECK_TRUE(microphone_health.error_message == "microphone fault");
|
|
|
|
MemoryDexHand dexhand;
|
|
CHECK_TRUE(dexhand.healthSnapshot().state ==
|
|
DeviceHealthState::Unknown);
|
|
dexhand.lifecycle = MemoryDexHand::Status::INITIALIZED;
|
|
CHECK_TRUE(dexhand.healthSnapshot().state ==
|
|
DeviceHealthState::Healthy);
|
|
dexhand.error = "temporary warning";
|
|
CHECK_TRUE(dexhand.healthSnapshot().state ==
|
|
DeviceHealthState::Degraded);
|
|
dexhand.lifecycle = MemoryDexHand::Status::FAULT;
|
|
CHECK_TRUE(dexhand.healthSnapshot().state ==
|
|
DeviceHealthState::Fault);
|
|
return true;
|
|
}
|
|
|
|
bool testConfiguredAndDynamicSnapshots()
|
|
{
|
|
cmvr::config::DeviceManagerConfig config;
|
|
config.set_name("snapshot-test");
|
|
config.set_version("9.1");
|
|
config.set_description("device manager snapshot test");
|
|
|
|
auto* disabled = config.add_devices();
|
|
disabled->set_id("disabled_camera");
|
|
disabled->set_type(
|
|
cmvr::config::DeviceConfigEntry::DEVICE_TYPE_CAMERA);
|
|
disabled->set_enable(false);
|
|
|
|
auto* broken = config.add_devices();
|
|
broken->set_id("broken_device");
|
|
broken->set_type(
|
|
cmvr::config::DeviceConfigEntry::DEVICE_TYPE_UNKNOWN);
|
|
broken->set_enable(true);
|
|
|
|
auto* duplicate_disabled = config.add_devices();
|
|
duplicate_disabled->set_id("duplicate_device");
|
|
duplicate_disabled->set_type(
|
|
cmvr::config::DeviceConfigEntry::DEVICE_TYPE_CAMERA);
|
|
duplicate_disabled->set_enable(false);
|
|
|
|
auto* duplicate_enabled = config.add_devices();
|
|
duplicate_enabled->set_id("duplicate_device");
|
|
duplicate_enabled->set_type(
|
|
cmvr::config::DeviceConfigEntry::DEVICE_TYPE_CAMERA);
|
|
duplicate_enabled->set_enable(true);
|
|
|
|
auto& manager = DeviceManager::getInstance(config);
|
|
auto configured = manager.snapshot();
|
|
CHECK_TRUE(configured.name == "snapshot-test");
|
|
CHECK_TRUE(configured.version == "9.1");
|
|
CHECK_TRUE(configured.description == "device manager snapshot test");
|
|
CHECK_TRUE(configured.devices.size() == 3);
|
|
CHECK_TRUE(isSorted(configured));
|
|
|
|
const auto* disabled_status =
|
|
findDevice(configured, "disabled_camera");
|
|
CHECK_TRUE(disabled_status != nullptr);
|
|
CHECK_TRUE(!disabled_status->enabled);
|
|
CHECK_TRUE(disabled_status->kind == DeviceKind::Camera);
|
|
CHECK_TRUE(disabled_status->type_name == "Camera");
|
|
CHECK_TRUE(disabled_status->state == ManagedDeviceState::Disabled);
|
|
CHECK_TRUE(disabled_status->health.state ==
|
|
DeviceHealthState::Unknown);
|
|
CHECK_TRUE(!disabled_status->abnormal);
|
|
CHECK_TRUE(disabled_status->status_updated_at_unix_ms != 0);
|
|
|
|
const auto* broken_status = findDevice(configured, "broken_device");
|
|
CHECK_TRUE(broken_status != nullptr);
|
|
CHECK_TRUE(broken_status->enabled);
|
|
CHECK_TRUE(broken_status->state == ManagedDeviceState::Error);
|
|
CHECK_TRUE(broken_status->health.state == DeviceHealthState::Unknown);
|
|
CHECK_TRUE(broken_status->abnormal);
|
|
CHECK_TRUE(!broken_status->error_message.empty());
|
|
CHECK_TRUE(broken_status->error_message.size() <= 512);
|
|
|
|
const auto* duplicate_status =
|
|
findDevice(configured, "duplicate_device");
|
|
CHECK_TRUE(duplicate_status != nullptr);
|
|
CHECK_TRUE(duplicate_status->enabled);
|
|
CHECK_TRUE(duplicate_status->state == ManagedDeviceState::Error);
|
|
CHECK_TRUE(duplicate_status->abnormal);
|
|
CHECK_TRUE(duplicate_status->error_message ==
|
|
"duplicate configured device id: duplicate_device");
|
|
|
|
auto healthy = std::make_shared<FakeDevice>("z_healthy");
|
|
auto degraded = std::make_shared<FakeDevice>("a_degraded");
|
|
degraded->health = {
|
|
DeviceHealthState::Degraded, std::string(700, 'd')};
|
|
auto start_fail = std::make_shared<FakeDevice>("m_start_fail");
|
|
start_fail->start_result = false;
|
|
auto stop_fail = std::make_shared<FakeDevice>("n_stop_fail");
|
|
stop_fail->stop_result = false;
|
|
auto health_throw = std::make_shared<FakeDevice>("b_health_throw");
|
|
health_throw->throw_on_health = true;
|
|
|
|
manager.registerDevice(healthy);
|
|
manager.registerDevice(degraded);
|
|
manager.registerDevice(start_fail);
|
|
manager.registerDevice(stop_fail);
|
|
manager.registerDevice(health_throw);
|
|
|
|
// Duplicate registration must retain the original object and status.
|
|
manager.registerDevice(
|
|
std::make_shared<FakeDevice>("z_healthy", DeviceKind::Speaker));
|
|
CHECK_TRUE(manager.getDeviceBase("z_healthy") == healthy);
|
|
|
|
const auto registered = manager.snapshot();
|
|
CHECK_TRUE(isSorted(registered));
|
|
const auto* healthy_registered =
|
|
findDevice(registered, "z_healthy");
|
|
CHECK_TRUE(healthy_registered != nullptr);
|
|
CHECK_TRUE(healthy_registered->state ==
|
|
ManagedDeviceState::Registered);
|
|
CHECK_TRUE(healthy_registered->health.state ==
|
|
DeviceHealthState::Healthy);
|
|
CHECK_TRUE(!healthy_registered->abnormal);
|
|
|
|
const auto* degraded_registered =
|
|
findDevice(registered, "a_degraded");
|
|
CHECK_TRUE(degraded_registered != nullptr);
|
|
CHECK_TRUE(degraded_registered->abnormal);
|
|
CHECK_TRUE(degraded_registered->health.state ==
|
|
DeviceHealthState::Degraded);
|
|
CHECK_TRUE(degraded_registered->health.error_message.size() == 512);
|
|
CHECK_TRUE(degraded_registered->error_message.size() == 512);
|
|
|
|
const auto* thrown_health =
|
|
findDevice(registered, "b_health_throw");
|
|
CHECK_TRUE(thrown_health != nullptr);
|
|
CHECK_TRUE(thrown_health->abnormal);
|
|
CHECK_TRUE(thrown_health->health.state ==
|
|
DeviceHealthState::Fault);
|
|
CHECK_TRUE(thrown_health->health.error_message.size() <= 512);
|
|
CHECK_TRUE(thrown_health->error_message.size() <= 512);
|
|
|
|
manager.start();
|
|
const auto running = manager.snapshot();
|
|
CHECK_TRUE(findDevice(running, "z_healthy")->state ==
|
|
ManagedDeviceState::Running);
|
|
CHECK_TRUE(findDevice(running, "m_start_fail")->state ==
|
|
ManagedDeviceState::Error);
|
|
CHECK_TRUE(findDevice(running, "m_start_fail")->abnormal);
|
|
CHECK_TRUE(findDevice(running, "m_start_fail")->health.state ==
|
|
DeviceHealthState::Healthy);
|
|
CHECK_TRUE(healthy->start_calls.load() == 1);
|
|
|
|
// The earlier value snapshot remains independent from manager mutations.
|
|
CHECK_TRUE(healthy_registered->state ==
|
|
ManagedDeviceState::Registered);
|
|
|
|
manager.stop();
|
|
const auto stopped = manager.snapshot();
|
|
CHECK_TRUE(findDevice(stopped, "z_healthy")->state ==
|
|
ManagedDeviceState::Stopped);
|
|
CHECK_TRUE(findDevice(stopped, "n_stop_fail")->state ==
|
|
ManagedDeviceState::Error);
|
|
CHECK_TRUE(findDevice(stopped, "n_stop_fail")->abnormal);
|
|
CHECK_TRUE(healthy->stop_calls.load() == 1);
|
|
return true;
|
|
}
|
|
|
|
bool testConcurrentSnapshotAndRegistration()
|
|
{
|
|
auto& manager = DeviceManager::getInstance();
|
|
std::atomic<bool> done{false};
|
|
std::atomic<bool> reader_ok{true};
|
|
|
|
std::thread reader([&] {
|
|
while (!done.load(std::memory_order_acquire)) {
|
|
const auto current = manager.snapshot();
|
|
if (!isSorted(current)) {
|
|
reader_ok.store(false, std::memory_order_release);
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
|
|
for (int i = 0; i < 32; ++i) {
|
|
manager.registerDevice(
|
|
std::make_shared<FakeDevice>(
|
|
"concurrent_" + std::to_string(i)));
|
|
}
|
|
done.store(true, std::memory_order_release);
|
|
reader.join();
|
|
|
|
CHECK_TRUE(reader_ok.load(std::memory_order_acquire));
|
|
const auto final_snapshot = manager.snapshot();
|
|
CHECK_TRUE(isSorted(final_snapshot));
|
|
for (int i = 0; i < 32; ++i) {
|
|
CHECK_TRUE(
|
|
findDevice(final_snapshot,
|
|
"concurrent_" + std::to_string(i)) != nullptr);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main()
|
|
{
|
|
DeviceManager::destroyInstance();
|
|
const bool success =
|
|
testCategoryHealthAdapters() &&
|
|
testConfiguredAndDynamicSnapshots() &&
|
|
testConcurrentSnapshotAndRegistration();
|
|
DeviceManager::destroyInstance();
|
|
return success ? 0 : 1;
|
|
}
|