blob: 16a886c56dd565be2ac15309a9cb580d04e49829 [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 <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