模擬 DDK

驅動程式庫單元測試架構

簡易範例

以下是使用 mock-ddk 程式庫模擬驅動程式驅動程式庫的基本示例 以便進行測試

首先,需要測試的簡易驅動程式庫。這個範例驅動程式庫會是 但本文所述的所有程式碼都會使用這種參數

// Very simple driver:
class MyDevice;
using MyDeviceType = ddk::Device<MyDevice, ddk::Unbindable, ddk::Initializable>;

class MyDevice : public MyDeviceType {
  public:
    MyDevice(zx_device_t* parent)
      : MyDeviceType(parent), client_(parent) {}

    static zx_status_t Create(void* ctx, zx_device_t* parent) {
        auto device = std::make_unique<MyDevice>(parent);
        // Usually do init stuff here that might fail.

        auto status = device->DdkAdd("my-device-name");
        if (status == ZX_OK) {
          // Intentionally leak this device because it's owned by the driver framework.
           [[maybe_unused]] auto unused = device.release();
        }
        return status;
    }

    // Methods required by the ddk mixins
    void DdkInit(ddk::InitTxn txn) { txn.Reply(ZX_OK); }
    void DdkUnbind(ddk::UnbindTxn txn) { txn.Reply(); }
    void DdkRelease() { delete this; }
  private:
    ddk::FooProtocolClient function_;
};

static zx_driver_ops_t my_driver_ops = []() -> zx_driver_ops_t {
  zx_driver_ops_t ops{};
  ops.version = DRIVER_OPS_VERSION;
  ops.bind = MyDevice::Create;
  return ops;
}();

ZIRCON_DRIVER(my_device, my_driver_ops, "fuchsia", "0.1");

這個驅動程式庫通常只能由驅動程式代管程序載入,就像驅動程式的程式庫一樣 不需要提供給一般元件使用另外,駕駛人通常 接收家長裝置上的資訊。透過 mock-ddk 程式庫 可透過模擬驅動程式介面載入及呼叫:

