| // 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 <zircon/status.h> |
| #include <zircon/syscalls.h> |
| |
| #include <memory> |
| |
| #include <fbl/unique_fd.h> |
| #include <gtest/gtest.h> |
| |
| #include "integration-test.h" |
| |
| namespace libdriver_integration_test { |
| |
| class BasicLifecycleTest : public IntegrationTest {}; |
| |
| // This test checks what happens when a driver returns an error from bind. |
| TEST_F(BasicLifecycleTest, BindError) { |
| std::unique_ptr<RootMockDevice> root_mock_device; |
| |
| auto promise = |
| ExpectBind(&root_mock_device, [](HookInvocation record, Completer<void> completer) { |
| completer.complete_ok(); |
| ActionList actions; |
| actions.AppendReturnStatus(ZX_ERR_NOT_SUPPORTED); |
| return actions; |
| }); |
| RunPromise(std::move(promise)); |
| } |
| |
| // This test confirms that after a device has been added: |
| // 1) When it's parent is removed, the device receives its unbind() callback. |
| // 2) If the device calls device_remove() in the unbind() callback, its |
| // release() callback gets called later. |
| TEST_F(BasicLifecycleTest, BindThenUnbindAndRemove) { |
| std::unique_ptr<RootMockDevice> root_mock_device; |
| std::unique_ptr<MockDevice> mock_child_device; |
| |
| auto promise = |
| CreateFirstChild(&root_mock_device, &mock_child_device).and_then([&]() -> Promise<void> { |
| // Destroy the test device. This should cause an unbind of the child |
| // device. |
| root_mock_device.reset(); |
| return ExpectUnbindThenRelease(mock_child_device); |
| }); |
| |
| RunPromise(std::move(promise)); |
| } |
| |
| // This test confirms that after a device has been added: |
| // 1) We can open it via devfs, and its open() hook gets called. |
| // 2) We can remove the device via device_async_remove() and its unbind() hook gets called |
| // 3) We can close the opened connection, and its close() and then release() hook gets called. |
| TEST_F(BasicLifecycleTest, BindThenOpenRemoveAndClose) { |
| std::unique_ptr<RootMockDevice> root_mock_device; |
| std::unique_ptr<MockDevice> mock_child_device; |
| fidl::InterfacePtr<fuchsia::io::Node> client; |
| |
| auto promise = |
| CreateFirstChild(&root_mock_device, &mock_child_device) |
| .and_then([&]() { return DoWaitForPath(mock_child_device->path()); }) |
| .and_then([&]() { |
| // Do the open and wait for acknowledgement that it was successful. |
| auto wait_for_open = DoOpen(mock_child_device->path(), &client); |
| auto expect_open = |
| ExpectOpen(mock_child_device, |
| [](HookInvocation record, uint32_t flags, Completer<void> completer) { |
| completer.complete_ok(); |
| ActionList actions; |
| // Request the child device be removed. |
| actions.AppendAsyncRemoveDevice(); |
| actions.AppendReturnStatus(ZX_OK); |
| return actions; |
| }); |
| auto expect_unbind = ExpectUnbind(mock_child_device, |
| [](HookInvocation record, Completer<void> completer) { |
| ActionList actions; |
| actions.AppendUnbindReply(std::move(completer)); |
| return actions; |
| }); |
| return expect_open.and_then(std::move(expect_unbind)) |
| .and_then(std::move(wait_for_open)); |
| }) |
| .and_then([&]() { |
| // Close the newly opened connection |
| client.Unbind(); |
| return ExpectClose(mock_child_device, [](HookInvocation record, uint32_t flags, |
| Completer<void> completer) { |
| completer.complete_ok(); |
| ActionList actions; |
| actions.AppendReturnStatus(ZX_OK); |
| return actions; |
| }); |
| }) |
| .and_then([&]() -> Promise<void> { |
| // Since DdkAsyncRemove() has been called and all connections have been closed, |
| // the device should be released. |
| return ExpectRelease(mock_child_device); |
| }); |
| |
| RunPromise(std::move(promise)); |
| } |
| |
| // This test confirms that after a device has been added: |
| // 1) We can open it via devfs, and its open() hook gets called. |
| // 2) We can close the opened connection, and its close() hook gets called. |
| // 3) Invoking device_remove causes the release hook to run. |
| TEST_F(BasicLifecycleTest, BindThenOpenCloseAndRemove) { |
| std::unique_ptr<RootMockDevice> root_mock_device; |
| std::unique_ptr<MockDevice> mock_child_device; |
| fidl::InterfacePtr<fuchsia::io::Node> client; |
| |
| auto promise = |
| CreateFirstChild(&root_mock_device, &mock_child_device) |
| .and_then([&]() { return DoWaitForPath(mock_child_device->path()); }) |
| .and_then([&]() { |
| // Do the open and wait for acknowledgement that it was successful. |
| auto wait_for_open = DoOpen(mock_child_device->path(), &client); |
| auto expect_open = |
| ExpectOpen(mock_child_device, |
| [](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([&]() { |
| // Close the newly opened connection |
| client.Unbind(); |
| return ExpectClose(mock_child_device, [](HookInvocation record, uint32_t flags, |
| Completer<void> completer) { |
| completer.complete_ok(); |
| ActionList actions; |
| actions.AppendReturnStatus(ZX_OK); |
| return actions; |
| }); |
| }) |
| .and_then([&]() -> Promise<void> { |
| // Destroy the test device. This should cause an unbind of the child |
| // device. |
| root_mock_device.reset(); |
| return ExpectUnbindThenRelease(mock_child_device); |
| }); |
| |
| RunPromise(std::move(promise)); |
| } |
| |
| // This test confirms that after a device has been added and opened, it won't be |
| // released until after its been closed. |
| TEST_F(BasicLifecycleTest, BindThenOpenRemoveThenClose) { |
| std::unique_ptr<RootMockDevice> root_mock_device; |
| std::unique_ptr<MockDevice> mock_child_device; |
| fidl::InterfacePtr<fuchsia::io::Node> client; |
| |
| auto promise = |
| CreateFirstChild(&root_mock_device, &mock_child_device) |
| .and_then([&]() { return DoWaitForPath(mock_child_device->path()); }) |
| .and_then([&]() { |
| // Do the open and wait for acknowledgement that it was successful. |
| auto wait_for_open = DoOpen(mock_child_device->path(), &client); |
| auto expect_open = |
| ExpectOpen(mock_child_device, |
| [](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> { |
| // Destroy the test device. This should cause an unbind of the child |
| // device. |
| root_mock_device.reset(); |
| return ExpectUnbind(mock_child_device, |
| [](HookInvocation record, Completer<void> completer) { |
| ActionList actions; |
| actions.AppendUnbindReply(std::move(completer)); |
| return actions; |
| }); |
| }) |
| .and_then([&]() { |
| // Close the newly opened connection. Release shouldn't be able to |
| // happen until then. |
| client.Unbind(); |
| return ExpectClose(mock_child_device, [](HookInvocation record, uint32_t flags, |
| Completer<void> completer) { |
| completer.complete_ok(); |
| ActionList actions; |
| actions.AppendReturnStatus(ZX_OK); |
| return actions; |
| }); |
| }) |
| .and_then(ExpectRelease(mock_child_device)); |
| |
| RunPromise(std::move(promise)); |
| } |
| |
| class NeverOpenOrClose : public MockDeviceHooks { |
| public: |
| NeverOpenOrClose() : MockDeviceHooks(Completer()) {} |
| |
| void Open(HookInvocation record, uint32_t flags, OpenCallback callback) override { |
| ZX_PANIC("open should never be called"); |
| } |
| void Close(HookInvocation record, uint32_t flags, CloseCallback callback) override { |
| ZX_PANIC("close should never be called"); |
| } |
| }; |
| |
| TEST_F(BasicLifecycleTest, StatDoesntCallOpen) { |
| std::unique_ptr<RootMockDevice> root_mock_device; |
| std::unique_ptr<MockDevice> mock_child_device; |
| fidl::InterfacePtr<fuchsia::io::Node> client; |
| |
| auto promise = CreateFirstChild(&root_mock_device, &mock_child_device) |
| .and_then([&]() { return DoWaitForPath(mock_child_device->path()); }) |
| .and_then([&]() { |
| mock_child_device->set_hooks(std::make_unique<NeverOpenOrClose>()); |
| return DoOpen(mock_child_device->path(), &client, |
| fuchsia::io::OPEN_FLAG_NODE_REFERENCE); |
| }) |
| .and_then([&]() { client.Unbind(); }); |
| |
| RunPromise(std::move(promise)); |
| } |
| |
| } // namespace libdriver_integration_test |