blob: b04dca5055a27abd87dcc2c4871c963ca5ef657d [file] [log] [blame]
// 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