TEST(FooDevice, BasicTest) {
  std::shared_ptr<MockDevice> fake_parent = MockDevice::FakeRootParent();
  ASSERT_OK(MyDevice::Create(nullptr, fake_parent.get());
  auto child_dev = fake_parent->GetLatestChild();
  child_dev->InitOp(); // Call the device's Init op
  // Do some testing here

  child_dev->UnbindOp(); // Call the Unbind op if needed
  // The mock-ddk will automatically call DdkRelease() on any remaining children of
  // fake_parent upon destruction.
}

Mock-DDK 總覽

模擬物件只是以 zx_device_t 追蹤、 與模擬驅動程式的主機互動,以及允許通話 安裝到裝置上。沒有全域狀態 - 如果是根為「父項」裝置 在超出範圍的情況下,zx_device_t 的所有元件都會刪除並刪除 使用者的隨附裝置

以下互動模型說明 mock-ddk 與驅動程式庫互動的方式:

圖:互動模型

與駕駛員互動

mock_ddk 會模擬並向驅動程式主機發出可用呼叫。

正在呼叫裝置「
」(裝置作業)
正在呼叫驅動程式主機
(Libdriver API)
透過 MockDevice 呼叫裝置作業。函式的名稱為運算名稱 + Op
範例:
使用 InitOp() 呼叫 init 函式
系統會將 libdriver API 中的所有呼叫記錄在適當的裝置上,但不會採取任何行動。
範例:
如要測試是否已呼叫 device_init_reply(),請呼叫 InitReplyCalled()
;如要等待通話,請呼叫 WaitUntilInitReplyCalled()

生命週期測試範例

auto parent = MockDevice::FakeRootParent();
MyDevice::Create(nullptr, parent.get());
// make sure the child device is there
ASSERT_EQ(1, parent->child_count());
auto* child = parent->GetLatestChild();
// If your device has an init function:
child->InitOp();
// Use this if init replies asynchronously:
EXPECT_EQ(ZX_OK,  child->WaitUntilInitReplyCalled());
// Otherwise, can just verify init replied:
EXPECT_TRUE(child->InitReplyCalled());
// If your device has an unbind function:
child->UnbindOp();
// Use this if unbind replies asynchronously:
EXPECT_EQ(ZX_OK, child->WaitUntilUnbindReplyCalled());
// Otherwise, can just verify init replied:
EXPECT_TRUE(child->UnbindReplyCalled());
// Mock-ddk will release all the devices on destruction, or you can do it manually.

自動解除繫結並釋出

驅動程式主機在釋放驅動程式庫前一律會被解除繫結, 步驟必須在模擬中手動完成 如果接受測試有多個驅動程式,以這種方式將模型自動化 取消繫結和釋放行為Mock DDK 具有以下輔助函式: 用途:

auto parent = MockDevice::FakeRootParent();
MyDevice::Create(nullptr, parent.get());
zx_device_t* child_dev = parent->GetLatestChild();

MyDevice::Create(nullptr, child_dev);
// The state of the tree is now:
//         parent   <--  FakeRootParent
//           |
//        child_dev
//           |
//       grandchild

// You want to remove both test devices, by calling unbind and release in the right order?
device_async_remove(child_dev);

// ReleaseFlaggedDevices performs the unbind and release of any device
// below the input device that has had device_async_remove called on it.
mock_ddk::ReleaseFlaggedDevices(parent.get());

取得裝置背景資訊

mock-ddk 只會處理與裝置相關聯的 zx_device_t。 不過,如果您已指派裝置背景資訊,例如使用 決定要在 ddktl 程式庫中存取對應的 ddk::Device:

  auto fake_parent = MockDevice::FakeRootParent();
  // May not get the device* back from bind:
  ASSERT_OK(MyDevice::Create(nullptr, fake_parent.get());

  // Never fear! Recover device from parent:
  MockDevice* child_dev = fake_parent->GetLatestChild();
  MyDevice* test_dev = child_dev->GetDeviceContext<MyDevice>();

與其他駕駛人互動

您可以為裝置新增部分資訊 (通常是家長),讓 接受測試的裝置可以擷取預期值。

模擬父項通訊協定

在孩童裝置預計推出之前,先將家長通訊協定新增至父項通訊協定 透過呼叫 device_get_protocol() 來存取這些裝置

auto parent = MockDevice::FakeRootParent();
const void* ctx = reinterpret_cast<void*>(0x10),
const void* ops = nullptr,

parent->AddProtocol(8, ops, ctx);

片段通訊協定

複合裝置會從多個父項「片段」取得通訊協定。這代表了 或以名稱做為索引鍵的通訊協定Mock-ddk 可將名稱繫結至通訊協定 表示它來自片段

auto parent = MockDevice::FakeRootParent();
// Mock-ddk uses the same call as adding a
// normal parent protocol:
parent->AddProtocol(ZX_PROTOCOL_GPIO, gpio.GetProto()->ops, gpio.GetProto()->ctx, "fragment-1");
parent->AddProtocol(ZX_PROTOCOL_I2C, i2c.GetProto()->ops, i2c.GetProto()->ctx, "fragment-2");
parent->AddProtocol(ZX_PROTOCOL_CODEC, codec.GetProto()->ops, codec.GetProto()->ctx, "fragment-3");
// gpio, i2c, and codec are device objects with mocked/faked HW interfaces.

模擬 FIDL 連線

如果裝置提供 FIDL 通訊協定,測試可能需要呼叫 fidl 函式。這可能很難,因為 fidl 函式會 做為引數您可以建立用戶端 深入瞭解裝置類別

auto fake_parent = MockDevice::FakeRootParent();
ASSERT_OK(MyDevice::Create(nullptr, fake_parent.get());
MockDevice* child_dev = fake_parent->GetLatestChild();
MyDevice* test_dev = child_dev->GetDeviceContext<MyDevice>();

async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<fidl_proto>();
std::optional<fidl::ServerBindingRef<fidl_proto>> fidl_server;
fidl_server = fidl::BindServer(
    loop.dispatcher(), std::move(endpoints->server), test_dev);
loop.StartThread("thread-name");
fidl::WireSyncClient fidl_client{std::move(endpoints->client)};
// fidl_client can be used synchronously.

模擬中繼資料

您可以將中繼資料新增至受測試裝置的任何祖系, 中繼資料已傳播,以提供給所有子系使用。

auto parent = MockDevice::FakeRootParent();
const char kSource[] = "test";
parent->SetMetadata(kFakeMetadataType, kSource, sizeof(kSource));

載入韌體

載入韌體已淘汰,但該函式已納入 仍需注意的驅動程式:

auto fake_parent = MockDevice::FakeRootParent();
ASSERT_OK(MyDevice::Create(nullptr, fake_parent.get());
MockDevice* child_dev = fake_parent->GetLatestChild();
MyDevice* test_dev = child_dev->GetDeviceContext<MyDevice>();
constexpr std::string_view kFirmwarePath = "test path";
std::vector<uint8_t> kFirmware(200, 42);
child_dev->SetFirmware(kFirmware, kFirmwarePath);
EXPECT_TRUE(test_dev->LoadFirmware(kFirmwarePath).is_ok());

常見問題

  • 未呼叫 Init/取消繫結
    • 使用 MockDevice::InitOp() 呼叫 Init
    • 使用 MockDevice::UnbindOp() 呼叫解除繫結,或呼叫 device_async_remove() 並呼叫 mock_ddk::ReleaseFlaggedDevices
  • 直接刪除裝置
    • 解決方案:呼叫 DdkAdd() 後,從目前的範圍釋出裝置