| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fuchsia/device/cpp/fidl.h> |
| #include <fuchsia/device/test/cpp/fidl.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| |
| #include <memory> |
| |
| #include <ddk/platform-defs.h> |
| #include <fbl/unique_fd.h> |
| #include <gtest/gtest.h> |
| |
| #include "integration-test.h" |
| |
| namespace libdriver_integration_test { |
| |
| class CompositeDeviceTest : public IntegrationTest { |
| public: |
| static void SetUpTestCase() { DoSetup(true /* should_create_composite */); } |
| |
| protected: |
| // Create the fragments for the well-known composite that the mock sysdev creates. |
| Promise<void> CreateFragmentDevices(std::unique_ptr<RootMockDevice>* root_device, |
| std::unique_ptr<MockDevice>* child1_device, |
| std::unique_ptr<MockDevice>* child2_device) { |
| fit::bridge<void, Error> child1_bridge; |
| fit::bridge<void, Error> child2_bridge; |
| return ExpectBind(root_device, |
| [=, child1_completer = std::move(child1_bridge.completer), |
| child2_completer = std::move(child2_bridge.completer)]( |
| HookInvocation record, Completer<void> completer) mutable { |
| std::vector<zx_device_prop_t> child1_props({ |
| {BIND_PLATFORM_DEV_VID, 0, PDEV_VID_TEST}, |
| {BIND_PLATFORM_DEV_PID, 0, PDEV_PID_LIBDRIVER_TEST}, |
| {BIND_PLATFORM_DEV_DID, 0, PDEV_DID_TEST_CHILD_1}, |
| }); |
| std::vector<zx_device_prop_t> child2_props({ |
| {BIND_PLATFORM_DEV_VID, 0, PDEV_VID_TEST}, |
| {BIND_PLATFORM_DEV_PID, 0, PDEV_PID_LIBDRIVER_TEST}, |
| {BIND_PLATFORM_DEV_DID, 0, PDEV_DID_TEST_CHILD_2}, |
| }); |
| ActionList actions; |
| actions.AppendAddMockDevice(loop_.dispatcher(), (*root_device)->path(), |
| "fragment1", std::move(child1_props), ZX_OK, |
| std::move(child1_completer), child1_device); |
| actions.AppendAddMockDevice(loop_.dispatcher(), (*root_device)->path(), |
| "fragment2", std::move(child2_props), ZX_OK, |
| std::move(child2_completer), child2_device); |
| actions.AppendReturnStatus(ZX_OK); |
| |
| (*child1_device)->set_hooks(std::make_unique<IgnoreGetProtocol>()); |
| (*child2_device)->set_hooks(std::make_unique<IgnoreGetProtocol>()); |
| completer.complete_ok(); |
| return actions; |
| }) |
| .and_then(child1_bridge.consumer.promise_or(::fit::error("child1 create abandoned"))) |
| .and_then(child2_bridge.consumer.promise_or(::fit::error("child2 create abandoned"))); |
| } |
| }; |
| |
| // This test creates two devices that match the well-known composite in the |
| // test sysdev driver. It then waits for it to appear in devfs. |
| TEST_F(CompositeDeviceTest, CreateTest) { |
| std::unique_ptr<RootMockDevice> root_device; |
| std::unique_ptr<MockDevice> child_device1, child_device2; |
| fidl::InterfacePtr<fuchsia::io::Node> client; |
| |
| auto promise = CreateFragmentDevices(&root_device, &child_device1, &child_device2) |
| .and_then(DoWaitForPath("composite")) |
| .and_then([&]() -> Promise<void> { return DoOpen("composite", &client); }) |
| .and_then([&]() -> Promise<void> { |
| // Destroy the test device. This should cause an unbind of the child |
| // device. |
| root_device.reset(); |
| return JoinPromises(ExpectUnbindThenRelease(child_device1), |
| ExpectUnbindThenRelease(child_device2)); |
| }); |
| |
| RunPromise(std::move(promise)); |
| } |
| |
| // TODO(fxbug.dev/8452): Re-enable once flake is fixed. |
| // |
| // This test creates the well-known composite, and force binds a test driver |
| // stack to the composite. It then forces one of the fragments to unbind. |
| // It verifies that the composite mock-device's unbind hook is called. |
| TEST_F(CompositeDeviceTest, DISABLED_UnbindFragment) { |
| std::unique_ptr<RootMockDevice> root_device, composite_mock; |
| std::unique_ptr<MockDevice> child_device1, child_device2, composite_child_device; |
| fidl::InterfacePtr<fuchsia::io::Node> client; |
| fidl::InterfacePtr<fuchsia::device::Controller> composite, child1_controller; |
| fidl::SynchronousInterfacePtr<fuchsia::device::test::RootDevice> composite_test; |
| |
| auto promise = |
| CreateFragmentDevices(&root_device, &child_device1, &child_device2) |
| .and_then(DoWaitForPath("composite")) |
| .and_then([&]() -> Promise<void> { return DoWaitForPath("composite/test"); }) |
| .and_then([&]() -> Promise<void> { return DoOpen("composite/test", &client); }) |
| .and_then([&]() -> Promise<void> { |
| composite_test.Bind(client.Unbind().TakeChannel()); |
| |
| auto bind_callback = [&composite_mock, &composite_child_device]( |
| HookInvocation record, Completer<void> completer) { |
| // Create a test child that we can monitor for hooks. |
| ActionList actions; |
| actions.AppendAddMockDevice(loop_.dispatcher(), composite_mock->path(), "child", |
| std::vector<zx_device_prop_t>{}, ZX_OK, |
| std::move(completer), &composite_child_device); |
| actions.AppendReturnStatus(ZX_OK); |
| return actions; |
| }; |
| |
| fit::bridge<void, Error> bridge; |
| auto bind_hook = |
| std::make_unique<BindOnce>(std::move(bridge.completer), std::move(bind_callback)); |
| // Bind the mock device driver to a new child |
| zx_status_t status = RootMockDevice::CreateFromTestRoot( |
| devmgr_, loop_.dispatcher(), std::move(composite_test), std::move(bind_hook), |
| &composite_mock); |
| PROMISE_ASSERT(ASSERT_EQ(status, ZX_OK)); |
| |
| return bridge.consumer.promise_or(::fit::error("bind abandoned")); |
| }) |
| .and_then([&]() -> Promise<void> { |
| // Open up child1, so we can send it an unbind request |
| auto wait_for_open = DoOpen(child_device1->path(), &client); |
| auto expect_open = ExpectOpen(child_device1, [](HookInvocation record, uint32_t flags, |
| Completer<void> completer) { |
| completer.complete_ok(); |
| ActionList actions; |
| actions.AppendReturnStatus(ZX_OK); |
| return actions; |
| }); |
| return expect_open.and_then(std::move(wait_for_open)); |
| }) |
| .and_then([&]() -> Promise<void> { |
| // Send the unbind request to child1 |
| zx_status_t status = |
| child1_controller.Bind(client.Unbind().TakeChannel(), loop_.dispatcher()); |
| PROMISE_ASSERT(ASSERT_EQ(status, ZX_OK)); |
| |
| fit::bridge<void, Error> bridge; |
| child1_controller->ScheduleUnbind( |
| [completer = std::move(bridge.completer)]( |
| fuchsia::device::Controller_ScheduleUnbind_Result result) mutable { |
| if (result.is_response()) { |
| completer.complete_ok(); |
| } else { |
| completer.complete_error(std::string("unbind failed")); |
| } |
| }); |
| |
| // We should receive the unbind for child1, and then soon after for |
| // the composite. |
| auto unbind_promise = |
| ExpectUnbind(child_device1, [](HookInvocation record, Completer<void> completer) { |
| ActionList actions; |
| // We don't care about when the unbind reply actually finishes. |
| // The ExpectRelease below will serialize against it anyway. |
| Promise<void> unbind_reply_done; |
| actions.AppendUnbindReply(&unbind_reply_done); |
| // Complete here instead of in remove device, since the remove |
| // device completion doesn't fire until after we're notified, |
| // which might be after the unbind of the composite begins |
| completer.complete_ok(); |
| return actions; |
| }).and_then(ExpectUnbindThenRelease(composite_child_device)); |
| |
| return unbind_promise.and_then( |
| bridge.consumer.promise_or(::fit::error("Unbind abandoned"))); |
| }) |
| .and_then([&]() -> Promise<void> { |
| child1_controller.Unbind(); |
| return ExpectClose(child_device1, [](HookInvocation record, uint32_t flags, |
| Completer<void> completer) { |
| completer.complete_ok(); |
| ActionList actions; |
| actions.AppendReturnStatus(ZX_OK); |
| return actions; |
| }); |
| }) |
| .and_then(ExpectRelease(child_device1)) |
| .and_then([&]() -> Promise<void> { |
| // Destroy the test device. This should cause an unbind of the last child |
| // device. |
| root_device.reset(); |
| return ExpectUnbindThenRelease(child_device2); |
| }); |
| |
| RunPromise(std::move(promise)); |
| } |
| |
| } // namespace libdriver_integration_test |